Home Assistant Unofficial Reference 2024.12.1
tag.py
Go to the documentation of this file.
1 """Provides tag scanning for MQTT."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Callable
6 import functools
7 import logging
8 
9 import voluptuous as vol
10 
11 from homeassistant.components import tag
12 from homeassistant.config_entries import ConfigEntry
13 from homeassistant.const import CONF_DEVICE, CONF_VALUE_TEMPLATE
14 from homeassistant.core import HassJobType, HomeAssistant, callback
16 from homeassistant.helpers.service_info.mqtt import ReceivePayloadType
17 from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
18 
19 from . import subscription
20 from .config import MQTT_BASE_SCHEMA
21 from .const import ATTR_DISCOVERY_HASH, CONF_QOS, CONF_TOPIC
22 from .discovery import MQTTDiscoveryPayload
23 from .entity import (
24  MqttDiscoveryDeviceUpdateMixin,
25  async_handle_schema_error,
26  async_setup_non_entity_entry_helper,
27  send_discovery_done,
28  update_device,
29 )
30 from .models import (
31  DATA_MQTT,
32  MqttValueTemplate,
33  MqttValueTemplateException,
34  ReceiveMessage,
35 )
36 from .schemas import MQTT_ENTITY_DEVICE_INFO_SCHEMA
37 from .subscription import EntitySubscription
38 from .util import valid_subscribe_topic
39 
40 _LOGGER = logging.getLogger(__name__)
41 
42 LOG_NAME = "Tag"
43 
44 TAG = "tag"
45 
46 DISCOVERY_SCHEMA = MQTT_BASE_SCHEMA.extend(
47  {
48  vol.Optional(CONF_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA,
49  vol.Required(CONF_TOPIC): valid_subscribe_topic,
50  vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
51  },
52  extra=vol.REMOVE_EXTRA,
53 )
54 
55 
56 async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> None:
57  """Set up MQTT tag scanner dynamically through MQTT discovery."""
58 
59  setup = functools.partial(_async_setup_tag, hass, config_entry=config_entry)
60  async_setup_non_entity_entry_helper(hass, TAG, setup, DISCOVERY_SCHEMA)
61 
62 
63 async def _async_setup_tag(
64  hass: HomeAssistant,
65  config: ConfigType,
66  config_entry: ConfigEntry,
67  discovery_data: DiscoveryInfoType,
68 ) -> None:
69  """Set up the MQTT tag scanner."""
70  discovery_hash = discovery_data[ATTR_DISCOVERY_HASH]
71  discovery_id = discovery_hash[1]
72 
73  device_id = update_device(hass, config_entry, config)
74  if device_id is not None and device_id not in (tags := hass.data[DATA_MQTT].tags):
75  tags[device_id] = {}
76 
77  tag_scanner = MQTTTagScanner(
78  hass,
79  config,
80  device_id,
81  discovery_data,
82  config_entry,
83  )
84 
85  await tag_scanner.subscribe_topics()
86 
87  if device_id:
88  tags[device_id][discovery_id] = tag_scanner
89 
90  send_discovery_done(hass, discovery_data)
91 
92 
93 def async_has_tags(hass: HomeAssistant, device_id: str) -> bool:
94  """Device has tag scanners."""
95  if device_id not in (tags := hass.data[DATA_MQTT].tags):
96  return False
97  return tags[device_id] != {}
98 
99 
101  """MQTT Tag scanner."""
102 
103  _value_template: Callable[[ReceivePayloadType, str], ReceivePayloadType]
104 
105  def __init__(
106  self,
107  hass: HomeAssistant,
108  config: ConfigType,
109  device_id: str | None,
110  discovery_data: DiscoveryInfoType,
111  config_entry: ConfigEntry,
112  ) -> None:
113  """Initialize."""
114  self._config_config = config
115  self._config_entry_config_entry_config_entry = config_entry
116  self.device_iddevice_id = device_id
117  self.discovery_datadiscovery_data = discovery_data
118  self.hasshasshass = hass
119  self._sub_state_sub_state: dict[str, EntitySubscription] | None = None
120  self._value_template_value_template = MqttValueTemplate(
121  config.get(CONF_VALUE_TEMPLATE)
122  ).async_render_with_possible_json_value
123 
124  MqttDiscoveryDeviceUpdateMixin.__init__(
125  self, hass, discovery_data, device_id, config_entry, LOG_NAME
126  )
127 
128  async def async_update(self, discovery_data: MQTTDiscoveryPayload) -> None:
129  """Handle MQTT tag discovery updates."""
130  # Update tag scanner
131  try:
132  config: DiscoveryInfoType = DISCOVERY_SCHEMA(discovery_data)
133  except vol.Invalid as err:
134  async_handle_schema_error(discovery_data, err)
135  return
136  self._config_config = config
137  self._value_template_value_template = MqttValueTemplate(
138  config.get(CONF_VALUE_TEMPLATE)
139  ).async_render_with_possible_json_value
140  update_device(self.hasshasshass, self._config_entry_config_entry_config_entry, config)
141  await self.subscribe_topicssubscribe_topics()
142 
143  @callback
144  def _async_tag_scanned(self, msg: ReceiveMessage) -> None:
145  """Handle new tag scanned."""
146  try:
147  tag_id = str(self._value_template_value_template(msg.payload, "")).strip()
148  except MqttValueTemplateException as exc:
149  _LOGGER.warning(exc)
150  return
151  if not tag_id: # No output from template, ignore
152  return
153 
154  self.hasshasshass.async_create_task(
155  tag.async_scan_tag(self.hasshasshass, tag_id, self.device_iddevice_id)
156  )
157 
158  async def subscribe_topics(self) -> None:
159  """Subscribe to MQTT topics."""
160  self._sub_state_sub_state = subscription.async_prepare_subscribe_topics(
161  self.hasshasshass,
162  self._sub_state_sub_state,
163  {
164  "state_topic": {
165  "topic": self._config_config[CONF_TOPIC],
166  "msg_callback": self._async_tag_scanned_async_tag_scanned,
167  "qos": self._config_config[CONF_QOS],
168  "job_type": HassJobType.Callback,
169  }
170  },
171  )
172  subscription.async_subscribe_topics_internal(self.hasshasshass, self._sub_state_sub_state)
173 
174  async def async_tear_down(self) -> None:
175  """Cleanup tag scanner."""
176  discovery_hash = self.discovery_datadiscovery_data[ATTR_DISCOVERY_HASH]
177  discovery_id = discovery_hash[1]
178  self._sub_state_sub_state = subscription.async_unsubscribe_topics(
179  self.hasshasshass, self._sub_state_sub_state
180  )
181  tags = self.hasshasshass.data[DATA_MQTT].tags
182  if self.device_iddevice_id in tags and discovery_id in tags[self.device_iddevice_id]:
183  del tags[self.device_iddevice_id][discovery_id]
None __init__(self, HomeAssistant hass, ConfigType config, str|None device_id, DiscoveryInfoType discovery_data, ConfigEntry config_entry)
Definition: tag.py:112
None _async_tag_scanned(self, ReceiveMessage msg)
Definition: tag.py:144
None async_update(self, MQTTDiscoveryPayload discovery_data)
Definition: tag.py:128
str|None update_device(HomeAssistant hass, ConfigEntry config_entry, ConfigType config)
Definition: entity.py:1512
None send_discovery_done(HomeAssistant hass, DiscoveryInfoType discovery_data)
Definition: entity.py:615
None async_setup_non_entity_entry_helper(HomeAssistant hass, str domain, _SetupNonEntityHelperCallbackProtocol async_setup, vol.Schema discovery_schema)
Definition: entity.py:204
None async_handle_schema_error(MQTTDiscoveryPayload discovery_payload, vol.Invalid err)
Definition: entity.py:152
None _async_setup_tag(HomeAssistant hass, ConfigType config, ConfigEntry config_entry, DiscoveryInfoType discovery_data)
Definition: tag.py:68
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry)
Definition: tag.py:56
bool async_has_tags(HomeAssistant hass, str device_id)
Definition: tag.py:93