From 03e9dff32768b6e24ffdbe7f849b5304fbad15b0 Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Wed, 15 Feb 2023 16:31:49 +0100 Subject: [PATCH] Allow resetting MQTT climate attributes and ignore empty values (#87936) Allow reseting MQTT climate attributes --- homeassistant/components/mqtt/climate.py | 11 +++++- tests/components/mqtt/test_climate.py | 49 +++++++++++++++++++++++- 2 files changed, 57 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/mqtt/climate.py b/homeassistant/components/mqtt/climate.py index 062e048b2af..140d9fb128b 100644 --- a/homeassistant/components/mqtt/climate.py +++ b/homeassistant/components/mqtt/climate.py @@ -646,7 +646,16 @@ class MqttClimate(MqttEntity, ClimateEntity): ) -> None: """Handle climate attributes coming via MQTT.""" payload = render_template(msg, template_name) - + if not payload: + _LOGGER.debug( + "Invalid empty payload for attribute %s, ignoring update", + attr, + ) + return + if payload == PAYLOAD_NONE: + setattr(self, attr, None) + get_mqtt_data(self.hass).state_write_requests.write_state_request(self) + return try: setattr(self, attr, float(payload)) get_mqtt_data(self.hass).state_write_requests.write_state_request(self) diff --git a/tests/components/mqtt/test_climate.py b/tests/components/mqtt/test_climate.py index af880993ab2..4e21481c9ab 100644 --- a/tests/components/mqtt/test_climate.py +++ b/tests/components/mqtt/test_climate.py @@ -1109,7 +1109,9 @@ async def test_get_target_temperature_low_high_with_templates( assert state.attributes.get("target_temp_high") == 1032 # Temperature - with invalid value - async_fire_mqtt_message(hass, "temperature-state", '"-INVALID-"') + async_fire_mqtt_message( + hass, "temperature-state", '{"temp_low": "INVALID", "temp_high": "INVALID"}' + ) state = hass.states.get(ENTITY_CLIMATE) # make sure, the invalid value gets logged... assert "Could not parse temperature_low_state_template from" in caplog.text @@ -1118,6 +1120,32 @@ async def test_get_target_temperature_low_high_with_templates( assert state.attributes.get("target_temp_low") == 1031 assert state.attributes.get("target_temp_high") == 1032 + # Reset the high and low values using a "None" of JSON null value + async_fire_mqtt_message( + hass, "temperature-state", '{"temp_low": "None", "temp_high": null}' + ) + state = hass.states.get(ENTITY_CLIMATE) + + assert state.attributes.get("target_temp_low") is None + assert state.attributes.get("target_temp_high") is None + + # Test ignoring an empty state works okay + caplog.clear() + async_fire_mqtt_message( + hass, "temperature-state", '{"temp_low": "", "temp_high": "21"}' + ) + state = hass.states.get(ENTITY_CLIMATE) + assert state.attributes.get("target_temp_low") is None + assert state.attributes.get("target_temp_high") == 21.0 + async_fire_mqtt_message( + hass, "temperature-state", '{"temp_low": "18", "temp_high": ""}' + ) + state = hass.states.get(ENTITY_CLIMATE) + assert state.attributes.get("target_temp_low") == 18.0 + assert state.attributes.get("target_temp_high") == 21.0 + assert "Could not parse temperature_low_state_template from" not in caplog.text + assert "Could not parse temperature_high_state_template from" not in caplog.text + async def test_get_with_templates( hass: HomeAssistant, @@ -1180,6 +1208,11 @@ async def test_get_with_templates( # ... but the actual value stays unchanged. assert state.attributes.get("temperature") == 1031 + # Temperature - with JSON null value + async_fire_mqtt_message(hass, "temperature-state", "null") + state = hass.states.get(ENTITY_CLIMATE) + assert state.attributes.get("temperature") is None + # Humidity - with valid value assert state.attributes.get("humidity") is None async_fire_mqtt_message(hass, "humidity-state", '"82"') @@ -1196,6 +1229,11 @@ async def test_get_with_templates( # ... but the actual value stays unchanged. assert state.attributes.get("humidity") == 82 + # reset the humidity + async_fire_mqtt_message(hass, "humidity-state", "null") + state = hass.states.get(ENTITY_CLIMATE) + assert state.attributes.get("humidity") is None + # Preset Mode assert state.attributes.get("preset_mode") == "none" async_fire_mqtt_message(hass, "current-preset-mode", '{"attribute": "eco"}') @@ -1223,11 +1261,18 @@ async def test_get_with_templates( async_fire_mqtt_message(hass, "current-temperature", '"74656"') state = hass.states.get(ENTITY_CLIMATE) assert state.attributes.get("current_temperature") == 74656 + # Test resetting the current temperature using a JSON null value + async_fire_mqtt_message(hass, "current-temperature", "null") + state = hass.states.get(ENTITY_CLIMATE) + assert state.attributes.get("current_temperature") is None # Current humidity async_fire_mqtt_message(hass, "current-humidity", '"35"') state = hass.states.get(ENTITY_CLIMATE) assert state.attributes.get("current_humidity") == 35 + async_fire_mqtt_message(hass, "current-humidity", "null") + state = hass.states.get(ENTITY_CLIMATE) + assert state.attributes.get("current_humidity") is None # Action async_fire_mqtt_message(hass, "action", '"cooling"') @@ -1272,7 +1317,7 @@ async def test_set_and_templates( # Preset Mode await common.async_set_preset_mode(hass, PRESET_ECO, ENTITY_CLIMATE) - mqtt_mock.async_publish.call_count == 1 + assert mqtt_mock.async_publish.call_count == 1 mqtt_mock.async_publish.assert_any_call( "preset-mode-topic", "preset_mode: eco", 0, False )