diff --git a/homeassistant/components/matter/__init__.py b/homeassistant/components/matter/__init__.py index b96be6bee45..b1470ecc422 100644 --- a/homeassistant/components/matter/__init__.py +++ b/homeassistant/components/matter/__init__.py @@ -2,7 +2,6 @@ from __future__ import annotations import asyncio -from typing import cast import async_timeout from matter_server.client import MatterClient @@ -245,7 +244,7 @@ def _async_init_services(hass: HomeAssistant) -> None: # This could be more efficient for node in await matter_client.get_nodes(): if node.unique_id == unique_id: - return cast(int, node.node_id) + return node.node_id return None diff --git a/homeassistant/components/matter/adapter.py b/homeassistant/components/matter/adapter.py index 42e6070d49b..b573ed0a3fc 100644 --- a/homeassistant/components/matter/adapter.py +++ b/homeassistant/components/matter/adapter.py @@ -47,8 +47,12 @@ class MatterAdapter: for node in await self.matter_client.get_nodes(): self._setup_node(node) - def node_added_callback(event: EventType, node: MatterNode) -> None: + def node_added_callback(event: EventType, node: MatterNode | None) -> None: """Handle node added event.""" + if node is None: + # We can clean this up when we've improved the typing in the library. + # https://github.com/home-assistant-libs/python-matter-server/pull/153 + raise RuntimeError("Node added event without node") self._setup_node(node) self.config_entry.async_on_unload( @@ -61,8 +65,9 @@ class MatterAdapter: bridge_unique_id: str | None = None - if node.aggregator_device_type_instance is not None: - node_info = node.root_device_type_instance.get_cluster(all_clusters.Basic) + if node.aggregator_device_type_instance is not None and ( + node_info := node.root_device_type_instance.get_cluster(all_clusters.Basic) + ): self._create_device_registry( node_info, node_info.nodeLabel or "Hub device", None ) diff --git a/homeassistant/components/matter/binary_sensor.py b/homeassistant/components/matter/binary_sensor.py index 8d0b2f08c2a..15ad13d25ad 100644 --- a/homeassistant/components/matter/binary_sensor.py +++ b/homeassistant/components/matter/binary_sensor.py @@ -39,9 +39,8 @@ class MatterBinarySensor(MatterEntity, BinarySensorEntity): @callback def _update_from_device(self) -> None: """Update from device.""" - self._attr_is_on = self._device_type_instance.get_cluster( - clusters.BooleanState - ).stateValue + cluster = self._device_type_instance.get_cluster(clusters.BooleanState) + self._attr_is_on = cluster.stateValue if cluster else None class MatterOccupancySensor(MatterBinarySensor): @@ -52,11 +51,9 @@ class MatterOccupancySensor(MatterBinarySensor): @callback def _update_from_device(self) -> None: """Update from device.""" - occupancy = self._device_type_instance.get_cluster( - clusters.OccupancySensing - ).occupancy + cluster = self._device_type_instance.get_cluster(clusters.OccupancySensing) # The first bit = if occupied - self._attr_is_on = occupancy & 1 == 1 + self._attr_is_on = cluster.occupancy & 1 == 1 if cluster else None @dataclass diff --git a/homeassistant/components/matter/config_flow.py b/homeassistant/components/matter/config_flow.py index 98146a6ae01..6e25370d86a 100644 --- a/homeassistant/components/matter/config_flow.py +++ b/homeassistant/components/matter/config_flow.py @@ -20,6 +20,7 @@ from homeassistant.components.hassio import ( from homeassistant.const import CONF_URL from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import AbortFlow, FlowResult +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import aiohttp_client from .addon import get_addon_manager @@ -131,7 +132,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): try: await self.start_task - except (CannotConnect, AddonError, AbortFlow) as err: + except (FailedConnect, AddonError, AbortFlow) as err: self.start_task = None LOGGER.error(err) return self.async_show_progress_done(next_step_id="start_failed") @@ -170,7 +171,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): else: break else: - raise CannotConnect("Failed to start Matter Server add-on: timeout") + raise FailedConnect("Failed to start Matter Server add-on: timeout") finally: # Continue the flow after show progress when the task is done. self.hass.async_create_task( @@ -324,3 +325,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): CONF_INTEGRATION_CREATED_ADDON: self.integration_created_addon, }, ) + + +class FailedConnect(HomeAssistantError): + """Failed to connect to the Matter Server.""" diff --git a/homeassistant/components/matter/entity.py b/homeassistant/components/matter/entity.py index 019631750f4..4f28c1d2369 100644 --- a/homeassistant/components/matter/entity.py +++ b/homeassistant/components/matter/entity.py @@ -59,7 +59,15 @@ class MatterEntity(Entity): self._unsubscribes: list[Callable] = [] # for fast lookups we create a mapping to the attribute paths self._attributes_map: dict[type, str] = {} - self._attr_unique_id = f"{matter_client.server_info.compressed_fabric_id}-{node.unique_id}-{device_type_instance.endpoint}-{device_type_instance.device_type.device_type}" + server_info = matter_client.server_info + # The server info is set when the client connects to the server. + assert server_info is not None + self._attr_unique_id = ( + f"{server_info.compressed_fabric_id}-" + f"{node.unique_id}-" + f"{device_type_instance.endpoint}-" + f"{device_type_instance.device_type.device_type}" + ) @property def device_info(self) -> DeviceInfo | None: diff --git a/homeassistant/components/matter/light.py b/homeassistant/components/matter/light.py index a07b791c64d..0136b74f32e 100644 --- a/homeassistant/components/matter/light.py +++ b/homeassistant/components/matter/light.py @@ -57,6 +57,9 @@ class MatterLight(MatterEntity, LightEntity): return level_control = self._device_type_instance.get_cluster(clusters.LevelControl) + # We check above that the device supports brightness, ie level control. + assert level_control is not None + level = round( renormalize( kwargs[ATTR_BRIGHTNESS], @@ -86,20 +89,20 @@ class MatterLight(MatterEntity, LightEntity): @callback def _update_from_device(self) -> None: """Update from device.""" - if self._attr_supported_color_modes is None: - if self._supports_brightness(): - self._attr_supported_color_modes = {ColorMode.BRIGHTNESS} + supports_brigthness = self._supports_brightness() + + if self._attr_supported_color_modes is None and supports_brigthness: + self._attr_supported_color_modes = {ColorMode.BRIGHTNESS} if attr := self.get_matter_attribute(clusters.OnOff.Attributes.OnOff): self._attr_is_on = attr.value - if ( - clusters.LevelControl.Attributes.CurrentLevel - in self.entity_description.subscribe_attributes - ): + if supports_brigthness: level_control = self._device_type_instance.get_cluster( clusters.LevelControl ) + # We check above that the device supports brightness, ie level control. + assert level_control is not None # Convert brightness to Home Assistant = 0..255 self._attr_brightness = round( diff --git a/homeassistant/components/matter/manifest.json b/homeassistant/components/matter/manifest.json index b94ba5f58ad..129110ba519 100644 --- a/homeassistant/components/matter/manifest.json +++ b/homeassistant/components/matter/manifest.json @@ -3,7 +3,7 @@ "name": "Matter (BETA)", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/matter", - "requirements": ["python-matter-server==1.0.7"], + "requirements": ["python-matter-server==1.0.8"], "dependencies": ["websocket_api"], "codeowners": ["@home-assistant/matter"], "iot_class": "local_push" diff --git a/homeassistant/components/matter/switch.py b/homeassistant/components/matter/switch.py index 6968a3d095d..f86a7cbb023 100644 --- a/homeassistant/components/matter/switch.py +++ b/homeassistant/components/matter/switch.py @@ -56,7 +56,8 @@ class MatterSwitch(MatterEntity, SwitchEntity): @callback def _update_from_device(self) -> None: """Update from device.""" - self._attr_is_on = self._device_type_instance.get_cluster(clusters.OnOff).onOff + cluster = self._device_type_instance.get_cluster(clusters.OnOff) + self._attr_is_on = cluster.onOff if cluster else None @dataclass diff --git a/requirements_all.txt b/requirements_all.txt index 382feba38b6..385fec56a16 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2038,7 +2038,7 @@ python-kasa==0.5.0 # python-lirc==1.2.3 # homeassistant.components.matter -python-matter-server==1.0.7 +python-matter-server==1.0.8 # homeassistant.components.xiaomi_miio python-miio==0.5.12 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 91c99f7daba..6d8d13f41ab 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1428,7 +1428,7 @@ python-juicenet==1.1.0 python-kasa==0.5.0 # homeassistant.components.matter -python-matter-server==1.0.7 +python-matter-server==1.0.8 # homeassistant.components.xiaomi_miio python-miio==0.5.12