diff --git a/homeassistant/helpers/trigger_template_entity.py b/homeassistant/helpers/trigger_template_entity.py index 7f8ad41d7bb..9df263207eb 100644 --- a/homeassistant/helpers/trigger_template_entity.py +++ b/homeassistant/helpers/trigger_template_entity.py @@ -176,18 +176,43 @@ class TriggerBaseEntity(Entity): extra_state_attributes[attr] = last_state.attributes[attr] self._rendered[CONF_ATTRIBUTES] = extra_state_attributes + def _render_availability_template(self, variables: dict[str, Any]) -> None: + """Render availability template.""" + rendered = dict(self._static_rendered) + self._rendered = self._static_rendered + try: + key = CONF_AVAILABILITY + if key in self._to_render_simple: + rendered[key] = self._config[key].async_render( + variables, + parse_result=key in self._parse_result, + ) + elif key in self._to_render_complex: + rendered[key] = render_complex( + self._config[key], + variables, + ) + except TemplateError as err: + logging.getLogger(f"{__package__}.{self.entity_id.split('.')[0]}").error( + "Error rendering %s template for %s: %s", key, self.entity_id, err + ) + self._rendered = rendered + def _render_templates(self, variables: dict[str, Any]) -> None: """Render templates.""" + rendered = dict(self._rendered) try: - rendered = dict(self._static_rendered) - for key in self._to_render_simple: + if key == CONF_AVAILABILITY: + continue rendered[key] = self._config[key].async_render( variables, parse_result=key in self._parse_result, ) for key in self._to_render_complex: + if key == CONF_AVAILABILITY: + continue rendered[key] = render_complex( self._config[key], variables, @@ -204,7 +229,6 @@ class TriggerBaseEntity(Entity): logging.getLogger(f"{__package__}.{self.entity_id.split('.')[0]}").error( "Error rendering %s template for %s: %s", key, self.entity_id, err ) - self._rendered = self._static_rendered class ManualTriggerEntity(TriggerBaseEntity): @@ -231,16 +255,22 @@ class ManualTriggerEntity(TriggerBaseEntity): Ex: self._process_manual_data(payload) """ + run_variables: dict[str, Any] = {"value": value} + this = None + if state := self.hass.states.get(self.entity_id): + this = state.as_dict() + # Silently try if variable is a json and store result in `value_json` if it is. + with contextlib.suppress(*JSON_DECODE_EXCEPTIONS): + run_variables["value_json"] = json_loads(run_variables["value"]) + variables = {"this": this, **(run_variables or {})} + self._render_availability_template(variables) + self.async_write_ha_state() this = None if state := self.hass.states.get(self.entity_id): this = state.as_dict() - run_variables: dict[str, Any] = {"value": value} - # Silently try if variable is a json and store result in `value_json` if it is. - with contextlib.suppress(*JSON_DECODE_EXCEPTIONS): - run_variables["value_json"] = json_loads(run_variables["value"]) - variables = {"this": this, **(run_variables or {})} + variables["this"] = this self._render_templates(variables) diff --git a/tests/components/command_line/test_sensor.py b/tests/components/command_line/test_sensor.py index f7879b334cd..bf1c15a2a49 100644 --- a/tests/components/command_line/test_sensor.py +++ b/tests/components/command_line/test_sensor.py @@ -808,3 +808,52 @@ async def test_availability( entity_state = hass.states.get("sensor.test") assert entity_state assert entity_state.state == STATE_UNAVAILABLE + + +@pytest.mark.parametrize( + "get_config", + [ + { + "command_line": [ + { + "sensor": { + "name": "Test", + "command": "echo {{ states.sensor.input_sensor.state }}", + "availability": "{{ value|is_number}}", + "unit_of_measurement": " ", + "state_class": "measurement", + } + } + ] + } + ], +) +async def test_template_render_not_break_for_availability( + hass: HomeAssistant, load_yaml_integration: None +) -> None: + """Ensure command with templates get rendered properly.""" + hass.states.async_set("sensor.input_sensor", "sensor_value") + + # Give time for template to load + async_fire_time_changed( + hass, + dt_util.utcnow() + timedelta(minutes=1), + ) + await hass.async_block_till_done(wait_background_tasks=True) + + entity_state = hass.states.get("sensor.test") + assert entity_state + assert entity_state.state == STATE_UNAVAILABLE + + hass.states.async_set("sensor.input_sensor", "1") + + # Give time for template to load + async_fire_time_changed( + hass, + dt_util.utcnow() + timedelta(minutes=1), + ) + await hass.async_block_till_done(wait_background_tasks=True) + + entity_state = hass.states.get("sensor.test") + assert entity_state + assert entity_state.state == "1" diff --git a/tests/components/command_line/test_switch.py b/tests/components/command_line/test_switch.py index 549e729892c..ed8032d7789 100644 --- a/tests/components/command_line/test_switch.py +++ b/tests/components/command_line/test_switch.py @@ -564,7 +564,7 @@ async def test_templating(hass: HomeAssistant) -> None: "command_off": f"echo 0 > {path}", "value_template": '{{ value=="1" }}', "icon": ( - '{% if states("switch.test2")=="on" %} mdi:on {% else %} mdi:off {% endif %}' + '{% if states("switch.test")=="on" %} mdi:on {% else %} mdi:off {% endif %}' ), "name": "Test2", }, diff --git a/tests/components/rest/test_sensor.py b/tests/components/rest/test_sensor.py index 2e02063b215..31ce261e312 100644 --- a/tests/components/rest/test_sensor.py +++ b/tests/components/rest/test_sensor.py @@ -1054,3 +1054,54 @@ async def test_availability_in_config(hass: HomeAssistant) -> None: state = hass.states.get("sensor.rest_sensor") assert state.state == STATE_UNAVAILABLE + + +@respx.mock +async def test_json_response_with_availability(hass: HomeAssistant) -> None: + """Test availability with complex json.""" + + respx.get("http://localhost").respond( + status_code=HTTPStatus.OK, + json={"heartbeatList": {"1": [{"status": 1, "ping": 21.4}]}}, + ) + assert await async_setup_component( + hass, + DOMAIN, + { + DOMAIN: [ + { + "resource": "http://localhost", + "sensor": [ + { + "unique_id": "complex_json", + "name": "complex_json", + "value_template": '{% set v = value_json.heartbeatList["1"][-1] %}{{ v.ping }}', + "availability": '{% set v = value_json.heartbeatList["1"][-1] %}{{ v.status == 1 and is_number(v.ping) }}', + "unit_of_measurement": "ms", + "state_class": "measurement", + } + ], + } + ] + }, + ) + await async_setup_component(hass, "homeassistant", {}) + await hass.async_block_till_done() + assert len(hass.states.async_all(SENSOR_DOMAIN)) == 1 + + state = hass.states.get("sensor.complex_json") + assert state.state == "21.4" + + respx.get("http://localhost").respond( + status_code=HTTPStatus.OK, + json={"heartbeatList": {"1": [{"status": 0, "ping": None}]}}, + ) + await hass.services.async_call( + "homeassistant", + "update_entity", + {ATTR_ENTITY_ID: ["sensor.complex_json"]}, + blocking=True, + ) + await hass.async_block_till_done() + state = hass.states.get("sensor.complex_json") + assert state.state == STATE_UNAVAILABLE