From 35193b6fc2741d3d40b4b648e49a761a62c3de95 Mon Sep 17 00:00:00 2001 From: petretiandrea Date: Mon, 27 Nov 2023 21:25:21 +0100 Subject: [PATCH 1/3] support p125m as power monitor (#623) --- custom_components/tapo/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/tapo/const.py b/custom_components/tapo/const.py index 7563960..f8f5c38 100755 --- a/custom_components/tapo/const.py +++ b/custom_components/tapo/const.py @@ -22,7 +22,7 @@ "tp15", "p100m", ] -SUPPORTED_DEVICE_AS_SWITCH_POWER_MONITOR = ["p110", "p115", "p110m"] +SUPPORTED_DEVICE_AS_SWITCH_POWER_MONITOR = ["p110", "p115", "p110m", "p125m"] SUPPORTED_DEVICE_AS_LIGHT = { "l920": [ColorMode.ONOFF, ColorMode.BRIGHTNESS, ColorMode.HS], "l930": [ColorMode.ONOFF, ColorMode.BRIGHTNESS, ColorMode.COLOR_TEMP, ColorMode.HS], From f372c56ced01ceb1ec9e2a212708bbbcbcbed730 Mon Sep 17 00:00:00 2001 From: petretiandrea Date: Mon, 27 Nov 2023 21:42:41 +0100 Subject: [PATCH 2/3] Feature/support s505d (#624) --- custom_components/tapo/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/tapo/const.py b/custom_components/tapo/const.py index f8f5c38..8f97f3c 100755 --- a/custom_components/tapo/const.py +++ b/custom_components/tapo/const.py @@ -17,7 +17,6 @@ "p115", "p125", "p125m", - "s500", "p110m", "tp15", "p100m", @@ -35,6 +34,7 @@ "tl33": [ColorMode.ONOFF, ColorMode.BRIGHTNESS, ColorMode.COLOR_TEMP, ColorMode.HS], "tl31": [ColorMode.ONOFF, ColorMode.BRIGHTNESS, ColorMode.COLOR_TEMP], "s500d": [ColorMode.ONOFF, ColorMode.BRIGHTNESS], + "s505d": [ColorMode.ONOFF, ColorMode.BRIGHTNESS], "s500": [ColorMode.ONOFF, ColorMode.BRIGHTNESS], "s505": [ColorMode.ONOFF], "ts15": [ColorMode.ONOFF], From e56c7c728b7ebec7fa559813bf5992938bd974b5 Mon Sep 17 00:00:00 2001 From: petretiandrea Date: Mon, 27 Nov 2023 21:44:58 +0100 Subject: [PATCH 3/3] bump to plugp100 3.14 (#621) --- .github/workflows/validation.yml | 47 +++++++-------- custom_components/tapo/hub/tapo_hub.py | 60 ++++++++++--------- tests/unit/hub/test_tapo_hub.py | 80 +++++++++++++++++++------- 3 files changed, 118 insertions(+), 69 deletions(-) diff --git a/.github/workflows/validation.yml b/.github/workflows/validation.yml index 12140d7..737f9b3 100644 --- a/.github/workflows/validation.yml +++ b/.github/workflows/validation.yml @@ -13,7 +13,7 @@ on: - dev env: - DEFAULT_PYTHON: 3.9 + DEFAULT_PYTHON: 3.11 jobs: pre-commit: @@ -52,25 +52,26 @@ jobs: with: category: "integration" - # tests: - # runs-on: "ubuntu-latest" - # name: Run tests - # steps: - # - name: Check out code from GitHub - # uses: "actions/checkout@v3.0.2" - # - name: Setup Python ${{ env.DEFAULT_PYTHON }} - # uses: "actions/setup-python@v3.1.2" - # with: - # python-version: ${{ env.DEFAULT_PYTHON }} - # - name: Install requirements - # run: | - # pip install --constraint=.github/workflows/constraints.txt pip - # pip install -r requirements_test.txt - # - name: Tests suite - # run: | - # pytest \ - # --timeout=9 \ - # --durations=10 \ - # -n auto \ - # -p no:sugar \ - # tests + tests: + runs-on: "ubuntu-latest" + name: Run tests + steps: + - name: Check out code from GitHub + uses: actions/checkout@v4.1.1 + - name: Setup Python ${{ env.DEFAULT_PYTHON }} + uses: actions/setup-python@v4.7.1 + with: + python-version: ${{ env.DEFAULT_PYTHON }} + - name: Install requirements + run: | + pip install --constraint=.github/workflows/constraints.txt pip + pip install -r requirements_test.txt + - name: Unit tests suite + run: | + pytest \ + --timeout=9 \ + --durations=10 \ + --cov-fail-under=40 \ + -n auto \ + -p no:sugar \ + tests/unit diff --git a/custom_components/tapo/hub/tapo_hub.py b/custom_components/tapo/hub/tapo_hub.py index d1dc63c..1602fb6 100644 --- a/custom_components/tapo/hub/tapo_hub.py +++ b/custom_components/tapo/hub/tapo_hub.py @@ -21,12 +21,12 @@ from plugp100.api.hub.hub_device import HubDevice from plugp100.api.hub.hub_device_tracker import DeviceAdded from plugp100.api.hub.hub_device_tracker import HubDeviceEvent +from plugp100.api.hub.ke100_device import KE100Device from plugp100.api.hub.s200b_device import S200ButtonDevice from plugp100.api.hub.switch_child_device import SwitchChildDevice from plugp100.api.hub.t100_device import T100MotionSensor from plugp100.api.hub.t110_device import T110SmartDoor from plugp100.api.hub.t31x_device import T31Device -from plugp100.api.hub.ke100_device import KE100Device from plugp100.responses.device_state import DeviceInfo from plugp100.responses.hub_childs.hub_child_base_info import HubChildBaseInfo @@ -60,9 +60,13 @@ async def initialize_hub(self, hass: HomeAssistant): device_list = ( (await self.hub.get_children()).get_or_else([]).get_children_base_info() ) - await self.setup_child_devices(hass, registry, device_list) - child_coordinators = await self.setup_child_coordinators( - hass, device_list, polling_rate + _LOGGER.info( + "Found %d children associated to hub %s", + len(device_list), + device_info.device_id, + ) + child_coordinators = await self.setup_children( + hass, registry, device_list, polling_rate ) hass.data[DOMAIN][self.entry.entry_id] = HassTapoDeviceData( coordinator=hub_coordinator, @@ -89,42 +93,46 @@ async def _handle_child_device_event(event: HubDeviceEvent): await hass.config_entries.async_forward_entry_setups(self.entry, HUB_PLATFORMS) return True - async def setup_child_devices( + async def setup_children( self, hass: HomeAssistant, registry: DeviceRegistry, device_list: list[HubChildBaseInfo], - ): - knwon_children = [ - self.add_child_device(registry, device_state).id - for device_state in device_list + polling_rate: timedelta, + ) -> List[TapoHubChildCoordinator]: + setup_results = [ + await self._add_hass_tapo_child_device( + hass, registry, child_device, polling_rate + ) + for child_device in device_list ] + device_entries, child_coordinators = zip(*setup_results) # delete device which is no longer available to hub for device in dr.async_entries_for_config_entry(registry, self.entry.entry_id): # avoid delete hub device which has a connection - if device.id not in knwon_children and len(device.connections) == 0: + if ( + device.id not in map(lambda x: x.id, device_entries) + and len(device.connections) == 0 + ): registry.async_remove_device(device.id) - async def setup_child_coordinators( + return child_coordinators + + async def _add_hass_tapo_child_device( self, hass: HomeAssistant, - device_list: list[HubChildBaseInfo], + registry: DeviceRegistry, + device_state: HubChildBaseInfo, polling_rate: timedelta, - ) -> List[TapoHubChildCoordinator]: - child_coordinators = [] - for child in device_list: - child_device = _create_child_device(child, self.hub) - if child_device is not None: - child_coordinator = TapoHubChildCoordinator( - hass, child_device, polling_rate - ) - await child_coordinator.async_config_entry_first_refresh() - child_coordinators.append(child_coordinator) - - return child_coordinators - - def add_child_device( + ) -> (DeviceEntry, TapoHubChildCoordinator): + entry = self._hass_add_child_device(registry, device_state) + child_device = _create_child_device(device_state, self.hub) + coordinator = TapoHubChildCoordinator(hass, child_device, polling_rate) + await coordinator.async_config_entry_first_refresh() + return (entry, coordinator) + + def _hass_add_child_device( self, registry: DeviceRegistry, device_state: HubChildBaseInfo ) -> DeviceEntry: return registry.async_get_or_create( diff --git a/tests/unit/hub/test_tapo_hub.py b/tests/unit/hub/test_tapo_hub.py index be15f92..ac72080 100644 --- a/tests/unit/hub/test_tapo_hub.py +++ b/tests/unit/hub/test_tapo_hub.py @@ -1,28 +1,27 @@ -import pytest from datetime import timedelta - from unittest.mock import Mock from unittest.mock import patch +import pytest +from custom_components.tapo.hub.tapo_hub import TapoHub from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant - +from homeassistant.helpers.device_registry import DeviceRegistry from plugp100.api.hub.hub_device import HubDevice -from plugp100.responses.hub_childs.hub_child_base_info import HubChildBaseInfo from plugp100.api.hub.ke100_device import KE100Device from plugp100.api.hub.s200b_device import S200ButtonDevice from plugp100.api.hub.switch_child_device import SwitchChildDevice from plugp100.api.hub.t100_device import T100MotionSensor from plugp100.api.hub.t110_device import T110SmartDoor from plugp100.api.hub.t31x_device import T31Device - -from custom_components.tapo.hub.tapo_hub import TapoHub +from plugp100.responses.hub_childs.hub_child_base_info import HubChildBaseInfo class TestTapoHub: config = Mock(ConfigEntry) hub_device = Mock(HubDevice) hass = Mock(HomeAssistant) + registry = Mock(DeviceRegistry) polling_rate = Mock(timedelta) @pytest.mark.asyncio @@ -44,17 +43,58 @@ async def test_setup_child_coordinators_should_create_correct_types( with patch( "homeassistant.helpers.update_coordinator.DataUpdateCoordinator.async_config_entry_first_refresh" ): - base_child_info = Mock(HubChildBaseInfo) - base_child_info.model = model - base_child_info.device_id = "123ABC" - - hub = TapoHub(entry=self.config, hub=self.hub_device) - result = await hub.setup_child_coordinators( - hass=self.hass, - device_list=[base_child_info], - polling_rate=self.polling_rate, - ) - - assert len(result) == 1 - assert type(result[0].device) == expected_type - print(result[0].device) + with patch( + "homeassistant.helpers.device_registry.DeviceRegistry.async_get_or_create" + ): + with patch( + "homeassistant.helpers.device_registry.async_entries_for_config_entry" + ): + base_child_info = Mock(HubChildBaseInfo) + base_child_info.model = model + base_child_info.device_id = "123ABC" + base_child_info.nickname = "123ABC" + base_child_info.firmware_version = "1.2.3" + base_child_info.hardware_version = "hw1.0" + + hub = TapoHub(entry=self.config, hub=self.hub_device) + result = await hub.setup_children( + hass=self.hass, + registry=self.registry, + device_list=[base_child_info], + polling_rate=self.polling_rate, + ) + + assert len(result) == 1 + assert type(result[0].device) is expected_type + + @pytest.mark.asyncio + @pytest.mark.parametrize("children_count", [10, 50, 55, 101]) + async def test_setup_all_children(self, children_count: int): + with patch( + "homeassistant.helpers.update_coordinator.DataUpdateCoordinator.async_config_entry_first_refresh" + ): + with patch( + "homeassistant.helpers.device_registry.DeviceRegistry.async_get_or_create" + ): + with patch( + "homeassistant.helpers.device_registry.async_entries_for_config_entry" + ): + children = [] + for i in range(0, children_count): + mock = Mock(HubChildBaseInfo) + mock.model = "T110" + mock.device_id = f"123ABC{i}" + mock.nickname = f"123ABC{i}" + mock.firmware_version = f"1.2.{i}" + mock.hardware_version = f"hw{i}.0" + children.append(mock) + + hub = TapoHub(entry=self.config, hub=self.hub_device) + result = await hub.setup_children( + hass=self.hass, + registry=self.registry, + device_list=children, + polling_rate=self.polling_rate, + ) + + assert len(result) == children_count