diff --git a/homeassistant/components/mqtt/siren.py b/homeassistant/components/mqtt/siren.py index 0d8a53e98ea..fb0a05c93f5 100644 --- a/homeassistant/components/mqtt/siren.py +++ b/homeassistant/components/mqtt/siren.py @@ -49,7 +49,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, @@ -57,7 +62,6 @@ from .models import ( ReceiveMessage, ReceivePayloadType, ) -from .util import get_mqtt_data DEFAULT_NAME = "MQTT Siren" DEFAULT_PAYLOAD_ON = "ON" @@ -223,6 +227,7 @@ class MqttSiren(MqttEntity, SirenEntity): @callback @log_messages(self.hass, self.entity_id) + @write_state_on_attr_change(self, {"_attr_is_on", "_extra_attributes"}) def state_message_received(msg: ReceiveMessage) -> None: """Handle new MQTT state messages.""" payload = self._value_template(msg.payload) @@ -265,7 +270,6 @@ class MqttSiren(MqttEntity, SirenEntity): if json_payload[STATE] == PAYLOAD_NONE: self._attr_is_on = None del json_payload[STATE] - get_mqtt_data(self.hass).state_write_requests.write_state_request(self) if json_payload: # process attributes @@ -279,8 +283,10 @@ class MqttSiren(MqttEntity, SirenEntity): invalid_siren_parameters, ) return + # To be able to track changes to self._extra_attributes we assign + # a fresh copy to make the original tracked reference immutable. + self._extra_attributes = dict(self._extra_attributes) self._update(process_turn_on_params(self, params)) - get_mqtt_data(self.hass).state_write_requests.write_state_request(self) if self._config.get(CONF_STATE_TOPIC) is None: # Force into optimistic mode. diff --git a/tests/components/mqtt/test_siren.py b/tests/components/mqtt/test_siren.py index 4f703ff0023..8a576068216 100644 --- a/tests/components/mqtt/test_siren.py +++ b/tests/components/mqtt/test_siren.py @@ -44,6 +44,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, @@ -1092,3 +1093,53 @@ async def test_unload_entry( await help_test_unload_config_entry_with_platform( hass, mqtt_mock_entry, domain, config ) + + +@pytest.mark.parametrize( + "hass_config", + [ + help_custom_config( + siren.DOMAIN, + DEFAULT_CONFIG, + ( + { + "state_topic": "test-topic", + "available_tones": ["siren", "bell"], + "availability_topic": "availability-topic", + "json_attributes_topic": "json-attributes-topic", + }, + ), + ) + ], +) +@pytest.mark.parametrize( + ("topic", "payload1", "payload2"), + [ + ("availability-topic", "online", "offline"), + ("json-attributes-topic", '{"attr1": "val1"}', '{"attr1": "val2"}'), + ("test-topic", "ON", "OFF"), + ("test-topic", '{"state": "ON"}', '{"state": "OFF"}'), + ("test-topic", '{"state":"ON","tone":"siren"}', '{"state":"ON","tone":"bell"}'), + ( + "test-topic", + '{"state":"ON","tone":"siren"}', + '{"state":"OFF","tone":"siren"}', + ), + # Attriute volume_level 2 is invalid, but the state is valid and should update + ( + "test-topic", + '{"state":"ON","volume_level":0.5}', + '{"state":"OFF","volume_level":2}', + ), + ], +) +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)