Home Assistant Unofficial Reference 2024.12.1
helpers.py
Go to the documentation of this file.
1 """Helper functions for mysensors package."""
2 
3 from __future__ import annotations
4 
5 from collections import defaultdict
6 from collections.abc import Callable
7 from enum import IntEnum
8 import logging
9 from typing import cast
10 
11 from mysensors import BaseAsyncGateway, Message
12 from mysensors.sensor import ChildSensor
13 import voluptuous as vol
14 
15 from homeassistant.const import CONF_NAME, Platform
16 from homeassistant.core import HomeAssistant, callback
18 from homeassistant.helpers.dispatcher import async_dispatcher_send
19 from homeassistant.util.decorator import Registry
20 
21 from .const import (
22  ATTR_DEVICES,
23  ATTR_GATEWAY_ID,
24  ATTR_NODE_ID,
25  DOMAIN,
26  FLAT_PLATFORM_TYPES,
27  MYSENSORS_DISCOVERED_NODES,
28  MYSENSORS_DISCOVERY,
29  MYSENSORS_NODE_DISCOVERY,
30  MYSENSORS_ON_UNLOAD,
31  TYPE_TO_PLATFORMS,
32  DevId,
33  GatewayId,
34  SensorType,
35  ValueType,
36 )
37 
38 _LOGGER = logging.getLogger(__name__)
39 SCHEMAS: Registry[
40  tuple[str, str], Callable[[BaseAsyncGateway, ChildSensor, ValueType], vol.Schema]
41 ] = Registry()
42 
43 
44 @callback
45 def on_unload(hass: HomeAssistant, gateway_id: GatewayId, fnct: Callable) -> None:
46  """Register a callback to be called when entry is unloaded.
47 
48  This function is used by platforms to cleanup after themselves.
49  """
50  key = MYSENSORS_ON_UNLOAD.format(gateway_id)
51  if key not in hass.data[DOMAIN]:
52  hass.data[DOMAIN][key] = []
53  hass.data[DOMAIN][key].append(fnct)
54 
55 
56 @callback
58  hass: HomeAssistant, gateway_id: GatewayId, platform: str, new_devices: list[DevId]
59 ) -> None:
60  """Discover a MySensors platform."""
61  _LOGGER.debug("Discovering platform %s with devIds: %s", platform, new_devices)
63  hass,
64  MYSENSORS_DISCOVERY.format(gateway_id, platform),
65  {
66  ATTR_DEVICES: new_devices,
67  CONF_NAME: DOMAIN,
68  ATTR_GATEWAY_ID: gateway_id,
69  },
70  )
71 
72 
73 @callback
75  hass: HomeAssistant, gateway_id: GatewayId, node_id: int
76 ) -> None:
77  """Discover a MySensors node."""
78  discovered_nodes = hass.data[DOMAIN].setdefault(
79  MYSENSORS_DISCOVERED_NODES.format(gateway_id), set()
80  )
81 
82  if node_id not in discovered_nodes:
83  discovered_nodes.add(node_id)
85  hass,
86  MYSENSORS_NODE_DISCOVERY,
87  {
88  ATTR_GATEWAY_ID: gateway_id,
89  ATTR_NODE_ID: node_id,
90  },
91  )
92 
93 
95  gateway: BaseAsyncGateway, child: ChildSensor, value_type_name: ValueType
96 ) -> vol.Schema:
97  """Return a default validation schema for value types."""
98  schema = {value_type_name: cv.string}
99  return get_child_schema(gateway, child, value_type_name, schema)
100 
101 
102 @SCHEMAS.register(("light", "V_DIMMER"))
104  gateway: BaseAsyncGateway, child: ChildSensor, value_type_name: ValueType
105 ) -> vol.Schema:
106  """Return a validation schema for V_DIMMER."""
107  schema = {"V_DIMMER": cv.string, "V_LIGHT": cv.string}
108  return get_child_schema(gateway, child, value_type_name, schema)
109 
110 
111 @SCHEMAS.register(("light", "V_PERCENTAGE"))
113  gateway: BaseAsyncGateway, child: ChildSensor, value_type_name: ValueType
114 ) -> vol.Schema:
115  """Return a validation schema for V_PERCENTAGE."""
116  schema = {"V_PERCENTAGE": cv.string, "V_STATUS": cv.string}
117  return get_child_schema(gateway, child, value_type_name, schema)
118 
119 
120 @SCHEMAS.register(("light", "V_RGB"))
122  gateway: BaseAsyncGateway, child: ChildSensor, value_type_name: ValueType
123 ) -> vol.Schema:
124  """Return a validation schema for V_RGB."""
125  schema = {"V_RGB": cv.string, "V_STATUS": cv.string}
126  return get_child_schema(gateway, child, value_type_name, schema)
127 
128 
129 @SCHEMAS.register(("light", "V_RGBW"))
131  gateway: BaseAsyncGateway, child: ChildSensor, value_type_name: ValueType
132 ) -> vol.Schema:
133  """Return a validation schema for V_RGBW."""
134  schema = {"V_RGBW": cv.string, "V_STATUS": cv.string}
135  return get_child_schema(gateway, child, value_type_name, schema)
136 
137 
138 @SCHEMAS.register(("switch", "V_IR_SEND"))
140  gateway: BaseAsyncGateway, child: ChildSensor, value_type_name: ValueType
141 ) -> vol.Schema:
142  """Return a validation schema for V_IR_SEND."""
143  schema = {"V_IR_SEND": cv.string, "V_LIGHT": cv.string}
144  return get_child_schema(gateway, child, value_type_name, schema)
145 
146 
148  gateway: BaseAsyncGateway,
149  child: ChildSensor,
150  value_type_name: ValueType,
151  schema: dict,
152 ) -> vol.Schema:
153  """Return a child schema."""
154  set_req = gateway.const.SetReq
155  child_schema = cast(vol.Schema, child.get_schema(gateway.protocol_version))
156  return child_schema.extend(
157  {
158  vol.Required(
159  set_req[name].value, msg=invalid_msg(gateway, child, name)
160  ): child_schema.schema.get(set_req[name].value, valid)
161  for name, valid in schema.items()
162  },
163  extra=vol.ALLOW_EXTRA,
164  )
165 
166 
168  gateway: BaseAsyncGateway, child: ChildSensor, value_type_name: ValueType
169 ) -> str:
170  """Return a message for an invalid child during schema validation."""
171  presentation = gateway.const.Presentation
172  set_req = gateway.const.SetReq
173  return f"{presentation(child.type).name} requires value_type {set_req[value_type_name].name}"
174 
175 
177  gateway_id: GatewayId, msg: Message
178 ) -> dict[Platform, list[DevId]]:
179  """Validate a set message."""
180  if not validate_node(msg.gateway, msg.node_id):
181  return {}
182  child = msg.gateway.sensors[msg.node_id].children[msg.child_id]
183  return validate_child(gateway_id, msg.gateway, msg.node_id, child, msg.sub_type)
184 
185 
186 def validate_node(gateway: BaseAsyncGateway, node_id: int) -> bool:
187  """Validate a node."""
188  if gateway.sensors[node_id].sketch_name is None:
189  _LOGGER.debug("Node %s is missing sketch name", node_id)
190  return False
191  return True
192 
193 
195  gateway_id: GatewayId,
196  gateway: BaseAsyncGateway,
197  node_id: int,
198  child: ChildSensor,
199  value_type: int | None = None,
200 ) -> defaultdict[Platform, list[DevId]]:
201  """Validate a child. Returns a dict mapping hass platform names to list of DevId."""
202  validated: defaultdict[Platform, list[DevId]] = defaultdict(list)
203  presentation: type[IntEnum] = gateway.const.Presentation
204  set_req: type[IntEnum] = gateway.const.SetReq
205  child_type_name: SensorType | None = next(
206  (member.name for member in presentation if member.value == child.type), None
207  )
208  if not child_type_name:
209  _LOGGER.warning("Child type %s is not supported", child.type)
210  return validated
211 
212  value_types: set[int] = {value_type} if value_type else {*child.values}
213  value_type_names: set[ValueType] = {
214  member.name for member in set_req if member.value in value_types
215  }
216  platforms: list[Platform] = TYPE_TO_PLATFORMS.get(child_type_name, [])
217  if not platforms:
218  _LOGGER.warning("Child type %s is not supported", child.type)
219  return validated
220 
221  for platform in platforms:
222  platform_v_names: set[ValueType] = FLAT_PLATFORM_TYPES[
223  platform, child_type_name
224  ]
225  v_names: set[ValueType] = platform_v_names & value_type_names
226  if not v_names:
227  child_value_names: set[ValueType] = {
228  member.name for member in set_req if member.value in child.values
229  }
230  v_names = platform_v_names & child_value_names
231 
232  for v_name in v_names:
233  child_schema_gen = SCHEMAS.get((platform, v_name), default_schema)
234  child_schema = child_schema_gen(gateway, child, v_name)
235  try:
236  child_schema(child.values)
237  except vol.Invalid as exc:
238  _LOGGER.warning(
239  "Invalid %s on node %s, %s platform: %s",
240  child,
241  node_id,
242  platform,
243  exc,
244  )
245  continue
246  dev_id: DevId = (
247  gateway_id,
248  node_id,
249  child.id,
250  set_req[v_name].value,
251  )
252  validated[platform].append(dev_id)
253 
254  return validated
None on_unload(HomeAssistant hass, GatewayId gateway_id, Callable fnct)
Definition: helpers.py:45
vol.Schema light_rgb_schema(BaseAsyncGateway gateway, ChildSensor child, ValueType value_type_name)
Definition: helpers.py:123
vol.Schema get_child_schema(BaseAsyncGateway gateway, ChildSensor child, ValueType value_type_name, dict schema)
Definition: helpers.py:152
vol.Schema light_dimmer_schema(BaseAsyncGateway gateway, ChildSensor child, ValueType value_type_name)
Definition: helpers.py:105
vol.Schema light_rgbw_schema(BaseAsyncGateway gateway, ChildSensor child, ValueType value_type_name)
Definition: helpers.py:132
vol.Schema switch_ir_send_schema(BaseAsyncGateway gateway, ChildSensor child, ValueType value_type_name)
Definition: helpers.py:141
bool validate_node(BaseAsyncGateway gateway, int node_id)
Definition: helpers.py:186
vol.Schema light_percentage_schema(BaseAsyncGateway gateway, ChildSensor child, ValueType value_type_name)
Definition: helpers.py:114
str invalid_msg(BaseAsyncGateway gateway, ChildSensor child, ValueType value_type_name)
Definition: helpers.py:169
None discover_mysensors_platform(HomeAssistant hass, GatewayId gateway_id, str platform, list[DevId] new_devices)
Definition: helpers.py:59
vol.Schema default_schema(BaseAsyncGateway gateway, ChildSensor child, ValueType value_type_name)
Definition: helpers.py:96
None discover_mysensors_node(HomeAssistant hass, GatewayId gateway_id, int node_id)
Definition: helpers.py:76
defaultdict[Platform, list[DevId]] validate_child(GatewayId gateway_id, BaseAsyncGateway gateway, int node_id, ChildSensor child, int|None value_type=None)
Definition: helpers.py:200
dict[Platform, list[DevId]] validate_set_msg(GatewayId gateway_id, Message msg)
Definition: helpers.py:178
None async_dispatcher_send(HomeAssistant hass, str signal, *Any args)
Definition: dispatcher.py:193