1 """MQTT (entity) component mixins and helpers."""
3 from __future__
import annotations
5 from abc
import ABC, abstractmethod
6 from collections.abc
import Callable, Coroutine
7 from functools
import partial
9 from typing
import TYPE_CHECKING, Any, Protocol, cast, final
11 import voluptuous
as vol
15 ATTR_CONFIGURATION_URL,
39 EventDeviceRegistryUpdatedData,
42 async_dispatcher_connect,
43 async_dispatcher_send,
48 async_track_device_registry_updated_event,
49 async_track_entity_registry_updated_event,
63 from .
import debug_info, subscription
64 from .client
import async_publish
67 ATTR_DISCOVERY_PAYLOAD,
72 CONF_AVAILABILITY_MODE,
73 CONF_AVAILABILITY_TEMPLATE,
74 CONF_AVAILABILITY_TOPIC,
75 CONF_CONFIGURATION_URL,
77 CONF_ENABLED_BY_DEFAULT,
82 CONF_JSON_ATTRS_TEMPLATE,
83 CONF_JSON_ATTRS_TOPIC,
86 CONF_PAYLOAD_AVAILABLE,
87 CONF_PAYLOAD_NOT_AVAILABLE,
98 MQTT_CONNECTION_STATE,
100 from .debug_info
import log_message
101 from .discovery
import (
104 MQTT_DISCOVERY_UPDATED,
105 MQTTDiscoveryPayload,
106 clear_discovery_hash,
107 get_origin_log_string,
108 get_origin_support_url,
111 from .models
import (
115 MqttValueTemplateException,
119 from .subscription
import (
121 async_prepare_subscribe_topics,
122 async_subscribe_topics_internal,
123 async_unsubscribe_topics,
125 from .util
import mqtt_config_entry_enabled
127 _LOGGER = logging.getLogger(__name__)
129 MQTT_ATTRIBUTES_BLOCKED = {
136 "entity_registry_enabled_default",
137 "extra_state_attributes",
143 "supported_features",
145 "unit_of_measurement",
151 discovery_payload: MQTTDiscoveryPayload, err: vol.Invalid
153 """Help handling schema errors on MQTT discovery messages."""
154 discovery_topic: str = discovery_payload.discovery_data[ATTR_DISCOVERY_TOPIC]
156 "Error '%s' when processing MQTT discovery message topic: '%s', message: '%s'",
165 discovery_payload: MQTTDiscoveryPayload,
167 """Handle discovery failure."""
168 discovery_hash = discovery_payload.discovery_data[ATTR_DISCOVERY_HASH]
174 hass: HomeAssistant, domain: str, discovery_payload: MQTTDiscoveryPayload
176 """Verify MQTT config entry is enabled or log warning."""
180 "MQTT integration is disabled, skipping setup of discovered item "
181 "MQTT %s, payload %s"
191 """Callback protocol for async_setup in async_setup_non_entity_entry_helper."""
194 self, config: ConfigType, discovery_data: DiscoveryInfoType
202 async_setup: _SetupNonEntityHelperCallbackProtocol,
203 discovery_schema: vol.Schema,
205 """Set up automation or tag creation dynamically through MQTT discovery."""
206 mqtt_data = hass.data[DATA_MQTT]
208 async
def _async_setup_non_entity_entry_from_discovery(
209 discovery_payload: MQTTDiscoveryPayload,
211 """Set up an MQTT entity, automation or tag from discovery."""
213 hass, domain, discovery_payload
217 config: ConfigType = discovery_schema(discovery_payload)
218 await
async_setup(config, discovery_data=discovery_payload.discovery_data)
219 except vol.Invalid
as err:
226 mqtt_data.reload_dispatchers.append(
229 MQTT_DISCOVERY_NEW.format(domain,
"mqtt"),
230 _async_setup_non_entity_entry_from_discovery,
239 entity_class: type[MqttEntity] |
None,
241 async_add_entities: AddEntitiesCallback,
242 discovery_schema: VolSchemaType,
243 platform_schema_modern: VolSchemaType,
244 schema_class_mapping: dict[str, type[MqttEntity]] |
None =
None,
246 """Set up entity creation dynamically through MQTT discovery."""
247 mqtt_data = hass.data[DATA_MQTT]
250 def _async_setup_entity_entry_from_discovery(
251 discovery_payload: MQTTDiscoveryPayload,
253 """Set up an MQTT entity from discovery."""
254 nonlocal entity_class
256 hass, domain, discovery_payload
260 config: DiscoveryInfoType = discovery_schema(discovery_payload)
261 if schema_class_mapping
is not None:
262 entity_class = schema_class_mapping[config[CONF_SCHEMA]]
264 assert entity_class
is not None
266 [entity_class(hass, config, entry, discovery_payload.discovery_data)]
268 except vol.Invalid
as err:
275 mqtt_data.reload_dispatchers.append(
278 MQTT_DISCOVERY_NEW.format(domain,
"mqtt"),
279 _async_setup_entity_entry_from_discovery,
284 def _async_setup_entities() -> None:
285 """Set up MQTT items from configuration.yaml."""
286 nonlocal entity_class
287 mqtt_data = hass.data[DATA_MQTT]
288 if not (config_yaml := mqtt_data.config):
290 yaml_configs: list[ConfigType] = [
292 for config_item
in config_yaml
293 for config_domain, configs
in config_item.items()
294 for config
in configs
295 if config_domain == domain
297 entities: list[Entity] = []
298 for yaml_config
in yaml_configs:
300 config = platform_schema_modern(yaml_config)
301 if schema_class_mapping
is not None:
302 entity_class = schema_class_mapping[config[CONF_SCHEMA]]
304 assert entity_class
is not None
305 entities.append(entity_class(hass, config, entry,
None))
306 except vol.Invalid
as exc:
308 config_file = getattr(yaml_config,
"__config_file__",
"?")
309 line = getattr(yaml_config,
"__line__",
"?")
310 issue_id = hex(hash(frozenset(yaml_config)))
311 yaml_config_str = yaml_dump(yaml_config)
313 f
"https://www.home-assistant.io/integrations/{domain}.mqtt/"
321 severity=IssueSeverity.ERROR,
322 learn_more_url=learn_more_url,
323 translation_placeholders={
325 "config_file": config_file,
327 "config": yaml_config_str,
330 translation_key=
"invalid_platform_config",
333 "%s for manually configured MQTT %s item, in %s, line %s Got %s",
345 mqtt_data.reload_schema[domain] = platform_schema_modern
347 mqtt_data.reload_handlers[domain] = _async_setup_entities
348 _async_setup_entities()
352 hass: HomeAssistant, entity: Entity, config: ConfigType, entity_id_format: str
354 """Set entity_id from object_id if defined in config."""
355 if CONF_OBJECT_ID
in config:
357 entity_id_format, config[CONF_OBJECT_ID],
None, hass
362 """Mixin used for platforms that support JSON attributes."""
364 _attributes_extra_blocked: frozenset[str] = frozenset()
365 _attr_tpl: Callable[[ReceivePayloadType], ReceivePayloadType] |
None =
None
368 """Initialize the JSON attributes mixin."""
373 """Subscribe MQTT events."""
379 """Handle updated discovery message."""
384 """Handle updated discovery message."""
388 """(Re)Subscribe to topics."""
391 template, entity=self
392 ).async_render_with_possible_json_value
397 CONF_JSON_ATTRS_TOPIC: {
399 "msg_callback": partial(
400 self._message_callback,
402 {
"_attr_extra_state_attributes"},
407 "job_type": HassJobType.Callback,
414 """(Re)Subscribe to topics."""
418 """Unsubscribe when removed."""
425 """Update extra state attributes."""
427 self.
_attr_tpl_attr_tpl(msg.payload)
if self.
_attr_tpl_attr_tpl
is not None else msg.payload
430 json_dict =
json_loads(payload)
if isinstance(payload, str)
else None
432 _LOGGER.warning(
"Erroneous JSON: %s", payload)
434 if isinstance(json_dict, dict):
437 for k, v
in json_dict.items()
438 if k
not in MQTT_ATTRIBUTES_BLOCKED
439 and k
not in self._attributes_extra_blocked
443 _LOGGER.warning(
"JSON result was not a dictionary")
447 """Mixin used for platforms that report availability."""
450 """Initialize the availability mixin."""
452 self.
_available_available: dict[str, str | bool] = {}
457 """Subscribe MQTT events."""
464 MQTT_CONNECTION_STATE,
470 """Handle updated discovery message."""
475 """Handle updated discovery message."""
480 self._avail_topics: dict[str, dict[str, Any]] = {}
481 if CONF_AVAILABILITY_TOPIC
in config:
482 self._avail_topics[config[CONF_AVAILABILITY_TOPIC]] = {
483 CONF_PAYLOAD_AVAILABLE: config[CONF_PAYLOAD_AVAILABLE],
484 CONF_PAYLOAD_NOT_AVAILABLE: config[CONF_PAYLOAD_NOT_AVAILABLE],
485 CONF_AVAILABILITY_TEMPLATE: config.get(CONF_AVAILABILITY_TEMPLATE),
488 if CONF_AVAILABILITY
in config:
489 avail: dict[str, Any]
490 for avail
in config[CONF_AVAILABILITY]:
491 self._avail_topics[avail[CONF_TOPIC]] = {
492 CONF_PAYLOAD_AVAILABLE: avail[CONF_PAYLOAD_AVAILABLE],
493 CONF_PAYLOAD_NOT_AVAILABLE: avail[CONF_PAYLOAD_NOT_AVAILABLE],
494 CONF_AVAILABILITY_TEMPLATE: avail.get(CONF_VALUE_TEMPLATE),
497 for avail_topic_conf
in self._avail_topics.values():
498 if template := avail_topic_conf[CONF_AVAILABILITY_TEMPLATE]:
500 template, entity=self
501 ).async_render_with_possible_json_value
506 """(Re)Subscribe to topics."""
508 topic: (self.
_available_available.
get(topic,
False))
for topic
in self._avail_topics
510 topics: dict[str, dict[str, Any]] = {
511 f
"availability_{topic}": {
513 "msg_callback": partial(
514 self._message_callback,
520 "encoding": self.
_avail_config_avail_config[CONF_ENCODING]
or None,
521 "job_type": HassJobType.Callback,
523 for topic
in self._avail_topics
534 """Handle a new received MQTT availability message."""
536 avail_topic = self._avail_topics[topic]
537 template = avail_topic[CONF_AVAILABILITY_TEMPLATE]
538 payload =
template(msg.payload)
if template
else msg.payload
540 if payload == avail_topic[CONF_PAYLOAD_AVAILABLE]:
543 elif payload == avail_topic[CONF_PAYLOAD_NOT_AVAILABLE]:
549 """(Re)Subscribe to topics."""
554 """Update state on connection/disconnection to MQTT broker."""
555 if not self.
hasshass.is_stopping:
559 """Unsubscribe when removed."""
566 """Return if the device is available."""
567 mqtt_data = self.
hasshass.data[DATA_MQTT]
568 client = mqtt_data.client
569 if not client.connected
and not self.
hasshass.is_stopping:
571 if not self._avail_topics:
573 if self.
_avail_config_avail_config[CONF_AVAILABILITY_MODE] == AVAILABILITY_ALL:
574 return all(self.
_available_available.values())
575 if self.
_avail_config_avail_config[CONF_AVAILABILITY_MODE] == AVAILABILITY_ANY:
576 return any(self.
_available_available.values())
581 hass: HomeAssistant, device_id: str |
None, config_entry_id: str |
None
583 """Clean up the device registry after MQTT removal.
585 Remove MQTT from the device registry entry if there are no remaining
586 entities, triggers or tags.
590 from .
import device_trigger, tag
592 device_registry = dr.async_get(hass)
593 entity_registry = er.async_get(hass)
596 and device_id
not in device_registry.deleted_devices
598 and not er.async_entries_for_device(
599 entity_registry, device_id, include_disabled_entities=
False
601 and not await device_trigger.async_get_triggers(hass, device_id)
602 and not tag.async_has_tags(hass, device_id)
604 device_registry.async_update_device(
605 device_id, remove_config_entry_id=config_entry_id
610 """Get the discovery hash from the discovery data."""
611 discovery_hash: tuple[str, str] = discovery_data[ATTR_DISCOVERY_HASH]
612 return discovery_hash
616 """Acknowledge a discovery message has been handled."""
623 discovery_data: DiscoveryInfoType,
624 remove_discovery_updated: Callable[[],
None] |
None =
None,
626 """Stop discovery updates of being sent."""
627 if remove_discovery_updated:
628 remove_discovery_updated()
629 remove_discovery_updated =
None
635 hass: HomeAssistant, discovery_data: DiscoveryInfoType
637 """Clear retained discovery payload.
639 Remove discovery topic in broker to avoid rediscovery
640 after a restart of Home Assistant.
642 discovery_topic = discovery_data[ATTR_DISCOVERY_TOPIC]
643 await
async_publish(hass, discovery_topic,
None, retain=
True)
648 discovery_data: DiscoveryInfoType,
649 event: Event[er.EventEntityRegistryUpdatedData],
651 """Clear the discovery topic if the entity is removed."""
652 if event.data[
"action"] ==
"remove":
658 """Add support for auto discovery for platforms without an entity."""
663 discovery_data: DiscoveryInfoType,
664 device_id: str |
None,
665 config_entry: ConfigEntry,
668 """Initialize the update service."""
683 MQTT_DISCOVERY_UPDATED.format(*discovery_hash),
686 config_entry.async_on_unload(self.
_entry_unload_entry_unload)
687 if device_id
is not None:
692 "%s %s has been initialized",
699 """Handle cleanup when the config entry is unloaded."""
707 discovery_payload: MQTTDiscoveryPayload,
709 """Handle discovery update."""
714 discovery_payload.migrate_discovery
717 == discovery_payload.discovery_data[ATTR_DISCOVERY_TOPIC]
720 discovery_hash = self.
_discovery_data_discovery_data[ATTR_DISCOVERY_HASH]
722 self.
_discovery_data_discovery_data[ATTR_DISCOVERY_PAYLOAD], include_url=
False
724 action =
"Rollback" if discovery_payload.device_discovery
else "Migration"
725 schema_type =
"platform" if discovery_payload.device_discovery
else "device"
727 "%s to MQTT %s discovery schema started for %s '%s'"
728 "%s on topic %s. To complete %s, publish a %s discovery "
729 "message with %s '%s'. After completed %s, "
730 "publish an empty (retained) payload to %s",
755 "Got update for %s with hash: %s '%s'",
760 new_discovery_topic = discovery_payload.discovery_data[ATTR_DISCOVERY_TOPIC]
765 if self.
_discovery_data_discovery_data[ATTR_DISCOVERY_TOPIC] != new_discovery_topic:
768 self.
_discovery_data_discovery_data[ATTR_DISCOVERY_PAYLOAD], include_url=
False
771 discovery_payload.discovery_data[ATTR_DISCOVERY_PAYLOAD],
775 discovery_payload.discovery_data[ATTR_DISCOVERY_PAYLOAD]
777 if new_origin_support_url:
778 get_support = f
"for support visit {new_origin_support_url}"
781 "for documentation on migration to device schema or rollback to "
782 "discovery schema, visit https://www.home-assistant.io/integrations/"
783 "mqtt/#migration-from-single-component-to-device-based-discovery"
786 "Received a conflicting MQTT discovery message for %s '%s' which was "
787 "previously discovered on topic %s%s; the conflicting discovery "
788 "message was received on topic %s%s; %s",
802 and discovery_payload != self.
_discovery_data_discovery_data[ATTR_DISCOVERY_PAYLOAD]
805 "Updating %s with hash %s",
813 self.
_discovery_data_discovery_data[ATTR_DISCOVERY_PAYLOAD] = discovery_payload
814 elif not discovery_payload:
822 "%s %s has been removed",
837 self, event: Event[EventDeviceRegistryUpdatedData]
839 """Handle the manual removal of a device."""
856 """Handle the cleanup of the discovery service."""
868 async
def async_update(self, discovery_data: MQTTDiscoveryPayload) ->
None:
869 """Handle the update of platform specific parts, extend to the platform."""
873 """Handle the cleanup of platform specific parts, extend to the platform."""
876 class MqttDiscoveryUpdateMixin(Entity):
877 """Mixin used to handle updated discovery message for entity based platforms."""
882 discovery_data: DiscoveryInfoType |
None,
883 discovery_update: Callable[[MQTTDiscoveryPayload], Coroutine[Any, Any,
None]]
886 """Initialize the discovery update mixin."""
891 if discovery_data
is None:
893 mqtt_data = hass.data[DATA_MQTT]
895 discovery_hash: tuple[str, str] = discovery_data[ATTR_DISCOVERY_HASH]
901 """Subscribe to discovery updates."""
906 discovery_hash: tuple[str, str] = self.
_discovery_data_discovery_data[ATTR_DISCOVERY_HASH]
907 debug_info.add_entity_discovery_data(
915 MQTT_DISCOVERY_UPDATED.format(*discovery_hash),
920 self: MqttDiscoveryUpdateMixin,
922 """Remove entity's state and entity registry entry.
924 Remove entity from entity registry if it is registered,
925 this also removes the state. If the entity is not in the entity
926 registry, just remove the state.
928 entity_registry = er.async_get(self.
hasshass)
929 if entity_entry := entity_registry.async_get(self.
entity_identity_id):
930 entity_registry.async_remove(self.
entity_identity_id)
932 self.
hasshass, entity_entry.device_id, entity_entry.config_entry_id
939 payload: MQTTDiscoveryPayload,
940 discovery_update: Callable[[MQTTDiscoveryPayload], Coroutine[Any, Any,
None]],
941 discovery_data: DiscoveryInfoType,
943 """Process discovery update."""
945 await discovery_update(payload)
950 """Process discovery update and remove entity."""
964 """Handle discovery update.
966 If the payload has changed we will create a task to
967 do the discovery update.
969 As this callback can fire when nothing has changed, this
970 is a normal function to avoid task creation until it is needed.
978 payload.migrate_discovery
981 == payload.discovery_data[ATTR_DISCOVERY_TOPIC]
985 "Discovery migration is not possible for "
986 "for entity %s on topic %s. A unique_id "
987 "and device context is required, got unique_id: %s, device: %s",
997 discovery_hash = self.
_discovery_data_discovery_data[ATTR_DISCOVERY_HASH]
999 self.
_discovery_data_discovery_data[ATTR_DISCOVERY_PAYLOAD], include_url=
False
1001 action =
"Rollback" if payload.device_discovery
else "Migration"
1002 schema_type =
"platform" if payload.device_discovery
else "device"
1004 "%s to MQTT %s discovery schema started for entity %s"
1005 "%s on topic %s. To complete %s, publish a %s discovery "
1006 "message with %s entity '%s'. After completed %s, "
1007 "publish an empty (retained) payload to %s",
1020 old_payload = self.
_discovery_data_discovery_data[ATTR_DISCOVERY_PAYLOAD]
1022 "Got update for entity with hash: %s '%s'",
1026 new_discovery_topic = payload.discovery_data[ATTR_DISCOVERY_TOPIC]
1030 if self.
_discovery_data_discovery_data[ATTR_DISCOVERY_TOPIC] != new_discovery_topic:
1033 self.
_discovery_data_discovery_data[ATTR_DISCOVERY_PAYLOAD], include_url=
False
1036 payload.discovery_data[ATTR_DISCOVERY_PAYLOAD], include_url=
False
1039 payload.discovery_data[ATTR_DISCOVERY_PAYLOAD]
1041 if new_origin_support_url:
1042 get_support = f
"for support visit {new_origin_support_url}"
1045 "for documentation on migration to device schema or rollback to "
1046 "discovery schema, visit https://www.home-assistant.io/integrations/"
1047 "mqtt/#migration-from-single-component-to-device-based-discovery"
1050 "Received a conflicting MQTT discovery message for entity %s; the "
1051 "entity was previously discovered on topic %s%s; the conflicting "
1052 "discovery message was received on topic %s%s; %s",
1056 new_discovery_topic,
1063 debug_info.update_entity_discovery_data(self.
hasshass, payload, self.
entity_identity_id)
1067 _LOGGER.info(
"Removing component: %s", self.
entity_identity_id)
1069 _LOGGER.info(
"Unloading component: %s", self.
entity_identity_id)
1070 self.
hasshass.async_create_task(
1074 if old_payload != payload:
1076 _LOGGER.info(
"Updating component: %s", self.
entity_identity_id)
1077 self.
hasshass.async_create_task(
1084 _LOGGER.debug(
"Ignoring unchanged update for: %s", self.
entity_identity_id)
1088 """Clear retained discovery topic in broker."""
1100 """Finish adding entity to platform."""
1109 """Abort adding an entity to a platform."""
1111 discovery_hash: tuple[str, str] = self.
_discovery_data_discovery_data[ATTR_DISCOVERY_HASH]
1118 async_clear_discovery_topic_if_entity_removed,
1129 """Stop listening to signal and cleanup discovery data.."""
1133 """Stop listening to signal and cleanup discovery data."""
1142 specifications: dict[str, Any] |
None,
1143 ) -> DeviceInfo |
None:
1144 """Return a device description for device registry."""
1145 if not specifications:
1149 identifiers={(DOMAIN, id_)
for id_
in specifications[CONF_IDENTIFIERS]},
1151 (conn_[0], conn_[1])
for conn_
in specifications[CONF_CONNECTIONS]
1155 if CONF_MANUFACTURER
in specifications:
1156 info[ATTR_MANUFACTURER] = specifications[CONF_MANUFACTURER]
1158 if CONF_MODEL
in specifications:
1159 info[ATTR_MODEL] = specifications[CONF_MODEL]
1161 if CONF_MODEL_ID
in specifications:
1162 info[ATTR_MODEL_ID] = specifications[CONF_MODEL_ID]
1164 if CONF_NAME
in specifications:
1165 info[ATTR_NAME] = specifications[CONF_NAME]
1167 if CONF_HW_VERSION
in specifications:
1168 info[ATTR_HW_VERSION] = specifications[CONF_HW_VERSION]
1170 if CONF_SERIAL_NUMBER
in specifications:
1171 info[ATTR_SERIAL_NUMBER] = specifications[CONF_SERIAL_NUMBER]
1173 if CONF_SW_VERSION
in specifications:
1174 info[ATTR_SW_VERSION] = specifications[CONF_SW_VERSION]
1176 if CONF_VIA_DEVICE
in specifications:
1177 info[ATTR_VIA_DEVICE] = (DOMAIN, specifications[CONF_VIA_DEVICE])
1179 if CONF_SUGGESTED_AREA
in specifications:
1180 info[ATTR_SUGGESTED_AREA] = specifications[CONF_SUGGESTED_AREA]
1182 if CONF_CONFIGURATION_URL
in specifications:
1183 info[ATTR_CONFIGURATION_URL] = specifications[CONF_CONFIGURATION_URL]
1190 hass: HomeAssistant, device_info: DeviceInfo |
None, config_entry: ConfigEntry
1192 """Ensure the via device is in the device registry."""
1195 or CONF_VIA_DEVICE
not in device_info
1196 or (device_registry := dr.async_get(hass)).async_get_device(
1197 identifiers={device_info[
"via_device"]}
1204 "Device identifier %s via_device reference from device_info %s "
1205 "not found in the Device Registry, creating new entry",
1206 device_info[
"via_device"],
1209 device_registry.async_get_or_create(
1210 config_entry_id=config_entry.entry_id,
1211 identifiers={device_info[
"via_device"]},
1216 """Mixin used for mqtt platforms that support the device registry."""
1219 self, specifications: dict[str, Any] |
None, config_entry: ConfigEntry
1221 """Initialize the device mixin."""
1226 """Handle updated discovery message."""
1228 device_registry = dr.async_get(self.
hasshass)
1232 if device_info
is not None:
1234 device_registry.async_get_or_create(
1235 config_entry_id=config_entry_id, **device_info
1240 """Return a device description for device registry."""
1245 MqttAttributesMixin,
1246 MqttAvailabilityMixin,
1247 MqttDiscoveryUpdateMixin,
1248 MqttEntityDeviceInfo,
1250 """Representation of an MQTT entity."""
1252 _attr_force_update =
False
1253 _attr_has_entity_name =
True
1254 _attr_should_poll =
False
1255 _default_name: str |
None
1256 _entity_id_format: str
1260 hass: HomeAssistant,
1262 config_entry: ConfigEntry,
1263 discovery_data: DiscoveryInfoType |
None,
1265 """Init the MQTT Entity."""
1267 self.
_config_config: ConfigType = config
1269 self.
_sub_state_sub_state: dict[str, EntitySubscription] = {}
1281 MqttAttributesMixin.__init__(self, config)
1282 MqttAvailabilityMixin.__init__(self, config)
1283 MqttDiscoveryUpdateMixin.__init__(
1286 MqttEntityDeviceInfo.__init__(self, config.get(CONF_DEVICE), config_entry)
1290 """Set entity_id from object_id if defined in config."""
1297 """Subscribe to MQTT events."""
1302 self.
_sub_state_sub_state = subscription.async_prepare_subscribe_topics(
1311 """Call before the discovery message is acknowledged.
1313 To be extended by subclasses.
1317 """Handle updated discovery message."""
1319 config: DiscoveryInfoType = self.config_schema()(discovery_payload)
1320 except vol.Invalid
as err:
1334 self.
_sub_state_sub_state = subscription.async_prepare_subscribe_topics(
1347 """Unsubscribe when removed."""
1348 self.
_sub_state_sub_state = subscription.async_unsubscribe_topics(
1351 await MqttAttributesMixin.async_will_remove_from_hass(self)
1352 await MqttAvailabilityMixin.async_will_remove_from_hass(self)
1353 await MqttDiscoveryUpdateMixin.async_will_remove_from_hass(self)
1359 payload: PublishPayloadType,
1361 retain: bool =
False,
1362 encoding: str |
None = DEFAULT_ENCODING,
1364 """Publish message to an MQTT topic."""
1376 self, topic: str, payload: PublishPayloadType
1378 """Publish payload to a topic using config."""
1382 self.
_config_config[CONF_QOS],
1383 self.
_config_config[CONF_RETAIN],
1384 self.
_config_config[CONF_ENCODING],
1390 """Return the config schema."""
1393 """Help setting the entity name if needed."""
1394 entity_name: str |
None | UndefinedType = config.get(CONF_NAME, UNDEFINED)
1396 if entity_name
is not UNDEFINED:
1400 self.
_attr_name_attr_name = self._default_name
1401 elif hasattr(self,
"_attr_name"):
1405 delattr(self,
"_attr_name")
1406 if CONF_DEVICE
in config
and CONF_NAME
not in config[CONF_DEVICE]:
1408 "MQTT device information always needs to include a name, got %s, "
1409 "if device information is shared between multiple entities, the device "
1410 "name must be included in each entity's device configuration",
1415 """(Re)Setup the common attributes for the entity."""
1418 config.get(CONF_ENABLED_BY_DEFAULT)
1426 """(Re)Setup the entity."""
1431 """(Re)Subscribe to topics."""
1435 """(Re)Subscribe to topics."""
1439 self, attrs_snapshot: tuple[tuple[str, Any | UndefinedType], ...]
1441 """Return True if attributes on entity changed or if update is forced."""
1444 for attribute, last_value
in attrs_snapshot:
1445 if getattr(self, attribute, UNDEFINED) != last_value:
1452 msg_callback: MessageCallbackType,
1453 attributes: set[str] |
None,
1454 msg: ReceiveMessage,
1456 """Process the message callback."""
1457 if attributes
is not None:
1458 attrs_snapshot: tuple[tuple[str, Any | UndefinedType], ...] =
tuple(
1459 (attribute, getattr(self, attribute, UNDEFINED))
1460 for attribute
in attributes
1462 mqtt_data = self.
hasshasshass.data[DATA_MQTT]
1463 messages = mqtt_data.debug_info_entities[self.
entity_identity_id][
"subscriptions"][
1464 msg.subscribed_topic
1466 if msg
not in messages:
1467 messages.append(msg)
1471 except MqttValueTemplateException
as exc:
1472 _LOGGER.warning(exc)
1475 if attributes
is not None and self.
_attrs_have_changed_attrs_have_changed(attrs_snapshot):
1476 mqtt_data.state_write_requests.write_state_request(self)
1480 state_topic_config_key: str,
1481 msg_callback: Callable[[ReceiveMessage],
None],
1482 tracked_attributes: set[str] |
None,
1483 disable_encoding: bool =
False,
1485 """Add a subscription."""
1486 qos: int = self.
_config_config[CONF_QOS]
1487 encoding: str |
None =
None
1488 if not disable_encoding:
1489 encoding = self.
_config_config[CONF_ENCODING]
or None
1491 state_topic_config_key
in self.
_config_config
1492 and self.
_config_config[state_topic_config_key]
is not None
1495 "topic": self.
_config_config[state_topic_config_key],
1496 "msg_callback": partial(
1501 "encoding": encoding,
1502 "job_type": HassJobType.Callback,
1509 hass: HomeAssistant,
1510 config_entry: ConfigEntry,
1513 """Update device registry."""
1514 if CONF_DEVICE
not in config:
1517 device: DeviceEntry |
None =
None
1518 device_registry = dr.async_get(hass)
1519 config_entry_id = config_entry.entry_id
1524 if config_entry_id
is not None and device_info
is not None:
1525 update_device_info = cast(dict[str, Any], device_info)
1526 update_device_info[
"config_entry_id"] = config_entry_id
1527 device = device_registry.async_get_or_create(**update_device_info)
1529 return device.id
if device
else None
1534 hass: HomeAssistant,
1535 event: Event[EventDeviceRegistryUpdatedData],
1536 mqtt_device_id: str,
1537 config_entry_id: str,
1539 """Check if the passed event indicates MQTT was removed from a device."""
1540 if event.data[
"action"] ==
"update":
1541 if "config_entries" not in event.data[
"changes"]:
1543 device_registry = dr.async_get(hass)
1545 device_entry := device_registry.async_get(mqtt_device_id)
1546 )
and config_entry_id
in device_entry.config_entries:
None _attributes_message_received(self, ReceiveMessage msg)
_attr_extra_state_attributes
None _attributes_subscribe_topics(self)
None attributes_prepare_discovery_update(self, DiscoveryInfoType config)
None __init__(self, ConfigType config)
None _attributes_prepare_subscribe_topics(self)
None async_added_to_hass(self)
None attributes_discovery_update(self, DiscoveryInfoType config)
None async_will_remove_from_hass(self)
None __init__(self, ConfigType config)
None _availability_message_received(self, ReceiveMessage msg)
None async_added_to_hass(self)
None _availability_prepare_subscribe_topics(self)
None availability_prepare_discovery_update(self, DiscoveryInfoType config)
None async_will_remove_from_hass(self)
None _availability_setup_from_config(self, ConfigType config)
None async_mqtt_connection_state_changed(self, bool state)
None availability_discovery_update(self, DiscoveryInfoType config)
None _availability_subscribe_topics(self)
None _async_device_removed(self, Event[EventDeviceRegistryUpdatedData] event)
None _async_tear_down(self)
None __init__(self, HomeAssistant hass, DiscoveryInfoType discovery_data, str|None device_id, ConfigEntry config_entry, str log_name)
None async_tear_down(self)
None async_update(self, MQTTDiscoveryPayload discovery_data)
_remove_discovery_updated
None _entry_unload(self, *Any _)
None async_discovery_update(self, MQTTDiscoveryPayload discovery_payload)
None _async_process_discovery_update_and_remove(self)
None add_to_platform_finish(self)
None _async_process_discovery_update(self, MQTTDiscoveryPayload payload, Callable[[MQTTDiscoveryPayload], Coroutine[Any, Any, None]] discovery_update, DiscoveryInfoType discovery_data)
None _async_discovery_callback(self, MQTTDiscoveryPayload payload)
None async_added_to_hass(self)
None async_removed_from_registry(self)
None _async_remove_state_and_registry_entry(MqttDiscoveryUpdateMixin self)
None async_will_remove_from_hass(self)
None __init__(self, HomeAssistant hass, DiscoveryInfoType|None discovery_data, Callable[[MQTTDiscoveryPayload], Coroutine[Any, Any, None]]|None discovery_update=None)
_remove_discovery_updated
None _cleanup_discovery_on_remove(self)
None add_to_platform_abort(self)
None __init__(self, dict[str, Any]|None specifications, ConfigEntry config_entry)
DeviceInfo|None device_info(self)
None device_info_discovery_update(self, DiscoveryInfoType config)
_attr_entity_registry_enabled_default
None _setup_from_config(self, ConfigType config)
None _prepare_subscribe_topics(self)
None __init__(self, HomeAssistant hass, ConfigType config, ConfigEntry config_entry, DiscoveryInfoType|None discovery_data)
bool _attrs_have_changed(self, tuple[tuple[str, Any|UndefinedType],...] attrs_snapshot)
None async_publish_with_config(self, str topic, PublishPayloadType payload)
None discovery_update(self, MQTTDiscoveryPayload discovery_payload)
None _setup_common_attributes_from_config(self, ConfigType config)
None async_added_to_hass(self)
VolSchemaType config_schema()
bool add_subscription(self, str state_topic_config_key, Callable[[ReceiveMessage], None] msg_callback, set[str]|None tracked_attributes, bool disable_encoding=False)
None _init_entity_id(self)
None _set_entity_name(self, ConfigType config)
None async_publish(self, str topic, PublishPayloadType payload, int qos=0, bool retain=False, str|None encoding=DEFAULT_ENCODING)
None _message_callback(self, MessageCallbackType msg_callback, set[str]|None attributes, ReceiveMessage msg)
None _subscribe_topics(self)
None mqtt_async_added_to_hass(self)
None async_will_remove_from_hass(self)
None __call__(self, ConfigType config, DiscoveryInfoType discovery_data)
None async_write_ha_state(self)
bool _default_to_device_class_name(self)
None async_on_remove(self, CALLBACK_TYPE func)
None async_remove(self, *bool force_remove=False)
DeviceInfo|None device_info(self)
web.Response get(self, web.Request request, str config_key)
None async_publish(HomeAssistant hass, str topic, PublishPayloadType payload, int|None qos=0, bool|None retain=False, str|None encoding=DEFAULT_ENCODING)
None log_message(HomeAssistant hass, str entity_id, str topic, PublishPayloadType payload, int qos, bool retain)
None clear_discovery_hash(HomeAssistant hass, tuple[str, str] discovery_hash)
None set_discovery_hash(HomeAssistant hass, tuple[str, str] discovery_hash)
str get_origin_log_string(MQTTDiscoveryPayload discovery_payload, *bool include_url)
str|None get_origin_support_url(MQTTDiscoveryPayload discovery_payload)
None cleanup_device_registry(HomeAssistant hass, str|None device_id, str|None config_entry_id)
None async_clear_discovery_topic_if_entity_removed(HomeAssistant hass, DiscoveryInfoType discovery_data, Event[er.EventEntityRegistryUpdatedData] event)
None stop_discovery_updates(HomeAssistant hass, DiscoveryInfoType discovery_data, Callable[[], None]|None remove_discovery_updated=None)
None _handle_discovery_failure(HomeAssistant hass, MQTTDiscoveryPayload discovery_payload)
None ensure_via_device_exists(HomeAssistant hass, DeviceInfo|None device_info, ConfigEntry config_entry)
str|None update_device(HomeAssistant hass, ConfigEntry config_entry, ConfigType config)
tuple[str, str] get_discovery_hash(DiscoveryInfoType discovery_data)
DeviceInfo|None device_info_from_specifications(dict[str, Any]|None specifications)
None async_setup_entity_entry_helper(HomeAssistant hass, ConfigEntry entry, type[MqttEntity]|None entity_class, str domain, AddEntitiesCallback async_add_entities, VolSchemaType discovery_schema, VolSchemaType platform_schema_modern, dict[str, type[MqttEntity]]|None schema_class_mapping=None)
bool async_removed_from_device(HomeAssistant hass, Event[EventDeviceRegistryUpdatedData] event, str mqtt_device_id, str config_entry_id)
bool _verify_mqtt_config_entry_enabled_for_discovery(HomeAssistant hass, str domain, MQTTDiscoveryPayload discovery_payload)
None send_discovery_done(HomeAssistant hass, DiscoveryInfoType discovery_data)
None async_setup_non_entity_entry_helper(HomeAssistant hass, str domain, _SetupNonEntityHelperCallbackProtocol async_setup, vol.Schema discovery_schema)
None async_remove_discovery_payload(HomeAssistant hass, DiscoveryInfoType discovery_data)
None init_entity_id_from_config(HomeAssistant hass, Entity entity, ConfigType config, str entity_id_format)
None async_handle_schema_error(MQTTDiscoveryPayload discovery_payload, vol.Invalid err)
None async_subscribe_topics_internal(HomeAssistant hass, dict[str, EntitySubscription] sub_state)
dict[str, EntitySubscription] async_prepare_subscribe_topics(HomeAssistant hass, dict[str, EntitySubscription]|None sub_state, dict[str, dict[str, Any]] topics)
bool|None mqtt_config_entry_enabled(HomeAssistant hass)
bool async_setup(HomeAssistant hass, ConfigType config)
None async_create_issue(HomeAssistant hass, str entry_id)
bool template(HomeAssistant hass, Template value_template, TemplateVarsType variables=None)
Callable[[], None] async_dispatcher_connect(HomeAssistant hass, str signal, Callable[..., Any] target)
None async_dispatcher_send(HomeAssistant hass, str signal, *Any args)
str async_generate_entity_id(str entity_id_format, str|None name, Iterable[str]|None current_ids=None, HomeAssistant|None hass=None)
CALLBACK_TYPE async_track_device_registry_updated_event(HomeAssistant hass, str|Iterable[str] device_ids, Callable[[Event[EventDeviceRegistryUpdatedData]], Any] action, HassJobType|None job_type=None)
CALLBACK_TYPE async_track_entity_registry_updated_event(HomeAssistant hass, str|Iterable[str] entity_ids, Callable[[Event[EventEntityRegistryUpdatedData]], Any] action, HassJobType|None job_type=None)