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 <erik@montnemery.com> * Update homeassistant/components/mqtt/device_trigger.py Co-authored-by: Erik Montnemery <erik@montnemery.com> --------- Co-authored-by: Erik Montnemery <erik@montnemery.com>pull/108318/head
parent
066a0ccc6d
commit
04f0128a1c
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in New Issue