diff --git a/homeassistant/components/matter/adapter.py b/homeassistant/components/matter/adapter.py index 9f16dae8334..f3a764bc99f 100644 --- a/homeassistant/components/matter/adapter.py +++ b/homeassistant/components/matter/adapter.py @@ -52,13 +52,62 @@ class MatterAdapter: async def setup_nodes(self) -> None: """Set up all existing nodes and subscribe to new nodes.""" - for node in await self.matter_client.get_nodes(): + for node in self.matter_client.get_nodes(): self._setup_node(node) def node_added_callback(event: EventType, node: MatterNode) -> None: """Handle node added event.""" self._setup_node(node) + def endpoint_added_callback(event: EventType, data: dict[str, int]) -> None: + """Handle endpoint added event.""" + node = self.matter_client.get_node(data["node_id"]) + self._setup_endpoint(node.endpoints[data["endpoint_id"]]) + + def endpoint_removed_callback(event: EventType, data: dict[str, int]) -> None: + """Handle endpoint removed event.""" + server_info = cast(ServerInfoMessage, self.matter_client.server_info) + try: + node = self.matter_client.get_node(data["node_id"]) + except KeyError: + return # race condition + device_registry = dr.async_get(self.hass) + endpoint = node.endpoints.get(data["endpoint_id"]) + if not endpoint: + return # race condition + node_device_id = get_device_id( + server_info, + node.endpoints[data["endpoint_id"]], + ) + identifier = (DOMAIN, f"{ID_TYPE_DEVICE_ID}_{node_device_id}") + if device := device_registry.async_get_device({identifier}): + device_registry.async_remove_device(device.id) + + def node_removed_callback(event: EventType, node_id: int) -> None: + """Handle node removed event.""" + try: + node = self.matter_client.get_node(node_id) + except KeyError: + return # race condition + for endpoint_id in node.endpoints: + endpoint_removed_callback( + EventType.ENDPOINT_REMOVED, + {"node_id": node_id, "endpoint_id": endpoint_id}, + ) + + self.config_entry.async_on_unload( + self.matter_client.subscribe( + endpoint_added_callback, EventType.ENDPOINT_ADDED + ) + ) + self.config_entry.async_on_unload( + self.matter_client.subscribe( + endpoint_removed_callback, EventType.ENDPOINT_REMOVED + ) + ) + self.config_entry.async_on_unload( + self.matter_client.subscribe(node_removed_callback, EventType.NODE_REMOVED) + ) self.config_entry.async_on_unload( self.matter_client.subscribe(node_added_callback, EventType.NODE_ADDED) ) diff --git a/homeassistant/components/matter/entity.py b/homeassistant/components/matter/entity.py index bf0a74ef845..d4e90508afa 100644 --- a/homeassistant/components/matter/entity.py +++ b/homeassistant/components/matter/entity.py @@ -75,9 +75,11 @@ class MatterEntity(Entity): await super().async_added_to_hass() # Subscribe to attribute updates. + sub_paths: list[str] = [] for attr_cls in self._entity_info.attributes_to_watch: attr_path = self.get_matter_attribute_path(attr_cls) self._attributes_map[attr_cls] = attr_path + sub_paths.append(attr_path) self._unsubscribes.append( self.matter_client.subscribe( callback=self._on_matter_event, @@ -86,6 +88,9 @@ class MatterEntity(Entity): attr_path_filter=attr_path, ) ) + await self.matter_client.subscribe_attribute( + self._endpoint.node.node_id, sub_paths + ) # subscribe to node (availability changes) self._unsubscribes.append( self.matter_client.subscribe( diff --git a/homeassistant/components/matter/helpers.py b/homeassistant/components/matter/helpers.py index 4b609950256..0274c80edf8 100644 --- a/homeassistant/components/matter/helpers.py +++ b/homeassistant/components/matter/helpers.py @@ -95,7 +95,7 @@ async def get_node_from_device_entry( node = next( ( node - for node in await matter_client.get_nodes() + for node in matter_client.get_nodes() for endpoint in node.endpoints.values() if get_device_id(server_info, endpoint) == device_id ), diff --git a/homeassistant/components/matter/manifest.json b/homeassistant/components/matter/manifest.json index 707f7e70ee3..0bf900c8812 100644 --- a/homeassistant/components/matter/manifest.json +++ b/homeassistant/components/matter/manifest.json @@ -6,5 +6,5 @@ "dependencies": ["websocket_api"], "documentation": "https://www.home-assistant.io/integrations/matter", "iot_class": "local_push", - "requirements": ["python-matter-server==3.5.1"] + "requirements": ["python-matter-server==3.6.0"] } diff --git a/requirements_all.txt b/requirements_all.txt index 787b92fbc75..18c20ea11f7 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2103,7 +2103,7 @@ python-kasa==0.5.1 # python-lirc==1.2.3 # homeassistant.components.matter -python-matter-server==3.5.1 +python-matter-server==3.6.0 # homeassistant.components.xiaomi_miio python-miio==0.5.12 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 3e0ba0b8405..c7f49154fda 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1541,7 +1541,7 @@ python-juicenet==1.1.0 python-kasa==0.5.1 # homeassistant.components.matter -python-matter-server==3.5.1 +python-matter-server==3.6.0 # homeassistant.components.xiaomi_miio python-miio==0.5.12 diff --git a/tests/components/matter/fixtures/config_entry_diagnostics.json b/tests/components/matter/fixtures/config_entry_diagnostics.json index 95ef0b6a850..53477792e43 100644 --- a/tests/components/matter/fixtures/config_entry_diagnostics.json +++ b/tests/components/matter/fixtures/config_entry_diagnostics.json @@ -669,7 +669,9 @@ "1/1030/65529": [], "1/1030/65531": [0, 1, 2, 65528, 65529, 65531, 65532, 65533] }, - "available": true + "available": true, + "is_bridge": false, + "attribute_subscriptions": [] } ], "events": [] diff --git a/tests/components/matter/fixtures/config_entry_diagnostics_redacted.json b/tests/components/matter/fixtures/config_entry_diagnostics_redacted.json index 794980a8fd4..3c5b82ad5b8 100644 --- a/tests/components/matter/fixtures/config_entry_diagnostics_redacted.json +++ b/tests/components/matter/fixtures/config_entry_diagnostics_redacted.json @@ -482,7 +482,9 @@ "1/1030/65529": [], "1/1030/65531": [0, 1, 2, 65528, 65529, 65531, 65532, 65533] }, - "available": true + "available": true, + "is_bridge": false, + "attribute_subscriptions": [] } ], "events": [] diff --git a/tests/components/matter/fixtures/nodes/color-temperature-light.json b/tests/components/matter/fixtures/nodes/color-temperature-light.json index f5a6d5fd1e9..7552fa833fb 100644 --- a/tests/components/matter/fixtures/nodes/color-temperature-light.json +++ b/tests/components/matter/fixtures/nodes/color-temperature-light.json @@ -313,5 +313,6 @@ "1/4/65529": [0, 5, 2, 4, 3, 1], "1/4/65531": [0, 65528, 65529, 65531, 65532, 65533] }, - "available": true + "available": true, + "attribute_subscriptions": [] } diff --git a/tests/components/matter/fixtures/nodes/contact-sensor.json b/tests/components/matter/fixtures/nodes/contact-sensor.json index 3500c73f790..909f7be2ebe 100644 --- a/tests/components/matter/fixtures/nodes/contact-sensor.json +++ b/tests/components/matter/fixtures/nodes/contact-sensor.json @@ -85,5 +85,6 @@ "1/69/65529": [], "1/69/65531": [0, 65528, 65529, 65531, 65532, 65533] }, - "available": true + "available": true, + "attribute_subscriptions": [] } diff --git a/tests/components/matter/fixtures/nodes/device_diagnostics.json b/tests/components/matter/fixtures/nodes/device_diagnostics.json index c0e1e898028..4b834cd9090 100644 --- a/tests/components/matter/fixtures/nodes/device_diagnostics.json +++ b/tests/components/matter/fixtures/nodes/device_diagnostics.json @@ -467,5 +467,7 @@ "1/1030/65529": [], "1/1030/65531": [0, 1, 2, 65528, 65529, 65531, 65532, 65533] }, - "available": true + "available": true, + "is_bridge": false, + "attribute_subscriptions": [] } diff --git a/tests/components/matter/fixtures/nodes/dimmable-light.json b/tests/components/matter/fixtures/nodes/dimmable-light.json index 32dfd29e796..e14c922857c 100644 --- a/tests/components/matter/fixtures/nodes/dimmable-light.json +++ b/tests/components/matter/fixtures/nodes/dimmable-light.json @@ -407,5 +407,6 @@ "1/1030/65529": [], "1/1030/65531": [0, 1, 2, 65528, 65529, 65531, 65532, 65533] }, - "available": true + "available": true, + "attribute_subscriptions": [] } diff --git a/tests/components/matter/fixtures/nodes/door-lock.json b/tests/components/matter/fixtures/nodes/door-lock.json index 62162b3c2d7..1477d78aa67 100644 --- a/tests/components/matter/fixtures/nodes/door-lock.json +++ b/tests/components/matter/fixtures/nodes/door-lock.json @@ -505,5 +505,6 @@ 0, 1, 2, 3, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 33, 35, 36, 37, 38, 41, 43, 48, 49, 51, 65528, 65529, 65530, 65531, 65532, 65533 ] - } + }, + "attribute_subscriptions": [] } diff --git a/tests/components/matter/fixtures/nodes/extended-color-light.json b/tests/components/matter/fixtures/nodes/extended-color-light.json index e8c4603ab9c..f4d83239b6d 100644 --- a/tests/components/matter/fixtures/nodes/extended-color-light.json +++ b/tests/components/matter/fixtures/nodes/extended-color-light.json @@ -313,5 +313,6 @@ "1/4/65529": [0, 5, 2, 4, 3, 1], "1/4/65531": [0, 65528, 65529, 65531, 65532, 65533] }, - "available": true + "available": true, + "attribute_subscriptions": [] } diff --git a/tests/components/matter/fixtures/nodes/flow-sensor.json b/tests/components/matter/fixtures/nodes/flow-sensor.json index 4e9efad2268..e1fc2a36585 100644 --- a/tests/components/matter/fixtures/nodes/flow-sensor.json +++ b/tests/components/matter/fixtures/nodes/flow-sensor.json @@ -78,5 +78,6 @@ "1/1028/65529": [], "1/1028/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533] }, - "available": true + "available": true, + "attribute_subscriptions": [] } diff --git a/tests/components/matter/fixtures/nodes/humidity-sensor.json b/tests/components/matter/fixtures/nodes/humidity-sensor.json index 23dcf667c58..a1940fc1857 100644 --- a/tests/components/matter/fixtures/nodes/humidity-sensor.json +++ b/tests/components/matter/fixtures/nodes/humidity-sensor.json @@ -77,5 +77,6 @@ "1/1029/65529": [], "1/1029/65531": [0, 1, 2, 65528, 65529, 65531, 65532, 65533] }, - "available": true + "available": true, + "attribute_subscriptions": [] } diff --git a/tests/components/matter/fixtures/nodes/light-sensor.json b/tests/components/matter/fixtures/nodes/light-sensor.json index 6289cb77da5..93583c34292 100644 --- a/tests/components/matter/fixtures/nodes/light-sensor.json +++ b/tests/components/matter/fixtures/nodes/light-sensor.json @@ -85,5 +85,6 @@ "1/1024/65529": [], "1/1024/65531": [0, 1, 2, 3, 4, 65528, 65529, 65531, 65532, 65533] }, - "available": true + "available": true, + "attribute_subscriptions": [] } diff --git a/tests/components/matter/fixtures/nodes/occupancy-sensor.json b/tests/components/matter/fixtures/nodes/occupancy-sensor.json index cac06cbae20..d8f2580c2b0 100644 --- a/tests/components/matter/fixtures/nodes/occupancy-sensor.json +++ b/tests/components/matter/fixtures/nodes/occupancy-sensor.json @@ -93,5 +93,6 @@ "1/1030/65529": [], "1/1030/65531": [0, 1, 2, 65528, 65529, 65531, 65532, 65533] }, - "available": true + "available": true, + "attribute_subscriptions": [] } diff --git a/tests/components/matter/fixtures/nodes/on-off-plugin-unit.json b/tests/components/matter/fixtures/nodes/on-off-plugin-unit.json index 376426057af..43ba486bc29 100644 --- a/tests/components/matter/fixtures/nodes/on-off-plugin-unit.json +++ b/tests/components/matter/fixtures/nodes/on-off-plugin-unit.json @@ -136,5 +136,6 @@ "1/29/65529": [], "1/29/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533] }, - "available": true + "available": true, + "attribute_subscriptions": [] } diff --git a/tests/components/matter/fixtures/nodes/onoff-light-alt-name.json b/tests/components/matter/fixtures/nodes/onoff-light-alt-name.json index 8b1f1004e59..f29361da128 100644 --- a/tests/components/matter/fixtures/nodes/onoff-light-alt-name.json +++ b/tests/components/matter/fixtures/nodes/onoff-light-alt-name.json @@ -407,5 +407,6 @@ "1/1030/65529": [], "1/1030/65531": [0, 1, 2, 65528, 65529, 65531, 65532, 65533] }, - "available": true + "available": true, + "attribute_subscriptions": [] } diff --git a/tests/components/matter/fixtures/nodes/onoff-light-no-name.json b/tests/components/matter/fixtures/nodes/onoff-light-no-name.json index 3fa4ca49ccd..8a1134409a9 100644 --- a/tests/components/matter/fixtures/nodes/onoff-light-no-name.json +++ b/tests/components/matter/fixtures/nodes/onoff-light-no-name.json @@ -407,5 +407,6 @@ "1/1030/65529": [], "1/1030/65531": [0, 1, 2, 65528, 65529, 65531, 65532, 65533] }, - "available": true + "available": true, + "attribute_subscriptions": [] } diff --git a/tests/components/matter/fixtures/nodes/onoff-light.json b/tests/components/matter/fixtures/nodes/onoff-light.json index 3db9105562a..65ef0be5c8e 100644 --- a/tests/components/matter/fixtures/nodes/onoff-light.json +++ b/tests/components/matter/fixtures/nodes/onoff-light.json @@ -407,5 +407,6 @@ "1/1030/65529": [], "1/1030/65531": [0, 1, 2, 65528, 65529, 65531, 65532, 65533] }, - "available": true + "available": true, + "attribute_subscriptions": [] } diff --git a/tests/components/matter/fixtures/nodes/pressure-sensor.json b/tests/components/matter/fixtures/nodes/pressure-sensor.json index 60335aa602d..a47cda28056 100644 --- a/tests/components/matter/fixtures/nodes/pressure-sensor.json +++ b/tests/components/matter/fixtures/nodes/pressure-sensor.json @@ -77,5 +77,6 @@ "1/1027/65529": [], "1/1027/65531": [0, 1, 2, 65528, 65529, 65531, 65532, 65533] }, - "available": true + "available": true, + "attribute_subscriptions": [] } diff --git a/tests/components/matter/fixtures/nodes/temperature-sensor.json b/tests/components/matter/fixtures/nodes/temperature-sensor.json index 426b6653623..c7d372ac2d7 100644 --- a/tests/components/matter/fixtures/nodes/temperature-sensor.json +++ b/tests/components/matter/fixtures/nodes/temperature-sensor.json @@ -83,5 +83,6 @@ "1/1026/65529": [], "1/1026/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533] }, - "available": true + "available": true, + "attribute_subscriptions": [] } diff --git a/tests/components/matter/fixtures/nodes/window-covering_full.json b/tests/components/matter/fixtures/nodes/window-covering_full.json index 3ffd68bd319..feb75409526 100644 --- a/tests/components/matter/fixtures/nodes/window-covering_full.json +++ b/tests/components/matter/fixtures/nodes/window-covering_full.json @@ -264,5 +264,6 @@ 65528, 65529, 65531, 65532, 65533, 0, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 23, 26, 6 ] - } + }, + "attribute_subscriptions": [] } diff --git a/tests/components/matter/fixtures/nodes/window-covering_lift.json b/tests/components/matter/fixtures/nodes/window-covering_lift.json index cd200eb15f0..afc2a2f734f 100644 --- a/tests/components/matter/fixtures/nodes/window-covering_lift.json +++ b/tests/components/matter/fixtures/nodes/window-covering_lift.json @@ -245,5 +245,6 @@ "1/258/65528": [], "1/258/65529": [0, 1, 2, 5], "1/258/65531": [65528, 65529, 65531, 65532, 65533, 0, 1, 5, 7, 10, 13, 23] - } + }, + "attribute_subscriptions": [] } diff --git a/tests/components/matter/fixtures/nodes/window-covering_pa-lift.json b/tests/components/matter/fixtures/nodes/window-covering_pa-lift.json index 9214b9511be..8d3335bbd6c 100644 --- a/tests/components/matter/fixtures/nodes/window-covering_pa-lift.json +++ b/tests/components/matter/fixtures/nodes/window-covering_pa-lift.json @@ -349,5 +349,6 @@ 0, 1, 3, 5, 7, 8, 10, 11, 13, 14, 16, 17, 23, 65528, 65529, 65531, 65532, 65533 ] - } + }, + "attribute_subscriptions": [] } diff --git a/tests/components/matter/fixtures/nodes/window-covering_pa-tilt.json b/tests/components/matter/fixtures/nodes/window-covering_pa-tilt.json index d432d7cf50a..44347dbd964 100644 --- a/tests/components/matter/fixtures/nodes/window-covering_pa-tilt.json +++ b/tests/components/matter/fixtures/nodes/window-covering_pa-tilt.json @@ -250,5 +250,6 @@ "1/258/65531": [ 65528, 65529, 65531, 65532, 65533, 0, 7, 9, 10, 12, 13, 15, 23, 6 ] - } + }, + "attribute_subscriptions": [] } diff --git a/tests/components/matter/fixtures/nodes/window-covering_tilt.json b/tests/components/matter/fixtures/nodes/window-covering_tilt.json index 1fe0d99ac38..a33e0f24c3f 100644 --- a/tests/components/matter/fixtures/nodes/window-covering_tilt.json +++ b/tests/components/matter/fixtures/nodes/window-covering_tilt.json @@ -245,5 +245,6 @@ "1/258/65528": [], "1/258/65529": [0, 1, 2, 8], "1/258/65531": [65528, 65529, 65531, 65532, 65533, 0, 7, 10, 13, 23, 6] - } + }, + "attribute_subscriptions": [] } diff --git a/tests/components/matter/test_adapter.py b/tests/components/matter/test_adapter.py index 9c37033dd4f..0d7cfc9c2ed 100644 --- a/tests/components/matter/test_adapter.py +++ b/tests/components/matter/test_adapter.py @@ -136,7 +136,7 @@ async def test_node_added_subscription( integration: MagicMock, ) -> None: """Test subscription to new devices work.""" - assert matter_client.subscribe.call_count == 1 + assert matter_client.subscribe.call_count == 4 assert matter_client.subscribe.call_args[0][1] == EventType.NODE_ADDED node_added_callback = matter_client.subscribe.call_args[0][0] diff --git a/tests/components/matter/test_init.py b/tests/components/matter/test_init.py index 58d0d0d445b..bbe77b76af5 100644 --- a/tests/components/matter/test_init.py +++ b/tests/components/matter/test_init.py @@ -168,7 +168,7 @@ async def test_listen_failure_config_entry_not_loaded( matter_client.connect.side_effect = MatterError("Boom") raise error - async def get_nodes() -> list[MagicMock]: + def get_nodes() -> list[MagicMock]: """Mock the client get_nodes method.""" listen_block.set() return []