From 04f0128a1c9f75468b2c9ef96ba939bd1b6e3e31 Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Tue, 30 Jan 2024 20:50:39 +0100 Subject: [PATCH] Simplify MQTT device triggers in automations (#108309) * Simplify MQTT device trigger * Add test non unique trigger_id * Adjust deprecation warning * Make discovery_id optional * refactor double if * Improve validation, add tests and deprecation comments * Avoid breaking change * Inmprove error message * Match on discovery_id instead of discovery_info * Revert an unrelated change * follow up comments * Add comment and test on device update with non unique trigger * Update homeassistant/components/mqtt/device_trigger.py Co-authored-by: Erik Montnemery * Update homeassistant/components/mqtt/device_trigger.py Co-authored-by: Erik Montnemery --------- Co-authored-by: Erik Montnemery --- .../components/mqtt/device_trigger.py | 112 +++++-- tests/components/mqtt/test_device_trigger.py | 286 ++++++++++++++++-- 2 files changed, 349 insertions(+), 49 deletions(-) diff --git a/homeassistant/components/mqtt/device_trigger.py b/homeassistant/components/mqtt/device_trigger.py index fc7528743fa..b6d505d7c98 100644 --- a/homeassistant/components/mqtt/device_trigger.py +++ b/homeassistant/components/mqtt/device_trigger.py @@ -34,7 +34,7 @@ from .const import ( CONF_TOPIC, DOMAIN, ) -from .discovery import MQTTDiscoveryPayload +from .discovery import MQTTDiscoveryPayload, clear_discovery_hash from .mixins import ( MQTT_ENTITY_DEVICE_INFO_SCHEMA, MqttDiscoveryDeviceUpdate, @@ -62,10 +62,13 @@ TRIGGER_SCHEMA = DEVICE_TRIGGER_BASE_SCHEMA.extend( vol.Required(CONF_PLATFORM): DEVICE, vol.Required(CONF_DOMAIN): DOMAIN, vol.Required(CONF_DEVICE_ID): str, - vol.Required(CONF_DISCOVERY_ID): str, + # The use of CONF_DISCOVERY_ID was deprecated in HA Core 2024.2. + # By default, a MQTT device trigger now will be referenced by + # device_id, type and subtype instead. + vol.Optional(CONF_DISCOVERY_ID): str, vol.Required(CONF_TYPE): cv.string, vol.Required(CONF_SUBTYPE): cv.string, - } + }, ) TRIGGER_DISCOVERY_SCHEMA = MQTT_BASE_SCHEMA.extend( @@ -123,6 +126,7 @@ class Trigger: device_id: str = attr.ib() discovery_data: DiscoveryInfoType | None = attr.ib() + discovery_id: str | None = attr.ib() hass: HomeAssistant = attr.ib() payload: str | None = attr.ib() qos: int | None = attr.ib() @@ -202,6 +206,7 @@ class MqttDeviceTrigger(MqttDiscoveryDeviceUpdate): self.discovery_data = discovery_data self.hass = hass self._mqtt_data = get_mqtt_data(hass) + self.trigger_id = f"{device_id}_{config[CONF_TYPE]}_{config[CONF_SUBTYPE]}" MqttDiscoveryDeviceUpdate.__init__( self, @@ -216,11 +221,19 @@ class MqttDeviceTrigger(MqttDiscoveryDeviceUpdate): """Initialize the device trigger.""" discovery_hash = self.discovery_data[ATTR_DISCOVERY_HASH] discovery_id = discovery_hash[1] - if discovery_id not in self._mqtt_data.device_triggers: - self._mqtt_data.device_triggers[discovery_id] = Trigger( + # The use of CONF_DISCOVERY_ID was deprecated in HA Core 2024.2. + # To make sure old automation keep working we determine the trigger_id + # based on the discovery_id if it is set. + for trigger_id, trigger in self._mqtt_data.device_triggers.items(): + if trigger.discovery_id == discovery_id: + self.trigger_id = trigger_id + break + if self.trigger_id not in self._mqtt_data.device_triggers: + self._mqtt_data.device_triggers[self.trigger_id] = Trigger( hass=self.hass, device_id=self.device_id, discovery_data=self.discovery_data, + discovery_id=discovery_id, type=self._config[CONF_TYPE], subtype=self._config[CONF_SUBTYPE], topic=self._config[CONF_TOPIC], @@ -229,7 +242,7 @@ class MqttDeviceTrigger(MqttDiscoveryDeviceUpdate): value_template=self._config[CONF_VALUE_TEMPLATE], ) else: - await self._mqtt_data.device_triggers[discovery_id].update_trigger( + await self._mqtt_data.device_triggers[self.trigger_id].update_trigger( self._config ) debug_info.add_trigger_discovery_data( @@ -239,22 +252,39 @@ class MqttDeviceTrigger(MqttDiscoveryDeviceUpdate): async def async_update(self, discovery_data: MQTTDiscoveryPayload) -> None: """Handle MQTT device trigger discovery updates.""" discovery_hash = self.discovery_data[ATTR_DISCOVERY_HASH] - discovery_id = discovery_hash[1] debug_info.update_trigger_discovery_data( self.hass, discovery_hash, discovery_data ) config = TRIGGER_DISCOVERY_SCHEMA(discovery_data) + new_trigger_id = f"{self.device_id}_{config[CONF_TYPE]}_{config[CONF_SUBTYPE]}" + if new_trigger_id != self.trigger_id: + mqtt_data = get_mqtt_data(self.hass) + if new_trigger_id in mqtt_data.device_triggers: + _LOGGER.error( + "Cannot update device trigger %s due to an existing duplicate " + "device trigger with the same device_id, " + "type and subtype. Got: %s", + discovery_hash, + config, + ) + return + # Update trigger_id based index after update of type or subtype + mqtt_data.device_triggers[new_trigger_id] = mqtt_data.device_triggers.pop( + self.trigger_id + ) + self.trigger_id = new_trigger_id + update_device(self.hass, self._config_entry, config) - device_trigger: Trigger = self._mqtt_data.device_triggers[discovery_id] + device_trigger: Trigger = self._mqtt_data.device_triggers[self.trigger_id] await device_trigger.update_trigger(config) async def async_tear_down(self) -> None: """Cleanup device trigger.""" discovery_hash = self.discovery_data[ATTR_DISCOVERY_HASH] - discovery_id = discovery_hash[1] - if discovery_id in self._mqtt_data.device_triggers: + if self.trigger_id in self._mqtt_data.device_triggers: _LOGGER.info("Removing trigger: %s", discovery_hash) - trigger: Trigger = self._mqtt_data.device_triggers[discovery_id] + trigger: Trigger = self._mqtt_data.device_triggers[self.trigger_id] + trigger.discovery_data = None trigger.detach_trigger() debug_info.remove_trigger_discovery_data(self.hass, discovery_hash) @@ -267,7 +297,30 @@ async def async_setup_trigger( ) -> None: """Set up the MQTT device trigger.""" config = TRIGGER_DISCOVERY_SCHEMA(config) + + # We update the device based on the trigger config to obtain the device_id. + # In all cases the setup will lead to device entry to be created or updated. + # If the trigger is a duplicate, trigger creation will be cancelled but we allow + # the device data to be updated to not add additional complexity to the code. device_id = update_device(hass, config_entry, config) + discovery_id = discovery_data[ATTR_DISCOVERY_HASH][1] + trigger_type = config[CONF_TYPE] + trigger_subtype = config[CONF_SUBTYPE] + trigger_id = f"{device_id}_{trigger_type}_{trigger_subtype}" + mqtt_data = get_mqtt_data(hass) + if ( + trigger_id in mqtt_data.device_triggers + and mqtt_data.device_triggers[trigger_id].discovery_data is not None + ): + _LOGGER.error( + "Config for device trigger %s conflicts with existing " + "device trigger, cannot set up trigger, got: %s", + discovery_id, + config, + ) + send_discovery_done(hass, discovery_data) + clear_discovery_hash(hass, discovery_data[ATTR_DISCOVERY_HASH]) + return None if TYPE_CHECKING: assert isinstance(device_id, str) @@ -283,8 +336,9 @@ async def async_removed_from_device(hass: HomeAssistant, device_id: str) -> None mqtt_data = get_mqtt_data(hass) triggers = await async_get_triggers(hass, device_id) for trig in triggers: - device_trigger: Trigger = mqtt_data.device_triggers.pop(trig[CONF_DISCOVERY_ID]) - if device_trigger: + trigger_id = f"{device_id}_{trig[CONF_TYPE]}_{trig[CONF_SUBTYPE]}" + if trigger_id in mqtt_data.device_triggers: + device_trigger = mqtt_data.device_triggers.pop(trigger_id) device_trigger.detach_trigger() discovery_data = device_trigger.discovery_data if TYPE_CHECKING: @@ -303,7 +357,7 @@ async def async_get_triggers( if not mqtt_data.device_triggers: return triggers - for discovery_id, trig in mqtt_data.device_triggers.items(): + for trig in mqtt_data.device_triggers.values(): if trig.device_id != device_id or trig.topic is None: continue @@ -312,7 +366,6 @@ async def async_get_triggers( "device_id": device_id, "type": trig.type, "subtype": trig.subtype, - "discovery_id": discovery_id, } triggers.append(trigger) @@ -326,15 +379,33 @@ async def async_attach_trigger( trigger_info: TriggerInfo, ) -> CALLBACK_TYPE: """Attach a trigger.""" + trigger_id: str | None = None mqtt_data = get_mqtt_data(hass) device_id = config[CONF_DEVICE_ID] - discovery_id = config[CONF_DISCOVERY_ID] - if discovery_id not in mqtt_data.device_triggers: - mqtt_data.device_triggers[discovery_id] = Trigger( + # The use of CONF_DISCOVERY_ID was deprecated in HA Core 2024.2. + # In case CONF_DISCOVERY_ID is still used in an automation, + # we reference the device trigger by discovery_id instead of + # referencing it by device_id, type and subtype, which is the default. + discovery_id: str | None = config.get(CONF_DISCOVERY_ID) + if discovery_id is not None: + for trig_id, trig in mqtt_data.device_triggers.items(): + if trig.discovery_id == discovery_id: + trigger_id = trig_id + break + + # Reference the device trigger by device_id, type and subtype. + if trigger_id is None: + trigger_type = config[CONF_TYPE] + trigger_subtype = config[CONF_SUBTYPE] + trigger_id = f"{device_id}_{trigger_type}_{trigger_subtype}" + + if trigger_id not in mqtt_data.device_triggers: + mqtt_data.device_triggers[trigger_id] = Trigger( hass=hass, device_id=device_id, discovery_data=None, + discovery_id=discovery_id, type=config[CONF_TYPE], subtype=config[CONF_SUBTYPE], topic=None, @@ -342,6 +413,5 @@ async def async_attach_trigger( qos=None, value_template=None, ) - return await mqtt_data.device_triggers[discovery_id].add_trigger( - action, trigger_info - ) + + return await mqtt_data.device_triggers[trigger_id].add_trigger(action, trigger_info) diff --git a/tests/components/mqtt/test_device_trigger.py b/tests/components/mqtt/test_device_trigger.py index fffb9e57f84..ade28ac2c1d 100644 --- a/tests/components/mqtt/test_device_trigger.py +++ b/tests/components/mqtt/test_device_trigger.py @@ -70,7 +70,6 @@ async def test_get_triggers( "platform": "device", "domain": DOMAIN, "device_id": device_entry.id, - "discovery_id": "bla", "type": "button_short_press", "subtype": "button_1", "metadata": {}, @@ -191,7 +190,6 @@ async def test_discover_bad_triggers( "platform": "device", "domain": DOMAIN, "device_id": device_entry.id, - "discovery_id": "bla", "type": "button_short_press", "subtype": "button_1", "metadata": {}, @@ -207,12 +205,13 @@ async def test_update_remove_triggers( hass: HomeAssistant, device_registry: dr.DeviceRegistry, mqtt_mock_entry: MqttMockHAClientGenerator, + caplog: pytest.LogCaptureFixture, ) -> None: """Test triggers can be updated and removed.""" await mqtt_mock_entry() config1 = { "automation_type": "trigger", - "device": {"identifiers": ["0AFFD2"]}, + "device": {"identifiers": ["0AFFD2"], "name": "milk"}, "payload": "short_press", "topic": "foobar/triggers/button1", "type": "button_short_press", @@ -223,25 +222,36 @@ async def test_update_remove_triggers( config2 = { "automation_type": "trigger", - "device": {"identifiers": ["0AFFD2"]}, + "device": {"identifiers": ["0AFFD2"], "name": "beer"}, + "payload": "short_press", + "topic": "foobar/triggers/button1", + "type": "button_short_press", + "subtype": "button_1", + } + config2["topic"] = "foobar/tag_scanned2" + data2 = json.dumps(config2) + + config3 = { + "automation_type": "trigger", + "device": {"identifiers": ["0AFFD2"], "name": "beer"}, "payload": "short_press", "topic": "foobar/triggers/button1", "type": "button_short_press", "subtype": "button_2", } - config2["topic"] = "foobar/tag_scanned2" - data2 = json.dumps(config2) + config3["topic"] = "foobar/tag_scanned2" + data3 = json.dumps(config3) async_fire_mqtt_message(hass, "homeassistant/device_automation/bla/config", data1) await hass.async_block_till_done() device_entry = device_registry.async_get_device(identifiers={("mqtt", "0AFFD2")}) + assert device_entry.name == "milk" expected_triggers1 = [ { "platform": "device", "domain": DOMAIN, "device_id": device_entry.id, - "discovery_id": "bla", "type": "button_short_press", "subtype": "button_1", "metadata": {}, @@ -254,11 +264,21 @@ async def test_update_remove_triggers( hass, DeviceAutomationType.TRIGGER, device_entry.id ) assert triggers == unordered(expected_triggers1) + assert device_entry.name == "milk" - # Update trigger + # Update trigger topic async_fire_mqtt_message(hass, "homeassistant/device_automation/bla/config", data2) await hass.async_block_till_done() + triggers = await async_get_device_automations( + hass, DeviceAutomationType.TRIGGER, device_entry.id + ) + assert triggers == unordered(expected_triggers1) + device_entry = device_registry.async_get_device(identifiers={("mqtt", "0AFFD2")}) + assert device_entry.name == "beer" + # Update trigger type / subtype + async_fire_mqtt_message(hass, "homeassistant/device_automation/bla/config", data3) + await hass.async_block_till_done() triggers = await async_get_device_automations( hass, DeviceAutomationType.TRIGGER, device_entry.id ) @@ -275,7 +295,7 @@ async def test_update_remove_triggers( async def test_if_fires_on_mqtt_message( hass: HomeAssistant, device_registry: dr.DeviceRegistry, - calls, + calls: list[ServiceCall], mqtt_mock_entry: MqttMockHAClientGenerator, ) -> None: """Test triggers firing.""" @@ -351,10 +371,202 @@ async def test_if_fires_on_mqtt_message( assert calls[1].data["some"] == "long_press" +async def test_if_discovery_id_is_prefered( + hass: HomeAssistant, + device_registry: dr.DeviceRegistry, + calls: list[ServiceCall], + mqtt_mock_entry: MqttMockHAClientGenerator, +) -> None: + """Test if discovery is preferred over referencing by type/subtype. + + The use of CONF_DISCOVERY_ID was deprecated in HA Core 2024.2. + By default, a MQTT device trigger now will be referenced by + device_id, type and subtype instead. + If discovery_id is found an an automation it will have a higher + priority and than type and subtype. + """ + await mqtt_mock_entry() + data1 = ( + '{ "automation_type":"trigger",' + ' "device":{"identifiers":["0AFFD2"]},' + ' "payload": "short_press",' + ' "topic": "foobar/triggers/button1",' + ' "type": "button_short_press",' + ' "subtype": "button_1" }' + ) + # type and subtype of data 2 do not match with the type and subtype + # in the automation, because discovery_id matches, the trigger will fire + data2 = ( + '{ "automation_type":"trigger",' + ' "device":{"identifiers":["0AFFD2"]},' + ' "payload": "long_press",' + ' "topic": "foobar/triggers/button1",' + ' "type": "button_long_press",' + ' "subtype": "button_2" }' + ) + async_fire_mqtt_message(hass, "homeassistant/device_automation/bla1/config", data1) + async_fire_mqtt_message(hass, "homeassistant/device_automation/bla2/config", data2) + await hass.async_block_till_done() + device_entry = device_registry.async_get_device(identifiers={("mqtt", "0AFFD2")}) + + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: [ + { + "trigger": { + "platform": "device", + "domain": DOMAIN, + "device_id": device_entry.id, + "type": "button_short_press", + "subtype": "button_1", + }, + "action": { + "service": "test.automation", + "data_template": {"some": ("short_press")}, + }, + }, + { + "trigger": { + "platform": "device", + "domain": DOMAIN, + "device_id": device_entry.id, + "discovery_id": "bla2", + "type": "completely_different_type", + "subtype": "completely_different_sub_type", + }, + "action": { + "service": "test.automation", + "data_template": {"some": ("long_press")}, + }, + }, + ] + }, + ) + + # Fake short press, matching on type and subtype + async_fire_mqtt_message(hass, "foobar/triggers/button1", "short_press") + await hass.async_block_till_done() + assert len(calls) == 1 + assert calls[0].data["some"] == "short_press" + + # Fake long press, matching on discovery_id + calls.clear() + async_fire_mqtt_message(hass, "foobar/triggers/button1", "long_press") + await hass.async_block_till_done() + assert len(calls) == 1 + assert calls[0].data["some"] == "long_press" + + +async def test_non_unique_triggers( + hass: HomeAssistant, + device_registry: dr.DeviceRegistry, + calls: list[ServiceCall], + mqtt_mock_entry: MqttMockHAClientGenerator, + caplog: pytest.LogCaptureFixture, +) -> None: + """Test non unique triggers.""" + await mqtt_mock_entry() + data1 = ( + '{ "automation_type":"trigger",' + ' "device":{"identifiers":["0AFFD2"], "name": "milk"},' + ' "payload": "short_press",' + ' "topic": "foobar/triggers/button1",' + ' "type": "press",' + ' "subtype": "button" }' + ) + data2 = ( + '{ "automation_type":"trigger",' + ' "device":{"identifiers":["0AFFD2"], "name": "beer"},' + ' "payload": "long_press",' + ' "topic": "foobar/triggers/button2",' + ' "type": "press",' + ' "subtype": "button" }' + ) + async_fire_mqtt_message(hass, "homeassistant/device_automation/bla1/config", data1) + await hass.async_block_till_done() + device_entry = device_registry.async_get_device(identifiers={("mqtt", "0AFFD2")}) + assert device_entry.name == "milk" + + async_fire_mqtt_message(hass, "homeassistant/device_automation/bla2/config", data2) + await hass.async_block_till_done() + device_entry = device_registry.async_get_device(identifiers={("mqtt", "0AFFD2")}) + # The device entry was updated, but the trigger was not unique + # and therefore it was not set up. + assert device_entry.name == "beer" + assert ( + "Config for device trigger bla2 conflicts with existing device trigger, cannot set up trigger" + in caplog.text + ) + + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: [ + { + "trigger": { + "platform": "device", + "domain": DOMAIN, + "device_id": device_entry.id, + "type": "press", + "subtype": "button", + }, + "action": { + "service": "test.automation", + "data_template": {"some": ("press1")}, + }, + }, + { + "trigger": { + "platform": "device", + "domain": DOMAIN, + "device_id": device_entry.id, + "type": "press", + "subtype": "button", + }, + "action": { + "service": "test.automation", + "data_template": {"some": ("press2")}, + }, + }, + ] + }, + ) + + # Try to trigger first config. + # and triggers both attached instances. + async_fire_mqtt_message(hass, "foobar/triggers/button1", "short_press") + await hass.async_block_till_done() + assert len(calls) == 2 + assert calls[0].data["some"] == "press1" + assert calls[1].data["some"] == "press2" + + # Trigger second config references to same trigger + # and triggers both attached instances. + async_fire_mqtt_message(hass, "foobar/triggers/button2", "long_press") + await hass.async_block_till_done() + assert len(calls) == 2 + assert calls[0].data["some"] == "press1" + assert calls[1].data["some"] == "press2" + + # Removing the first trigger will clean up + calls.clear() + async_fire_mqtt_message(hass, "homeassistant/device_automation/bla1/config", "") + await hass.async_block_till_done() + await hass.async_block_till_done() + assert ( + "Device trigger ('device_automation', 'bla1') has been removed" in caplog.text + ) + async_fire_mqtt_message(hass, "foobar/triggers/button1", "short_press") + assert len(calls) == 0 + + async def test_if_fires_on_mqtt_message_template( hass: HomeAssistant, device_registry: dr.DeviceRegistry, - calls, + calls: list[ServiceCall], mqtt_mock_entry: MqttMockHAClientGenerator, ) -> None: """Test triggers firing.""" @@ -435,7 +647,7 @@ async def test_if_fires_on_mqtt_message_template( async def test_if_fires_on_mqtt_message_late_discover( hass: HomeAssistant, device_registry: dr.DeviceRegistry, - calls, + calls: list[ServiceCall], mqtt_mock_entry: MqttMockHAClientGenerator, ) -> None: """Test triggers firing of MQTT device triggers discovered after setup.""" @@ -522,8 +734,9 @@ async def test_if_fires_on_mqtt_message_late_discover( async def test_if_fires_on_mqtt_message_after_update( hass: HomeAssistant, device_registry: dr.DeviceRegistry, - calls, + calls: list[ServiceCall], mqtt_mock_entry: MqttMockHAClientGenerator, + caplog: pytest.LogCaptureFixture, ) -> None: """Test triggers firing after update.""" await mqtt_mock_entry() @@ -537,11 +750,19 @@ async def test_if_fires_on_mqtt_message_after_update( data2 = ( '{ "automation_type":"trigger",' ' "device":{"identifiers":["0AFFD2"]},' - ' "topic": "foobar/triggers/buttonOne",' - ' "type": "button_long_press",' + ' "topic": "foobar/triggers/button1",' + ' "type": "button_short_press",' ' "subtype": "button_2" }' ) + data3 = ( + '{ "automation_type":"trigger",' + ' "device":{"identifiers":["0AFFD2"]},' + ' "topic": "foobar/triggers/buttonOne",' + ' "type": "button_short_press",' + ' "subtype": "button_1" }' + ) async_fire_mqtt_message(hass, "homeassistant/device_automation/bla1/config", data1) + async_fire_mqtt_message(hass, "homeassistant/device_automation/bla2/config", data2) await hass.async_block_till_done() device_entry = device_registry.async_get_device(identifiers={("mqtt", "0AFFD2")}) @@ -574,29 +795,38 @@ async def test_if_fires_on_mqtt_message_after_update( await hass.async_block_till_done() assert len(calls) == 1 + # Update the trigger with existing type/subtype change + async_fire_mqtt_message(hass, "homeassistant/device_automation/bla2/config", data1) + await hass.async_block_till_done() + assert "Cannot update device trigger ('device_automation', 'bla2')" in caplog.text + # Update the trigger with different topic - async_fire_mqtt_message(hass, "homeassistant/device_automation/bla1/config", data2) + async_fire_mqtt_message(hass, "homeassistant/device_automation/bla1/config", data3) await hass.async_block_till_done() + calls.clear() async_fire_mqtt_message(hass, "foobar/triggers/button1", "") await hass.async_block_till_done() + assert len(calls) == 0 + + calls.clear() + async_fire_mqtt_message(hass, "foobar/triggers/buttonOne", "") + await hass.async_block_till_done() assert len(calls) == 1 - async_fire_mqtt_message(hass, "foobar/triggers/buttonOne", "") - await hass.async_block_till_done() - assert len(calls) == 2 - # Update the trigger with same topic - async_fire_mqtt_message(hass, "homeassistant/device_automation/bla1/config", data2) + async_fire_mqtt_message(hass, "homeassistant/device_automation/bla1/config", data3) await hass.async_block_till_done() + calls.clear() async_fire_mqtt_message(hass, "foobar/triggers/button1", "") await hass.async_block_till_done() - assert len(calls) == 2 + assert len(calls) == 0 + calls.clear() async_fire_mqtt_message(hass, "foobar/triggers/buttonOne", "") await hass.async_block_till_done() - assert len(calls) == 3 + assert len(calls) == 1 async def test_no_resubscribe_same_topic( @@ -649,7 +879,7 @@ async def test_no_resubscribe_same_topic( async def test_not_fires_on_mqtt_message_after_remove_by_mqtt( hass: HomeAssistant, device_registry: dr.DeviceRegistry, - calls, + calls: list[ServiceCall], mqtt_mock_entry: MqttMockHAClientGenerator, ) -> None: """Test triggers not firing after removal.""" @@ -715,7 +945,7 @@ async def test_not_fires_on_mqtt_message_after_remove_from_registry( hass: HomeAssistant, hass_ws_client: WebSocketGenerator, device_registry: dr.DeviceRegistry, - calls, + calls: list[ServiceCall], mqtt_mock_entry: MqttMockHAClientGenerator, ) -> None: """Test triggers not firing after removal.""" @@ -1411,9 +1641,9 @@ async def test_trigger_debug_info( config1 = { "platform": "mqtt", "automation_type": "trigger", - "topic": "test-topic", + "topic": "test-topic1", "type": "foo", - "subtype": "bar", + "subtype": "bar1", "device": { "connections": [[dr.CONNECTION_NETWORK_MAC, "02:5b:26:a8:dc:12"]], "manufacturer": "Whatever", @@ -1427,7 +1657,7 @@ async def test_trigger_debug_info( "automation_type": "trigger", "topic": "test-topic2", "type": "foo", - "subtype": "bar", + "subtype": "bar2", "device": { "connections": [[dr.CONNECTION_NETWORK_MAC, "02:5b:26:a8:dc:12"]], }, @@ -1477,7 +1707,7 @@ async def test_trigger_debug_info( async def test_unload_entry( hass: HomeAssistant, - calls, + calls: list[ServiceCall], device_registry: dr.DeviceRegistry, mqtt_mock: MqttMockHAClient, ) -> None: