mirror of
https://github.com/python-kasa/python-kasa.git
synced 2025-10-12 10:28:01 +00:00
Add known smart requests to dump_devinfo (#597)
* Add known smart requests to dump_devinfo * Move smartrequest.py to devtools * Update post-review
This commit is contained in:
@@ -14,14 +14,18 @@ import logging
|
||||
import re
|
||||
from collections import defaultdict, namedtuple
|
||||
from pprint import pprint
|
||||
from typing import Dict, List
|
||||
|
||||
import asyncclick as click
|
||||
|
||||
from devtools.helpers.smartrequests import COMPONENT_REQUESTS, SmartRequest
|
||||
from kasa import AuthenticationException, Credentials, Discover, SmartDevice
|
||||
from kasa.discover import DiscoveryResult
|
||||
from kasa.exceptions import SmartErrorCode
|
||||
from kasa.tapo.tapodevice import TapoDevice
|
||||
|
||||
Call = namedtuple("Call", "module method")
|
||||
SmartCall = namedtuple("SmartCall", "module request should_succeed")
|
||||
|
||||
|
||||
def scrub(res):
|
||||
@@ -46,11 +50,19 @@ def scrub(res):
|
||||
"oem_id",
|
||||
"nickname",
|
||||
"alias",
|
||||
"bssid",
|
||||
"channel",
|
||||
]
|
||||
|
||||
for k, v in res.items():
|
||||
if isinstance(v, collections.abc.Mapping):
|
||||
res[k] = scrub(res.get(k))
|
||||
elif (
|
||||
isinstance(v, list)
|
||||
and len(v) > 0
|
||||
and isinstance(v[0], collections.abc.Mapping)
|
||||
):
|
||||
res[k] = [scrub(vi) for vi in v]
|
||||
else:
|
||||
if k in keys_to_scrub:
|
||||
if k in ["latitude", "latitude_i", "longitude", "longitude_i"]:
|
||||
@@ -64,6 +76,8 @@ def scrub(res):
|
||||
v = base64.b64encode(b"#MASKED_NAME#").decode()
|
||||
elif k in ["alias"]:
|
||||
v = "#MASKED_NAME#"
|
||||
elif isinstance(res[k], int):
|
||||
v = 0
|
||||
else:
|
||||
v = re.sub(r"\w", "0", v)
|
||||
|
||||
@@ -179,7 +193,7 @@ async def get_legacy_fixture(device):
|
||||
)
|
||||
)
|
||||
|
||||
if device._discovery_info:
|
||||
if device._discovery_info and not device._discovery_info.get("system"):
|
||||
# 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)
|
||||
@@ -200,59 +214,22 @@ async def get_legacy_fixture(device):
|
||||
return save_filename, copy_folder, final
|
||||
|
||||
|
||||
async def get_smart_fixture(device: SmartDevice):
|
||||
"""Get fixture for new TAPO style protocol."""
|
||||
items = [
|
||||
Call(module="component_nego", method="component_nego"),
|
||||
Call(module="device_info", method="get_device_info"),
|
||||
Call(module="device_usage", method="get_device_usage"),
|
||||
Call(module="device_time", method="get_device_time"),
|
||||
Call(module="energy_usage", method="get_energy_usage"),
|
||||
Call(module="current_power", method="get_current_power"),
|
||||
Call(module="temp_humidity_records", method="get_temp_humidity_records"),
|
||||
Call(module="child_device_list", method="get_child_device_list"),
|
||||
Call(
|
||||
module="trigger_logs",
|
||||
method={"get_trigger_logs": {"page_size": 5, "start_id": 0}},
|
||||
),
|
||||
Call(
|
||||
module="child_device_component_list",
|
||||
method="get_child_device_component_list",
|
||||
),
|
||||
]
|
||||
|
||||
successes = []
|
||||
|
||||
for test_call in items:
|
||||
try:
|
||||
click.echo(f"Testing {test_call}..", nl=False)
|
||||
response = await device.protocol.query(test_call.method)
|
||||
except AuthenticationException as ex:
|
||||
click.echo(
|
||||
click.style(
|
||||
f"Unable to query the device due to an authentication error: {ex}",
|
||||
bold=True,
|
||||
fg="red",
|
||||
)
|
||||
)
|
||||
exit(1)
|
||||
except Exception as ex:
|
||||
click.echo(click.style(f"FAIL {ex}", fg="red"))
|
||||
else:
|
||||
if not response:
|
||||
click.echo(click.style("FAIL not suported", fg="red"))
|
||||
else:
|
||||
click.echo(click.style("OK", fg="green"))
|
||||
successes.append(test_call)
|
||||
|
||||
requests = []
|
||||
for succ in successes:
|
||||
requests.append({"method": succ.method})
|
||||
|
||||
final_query = {"multipleRequest": {"requests": requests}}
|
||||
|
||||
async def _make_requests_or_exit(
|
||||
device: SmartDevice, requests: List[SmartRequest], name: str
|
||||
) -> Dict[str, Dict]:
|
||||
final = {}
|
||||
try:
|
||||
responses = await device.protocol.query(final_query)
|
||||
end = len(requests)
|
||||
step = 10 # Break the requests down as there seems to be a size limit
|
||||
for i in range(0, end, step):
|
||||
x = i
|
||||
requests_step = requests[x : x + step]
|
||||
responses = await device.protocol.query(
|
||||
SmartRequest._create_request_dict(requests_step)
|
||||
)
|
||||
for method, result in responses.items():
|
||||
final[method] = result
|
||||
return final
|
||||
except AuthenticationException as ex:
|
||||
click.echo(
|
||||
click.style(
|
||||
@@ -264,14 +241,112 @@ async def get_smart_fixture(device: SmartDevice):
|
||||
exit(1)
|
||||
except Exception as ex:
|
||||
click.echo(
|
||||
click.style(
|
||||
f"Unable to query all successes at once: {ex}", bold=True, fg="red"
|
||||
)
|
||||
click.style(f"Unable to query {name} at once: {ex}", bold=True, fg="red")
|
||||
)
|
||||
exit(1)
|
||||
final = {}
|
||||
for method, result in responses.items():
|
||||
final[method] = result
|
||||
|
||||
|
||||
async def get_smart_fixture(device: TapoDevice):
|
||||
"""Get fixture for new TAPO style protocol."""
|
||||
extra_test_calls = [
|
||||
SmartCall(
|
||||
module="temp_humidity_records",
|
||||
request=SmartRequest.get_raw_request("get_temp_humidity_records"),
|
||||
should_succeed=False,
|
||||
),
|
||||
SmartCall(
|
||||
module="child_device_list",
|
||||
request=SmartRequest.get_raw_request("get_child_device_list"),
|
||||
should_succeed=False,
|
||||
),
|
||||
SmartCall(
|
||||
module="child_device_component_list",
|
||||
request=SmartRequest.get_raw_request("get_child_device_component_list"),
|
||||
should_succeed=False,
|
||||
),
|
||||
SmartCall(
|
||||
module="trigger_logs",
|
||||
request=SmartRequest.get_raw_request(
|
||||
"get_trigger_logs", SmartRequest.GetTriggerLogsParams(5, 0)
|
||||
),
|
||||
should_succeed=False,
|
||||
),
|
||||
]
|
||||
|
||||
successes = []
|
||||
|
||||
click.echo("Testing component_nego call ..", nl=False)
|
||||
responses = await _make_requests_or_exit(
|
||||
device, [SmartRequest.component_nego()], "component_nego call"
|
||||
)
|
||||
component_info_response = responses["component_nego"]
|
||||
click.echo(click.style("OK", fg="green"))
|
||||
successes.append(
|
||||
SmartCall(
|
||||
module="component_nego",
|
||||
request=SmartRequest("component_nego"),
|
||||
should_succeed=True,
|
||||
)
|
||||
)
|
||||
|
||||
test_calls = []
|
||||
should_succeed = []
|
||||
|
||||
for item in component_info_response["component_list"]:
|
||||
component_id = item["id"]
|
||||
if requests := COMPONENT_REQUESTS.get(component_id):
|
||||
component_test_calls = [
|
||||
SmartCall(module=component_id, request=request, should_succeed=True)
|
||||
for request in requests
|
||||
]
|
||||
test_calls.extend(component_test_calls)
|
||||
should_succeed.extend(component_test_calls)
|
||||
elif component_id not in COMPONENT_REQUESTS:
|
||||
click.echo(f"Skipping {component_id}..", nl=False)
|
||||
click.echo(click.style("UNSUPPORTED", fg="yellow"))
|
||||
|
||||
test_calls.extend(extra_test_calls)
|
||||
|
||||
for test_call in test_calls:
|
||||
click.echo(f"Testing {test_call.module}..", nl=False)
|
||||
try:
|
||||
click.echo(f"Testing {test_call}..", nl=False)
|
||||
response = await device.protocol.query(
|
||||
SmartRequest._create_request_dict(test_call.request)
|
||||
)
|
||||
except AuthenticationException as ex:
|
||||
click.echo(
|
||||
click.style(
|
||||
f"Unable to query the device due to an authentication error: {ex}",
|
||||
bold=True,
|
||||
fg="red",
|
||||
)
|
||||
)
|
||||
exit(1)
|
||||
except Exception as ex:
|
||||
if (
|
||||
not test_call.should_succeed
|
||||
and hasattr(ex, "error_code")
|
||||
and ex.error_code == SmartErrorCode.UNKNOWN_METHOD_ERROR
|
||||
):
|
||||
click.echo(click.style("FAIL - EXPECTED", fg="green"))
|
||||
else:
|
||||
click.echo(click.style(f"FAIL {ex}", fg="red"))
|
||||
else:
|
||||
if not response:
|
||||
click.echo(click.style("FAIL no response", fg="red"))
|
||||
else:
|
||||
if not test_call.should_succeed:
|
||||
click.echo(click.style("OK - EXPECTED FAIL", fg="red"))
|
||||
else:
|
||||
click.echo(click.style("OK", fg="green"))
|
||||
successes.append(test_call)
|
||||
|
||||
requests = []
|
||||
for succ in successes:
|
||||
requests.append(succ.request)
|
||||
|
||||
final = await _make_requests_or_exit(device, requests, "all successes at once")
|
||||
|
||||
# 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.
|
||||
|
Reference in New Issue
Block a user