126 files

This commit is contained in:
Home Assistant Version Control
2026-08-10 04:41:24 +00:00
parent a7eec430e8
commit df4e74f056
126 changed files with 1522 additions and 9695 deletions
+5 -3
View File
@@ -430,6 +430,11 @@ class AlarmoCoordinator(DataUpdateCoordinator):
)
return
if action == const.EVENT_ACTION_DISARM:
_LOGGER.info("Received request for disarming")
await alarm_entity.async_alarm_disarm(None, skip_code=True)
return
arm_mode = (
alarm_entity._revert_state
if alarm_entity._revert_state in const.ARM_MODES
@@ -452,9 +457,6 @@ class AlarmoCoordinator(DataUpdateCoordinator):
elif action == const.EVENT_ACTION_RETRY_ARM:
_LOGGER.info("Received request for retry arming")
await alarm_entity.async_handle_arm_request(arm_mode, skip_code=True)
elif action == const.EVENT_ACTION_DISARM:
_LOGGER.info("Received request for disarming")
await alarm_entity.async_alarm_disarm(None, skip_code=True)
else:
_LOGGER.info(
"Received request for arming with mode %s",
@@ -323,8 +323,11 @@ class AlarmoBaseEntity(AlarmControlPanelEntity, RestoreEntity):
async def _validate_code(self, code, to_state): # noqa PLR0911
"""Validate code and user permissions for a requested state change.
Returns a (success, error_event) tuple. When success is True,
error_event is None.
Returns a (success, info) tuple.
When validation is successful, success is True, otherwise False.
When success is True, info is the user data
(or None if no user/code was needed).
When success is False, info is the error event.
"""
# check bypass rules
if (
@@ -404,7 +407,7 @@ class AlarmoBaseEntity(AlarmControlPanelEntity, RestoreEntity):
# success
self._changed_by = user[ATTR_NAME]
return True, None
return True, user
async def async_service_disarm_handler(self, code, context_id=None):
"""Handle external disarm request from alarmo.disarm service."""
+2
View File
@@ -44,6 +44,8 @@ def validate_area(trigger, area_id, hass):
return False
elif trigger[const.ATTR_AREA]:
return trigger[const.ATTR_AREA] == area_id
elif area_id and hass.data[const.DOMAIN].get("master"):
return False
elif len(hass.data[const.DOMAIN]["areas"]) == 1:
return True
else:
+1 -1
View File
@@ -15,7 +15,7 @@ from homeassistant.components.alarm_control_panel import (
AlarmControlPanelEntityFeature,
)
VERSION = "1.10.18"
VERSION = "1.10.19"
NAME = "Alarmo"
MANUFACTURER = "@nielsfaber"
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -17,5 +17,5 @@
"iot_class": "local_push",
"issue_tracker": "https://github.com/nielsfaber/alarmo/issues",
"requirements": [],
"version": "1.10.18"
"version": "1.10.19"
}
+38 -13
View File
@@ -130,7 +130,11 @@ class SensorHandler:
def __init__(self, hass: HomeAssistant):
"""Initialize the sensor handler."""
self._config = None
# The sensor config is only loaded once HA has finished starting (see
# _setup_sensor_listeners below). Until then this must be an empty dict
# rather than None: restoring a persisted alarm state can call into
# active_sensors_for_alarm_state() before that point.
self._config = {}
self.hass = hass
self._state_listener = None
self._subscriptions = []
@@ -144,9 +148,10 @@ class SensorHandler:
@callback
def async_update_sensor_config():
"""Sensor config updated, reload the configuration."""
self._config = self.hass.data[const.DOMAIN][
"coordinator"
].store.async_get_sensors()
self._config = (
self.hass.data[const.DOMAIN]["coordinator"].store.async_get_sensors()
or {}
)
self._groups = self.hass.data[const.DOMAIN][
"coordinator"
].store.async_get_sensor_groups()
@@ -377,15 +382,28 @@ class SensorHandler:
new_state = parse_sensor_state(event.data["new_state"])
sensor_config = self._config[entity]
if old_state == STATE_UNKNOWN:
# sensor is unknown at startup,
# state which comes after is considered as initial state
_LOGGER.debug(
"Initial state for %s is %s",
entity,
new_state,
)
self.update_ready_to_arm_status(sensor_config["area"])
return
if new_state not in (STATE_OPEN, STATE_UNAVAILABLE) or (
sensor_config[ATTR_ALLOW_OPEN] and new_state == STATE_OPEN
):
# transition to a safe state, or to open while the sensor is
# allowed to be open — treat as initial state
_LOGGER.debug(
"Initial state for %s is %s",
entity,
new_state,
)
self.update_ready_to_arm_status(sensor_config["area"])
return
else:
# transition to a violation state — do not treat as initial,
# proceed through normal trigger evaluation
_LOGGER.debug(
"Sensor %s recovered from unknown to %s while alarm is %s, "
"evaluating as live state change",
entity,
new_state,
self.hass.data[const.DOMAIN]["areas"][sensor_config["area"]].state,
)
if old_state == new_state:
# not a state change - ignore
return
@@ -740,6 +758,13 @@ class SensorHandler:
# Skip unknown sensors - they'll be handled when they become known
continue
if sensor_config[ATTR_ALLOW_OPEN] and sensor_state == STATE_OPEN:
_LOGGER.debug(
"Sensor %s is open with allow_open, skipping startup eval",
entity_id,
)
continue
# Check if sensor state is allowed in current alarm state
res = sensor_state_allowed(sensor_state, sensor_config, alarm_entity.state)
+4 -4
View File
@@ -381,10 +381,10 @@ class AlarmoStorage:
for area in data["areas"]:
modes = {
mode: ModeEntry(
enabled=config["enabled"],
exit_time=config["exit_time"],
entry_time=config["entry_time"],
trigger_time=config["trigger_time"],
enabled=config.get("enabled", False),
exit_time=config.get("exit_time", None),
entry_time=config.get("entry_time", None),
trigger_time=config.get("trigger_time", None),
)
for (mode, config) in area["modes"].items()
}