1 """Provides device automations for MQTT."""
3 from __future__
import annotations
5 from collections.abc
import Callable
6 from dataclasses
import dataclass, field
8 from typing
import TYPE_CHECKING, Any
10 import voluptuous
as vol
28 from .
import debug_info, trigger
as mqtt_trigger
29 from .config
import MQTT_BASE_SCHEMA
38 from .discovery
import MQTTDiscoveryPayload, clear_discovery_hash
39 from .entity
import MqttDiscoveryDeviceUpdateMixin, send_discovery_done, update_device
40 from .models
import DATA_MQTT
41 from .schemas
import MQTT_ENTITY_DEVICE_INFO_SCHEMA
43 _LOGGER = logging.getLogger(__name__)
45 CONF_AUTOMATION_TYPE =
"automation_type"
46 CONF_DISCOVERY_ID =
"discovery_id"
47 CONF_SUBTYPE =
"subtype"
48 DEFAULT_ENCODING =
"utf-8"
53 CONF_PLATFORM: DEVICE,
57 TRIGGER_SCHEMA = DEVICE_TRIGGER_BASE_SCHEMA.extend(
59 vol.Required(CONF_PLATFORM): DEVICE,
60 vol.Required(CONF_DOMAIN): DOMAIN,
61 vol.Required(CONF_DEVICE_ID): str,
65 vol.Optional(CONF_DISCOVERY_ID): str,
66 vol.Required(CONF_TYPE): cv.string,
67 vol.Required(CONF_SUBTYPE): cv.string,
71 TRIGGER_DISCOVERY_SCHEMA = MQTT_BASE_SCHEMA.extend(
73 vol.Required(CONF_AUTOMATION_TYPE): str,
74 vol.Required(CONF_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA,
75 vol.Optional(CONF_PAYLOAD, default=
None): vol.Any(
None, cv.string),
76 vol.Required(CONF_SUBTYPE): cv.string,
77 vol.Required(CONF_TOPIC): cv.string,
78 vol.Required(CONF_TYPE): cv.string,
79 vol.Optional(CONF_VALUE_TEMPLATE, default=
None): vol.Any(
None, cv.string),
81 extra=vol.REMOVE_EXTRA,
84 LOG_NAME =
"Device trigger"
87 @dataclass(slots=True)
89 """Attached trigger settings."""
91 action: TriggerActionType
92 trigger_info: TriggerInfo
94 remove: CALLBACK_TYPE |
None =
None
97 """Attach MQTT trigger."""
98 mqtt_config: dict[str, Any] = {
99 CONF_PLATFORM: DOMAIN,
100 CONF_TOPIC: self.trigger.topic,
101 CONF_ENCODING: DEFAULT_ENCODING,
102 CONF_QOS: self.trigger.qos,
104 if self.trigger.payload:
105 mqtt_config[CONF_PAYLOAD] = self.trigger.payload
106 if self.trigger.value_template:
107 mqtt_config[CONF_VALUE_TEMPLATE] = self.trigger.value_template
108 mqtt_config = mqtt_trigger.TRIGGER_SCHEMA(mqtt_config)
112 self.
removeremove = await mqtt_trigger.async_attach_trigger(
120 @dataclass(slots=True, kw_only=True)
122 """Device trigger settings."""
125 discovery_data: DiscoveryInfoType |
None =
None
126 discovery_id: str |
None =
None
133 value_template: str |
None
134 trigger_instances: list[TriggerInstance] = field(default_factory=list)
137 self, action: TriggerActionType, trigger_info: TriggerInfo
138 ) -> Callable[[],
None]:
139 """Add MQTT trigger."""
141 self.trigger_instances.append(instance)
143 if self.
topictopic
is not None:
145 await instance.async_attach_trigger()
149 """Remove trigger."""
150 if instance
not in self.trigger_instances:
155 self.trigger_instances.
remove(instance)
159 async
def update_trigger(self, config: ConfigType) ->
None:
160 """Update MQTT device trigger."""
161 self.
typetype = config[CONF_TYPE]
164 self.
qosqos = config[CONF_QOS]
165 topic_changed = self.
topictopic != config[CONF_TOPIC]
166 self.
topictopic = config[CONF_TOPIC]
173 for trig
in self.trigger_instances:
174 await trig.async_attach_trigger()
176 def detach_trigger(self) -> None:
177 """Remove MQTT device trigger."""
179 self.
topictopic =
None
182 for trig
in self.trigger_instances:
189 """Setup a MQTT device trigger with auto discovery."""
196 discovery_data: DiscoveryInfoType,
197 config_entry: ConfigEntry,
206 self.
trigger_idtrigger_id = f
"{device_id}_{config[CONF_TYPE]}_{config[CONF_SUBTYPE]}"
208 MqttDiscoveryDeviceUpdateMixin.__init__(
218 """Initialize the device trigger."""
219 discovery_hash = self.
discovery_datadiscovery_data[ATTR_DISCOVERY_HASH]
220 discovery_id = discovery_hash[1]
224 for trigger_id, trigger
in self.
_mqtt_data_mqtt_data.device_triggers.items():
225 if trigger.discovery_id == discovery_id:
233 discovery_id=discovery_id,
234 type=self.
_config_config[CONF_TYPE],
235 subtype=self.
_config_config[CONF_SUBTYPE],
236 topic=self.
_config_config[CONF_TOPIC],
237 payload=self.
_config_config[CONF_PAYLOAD],
238 qos=self.
_config_config[CONF_QOS],
239 value_template=self.
_config_config[CONF_VALUE_TEMPLATE],
245 debug_info.add_trigger_discovery_data(
249 async
def async_update(self, discovery_data: MQTTDiscoveryPayload) ->
None:
250 """Handle MQTT device trigger discovery updates."""
251 discovery_hash = self.
discovery_datadiscovery_data[ATTR_DISCOVERY_HASH]
252 debug_info.update_trigger_discovery_data(
253 self.
hasshasshass, discovery_hash, discovery_data
256 new_trigger_id = f
"{self.device_id}_{config[CONF_TYPE]}_{config[CONF_SUBTYPE]}"
257 if new_trigger_id != self.
trigger_idtrigger_id:
258 mqtt_data = self.
hasshasshass.data[DATA_MQTT]
259 if new_trigger_id
in mqtt_data.device_triggers:
261 "Cannot update device trigger %s due to an existing duplicate "
262 "device trigger with the same device_id, "
263 "type and subtype. Got: %s",
269 mqtt_data.device_triggers[new_trigger_id] = mqtt_data.device_triggers.pop(
276 await device_trigger.update_trigger(config)
279 """Cleanup device trigger."""
280 discovery_hash = self.
discovery_datadiscovery_data[ATTR_DISCOVERY_HASH]
282 _LOGGER.info(
"Removing trigger: %s", discovery_hash)
284 trigger.discovery_data =
None
285 trigger.detach_trigger()
286 debug_info.remove_trigger_discovery_data(self.
hasshasshass, discovery_hash)
292 config_entry: ConfigEntry,
293 discovery_data: DiscoveryInfoType,
295 """Set up the MQTT device trigger."""
303 discovery_id = discovery_data[ATTR_DISCOVERY_HASH][1]
304 trigger_type = config[CONF_TYPE]
305 trigger_subtype = config[CONF_SUBTYPE]
306 trigger_id = f
"{device_id}_{trigger_type}_{trigger_subtype}"
307 mqtt_data = hass.data[DATA_MQTT]
309 trigger_id
in mqtt_data.device_triggers
310 and mqtt_data.device_triggers[trigger_id].discovery_data
is not None
313 "Config for device trigger %s conflicts with existing "
314 "device trigger, cannot set up trigger, got: %s",
323 assert isinstance(device_id, str)
325 hass, config, device_id, discovery_data, config_entry
327 await mqtt_device_trigger.async_setup()
332 """Handle Mqtt removed from a device."""
333 mqtt_data = hass.data[DATA_MQTT]
335 for trig
in triggers:
336 trigger_id = f
"{device_id}_{trig[CONF_TYPE]}_{trig[CONF_SUBTYPE]}"
337 if trigger_id
in mqtt_data.device_triggers:
338 device_trigger = mqtt_data.device_triggers.pop(trigger_id)
339 device_trigger.detach_trigger()
340 discovery_data = device_trigger.discovery_data
342 assert discovery_data
is not None
343 discovery_hash = discovery_data[ATTR_DISCOVERY_HASH]
344 debug_info.remove_trigger_discovery_data(hass, discovery_hash)
348 hass: HomeAssistant, device_id: str
349 ) -> list[dict[str, str]]:
350 """List device triggers for MQTT devices."""
351 mqtt_data = hass.data[DATA_MQTT]
353 if not mqtt_data.device_triggers:
359 "device_id": device_id,
361 "subtype": trig.subtype,
363 for trig
in mqtt_data.device_triggers.values()
364 if trig.device_id == device_id
and trig.topic
is not None
371 action: TriggerActionType,
372 trigger_info: TriggerInfo,
374 """Attach a trigger."""
375 trigger_id: str |
None =
None
376 mqtt_data = hass.data[DATA_MQTT]
377 device_id = config[CONF_DEVICE_ID]
383 discovery_id: str |
None = config.get(CONF_DISCOVERY_ID)
384 if discovery_id
is not None:
385 for trig_id, trig
in mqtt_data.device_triggers.items():
386 if trig.discovery_id == discovery_id:
391 if trigger_id
is None:
392 trigger_type = config[CONF_TYPE]
393 trigger_subtype = config[CONF_SUBTYPE]
394 trigger_id = f
"{device_id}_{trigger_type}_{trigger_subtype}"
396 if trigger_id
not in mqtt_data.device_triggers:
397 mqtt_data.device_triggers[trigger_id] =
Trigger(
401 discovery_id=discovery_id,
402 type=config[CONF_TYPE],
403 subtype=config[CONF_SUBTYPE],
410 return await mqtt_data.device_triggers[trigger_id].add_trigger(action, trigger_info)
None __init__(self, HomeAssistant hass, ConfigType config, str device_id, DiscoveryInfoType discovery_data, ConfigEntry config_entry)
None async_update(self, MQTTDiscoveryPayload discovery_data)
None async_tear_down(self)
None async_attach_trigger(self)
Callable[[], None] add_trigger(self, TriggerActionType action, TriggerInfo trigger_info)
bool remove(self, _T matcher)
None async_removed_from_device(HomeAssistant hass, str device_id)
CALLBACK_TYPE async_attach_trigger(HomeAssistant hass, ConfigType config, TriggerActionType action, TriggerInfo trigger_info)
list[dict[str, str]] async_get_triggers(HomeAssistant hass, str device_id)
None async_setup_trigger(HomeAssistant hass, ConfigType config, ConfigEntry config_entry, DiscoveryInfoType discovery_data)
None clear_discovery_hash(HomeAssistant hass, tuple[str, str] discovery_hash)
str|None update_device(HomeAssistant hass, ConfigEntry config_entry, ConfigType config)
None send_discovery_done(HomeAssistant hass, DiscoveryInfoType discovery_data)
None async_remove(HomeAssistant hass, str intent_type)