def _handle_suppress_state(self, entity, attribute, old, new, kwargs): for i in range(0, len(self._suppress_condition)): condition = self._suppress_condition[i] if conditions.evaluate_condition( self, self.datetime(), [condition], triggers={entity: new}): self.log('Suppress condition evaluates true: %s (%s: %s->%s)' % ( condition, entity, old, new)) self._suppress_evaluation_times[i] = self.datetime()
def _get_best_matching_output(self, triggers=None): for output in self._config.get(CONF_OUTPUT): if conditions.evaluate_condition(self, self.datetime(), output.get(CONF_CONDITION), triggers=triggers): return output return None
def _get_matching_outputs(self, event) -> list: event_tags = event.get(scc.CONF_TAGS) matches = [] current_datetime = self._app.datetime() for output in self._config.get(scc.CONF_OUTPUTS): if scc.CONF_CONDITION in output and not conditions.evaluate_condition( self._app, current_datetime, output[scc.CONF_CONDITION], evaluators=self._output_evaluators, event=event): continue matches.append(output) return matches
def _trigger(self, kwargs=None): self.log('Evaluating whether or not to fire an event...') self._trigger_callback = None reference = kwargs[KEY_REFERENCE] for i in range(0, len(self._suppress_condition)): condition = self._suppress_condition[i] eval_time = self._suppress_evaluation_times[i] if eval_time: seconds_since_suppress = int((reference - eval_time).total_seconds()) if seconds_since_suppress <= self._window_seconds: self.log('Suppress condition \'%s\' triggered too recently (%i secs ' 'ago), skipping...' % (condition, seconds_since_suppress)) return False for i in range(0, len(self._trigger_condition)): condition = self._trigger_condition[i] eval_time = self._trigger_evaluation_times[i] if not eval_time: self.log('Trigger condition \'%s\' has not yet triggered, skipping ...' % condition) return False seconds_since_trigger = int((reference - eval_time).total_seconds()) if seconds_since_trigger > self._window_seconds: self.log('Trigger condition \'%s\' triggered too long ago (%i secs ago), ' 'skipping...' % (condition, seconds_since_trigger)) return False if self._disable_condition and conditions.evaluate_condition( self, self.datetime(), self._disable_condition): self.log('Disable condition \'%s\' evalutes true, skipping...' % self._disable_condition) return False if self._last_overall_trigger is not None: seconds_since_overall_trigger = int((self.datetime() - self._last_overall_trigger).total_seconds()) if seconds_since_overall_trigger < self._reset_seconds: self.log('Overall trigger was too recent (%i secs ago), ' 'skipping...' % seconds_since_overall_trigger) return False self._last_overall_trigger = self.datetime() self.fire_event(self._event, **self._event_data) self.log('Triggered! Fired event: \'%s\' with data \'%s\'' % ( self._event, self._event_data))
def _handle_trigger_state(self, entity, attribute, old, new, kwargs): schedule_evaluation = False for i in range(0, len(self._trigger_condition)): condition = self._trigger_condition[i] if conditions.evaluate_condition( self, self.datetime(), [condition], triggers={entity: new}): self.log('Trigger condition evaluates true: %s (%s: %s->%s)' % ( condition, entity, old, new)) self._trigger_evaluation_times[i] = self.datetime() schedule_evaluation = True if schedule_evaluation and not self._trigger_callback: # Keep the time we expect the timeout to fire, and use that as the # reference time to avoid appdaemon scheduling delays breaking the time # comparisons. kwargs = {} kwargs[KEY_REFERENCE] = self.datetime() + datetime.timedelta( seconds=self._window_seconds) self.log('Scheduling trigger evaluation in %i second(s)...' % self._window_seconds) self._trigger_callback = self.run_in( self._trigger, self._window_seconds, **kwargs)
def _trigger_callback(self, entity, attribute, old, new, kwargs): activate = kwargs[KEY_ACTIVATE] self.log('Trigger callback (activate=%s): %s (old: %s, new: %s)' % (activate, entity, old, new)) if old == 'unavailable': self.log( 'Unavailable: Skipping previously unavailable state for: %s' % entity) return if self._is_disabled(): self.log('Disabled: Skipping trigger for: %s' % entity) return elif self._pause_timer: self.log('Paused: Skipping trigger for: %s' % entity) return elif self._manual_mode: self.log('Manual mode: Skipping trigger for: %s' % entity) return triggers = {entity: new} condition = self._config.get( CONF_TRIGGER_ACTIVATE_CONDITION if activate else CONF_TRIGGER_DEACTIVATE_CONDITION) triggered = conditions.evaluate_condition(self, self.datetime(), condition, triggers=triggers) activate_key = KEY_ACTIVATE if activate else KEY_DEACTIVATE if triggered: output = self._get_best_matching_output(triggers=triggers) if output: # Prune last actions list. self._prune_last_actions() self.log('Last-actions: %s' % self._last_actions) # Safety precaution: Pause changes if more distinct actions than # max_actions_per_min (avoid lights flapping due to more configuration # choices). (e.g. imagine a trigger than turns lights on when # brightness dips below X, but turns them off when it rises above X: a # poorly configured instance could cause the lights to flap) # Implicitly, this is allowing multiple repitions of the same action # with no pauseing (e.g. repeatedly turning on the same light due to # walking past multiple motion sensors is just fine). max_actions_per_min = self._config.get( CONF_MAX_ACTIONS_PER_MIN) if self._opposing_last_actions() >= max_actions_per_min: self.log( 'Pausing attempts to %s output as >%i (%s) distinct ' 'actions have been executed in the last minute: %s' % (activate_key, max_actions_per_min, CONF_MAX_ACTIONS_PER_MIN, output)) # Pause for 1 minute (it's max actions per minute). self._pause_timer.create(seconds=1 * 60) self._update_status() return # If this would just activate the exact same output, just reset # the timer rather than re-activating (as otherwise we lose custom # adjustments made to the lighting). if (activate and self._main_timer and self._last_actions and self._last_actions[0][1] == activate and self._last_actions[0][2] == output): self.log('Same output triggered by %s. ' 'Resetting timer only.' % entity) self._main_timer.create(self._get_soft_timeout()) else: if activate: self._main_timer.create(self._get_soft_timeout()) else: self._main_timer.cancel() self._activate(output, activate=activate) self._last_trigger[activate_key] = self.get_state( entity, attribute=KEY_FRIENDLY_NAME) self._update_status()
def _is_disabled(self): return (self._config.get(CONF_DISABLE_CONDITION) and conditions.evaluate_condition( self, self.datetime(), self._config.get(CONF_DISABLE_CONDITION)))
def _should_extend(self): return (self._config.get(CONF_EXTEND_CONDITION) and conditions.evaluate_condition( self, self.datetime(), self._config.get(CONF_EXTEND_CONDITION)))