def mold_indicator_startup(event): """Add listeners and get 1st state.""" _LOGGER.debug("Startup for %s", self.entity_id) async_track_state_change(self.hass, self._entities, mold_indicator_sensors_state_listener) # Read initial state indoor_temp = self.hass.states.get(self._indoor_temp_sensor) outdoor_temp = self.hass.states.get(self._outdoor_temp_sensor) indoor_hum = self.hass.states.get(self._indoor_humidity_sensor) schedule_update = self._update_sensor(self._indoor_temp_sensor, None, indoor_temp) schedule_update = False if not self._update_sensor( self._outdoor_temp_sensor, None, outdoor_temp) else\ schedule_update schedule_update = False if not self._update_sensor( self._indoor_humidity_sensor, None, indoor_hum) else\ schedule_update if schedule_update: self.async_schedule_update_ha_state(True)
async def async_added_to_hass(self): """Call when entity about to be added.""" @callback def async_threshold_sensor_state_listener(entity, old_state, new_state): """Handle sensor state changes.""" if new_state.state == STATE_UNKNOWN: return entity_obs_list = self.entity_obs[entity] for entity_obs in entity_obs_list: platform = entity_obs['platform'] self.watchers[platform](entity_obs) prior = self.prior for obs in self.current_obs.values(): prior = update_probability( prior, obs['prob_true'], obs['prob_false']) self.probability = prior self.hass.async_add_job(self.async_update_ha_state, True) async_track_state_change( self.hass, self.entity_obs, async_threshold_sensor_state_listener)
def run(self): """Method called be object after driver is started.""" state = self._hass.states.get(self._entity_id) self.update_temperature(new_state=state) async_track_state_change( self._hass, self._entity_id, self.update_temperature)
def async_added_to_hass(self): """Subscribe mqtt events. This method must be run in the event loop and returns a coroutine. """ if (self._mqtt): async_track_state_change( self.hass, self.entity_id, self._async_state_changed_listener ) @callback def message_received(topic, payload, qos): """Run when new MQTT message has been received.""" #_LOGGER.warning("[ALARM] MQTT Topic: %s Payload: %s", topic, payload) if payload.split(" ")[0] == self._payload_disarm: #_LOGGER.warning("Disarming %s", payload) #TODO self._hass.states.get('binary_sensor.siren_sensor') #Use this method to relay open states if (self._override_code): self.alarm_disarm(self._code) else: self.alarm_disarm(payload.split(" ")[1]) elif payload == self._payload_arm_home: self.alarm_arm_home('') elif payload == self._payload_arm_away: self.alarm_arm_away('') elif payload == self._payload_arm_night: self.alarm_arm_night('') else: _LOGGER.warning("[ALARM/MQTT] Received unexpected payload: %s", payload) return if (self._mqtt): return self.mqtt.async_subscribe( self.hass, self._command_topic, message_received, self._qos)
def __init__(self, hass, name, broadlink_device, ircodes_ini, min_temp, max_temp, target_temp, temp_sensor_entity_id, operation_list, fan_list, default_operation, default_fan_mode, default_operation_from_idle): """Initialize the Broadlink IR Climate device.""" self.hass = hass self._name = name self._min_temp = min_temp self._max_temp = max_temp self._target_temperature = target_temp self._target_temperature_step = 1 self._unit_of_measurement = hass.config.units.temperature_unit self._current_temperature = 0 self._temp_sensor_entity_id = temp_sensor_entity_id self._current_operation = default_operation self._current_fan_mode = default_fan_mode self._operation_list = operation_list self._fan_list = fan_list self._default_operation_from_idle = default_operation_from_idle self._broadlink_device = broadlink_device self._commands_ini = ircodes_ini if temp_sensor_entity_id: async_track_state_change( hass, temp_sensor_entity_id, self._async_temp_sensor_changed) sensor_state = hass.states.get(temp_sensor_entity_id) if sensor_state: self._async_update_current_temp(sensor_state)
def __init__(self, hass, entity_id, name, lower, upper, hysteresis, device_class): """Initialize the Threshold sensor.""" self._hass = hass self._entity_id = entity_id self._name = name self._threshold_lower = lower self._threshold_upper = upper self._hysteresis = hysteresis self._device_class = device_class self._state_position = None self._state = False self.sensor_value = None # pylint: disable=invalid-name @callback def async_threshold_sensor_state_listener( entity, old_state, new_state): """Handle sensor state changes.""" try: self.sensor_value = None if new_state.state == STATE_UNKNOWN \ else float(new_state.state) except (ValueError, TypeError): self.sensor_value = None _LOGGER.warning("State is not numerical") hass.async_add_job(self.async_update_ha_state, True) async_track_state_change( hass, entity_id, async_threshold_sensor_state_listener)
def __init__(self, hass, name, heater_entity_id, sensor_entity_id, min_temp, max_temp, target_temp, ac_mode, min_cycle_duration, tolerance, keep_alive): """Initialize the thermostat.""" self.hass = hass self._name = name self.heater_entity_id = heater_entity_id self.ac_mode = ac_mode self.min_cycle_duration = min_cycle_duration self._tolerance = tolerance self._keep_alive = keep_alive self._active = False self._cur_temp = None self._min_temp = min_temp self._max_temp = max_temp self._target_temp = target_temp self._unit = hass.config.units.temperature_unit async_track_state_change( hass, sensor_entity_id, self._async_sensor_changed) async_track_state_change( hass, heater_entity_id, self._async_switch_changed) if self._keep_alive: async_track_time_interval( hass, self._async_keep_alive, self._keep_alive) sensor_state = hass.states.get(sensor_entity_id) if sensor_state: self._async_update_temp(sensor_state)
def __init__(self, hass, entity_id, name, watched_entity_id, state, repeat, skip_first, message_template, done_message_template, notifiers, can_ack): """Initialize the alert.""" self.hass = hass self._name = name self._alert_state = state self._skip_first = skip_first self._message_template = message_template if self._message_template is not None: self._message_template.hass = hass self._done_message_template = done_message_template if self._done_message_template is not None: self._done_message_template.hass = hass self._notifiers = notifiers self._can_ack = can_ack self._delay = [timedelta(minutes=val) for val in repeat] self._next_delay = 0 self._firing = False self._ack = False self._cancel = None self._send_done_message = False self.entity_id = ENTITY_ID_FORMAT.format(entity_id) event.async_track_state_change( hass, watched_entity_id, self.watched_entity_change)
def __init__(self, hass, entity_id, name, threshold, limit_type, device_class): """Initialize the Threshold sensor.""" self._hass = hass self._entity_id = entity_id self.is_upper = limit_type == 'upper' self._name = name self._threshold = threshold self._device_class = device_class self._deviation = False self.sensor_value = 0 @callback # pylint: disable=invalid-name def async_threshold_sensor_state_listener( entity, old_state, new_state): """Called when the sensor changes state.""" if new_state.state == STATE_UNKNOWN: return try: self.sensor_value = float(new_state.state) except ValueError: _LOGGER.error("State is not numerical") hass.async_add_job(self.async_update_ha_state, True) async_track_state_change( hass, entity_id, async_threshold_sensor_state_listener)
def async_added_to_hass(self): """Subscribe mqtt events. This method must be run in the event loop and returns a coroutine. """ async_track_state_change( self.hass, self.entity_id, self._async_state_changed_listener ) @callback def message_received(topic, payload, qos): """Run when new MQTT message has been received.""" if payload == self._payload_disarm: self.async_alarm_disarm(self._code) elif payload == self._payload_arm_home: self.async_alarm_arm_home(self._code) elif payload == self._payload_arm_away: self.async_alarm_arm_away(self._code) elif payload == self._payload_arm_night: self.async_alarm_arm_night(self._code) else: _LOGGER.warning("Received unexpected payload: %s", payload) return return mqtt.async_subscribe( self.hass, self._command_topic, message_received, self._qos)
def template_lock_startup(event): """Update template on startup.""" if self._state_entities != MATCH_ALL: # Track state change only for valid templates async_track_state_change( self._hass, self._state_entities, template_lock_state_listener) self.async_schedule_update_ha_state(True)
def template_bsensor_startup(event): """Update template on startup.""" if self._entities != MATCH_ALL: # Track state change only for valid templates async_track_state_change( self.hass, self._entities, template_bsensor_state_listener) self.async_check_state()
def template_light_startup(event): """Update template on startup.""" if (self._template is not None or self._level_template is not None): async_track_state_change( self.hass, self._entities, template_light_state_listener) self.async_schedule_update_ha_state(True)
async def async_added_to_hass(self): """Register listeners.""" for entity_id in self._entities: new_state = self.hass.states.get(entity_id) self.update_supported_features(entity_id, None, new_state, update_state=False) async_track_state_change(self.hass, self._entities, self.update_supported_features) await self.async_update()
async def run(self): """Method called by accessory after driver is started. Run inside the HAP-python event loop. """ state = self.hass.states.get(self.entity_id) self.hass.add_job(self.update_state_callback, None, None, state) async_track_state_change( self.hass, self.entity_id, self.update_state_callback)
def start_refresh(*args): """Register state tracking.""" @callback def force_refresh(*args): """Force the component to refresh.""" self.async_schedule_update_ha_state(True) force_refresh() async_track_state_change(self.hass, self._entity_id, force_refresh)
async def run(self): """Handle accessory driver started event. Run inside the HAP-python event loop. """ state = self.hass.states.get(self.entity_id) self.hass.add_job(self.update_state_callback, None, None, state) async_track_state_change( self.hass, self.entity_id, self.update_state_callback)
def async_register(self): """Register listener.""" from xknx.devices import ExposeSensor self.device = ExposeSensor( self.xknx, name=self.entity_id, group_address=self.address, value_type=self.type) self.xknx.devices.add(self.device) async_track_state_change( self.hass, self.entity_id, self._async_entity_changed)
def __init__(self, hass, name, heater_entity_id, sensor_entity_id, min_temp, max_temp, target_temp, ac_mode, min_cycle_duration, cold_tolerance, hot_tolerance, keep_alive, initial_operation_mode, away_temp, precision): """Initialize the thermostat.""" self.hass = hass self._name = name self.heater_entity_id = heater_entity_id self.ac_mode = ac_mode self.min_cycle_duration = min_cycle_duration self._cold_tolerance = cold_tolerance self._hot_tolerance = hot_tolerance self._keep_alive = keep_alive self._initial_operation_mode = initial_operation_mode self._saved_target_temp = target_temp if target_temp is not None \ else away_temp self._temp_precision = precision if self.ac_mode: self._current_operation = STATE_COOL self._operation_list = [STATE_COOL, STATE_OFF] else: self._current_operation = STATE_HEAT self._operation_list = [STATE_HEAT, STATE_OFF] if initial_operation_mode == STATE_OFF: self._enabled = False self._current_operation = STATE_OFF else: self._enabled = True self._active = False self._cur_temp = None self._temp_lock = asyncio.Lock() self._min_temp = min_temp self._max_temp = max_temp self._target_temp = target_temp self._unit = hass.config.units.temperature_unit self._support_flags = SUPPORT_FLAGS if away_temp is not None: self._support_flags = SUPPORT_FLAGS | SUPPORT_AWAY_MODE self._away_temp = away_temp self._is_away = False async_track_state_change( hass, sensor_entity_id, self._async_sensor_changed) async_track_state_change( hass, heater_entity_id, self._async_switch_changed) if self._keep_alive: async_track_time_interval( hass, self._async_control_heating, self._keep_alive) sensor_state = hass.states.get(sensor_entity_id) if sensor_state and sensor_state.state != STATE_UNKNOWN: self._async_update_temp(sensor_state)
def async_stats_sensor_startup(event): """Add listener and get recorded state.""" _LOGGER.debug("Startup for %s", self.entity_id) async_track_state_change( self.hass, self._entity_id, async_stats_sensor_state_listener) if 'recorder' in self.hass.config.components: # only use the database if it's configured self.hass.async_create_task( self._async_initialize_from_database() )
async def async_added_to_hass(self): """Handle entity which will be added.""" await super().async_added_to_hass() state = await self.async_get_last_state() if state: try: self._state = Decimal(state.state) except ValueError as err: _LOGGER.warning("Could not restore last state: %s", err) @callback def calc_integration(entity, old_state, new_state): """Handle the sensor state changes.""" if old_state is None or\ old_state.state in [STATE_UNKNOWN, STATE_UNAVAILABLE] or\ new_state.state in [STATE_UNKNOWN, STATE_UNAVAILABLE]: return if self._unit_of_measurement is None: unit = new_state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) self._unit_of_measurement = self._unit_template.format( "" if unit is None else unit) try: # integration as the Riemann integral of previous measures. area = 0 elapsed_time = (new_state.last_updated - old_state.last_updated).total_seconds() if self._method == TRAPEZOIDAL_METHOD: area = (Decimal(new_state.state) + Decimal(old_state.state))*Decimal(elapsed_time)/2 elif self._method == LEFT_METHOD: area = Decimal(old_state.state)*Decimal(elapsed_time) elif self._method == RIGHT_METHOD: area = Decimal(new_state.state)*Decimal(elapsed_time) integral = area / (self._unit_prefix * self._unit_time) assert isinstance(integral, Decimal) except ValueError as err: _LOGGER.warning("While calculating integration: %s", err) except DecimalException as err: _LOGGER.warning("Invalid state (%s > %s): %s", old_state.state, new_state.state, err) except AssertionError as err: _LOGGER.error("Could not calculate integral: %s", err) else: self._state += integral self.async_schedule_update_ha_state() async_track_state_change( self.hass, self._sensor_source_id, calc_integration)
def async_source_tracking(event): """Wait for source to be ready, then start meter.""" if self._tariff_entity is not None: _LOGGER.debug("track %s", self._tariff_entity) async_track_state_change(self.hass, self._tariff_entity, self.async_tariff_change) tariff_entity_state = self.hass.states.get(self._tariff_entity) if self._tariff != tariff_entity_state.state: return self._collecting = async_track_state_change( self.hass, self._sensor_source_id, self.async_reading)
async def async_added_to_hass(self): """After being added to hass, load from history.""" if ENABLE_LOAD_HISTORY and 'recorder' in self.hass.config.components: # only use the database if it's configured self.hass.async_add_job(self._load_history_from_db) async_track_state_change( self.hass, list(self._sensormap), self.state_changed) for entity_id in self._sensormap: state = self.hass.states.get(entity_id) if state is not None: self.state_changed(entity_id, None, state)
def async_setup(hass, config): """Set up the MQTT state feed.""" conf = config.get(DOMAIN, {}) base_topic = conf.get(CONF_BASE_TOPIC) pub_include = conf.get(CONF_INCLUDE, {}) pub_exclude = conf.get(CONF_EXCLUDE, {}) publish_attributes = conf.get(CONF_PUBLISH_ATTRIBUTES) publish_timestamps = conf.get(CONF_PUBLISH_TIMESTAMPS) publish_filter = generate_filter(pub_include.get(CONF_DOMAINS, []), pub_include.get(CONF_ENTITIES, []), pub_exclude.get(CONF_DOMAINS, []), pub_exclude.get(CONF_ENTITIES, [])) if not base_topic.endswith('/'): base_topic = base_topic + '/' @callback def _state_publisher(entity_id, old_state, new_state): if new_state is None: return if not publish_filter(entity_id): return payload = new_state.state mybase = base_topic + entity_id.replace('.', '/') + '/' hass.components.mqtt.async_publish(mybase + 'state', payload, 1, True) if publish_timestamps: if new_state.last_updated: hass.components.mqtt.async_publish( mybase + 'last_updated', new_state.last_updated.isoformat(), 1, True) if new_state.last_changed: hass.components.mqtt.async_publish( mybase + 'last_changed', new_state.last_changed.isoformat(), 1, True) if publish_attributes: for key, val in new_state.attributes.items(): if val: encoded_val = json.dumps(val, cls=JSONEncoder) hass.components.mqtt.async_publish(mybase + key, encoded_val, 1, True) async_track_state_change(hass, MATCH_ALL, _state_publisher) return True
def __init__(self, hass, entity_ids, name, sensor_type, round_digits): """Initialize the min/max sensor.""" self._hass = hass self._entity_ids = entity_ids self._sensor_type = sensor_type self._round_digits = round_digits if name: self._name = name else: self._name = '{} sensor'.format( next(v for k, v in SENSOR_TYPES.items() if self._sensor_type == v)).capitalize() self._unit_of_measurement = None self._unit_of_measurement_mismatch = False self.min_value = self.max_value = self.mean = self.last = None self.count_sensors = len(self._entity_ids) self.states = {} @callback def async_min_max_sensor_state_listener(entity, old_state, new_state): """Handle the sensor state changes.""" if (new_state.state is None or new_state.state in [STATE_UNKNOWN, STATE_UNAVAILABLE]): self.states[entity] = STATE_UNKNOWN hass.async_add_job(self.async_update_ha_state, True) return if self._unit_of_measurement is None: self._unit_of_measurement = new_state.attributes.get( ATTR_UNIT_OF_MEASUREMENT) if self._unit_of_measurement != new_state.attributes.get( ATTR_UNIT_OF_MEASUREMENT): _LOGGER.warning( "Units of measurement do not match for entity %s", self.entity_id) self._unit_of_measurement_mismatch = True try: self.states[entity] = float(new_state.state) self.last = float(new_state.state) except ValueError: _LOGGER.warning("Unable to store state. " "Only numerical states are supported") hass.async_add_job(self.async_update_ha_state, True) async_track_state_change( hass, entity_ids, async_min_max_sensor_state_listener)
def async_trigger(hass, config, action): """Listen for state changes based on configuration.""" value_template = config.get(CONF_VALUE_TEMPLATE) value_template.hass = hass # Local variable to keep track of if the action has already been triggered already_triggered = False @asyncio.coroutine def state_changed_listener(entity_id, from_s, to_s): """Listen for state changes and calls action.""" nonlocal already_triggered template_result = condition.async_template(hass, value_template) # Check to see if template returns true if template_result and not already_triggered: already_triggered = True hass.async_add_job(action, { 'trigger': { 'platform': 'template', 'entity_id': entity_id, 'from_state': from_s, 'to_state': to_s, }, }) elif not template_result: already_triggered = False return async_track_state_change(hass, value_template.extract_entities(), state_changed_listener)
async def async_setup(hass, config): """Set up the Plant component.""" component = EntityComponent(_LOGGER, DOMAIN, hass, group_name=GROUP_NAME_ALL_PLANTS) entities = [] for plant_name, plant_config in config[DOMAIN].items(): _LOGGER.info("Added plant %s", plant_name) entity = Plant(plant_name, plant_config) sensor_entity_ids = list(plant_config[CONF_SENSORS].values()) _LOGGER.debug("Subscribing to entity_ids %s", sensor_entity_ids) async_track_state_change(hass, sensor_entity_ids, entity.state_changed) entities.append(entity) await component.async_add_entities(entities) return True
def async_on_state_subscription(entity_id: str) -> None: """Subscribe and forward states for requested entities.""" unsub = async_track_state_change( hass, entity_id, send_home_assistant_state) entry_data.disconnect_callbacks.append(unsub) # Send initial state hass.async_create_task(send_home_assistant_state( entity_id, None, hass.states.get(entity_id)))
def async_start(self): """Start tracking members. This method must be run in the event loop. """ self._async_unsub_state_changed = async_track_state_change( self.hass, self.tracking, self._state_changed_listener )
def __init__(self, hass, device, friendly_name, sensor_class, value_template, entity_ids): """Initialize the Template binary sensor.""" self.hass = hass self.entity_id = async_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 @callback def template_bsensor_state_listener(entity, old_state, new_state): """Called when the target device changes state.""" hass.async_add_job(self.async_update_ha_state, True) async_track_state_change( hass, entity_ids, template_bsensor_state_listener)
def async_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) time_delta = config.get(CONF_FOR) value_template = config.get(CONF_VALUE_TEMPLATE) async_remove_track_same = None if value_template is not None: value_template.hass = hass @callback def check_numeric_state(entity, from_s, to_s): """Return True if they should trigger.""" if to_s is None: return False variables = { 'trigger': { 'platform': 'numeric_state', 'entity_id': entity, 'below': below, 'above': above, } } # If new one doesn't match, nothing to do if not condition.async_numeric_state( hass, to_s, below, above, value_template, variables): return False return True @callback def state_automation_listener(entity, from_s, to_s): """Listen for state changes and calls action.""" nonlocal async_remove_track_same if not check_numeric_state(entity, from_s, to_s): return variables = { 'trigger': { 'platform': 'numeric_state', 'entity_id': entity, 'below': below, 'above': above, 'from_state': from_s, 'to_state': to_s, } } # 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.async_numeric_state( hass, from_s, below, above, value_template, variables): return @callback def call_action(): """Call action with right context.""" hass.async_run_job(action, variables) if not time_delta: call_action() return async_remove_track_same = async_track_same_state( hass, True, time_delta, call_action, entity_ids=entity_id, async_check_func=check_numeric_state) unsub = async_track_state_change( hass, entity_id, state_automation_listener) @callback def async_remove(): """Remove state listeners async.""" unsub() if async_remove_track_same: async_remove_track_same() # pylint: disable=not-callable return async_remove
async def test_track_state_change_from_to_state_match(hass): """Test track_state_change with from and to state matchers.""" from_and_to_state_runs = [] only_from_runs = [] only_to_runs = [] match_all_runs = [] no_to_from_specified_runs = [] def from_and_to_state_callback(entity_id, old_state, new_state): from_and_to_state_runs.append(1) def only_from_state_callback(entity_id, old_state, new_state): only_from_runs.append(1) def only_to_state_callback(entity_id, old_state, new_state): only_to_runs.append(1) def match_all_callback(entity_id, old_state, new_state): match_all_runs.append(1) def no_to_from_specified_callback(entity_id, old_state, new_state): no_to_from_specified_runs.append(1) async_track_state_change(hass, "light.Bowl", from_and_to_state_callback, "on", "off") async_track_state_change(hass, "light.Bowl", only_from_state_callback, "on", None) async_track_state_change(hass, "light.Bowl", only_to_state_callback, None, ["off", "standby"]) async_track_state_change(hass, "light.Bowl", match_all_callback, MATCH_ALL, MATCH_ALL) async_track_state_change(hass, "light.Bowl", no_to_from_specified_callback) hass.states.async_set("light.Bowl", "on") await hass.async_block_till_done() assert len(from_and_to_state_runs) == 0 assert len(only_from_runs) == 0 assert len(only_to_runs) == 0 assert len(match_all_runs) == 1 assert len(no_to_from_specified_runs) == 1 hass.states.async_set("light.Bowl", "off") await hass.async_block_till_done() assert len(from_and_to_state_runs) == 1 assert len(only_from_runs) == 1 assert len(only_to_runs) == 1 assert len(match_all_runs) == 2 assert len(no_to_from_specified_runs) == 2 hass.states.async_set("light.Bowl", "on") await hass.async_block_till_done() assert len(from_and_to_state_runs) == 1 assert len(only_from_runs) == 1 assert len(only_to_runs) == 1 assert len(match_all_runs) == 3 assert len(no_to_from_specified_runs) == 3 hass.states.async_set("light.Bowl", "on") await hass.async_block_till_done() assert len(from_and_to_state_runs) == 1 assert len(only_from_runs) == 1 assert len(only_to_runs) == 1 assert len(match_all_runs) == 3 assert len(no_to_from_specified_runs) == 3 hass.states.async_set("light.Bowl", "off") await hass.async_block_till_done() assert len(from_and_to_state_runs) == 2 assert len(only_from_runs) == 2 assert len(only_to_runs) == 2 assert len(match_all_runs) == 4 assert len(no_to_from_specified_runs) == 4 hass.states.async_set("light.Bowl", "off") await hass.async_block_till_done() assert len(from_and_to_state_runs) == 2 assert len(only_from_runs) == 2 assert len(only_to_runs) == 2 assert len(match_all_runs) == 4 assert len(no_to_from_specified_runs) == 4
async def async_trigger(hass, config, action, automation_info): """Listen for state changes based on configuration.""" entity_id = config.get(CONF_ENTITY_ID) below = config.get(CONF_BELOW) above = config.get(CONF_ABOVE) time_delta = config.get(CONF_FOR) value_template = config.get(CONF_VALUE_TEMPLATE) unsub_track_same = {} entities_triggered = set() if value_template is not None: value_template.hass = hass @callback def check_numeric_state(entity, from_s, to_s): """Return True if criteria are now met.""" if to_s is None: return False variables = { 'trigger': { 'platform': 'numeric_state', 'entity_id': entity, 'below': below, 'above': above, } } return condition.async_numeric_state(hass, to_s, below, above, value_template, variables) @callback def state_automation_listener(entity, from_s, to_s): """Listen for state changes and calls action.""" @callback def call_action(): """Call action with right context.""" hass.async_run_job( action( { 'trigger': { 'platform': 'numeric_state', 'entity_id': entity, 'below': below, 'above': above, 'from_state': from_s, 'to_state': to_s, } }, context=to_s.context)) matching = check_numeric_state(entity, from_s, to_s) if not matching: entities_triggered.discard(entity) elif entity not in entities_triggered: entities_triggered.add(entity) if time_delta: unsub_track_same[entity] = async_track_same_state( hass, time_delta, call_action, entity_ids=entity, async_check_same_func=check_numeric_state) else: call_action() unsub = async_track_state_change(hass, entity_id, state_automation_listener) @callback def async_remove(): """Remove state listeners async.""" unsub() for async_remove in unsub_track_same.values(): async_remove() unsub_track_same.clear() return async_remove
def setup(hass, config): """Set up the MQTT state feed.""" conf = config.get(DOMAIN, {}) base_topic = conf.get(CONF_BASE_TOPIC) entity_include = conf.get(CONF_INCLUDE, {}) entity_exclude = conf.get(CONF_EXCLUDE, {}) entity_filter = generate_filter(entity_include.get(CONF_DOMAINS, []), entity_include.get(CONF_ENTITIES, []), entity_exclude.get(CONF_DOMAINS, []), entity_exclude.get(CONF_ENTITIES, [])) if not base_topic.endswith('/'): base_topic = base_topic + '/' def _publish_all_states(*_): states = hass.states.all() for state in states: _state_publisher(state.entity_id, None, state) def _handle_hass_status(topic, payload, qos): if payload == 'online': async_call_later(hass, 20, _publish_all_states) def _state_publisher(entity_id, old_state, new_state): if new_state is None: return if not entity_filter(entity_id): return entity_id_parts = entity_id.split('.') domain = entity_id_parts[0] entity_state = hass.states.get(entity_id) mybase = base_topic + entity_id.replace('.', '/') + '/state' if domain == 'switch' or domain == 'binary_sensor': data = {} if new_state.state == 'on': data['state'] = 'ON' elif new_state.state == 'off': data['state'] = 'OFF' payload = json.dumps(data, cls=JSONEncoder) hass.components.mqtt.async_publish(mybase, payload, 1, False) elif domain == 'light': data = {} if new_state.state == 'on': data['state'] = 'ON' elif new_state.state == 'off': data['state'] = 'OFF' try: data['brightness'] = new_state.attributes['brightness'] except KeyError: pass payload = json.dumps(data, cls=JSONEncoder) hass.components.mqtt.async_publish(mybase, payload, 1, False) elif domain == 'lock': data = {} if new_state.state == 'locked': data['state'] = 'LOCK' elif new_state.state == 'unlocked': data['state'] = 'UNLOCK' try: data['notification'] = entity_state.attributes['notification'] except KeyError: pass try: data['lock_status'] = entity_state.attributes['lock_status'] except KeyError: pass payload = json.dumps(data, cls=JSONEncoder) hass.components.mqtt.async_publish(mybase, payload, 1, False) elif domain == 'sensor': data = {} data['state'] = new_state.state payload = json.dumps(data, cls=JSONEncoder) hass.components.mqtt.async_publish(mybase, payload, 1, False) def _state_message_received(topic, payload, qos): """Handle new MQTT state messages.""" # Parse entity from topic topic_parts = topic.split('/') domain = topic_parts[-3] entity = topic_parts[-2] entity_id = "{0}.{1}".format(domain, entity) if not entity_filter(entity_id): return data = {ATTR_ENTITY_ID: entity_id} if domain == 'switch': if payload == mqtt_switch.DEFAULT_PAYLOAD_ON: hass.async_add_job(hass.services.async_call(domain, SERVICE_TURN_ON, data)) if payload == mqtt_switch.DEFAULT_PAYLOAD_OFF: hass.async_add_job(hass.services.async_call(domain, SERVICE_TURN_OFF, data)) elif domain == 'light': payload = json.loads(payload) if payload['state'] == 'ON': try: data['brightness'] = payload['brightness'] except KeyError: pass hass.async_add_job(hass.services.async_call(domain, SERVICE_TURN_ON, data)) if payload['state'] == 'OFF': hass.async_add_job(hass.services.async_call(domain, SERVICE_TURN_OFF, data)) elif domain == 'lock': if payload == mqtt_lock.DEFAULT_PAYLOAD_LOCK: hass.async_add_job(hass.services.async_call(domain, SERVICE_LOCK, data)) if payload == mqtt_lock.DEFAULT_PAYLOAD_UNLOCK: hass.async_add_job(hass.services.async_call(domain, SERVICE_UNLOCK, data)) async_track_state_change(hass, MATCH_ALL, _state_publisher) mqtt.subscribe(hass, base_topic+'+/+/set', _state_message_received) mqtt.subscribe(hass, 'hass/status', _handle_hass_status) return True
async def async_added_to_hass(self): """Run when entity about to be added.""" await super().async_added_to_hass() # Add listener async_track_state_change(self.hass, self.sensor_entity_id, self._async_sensor_changed) if self.heater_entity_id is not None: async_track_state_change(self.hass, self.heater_entity_id, self._async_switch_changed) if self.cooler_entity_id is not None: async_track_state_change(self.hass, self.cooler_entity_id, self._async_switch_changed) if self.fan_entity_id is not None: async_track_state_change(self.hass, self.fan_entity_id, self._async_switch_changed) if self.dryer_entity_id is not None: async_track_state_change(self.hass, self.dryer_entity_id, self._async_switch_changed) if self._keep_alive: async_track_time_interval(self.hass, self._async_control_heating, self._keep_alive) @callback def _async_startup(event): """Init on startup.""" sensor_state = self.hass.states.get(self.sensor_entity_id) if sensor_state and sensor_state.state not in ( STATE_UNAVAILABLE, STATE_UNKNOWN, ): self._async_update_temp(sensor_state) self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, _async_startup) # Check If we have an old state old_state = await self.async_get_last_state() if old_state is not None: # If we have no initial temperature, restore if self._target_temp is None: # If we have a previously saved temperature if old_state.attributes.get(ATTR_TEMPERATURE) is None: if self._hvac_mode == HVAC_MODE_COOL: self._target_temp = self.max_temp if self._hvac_mode == HVAC_MODE_FAN_ONLY: self._target_temp = self.max_temp if self._hvac_mode == HVAC_MODE_HEAT: self._target_temp = self.min_temp if self._hvac_mode == HVAC_MODE_DRY: self._target_temp = self._min_temp _LOGGER.warning( "Undefined target temperature," "falling back to %s", self._target_temp, ) else: self._target_temp = float( old_state.attributes[ATTR_TEMPERATURE]) if old_state.attributes.get(ATTR_PRESET_MODE) == PRESET_AWAY: self._is_away = True if not self._hvac_mode and old_state.state: self._hvac_mode = old_state.state else: # No previous state, try and restore defaults if self._target_temp is None: if self._hvac_mode == HVAC_MODE_COOL: self._target_temp = self.max_temp if self._hvac_mode == HVAC_MODE_FAN_ONLY: self._target_temp = self.max_temp if self._hvac_mode == HVAC_MODE_HEAT: self._target_temp = self.min_temp if self._hvac_mode == HVAC_MODE_DRY: self._target_temp = self.min_temp _LOGGER.warning("No previously saved temperature, setting to %s", self._target_temp) # Set default state to off if not self._hvac_mode: self._hvac_mode = HVAC_MODE_OFF
def __init__(self, hass, area): """Initialize the area presence binary sensor.""" self.area = area self.hass = hass self._name = f"Area ({self.area.name})" self._state = None self.last_off_time = datetime.utcnow() self._passive = self.area.passive_start # Prevent acting until all is loaded _LOGGER.info(f"Area {self.area.slug} presence sensor initializing.") # Fetch presence sensors self.presence_sensors = [] for component, entities in self.area.entities.items(): if component not in PRESENCE_DEVICE_COMPONENTS: continue for entity in entities: if (component == BINARY_SENSOR_DOMAIN and entity.device_class not in self.area.presence_device_class): continue self.presence_sensors.append(entity.entity_id) # Append presence_hold switch as a presence_sensor presence_hold_switch_id = f"{SWITCH_DOMAIN}.area_presence_hold_{self.area.slug}" self.presence_sensors.append(presence_hold_switch_id) area_lights = [ entity.entity_id for entity in self.area.entities[LIGHT_DOMAIN] ] if LIGHT_DOMAIN in self.area.entities.keys() else [] area_climate = [ entity.entity_id for entity in self.area.entities[CLIMATE_DOMAIN] ] if CLIMATE_DOMAIN in self.area.entities.keys() else [] # Set attributes self._attributes = { 'presence_sensors': self.presence_sensors, 'active_sensors': [], 'lights': area_lights, 'climate': area_climate, 'clear_timeout': self.area.clear_timeout, 'update_interval': self.area.update_interval, 'on_states': self.area.on_states, 'exterior': self.area.exterior } # Track presence sensors async_track_state_change(hass, self.presence_sensors, self.sensor_state_change) # Track autolight_disable sensor if available if self.area.automatic_lights[CONF_AL_DISABLE_ENTITY]: async_track_state_change( hass, self.area.automatic_lights[CONF_AL_DISABLE_ENTITY], self.autolight_disable_state_change) # Timed self update delta = timedelta(seconds=self.area.update_interval) async_track_time_interval(self.hass, self.update_area, delta) _LOGGER.info(f"Area {self.area.slug} presence sensor initialized.")
async def async_added_to_hass(self): async_track_state_change(self.hass, self._sensor, self.state_changed)
def sensor_startup(event): # pylint: disable=w0613 """Update template on startup.""" async_track_state_change(self._hass, [self._weather_entity], sensor_state_listener) self.async_schedule_update_ha_state(True)
async def async_added_to_hass(self): """Run when entity about to be added.""" await super().async_added_to_hass() # Add listener await self._subscribe_topics() # Check If we have an old state old_state = await self.async_get_last_state() if old_state is not None: # If we have no initial temperature, restore if old_state.attributes.get(ATTR_TEMPERATURE) is not None: self._target_temp = float( old_state.attributes[ATTR_TEMPERATURE]) if old_state.attributes.get(ATTR_PRESET_MODE) == PRESET_AWAY: self._is_away = True if old_state.attributes.get(ATTR_FAN_MODE) is not None: self._fan_mode = old_state.attributes.get(ATTR_FAN_MODE) if old_state.attributes.get(ATTR_SWING_MODE) is not None: self._swing_mode = old_state.attributes.get(ATTR_SWING_MODE) for attr in ATTRIBUTES_IRHVAC: val = old_state.attributes.get(attr) if val is not None: setattr(self, "_" + attr, val) if old_state.state: self._hvac_mode = old_state.state self._enabled = self._hvac_mode != STATE_OFF if self._enabled: self._last_on_mode = self._hvac_mode else: # No previous state, try and restore defaults if self._target_temp is None: self._target_temp = self._def_target_temp _LOGGER.warning( "No previously saved temperature, setting to %s", self._target_temp ) # Set initial state if self._hvac_mode is STATE_UNKNOWN: self._hvac_mode = self._init_hvac_mode if self._hvac_mode is HVAC_MODE_OFF: self.power_mode = STATE_OFF self._enabled = False else: self.power_mode = STATE_ON self._enabled = True if self._temp_sensor: async_track_state_change(self.hass, self._temp_sensor, self._async_sensor_changed) temp_sensor_state = self.hass.states.get(self._temp_sensor) if temp_sensor_state and temp_sensor_state.state != STATE_UNKNOWN and temp_sensor_state.state != STATE_UNAVAILABLE: self._async_update_temp(temp_sensor_state) if self._humidity_sensor: async_track_state_change(self.hass, self._humidity_sensor, self._async_humidity_sensor_changed) humidity_sensor_state = self.hass.states.get(self._humidity_sensor) if humidity_sensor_state and humidity_sensor_state.state != STATE_UNKNOWN and humidity_sensor_state.state != STATE_UNAVAILABLE: self._async_update_humidity(humidity_sensor_state) if self._power_sensor: async_track_state_change(self.hass, self._power_sensor, self._async_power_sensor_changed)
async def update_listener(hass: HomeAssistant, config_entry: ConfigEntry) -> None: """Update listener.""" # Get current code slots and new code slots, and remove entities for current code # slots that are being removed curr_slots = range(config_entry.data[CONF_START], config_entry.data[CONF_SLOTS] + 1) new_slots = range(config_entry.options[CONF_START], config_entry.options[CONF_SLOTS] + 1) await remove_generated_entities(hass, config_entry, list(set(curr_slots) - set(new_slots)), False) # If the path has changed delete the old base folder, otherwise if the lock name # has changed only delete the old lock folder if config_entry.options[CONF_PATH] != config_entry.data[CONF_PATH]: await hass.async_add_executor_job(delete_folder, hass.config.path(), config_entry.data[CONF_PATH]) elif config_entry.options[CONF_LOCK_NAME] != config_entry.data[ CONF_LOCK_NAME]: await hass.async_add_executor_job( delete_folder, hass.config.path(), config_entry.data[CONF_PATH], config_entry.data[CONF_LOCK_NAME], ) new_data = config_entry.options.copy() new_data.pop(CONF_GENERATE, None) hass.config_entries.async_update_entry( entry=config_entry, unique_id=config_entry.options[CONF_LOCK_NAME], data=new_data, ) primary_lock = KeymasterLock( config_entry.data[CONF_LOCK_NAME], config_entry.data[CONF_LOCK_ENTITY_ID], config_entry.data[CONF_ALARM_LEVEL_OR_USER_CODE_ENTITY_ID], config_entry.data[CONF_ALARM_TYPE_OR_ACCESS_CONTROL_ENTITY_ID], config_entry.data[CONF_SENSOR_NAME], ) hass.data[DOMAIN][config_entry.entry_id].update({ PRIMARY_LOCK: primary_lock, CHILD_LOCKS: [ KeymasterLock( lock_name, lock[CONF_LOCK_ENTITY_ID], lock[CONF_ALARM_LEVEL_OR_USER_CODE_ENTITY_ID], lock[CONF_ALARM_TYPE_OR_ACCESS_CONTROL_ENTITY_ID], ) for lock_name, lock in config_entry.data.get( CONF_CHILD_LOCKS, {}).items() ], }) servicedata = {"lockname": primary_lock.lock_name} await hass.services.async_call(DOMAIN, SERVICE_GENERATE_PACKAGE, servicedata) async def async_entity_state_listener(changed_entity: str, old_state: State, new_state: State) -> None: """Listener to track state changes to lock entities.""" handle_state_change(hass, config_entry, changed_entity, old_state, new_state) # Unsubscribe to any listeners so we can create new ones for unsub_listener in hass.data[DOMAIN][config_entry.entry_id].get( UNSUB_LISTENERS, []): unsub_listener() hass.data[DOMAIN][config_entry.entry_id].get(UNSUB_LISTENERS, []).clear() # Create new listeners hass.data[DOMAIN][config_entry.entry_id][UNSUB_LISTENERS].append( async_track_state_change(hass, primary_lock.lock_entity_id, async_entity_state_listener))
async def async_added_to_hass(self): """Register update dispatcher.""" event.async_track_state_change(self.hass, self.outdoorSensor, self.change) self._may_update = True
async def async_homeassistant_started_listener(evt: Event): """Start tracking state changes after HomeAssistant has started.""" # Listen to lock state changes so we can fire an event hass.data[DOMAIN][config_entry.entry_id][UNSUB_LISTENERS].append( async_track_state_change(hass, primary_lock.lock_entity_id, async_entity_state_listener))
async def test_track_state_change(hass): """Test track_state_change.""" # 2 lists to track how often our callbacks get called specific_runs = [] wildcard_runs = [] wildercard_runs = [] def specific_run_callback(entity_id, old_state, new_state): specific_runs.append(1) async_track_state_change(hass, "light.Bowl", specific_run_callback, "on", "off") @ha.callback def wildcard_run_callback(entity_id, old_state, new_state): wildcard_runs.append((old_state, new_state)) async_track_state_change(hass, "light.Bowl", wildcard_run_callback) @asyncio.coroutine def wildercard_run_callback(entity_id, old_state, new_state): wildercard_runs.append((old_state, new_state)) async_track_state_change(hass, MATCH_ALL, wildercard_run_callback) # Adding state to state machine hass.states.async_set("light.Bowl", "on") await hass.async_block_till_done() assert len(specific_runs) == 0 assert len(wildcard_runs) == 1 assert len(wildercard_runs) == 1 assert wildcard_runs[-1][0] is None assert wildcard_runs[-1][1] is not None # Set same state should not trigger a state change/listener hass.states.async_set("light.Bowl", "on") await hass.async_block_till_done() assert len(specific_runs) == 0 assert len(wildcard_runs) == 1 assert len(wildercard_runs) == 1 # State change off -> on hass.states.async_set("light.Bowl", "off") await hass.async_block_till_done() assert len(specific_runs) == 1 assert len(wildcard_runs) == 2 assert len(wildercard_runs) == 2 # State change off -> off hass.states.async_set("light.Bowl", "off", {"some_attr": 1}) await hass.async_block_till_done() assert len(specific_runs) == 1 assert len(wildcard_runs) == 3 assert len(wildercard_runs) == 3 # State change off -> on hass.states.async_set("light.Bowl", "on") await hass.async_block_till_done() assert len(specific_runs) == 1 assert len(wildcard_runs) == 4 assert len(wildercard_runs) == 4 hass.states.async_remove("light.bowl") await hass.async_block_till_done() assert len(specific_runs) == 1 assert len(wildcard_runs) == 5 assert len(wildercard_runs) == 5 assert wildcard_runs[-1][0] is not None assert wildcard_runs[-1][1] is None assert wildercard_runs[-1][0] is not None assert wildercard_runs[-1][1] is None # Set state for different entity id hass.states.async_set("switch.kitchen", "on") await hass.async_block_till_done() assert len(specific_runs) == 1 assert len(wildcard_runs) == 5 assert len(wildercard_runs) == 6
def __init__(self, hass, name, heater_entity_id, sensor_entity_id, min_temp, max_temp, target_temp, ac_mode, min_cycle_duration, cold_tolerance, hot_tolerance, keep_alive, initial_operation_mode, difference, away_temp, kp, ki, kd, pwm, autotune, noiseband): """Initialize the thermostat.""" self.hass = hass self._name = name self.heater_entity_id = heater_entity_id self.ac_mode = ac_mode self.min_cycle_duration = min_cycle_duration self._cold_tolerance = cold_tolerance self._hot_tolerance = hot_tolerance self._keep_alive = keep_alive self._initial_operation_mode = initial_operation_mode self._saved_target_temp = target_temp if target_temp is not None \ else away_temp if self.ac_mode: self._current_operation = STATE_COOL self._operation_list = [STATE_COOL, STATE_OFF] self.minOut = -difference self.maxOut = 0 else: self._current_operation = STATE_HEAT self._operation_list = [STATE_HEAT, STATE_OFF] self.minOut = 0 self.maxOut = difference if initial_operation_mode == STATE_OFF: self._enabled = False self._current_operation = STATE_OFF else: self._enabled = True self._active = False self._cur_temp = None self._min_temp = min_temp self._max_temp = max_temp self._target_temp = target_temp self._unit = hass.config.units.temperature_unit self._support_flags = SUPPORT_FLAGS if away_temp is not None: self._support_flags = SUPPORT_FLAGS | SUPPORT_AWAY_MODE self.difference = difference self._away_temp = away_temp self._is_away = False self.kp = kp self.ki = ki self.kd = kd self.pwm = pwm self.autotune = autotune self.sensor_entity_id = sensor_entity_id self.time_changed = time.time() if self.autotune != "none": self.pidAutotune = pid_controller.PIDAutotune(self._target_temp, self.difference, self._keep_alive.seconds, self._keep_alive.seconds, self.minOut, self.maxOut, noiseband, time.time) _LOGGER.warning("Autotune will run with the next Setpoint Value you set." "changes, submited after doesn't have any effekt until it's finished") else: self.pidController = pid_controller.PIDArduino(self._keep_alive.seconds, self.kp, self.ki, self.kd, self.minOut, self.maxOut, time.time) async_track_state_change( hass, sensor_entity_id, self._async_sensor_changed) async_track_state_change( hass, heater_entity_id, self._async_switch_changed) if self._keep_alive: async_track_time_interval( hass, self._async_keep_alive, self._keep_alive) sensor_state = hass.states.get(sensor_entity_id) if sensor_state and sensor_state.state != STATE_UNKNOWN: self._async_update_temp(sensor_state)
async def async_added_to_hass(self): """Run when entity about to be added.""" await super().async_added_to_hass() # Add listener async_track_state_change( self.hass, self.sensor_entity_id, self._async_sensor_changed) if self._hvac_mode == HVAC_MODE_HEAT: async_track_state_change( self.hass, self.heaters_entity_ids, self._async_switch_changed) elif self._hvac_mode == HVAC_MODE_COOL: async_track_state_change( self.hass, self.coolers_entity_ids, self._async_switch_changed) async_track_state_change( self.hass, self.target_entity_id, self._async_target_changed) if self._related_climate is not None: async_track_state_change( self.hass, self._related_climate, self._async_switch_changed) @callback def _async_startup(event): """Init on startup.""" sensor_state = self._getStateSafe(self.sensor_entity_id) if sensor_state and sensor_state != STATE_UNKNOWN: self._async_update_temp(sensor_state) target_state = self._getStateSafe(self.target_entity_id) if target_state and \ target_state != STATE_UNKNOWN and \ self._hvac_mode != HVAC_MODE_HEAT_COOL: self._async_update_program_temp(target_state) self.hass.bus.async_listen_once( EVENT_HOMEASSISTANT_START, _async_startup) # Check If we have an old state old_state = await self.async_get_last_state() _LOGGER.info("old state: %s", old_state) if old_state is not None: # If we have no initial temperature, restore if self._target_temp is None: # If we have a previously saved temperature if old_state.attributes.get(ATTR_TEMPERATURE) is None: target_entity_state = self._getStateSafe(target_entity_id) if target_entity_state is not None: self._target_temp = float(target_entity_state) else: self._target_temp = float((self._min_temp + self._max_temp)/2) _LOGGER.warning("Undefined target temperature," "falling back to %s", self._target_temp) else: self._target_temp = float( old_state.attributes[ATTR_TEMPERATURE]) if (self._initial_hvac_mode is None and old_state.state is not None): self._hvac_mode = \ old_state.state self._enabled = self._hvac_mode != HVAC_MODE_OFF else: # No previous state, try and restore defaults if self._target_temp is None: self._target_temp = float((self._min_temp + self._max_temp)/2) _LOGGER.warning("No previously saved temperature, setting to %s", self._target_temp) # Set default state to off if not self._hvac_mode: self._hvac_mode = HVAC_MODE_OFF
async def async_attach_trigger( hass: HomeAssistant, config, action, automation_info, *, platform_type: str = "state", ) -> CALLBACK_TYPE: """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, MATCH_ALL) time_delta = config.get(CONF_FOR) template.attach(hass, time_delta) match_all = from_state == MATCH_ALL and to_state == MATCH_ALL unsub_track_same = {} period: Dict[str, timedelta] = {} @callback def state_automation_listener(entity, from_s, to_s): """Listen for state changes and calls action.""" @callback def call_action(): """Call action with right context.""" hass.async_run_job( action( { "trigger": { "platform": platform_type, "entity_id": entity, "from_state": from_s, "to_state": to_s, "for": time_delta if not time_delta else period[entity], } }, context=to_s.context, )) # Ignore changes to state attributes if from/to is in use if (not match_all and from_s is not None and to_s is not None and from_s.state == to_s.state): return if not time_delta: call_action() return variables = { "trigger": { "platform": "state", "entity_id": entity, "from_state": from_s, "to_state": to_s, } } try: if isinstance(time_delta, template.Template): period[entity] = vol.All( cv.time_period, cv.positive_timedelta)(time_delta.async_render(variables)) elif isinstance(time_delta, dict): time_delta_data = {} time_delta_data.update( template.render_complex(time_delta, variables)) period[entity] = vol.All( cv.time_period, cv.positive_timedelta)(time_delta_data) else: period[entity] = time_delta except (exceptions.TemplateError, vol.Invalid) as ex: _LOGGER.error("Error rendering '%s' for template: %s", automation_info["name"], ex) return unsub_track_same[entity] = async_track_same_state( hass, period[entity], call_action, lambda _, _2, to_state: to_state.state == to_s.state, entity_ids=entity, ) unsub = async_track_state_change(hass, entity_id, state_automation_listener, from_state, to_state) @callback def async_remove(): """Remove state listeners async.""" unsub() for async_remove in unsub_track_same.values(): async_remove() unsub_track_same.clear() return async_remove
def async_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) time_delta = config.get(CONF_FOR) value_template = config.get(CONF_VALUE_TEMPLATE) async_remove_track_same = None already_triggered = False if value_template is not None: value_template.hass = hass @callback def check_numeric_state(entity, from_s, to_s): """Return True if criteria are now met.""" if to_s is None: return False variables = { 'trigger': { 'platform': 'numeric_state', 'entity_id': entity, 'below': below, 'above': above, } } return condition.async_numeric_state(hass, to_s, below, above, value_template, variables) @callback def state_automation_listener(entity, from_s, to_s): """Listen for state changes and calls action.""" nonlocal already_triggered, async_remove_track_same @callback def call_action(): """Call action with right context.""" hass.async_run_job( action, { 'trigger': { 'platform': 'numeric_state', 'entity_id': entity, 'below': below, 'above': above, 'from_state': from_s, 'to_state': to_s, } }) matching = check_numeric_state(entity, from_s, to_s) if matching and not already_triggered: if time_delta: async_remove_track_same = async_track_same_state( hass, time_delta, call_action, entity_ids=entity_id, async_check_same_func=check_numeric_state) else: call_action() already_triggered = matching unsub = async_track_state_change(hass, entity_id, state_automation_listener) @callback def async_remove(): """Remove state listeners async.""" unsub() if async_remove_track_same: async_remove_track_same() # pylint: disable=not-callable return async_remove
async def async_added_to_hass(self): """Register callbacks.""" @callback def filter_sensor_state_listener(entity, old_state, new_state, update_ha=True): """Handle device state changes.""" if new_state.state in [STATE_UNKNOWN, STATE_UNAVAILABLE]: return temp_state = new_state try: for filt in self._filters: filtered_state = filt.filter_state(copy(temp_state)) _LOGGER.debug( "%s(%s=%s) -> %s", filt.name, self._entity, temp_state.state, "skip" if filt.skip_processing else filtered_state.state) if filt.skip_processing: return temp_state = filtered_state except ValueError: _LOGGER.error("Could not convert state: %s to number", self._state) return self._state = temp_state.state if self._icon is None: self._icon = new_state.attributes.get(ATTR_ICON, ICON) if self._unit_of_measurement is None: self._unit_of_measurement = new_state.attributes.get( ATTR_UNIT_OF_MEASUREMENT) if update_ha: self.async_schedule_update_ha_state() if 'recorder' in self.hass.config.components: history_list = [] largest_window_items = 0 largest_window_time = timedelta(0) # Determine the largest window_size by type for filt in self._filters: if filt.window_unit == WINDOW_SIZE_UNIT_NUMBER_EVENTS\ and largest_window_items < filt.window_size: largest_window_items = filt.window_size elif filt.window_unit == WINDOW_SIZE_UNIT_TIME\ and largest_window_time < filt.window_size: largest_window_time = filt.window_size # Retrieve the largest window_size of each type if largest_window_items > 0: filter_history = await self.hass.async_add_job( partial(history.get_last_state_changes, self.hass, largest_window_items, entity_id=self._entity)) history_list.extend( [state for state in filter_history[self._entity]]) if largest_window_time > timedelta(seconds=0): start = dt_util.utcnow() - largest_window_time filter_history = await self.hass.async_add_job( partial(history.state_changes_during_period, self.hass, start, entity_id=self._entity)) history_list.extend([ state for state in filter_history[self._entity] if state not in history_list ]) # Sort the window states history_list = sorted(history_list, key=lambda s: s.last_updated) _LOGGER.debug("Loading from history: %s", [(s.state, s.last_updated) for s in history_list]) # Replay history through the filter chain prev_state = None for state in history_list: filter_sensor_state_listener(self._entity, prev_state, state, False) prev_state = state async_track_state_change(self.hass, self._entity, filter_sensor_state_listener)
def qr_sensor_startup(event): """Update template on startup.""" async_track_state_change(self.hass, self._entities, qr_state_listener)
def __init__(self, hass, name, ip_addr, port, mac_addr, timeout, target_temp_step, temp_sensor_entity_id, lights_entity_id, xfan_entity_id, health_entity_id, powersave_entity_id, sleep_entity_id, eightdegheat_entity_id, hvac_modes, fan_modes, swing_modes, encryption_key=None, uid=None): _LOGGER.info('Initialize the GREE climate device') self.hass = hass self._name = name self._ip_addr = ip_addr self._port = port self._mac_addr = mac_addr.decode('utf-8').lower() self._timeout = timeout self._target_temperature = None self._target_temperature_step = target_temp_step self._unit_of_measurement = hass.config.units.temperature_unit self._current_temperature = None self._temp_sensor_entity_id = temp_sensor_entity_id self._lights_entity_id = lights_entity_id self._xfan_entity_id = xfan_entity_id self._health_entity_id = health_entity_id self._powersave_entity_id = powersave_entity_id self._sleep_entity_id = sleep_entity_id self._eightdegheat_entity_id = eightdegheat_entity_id self._hvac_mode = None self._fan_mode = None self._swing_mode = None self._current_lights = None self._current_xfan = None self._current_health = None self._current_powersave = None self._current_sleep = None self._current_eightdegheat = None self._hvac_modes = hvac_modes self._fan_modes = fan_modes self._swing_modes = swing_modes if encryption_key: _LOGGER.info( 'Using configured encryption key: {}'.format(encryption_key)) self._encryption_key = encryption_key.encode("utf8") else: self._encryption_key = self.GetDeviceKey().encode("utf8") _LOGGER.info('Fetched device encrytion key: %s' % str(self._encryption_key)) if uid: self._uid = uid else: self._uid = 0 self._acOptions = { 'Pow': None, 'Mod': None, 'SetTem': None, 'WdSpd': None, 'Air': None, 'Blo': None, 'Health': None, 'SwhSlp': None, 'Lig': None, 'SwingLfRig': None, 'SwUpDn': None, 'Quiet': None, 'Tur': None, 'StHt': None, 'TemUn': None, 'HeatCoolType': None, 'TemRec': None, 'SvSt': None, 'SlpMod': None } self._firstTimeRun = True # Cipher to use to encrypt/decrypt self.CIPHER = AES.new(self._encryption_key, AES.MODE_ECB) if temp_sensor_entity_id: _LOGGER.info('Setting up temperature sensor: ' + str(temp_sensor_entity_id)) async_track_state_change(hass, temp_sensor_entity_id, self._async_temp_sensor_changed) if lights_entity_id: _LOGGER.info('Setting up lights entity: ' + str(lights_entity_id)) async_track_state_change(hass, lights_entity_id, self._async_lights_entity_state_changed) if xfan_entity_id: _LOGGER.info('Setting up xfan entity: ' + str(xfan_entity_id)) async_track_state_change(hass, xfan_entity_id, self._async_xfan_entity_state_changed) if health_entity_id: _LOGGER.info('Setting up health entity: ' + str(health_entity_id)) async_track_state_change(hass, health_entity_id, self._async_health_entity_state_changed) if powersave_entity_id: _LOGGER.info('Setting up powersave entity: ' + str(powersave_entity_id)) async_track_state_change( hass, powersave_entity_id, self._async_powersave_entity_state_changed) if sleep_entity_id: _LOGGER.info('Setting up sleep entity: ' + str(sleep_entity_id)) async_track_state_change(hass, sleep_entity_id, self._async_sleep_entity_state_changed) if eightdegheat_entity_id: _LOGGER.info('Setting up 8℃ heat entity: ' + str(eightdegheat_entity_id)) async_track_state_change( hass, eightdegheat_entity_id, self._async_eightdegheat_entity_state_changed)
def async_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) async_remove_state_for_cancel = None async_remove_state_for_listener = None @callback def state_automation_listener(entity, from_s, to_s): """Listen for state changes and calls action.""" nonlocal async_remove_state_for_cancel, async_remove_state_for_listener def call_action(): """Call action with right context.""" hass.async_run_job( 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 @callback def state_for_listener(now): """Fire on state changes after a delay and calls action.""" async_remove_state_for_cancel() call_action() @callback 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 async_remove_state_for_listener() async_remove_state_for_cancel() async_remove_state_for_listener = async_track_point_in_utc_time( hass, state_for_listener, dt_util.utcnow() + time_delta) async_remove_state_for_cancel = async_track_state_change( hass, entity, state_for_cancel_listener) unsub = async_track_state_change(hass, entity_id, state_automation_listener, from_state, to_state) def async_remove(): """Remove state listeners async.""" unsub() # pylint: disable=not-callable if async_remove_state_for_cancel is not None: async_remove_state_for_cancel() if async_remove_state_for_listener is not None: async_remove_state_for_listener() return async_remove
async def async_enable_proactive_mode(hass, smart_home_config): """Enable the proactive mode. Proactive mode makes this component report state changes to Alexa. """ # Validate we can get access token. await smart_home_config.async_get_access_token() @callback def extra_significant_check( hass: HomeAssistant, old_state: str, old_attrs: dict, old_extra_arg: dict, new_state: str, new_attrs: dict, new_extra_arg: dict, ): """Check if the serialized data has changed.""" return old_extra_arg is not None and old_extra_arg != new_extra_arg checker = await create_checker(hass, DOMAIN, extra_significant_check) async def async_entity_state_listener( changed_entity: str, old_state: State | None, new_state: State | None, ): if not hass.is_running: return if not new_state: return if new_state.domain not in ENTITY_ADAPTERS: return if not smart_home_config.should_expose(changed_entity): _LOGGER.debug("Not exposing %s because filtered by config", changed_entity) return alexa_changed_entity: AlexaEntity = ENTITY_ADAPTERS[new_state.domain]( hass, smart_home_config, new_state) # Determine how entity should be reported on should_report = False should_doorbell = False for interface in alexa_changed_entity.interfaces(): if not should_report and interface.properties_proactively_reported( ): should_report = True if interface.name() == "Alexa.DoorbellEventSource": should_doorbell = True break if not should_report and not should_doorbell: return if should_doorbell: if new_state.state == STATE_ON: await async_send_doorbell_event_message( hass, smart_home_config, alexa_changed_entity) return alexa_properties = list(alexa_changed_entity.serialize_properties()) if not checker.async_is_significant_change(new_state, extra_arg=alexa_properties): return await async_send_changereport_message(hass, smart_home_config, alexa_changed_entity, alexa_properties) return async_track_state_change(hass, MATCH_ALL, async_entity_state_listener)
def template_bsensor_startup(event): """Update template on startup.""" async_track_state_change(self.hass, self._entities, template_bsensor_state_listener) self.hass.async_add_job(self.async_check_state)
def __init__(self, hass, name, heater_entity_id, sensor_entity_id, min_temp, max_temp, target_temp, ac_mode, min_cycle_duration, cold_tolerance, hot_tolerance, keep_alive, initial_operation_mode, away_temp, precision, heat_entity_id, regulation_entity_id, state_entity_id, regulation_duration, regulation_nb_duration, regulation_delta ): """Initialize the thermostat.""" self.hass = hass self._name = name self.heater_entity_id = heater_entity_id self.ac_mode = ac_mode self.min_cycle_duration = min_cycle_duration self._cold_tolerance = cold_tolerance self._hot_tolerance = hot_tolerance self._keep_alive = keep_alive self._initial_operation_mode = initial_operation_mode self._saved_target_temp = target_temp if target_temp is not None \ else away_temp self._temp_precision = precision if self.ac_mode: self._current_operation = STATE_COOL self._operation_list = [STATE_COOL, STATE_OFF] else: self._current_operation = STATE_HEAT self._operation_list = [STATE_HEAT, STATE_OFF] if initial_operation_mode == STATE_OFF: self._enabled = False self._current_operation = STATE_OFF else: self._enabled = True self._active = False self._cur_temp = None self._temp_lock = asyncio.Lock() self._min_temp = min_temp self._max_temp = max_temp self._target_temp = target_temp self._unit = hass.config.units.temperature_unit self._support_flags = SUPPORT_FLAGS if away_temp is not None: self._support_flags = SUPPORT_FLAGS | SUPPORT_AWAY_MODE self._away_temp = away_temp self._is_away = False async_track_state_change( hass, sensor_entity_id, self._async_sensor_changed) async_track_state_change( hass, heater_entity_id, self._async_switch_changed) if self._keep_alive: async_track_time_interval( hass, self._async_control_heating, self._keep_alive) sensor_state = hass.states.get(sensor_entity_id) if sensor_state and sensor_state.state != STATE_UNKNOWN: self._async_update_temp(sensor_state) self._heat_entity_id = heat_entity_id self._regulation_entity_id = regulation_entity_id self._state_entity_id = state_entity_id self._regulation_duration = regulation_duration self._regulation_nb_duration = regulation_nb_duration self._regulation_delta = regulation_delta self._nb_tick_regulation = 0 if self._regulation_duration: async_track_time_interval( hass, self._async_regulation, self._regulation_duration)
def template_sensor_startup(event): """Update template on startup.""" async_track_state_change( self.hass, self._entities, template_sensor_state_listener) self.async_schedule_update_ha_state(True)
def template_sensor_startup(event): """Update on startup.""" async_track_state_change(self.hass, self._entity, template_sensor_state_listener) self.hass.async_add_job(self.async_update_ha_state(True))
async def update_listener(hass: HomeAssistant, config_entry: ConfigEntry) -> None: """Update listener.""" # No need to update if the options match the data if not config_entry.options: return # If the path has changed delete the old base folder, otherwise if the lock name # has changed only delete the old lock folder if config_entry.options[CONF_PATH] != config_entry.data[CONF_PATH]: await hass.async_add_executor_job(delete_folder, hass.config.path(), config_entry.data[CONF_PATH]) elif config_entry.options[CONF_LOCK_NAME] != config_entry.data[ CONF_LOCK_NAME]: await hass.async_add_executor_job( delete_folder, hass.config.path(), config_entry.data[CONF_PATH], config_entry.data[CONF_LOCK_NAME], ) start_from = config_entry.data[CONF_START] code_slots = config_entry.data[CONF_SLOTS] old_slots = list(range(start_from, start_from + code_slots)) start_from = config_entry.options[CONF_START] code_slots = config_entry.options[CONF_SLOTS] new_slots = list(range(start_from, start_from + code_slots)) new_data = config_entry.options.copy() new_data.pop(CONF_GENERATE, None) hass.config_entries.async_update_entry( entry=config_entry, unique_id=config_entry.options[CONF_LOCK_NAME], data=new_data, options={}, ) primary_lock, child_locks = await generate_keymaster_locks( hass, config_entry) hass.data[DOMAIN][config_entry.entry_id].update({ PRIMARY_LOCK: primary_lock, CHILD_LOCKS: child_locks, }) servicedata = {"lockname": primary_lock.lock_name} await hass.services.async_call(DOMAIN, SERVICE_GENERATE_PACKAGE, servicedata, blocking=True) if old_slots != new_slots: async_dispatcher_send( hass, f"{DOMAIN}_{config_entry.entry_id}_code_slots_changed", old_slots, new_slots, ) # Unsubscribe to any listeners so we can create new ones for unsub_listener in hass.data[DOMAIN][config_entry.entry_id].get( UNSUB_LISTENERS, []): unsub_listener() hass.data[DOMAIN][config_entry.entry_id].get(UNSUB_LISTENERS, []).clear() if using_zwave_js(hass): hass.data[DOMAIN][config_entry.entry_id][UNSUB_LISTENERS].append( hass.bus.async_listen( ZWAVE_JS_EVENT, functools.partial(handle_zwave_js_event, hass, config_entry), )) # Check if alarm type/alarm level sensors are specified, in which case # we need to listen for lock state changes and derive the action from those # sensors locks_to_watch = [] for lock in [primary_lock, *child_locks]: if (lock.alarm_level_or_user_code_entity_id not in ( None, "sensor.fake", ) and lock.alarm_type_or_access_control_entity_id not in (None, "sensor.fake")): locks_to_watch.append(lock) if locks_to_watch: # Create new listeners for lock state changes hass.data[DOMAIN][config_entry.entry_id][UNSUB_LISTENERS].append( async_track_state_change( hass, [lock.lock_entity_id for lock in locks_to_watch], functools.partial(handle_state_change, hass, config_entry), from_state=[STATE_LOCKED, STATE_UNLOCKED], to_state=[STATE_LOCKED, STATE_UNLOCKED], ))
def run(self): """Method called by accessory after driver is started.""" state = self.hass.states.get(self.entity_id) self.update_state_callback(new_state=state) async_track_state_change(self.hass, self.entity_id, self.update_state_callback)
async def async_setup(hass, config): """Set up the triggers to control lights based on device presence.""" logger = logging.getLogger(__name__) device_tracker = hass.components.device_tracker group = hass.components.group light = hass.components.light person = hass.components.person conf = config[DOMAIN] disable_turn_off = conf.get(CONF_DISABLE_TURN_OFF) light_group = conf.get(CONF_LIGHT_GROUP, light.ENTITY_ID_ALL_LIGHTS) light_profile = conf.get(CONF_LIGHT_PROFILE) device_group = conf.get(CONF_DEVICE_GROUP, device_tracker.ENTITY_ID_ALL_DEVICES) device_entity_ids = group.get_entity_ids(device_group, device_tracker.DOMAIN) device_entity_ids.extend(group.get_entity_ids(device_group, person.DOMAIN)) if not device_entity_ids: logger.error("No devices found to track") return False # Get the light IDs from the specified group light_ids = group.get_entity_ids(light_group, light.DOMAIN) if not light_ids: logger.error("No lights found to turn on") return False def calc_time_for_light_when_sunset(): """Calculate the time when to start fading lights in when sun sets. Returns None if no next_setting data available. Async friendly. """ next_setting = get_astral_event_next(hass, SUN_EVENT_SUNSET) if not next_setting: return None return next_setting - LIGHT_TRANSITION_TIME * len(light_ids) def async_turn_on_before_sunset(light_id): """Turn on lights.""" if not device_tracker.is_on() or light.is_on(light_id): return hass.async_create_task( hass.services.async_call( DOMAIN_LIGHT, SERVICE_TURN_ON, { ATTR_ENTITY_ID: light_id, ATTR_TRANSITION: LIGHT_TRANSITION_TIME.seconds, ATTR_PROFILE: light_profile, }, ) ) def async_turn_on_factory(light_id): """Generate turn on callbacks as factory.""" @callback def async_turn_on_light(now): """Turn on specific light.""" async_turn_on_before_sunset(light_id) return async_turn_on_light # Track every time sun rises so we can schedule a time-based # pre-sun set event @callback def schedule_light_turn_on(now): """Turn on all the lights at the moment sun sets. We will schedule to have each light start after one another and slowly transition in. """ start_point = calc_time_for_light_when_sunset() if not start_point: return for index, light_id in enumerate(light_ids): async_track_point_in_utc_time( hass, async_turn_on_factory(light_id), start_point + index * LIGHT_TRANSITION_TIME, ) async_track_point_in_utc_time( hass, schedule_light_turn_on, get_astral_event_next(hass, SUN_EVENT_SUNRISE) ) # If the sun is already above horizon schedule the time-based pre-sun set # event. if is_up(hass): schedule_light_turn_on(None) @callback def check_light_on_dev_state_change(entity, old_state, new_state): """Handle tracked device state changes.""" lights_are_on = group.is_on(light_group) light_needed = not (lights_are_on or is_up(hass)) # These variables are needed for the elif check now = dt_util.utcnow() start_point = calc_time_for_light_when_sunset() # Do we need lights? if light_needed: logger.info("Home coming event for %s. Turning lights on", entity) hass.async_create_task( hass.services.async_call( DOMAIN_LIGHT, SERVICE_TURN_ON, {ATTR_ENTITY_ID: light_ids, ATTR_PROFILE: light_profile}, ) ) # Are we in the time span were we would turn on the lights # if someone would be home? # Check this by seeing if current time is later then the point # in time when we would start putting the lights on. elif start_point and start_point < now < get_astral_event_next( hass, SUN_EVENT_SUNSET ): # Check for every light if it would be on if someone was home # when the fading in started and turn it on if so for index, light_id in enumerate(light_ids): if now > start_point + index * LIGHT_TRANSITION_TIME: hass.async_create_task( hass.services.async_call( DOMAIN_LIGHT, SERVICE_TURN_ON, {ATTR_ENTITY_ID: light_id} ) ) else: # If this light didn't happen to be turned on yet so # will all the following then, break. break async_track_state_change( hass, device_entity_ids, check_light_on_dev_state_change, STATE_NOT_HOME, STATE_HOME, ) if disable_turn_off: return True @callback def turn_off_lights_when_all_leave(entity, old_state, new_state): """Handle device group state change.""" if not group.is_on(light_group): return logger.info("Everyone has left but there are lights on. Turning them off") hass.async_create_task( hass.services.async_call( DOMAIN_LIGHT, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: light_ids} ) ) async_track_state_change( hass, device_group, turn_off_lights_when_all_leave, STATE_HOME, STATE_NOT_HOME ) return True
def async_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 = get_deprecated(config, CONF_TO, CONF_STATE, MATCH_ALL) time_delta = config.get(CONF_FOR) async_remove_state_for_cancel = None async_remove_state_for_listener = None match_all = (from_state == MATCH_ALL and to_state == MATCH_ALL) @callback def clear_listener(): """Clear all unsub listener.""" nonlocal async_remove_state_for_cancel, async_remove_state_for_listener # pylint: disable=not-callable if async_remove_state_for_listener is not None: async_remove_state_for_listener() async_remove_state_for_listener = None if async_remove_state_for_cancel is not None: async_remove_state_for_cancel() async_remove_state_for_cancel = None @callback def state_automation_listener(entity, from_s, to_s): """Listen for state changes and calls action.""" nonlocal async_remove_state_for_cancel, async_remove_state_for_listener def call_action(): """Call action with right context.""" hass.async_run_job( action, { 'trigger': { 'platform': 'state', 'entity_id': entity, 'from_state': from_s, 'to_state': to_s, 'for': time_delta, } }) # Ignore changes to state attributes if from/to is in use if (not match_all and from_s is not None and to_s is not None and from_s.last_changed == to_s.last_changed): return if time_delta is None: call_action() return @callback def state_for_listener(now): """Fire on state changes after a delay and calls action.""" nonlocal async_remove_state_for_listener async_remove_state_for_listener = None clear_listener() call_action() @callback 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 clear_listener() # cleanup previous listener clear_listener() async_remove_state_for_listener = async_track_point_in_utc_time( hass, state_for_listener, dt_util.utcnow() + time_delta) async_remove_state_for_cancel = async_track_state_change( hass, entity, state_for_cancel_listener) unsub = async_track_state_change(hass, entity_id, state_automation_listener, from_state, to_state) @callback def async_remove(): """Remove state listeners async.""" unsub() clear_listener() return async_remove