Add helper method to get matter device info (#103765)

* Add helper method to get matter device info

* Cleanup async

* Guard

* get node from device id instead of node id

* Fix test

---------

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
This commit is contained in:
Marcel van der Veldt 2023-11-10 19:43:54 +01:00 committed by GitHub
parent e157206eeb
commit ebdd2daab6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 57 additions and 18 deletions

View File

@ -3,6 +3,7 @@ from __future__ import annotations
import asyncio import asyncio
from contextlib import suppress from contextlib import suppress
from functools import cache
from matter_server.client import MatterClient from matter_server.client import MatterClient
from matter_server.client.exceptions import CannotConnect, InvalidServerVersion from matter_server.client.exceptions import CannotConnect, InvalidServerVersion
@ -28,12 +29,34 @@ from .addon import get_addon_manager
from .api import async_register_api from .api import async_register_api
from .const import CONF_INTEGRATION_CREATED_ADDON, CONF_USE_ADDON, DOMAIN, LOGGER from .const import CONF_INTEGRATION_CREATED_ADDON, CONF_USE_ADDON, DOMAIN, LOGGER
from .discovery import SUPPORTED_PLATFORMS from .discovery import SUPPORTED_PLATFORMS
from .helpers import MatterEntryData, get_matter, get_node_from_device_entry from .helpers import (
MatterEntryData,
get_matter,
get_node_from_device_entry,
node_from_ha_device_id,
)
from .models import MatterDeviceInfo
CONNECT_TIMEOUT = 10 CONNECT_TIMEOUT = 10
LISTEN_READY_TIMEOUT = 30 LISTEN_READY_TIMEOUT = 30
@callback
@cache
def get_matter_device_info(
hass: HomeAssistant, device_id: str
) -> MatterDeviceInfo | None:
"""Return Matter device info or None if device does not exist."""
if not (node := node_from_ha_device_id(hass, device_id)):
return None
return MatterDeviceInfo(
unique_id=node.device_info.uniqueID,
vendor_id=hex(node.device_info.vendorID),
product_id=hex(node.device_info.productID),
)
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Matter from a config entry.""" """Set up Matter from a config entry."""
if use_addon := entry.data.get(CONF_USE_ADDON): if use_addon := entry.data.get(CONF_USE_ADDON):
@ -190,7 +213,7 @@ async def async_remove_config_entry_device(
hass: HomeAssistant, config_entry: ConfigEntry, device_entry: dr.DeviceEntry hass: HomeAssistant, config_entry: ConfigEntry, device_entry: dr.DeviceEntry
) -> bool: ) -> bool:
"""Remove a config entry from a device.""" """Remove a config entry from a device."""
node = await get_node_from_device_entry(hass, device_entry) node = get_node_from_device_entry(hass, device_entry)
if node is None: if node is None:
return True return True
@ -218,21 +241,11 @@ async def async_remove_config_entry_device(
def _async_init_services(hass: HomeAssistant) -> None: def _async_init_services(hass: HomeAssistant) -> None:
"""Init services.""" """Init services."""
async def _node_id_from_ha_device_id(ha_device_id: str) -> int | None:
"""Get node id from ha device id."""
dev_reg = dr.async_get(hass)
device = dev_reg.async_get(ha_device_id)
if device is None:
return None
if node := await get_node_from_device_entry(hass, device):
return node.node_id
return None
async def open_commissioning_window(call: ServiceCall) -> None: async def open_commissioning_window(call: ServiceCall) -> None:
"""Open commissioning window on specific node.""" """Open commissioning window on specific node."""
node_id = await _node_id_from_ha_device_id(call.data["device_id"]) node = node_from_ha_device_id(hass, call.data["device_id"])
if node_id is None: if node is None:
raise HomeAssistantError("This is not a Matter device") raise HomeAssistantError("This is not a Matter device")
matter_client = get_matter(hass).matter_client matter_client = get_matter(hass).matter_client
@ -240,7 +253,7 @@ def _async_init_services(hass: HomeAssistant) -> None:
# We are sending device ID . # We are sending device ID .
try: try:
await matter_client.open_commissioning_window(node_id) await matter_client.open_commissioning_window(node.node_id)
except NodeCommissionFailed as err: except NodeCommissionFailed as err:
raise HomeAssistantError(str(err)) from err raise HomeAssistantError(str(err)) from err

View File

@ -58,7 +58,7 @@ async def async_get_device_diagnostics(
"""Return diagnostics for a device.""" """Return diagnostics for a device."""
matter = get_matter(hass) matter = get_matter(hass)
server_diagnostics = await matter.matter_client.get_diagnostics() server_diagnostics = await matter.matter_client.get_diagnostics()
node = await get_node_from_device_entry(hass, device) node = get_node_from_device_entry(hass, device)
return { return {
"server_info": dataclass_to_dict(server_diagnostics.info), "server_info": dataclass_to_dict(server_diagnostics.info),

View File

@ -66,7 +66,18 @@ def get_device_id(
return f"{operational_instance_id}-{postfix}" return f"{operational_instance_id}-{postfix}"
async def get_node_from_device_entry( @callback
def node_from_ha_device_id(hass: HomeAssistant, ha_device_id: str) -> MatterNode | None:
"""Get node id from ha device id."""
dev_reg = dr.async_get(hass)
device = dev_reg.async_get(ha_device_id)
if device is None:
raise ValueError("Invalid device ID")
return get_node_from_device_entry(hass, device)
@callback
def get_node_from_device_entry(
hass: HomeAssistant, device: dr.DeviceEntry hass: HomeAssistant, device: dr.DeviceEntry
) -> MatterNode | None: ) -> MatterNode | None:
"""Return MatterNode from device entry.""" """Return MatterNode from device entry."""

View File

@ -2,6 +2,7 @@
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass from dataclasses import dataclass
from typing import TypedDict
from chip.clusters import Objects as clusters from chip.clusters import Objects as clusters
from chip.clusters.Objects import ClusterAttributeDescriptor from chip.clusters.Objects import ClusterAttributeDescriptor
@ -16,6 +17,20 @@ SensorValueTypes = type[
] ]
class MatterDeviceInfo(TypedDict):
"""Dictionary with Matter Device info.
Used to send to other Matter controllers,
such as Google Home to prevent duplicated devices.
Reference: https://developers.home.google.com/matter/device-deduplication
"""
unique_id: str
vendor_id: str # vendorId hex string
product_id: str # productId hex string
@dataclass @dataclass
class MatterEntityInfo: class MatterEntityInfo:
"""Info discovered from (primary) Matter Attribute to create entity.""" """Info discovered from (primary) Matter Attribute to create entity."""

View File

@ -56,7 +56,7 @@ async def test_get_node_from_device_entry(
device_registry, config_entry.entry_id device_registry, config_entry.entry_id
)[0] )[0]
assert device_entry assert device_entry
node_from_device_entry = await get_node_from_device_entry(hass, device_entry) node_from_device_entry = get_node_from_device_entry(hass, device_entry)
assert node_from_device_entry is node assert node_from_device_entry is node