def trigger(hass, config, action): """Listen for state changes based on configuration.""" value_template = config.get(CONF_VALUE_TEMPLATE) # Local variable to keep track of if the action has already been triggered already_triggered = False def state_changed_listener(entity_id, from_s, to_s): """Listen for state changes and calls action.""" nonlocal already_triggered template_result = condition.template(hass, value_template) # Check to see if template returns true if template_result and not already_triggered: already_triggered = True action({ 'trigger': { 'platform': 'template', 'entity_id': entity_id, 'from_state': from_s, 'to_state': to_s, }, }) elif not template_result: already_triggered = False track_state_change(hass, MATCH_ALL, state_changed_listener) return True
def trigger(hass, config, action): """Listen for state changes based on configuration.""" entity_id = config.get(CONF_ENTITY_ID) from_state = config.get(CONF_FROM, MATCH_ALL) to_state = config.get(CONF_TO) or config.get(CONF_STATE) or MATCH_ALL time_delta = config.get(CONF_FOR) def state_automation_listener(entity, from_s, to_s): """Listen for state changes and calls action.""" def call_action(): """Call action with right context.""" action({ 'trigger': { 'platform': 'state', 'entity_id': entity, 'from_state': from_s, 'to_state': to_s, 'for': time_delta, } }) if time_delta is None: call_action() return def state_for_listener(now): """Fire on state changes after a delay and calls action.""" hass.bus.remove_listener(EVENT_STATE_CHANGED, attached_state_for_cancel) call_action() def state_for_cancel_listener(entity, inner_from_s, inner_to_s): """Fire on changes and cancel for listener if changed.""" if inner_to_s.state == to_s.state: return hass.bus.remove_listener(EVENT_TIME_CHANGED, attached_state_for_listener) hass.bus.remove_listener(EVENT_STATE_CHANGED, attached_state_for_cancel) attached_state_for_listener = track_point_in_time( hass, state_for_listener, dt_util.utcnow() + time_delta) attached_state_for_cancel = track_state_change( hass, entity, state_for_cancel_listener) track_state_change(hass, entity_id, state_automation_listener, from_state, to_state) return True
def trigger(hass, config, action): """Listen for state changes based on configuration.""" entity_id = config.get(CONF_ENTITY_ID) from_state = config.get(CONF_FROM, MATCH_ALL) to_state = config.get(CONF_TO) or config.get(CONF_STATE) or MATCH_ALL time_delta = config.get(CONF_FOR) def state_automation_listener(entity, from_s, to_s): """Listen for state changes and calls action.""" def call_action(): """Call action with right context.""" action({ 'trigger': { 'platform': 'state', 'entity_id': entity, 'from_state': from_s, 'to_state': to_s, 'for': time_delta, } }) if time_delta is None: call_action() return def state_for_listener(now): """Fire on state changes after a delay and calls action.""" hass.bus.remove_listener( EVENT_STATE_CHANGED, attached_state_for_cancel) call_action() def state_for_cancel_listener(entity, inner_from_s, inner_to_s): """Fire on changes and cancel for listener if changed.""" if inner_to_s.state == to_s.state: return hass.bus.remove_listener(EVENT_TIME_CHANGED, attached_state_for_listener) hass.bus.remove_listener(EVENT_STATE_CHANGED, attached_state_for_cancel) attached_state_for_listener = track_point_in_time( hass, state_for_listener, dt_util.utcnow() + time_delta) attached_state_for_cancel = track_state_change( hass, entity, state_for_cancel_listener) track_state_change( hass, entity_id, state_automation_listener, from_state, to_state) return True
def setup(hass, config): # pylint: disable=too-many-locals,too-many-statements """Get the zones and offsets from configuration.yaml.""" ignored_zones = [] if 'ignored_zones' in config[DOMAIN]: for variable in config[DOMAIN]['ignored_zones']: ignored_zones.append(variable) # Get the devices from configuration.yaml. if 'devices' not in config[DOMAIN]: _LOGGER.error('devices not found in config') return False proximity_devices = [] for variable in config[DOMAIN]['devices']: proximity_devices.append(variable) # Get the direction of travel tolerance from configuration.yaml. tolerance = config[DOMAIN].get('tolerance', DEFAULT_TOLERANCE) # Get the zone to monitor proximity to from configuration.yaml. proximity_zone = config[DOMAIN].get('zone', DEFAULT_PROXIMITY_ZONE) entity_id = DOMAIN + '.' + proximity_zone proximity_zone = 'zone.' + proximity_zone state = hass.states.get(proximity_zone) zone_friendly_name = (state.name).lower() # Set the default values. dist_to_zone = 'not set' dir_of_travel = 'not set' nearest = 'not set' proximity = Proximity(hass, zone_friendly_name, dist_to_zone, dir_of_travel, nearest, ignored_zones, proximity_devices, tolerance, proximity_zone) proximity.entity_id = entity_id proximity.update_ha_state() # Main command to monitor proximity of devices. track_state_change(hass, proximity_devices, proximity.check_proximity_state_change) return True
def __init__(self, hass, device, friendly_name, sensor_class, value_template, entity_ids): """Initialize the Template binary sensor.""" self.hass = hass self.entity_id = generate_entity_id(ENTITY_ID_FORMAT, device, hass=hass) self._name = friendly_name self._sensor_class = sensor_class self._template = value_template self._state = None self.update() def template_bsensor_state_listener(entity, old_state, new_state): """Called when the target device changes state.""" self.update_ha_state(True) track_state_change(hass, entity_ids, template_bsensor_state_listener)
def __init__(self, hass, name, children, commands, attributes): """Initialize the Universal media device.""" # pylint: disable=too-many-arguments self.hass = hass self._name = name self._children = children self._cmds = commands self._attrs = attributes self._child_state = None def on_dependency_update(*_): """Update bm state when dependencies update.""" self.update_ha_state(True) depend = copy(children) for entity in attributes.values(): depend.append(entity[0]) track_state_change(hass, depend, on_dependency_update)
def __init__(self, hass, name, heater_entity_id, sensor_entity_id, min_temp, max_temp, target_temp): """Initialize the thermostat.""" self.hass = hass self._name = name self.heater_entity_id = heater_entity_id self._active = False self._cur_temp = None self._min_temp = min_temp self._max_temp = max_temp self._target_temp = target_temp self._unit = None track_state_change(hass, sensor_entity_id, self._sensor_changed) sensor_state = hass.states.get(sensor_entity_id) if sensor_state: self._update_temp(sensor_state)
def __init__(self, hass, device_id, friendly_name, unit_of_measurement, state_template, entity_ids): """Initialize the sensor.""" self.hass = hass self.entity_id = generate_entity_id(ENTITY_ID_FORMAT, device_id, hass=hass) self._name = friendly_name self._unit_of_measurement = unit_of_measurement self._template = state_template self._state = None self.update() def template_sensor_state_listener(entity, old_state, new_state): """Called when the target device changes state.""" self.update_ha_state(True) track_state_change(hass, entity_ids, template_sensor_state_listener)
def trigger(hass, config, action): """Listen for state changes based on configuration.""" entity_id = config.get(CONF_ENTITY_ID) below = config.get(CONF_BELOW) above = config.get(CONF_ABOVE) value_template = config.get(CONF_VALUE_TEMPLATE) # pylint: disable=unused-argument def state_automation_listener(entity, from_s, to_s): """Listen for state changes and calls action.""" if to_s is None: return variables = { 'trigger': { 'platform': 'numeric_state', 'entity_id': entity, 'below': below, 'above': above, } } # If new one doesn't match, nothing to do if not condition.numeric_state( hass, to_s, below, above, value_template, variables): return # Only match if old didn't exist or existed but didn't match # Written as: skip if old one did exist and matched if from_s is not None and condition.numeric_state( hass, from_s, below, above, value_template, variables): return variables['trigger']['from_state'] = from_s variables['trigger']['to_state'] = to_s action(variables) track_state_change( hass, entity_id, state_automation_listener) return True
def __init__(self, hass, device_id, friendly_name, state_template, on_action, off_action, entity_ids): """Initialize the Template switch.""" self.hass = hass self.entity_id = generate_entity_id(ENTITY_ID_FORMAT, device_id, hass=hass) self._name = friendly_name self._template = state_template self._on_script = Script(hass, on_action) self._off_script = Script(hass, off_action) self._state = False self.update() def template_switch_state_listener(entity, old_state, new_state): """Called when the target device changes state.""" self.update_ha_state(True) track_state_change(hass, entity_ids, template_switch_state_listener)
def trigger(hass, config, action): """Listen for state changes based on configuration.""" entity_id = config.get(CONF_ENTITY_ID) below = config.get(CONF_BELOW) above = config.get(CONF_ABOVE) value_template = config.get(CONF_VALUE_TEMPLATE) # pylint: disable=unused-argument def state_automation_listener(entity, from_s, to_s): """Listen for state changes and calls action.""" if to_s is None: return variables = { 'trigger': { 'platform': 'numeric_state', 'entity_id': entity, 'below': below, 'above': above, } } # If new one doesn't match, nothing to do if not condition.numeric_state(hass, to_s, below, above, value_template, variables): return # Only match if old didn't exist or existed but didn't match # Written as: skip if old one did exist and matched if from_s is not None and condition.numeric_state( hass, from_s, below, above, value_template, variables): return variables['trigger']['from_state'] = from_s variables['trigger']['to_state'] = to_s action(variables) track_state_change(hass, entity_id, state_automation_listener) return True
def trigger(hass, config, action): """Listen for state changes based on configuration.""" entity_id = config.get(CONF_ENTITY_ID) zone_entity_id = config.get(CONF_ZONE) event = config.get(CONF_EVENT) def zone_automation_listener(entity, from_s, to_s): """Listen for state changes and calls action.""" if from_s and not location.has_location(from_s) or \ not location.has_location(to_s): return zone_state = hass.states.get(zone_entity_id) if from_s: from_match = condition.zone(hass, zone_state, from_s) else: from_match = False to_match = condition.zone(hass, zone_state, to_s) # pylint: disable=too-many-boolean-expressions if event == EVENT_ENTER and not from_match and to_match or \ event == EVENT_LEAVE and from_match and not to_match: action({ 'trigger': { 'platform': 'zone', 'entity_id': entity, 'from_state': from_s, 'to_state': to_s, 'zone': zone_state, 'event': event, }, }) track_state_change( hass, entity_id, zone_automation_listener, MATCH_ALL, MATCH_ALL) return True
def trigger(hass, config, action): """Listen for state changes based on configuration.""" entity_id = config.get(CONF_ENTITY_ID) zone_entity_id = config.get(CONF_ZONE) event = config.get(CONF_EVENT) def zone_automation_listener(entity, from_s, to_s): """Listen for state changes and calls action.""" if from_s and not location.has_location(from_s) or \ not location.has_location(to_s): return zone_state = hass.states.get(zone_entity_id) if from_s: from_match = condition.zone(hass, zone_state, from_s) else: from_match = False to_match = condition.zone(hass, zone_state, to_s) # pylint: disable=too-many-boolean-expressions if event == EVENT_ENTER and not from_match and to_match or \ event == EVENT_LEAVE and from_match and not to_match: action({ 'trigger': { 'platform': 'zone', 'entity_id': entity, 'from_state': from_s, 'to_state': to_s, 'zone': zone_state, 'event': event, }, }) track_state_change(hass, entity_id, zone_automation_listener, MATCH_ALL, MATCH_ALL) return True
def state_automation_listener(entity, from_s, to_s): """Listen for state changes and calls action.""" def call_action(): """Call action with right context.""" action({ 'trigger': { 'platform': 'state', 'entity_id': entity, 'from_state': from_s, 'to_state': to_s, 'for': time_delta, } }) if time_delta is None: call_action() return def state_for_listener(now): """Fire on state changes after a delay and calls action.""" hass.bus.remove_listener(EVENT_STATE_CHANGED, attached_state_for_cancel) call_action() def state_for_cancel_listener(entity, inner_from_s, inner_to_s): """Fire on changes and cancel for listener if changed.""" if inner_to_s.state == to_s.state: return hass.bus.remove_listener(EVENT_TIME_CHANGED, attached_state_for_listener) hass.bus.remove_listener(EVENT_STATE_CHANGED, attached_state_for_cancel) attached_state_for_listener = track_point_in_time( hass, state_for_listener, dt_util.utcnow() + time_delta) attached_state_for_cancel = track_state_change( hass, entity, state_for_cancel_listener)
def state_automation_listener(entity, from_s, to_s): """Listen for state changes and calls action.""" def call_action(): """Call action with right context.""" action({ 'trigger': { 'platform': 'state', 'entity_id': entity, 'from_state': from_s, 'to_state': to_s, 'for': time_delta, } }) if time_delta is None: call_action() return def state_for_listener(now): """Fire on state changes after a delay and calls action.""" hass.bus.remove_listener( EVENT_STATE_CHANGED, attached_state_for_cancel) call_action() def state_for_cancel_listener(entity, inner_from_s, inner_to_s): """Fire on changes and cancel for listener if changed.""" if inner_to_s.state == to_s.state: return hass.bus.remove_listener(EVENT_TIME_CHANGED, attached_state_for_listener) hass.bus.remove_listener(EVENT_STATE_CHANGED, attached_state_for_cancel) attached_state_for_listener = track_point_in_time( hass, state_for_listener, dt_util.utcnow() + time_delta) attached_state_for_cancel = track_state_change( hass, entity, state_for_cancel_listener)
def __init__(self, hass, name, indoor_temp_sensor, outdoor_temp_sensor, indoor_humidity_sensor, calib_factor): """Initialize the sensor.""" self._state = None self._name = name self._indoor_temp_sensor = indoor_temp_sensor self._indoor_humidity_sensor = indoor_humidity_sensor self._outdoor_temp_sensor = outdoor_temp_sensor self._calib_factor = calib_factor self._is_metric = (hass.config.temperature_unit == TEMP_CELSIUS) self._dewpoint = None self._indoor_temp = None self._outdoor_temp = None self._indoor_hum = None self._crit_temp = None track_state_change(hass, indoor_temp_sensor, self._sensor_changed) track_state_change(hass, outdoor_temp_sensor, self._sensor_changed) track_state_change(hass, indoor_humidity_sensor, self._sensor_changed) # Read initial state indoor_temp = hass.states.get(indoor_temp_sensor) outdoor_temp = hass.states.get(outdoor_temp_sensor) indoor_hum = hass.states.get(indoor_humidity_sensor) if indoor_temp: self._indoor_temp = \ MoldIndicator._update_temp_sensor(indoor_temp) if outdoor_temp: self._outdoor_temp = \ MoldIndicator._update_temp_sensor(outdoor_temp) if indoor_hum: self._indoor_hum = \ MoldIndicator._update_hum_sensor(indoor_hum) self.update()
def test_track_state_change(self): """Test track_state_change.""" # 2 lists to track how often our callbacks get called specific_runs = [] wildcard_runs = [] wildercard_runs = [] track_state_change( self.hass, 'light.Bowl', lambda a, b, c: specific_runs.append(1), 'on', 'off') track_state_change( self.hass, 'light.Bowl', lambda _, old_s, new_s: wildcard_runs.append((old_s, new_s))) track_state_change( self.hass, MATCH_ALL, lambda _, old_s, new_s: wildercard_runs.append((old_s, new_s))) # Adding state to state machine self.hass.states.set("light.Bowl", "on") self.hass.pool.block_till_done() self.assertEqual(0, len(specific_runs)) self.assertEqual(1, len(wildcard_runs)) self.assertEqual(1, len(wildercard_runs)) self.assertIsNone(wildcard_runs[-1][0]) self.assertIsNotNone(wildcard_runs[-1][1]) # Set same state should not trigger a state change/listener self.hass.states.set('light.Bowl', 'on') self.hass.pool.block_till_done() self.assertEqual(0, len(specific_runs)) self.assertEqual(1, len(wildcard_runs)) self.assertEqual(1, len(wildercard_runs)) # State change off -> on self.hass.states.set('light.Bowl', 'off') self.hass.pool.block_till_done() self.assertEqual(1, len(specific_runs)) self.assertEqual(2, len(wildcard_runs)) self.assertEqual(2, len(wildercard_runs)) # State change off -> off self.hass.states.set('light.Bowl', 'off', {"some_attr": 1}) self.hass.pool.block_till_done() self.assertEqual(1, len(specific_runs)) self.assertEqual(3, len(wildcard_runs)) self.assertEqual(3, len(wildercard_runs)) # State change off -> on self.hass.states.set('light.Bowl', 'on') self.hass.pool.block_till_done() self.assertEqual(1, len(specific_runs)) self.assertEqual(4, len(wildcard_runs)) self.assertEqual(4, len(wildercard_runs)) self.hass.states.remove('light.bowl') self.hass.pool.block_till_done() self.assertEqual(1, len(specific_runs)) self.assertEqual(5, len(wildcard_runs)) self.assertEqual(5, len(wildercard_runs)) self.assertIsNotNone(wildcard_runs[-1][0]) self.assertIsNone(wildcard_runs[-1][1]) self.assertIsNotNone(wildercard_runs[-1][0]) self.assertIsNone(wildercard_runs[-1][1]) # Set state for different entity id self.hass.states.set('switch.kitchen', 'on') self.hass.pool.block_till_done() self.assertEqual(1, len(specific_runs)) self.assertEqual(5, len(wildcard_runs)) self.assertEqual(6, len(wildercard_runs))
def track_state_change_decorator(action): """Decorator to track state changes.""" event.track_state_change(HASS, entity_ids, functools.partial(action, HASS), from_state, to_state) return action
def test_track_state_change(self): """Test track_state_change.""" # 2 lists to track how often our callbacks get called specific_runs = [] wildcard_runs = [] wildercard_runs = [] track_state_change(self.hass, 'light.Bowl', lambda a, b, c: specific_runs.append(1), 'on', 'off') track_state_change( self.hass, 'light.Bowl', lambda _, old_s, new_s: wildcard_runs.append((old_s, new_s))) track_state_change( self.hass, MATCH_ALL, lambda _, old_s, new_s: wildercard_runs.append((old_s, new_s))) # Adding state to state machine self.hass.states.set("light.Bowl", "on") self.hass.pool.block_till_done() self.assertEqual(0, len(specific_runs)) self.assertEqual(1, len(wildcard_runs)) self.assertEqual(1, len(wildercard_runs)) self.assertIsNone(wildcard_runs[-1][0]) self.assertIsNotNone(wildcard_runs[-1][1]) # Set same state should not trigger a state change/listener self.hass.states.set('light.Bowl', 'on') self.hass.pool.block_till_done() self.assertEqual(0, len(specific_runs)) self.assertEqual(1, len(wildcard_runs)) self.assertEqual(1, len(wildercard_runs)) # State change off -> on self.hass.states.set('light.Bowl', 'off') self.hass.pool.block_till_done() self.assertEqual(1, len(specific_runs)) self.assertEqual(2, len(wildcard_runs)) self.assertEqual(2, len(wildercard_runs)) # State change off -> off self.hass.states.set('light.Bowl', 'off', {"some_attr": 1}) self.hass.pool.block_till_done() self.assertEqual(1, len(specific_runs)) self.assertEqual(3, len(wildcard_runs)) self.assertEqual(3, len(wildercard_runs)) # State change off -> on self.hass.states.set('light.Bowl', 'on') self.hass.pool.block_till_done() self.assertEqual(1, len(specific_runs)) self.assertEqual(4, len(wildcard_runs)) self.assertEqual(4, len(wildercard_runs)) self.hass.states.remove('light.bowl') self.hass.pool.block_till_done() self.assertEqual(1, len(specific_runs)) self.assertEqual(5, len(wildcard_runs)) self.assertEqual(5, len(wildercard_runs)) self.assertIsNotNone(wildcard_runs[-1][0]) self.assertIsNone(wildcard_runs[-1][1]) self.assertIsNotNone(wildercard_runs[-1][0]) self.assertIsNone(wildercard_runs[-1][1]) # Set state for different entity id self.hass.states.set('switch.kitchen', 'on') self.hass.pool.block_till_done() self.assertEqual(1, len(specific_runs)) self.assertEqual(5, len(wildcard_runs)) self.assertEqual(6, len(wildercard_runs))
def start(self): """Start tracking members.""" track_state_change( self.hass, self.tracking, self._state_changed_listener)