From 78146d4e7770cd12b96a9df35c1bba69d9677b0e Mon Sep 17 00:00:00 2001 From: richard tristan <84513107+Richardtristan@users.noreply.github.com> Date: Sat, 24 Oct 2026 18:16:16 +0200 Subject: [PATCH] Keep hub children when the child component list is missing (#1797) Fixes #1796 Some hubs intermittently return a `getChildDeviceComponentList` result without the `child_component_list` key, as seen with an H200 (firmware 1.7.7 Build 20260917). This changes the logic, so that when the list is missing, children are neither created nor deleted for that update, and a debug message is logged. Treating the response as an empty list would delete the known children of a hub that has some, so they are kept until the next complete response. --- kasa/smart/smartdevice.py | 18 ++++++++++++++++-- tests/smart/test_smartdevice.py | 27 +++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/kasa/smart/smartdevice.py b/kasa/smart/smartdevice.py index 6be2392c..e6402131 100644 --- a/kasa/smart/smartdevice.py +++ b/kasa/smart/smartdevice.py @@ -109,9 +109,23 @@ class SmartDevice(Device): can't be created to avoid spamming the logs on every update. """ changed = False + if ( + child_component_list := child_device_components_resp.get( + "child_component_list" + ) + ) is None: + # Some hubs intermittently return the component list without the + # list itself, e.g. an H200 without children. Keep the known children + # instead of deleting them, the next update will reconcile. + _LOGGER.debug( + "No child component list for device %s, response: %s", + self.host, + child_device_components_resp, + ) + return changed + smart_children_components = { - child["device_id"]: child - for child in child_device_components_resp["child_component_list"] + child["device_id"]: child for child in child_component_list } children = self._children child_ids: set[str] = set() diff --git a/tests/smart/test_smartdevice.py b/tests/smart/test_smartdevice.py index aea73381..f497b5d1 100644 --- a/tests/smart/test_smartdevice.py +++ b/tests/smart/test_smartdevice.py @@ -876,6 +876,19 @@ async def test_dynamic_devices(dev: Device, caplog: pytest.LogCaptureFixture) -> expected_new_length -= 1 assert len(dev.children) == expected_new_length + # Test component list response without the list keeps the known children + + saved_child_device_components = mock_child_device_components + mock_child_device_components = {} + + with patch.object( + transport, "get_child_device_queries", side_effect=mock_get_child_device_queries + ): + await dev.update() + + assert len(dev.children) == expected_new_length + mock_child_device_components = saved_child_device_components + # Test no child devices mock_child_device_info["child_device_list"] = [] @@ -890,6 +903,20 @@ async def test_dynamic_devices(dev: Device, caplog: pytest.LogCaptureFixture) -> assert len(dev.children) == 0 + # Test component list response without the list on a hub without children, + # as returned intermittently by some hubs (e.g. H200 firmware 1.7.7) + + saved_child_device_components = mock_child_device_components + mock_child_device_components = {} + + with patch.object( + transport, "get_child_device_queries", side_effect=mock_get_child_device_queries + ): + await dev.update() + + assert len(dev.children) == 0 + mock_child_device_components = saved_child_device_components + # Logging tests are only for smartcam hubs as smart hubs do not test categories if not isinstance(dev, SmartCamDevice): return