From f83a5976032d1801c51f4328427739916f642e21 Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Mon, 25 Sep 2023 18:00:08 +0200 Subject: [PATCH] Avoid redundant calls to async_write_ha_state in mqtt humidifier (#100781) Avoid redundant calls to async_write_ha_state --- homeassistant/components/mqtt/humidifier.py | 22 ++++----- tests/components/mqtt/test_humidifier.py | 50 +++++++++++++++++++++ 2 files changed, 62 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/mqtt/humidifier.py b/homeassistant/components/mqtt/humidifier.py index 52d8db3fc98..1742a768ffb 100644 --- a/homeassistant/components/mqtt/humidifier.py +++ b/homeassistant/components/mqtt/humidifier.py @@ -52,7 +52,12 @@ from .const import ( PAYLOAD_NONE, ) from .debug_info import log_messages -from .mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, async_setup_entry_helper +from .mixins import ( + MQTT_ENTITY_COMMON_SCHEMA, + MqttEntity, + async_setup_entry_helper, + write_state_on_attr_change, +) from .models import ( MqttCommandTemplate, MqttValueTemplate, @@ -60,7 +65,7 @@ from .models import ( ReceiveMessage, ReceivePayloadType, ) -from .util import get_mqtt_data, valid_publish_topic, valid_subscribe_topic +from .util import valid_publish_topic, valid_subscribe_topic CONF_AVAILABLE_MODES_LIST = "modes" CONF_DEVICE_CLASS = "device_class" @@ -313,6 +318,7 @@ class MqttHumidifier(MqttEntity, HumidifierEntity): @callback @log_messages(self.hass, self.entity_id) + @write_state_on_attr_change(self, {"_attr_is_on"}) def state_received(msg: ReceiveMessage) -> None: """Handle new received MQTT message.""" payload = self._value_templates[CONF_STATE](msg.payload) @@ -325,12 +331,12 @@ class MqttHumidifier(MqttEntity, HumidifierEntity): self._attr_is_on = False elif payload == PAYLOAD_NONE: self._attr_is_on = None - get_mqtt_data(self.hass).state_write_requests.write_state_request(self) self.add_subscription(topics, CONF_STATE_TOPIC, state_received) @callback @log_messages(self.hass, self.entity_id) + @write_state_on_attr_change(self, {"_attr_action"}) def action_received(msg: ReceiveMessage) -> None: """Handle new received MQTT message.""" action_payload = self._value_templates[ATTR_ACTION](msg.payload) @@ -347,12 +353,12 @@ class MqttHumidifier(MqttEntity, HumidifierEntity): action_payload, ) return - get_mqtt_data(self.hass).state_write_requests.write_state_request(self) self.add_subscription(topics, CONF_ACTION_TOPIC, action_received) @callback @log_messages(self.hass, self.entity_id) + @write_state_on_attr_change(self, {"_attr_current_humidity"}) def current_humidity_received(msg: ReceiveMessage) -> None: """Handle new received MQTT message for the current humidity.""" rendered_current_humidity_payload = self._value_templates[ @@ -360,7 +366,6 @@ class MqttHumidifier(MqttEntity, HumidifierEntity): ](msg.payload) if rendered_current_humidity_payload == self._payload["HUMIDITY_RESET"]: self._attr_current_humidity = None - get_mqtt_data(self.hass).state_write_requests.write_state_request(self) return if not rendered_current_humidity_payload: _LOGGER.debug("Ignoring empty current humidity from '%s'", msg.topic) @@ -384,7 +389,6 @@ class MqttHumidifier(MqttEntity, HumidifierEntity): ) return self._attr_current_humidity = current_humidity - get_mqtt_data(self.hass).state_write_requests.write_state_request(self) self.add_subscription( topics, CONF_CURRENT_HUMIDITY_TOPIC, current_humidity_received @@ -392,6 +396,7 @@ class MqttHumidifier(MqttEntity, HumidifierEntity): @callback @log_messages(self.hass, self.entity_id) + @write_state_on_attr_change(self, {"_attr_target_humidity"}) def target_humidity_received(msg: ReceiveMessage) -> None: """Handle new received MQTT message for the target humidity.""" rendered_target_humidity_payload = self._value_templates[ATTR_HUMIDITY]( @@ -402,7 +407,6 @@ class MqttHumidifier(MqttEntity, HumidifierEntity): return if rendered_target_humidity_payload == self._payload["HUMIDITY_RESET"]: self._attr_target_humidity = None - get_mqtt_data(self.hass).state_write_requests.write_state_request(self) return try: target_humidity = round(float(rendered_target_humidity_payload)) @@ -426,7 +430,6 @@ class MqttHumidifier(MqttEntity, HumidifierEntity): ) return self._attr_target_humidity = target_humidity - get_mqtt_data(self.hass).state_write_requests.write_state_request(self) self.add_subscription( topics, CONF_TARGET_HUMIDITY_STATE_TOPIC, target_humidity_received @@ -434,12 +437,12 @@ class MqttHumidifier(MqttEntity, HumidifierEntity): @callback @log_messages(self.hass, self.entity_id) + @write_state_on_attr_change(self, {"_attr_mode"}) def mode_received(msg: ReceiveMessage) -> None: """Handle new received MQTT message for mode.""" mode = str(self._value_templates[ATTR_MODE](msg.payload)) if mode == self._payload["MODE_RESET"]: self._attr_mode = None - get_mqtt_data(self.hass).state_write_requests.write_state_request(self) return if not mode: _LOGGER.debug("Ignoring empty mode from '%s'", msg.topic) @@ -454,7 +457,6 @@ class MqttHumidifier(MqttEntity, HumidifierEntity): return self._attr_mode = mode - get_mqtt_data(self.hass).state_write_requests.write_state_request(self) self.add_subscription(topics, CONF_MODE_STATE_TOPIC, mode_received) diff --git a/tests/components/mqtt/test_humidifier.py b/tests/components/mqtt/test_humidifier.py index 0cc4d936841..4d2637a264f 100644 --- a/tests/components/mqtt/test_humidifier.py +++ b/tests/components/mqtt/test_humidifier.py @@ -38,6 +38,7 @@ from homeassistant.const import ( from homeassistant.core import HomeAssistant from .test_common import ( + help_custom_config, help_test_availability_when_connection_lost, help_test_availability_without_topic, help_test_custom_availability_payload, @@ -60,6 +61,7 @@ from .test_common import ( help_test_setting_attribute_via_mqtt_json_message, help_test_setting_attribute_with_template, help_test_setting_blocked_attribute_via_mqtt_json_message, + help_test_skipped_async_ha_write_state, help_test_unique_id, help_test_unload_config_entry_with_platform, help_test_update_with_json_attrs_bad_json, @@ -1569,3 +1571,51 @@ async def test_unload_config_entry( await help_test_unload_config_entry_with_platform( hass, mqtt_mock_entry, domain, config ) + + +@pytest.mark.parametrize( + "hass_config", + [ + help_custom_config( + humidifier.DOMAIN, + DEFAULT_CONFIG, + ( + { + "availability_topic": "availability-topic", + "json_attributes_topic": "json-attributes-topic", + "action_topic": "action-topic", + "target_humidity_state_topic": "target-humidity-state-topic", + "current_humidity_topic": "current-humidity-topic", + "mode_command_topic": "mode-command-topic", + "mode_state_topic": "mode-state-topic", + "modes": [ + "comfort", + "eco", + ], + }, + ), + ) + ], +) +@pytest.mark.parametrize( + ("topic", "payload1", "payload2"), + [ + ("availability-topic", "online", "offline"), + ("json-attributes-topic", '{"attr1": "val1"}', '{"attr1": "val2"}'), + ("state-topic", "ON", "OFF"), + ("action-topic", "idle", "humidifying"), + ("current-humidity-topic", "31", "32"), + ("target-humidity-state-topic", "30", "40"), + ("mode-state-topic", "comfort", "eco"), + ], +) +async def test_skipped_async_ha_write_state( + hass: HomeAssistant, + mqtt_mock_entry: MqttMockHAClientGenerator, + topic: str, + payload1: str, + payload2: str, +) -> None: + """Test a write state command is only called when there is change.""" + await mqtt_mock_entry() + await help_test_skipped_async_ha_write_state(hass, topic, payload1, payload2)