Home Assistant Unofficial Reference 2024.12.1
discovery.py
Go to the documentation of this file.
1 """Map Matter Nodes and Attributes to Home Assistant entities."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Generator
6 
7 from chip.clusters.Objects import ClusterAttributeDescriptor
8 from matter_server.client.models.node import MatterEndpoint
9 
10 from homeassistant.const import Platform
11 from homeassistant.core import callback
12 
13 from .binary_sensor import DISCOVERY_SCHEMAS as BINARY_SENSOR_SCHEMAS
14 from .button import DISCOVERY_SCHEMAS as BUTTON_SCHEMAS
15 from .climate import DISCOVERY_SCHEMAS as CLIMATE_SENSOR_SCHEMAS
16 from .const import FEATUREMAP_ATTRIBUTE_ID
17 from .cover import DISCOVERY_SCHEMAS as COVER_SCHEMAS
18 from .event import DISCOVERY_SCHEMAS as EVENT_SCHEMAS
19 from .fan import DISCOVERY_SCHEMAS as FAN_SCHEMAS
20 from .light import DISCOVERY_SCHEMAS as LIGHT_SCHEMAS
21 from .lock import DISCOVERY_SCHEMAS as LOCK_SCHEMAS
22 from .models import MatterDiscoverySchema, MatterEntityInfo
23 from .number import DISCOVERY_SCHEMAS as NUMBER_SCHEMAS
24 from .select import DISCOVERY_SCHEMAS as SELECT_SCHEMAS
25 from .sensor import DISCOVERY_SCHEMAS as SENSOR_SCHEMAS
26 from .switch import DISCOVERY_SCHEMAS as SWITCH_SCHEMAS
27 from .update import DISCOVERY_SCHEMAS as UPDATE_SCHEMAS
28 from .vacuum import DISCOVERY_SCHEMAS as VACUUM_SCHEMAS
29 from .valve import DISCOVERY_SCHEMAS as VALVE_SCHEMAS
30 
31 DISCOVERY_SCHEMAS: dict[Platform, list[MatterDiscoverySchema]] = {
32  Platform.BINARY_SENSOR: BINARY_SENSOR_SCHEMAS,
33  Platform.BUTTON: BUTTON_SCHEMAS,
34  Platform.CLIMATE: CLIMATE_SENSOR_SCHEMAS,
35  Platform.COVER: COVER_SCHEMAS,
36  Platform.EVENT: EVENT_SCHEMAS,
37  Platform.FAN: FAN_SCHEMAS,
38  Platform.LIGHT: LIGHT_SCHEMAS,
39  Platform.LOCK: LOCK_SCHEMAS,
40  Platform.NUMBER: NUMBER_SCHEMAS,
41  Platform.SELECT: SELECT_SCHEMAS,
42  Platform.SENSOR: SENSOR_SCHEMAS,
43  Platform.SWITCH: SWITCH_SCHEMAS,
44  Platform.UPDATE: UPDATE_SCHEMAS,
45  Platform.VACUUM: VACUUM_SCHEMAS,
46  Platform.VALVE: VALVE_SCHEMAS,
47 }
48 SUPPORTED_PLATFORMS = tuple(DISCOVERY_SCHEMAS)
49 
50 
51 @callback
52 def iter_schemas() -> Generator[MatterDiscoverySchema]:
53  """Iterate over all available discovery schemas."""
54  for platform_schemas in DISCOVERY_SCHEMAS.values():
55  yield from platform_schemas
56 
57 
58 @callback
60  endpoint: MatterEndpoint,
61 ) -> Generator[MatterEntityInfo]:
62  """Run discovery on MatterEndpoint and return matching MatterEntityInfo(s)."""
63  discovered_attributes: set[type[ClusterAttributeDescriptor]] = set()
64  device_info = endpoint.device_info
65  for schema in iter_schemas():
66  # abort if attribute(s) already discovered
67  if any(x in schema.required_attributes for x in discovered_attributes):
68  continue
69 
70  # check vendor_id
71  if (
72  schema.vendor_id is not None
73  and device_info.vendorID not in schema.vendor_id
74  ):
75  continue
76 
77  # check product_name
78  if (
79  schema.product_name is not None
80  and device_info.productName not in schema.product_name
81  ):
82  continue
83 
84  # check required device_type
85  if schema.device_type is not None and not any(
86  x in schema.device_type for x in endpoint.device_types
87  ):
88  continue
89 
90  # check absent device_type
91  if schema.not_device_type is not None and any(
92  x in schema.not_device_type for x in endpoint.device_types
93  ):
94  continue
95 
96  # check endpoint_id
97  if (
98  schema.endpoint_id is not None
99  and endpoint.endpoint_id not in schema.endpoint_id
100  ):
101  continue
102 
103  # check required attributes
104  if schema.required_attributes is not None and not all(
105  endpoint.has_attribute(None, val_schema)
106  for val_schema in schema.required_attributes
107  ):
108  continue
109 
110  # check for endpoint-attributes that may not be present
111  if schema.absent_attributes is not None and any(
112  endpoint.has_attribute(None, val_schema)
113  for val_schema in schema.absent_attributes
114  ):
115  continue
116 
117  # check for clusters that may not be present
118  if schema.absent_clusters is not None and any(
119  endpoint.node.has_cluster(val_schema)
120  for val_schema in schema.absent_clusters
121  ):
122  continue
123 
124  # check for required value in (primary) attribute
125  primary_attribute = schema.required_attributes[0]
126  primary_value = endpoint.get_attribute_value(None, primary_attribute)
127  if schema.value_contains is not None and (
128  isinstance(primary_value, list)
129  and schema.value_contains not in primary_value
130  ):
131  continue
132 
133  # check for required value in cluster featuremap
134  if schema.featuremap_contains is not None and (
135  not bool(
136  int(
137  endpoint.get_attribute_value(
138  primary_attribute.cluster_id, FEATUREMAP_ATTRIBUTE_ID
139  )
140  )
141  & schema.featuremap_contains
142  )
143  ):
144  continue
145 
146  # all checks passed, this value belongs to an entity
147 
148  attributes_to_watch = list(schema.required_attributes)
149  if schema.optional_attributes:
150  # check optional attributes
151  for optional_attribute in schema.optional_attributes:
152  if optional_attribute in attributes_to_watch:
153  continue
154  if endpoint.has_attribute(None, optional_attribute):
155  attributes_to_watch.append(optional_attribute)
156 
157  yield MatterEntityInfo(
158  endpoint=endpoint,
159  platform=schema.platform,
160  attributes_to_watch=attributes_to_watch,
161  entity_description=schema.entity_description,
162  entity_class=schema.entity_class,
163  discovery_schema=schema,
164  )
165 
166  # prevent re-discovery of the primary attribute if not allowed
167  if not schema.allow_multi:
168  discovered_attributes.update(schema.required_attributes)
Generator[MatterDiscoverySchema] iter_schemas()
Definition: discovery.py:52
Generator[MatterEntityInfo] async_discover_entities(MatterEndpoint endpoint)
Definition: discovery.py:61