def get_entities_to_track(self) -> list[str, TrackTemplate]: track_templates = [] if isinstance(self._power, Template): track_templates.append(TrackTemplate(self._power, None)) if self._per_state_power: for power in list(self._per_state_power.values()): if isinstance(power, Template): track_templates.append(TrackTemplate(power, None)) return track_templates
async def handle_render_template(hass, connection, msg): """Handle render_template command.""" template_str = msg["template"] template_obj = template.Template(template_str, hass) variables = msg.get("variables") timeout = msg.get("timeout") info = None if timeout: try: timed_out = await template_obj.async_render_will_timeout( timeout, strict=msg["strict"]) except TemplateError as ex: connection.send_error(msg["id"], const.ERR_TEMPLATE_ERROR, str(ex)) return if timed_out: connection.send_error( msg["id"], const.ERR_TEMPLATE_ERROR, f"Exceeded maximum execution time of {timeout}s", ) return @callback def _template_listener(event, updates): nonlocal info track_template_result = updates.pop() result = track_template_result.result if isinstance(result, TemplateError): connection.send_error(msg["id"], const.ERR_TEMPLATE_ERROR, str(result)) return connection.send_message( messages.event_message( msg["id"], { "result": result, "listeners": info.listeners } # type: ignore )) try: info = async_track_template_result( hass, [TrackTemplate(template_obj, variables)], _template_listener, raise_on_template_error=True, strict=msg["strict"], ) except TemplateError as ex: connection.send_error(msg["id"], const.ERR_TEMPLATE_ERROR, str(ex)) return connection.subscriptions[msg["id"]] = info.async_remove connection.send_result(msg["id"]) hass.loop.call_soon_threadsafe(info.async_refresh)
async def _async_template_startup(self, *_) -> None: template_var_tups: list[TrackTemplate] = [] has_availability_template = False values = {"this": TemplateStateFromEntityId(self.hass, self.entity_id)} for template, attributes in self._template_attrs.items(): template_var_tup = TrackTemplate(template, values) is_availability_template = False for attribute in attributes: # pylint: disable-next=protected-access if attribute._attribute == "_attr_available": has_availability_template = True is_availability_template = True attribute.async_setup() # Insert the availability template first in the list if is_availability_template: template_var_tups.insert(0, template_var_tup) else: template_var_tups.append(template_var_tup) result_info = async_track_template_result( self.hass, template_var_tups, self._handle_results, has_super_template=has_availability_template, ) self.async_on_remove(result_info.async_remove) self._async_update = result_info.async_refresh result_info.async_refresh()
async def _async_wait_template_step(self): """Handle a wait template.""" if CONF_TIMEOUT in self._action: delay = self._get_pos_time_period_template(CONF_TIMEOUT).total_seconds() else: delay = None self._script.last_action = self._action.get(CONF_ALIAS, "wait template") self._log( "Executing step %s%s", self._script.last_action, "" if delay is None else f" (timeout: {timedelta(seconds=delay)})", ) self._variables["wait"] = {"remaining": delay, "completed": False} wait_template = self._action[CONF_WAIT_TEMPLATE] wait_template.hass = self._hass # check if condition already okay if condition.async_template(self._hass, wait_template, self._variables): self._variables["wait"]["completed"] = True return @callback def _async_script_wait(event, updates): """Handle script after template condition is true.""" self._variables["wait"] = { "remaining": to_context.remaining if to_context else delay, "completed": True, } done.set() to_context = None info = async_track_template_result( self._hass, [TrackTemplate(wait_template, self._variables)], _async_script_wait, ) unsub = info.async_remove self._changed() done = asyncio.Event() tasks = [ self._hass.async_create_task(flag.wait()) for flag in (self._stop, done) ] try: async with timeout(delay) as to_context: await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED) except asyncio.TimeoutError as ex: if not self._action.get(CONF_CONTINUE_ON_TIMEOUT, True): self._log(_TIMEOUT_MSG) raise _StopScript from ex self._variables["wait"]["remaining"] = 0.0 finally: for task in tasks: task.cancel() unsub()
def __init__(self, hass, name, remote_entity_id, commands, min_temp, max_temp, target_temp, target_temp_step, hvac_modes, fan_modes, preset_modes, default_hvac_mode, default_fan_mode, default_preset_mode, temp_entity_id, power_template, domain, service, prefix): """Representation of a Xiaomi Remote Climate device.""" self.hass = hass self._name = name self._remote_entity_id = remote_entity_id self._commands = commands self._domain = domain self._service = service self._prefix = prefix self._min_temp = min_temp self._max_temp = max_temp self._target_temperature = target_temp self._target_temperature_step = target_temp_step self._unit_of_measurement = hass.config.units.temperature_unit self._current_temperature = None self._default_hvac_mode = default_hvac_mode self._current_hvac_mode = default_hvac_mode self._last_hvac_mode = default_hvac_mode self._default_fan_mode = default_fan_mode self._current_fan_mode = default_fan_mode self._last_fan_mode = default_fan_mode self._default_preset_mode = default_preset_mode self._current_preset_mode = default_preset_mode self._last_preset_mode = default_preset_mode self._temp_entity_id = temp_entity_id self._power_template = power_template self._hvac_modes = hvac_modes self._fan_modes = fan_modes self._preset_modes = preset_modes self._support_flags = SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE if preset_modes: self._support_flags |= SUPPORT_PRESET_MODE self._enabled_flags = self._support_flags if temp_entity_id: async_track_state_change(hass, temp_entity_id, self._async_temp_changed) if power_template: result = async_track_template_result( self.hass, [TrackTemplate(power_template, None)], self._async_power_changed, ) result.async_refresh() self.async_on_remove(result.async_remove)
async def _async_template_startup(self, *_) -> None: template_var_tups = [] for template, attributes in self._template_attrs.items(): template_var_tups.append(TrackTemplate(template, None)) for attribute in attributes: attribute.async_setup() result_info = async_track_template_result(self.hass, template_var_tups, self._handle_results) self.async_on_remove(result_info.async_remove) self._async_update = result_info.async_refresh result_info.async_refresh()
async def _async_template_startup(self, *_) -> None: # _handle_results will not write state until "_async_update" is set template_var_tups = [ TrackTemplate(template, None) for template in self._template_attrs ] result_info = async_track_template_result(self.hass, template_var_tups, self._handle_results) self.async_on_remove(result_info.async_remove) result_info.async_refresh() self.async_write_ha_state() self._async_update = result_info.async_refresh
def __init__(self, hass, config): self.hass = hass self.hub = hass.data[DOMAIN][HUB] = CrestronXsig() self.port = config.get(CONF_PORT) self.context = Context() self.to_hub = {} self.hub.register_sync_all_joins_callback(self.sync_joins_to_hub) if CONF_TO_HUB in config: track_templates = [] for entity in config[CONF_TO_HUB]: template_string = None if CONF_VALUE_TEMPLATE in entity: template = entity[CONF_VALUE_TEMPLATE] self.to_hub[entity[CONF_JOIN]] = template track_templates.append(TrackTemplate(template, None)) elif CONF_ATTRIBUTE in entity and CONF_ENTITY_ID in entity: template_string = ( "{{state_attr('" + entity[CONF_ENTITY_ID] + "','" + entity[CONF_ATTRIBUTE] + "')}}" ) template = Template(template_string, hass) self.to_hub[entity[CONF_JOIN]] = template track_templates.append(TrackTemplate(template, None)) elif CONF_ENTITY_ID in entity: template_string = "{{states('" + entity[CONF_ENTITY_ID] + "')}}" template = Template(template_string, hass) self.to_hub[entity[CONF_JOIN]] = template track_templates.append(TrackTemplate(template, None)) self.tracker = async_track_template_result( self.hass, track_templates, self.template_change_callback ) if CONF_FROM_HUB in config: self.from_hub = config[CONF_FROM_HUB] self.hub.register_callback(self.join_change_callback)
async def async_added_to_hass(self): """Subscribe to children and template state changes.""" @callback def _async_on_dependency_update(event): """Update ha state when dependencies update.""" self.async_set_context(event.context) self.async_schedule_update_ha_state(True) @callback def _async_on_template_update(event, updates): """Update ha state when dependencies update.""" result = updates.pop().result if isinstance(result, TemplateError): self._state_template_result = None else: self._state_template_result = result if event: self.async_set_context(event.context) self.async_schedule_update_ha_state(True) if self._state_template is not None: result = async_track_template_result( self.hass, [TrackTemplate(self._state_template, None)], _async_on_template_update, ) self.hass.bus.async_listen_once( EVENT_HOMEASSISTANT_START, callback(lambda _: result.async_refresh()) ) self.async_on_remove(result.async_remove) depend = copy(self._children) for entity in self._attrs.values(): depend.append(entity[0]) self.async_on_remove( self.hass.helpers.event.async_track_state_change_event( list(set(depend)), _async_on_dependency_update ) )
def handle_render_template(hass, connection, msg): """Handle render_template command.""" template = msg["template"] template.hass = hass variables = msg.get("variables") info = None @callback def _template_listener(event, updates): nonlocal info track_template_result = updates.pop() result = track_template_result.result if isinstance(result, TemplateError): _LOGGER.error( "TemplateError('%s') " "while processing template '%s'", result, track_template_result.template, ) result = None connection.send_message( messages.event_message( msg["id"], { "result": result, "listeners": info.listeners } # type: ignore )) info = async_track_template_result(hass, [TrackTemplate(template, variables)], _template_listener) connection.subscriptions[msg["id"]] = info.async_remove connection.send_result(msg["id"]) hass.loop.call_soon_threadsafe(info.async_refresh)
async def async_attach_trigger(hass, config, action, automation_info, *, platform_type="template"): """Listen for state changes based on configuration.""" trigger_id = automation_info.get("trigger_id") if automation_info else None value_template = config.get(CONF_VALUE_TEMPLATE) value_template.hass = hass time_delta = config.get(CONF_FOR) template.attach(hass, time_delta) delay_cancel = None job = HassJob(action) armed = False # Arm at setup if the template is already false. try: if not result_as_boolean( value_template.async_render(automation_info["variables"])): armed = True except exceptions.TemplateError as ex: _LOGGER.warning( "Error initializing 'template' trigger for '%s': %s", automation_info["name"], ex, ) @callback def template_listener(event, updates): """Listen for state changes and calls action.""" nonlocal delay_cancel, armed result = updates.pop().result if isinstance(result, exceptions.TemplateError): _LOGGER.warning( "Error evaluating 'template' trigger for '%s': %s", automation_info["name"], result, ) return if delay_cancel: # pylint: disable=not-callable delay_cancel() delay_cancel = None if not result_as_boolean(result): armed = True return # Only fire when previously armed. if not armed: return # Fire! armed = False entity_id = event and event.data.get("entity_id") from_s = event and event.data.get("old_state") to_s = event and event.data.get("new_state") if entity_id is not None: description = f"{entity_id} via template" else: description = "time change or manual update via template" template_variables = { "platform": platform_type, "entity_id": entity_id, "from_state": from_s, "to_state": to_s, } trigger_variables = { "for": time_delta, "description": description, "id": trigger_id, } @callback def call_action(*_): """Call action with right context.""" nonlocal trigger_variables hass.async_run_hass_job( job, {"trigger": { **template_variables, **trigger_variables }}, (to_s.context if to_s else None), ) if not time_delta: call_action() return try: period = cv.positive_time_period( template.render_complex(time_delta, {"trigger": template_variables})) except (exceptions.TemplateError, vol.Invalid) as ex: _LOGGER.error("Error rendering '%s' for template: %s", automation_info["name"], ex) return trigger_variables["for"] = period delay_cancel = async_call_later(hass, period.seconds, call_action) info = async_track_template_result( hass, [TrackTemplate(value_template, automation_info["variables"])], template_listener, ) unsub = info.async_remove @callback def async_remove(): """Remove state listeners async.""" unsub() if delay_cancel: # pylint: disable=not-callable delay_cancel() return async_remove
async def async_added_to_hass(self): """ Call when entity about to be added. All relevant update logic for instance attributes occurs within this closure. Other methods in this class are designed to avoid directly modifying instance attributes, by instead focusing on returning relevant data back to this method. The goal of this method is to ensure that `self.current_observations` and `self.probability` are set on a best-effort basis when this entity is register with hass. In addition, this method must register the state listener defined within, which will be called any time a relevant entity changes its state. """ @callback def async_threshold_sensor_state_listener(event): """ Handle sensor state changes. When a state changes, we must update our list of current observations, then calculate the new probability. """ new_state = event.data.get("new_state") if new_state is None or new_state.state == STATE_UNKNOWN: return entity = event.data.get("entity_id") self.current_observations.update(self._record_entity_observations(entity)) self.async_set_context(event.context) self._recalculate_and_write_state() self.async_on_remove( async_track_state_change_event( self.hass, list(self.observations_by_entity), async_threshold_sensor_state_listener, ) ) @callback def _async_template_result_changed(event, updates): track_template_result = updates.pop() template = track_template_result.template result = track_template_result.result entity = event and event.data.get("entity_id") if isinstance(result, TemplateError): _LOGGER.error( "TemplateError('%s') " "while processing template '%s' " "in entity '%s'", result, template, self.entity_id, ) should_trigger = False else: should_trigger = result_as_boolean(result) for obs in self.observations_by_template[template]: if should_trigger: obs_entry = {"entity_id": entity, **obs} else: obs_entry = None self.current_observations[obs["id"]] = obs_entry if event: self.async_set_context(event.context) self._recalculate_and_write_state() for template in self.observations_by_template: info = async_track_template_result( self.hass, [TrackTemplate(template, None)], _async_template_result_changed, ) self._callbacks.append(info) self.async_on_remove(info.async_remove) info.async_refresh() self.current_observations.update(self._initialize_current_observations()) self.probability = self._calculate_new_probability() self._deviation = bool(self.probability >= self._probability_threshold)
def __init__(self, name, next_action, previous_action, play_action, pause_action, vol_down_action, vol_up_action, player_status_keyword, topics, mqtt, hass): """Initialize""" self.hass = hass self._domain = __name__.split(".")[-2] self._name = name self._volume = 0.0 self._track_name = "" self._track_artist = "" self._track_album_name = "" self._mqtt_player_state = None self._state = None self._album_art = None self._next_script = None self._previous_script = None self._play_script = None self._pause_script = None self._vol_down_action = None self._vol_up_action = None self._vol_script = None if (next_action): self._next_script = Script(hass, next_action, self._name, self._domain) if (previous_action): self._previous_script = Script(hass, previous_action, self._name, self._domain) if (play_action): self._play_script = Script(hass, play_action, self._name, self._domain) if (pause_action): self._pause_script = Script(hass, pause_action, self._name, self._domain) if (vol_down_action): self._vol_down_action = Script(hass, vol_down_action, self._name, self._domain) if (vol_up_action): self._vol_up_action = Script(hass, vol_up_action, self._name, self._domain) self._player_status_keyword = player_status_keyword if topics is not None: for key, value in topics.items(): if key == "song_title": result = async_track_template_result( self.hass, [TrackTemplate(value, None)], self.tracktitle_listener) self.async_on_remove(result.async_remove) if key == "song_artist": result = async_track_template_result( self.hass, [TrackTemplate(value, None)], self.artist_listener) self.async_on_remove(result.async_remove) if key == "song_album": result = async_track_template_result( self.hass, [TrackTemplate(value, None)], self.album_listener) self.async_on_remove(result.async_remove) if key == "song_volume": result = async_track_template_result( self.hass, [TrackTemplate(value, None)], self.volume_listener) self.async_on_remove(result.async_remove) if key == "album_art": mqtt.subscribe(value, self.albumart_listener) if key == "player_status": result = async_track_template_result( self.hass, [TrackTemplate(value, None)], self.state_listener) self.async_on_remove(result.async_remove) if key == "volume": self._vol_script = Script(hass, value, self._name, self._domain)
async def async_attach_trigger(hass, config, action, automation_info, *, platform_type="template"): """Listen for state changes based on configuration.""" value_template = config.get(CONF_VALUE_TEMPLATE) value_template.hass = hass time_delta = config.get(CONF_FOR) template.attach(hass, time_delta) delay_cancel = None job = HassJob(action) @callback def template_listener(event, updates): """Listen for state changes and calls action.""" nonlocal delay_cancel result = updates.pop().result if delay_cancel: # pylint: disable=not-callable delay_cancel() delay_cancel = None if not result_as_boolean(result): return entity_id = event.data.get("entity_id") from_s = event.data.get("old_state") to_s = event.data.get("new_state") @callback def call_action(*_): """Call action with right context.""" hass.async_run_hass_job( job, { "trigger": { "platform": "template", "entity_id": entity_id, "from_state": from_s, "to_state": to_s, "for": time_delta if not time_delta else period, "description": f"{entity_id} via template", } }, (to_s.context if to_s else None), ) if not time_delta: call_action() return variables = { "trigger": { "platform": platform_type, "entity_id": entity_id, "from_state": from_s, "to_state": to_s, } } try: period = cv.positive_time_period( template.render_complex(time_delta, variables)) except (exceptions.TemplateError, vol.Invalid) as ex: _LOGGER.error("Error rendering '%s' for template: %s", automation_info["name"], ex) return delay_cancel = async_call_later(hass, period.seconds, call_action) info = async_track_template_result( hass, [TrackTemplate(value_template, automation_info["variables"])], template_listener, ) unsub = info.async_remove @callback def async_remove(): """Remove state listeners async.""" unsub() if delay_cancel: # pylint: disable=not-callable delay_cancel() return async_remove