async def validate_configs(hass, entity_configs): """Validate that entities exist and ensure templates are ready to use.""" entity_registry = await hass.helpers.entity_registry.async_get_registry() for entity_id, entity_config in entity_configs.items(): state = hass.states.get(entity_id) if state is None: _LOGGER.debug("Entity not found: %s", entity_id) continue entity = entity_registry.async_get(entity_id) if entity: entity_config[CONF_UNIQUE_ID] = get_system_unique_id(entity) else: entity_config[CONF_UNIQUE_ID] = entity_id if CONF_POWER in entity_config: power_val = entity_config[CONF_POWER] if isinstance(power_val, str) and is_template_string(power_val): entity_config[CONF_POWER] = Template(power_val, hass) elif isinstance(power_val, Template): entity_config[CONF_POWER].hass = hass elif CONF_POWER_ENTITY in entity_config: power_val = entity_config[CONF_POWER_ENTITY] if hass.states.get(power_val) is None: _LOGGER.debug("Sensor Entity not found: %s", power_val) else: entity_config[CONF_POWER] = power_val elif state.domain == SENSOR_DOMAIN: pass elif ATTR_CURRENT_POWER_W in state.attributes: pass else: _LOGGER.debug("No power value defined for: %s", entity_id)
def test_measure(self): """Test the history statistics sensor measure.""" later = dt_util.utcnow() - timedelta(seconds=15) earlier = later - timedelta(minutes=30) fake_states = { 'binary_sensor.test_id': [ ha.State('binary_sensor.test_id', 'on', last_changed=earlier), ha.State('binary_sensor.test_id', 'off', last_changed=later), ] } start = Template('{{ as_timestamp(now()) - 3600 }}', self.hass) end = Template('{{ now() }}', self.hass) sensor1 = HistoryStatsSensor(self.hass, 'binary_sensor.test_id', 'on', start, end, None, 'Test') sensor2 = HistoryStatsSensor(self.hass, 'unknown.id', 'on', start, end, None, 'Test') with patch( 'homeassistant.components.history.' 'state_changes_during_period', return_value=fake_states): with patch('homeassistant.components.history.get_state', return_value=None): sensor1.update() sensor2.update() self.assertEqual(sensor1.value, 0.5) self.assertEqual(sensor2.value, 0) self.assertEqual(sensor1.device_state_attributes['ratio'], '50.0%')
def setup_method(self): """Set up things to be run when tests are started.""" self.hass = get_test_home_assistant() self.name = "foo" self.method = "post" self.resource = "http://localhost/" self.state_resource = self.resource self.headers = {"Content-type": CONTENT_TYPE_JSON} self.auth = None self.body_on = Template("on", self.hass) self.body_off = Template("off", self.hass) self.switch = rest.RestSwitch( self.name, self.resource, self.state_resource, self.method, self.headers, self.auth, self.body_on, self.body_off, None, 10, True, ) self.switch.hass = self.hass
def __init__( self, hass: HomeAssistant, name_template: Template, value_template: Template, availability_template: Template | None, command_set_value: dict[str, Any], step_template: Template, minimum_template: Template | None, maximum_template: Template | None, optimistic: bool, unique_id: str | None, ) -> None: """Initialize the number.""" super().__init__(availability_template=availability_template) self._attr_name = DEFAULT_NAME self._name_template = name_template name_template.hass = hass with contextlib.suppress(TemplateError): self._attr_name = name_template.async_render(parse_result=False) self._value_template = value_template domain = __name__.split(".")[-2] self._command_set_value = Script(hass, command_set_value, self._attr_name, domain) self._step_template = step_template self._min_value_template = minimum_template self._max_value_template = maximum_template self._attr_assumed_state = self._optimistic = optimistic self._attr_unique_id = unique_id self._attr_value = None self._attr_step = None
def setup_method(self): """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() self.name = 'foo' self.resource = 'http://localhost/' self.body_on = Template('on', self.hass) self.body_off = Template('off', self.hass) self.switch = rest.RestSwitch(self.hass, self.name, self.resource, self.body_on, self.body_off, None, 10)
def test_measure(self): """Test the history statistics sensor measure.""" t0 = dt_util.utcnow() - timedelta(minutes=40) t1 = t0 + timedelta(minutes=20) t2 = dt_util.utcnow() - timedelta(minutes=10) # Start t0 t1 t2 End # |--20min--|--20min--|--10min--|--10min--| # |---off---|---on----|---off---|---on----| fake_states = { "binary_sensor.test_id": [ ha.State("binary_sensor.test_id", "on", last_changed=t0), ha.State("binary_sensor.test_id", "off", last_changed=t1), ha.State("binary_sensor.test_id", "on", last_changed=t2), ] } start = Template("{{ as_timestamp(now()) - 3600 }}", self.hass) end = Template("{{ now() }}", self.hass) sensor1 = HistoryStatsSensor( self.hass, "binary_sensor.test_id", "on", start, end, None, "time", "Test" ) sensor2 = HistoryStatsSensor( self.hass, "unknown.id", "on", start, end, None, "time", "Test" ) sensor3 = HistoryStatsSensor( self.hass, "binary_sensor.test_id", "on", start, end, None, "count", "test" ) sensor4 = HistoryStatsSensor( self.hass, "binary_sensor.test_id", "on", start, end, None, "ratio", "test" ) assert sensor1._type == "time" assert sensor3._type == "count" assert sensor4._type == "ratio" with patch( "homeassistant.components.history." "state_changes_during_period", return_value=fake_states, ): with patch("homeassistant.components.history.get_state", return_value=None): sensor1.update() sensor2.update() sensor3.update() sensor4.update() assert sensor1.state == 0.5 assert sensor2.state is None assert sensor3.state == 2 assert sensor4.state == 50
def test_measure(self): """Test the history statistics sensor measure.""" t0 = dt_util.utcnow() - timedelta(minutes=40) t1 = t0 + timedelta(minutes=20) t2 = dt_util.utcnow() - timedelta(minutes=10) # Start t0 t1 t2 End # |--20min--|--20min--|--10min--|--10min--| # |---off---|---on----|---off---|---on----| fake_states = { 'binary_sensor.test_id': [ ha.State('binary_sensor.test_id', 'on', last_changed=t0), ha.State('binary_sensor.test_id', 'off', last_changed=t1), ha.State('binary_sensor.test_id', 'on', last_changed=t2), ] } start = Template('{{ as_timestamp(now()) - 3600 }}', self.hass) end = Template('{{ now() }}', self.hass) sensor1 = HistoryStatsSensor( self.hass, 'binary_sensor.test_id', 'on', start, end, None, 'time', 'Test') sensor2 = HistoryStatsSensor( self.hass, 'unknown.id', 'on', start, end, None, 'time', 'Test') sensor3 = HistoryStatsSensor( self.hass, 'binary_sensor.test_id', 'on', start, end, None, 'count', 'test') sensor4 = HistoryStatsSensor( self.hass, 'binary_sensor.test_id', 'on', start, end, None, 'ratio', 'test') assert sensor1._type == 'time' assert sensor3._type == 'count' assert sensor4._type == 'ratio' with patch('homeassistant.components.history.' 'state_changes_during_period', return_value=fake_states): with patch('homeassistant.components.history.get_state', return_value=None): sensor1.update() sensor2.update() sensor3.update() sensor4.update() assert sensor1.state == 0.5 assert sensor2.state is None assert sensor3.state == 2 assert sensor4.state == 50
async def _create_counter( name: str, entity_types: Union[str, List[str]], states: Optional[List[str]] = None, prefix: Optional[str] = None, area: Optional[AreaSettings] = None, reject: bool = False, sum: bool = False, ) -> Optional[str]: """Create a counter or super counter if sum is true.""" platform: EntityPlatform = get_base().hass.data[CONF_ENTITY_PLATFORM][PLATFORM][0] area_string = f"area_{area[ATTR_ID]}_" if area is not None else "" area_title = f"Area {area[ATTR_ID][-5:-1]} " if area is not None else "" device_id = f"{DOMAIN}_{area_string}{name}" entity_id = f"{PLATFORM}.{device_id}" friendly_name = f"{TITLE} {area_title}{name.replace('_', ' ').title()}" await remove_counter(entity_id) templates: CounterTemplates if sum: templates = _super_counter_templates(entity_types, area, prefix) else: templates = _counter_templates(entity_types[0], states, area, reject) if templates is None: return None await platform.async_add_entities( [ await create_binary_sensor_entity( device_id, { CONF_FRIENDLY_NAME: friendly_name, CONF_ICON_TEMPLATE: Template("mdi:counter"), CONF_VALUE_TEMPLATE: Template(templates["state_template"]), CONF_ATTRIBUTE_TEMPLATES: { CONF_COUNT: Template(templates["count_template"]), CONF_ENTITIES: Template(templates["entity_template"]), CONF_TRACKED_ENTITY_COUNT: Template( templates["tracked_count_template"] ), }, }, ) ] ) return entity_id
async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: """Set up the SQL sensor entry.""" db_url: str = entry.options[CONF_DB_URL] name: str = entry.options[CONF_NAME] query_str: str = entry.options[CONF_QUERY] unit: str | None = entry.options.get(CONF_UNIT_OF_MEASUREMENT) template: str | None = entry.options.get(CONF_VALUE_TEMPLATE) column_name: str = entry.options[CONF_COLUMN_NAME] value_template: Template | None = None if template is not None: try: value_template = Template(template) value_template.ensure_valid() except TemplateError: value_template = None if value_template is not None: value_template.hass = hass try: engine = sqlalchemy.create_engine(db_url, future=True) sessmaker = scoped_session(sessionmaker(bind=engine, future=True)) except SQLAlchemyError as err: _LOGGER.error("Can not open database %s", {redact_credentials(str(err))}) return # MSSQL uses TOP and not LIMIT if not ("LIMIT" in query_str.upper() or "SELECT TOP" in query_str.upper()): if "mssql" in db_url: query_str = query_str.upper().replace("SELECT", "SELECT TOP 1") else: query_str = query_str.replace(";", "") + " LIMIT 1;" async_add_entities( [ SQLSensor( name, sessmaker, query_str, column_name, unit, value_template, entry.entry_id, ) ], True, )
def setup_method(self): """Set up things to be run when tests are started.""" self.hass = get_test_home_assistant() self.name = 'foo' self.method = 'post' self.resource = 'http://localhost/' self.headers = {'Content-type': 'application/json'} self.auth = None self.body_on = Template('on', self.hass) self.body_off = Template('off', self.hass) self.switch = rest.RestSwitch(self.name, self.resource, self.method, self.headers, self.auth, self.body_on, self.body_off, None, 10) self.switch.hass = self.hass
def add_template_attribute( self, attribute: str, template: Template, validator: Callable[[Any], Any] = None, on_update: Callable[[Any], None] | None = None, none_on_template_error: bool = False, ) -> None: """ Call in the constructor to add a template linked to a attribute. Parameters ---------- attribute The name of the attribute to link to. This attribute must exist unless a custom on_update method is supplied. template The template to calculate. validator Validator function to parse the result and ensure it's valid. on_update Called to store the template result rather than storing it the supplied attribute. Passed the result of the validator, or None if the template or validator resulted in an error. """ assert self.hass is not None, "hass cannot be None" template.hass = self.hass attribute = _TemplateAttribute(self, attribute, template, validator, on_update, none_on_template_error) self._template_attrs.setdefault(template, []) self._template_attrs[template].append(attribute)
def _check_deprecated_turn_off(hass, turn_off_action): """Create an equivalent script for old turn off actions.""" if isinstance(turn_off_action, str): method = DEPRECATED_TURN_OFF_ACTIONS[turn_off_action] new_config = OrderedDict([ ("service", f"{DOMAIN}.{SERVICE_CALL_METHOD}"), ( "data_template", OrderedDict([("entity_id", "{{ entity_id }}"), ("method", method)]), ), ]) example_conf = dump(OrderedDict([(CONF_TURN_OFF_ACTION, new_config)])) _LOGGER.warning( "The '%s' action for turn off Kodi is deprecated and " "will cease to function in a future release. You need to " "change it for a generic Home Assistant script sequence, " "which is, for this turn_off action, like this:\n%s", turn_off_action, example_conf, ) new_config["data_template"] = OrderedDict([ (key, Template(value, hass)) for key, value in new_config["data_template"].items() ]) turn_off_action = [new_config] return turn_off_action
def async_on_service_call(service: HomeassistantServiceCall) -> None: """Call service when user automation in ESPHome config is triggered.""" domain, service_name = service.service.split(".", 1) service_data = service.data if service.data_template: try: data_template = { key: Template(value) for key, value in service.data_template.items() } template.attach(hass, data_template) service_data.update( template.render_complex(data_template, service.variables)) except TemplateError as ex: _LOGGER.error("Error rendering data template for %s: %s", host, ex) return if service.is_event: # ESPHome uses servicecall packet for both events and service calls # Ensure the user can only send events of form 'esphome.xyz' if domain != "esphome": _LOGGER.error( "Can only generate events under esphome domain! (%s)", host) return hass.bus.async_fire(service.service, service_data) else: hass.async_create_task( hass.services.async_call(domain, service_name, service_data, blocking=True))
def async_track_template( hass: HomeAssistant, template: Template, action: Callable[[str, State, State], None], variables: Optional[Dict[str, Any]] = None, ) -> CALLBACK_TYPE: """Add a listener that track state changes with template condition.""" from . import condition # pylint: disable=import-outside-toplevel # Local variable to keep track of if the action has already been triggered already_triggered = False @callback def template_condition_listener(entity_id: str, from_s: State, to_s: State) -> None: """Check if condition is correct and run action.""" nonlocal already_triggered template_result = condition.async_template(hass, template, variables) # Check to see if template returns true if template_result and not already_triggered: already_triggered = True hass.async_run_job(action, entity_id, from_s, to_s) elif not template_result: already_triggered = False return async_track_state_change(hass, template.extract_entities(variables), template_condition_listener)
async def handle_render_template(hass, connection, msg): """Handle render_template command.""" template_str = msg["template"] template = Template(template_str, hass) variables = msg.get("variables") timeout = msg.get("timeout") info = None if timeout: try: timed_out = await template.async_render_will_timeout(timeout) 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, variables)], _template_listener, raise_on_template_error=True, ) 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)
def test_update_renders_value_in_template(self, mock_select, mock_socket): """Render the value in the provided template.""" test_value = 'test_value' mock_socket = mock_socket().__enter__() mock_socket.recv.return_value = test_value.encode() config = copy(TEST_CONFIG['sensor']) config[tcp.CONF_VALUE_TEMPLATE] = Template('{{ value }} {{ 1+1 }}') sensor = tcp.TcpSensor(self.hass, config) assert sensor._state == '%s 2' % test_value
def test_wrong_date(self): """Test when start or end value is not a timestamp or a date.""" good = Template("{{ now() }}", self.hass) bad = Template("{{ TEST }}", self.hass) sensor1 = HistoryStatsSensor(self.hass, "test", "on", good, bad, None, "time", "Test") sensor2 = HistoryStatsSensor(self.hass, "test", "on", bad, good, None, "time", "Test") before_update1 = sensor1._period before_update2 = sensor2._period sensor1.update_period() sensor2.update_period() assert before_update1 == sensor1._period assert before_update2 == sensor2._period
def _setup_test_switch(hass): body_on = Template("on", hass) body_off = Template("off", hass) switch = rest.RestSwitch( NAME, RESOURCE, STATE_RESOURCE, METHOD, HEADERS, AUTH, body_on, body_off, None, 10, True, ) switch.hass = hass return switch, body_on, body_off
def test_wrong_date(self): """Test when start or end value is not a timestamp or a date.""" good = Template('{{ now() }}', self.hass) bad = Template('{{ TEST }}', self.hass) sensor1 = HistoryStatsSensor(self.hass, 'test', 'on', good, bad, None, 'time', 'Test') sensor2 = HistoryStatsSensor(self.hass, 'test', 'on', bad, good, None, 'time', 'Test') before_update1 = sensor1._period before_update2 = sensor2._period sensor1.update_period() sensor2.update_period() assert before_update1 == sensor1._period assert before_update2 == sensor2._period
def test_template(self): """Test command sensor with template.""" data = command_line.CommandSensorData('echo 50') entity = command_line.CommandSensor( self.hass, data, 'test', 'in', Template('{{ value | multiply(0.1) }}', self.hass)) self.assertEqual(5, float(entity.state))
async def test_extract_devices(): """Test extracting devices.""" assert (condition.async_extract_devices({ "condition": "and", "conditions": [ { "condition": "device", "device_id": "abcd", "domain": "light" }, { "condition": "device", "device_id": "qwer", "domain": "switch" }, { "condition": "state", "entity_id": "sensor.not_a_device", "state": "100", }, { "condition": "not", "conditions": [ { "condition": "device", "device_id": "abcd_not", "domain": "light", }, { "condition": "device", "device_id": "qwer_not", "domain": "switch", }, ], }, { "condition": "or", "conditions": [ { "condition": "device", "device_id": "abcd_or", "domain": "light", }, { "condition": "device", "device_id": "qwer_or", "domain": "switch", }, ], }, Template("{{ is_state('light.example', 'on') }}"), ], }) == {"abcd", "qwer", "abcd_not", "qwer_not", "abcd_or", "qwer_or"})
def test_update_returns_if_template_render_fails( self, mock_select, mock_socket): """Return None if rendering the template fails.""" test_value = 'test_value' mock_socket = mock_socket().__enter__() mock_socket.recv.return_value = test_value.encode() config = copy(TEST_CONFIG['sensor']) config[tcp.CONF_VALUE_TEMPLATE] = Template("{{ this won't work") sensor = tcp.TcpSensor(self.hass, config) assert sensor.update() is None
def async_template(hass: HomeAssistant, value_template: Template, variables: TemplateVarsType = None) -> bool: """Test if template condition matches.""" try: value: str = value_template.async_render(variables, parse_result=False) except TemplateError as ex: raise ConditionErrorMessage("template", str(ex)) from ex return value.lower() == "true"
def test_template(self): """Test command sensor with template.""" data = command_line.CommandSensorData(self.hass, 'echo 50', 15) entity = command_line.CommandSensor( self.hass, data, 'test', 'in', Template('{{ value | multiply(0.1) }}', self.hass), []) entity.update() assert 5 == float(entity.state)
def async_template(hass: HomeAssistant, value_template: Template, variables: TemplateVarsType = None) -> bool: """Test if template condition matches.""" try: value = value_template.async_render(variables) except TemplateError as ex: _LOGGER.error("Error during template condition: %s", ex) return False return value.lower() == 'true'
def async_template(hass: HomeAssistant, value_template: Template, variables: TemplateVarsType = None) -> bool: """Test if template condition matches.""" try: value = value_template.async_render(variables) except TemplateError as ex: _LOGGER.error("Error during template condition: %s", ex) return False return value.lower() == "true"
def _setup_test_switch(hass): body_on = Template("on", hass) body_off = Template("off", hass) headers = {"Content-type": Template(CONTENT_TYPE_JSON, hass)} switch = rest.RestSwitch( NAME, DEVICE_CLASS, RESOURCE, STATE_RESOURCE, METHOD, headers, PARAMS, AUTH, body_on, body_off, None, 10, True, ) switch.hass = hass return switch, body_on, body_off
async def _valid_template(self, user_template): try: _LOGGER.debug(user_template) ut = Template(user_template, self.hass).async_render() if isinstance(ut, float): return True else: return False except Exception as e: _LOGGER.error(e) pass return False
def test_measure(self): """Test the history statistics sensor measure.""" t0 = dt_util.utcnow() - timedelta(minutes=40) t1 = t0 + timedelta(minutes=20) t2 = dt_util.utcnow() - timedelta(minutes=10) # Start t0 t1 t2 End # |--20min--|--20min--|--10min--|--10min--| # |---off---|---on----|---off---|---on----| fake_states = { 'binary_sensor.test_id': [ ha.State('binary_sensor.test_id', 'on', last_changed=t0), ha.State('binary_sensor.test_id', 'off', last_changed=t1), ha.State('binary_sensor.test_id', 'on', last_changed=t2), ] } start = Template('{{ as_timestamp(now()) - 3600 }}', self.hass) end = Template('{{ now() }}', self.hass) sensor1 = HistoryStatsSensor(self.hass, 'binary_sensor.test_id', 'on', start, end, None, 'Test') sensor2 = HistoryStatsSensor(self.hass, 'unknown.id', 'on', start, end, None, 'Test') with patch( 'homeassistant.components.history.' 'state_changes_during_period', return_value=fake_states): with patch('homeassistant.components.history.get_state', return_value=None): sensor1.update() sensor2.update() self.assertEqual(round(sensor1.value, 3), 0.5) self.assertEqual(round(sensor2.value, 3), 0) self.assertEqual(sensor1.device_state_attributes['ratio'], '50.0%')
def rewrite_common_legacy_to_modern_conf( entity_cfg: dict[str, Any], extra_legacy_fields: dict[str, str] = None) -> dict[str, Any]: """Rewrite legacy config.""" entity_cfg = {**entity_cfg} if extra_legacy_fields is None: extra_legacy_fields = {} for from_key, to_key in itertools.chain(LEGACY_FIELDS.items(), extra_legacy_fields.items()): if from_key not in entity_cfg or to_key in entity_cfg: continue val = entity_cfg.pop(from_key) if isinstance(val, str): val = Template(val) entity_cfg[to_key] = val if CONF_NAME in entity_cfg and isinstance(entity_cfg[CONF_NAME], str): entity_cfg[CONF_NAME] = Template(entity_cfg[CONF_NAME]) return entity_cfg
def async_on_service_call(service: HomeassistantServiceCall) -> None: """Call service when user automation in ESPHome config is triggered.""" domain, service_name = service.service.split(".", 1) service_data = service.data if service.data_template: try: data_template = { key: Template(value) # type: ignore[no-untyped-call] for key, value in service.data_template.items() } template.attach(hass, data_template) service_data.update( template.render_complex(data_template, service.variables)) except TemplateError as ex: _LOGGER.error("Error rendering data template for %s: %s", host, ex) return if service.is_event: # ESPHome uses servicecall packet for both events and service calls # Ensure the user can only send events of form 'esphome.xyz' if domain != "esphome": _LOGGER.error( "Can only generate events under esphome domain! (%s)", host) return # Call native tag scan if service_name == "tag_scanned" and device_id is not None: # Importing tag via hass.components in case it is overridden # in a custom_components (custom_components.tag) tag = hass.components.tag tag_id = service_data["tag_id"] hass.async_create_task(tag.async_scan_tag(tag_id, device_id)) return hass.bus.async_fire( service.service, { ATTR_DEVICE_ID: device_id, **service_data, }, ) else: hass.async_create_task( hass.services.async_call(domain, service_name, service_data, blocking=True))