diff --git a/devtools/dump_devinfo.py b/devtools/dump_devinfo.py index eff3a7b6..0c33ac0b 100644 --- a/devtools/dump_devinfo.py +++ b/devtools/dump_devinfo.py @@ -7,7 +7,6 @@ If you have new, yet unsupported device or a device with no devinfo file under 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,10 @@ import re from collections import defaultdict, namedtuple from pprint import pprint -import click +import asyncclick as click -from kasa import TPLinkSmartHomeProtocol +from kasa import Credentials, Discover +from kasa.discover import DiscoveryResult Call = namedtuple("Call", "module method") @@ -35,6 +35,13 @@ def scrub(res): "longitude_i", "latitude", "longitude", + "owner", + "device_id", + "ip", + "ssid", + "hw_id", + "fw_id", + "oem_id", ] for k, v in res.items(): @@ -44,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) @@ -63,8 +72,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 +106,15 @@ def cli(host, debug): successes = [] + credentials = Credentials(username=username, password=password) + device = await Discover.discover_single(host, credentials=credentials) + 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}}) - 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 +134,8 @@ def cli(host, debug): 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 +143,14 @@ def cli(host, debug): ) ) + 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/discover.py b/kasa/discover.py index 2523ba1a..61faeaff 100755 --- a/kasa/discover.py +++ b/kasa/discover.py @@ -423,7 +423,7 @@ class DiscoveryResult(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 f1995db8..af6a2c7f 100755 --- a/kasa/smartdevice.py +++ b/kasa/smartdevice.py @@ -211,6 +211,7 @@ class SmartDevice: # 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 @@ class SmartDevice: # 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)_4.1_1.1.0.json b/kasa/tests/fixtures/HS100(UK)_4.1_1.1.0.json index 448d6f2c..2425c17f 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": { + "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 + }, + "owner": "00000000000000000000000000000000" }, "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, - "rssi": -63, + "rssi": -66, + "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 acd9de1e..ee679cae 100644 --- a/kasa/tests/newfakes.py +++ b/kasa/tests/newfakes.py @@ -294,9 +294,10 @@ class FakeTransportProtocol(TPLinkSmartHomeProtocol): 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 (