2023-02-23 19:58:37 +00:00
|
|
|
"""Map Matter Nodes and Attributes to Home Assistant entities."""
|
2024-03-08 14:01:29 +00:00
|
|
|
|
2023-02-23 19:58:37 +00:00
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
from collections.abc import Generator
|
|
|
|
|
|
|
|
from chip.clusters.Objects import ClusterAttributeDescriptor
|
|
|
|
from matter_server.client.models.node import MatterEndpoint
|
|
|
|
|
|
|
|
from homeassistant.const import Platform
|
|
|
|
from homeassistant.core import callback
|
|
|
|
|
|
|
|
from .binary_sensor import DISCOVERY_SCHEMAS as BINARY_SENSOR_SCHEMAS
|
2023-07-03 10:33:50 +00:00
|
|
|
from .climate import DISCOVERY_SCHEMAS as CLIMATE_SENSOR_SCHEMAS
|
2023-04-04 12:16:11 +00:00
|
|
|
from .cover import DISCOVERY_SCHEMAS as COVER_SCHEMAS
|
2023-07-26 10:19:23 +00:00
|
|
|
from .event import DISCOVERY_SCHEMAS as EVENT_SCHEMAS
|
2024-05-28 10:24:58 +00:00
|
|
|
from .fan import DISCOVERY_SCHEMAS as FAN_SCHEMAS
|
2023-02-23 19:58:37 +00:00
|
|
|
from .light import DISCOVERY_SCHEMAS as LIGHT_SCHEMAS
|
2023-03-21 00:29:33 +00:00
|
|
|
from .lock import DISCOVERY_SCHEMAS as LOCK_SCHEMAS
|
2023-02-23 19:58:37 +00:00
|
|
|
from .models import MatterDiscoverySchema, MatterEntityInfo
|
|
|
|
from .sensor import DISCOVERY_SCHEMAS as SENSOR_SCHEMAS
|
|
|
|
from .switch import DISCOVERY_SCHEMAS as SWITCH_SCHEMAS
|
|
|
|
|
|
|
|
DISCOVERY_SCHEMAS: dict[Platform, list[MatterDiscoverySchema]] = {
|
|
|
|
Platform.BINARY_SENSOR: BINARY_SENSOR_SCHEMAS,
|
2023-07-03 10:33:50 +00:00
|
|
|
Platform.CLIMATE: CLIMATE_SENSOR_SCHEMAS,
|
2023-04-04 12:16:11 +00:00
|
|
|
Platform.COVER: COVER_SCHEMAS,
|
2023-07-26 10:19:23 +00:00
|
|
|
Platform.EVENT: EVENT_SCHEMAS,
|
2024-05-28 10:24:58 +00:00
|
|
|
Platform.FAN: FAN_SCHEMAS,
|
2023-02-23 19:58:37 +00:00
|
|
|
Platform.LIGHT: LIGHT_SCHEMAS,
|
2023-03-21 00:29:33 +00:00
|
|
|
Platform.LOCK: LOCK_SCHEMAS,
|
2023-02-23 19:58:37 +00:00
|
|
|
Platform.SENSOR: SENSOR_SCHEMAS,
|
|
|
|
Platform.SWITCH: SWITCH_SCHEMAS,
|
|
|
|
}
|
2023-03-27 20:21:56 +00:00
|
|
|
SUPPORTED_PLATFORMS = tuple(DISCOVERY_SCHEMAS)
|
2023-02-23 19:58:37 +00:00
|
|
|
|
|
|
|
|
|
|
|
@callback
|
|
|
|
def iter_schemas() -> Generator[MatterDiscoverySchema, None, None]:
|
|
|
|
"""Iterate over all available discovery schemas."""
|
|
|
|
for platform_schemas in DISCOVERY_SCHEMAS.values():
|
|
|
|
yield from platform_schemas
|
|
|
|
|
|
|
|
|
|
|
|
@callback
|
|
|
|
def async_discover_entities(
|
|
|
|
endpoint: MatterEndpoint,
|
|
|
|
) -> Generator[MatterEntityInfo, None, None]:
|
|
|
|
"""Run discovery on MatterEndpoint and return matching MatterEntityInfo(s)."""
|
|
|
|
discovered_attributes: set[type[ClusterAttributeDescriptor]] = set()
|
|
|
|
device_info = endpoint.device_info
|
|
|
|
for schema in iter_schemas():
|
|
|
|
# abort if attribute(s) already discovered
|
|
|
|
if any(x in schema.required_attributes for x in discovered_attributes):
|
|
|
|
continue
|
|
|
|
|
|
|
|
# check vendor_id
|
|
|
|
if (
|
|
|
|
schema.vendor_id is not None
|
|
|
|
and device_info.vendorID not in schema.vendor_id
|
|
|
|
):
|
|
|
|
continue
|
|
|
|
|
|
|
|
# check product_name
|
|
|
|
if (
|
|
|
|
schema.product_name is not None
|
|
|
|
and device_info.productName not in schema.product_name
|
|
|
|
):
|
|
|
|
continue
|
|
|
|
|
|
|
|
# check required device_type
|
|
|
|
if schema.device_type is not None and not any(
|
|
|
|
x in schema.device_type for x in endpoint.device_types
|
|
|
|
):
|
|
|
|
continue
|
|
|
|
|
|
|
|
# check absent device_type
|
|
|
|
if schema.not_device_type is not None and any(
|
|
|
|
x in schema.not_device_type for x in endpoint.device_types
|
|
|
|
):
|
|
|
|
continue
|
|
|
|
|
|
|
|
# check endpoint_id
|
|
|
|
if (
|
|
|
|
schema.endpoint_id is not None
|
|
|
|
and endpoint.endpoint_id not in schema.endpoint_id
|
|
|
|
):
|
|
|
|
continue
|
|
|
|
|
|
|
|
# check required attributes
|
|
|
|
if schema.required_attributes is not None and not all(
|
|
|
|
endpoint.has_attribute(None, val_schema)
|
|
|
|
for val_schema in schema.required_attributes
|
|
|
|
):
|
|
|
|
continue
|
|
|
|
|
|
|
|
# check for values that may not be present
|
|
|
|
if schema.absent_attributes is not None and any(
|
|
|
|
endpoint.has_attribute(None, val_schema)
|
|
|
|
for val_schema in schema.absent_attributes
|
|
|
|
):
|
|
|
|
continue
|
|
|
|
|
|
|
|
# all checks passed, this value belongs to an entity
|
|
|
|
|
|
|
|
attributes_to_watch = list(schema.required_attributes)
|
|
|
|
if schema.optional_attributes:
|
|
|
|
# check optional attributes
|
|
|
|
for optional_attribute in schema.optional_attributes:
|
|
|
|
if optional_attribute in attributes_to_watch:
|
|
|
|
continue
|
|
|
|
if endpoint.has_attribute(None, optional_attribute):
|
|
|
|
attributes_to_watch.append(optional_attribute)
|
|
|
|
|
|
|
|
yield MatterEntityInfo(
|
|
|
|
endpoint=endpoint,
|
|
|
|
platform=schema.platform,
|
|
|
|
attributes_to_watch=attributes_to_watch,
|
|
|
|
entity_description=schema.entity_description,
|
|
|
|
entity_class=schema.entity_class,
|
|
|
|
)
|
|
|
|
|
2023-12-04 16:21:41 +00:00
|
|
|
# prevent re-discovery of the primary attribute if not allowed
|
2023-02-23 19:58:37 +00:00
|
|
|
if not schema.allow_multi:
|
2023-12-04 16:21:41 +00:00
|
|
|
discovered_attributes.update(schema.required_attributes)
|