From e41ef83b08609e927f4ebc090602eecb6d86b7b4 Mon Sep 17 00:00:00 2001 From: sdb9696 Date: Tue, 28 Nov 2023 17:06:53 +0000 Subject: [PATCH 1/3] Fix dump_devinfo and add discovery_result to json --- devtools/dump_devinfo.py | 44 ++++++++++++------ kasa/discover.py | 2 +- kasa/smartdevice.py | 2 + kasa/tests/fixtures/HS100(UK)_1.0_1.2.6.json | 28 +++++++++++ kasa/tests/fixtures/HS100(UK)_4.1_1.1.0.json | 49 +++++++++++--------- kasa/tests/newfakes.py | 7 +-- 6 files changed, 90 insertions(+), 42 deletions(-) create mode 100644 kasa/tests/fixtures/HS100(UK)_1.0_1.2.6.json diff --git a/devtools/dump_devinfo.py b/devtools/dump_devinfo.py index eff3a7b60..49bfaf39b 100644 --- a/devtools/dump_devinfo.py +++ b/devtools/dump_devinfo.py @@ -7,7 +7,6 @@ Executing this script will several modules and methods one by one, and finally execute a query to query all of them at once. """ -import asyncio import collections.abc import json import logging @@ -15,9 +14,9 @@ from collections import defaultdict, namedtuple from pprint import pprint -import click +import asyncclick as click -from kasa import TPLinkSmartHomeProtocol +from kasa import Credentials, Discover Call = namedtuple("Call", "module method") @@ -35,6 +34,8 @@ def scrub(res): "longitude_i", "latitude", "longitude", + "device_owner_hash", + "device_id_hash", ] for k, v in res.items(): @@ -63,8 +64,22 @@ def default_to_regular(d): @click.command() @click.argument("host") +@click.option( + "--username", + default=None, + required=False, + envvar="TPLINK_CLOUD_USERNAME", + help="Username/email address to authenticate to device.", +) +@click.option( + "--password", + default=None, + required=False, + envvar="TPLINK_CLOUD_PASSWORD", + help="Password to use to authenticate to device.", +) @click.option("-d", "--debug", is_flag=True) -def cli(host, debug): +async def cli(host, debug, username, password): """Generate devinfo file for given device.""" if debug: logging.basicConfig(level=logging.DEBUG) @@ -83,15 +98,15 @@ def cli(host, debug): successes = [] - for test_call in items: - - async def _run_query(test_call): - protocol = TPLinkSmartHomeProtocol(host) - return await protocol.query({test_call.module: {test_call.method: None}}) + credentials = Credentials(username=username, password=password) + device = await Discover.discover_single(host, credentials=credentials) + for test_call in items: try: click.echo(f"Testing {test_call}..", nl=False) - info = asyncio.run(_run_query(test_call)) + info = await device.protocol.query( + {test_call.module: {test_call.method: None}} + ) resp = info[test_call.module] except Exception as ex: click.echo(click.style(f"FAIL {ex}", fg="red")) @@ -111,12 +126,8 @@ async def _run_query(test_call): final = default_to_regular(final) - async def _run_final_query(): - protocol = TPLinkSmartHomeProtocol(host) - return await protocol.query(final_query) - try: - final = asyncio.run(_run_final_query()) + final = await device.protocol.query(final_query) except Exception as ex: click.echo( click.style( @@ -124,6 +135,9 @@ async def _run_final_query(): ) ) + if device.discovery_info: + final["discovery_result"] = device.discovery_info + click.echo("Got %s successes" % len(successes)) click.echo(click.style("## device info file ##", bold=True)) diff --git a/kasa/discover.py b/kasa/discover.py index 2523ba1ad..61faeaff6 100755 --- a/kasa/discover.py +++ b/kasa/discover.py @@ -423,7 +423,7 @@ class EncryptionScheme(BaseModel): mac: str mgt_encrypt_schm: EncryptionScheme - device_id: Optional[str] = Field(default=None, alias="device_type_hash") + device_id: Optional[str] = Field(default=None, alias="device_id_hash") owner: Optional[str] = Field(default=None, alias="device_owner_hash") hw_ver: Optional[str] = None is_support_iot_cloud: Optional[bool] = None diff --git a/kasa/smartdevice.py b/kasa/smartdevice.py index f1995db82..0328ce996 100755 --- a/kasa/smartdevice.py +++ b/kasa/smartdevice.py @@ -211,6 +211,7 @@ def __init__( # checks in accessors. the @updated_required decorator does not ensure # mypy that these are not accessed incorrectly. self._last_update: Any = None + self.discovery_info: Optional[Dict[str, Any]] = None self._sys_info: Any = None # TODO: this is here to avoid changing tests self._features: Set[str] = set() @@ -371,6 +372,7 @@ def update_from_discover_info(self, info: Dict[str, Any]) -> None: # This allows setting of some info properties directly # from partial discovery info that will then be found # by the requires_update decorator + self.discovery_info = info self._set_sys_info(info) def _set_sys_info(self, sys_info: Dict[str, Any]) -> None: diff --git a/kasa/tests/fixtures/HS100(UK)_1.0_1.2.6.json b/kasa/tests/fixtures/HS100(UK)_1.0_1.2.6.json new file mode 100644 index 000000000..787f367e2 --- /dev/null +++ b/kasa/tests/fixtures/HS100(UK)_1.0_1.2.6.json @@ -0,0 +1,28 @@ +{ + "system": { + "get_sysinfo": { + "active_mode": "schedule", + "alias": "Living Room Lamp", + "dev_name": "Wi-Fi Smart Plug", + "deviceId": "0000000000000000000000000000000000000000", + "err_code": 0, + "feature": "TIM", + "fwId": "00000000000000000000000000000000", + "hwId": "00000000000000000000000000000000", + "hw_ver": "1.0", + "icon_hash": "", + "latitude": 0, + "led_off": 0, + "longitude": 0, + "mac": "00:00:00:00:00:00", + "model": "HS100(UK)", + "oemId": "00000000000000000000000000000000", + "on_time": 4102, + "relay_state": 1, + "rssi": -58, + "sw_ver": "1.2.6 Build 200727 Rel.120236", + "type": "IOT.SMARTPLUGSWITCH", + "updating": 0 + } + } +} diff --git a/kasa/tests/fixtures/HS100(UK)_4.1_1.1.0.json b/kasa/tests/fixtures/HS100(UK)_4.1_1.1.0.json index 448d6f2cc..a8524ac62 100644 --- a/kasa/tests/fixtures/HS100(UK)_4.1_1.1.0.json +++ b/kasa/tests/fixtures/HS100(UK)_4.1_1.1.0.json @@ -1,43 +1,46 @@ { - "emeter": { - "err_code": -1, - "err_msg": "module not support" - }, - "smartlife.iot.common.emeter": { - "err_code": -1, - "err_msg": "module not support" - }, - "smartlife.iot.dimmer": { - "err_code": -1, - "err_msg": "module not support" - }, - "smartlife.iot.smartbulb.lightingservice": { - "err_code": -1, - "err_msg": "module not support" + "discovery_result": { + "alias": "192.168.1.84", + "device_id_hash": "00000000000000000000000000000000", + "device_owner_hash": "00000000000000000000000000000000", + "device_type_text": "IOT.SMARTPLUGSWITCH", + "factory_default": true, + "hw_ver": "4.1", + "mac": "00-00-00-00-00-00", + "mgt_encrypt_schm": { + "encrypt_type": "KLAP", + "http_port": 80, + "is_support_https": false + }, + "model": "HS100(UK)" }, "system": { "get_sysinfo": { - "active_mode": "schedule", - "alias": "Unused 3", + "active_mode": "none", + "alias": "Bedroom Lamp 2", "dev_name": "Smart Wi-Fi Plug", "deviceId": "0000000000000000000000000000000000000000", "err_code": 0, "feature": "TIM", - "fwId": "00000000000000000000000000000000", "hwId": "00000000000000000000000000000000", "hw_ver": "4.1", "icon_hash": "", - "latitude": 0, + "latitude_i": 0, "led_off": 0, - "longitude": 0, + "longitude_i": 0, "mac": "00:00:00:00:00:00", + "mic_type": "IOT.SMARTPLUGSWITCH", "model": "HS100(UK)", + "next_action": { + "type": -1 + }, + "ntc_state": 0, "oemId": "00000000000000000000000000000000", - "on_time": 0, - "relay_state": 0, + "on_time": 3115, + "relay_state": 1, "rssi": -63, + "status": "new", "sw_ver": "1.1.0 Build 201016 Rel.175121", - "type": "IOT.SMARTPLUGSWITCH", "updating": 0 } } diff --git a/kasa/tests/newfakes.py b/kasa/tests/newfakes.py index acd9de1ee..ee679cae8 100644 --- a/kasa/tests/newfakes.py +++ b/kasa/tests/newfakes.py @@ -294,9 +294,10 @@ def __init__(self, info): for target in info: # print("target %s" % target) - for cmd in info[target]: - # print("initializing tgt %s cmd %s" % (target, cmd)) - proto[target][cmd] = info[target][cmd] + if target != "discovery_result": + for cmd in info[target]: + # print("initializing tgt %s cmd %s" % (target, cmd)) + proto[target][cmd] = info[target][cmd] # if we have emeter support, we need to add the missing pieces for module in ["emeter", "smartlife.iot.common.emeter"]: if ( From ff7647174ff816e24472f8c06ac28780f78738b4 Mon Sep 17 00:00:00 2001 From: sdb9696 Date: Wed, 29 Nov 2023 07:12:37 +0000 Subject: [PATCH 2/3] Update following review. Do not serialize aliases. --- devtools/dump_devinfo.py | 21 ++++++++++++++++---- kasa/smartdevice.py | 4 ++-- kasa/tests/fixtures/HS100(UK)_4.1_1.1.0.json | 16 +++++++-------- 3 files changed, 27 insertions(+), 14 deletions(-) diff --git a/devtools/dump_devinfo.py b/devtools/dump_devinfo.py index 49bfaf39b..0c33ac0b4 100644 --- a/devtools/dump_devinfo.py +++ b/devtools/dump_devinfo.py @@ -17,6 +17,7 @@ import asyncclick as click from kasa import Credentials, Discover +from kasa.discover import DiscoveryResult Call = namedtuple("Call", "module method") @@ -34,8 +35,13 @@ def scrub(res): "longitude_i", "latitude", "longitude", - "device_owner_hash", - "device_id_hash", + "owner", + "device_id", + "ip", + "ssid", + "hw_id", + "fw_id", + "oem_id", ] for k, v in res.items(): @@ -45,6 +51,8 @@ def scrub(res): if k in keys_to_scrub: if k in ["latitude", "latitude_i", "longitude", "longitude_i"]: v = 0 + elif k in ["ip"]: + v = "127.0.0.123" else: v = re.sub(r"\w", "0", v) @@ -135,8 +143,13 @@ async def cli(host, debug, username, password): ) ) - if device.discovery_info: - final["discovery_result"] = device.discovery_info + if device._discovery_info: + # Need to recreate a DiscoverResult here because we don't want the aliases + # in the fixture, we want the actual field names as returned by the device. + dr = DiscoveryResult(**device._discovery_info) + final["discovery_result"] = dr.dict( + by_alias=False, exclude_unset=True, exclude_none=True, exclude_defaults=True + ) click.echo("Got %s successes" % len(successes)) click.echo(click.style("## device info file ##", bold=True)) diff --git a/kasa/smartdevice.py b/kasa/smartdevice.py index 0328ce996..af6a2c7f0 100755 --- a/kasa/smartdevice.py +++ b/kasa/smartdevice.py @@ -211,7 +211,7 @@ def __init__( # checks in accessors. the @updated_required decorator does not ensure # mypy that these are not accessed incorrectly. self._last_update: Any = None - self.discovery_info: Optional[Dict[str, Any]] = None + self._discovery_info: Optional[Dict[str, Any]] = None self._sys_info: Any = None # TODO: this is here to avoid changing tests self._features: Set[str] = set() @@ -372,7 +372,7 @@ def update_from_discover_info(self, info: Dict[str, Any]) -> None: # This allows setting of some info properties directly # from partial discovery info that will then be found # by the requires_update decorator - self.discovery_info = info + self._discovery_info = info self._set_sys_info(info) def _set_sys_info(self, sys_info: Dict[str, Any]) -> None: diff --git a/kasa/tests/fixtures/HS100(UK)_4.1_1.1.0.json b/kasa/tests/fixtures/HS100(UK)_4.1_1.1.0.json index a8524ac62..2425c17f3 100644 --- a/kasa/tests/fixtures/HS100(UK)_4.1_1.1.0.json +++ b/kasa/tests/fixtures/HS100(UK)_4.1_1.1.0.json @@ -1,18 +1,18 @@ { "discovery_result": { - "alias": "192.168.1.84", - "device_id_hash": "00000000000000000000000000000000", - "device_owner_hash": "00000000000000000000000000000000", - "device_type_text": "IOT.SMARTPLUGSWITCH", + "device_id": "00000000000000000000000000000000", + "device_model": "HS100(UK)", + "device_type": "IOT.SMARTPLUGSWITCH", "factory_default": true, "hw_ver": "4.1", + "ip": "127.0.0.123", "mac": "00-00-00-00-00-00", "mgt_encrypt_schm": { "encrypt_type": "KLAP", "http_port": 80, "is_support_https": false }, - "model": "HS100(UK)" + "owner": "00000000000000000000000000000000" }, "system": { "get_sysinfo": { @@ -36,9 +36,9 @@ }, "ntc_state": 0, "oemId": "00000000000000000000000000000000", - "on_time": 3115, - "relay_state": 1, - "rssi": -63, + "on_time": 0, + "relay_state": 0, + "rssi": -66, "status": "new", "sw_ver": "1.1.0 Build 201016 Rel.175121", "updating": 0 From 8981bf5da1a3b7c71152b58c6f20ff41d35e912d Mon Sep 17 00:00:00 2001 From: sdb9696 <51370195+sdb9696@users.noreply.github.com> Date: Wed, 29 Nov 2023 18:01:41 +0000 Subject: [PATCH 3/3] Delete kasa/tests/fixtures/HS100(UK)_1.0_1.2.6.json --- kasa/tests/fixtures/HS100(UK)_1.0_1.2.6.json | 28 -------------------- 1 file changed, 28 deletions(-) delete mode 100644 kasa/tests/fixtures/HS100(UK)_1.0_1.2.6.json diff --git a/kasa/tests/fixtures/HS100(UK)_1.0_1.2.6.json b/kasa/tests/fixtures/HS100(UK)_1.0_1.2.6.json deleted file mode 100644 index 787f367e2..000000000 --- a/kasa/tests/fixtures/HS100(UK)_1.0_1.2.6.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "system": { - "get_sysinfo": { - "active_mode": "schedule", - "alias": "Living Room Lamp", - "dev_name": "Wi-Fi Smart Plug", - "deviceId": "0000000000000000000000000000000000000000", - "err_code": 0, - "feature": "TIM", - "fwId": "00000000000000000000000000000000", - "hwId": "00000000000000000000000000000000", - "hw_ver": "1.0", - "icon_hash": "", - "latitude": 0, - "led_off": 0, - "longitude": 0, - "mac": "00:00:00:00:00:00", - "model": "HS100(UK)", - "oemId": "00000000000000000000000000000000", - "on_time": 4102, - "relay_state": 1, - "rssi": -58, - "sw_ver": "1.2.6 Build 200727 Rel.120236", - "type": "IOT.SMARTPLUGSWITCH", - "updating": 0 - } - } -}