From f8e273981c304c6aec9fb9b961aa9d40630bd07a Mon Sep 17 00:00:00 2001 From: Teemu R Date: Mon, 29 Jan 2024 18:14:30 +0100 Subject: [PATCH] Add P300 fixture (#717) * Add P300 fixture * fixture after update * Add tests for p300 --- README.md | 5 + kasa/tapo/__init__.py | 3 +- kasa/tapo/childdevice.py | 2 +- kasa/tests/conftest.py | 45 +- .../fixtures/smart/P300(EU)_1.0_1.0.13.json | 520 ++++++++++++++++++ .../fixtures/smart/P300(EU)_1.0_1.0.7.json | 512 +++++++++++++++++ kasa/tests/newfakes.py | 33 +- kasa/tests/test_discovery.py | 13 +- kasa/tests/test_smartprotocol.py | 29 - 9 files changed, 1123 insertions(+), 39 deletions(-) create mode 100644 kasa/tests/fixtures/smart/P300(EU)_1.0_1.0.13.json create mode 100644 kasa/tests/fixtures/smart/P300(EU)_1.0_1.0.7.json diff --git a/README.md b/README.md index 42b1c99d..8da1fcdf 100644 --- a/README.md +++ b/README.md @@ -303,6 +303,11 @@ At the moment, the following devices have been confirmed to work: * Tapo S500D * Tapo S505 +#### Power strips + +* Tapo P300 + + ### Newer Kasa branded devices Some newer hardware versions of Kasa branded devices are now using the same protocol as diff --git a/kasa/tapo/__init__.py b/kasa/tapo/__init__.py index eeb3670c..0fe4297e 100644 --- a/kasa/tapo/__init__.py +++ b/kasa/tapo/__init__.py @@ -1,6 +1,7 @@ """Package for supporting tapo-branded and newer kasa devices.""" +from .childdevice import ChildDevice from .tapobulb import TapoBulb from .tapodevice import TapoDevice from .tapoplug import TapoPlug -__all__ = ["TapoDevice", "TapoPlug", "TapoBulb"] +__all__ = ["TapoDevice", "TapoPlug", "TapoBulb", "ChildDevice"] diff --git a/kasa/tapo/childdevice.py b/kasa/tapo/childdevice.py index c1b108a3..7b66a79d 100644 --- a/kasa/tapo/childdevice.py +++ b/kasa/tapo/childdevice.py @@ -35,7 +35,7 @@ class ChildDevice(TapoDevice): return child raise SmartDeviceException( - f"Unable to find child device with position {self._id}" + f"Unable to find child device with id {self._id}" ) self._last_update = self._sys_info = self._info = _get_child_info() diff --git a/kasa/tests/conftest.py b/kasa/tests/conftest.py index 24bc3372..a058c47e 100644 --- a/kasa/tests/conftest.py +++ b/kasa/tests/conftest.py @@ -13,15 +13,18 @@ import pytest # type: ignore # see https://github.com/pytest-dev/pytest/issues/ from kasa import ( Credentials, + DeviceConfig, Discover, SmartBulb, SmartDevice, SmartDimmer, SmartLightStrip, SmartPlug, + SmartProtocol, SmartStrip, ) -from kasa.tapo import TapoBulb, TapoDevice, TapoPlug +from kasa.protocol import BaseTransport +from kasa.tapo import TapoBulb, TapoPlug from kasa.xortransport import XorEncryption from .newfakes import FakeSmartProtocol, FakeTransportProtocol @@ -107,7 +110,7 @@ PLUGS = { *PLUGS_SMART, } STRIPS_IOT = {"HS107", "HS300", "KP303", "KP200", "KP400", "EP40"} -STRIPS_SMART: Set[str] = set() +STRIPS_SMART = {"P300"} STRIPS = {*STRIPS_IOT, *STRIPS_SMART} DIMMERS_IOT = {"ES20M", "HS220", "KS220M", "KS230", "KP405"} @@ -210,7 +213,7 @@ no_emeter_iot = parametrize( bulb = parametrize("bulbs", BULBS, protocol_filter={"SMART", "IOT"}) plug = parametrize("plugs", PLUGS, protocol_filter={"IOT"}) -strip = parametrize("strips", STRIPS, protocol_filter={"IOT"}) +strip = parametrize("strips", STRIPS, protocol_filter={"SMART", "IOT"}) dimmer = parametrize("dimmers", DIMMERS, protocol_filter={"IOT"}) lightstrip = parametrize("lightstrips", LIGHT_STRIPS, protocol_filter={"IOT"}) @@ -238,6 +241,11 @@ variable_temp_iot = parametrize( ) bulb_iot = parametrize("bulb devices iot", BULBS_IOT, protocol_filter={"IOT"}) +strip_iot = parametrize("strip devices iot", STRIPS_IOT, protocol_filter={"IOT"}) +strip_smart = parametrize( + "strip devices smart", STRIPS_SMART, protocol_filter={"SMART"} +) + plug_smart = parametrize("plug devices smart", PLUGS_SMART, protocol_filter={"SMART"}) bulb_smart = parametrize("bulb devices smart", BULBS_SMART, protocol_filter={"SMART"}) dimmers_smart = parametrize( @@ -338,6 +346,9 @@ def device_for_file(model, protocol): for d in DIMMERS_SMART: if d in model: return TapoBulb + for d in STRIPS_SMART: + if d in model: + return TapoPlug else: for d in STRIPS_IOT: if d in model: @@ -556,6 +567,34 @@ def unsupported_device_info(request, mocker): yield discovery_data +@pytest.fixture() +def dummy_protocol(): + """Return a smart protocol instance with a mocking-ready dummy transport.""" + + class DummyTransport(BaseTransport): + @property + def default_port(self) -> int: + return -1 + + @property + def credentials_hash(self) -> str: + return "dummy hash" + + async def send(self, request: str) -> Dict: + return {} + + async def close(self) -> None: + pass + + async def reset(self) -> None: + pass + + transport = DummyTransport(config=DeviceConfig(host="127.0.0.123")) + protocol = SmartProtocol(transport=transport) + + return protocol + + def pytest_addoption(parser): parser.addoption( "--ip", action="store", default=None, help="run against device on given ip" diff --git a/kasa/tests/fixtures/smart/P300(EU)_1.0_1.0.13.json b/kasa/tests/fixtures/smart/P300(EU)_1.0_1.0.13.json new file mode 100644 index 00000000..4c4402bc --- /dev/null +++ b/kasa/tests/fixtures/smart/P300(EU)_1.0_1.0.13.json @@ -0,0 +1,520 @@ +{ + "component_nego": { + "component_list": [ + { + "id": "device", + "ver_code": 2 + }, + { + "id": "firmware", + "ver_code": 2 + }, + { + "id": "quick_setup", + "ver_code": 3 + }, + { + "id": "time", + "ver_code": 1 + }, + { + "id": "wireless", + "ver_code": 1 + }, + { + "id": "schedule", + "ver_code": 2 + }, + { + "id": "countdown", + "ver_code": 2 + }, + { + "id": "antitheft", + "ver_code": 1 + }, + { + "id": "account", + "ver_code": 1 + }, + { + "id": "synchronize", + "ver_code": 1 + }, + { + "id": "sunrise_sunset", + "ver_code": 1 + }, + { + "id": "led", + "ver_code": 1 + }, + { + "id": "cloud_connect", + "ver_code": 1 + }, + { + "id": "iot_cloud", + "ver_code": 1 + }, + { + "id": "device_local_time", + "ver_code": 1 + }, + { + "id": "default_states", + "ver_code": 1 + }, + { + "id": "control_child", + "ver_code": 2 + }, + { + "id": "child_device", + "ver_code": 2 + }, + { + "id": "homekit", + "ver_code": 2 + }, + { + "id": "overheat_protection", + "ver_code": 1 + } + ] + }, + "discovery_result": { + "device_id": "00000000000000000000000000000000", + "device_model": "P300(EU)", + "device_type": "SMART.TAPOPLUG", + "factory_default": false, + "ip": "127.0.0.123", + "is_support_iot_cloud": true, + "mac": "78-8C-B5-00-00-00", + "mgt_encrypt_schm": { + "encrypt_type": "KLAP", + "http_port": 80, + "is_support_https": false, + "lv": 2 + }, + "obd_src": "tplink", + "owner": "00000000000000000000000000000000" + }, + "get_auto_update_info": { + "enable": false, + "random_range": 120, + "time": 180 + }, + "get_child_device_component_list": { + "child_component_list": [ + { + "component_list": [ + { + "id": "device", + "ver_code": 2 + }, + { + "id": "quick_setup", + "ver_code": 3 + }, + { + "id": "time", + "ver_code": 1 + }, + { + "id": "device_local_time", + "ver_code": 1 + }, + { + "id": "schedule", + "ver_code": 2 + }, + { + "id": "countdown", + "ver_code": 2 + }, + { + "id": "antitheft", + "ver_code": 1 + }, + { + "id": "account", + "ver_code": 1 + }, + { + "id": "synchronize", + "ver_code": 1 + }, + { + "id": "sunrise_sunset", + "ver_code": 1 + }, + { + "id": "cloud_connect", + "ver_code": 1 + }, + { + "id": "iot_cloud", + "ver_code": 1 + }, + { + "id": "firmware", + "ver_code": 2 + }, + { + "id": "default_states", + "ver_code": 1 + }, + { + "id": "overheat_protection", + "ver_code": 1 + } + ], + "device_id": "000000000000000000000000000000000000000000" + }, + { + "component_list": [ + { + "id": "device", + "ver_code": 2 + }, + { + "id": "quick_setup", + "ver_code": 3 + }, + { + "id": "time", + "ver_code": 1 + }, + { + "id": "device_local_time", + "ver_code": 1 + }, + { + "id": "schedule", + "ver_code": 2 + }, + { + "id": "countdown", + "ver_code": 2 + }, + { + "id": "antitheft", + "ver_code": 1 + }, + { + "id": "account", + "ver_code": 1 + }, + { + "id": "synchronize", + "ver_code": 1 + }, + { + "id": "sunrise_sunset", + "ver_code": 1 + }, + { + "id": "cloud_connect", + "ver_code": 1 + }, + { + "id": "iot_cloud", + "ver_code": 1 + }, + { + "id": "firmware", + "ver_code": 2 + }, + { + "id": "default_states", + "ver_code": 1 + }, + { + "id": "overheat_protection", + "ver_code": 1 + } + ], + "device_id": "000000000000000000000000000000000000000000" + }, + { + "component_list": [ + { + "id": "device", + "ver_code": 2 + }, + { + "id": "quick_setup", + "ver_code": 3 + }, + { + "id": "time", + "ver_code": 1 + }, + { + "id": "device_local_time", + "ver_code": 1 + }, + { + "id": "schedule", + "ver_code": 2 + }, + { + "id": "countdown", + "ver_code": 2 + }, + { + "id": "antitheft", + "ver_code": 1 + }, + { + "id": "account", + "ver_code": 1 + }, + { + "id": "synchronize", + "ver_code": 1 + }, + { + "id": "sunrise_sunset", + "ver_code": 1 + }, + { + "id": "cloud_connect", + "ver_code": 1 + }, + { + "id": "iot_cloud", + "ver_code": 1 + }, + { + "id": "firmware", + "ver_code": 2 + }, + { + "id": "default_states", + "ver_code": 1 + }, + { + "id": "overheat_protection", + "ver_code": 1 + } + ], + "device_id": "000000000000000000000000000000000000000000" + } + ], + "start_index": 0, + "sum": 3 + }, + "get_child_device_list": { + "child_device_list": [ + { + "avatar": "", + "bind_count": 1, + "category": "plug.powerstrip.sub-plug", + "default_states": { + "state": { + "on": false + }, + "type": "custom" + }, + "device_id": "000000000000000000000000000000000000000000", + "device_on": false, + "fw_id": "00000000000000000000000000000000", + "fw_ver": "1.0.13 Build 230925 Rel.150200", + "has_set_location_info": true, + "hw_id": "00000000000000000000000000000000", + "hw_ver": "1.0", + "latitude": 0, + "longitude": 0, + "mac": "788CB5000000", + "model": "P300", + "nickname": "I01BU0tFRF9OQU1FIw==", + "oem_id": "00000000000000000000000000000000", + "on_time": 0, + "original_device_id": "8022852468EC205A8178C7CBE81FC119213BC020", + "overheat_status": "normal", + "position": 3, + "region": "Europe/Berlin", + "slot_number": 3, + "status_follow_edge": true, + "type": "SMART.TAPOPLUG" + }, + { + "avatar": "", + "bind_count": 1, + "category": "plug.powerstrip.sub-plug", + "default_states": { + "type": "last_states" + }, + "device_id": "000000000000000000000000000000000000000000", + "device_on": false, + "fw_id": "00000000000000000000000000000000", + "fw_ver": "1.0.13 Build 230925 Rel.150200", + "has_set_location_info": true, + "hw_id": "00000000000000000000000000000000", + "hw_ver": "1.0", + "latitude": 0, + "longitude": 0, + "mac": "788CB5000000", + "model": "P300", + "nickname": "I01BU0tFRF9OQU1FIw==", + "oem_id": "00000000000000000000000000000000", + "on_time": 0, + "original_device_id": "8022852468EC205A8178C7CBE81FC119213BC020", + "overheat_status": "normal", + "position": 2, + "region": "Europe/Berlin", + "slot_number": 3, + "status_follow_edge": true, + "type": "SMART.TAPOPLUG" + }, + { + "avatar": "", + "bind_count": 1, + "category": "plug.powerstrip.sub-plug", + "default_states": { + "state": { + "on": true + }, + "type": "custom" + }, + "device_id": "000000000000000000000000000000000000000000", + "device_on": false, + "fw_id": "00000000000000000000000000000000", + "fw_ver": "1.0.13 Build 230925 Rel.150200", + "has_set_location_info": true, + "hw_id": "00000000000000000000000000000000", + "hw_ver": "1.0", + "latitude": 0, + "longitude": 0, + "mac": "788CB5000000", + "model": "P300", + "nickname": "I01BU0tFRF9OQU1FIw==", + "oem_id": "00000000000000000000000000000000", + "on_time": 0, + "original_device_id": "8022852468EC205A8178C7CBE81FC119213BC020", + "overheat_status": "normal", + "position": 1, + "region": "Europe/Berlin", + "slot_number": 3, + "status_follow_edge": true, + "type": "SMART.TAPOPLUG" + } + ], + "start_index": 0, + "sum": 3 + }, + "get_connect_cloud_state": { + "status": 0 + }, + "get_device_info": { + "avatar": "", + "device_id": "0000000000000000000000000000000000000000", + "fw_id": "00000000000000000000000000000000", + "fw_ver": "1.0.13 Build 230925 Rel.150200", + "has_set_location_info": true, + "hw_id": "00000000000000000000000000000000", + "hw_ver": "1.0", + "ip": "127.0.0.123", + "lang": "de_DE", + "latitude": 0, + "longitude": 0, + "mac": "78-8C-B5-00-00-00", + "model": "P300", + "nickname": "I01BU0tFRF9OQU1FIw==", + "oem_id": "00000000000000000000000000000000", + "region": "Europe/Berlin", + "rssi": -62, + "signal_level": 2, + "specs": "", + "ssid": "I01BU0tFRF9TU0lEIw==", + "time_diff": 60, + "type": "SMART.TAPOPLUG" + }, + "get_device_time": { + "region": "Europe/Berlin", + "time_diff": 60, + "timestamp": 1706320181 + }, + "get_device_usage": { + "time_usage": { + "past30": 0, + "past7": 0, + "today": 0 + } + }, + "get_fw_download_state": { + "auto_upgrade": false, + "download_progress": 0, + "reboot_time": 5, + "status": 0, + "upgrade_time": 5 + }, + "get_latest_fw": { + "fw_size": 0, + "fw_ver": "1.0.13 Build 230925 Rel.150200", + "hw_id": "", + "need_to_upgrade": false, + "oem_id": "", + "release_date": "", + "release_note": "", + "type": 0 + }, + "get_led_info": { + "led_rule": "night_mode", + "led_status": false, + "night_mode": { + "end_time": 496, + "night_mode_type": "sunrise_sunset", + "start_time": 1034, + "sunrise_offset": 0, + "sunset_offset": 0 + } + }, + "get_wireless_scan_info": { + "ap_list": [], + "wep_supported": false + }, + "qs_component_nego": { + "component_list": [ + { + "id": "quick_setup", + "ver_code": 3 + }, + { + "id": "sunrise_sunset", + "ver_code": 1 + }, + { + "id": "ble_whole_setup", + "ver_code": 1 + }, + { + "id": "iot_cloud", + "ver_code": 1 + }, + { + "id": "inherit", + "ver_code": 1 + }, + { + "id": "firmware", + "ver_code": 2 + }, + { + "id": "control_child", + "ver_code": 2 + }, + { + "id": "child_device", + "ver_code": 2 + } + ], + "extra_info": { + "device_model": "P300", + "device_type": "SMART.TAPOPLUG", + "is_klap": true + } + } +} diff --git a/kasa/tests/fixtures/smart/P300(EU)_1.0_1.0.7.json b/kasa/tests/fixtures/smart/P300(EU)_1.0_1.0.7.json new file mode 100644 index 00000000..1cd39621 --- /dev/null +++ b/kasa/tests/fixtures/smart/P300(EU)_1.0_1.0.7.json @@ -0,0 +1,512 @@ +{ + "component_nego": { + "component_list": [ + { + "id": "device", + "ver_code": 2 + }, + { + "id": "firmware", + "ver_code": 2 + }, + { + "id": "quick_setup", + "ver_code": 3 + }, + { + "id": "time", + "ver_code": 1 + }, + { + "id": "wireless", + "ver_code": 1 + }, + { + "id": "schedule", + "ver_code": 2 + }, + { + "id": "countdown", + "ver_code": 2 + }, + { + "id": "antitheft", + "ver_code": 1 + }, + { + "id": "account", + "ver_code": 1 + }, + { + "id": "synchronize", + "ver_code": 1 + }, + { + "id": "sunrise_sunset", + "ver_code": 1 + }, + { + "id": "led", + "ver_code": 1 + }, + { + "id": "cloud_connect", + "ver_code": 1 + }, + { + "id": "iot_cloud", + "ver_code": 1 + }, + { + "id": "device_local_time", + "ver_code": 1 + }, + { + "id": "default_states", + "ver_code": 1 + }, + { + "id": "control_child", + "ver_code": 2 + }, + { + "id": "child_device", + "ver_code": 2 + }, + { + "id": "homekit", + "ver_code": 1 + }, + { + "id": "overheat_protection", + "ver_code": 1 + } + ] + }, + "discovery_result": { + "device_id": "00000000000000000000000000000000", + "device_model": "P300(EU)", + "device_type": "SMART.TAPOPLUG", + "factory_default": false, + "ip": "127.0.0.123", + "is_support_iot_cloud": true, + "mac": "78-8C-B5-00-00-00", + "mgt_encrypt_schm": { + "encrypt_type": "AES", + "http_port": 80, + "is_support_https": false, + "lv": 2 + }, + "obd_src": "tplink", + "owner": "00000000000000000000000000000000" + }, + "get_auto_update_info": { + "enable": true, + "random_range": 120, + "time": 180 + }, + "get_child_device_component_list": { + "child_component_list": [ + { + "component_list": [ + { + "id": "device", + "ver_code": 2 + }, + { + "id": "quick_setup", + "ver_code": 3 + }, + { + "id": "time", + "ver_code": 1 + }, + { + "id": "device_local_time", + "ver_code": 1 + }, + { + "id": "schedule", + "ver_code": 2 + }, + { + "id": "countdown", + "ver_code": 2 + }, + { + "id": "antitheft", + "ver_code": 1 + }, + { + "id": "account", + "ver_code": 1 + }, + { + "id": "synchronize", + "ver_code": 1 + }, + { + "id": "sunrise_sunset", + "ver_code": 1 + }, + { + "id": "cloud_connect", + "ver_code": 1 + }, + { + "id": "iot_cloud", + "ver_code": 1 + }, + { + "id": "firmware", + "ver_code": 2 + }, + { + "id": "default_states", + "ver_code": 1 + }, + { + "id": "overheat_protection", + "ver_code": 1 + } + ], + "device_id": "000000000000000000000000000000000000000000" + }, + { + "component_list": [ + { + "id": "device", + "ver_code": 2 + }, + { + "id": "quick_setup", + "ver_code": 3 + }, + { + "id": "time", + "ver_code": 1 + }, + { + "id": "device_local_time", + "ver_code": 1 + }, + { + "id": "schedule", + "ver_code": 2 + }, + { + "id": "countdown", + "ver_code": 2 + }, + { + "id": "antitheft", + "ver_code": 1 + }, + { + "id": "account", + "ver_code": 1 + }, + { + "id": "synchronize", + "ver_code": 1 + }, + { + "id": "sunrise_sunset", + "ver_code": 1 + }, + { + "id": "cloud_connect", + "ver_code": 1 + }, + { + "id": "iot_cloud", + "ver_code": 1 + }, + { + "id": "firmware", + "ver_code": 2 + }, + { + "id": "default_states", + "ver_code": 1 + }, + { + "id": "overheat_protection", + "ver_code": 1 + } + ], + "device_id": "000000000000000000000000000000000000000000" + }, + { + "component_list": [ + { + "id": "device", + "ver_code": 2 + }, + { + "id": "quick_setup", + "ver_code": 3 + }, + { + "id": "time", + "ver_code": 1 + }, + { + "id": "device_local_time", + "ver_code": 1 + }, + { + "id": "schedule", + "ver_code": 2 + }, + { + "id": "countdown", + "ver_code": 2 + }, + { + "id": "antitheft", + "ver_code": 1 + }, + { + "id": "account", + "ver_code": 1 + }, + { + "id": "synchronize", + "ver_code": 1 + }, + { + "id": "sunrise_sunset", + "ver_code": 1 + }, + { + "id": "cloud_connect", + "ver_code": 1 + }, + { + "id": "iot_cloud", + "ver_code": 1 + }, + { + "id": "firmware", + "ver_code": 2 + }, + { + "id": "default_states", + "ver_code": 1 + }, + { + "id": "overheat_protection", + "ver_code": 1 + } + ], + "device_id": "000000000000000000000000000000000000000000" + } + ], + "start_index": 0, + "sum": 3 + }, + "get_child_device_list": { + "child_device_list": [ + { + "avatar": "", + "bind_count": 1, + "category": "plug.powerstrip.sub-plug", + "default_states": { + "type": "last_states" + }, + "device_id": "000000000000000000000000000000000000000000", + "device_on": true, + "fw_id": "00000000000000000000000000000000", + "fw_ver": "1.0.7 Build 220715 Rel.200458", + "has_set_location_info": true, + "hw_id": "00000000000000000000000000000000", + "hw_ver": "1.0", + "latitude": 0, + "longitude": 0, + "mac": "788CB5000000", + "model": "P300", + "nickname": "I01BU0tFRF9OQU1FIw==", + "oem_id": "00000000000000000000000000000000", + "on_time": 366, + "original_device_id": "8022852468EC205A8178C7CBE81FC119213BC020", + "overheat_status": "normal", + "position": 1, + "region": "Europe/Berlin", + "slot_number": 3, + "status_follow_edge": true, + "type": "SMART.TAPOPLUG" + }, + { + "avatar": "", + "bind_count": 1, + "category": "plug.powerstrip.sub-plug", + "default_states": { + "type": "last_states" + }, + "device_id": "000000000000000000000000000000000000000000", + "device_on": true, + "fw_id": "00000000000000000000000000000000", + "fw_ver": "1.0.7 Build 220715 Rel.200458", + "has_set_location_info": true, + "hw_id": "00000000000000000000000000000000", + "hw_ver": "1.0", + "latitude": 0, + "longitude": 0, + "mac": "788CB5000000", + "model": "P300", + "nickname": "I01BU0tFRF9OQU1FIw==", + "oem_id": "00000000000000000000000000000000", + "on_time": 366, + "original_device_id": "8022852468EC205A8178C7CBE81FC119213BC020", + "overheat_status": "normal", + "position": 2, + "region": "Europe/Berlin", + "slot_number": 3, + "status_follow_edge": true, + "type": "SMART.TAPOPLUG" + }, + { + "avatar": "", + "bind_count": 1, + "category": "plug.powerstrip.sub-plug", + "default_states": { + "type": "last_states" + }, + "device_id": "000000000000000000000000000000000000000000", + "device_on": true, + "fw_id": "00000000000000000000000000000000", + "fw_ver": "1.0.7 Build 220715 Rel.200458", + "has_set_location_info": true, + "hw_id": "00000000000000000000000000000000", + "hw_ver": "1.0", + "latitude": 0, + "longitude": 0, + "mac": "788CB5000000", + "model": "P300", + "nickname": "I01BU0tFRF9OQU1FIw==", + "oem_id": "00000000000000000000000000000000", + "on_time": 366, + "original_device_id": "8022852468EC205A8178C7CBE81FC119213BC020", + "overheat_status": "normal", + "position": 3, + "region": "Europe/Berlin", + "slot_number": 3, + "status_follow_edge": true, + "type": "SMART.TAPOPLUG" + } + ], + "start_index": 0, + "sum": 3 + }, + "get_connect_cloud_state": { + "status": 1 + }, + "get_device_info": { + "avatar": "", + "device_id": "0000000000000000000000000000000000000000", + "fw_id": "00000000000000000000000000000000", + "fw_ver": "1.0.7 Build 220715 Rel.200458", + "has_set_location_info": true, + "hw_id": "00000000000000000000000000000000", + "hw_ver": "1.0", + "ip": "127.0.0.123", + "lang": "", + "latitude": 0, + "longitude": 0, + "mac": "78-8C-B5-00-00-00", + "model": "P300", + "nickname": "I01BU0tFRF9OQU1FIw==", + "oem_id": "00000000000000000000000000000000", + "region": "Europe/Berlin", + "rssi": -58, + "signal_level": 2, + "specs": "", + "ssid": "I01BU0tFRF9TU0lEIw==", + "time_diff": 60, + "type": "SMART.TAPOPLUG" + }, + "get_device_time": { + "region": "Europe/Berlin", + "time_diff": 60, + "timestamp": 1706297406 + }, + "get_device_usage": { + "time_usage": { + "past30": 5, + "past7": 5, + "today": 5 + } + }, + "get_fw_download_state": { + "auto_upgrade": false, + "download_progress": 0, + "reboot_time": 5, + "status": 0, + "upgrade_time": 5 + }, + "get_led_info": { + "led_rule": "always", + "led_status": true, + "night_mode": { + "end_time": 497, + "night_mode_type": "sunrise_sunset", + "start_time": 1032, + "sunrise_offset": 0, + "sunset_offset": 0 + } + }, + "get_wireless_scan_info": { + "ap_list": [ + { + "bssid": "000000000000", + "channel": 0, + "cipher_type": 2, + "key_type": "wpa_psk", + "signal_level": 2, + "ssid": "I01BU0tFRF9TU0lEIw==" + } + ], + "wep_supported": false + }, + "qs_component_nego": { + "component_list": [ + { + "id": "quick_setup", + "ver_code": 3 + }, + { + "id": "sunrise_sunset", + "ver_code": 1 + }, + { + "id": "ble_whole_setup", + "ver_code": 1 + }, + { + "id": "iot_cloud", + "ver_code": 1 + }, + { + "id": "inherit", + "ver_code": 1 + }, + { + "id": "firmware", + "ver_code": 2 + }, + { + "id": "control_child", + "ver_code": 2 + }, + { + "id": "child_device", + "ver_code": 2 + } + ], + "extra_info": { + "device_model": "P300", + "device_type": "SMART.TAPOPLUG" + } + } +} diff --git a/kasa/tests/newfakes.py b/kasa/tests/newfakes.py index aa3d42be..d668f9ee 100644 --- a/kasa/tests/newfakes.py +++ b/kasa/tests/newfakes.py @@ -354,9 +354,36 @@ class FakeSmartTransport(BaseTransport): def _send_request(self, request_dict: dict): method = request_dict["method"] params = request_dict["params"] + + info = self.info + if method == "control_child": + device_id = params.get("device_id") + request_data = params.get("requestData") + + child_method = request_data.get("method") + child_params = request_data.get("params") + + children = info["get_child_device_list"]["child_device_list"] + + for child in children: + if child["device_id"] == device_id: + info = child + break + + # We only support get & set device info for now. + if child_method == "get_device_info": + return {"result": info, "error_code": 0} + elif child_method == "set_device_info": + info.update(child_params) + return {"error_code": 0} + + raise NotImplementedError( + "Method %s not implemented for children" % child_method + ) + if method == "component_nego" or method[:4] == "get_": - if method in self.info: - return {"result": self.info[method], "error_code": 0} + if method in info: + return {"result": info[method], "error_code": 0} elif ( missing_result := self.FIXTURE_MISSING_MAP.get(method) ) and missing_result[0] in self.components: @@ -373,7 +400,7 @@ class FakeSmartTransport(BaseTransport): return {"error_code": 0} elif method[:4] == "set_": target_method = f"get_{method[4:]}" - self.info[target_method].update(params) + info[target_method].update(params) return {"error_code": 0} async def close(self) -> None: diff --git a/kasa/tests/test_discovery.py b/kasa/tests/test_discovery.py index db4d8fc1..6ded034d 100644 --- a/kasa/tests/test_discovery.py +++ b/kasa/tests/test_discovery.py @@ -27,7 +27,16 @@ from kasa.discover import DiscoveryResult, _DiscoverProtocol, json_dumps from kasa.exceptions import AuthenticationException, UnsupportedDeviceException from kasa.xortransport import XorEncryption -from .conftest import bulb, bulb_iot, dimmer, lightstrip, new_discovery, plug, strip +from .conftest import ( + bulb, + bulb_iot, + dimmer, + lightstrip, + new_discovery, + plug, + strip, + strip_iot, +) UNSUPPORTED = { "result": { @@ -67,7 +76,7 @@ async def test_type_detection_bulb(dev: SmartDevice): assert d.device_type == DeviceType.Bulb -@strip +@strip_iot async def test_type_detection_strip(dev: SmartDevice): d = Discover._get_device_class(dev._last_update)("localhost") assert d.is_strip diff --git a/kasa/tests/test_smartprotocol.py b/kasa/tests/test_smartprotocol.py index 619caef0..5e212077 100644 --- a/kasa/tests/test_smartprotocol.py +++ b/kasa/tests/test_smartprotocol.py @@ -22,35 +22,6 @@ DUMMY_MULTIPLE_QUERY = { ERRORS = [e for e in SmartErrorCode if e != 0] -# TODO: this could be moved to conftest to make it available for other tests? -@pytest.fixture() -def dummy_protocol(): - """Return a smart protocol instance with a mocking-ready dummy transport.""" - - class DummyTransport(BaseTransport): - @property - def default_port(self) -> int: - return -1 - - @property - def credentials_hash(self) -> str: - return "dummy hash" - - async def send(self, request: str) -> Dict: - return {} - - async def close(self) -> None: - pass - - async def reset(self) -> None: - pass - - transport = DummyTransport(config=DeviceConfig(host="127.0.0.123")) - protocol = SmartProtocol(transport=transport) - - return protocol - - @pytest.mark.parametrize("error_code", ERRORS, ids=lambda e: e.name) async def test_smart_device_errors(dummy_protocol, mocker, error_code): mock_response = {"result": {"great": "success"}, "error_code": error_code.value}