1 """Matter to Home Assistant adapter."""
3 from __future__
import annotations
5 from typing
import TYPE_CHECKING, cast
7 from chip.clusters
import Objects
as clusters
8 from matter_server.client.models.device_types
import BridgedDevice
9 from matter_server.common.models
import EventType, ServerInfoMessage
17 from .const
import DOMAIN, ID_TYPE_DEVICE_ID, ID_TYPE_SERIAL, LOGGER
18 from .discovery
import async_discover_entities
19 from .helpers
import get_device_id
22 from matter_server.client
import MatterClient
23 from matter_server.client.models.node
import MatterEndpoint, MatterNode
27 """Strip spaces and null char from the name."""
30 name = name.replace(
"\x00",
"")
31 return name.strip()
or None
35 """Connect Matter into Home Assistant."""
40 matter_client: MatterClient,
41 config_entry: ConfigEntry,
43 """Initialize the adapter."""
47 self.platform_handlers: dict[Platform, AddEntitiesCallback] = {}
48 self.discovered_entities: set[str] = set()
51 self, platform: Platform, add_entities: AddEntitiesCallback
53 """Register a platform handler."""
54 self.platform_handlers[platform] = add_entities
57 """Set up all existing nodes and subscribe to new nodes."""
61 def node_added_callback(event: EventType, node: MatterNode) ->
None:
62 """Handle node added event."""
65 def node_updated_callback(event: EventType, node: MatterNode) ->
None:
66 """Handle node updated event."""
67 if not node.available:
73 def endpoint_added_callback(event: EventType, data: dict[str, int]) ->
None:
74 """Handle endpoint added event."""
75 node = self.
matter_clientmatter_client.get_node(data[
"node_id"])
76 self.
_setup_endpoint_setup_endpoint(node.endpoints[data[
"endpoint_id"]])
78 def endpoint_removed_callback(event: EventType, data: dict[str, int]) ->
None:
79 """Handle endpoint removed event."""
80 server_info = cast(ServerInfoMessage, self.
matter_clientmatter_client.server_info)
82 node = self.
matter_clientmatter_client.get_node(data[
"node_id"])
85 device_registry = dr.async_get(self.
hasshass)
86 endpoint = node.endpoints.get(data[
"endpoint_id"])
91 node.endpoints[data[
"endpoint_id"]],
93 identifier = (DOMAIN, f
"{ID_TYPE_DEVICE_ID}_{node_device_id}")
94 if device := device_registry.async_get_device(identifiers={identifier}):
95 device_registry.async_remove_device(device.id)
97 def node_removed_callback(event: EventType, node_id: int) ->
None:
98 """Handle node removed event."""
103 for endpoint_id
in node.endpoints:
104 endpoint_removed_callback(
105 EventType.ENDPOINT_REMOVED,
106 {
"node_id": node_id,
"endpoint_id": endpoint_id},
111 callback=endpoint_added_callback, event_filter=EventType.ENDPOINT_ADDED
116 callback=endpoint_removed_callback,
117 event_filter=EventType.ENDPOINT_REMOVED,
122 callback=node_removed_callback, event_filter=EventType.NODE_REMOVED
127 callback=node_added_callback, event_filter=EventType.NODE_ADDED
132 callback=node_updated_callback, event_filter=EventType.NODE_UPDATED
137 """Set up an node."""
138 LOGGER.debug(
"Setting up entities for node %s", node.node_id)
140 for endpoint
in node.endpoints.values():
143 except Exception
as err:
147 "Error setting up node %s: %s",
154 endpoint: MatterEndpoint,
156 """Create a device registry entry for a MatterNode."""
157 server_info = cast(ServerInfoMessage, self.
matter_clientmatter_client.server_info)
159 basic_info = endpoint.device_info
164 for x
in endpoint.device_types
165 if x.device_type != BridgedDevice.device_type
173 or (device_type.__name__
if device_type
else None)
177 bridge_device_id =
None
178 if endpoint.is_bridged_device
and endpoint.node.endpoints[0] != endpoint:
181 endpoint.node.endpoints[0],
183 bridge_device_id = f
"{ID_TYPE_DEVICE_ID}_{bridge_device_id}"
189 identifiers = {(DOMAIN, f
"{ID_TYPE_DEVICE_ID}_{node_device_id}")}
190 serial_number: str |
None =
None
193 basic_info_serial_number := basic_info.serialNumber
194 )
and "test" not in basic_info_serial_number.lower():
196 identifiers.add((DOMAIN, f
"{ID_TYPE_SERIAL}_{basic_info_serial_number}"))
197 serial_number = basic_info_serial_number
206 or device_type.__name__
212 if isinstance(basic_info, clusters.BridgedDeviceBasicInformation):
216 model_id =
str(product_id)
if (product_id := basic_info.productID)
else None
218 dr.async_get(self.
hasshass).async_get_or_create(
221 identifiers=identifiers,
222 hw_version=basic_info.hardwareVersionString,
223 sw_version=basic_info.softwareVersionString,
224 manufacturer=basic_info.vendorName
or endpoint.node.device_info.vendorName,
227 serial_number=serial_number,
228 via_device=(DOMAIN, bridge_device_id)
if bridge_device_id
else None,
232 """Set up a MatterEndpoint as HA Device."""
238 f
"{entity_info.platform}_{endpoint.node.node_id}_{endpoint.endpoint_id}_"
239 f
"{entity_info.primary_attribute.cluster_id}_"
240 f
"{entity_info.primary_attribute.attribute_id}_"
241 f
"{entity_info.entity_description.key}"
243 if discovery_key
in self.discovered_entities:
246 "Creating %s entity for %s",
247 entity_info.platform,
248 entity_info.primary_attribute,
250 self.discovered_entities.
add(discovery_key)
251 new_entity = entity_info.entity_class(
254 self.platform_handlers[entity_info.platform]([new_entity])
None _setup_node(self, MatterNode node)
None _setup_endpoint(self, MatterEndpoint endpoint)
None __init__(self, HomeAssistant hass, MatterClient matter_client, ConfigEntry config_entry)
None register_platform_handler(self, Platform platform, AddEntitiesCallback add_entities)
None _create_device_registry(self, MatterEndpoint endpoint)
bool add(self, _T matcher)
str|None get_clean_name(str|None name)
Generator[MatterEntityInfo] async_discover_entities(MatterEndpoint endpoint)
str get_device_id(ServerInfoMessage server_info, MatterEndpoint endpoint)