diff --git a/custom_components/alarmo/alarm_control_panel.py b/custom_components/alarmo/alarm_control_panel.py index 416eae0..16a4a4b 100755 --- a/custom_components/alarmo/alarm_control_panel.py +++ b/custom_components/alarmo/alarm_control_panel.py @@ -424,7 +424,7 @@ def async_handle_arm_request(self, arm_mode, **kwargs): _LOGGER.warning("Wrong code provided.") if self.open_sensors: self.open_sensors = None - self.async_write_ha_state() + self.schedule_update_ha_state() return False elif info and info[const.ATTR_IS_OVERRIDE_CODE]: bypass_open_sensors = True @@ -547,7 +547,7 @@ def async_update_config(area_id: str = None): coordinator = self.hass.data[const.DOMAIN]["coordinator"] self._config = coordinator.store.async_get_config() self._config.update(coordinator.store.async_get_area(self.area_id)) - self.async_write_ha_state() + self.schedule_update_ha_state() self.async_on_remove( async_dispatcher_connect(self.hass, "alarmo_config_updated", async_update_config) @@ -583,8 +583,9 @@ def async_update_state(self, state: str = None): _LOGGER.debug("entity {} was updated from {} to {}".format(self.entity_id, old_state, state)) - if self._timer: - self._timer() + if state in const.ARM_MODES + [STATE_ALARM_DISARMED]: + # cancel a running timer that possibly running when transitioning from states arming, pending, triggered + self.async_clear_timer() if self.state not in [STATE_ALARM_ARMING, STATE_ALARM_PENDING]: self.delay = None @@ -597,7 +598,7 @@ def async_update_state(self, state: str = None): dispatcher_send(self.hass, "alarmo_state_updated", self.area_id, old_state, state) - self.async_write_ha_state() + self.schedule_update_ha_state() def async_arm_failure(self, open_sensors: dict, context_id=None): """handle arm failure.""" @@ -611,7 +612,7 @@ def async_arm_failure(self, open_sensors: dict, context_id=None): if self._revert_state in const.ARM_MODES: self._arm_mode = self._revert_state - self.async_write_ha_state() + self.schedule_update_ha_state() dispatcher_send( self.hass, @@ -692,6 +693,21 @@ def async_arm(self, arm_mode, **kwargs): else: # proceed the arm _LOGGER.info("Alarm is now arming. Waiting for {} seconds.".format(exit_delay)) + + @callback + def async_leave_timer_finished(now): + """Update state at a scheduled point in time.""" + _LOGGER.debug("async_leave_timer_finished") + self.async_clear_timer() + self.async_arm( + self.arm_mode, + bypass_open_sensors=bypass_open_sensors, + skip_delay=True + ) + self.async_set_timer(exit_delay, async_leave_timer_finished) + self.delay = exit_delay + self.open_sensors = None + dispatcher_send( self.hass, "alarmo_event", @@ -703,21 +719,8 @@ def async_arm(self, arm_mode, **kwargs): const.ATTR_CONTEXT_ID: context_id } ) - self.delay = exit_delay - self.open_sensors = None self.async_update_state(STATE_ALARM_ARMING) - @callback - def async_leave_timer_finished(now): - """Update state at a scheduled point in time.""" - _LOGGER.debug("async_leave_timer_finished") - self.async_arm( - self.arm_mode, - bypass_open_sensors=bypass_open_sensors, - skip_delay=True - ) - - self.async_set_timer(exit_delay, async_leave_timer_finished) return True @callback @@ -751,7 +754,6 @@ def async_trigger(self, skip_delay: bool = False, open_sensors: dict = None): if not entry_delay: # countdown finished or immediate trigger event - self.async_update_state(STATE_ALARM_TRIGGERED) if trigger_time: # there is a max. trigger time configured @@ -759,8 +761,10 @@ def async_trigger(self, skip_delay: bool = False, open_sensors: dict = None): @callback def async_trigger_timer_finished(now): """Update state at a scheduled point in time.""" + _LOGGER.debug("async_trigger_timer_finished") self._changed_by = None + self.async_clear_timer() if self._config[const.ATTR_DISARM_AFTER_TRIGGER] or not self.arm_mode: self.bypassed_sensors = None self.async_update_state(STATE_ALARM_DISARMED) @@ -777,26 +781,37 @@ def async_trigger_timer_finished(now): ) self.async_set_timer(trigger_time, async_trigger_timer_finished) + else: + # clear previous timer when transitioning from pending state + self.async_clear_timer() + _LOGGER.info("Alarm is triggered!") + self.async_update_state(STATE_ALARM_TRIGGERED) else: # to pending state - self.delay = entry_delay - - self.async_update_state(STATE_ALARM_PENDING) @callback def async_entry_timer_finished(now): """Update state at a scheduled point in time.""" + self.async_clear_timer() _LOGGER.debug("async_entry_timer_finished") self.async_trigger() self.async_set_timer(entry_delay, async_entry_timer_finished) + self.delay = entry_delay _LOGGER.info("Alarm will be triggered after {} seconds.".format(entry_delay)) - def async_set_timer(self, delay, cb_func): + self.async_update_state(STATE_ALARM_PENDING) + + def async_clear_timer(self): + """clear a running timer.""" if self._timer: self._timer() + self._timer = None + + def async_set_timer(self, delay, cb_func): + self.async_clear_timer() now = dt_util.utcnow() if not isinstance(delay, datetime.timedelta): @@ -852,7 +867,7 @@ def async_update_config(area_id=None): self._config = coordinator.store.async_get_config() self.async_update_state() - self.async_write_ha_state() + self.schedule_update_ha_state() self.async_on_remove( async_dispatcher_connect(self.hass, "alarmo_config_updated", async_update_config) @@ -998,7 +1013,7 @@ def async_update_state(self, state: str = None): self.update_ready_to_arm_modes() - self.async_write_ha_state() + self.schedule_update_ha_state() @callback def async_alarm_disarm(self, **kwargs): @@ -1080,7 +1095,7 @@ def async_arm_failure(self, open_sensors: dict, context_id=None): const.ATTR_CONTEXT_ID: context_id } ) - self.async_write_ha_state() + self.schedule_update_ha_state() @callback def async_trigger(self, skip_delay: bool = False, _open_sensors: dict = None):