Fix devtools for P100 and add fixture (#753)

This commit is contained in:
Steven B 2024-02-14 19:43:10 +00:00 committed by GitHub
parent 13d8d94bd5
commit 57835276e3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 236 additions and 27 deletions

View File

@ -12,6 +12,7 @@ import collections.abc
import json import json
import logging import logging
import re import re
import traceback
from collections import defaultdict, namedtuple from collections import defaultdict, namedtuple
from pathlib import Path from pathlib import Path
from pprint import pprint from pprint import pprint
@ -19,7 +20,7 @@ from typing import Dict, List, Union
import asyncclick as click import asyncclick as click
from devtools.helpers.smartrequests import COMPONENT_REQUESTS, SmartRequest from devtools.helpers.smartrequests import SmartRequest, get_component_requests
from kasa import ( from kasa import (
AuthenticationException, AuthenticationException,
Credentials, Credentials,
@ -35,6 +36,8 @@ from kasa.smart import SmartDevice
Call = namedtuple("Call", "module method") Call = namedtuple("Call", "module method")
SmartCall = namedtuple("SmartCall", "module request should_succeed") SmartCall = namedtuple("SmartCall", "module request should_succeed")
_LOGGER = logging.getLogger(__name__)
def scrub(res): def scrub(res):
"""Remove identifiers from the given dict.""" """Remove identifiers from the given dict."""
@ -228,6 +231,8 @@ async def get_legacy_fixture(device):
else: else:
click.echo(click.style("OK", fg="green")) click.echo(click.style("OK", fg="green"))
successes.append((test_call, info)) successes.append((test_call, info))
finally:
await device.protocol.close()
final_query = defaultdict(defaultdict) final_query = defaultdict(defaultdict)
final = defaultdict(defaultdict) final = defaultdict(defaultdict)
@ -241,7 +246,8 @@ async def get_legacy_fixture(device):
final = await device.protocol.query(final_query) final = await device.protocol.query(final_query)
except Exception as ex: except Exception as ex:
_echo_error(f"Unable to query all successes at once: {ex}", bold=True, fg="red") _echo_error(f"Unable to query all successes at once: {ex}", bold=True, fg="red")
finally:
await device.protocol.close()
if device._discovery_info and not device._discovery_info.get("system"): if device._discovery_info and not device._discovery_info.get("system"):
# Need to recreate a DiscoverResult here because we don't want the aliases # 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. # in the fixture, we want the actual field names as returned by the device.
@ -316,7 +322,11 @@ async def _make_requests_or_exit(
_echo_error( _echo_error(
f"Unexpected exception querying {name} at once: {ex}", f"Unexpected exception querying {name} at once: {ex}",
) )
if _LOGGER.isEnabledFor(logging.DEBUG):
traceback.print_stack()
exit(1) exit(1)
finally:
await device.protocol.close()
async def get_smart_fixture(device: SmartDevice, batch_size: int): async def get_smart_fixture(device: SmartDevice, batch_size: int):
@ -367,14 +377,15 @@ async def get_smart_fixture(device: SmartDevice, batch_size: int):
for item in component_info_response["component_list"]: for item in component_info_response["component_list"]:
component_id = item["id"] component_id = item["id"]
if requests := COMPONENT_REQUESTS.get(component_id): ver_code = item["ver_code"]
if (requests := get_component_requests(component_id, ver_code)) is not None:
component_test_calls = [ component_test_calls = [
SmartCall(module=component_id, request=request, should_succeed=True) SmartCall(module=component_id, request=request, should_succeed=True)
for request in requests for request in requests
] ]
test_calls.extend(component_test_calls) test_calls.extend(component_test_calls)
should_succeed.extend(component_test_calls) should_succeed.extend(component_test_calls)
elif component_id not in COMPONENT_REQUESTS: else:
click.echo(f"Skipping {component_id}..", nl=False) click.echo(f"Skipping {component_id}..", nl=False)
click.echo(click.style("UNSUPPORTED", fg="yellow")) click.echo(click.style("UNSUPPORTED", fg="yellow"))
@ -396,7 +407,11 @@ async def get_smart_fixture(device: SmartDevice, batch_size: int):
if ( if (
not test_call.should_succeed not test_call.should_succeed
and hasattr(ex, "error_code") and hasattr(ex, "error_code")
and ex.error_code == SmartErrorCode.UNKNOWN_METHOD_ERROR and ex.error_code
in [
SmartErrorCode.UNKNOWN_METHOD_ERROR,
SmartErrorCode.TRANSPORT_NOT_AVAILABLE_ERROR,
]
): ):
click.echo(click.style("FAIL - EXPECTED", fg="green")) click.echo(click.style("FAIL - EXPECTED", fg="green"))
else: else:
@ -410,6 +425,8 @@ async def get_smart_fixture(device: SmartDevice, batch_size: int):
else: else:
click.echo(click.style("OK", fg="green")) click.echo(click.style("OK", fg="green"))
successes.append(test_call) successes.append(test_call)
finally:
await device.protocol.close()
requests = [] requests = []
for succ in successes: for succ in successes:

View File

@ -133,11 +133,14 @@ class SmartRequest:
return SmartRequest("get_device_usage") return SmartRequest("get_device_usage")
@staticmethod @staticmethod
def device_info_list() -> List["SmartRequest"]: def device_info_list(ver_code) -> List["SmartRequest"]:
"""Get device info list.""" """Get device info list."""
if ver_code == 1:
return [SmartRequest.get_device_info()]
return [ return [
SmartRequest.get_device_info(), SmartRequest.get_device_info(),
SmartRequest.get_device_usage(), SmartRequest.get_device_usage(),
SmartRequest.get_auto_update_info(),
] ]
@staticmethod @staticmethod
@ -149,7 +152,6 @@ class SmartRequest:
def firmware_info_list() -> List["SmartRequest"]: def firmware_info_list() -> List["SmartRequest"]:
"""Get info list.""" """Get info list."""
return [ return [
SmartRequest.get_auto_update_info(),
SmartRequest.get_raw_request("get_fw_download_state"), SmartRequest.get_raw_request("get_fw_download_state"),
SmartRequest.get_raw_request("get_latest_fw"), SmartRequest.get_raw_request("get_latest_fw"),
] ]
@ -165,9 +167,13 @@ class SmartRequest:
return SmartRequest("get_device_time") return SmartRequest("get_device_time")
@staticmethod @staticmethod
def get_wireless_scan_info() -> "SmartRequest": def get_wireless_scan_info(
params: Optional[GetRulesParams] = None
) -> "SmartRequest":
"""Get wireless scan info.""" """Get wireless scan info."""
return SmartRequest("get_wireless_scan_info") return SmartRequest(
"get_wireless_scan_info", params or SmartRequest.GetRulesParams()
)
@staticmethod @staticmethod
def get_schedule_rules(params: Optional[GetRulesParams] = None) -> "SmartRequest": def get_schedule_rules(params: Optional[GetRulesParams] = None) -> "SmartRequest":
@ -294,9 +300,13 @@ class SmartRequest:
@staticmethod @staticmethod
def get_component_info_requests(component_nego_response) -> List["SmartRequest"]: def get_component_info_requests(component_nego_response) -> List["SmartRequest"]:
"""Get a list of requests based on the component info response.""" """Get a list of requests based on the component info response."""
request_list = [] request_list: List["SmartRequest"] = []
for component in component_nego_response["component_list"]: for component in component_nego_response["component_list"]:
if requests := COMPONENT_REQUESTS.get(component["id"]): if (
requests := get_component_requests(
component["id"], int(component["ver_code"])
)
) is not None:
request_list.extend(requests) request_list.extend(requests)
return request_list return request_list
@ -314,8 +324,17 @@ class SmartRequest:
return request return request
def get_component_requests(component_id, ver_code):
"""Get the requests supported by the component and version."""
if (cr := COMPONENT_REQUESTS.get(component_id)) is None:
return None
if callable(cr):
return cr(ver_code)
return cr
COMPONENT_REQUESTS = { COMPONENT_REQUESTS = {
"device": SmartRequest.device_info_list(), "device": SmartRequest.device_info_list,
"firmware": SmartRequest.firmware_info_list(), "firmware": SmartRequest.firmware_info_list(),
"quick_setup": [SmartRequest.qs_component_nego()], "quick_setup": [SmartRequest.qs_component_nego()],
"inherit": [SmartRequest.get_raw_request("get_inherit_info")], "inherit": [SmartRequest.get_raw_request("get_inherit_info")],
@ -324,33 +343,33 @@ COMPONENT_REQUESTS = {
"schedule": SmartRequest.schedule_info_list(), "schedule": SmartRequest.schedule_info_list(),
"countdown": [SmartRequest.get_countdown_rules()], "countdown": [SmartRequest.get_countdown_rules()],
"antitheft": [SmartRequest.get_antitheft_rules()], "antitheft": [SmartRequest.get_antitheft_rules()],
"account": None, "account": [],
"synchronize": None, # sync_env "synchronize": [], # sync_env
"sunrise_sunset": None, # for schedules "sunrise_sunset": [], # for schedules
"led": [SmartRequest.get_led_info()], "led": [SmartRequest.get_led_info()],
"cloud_connect": [SmartRequest.get_raw_request("get_connect_cloud_state")], "cloud_connect": [SmartRequest.get_raw_request("get_connect_cloud_state")],
"iot_cloud": None, "iot_cloud": [],
"device_local_time": None, "device_local_time": [],
"default_states": None, # in device_info "default_states": [], # in device_info
"auto_off": [SmartRequest.get_auto_off_config()], "auto_off": [SmartRequest.get_auto_off_config()],
"localSmart": None, "localSmart": [],
"energy_monitoring": SmartRequest.energy_monitoring_list(), "energy_monitoring": SmartRequest.energy_monitoring_list(),
"power_protection": SmartRequest.power_protection_list(), "power_protection": SmartRequest.power_protection_list(),
"current_protection": None, # overcurrent in device_info "current_protection": [], # overcurrent in device_info
"matter": None, "matter": [],
"preset": [SmartRequest.get_preset_rules()], "preset": [SmartRequest.get_preset_rules()],
"brightness": None, # in device_info "brightness": [], # in device_info
"color": None, # in device_info "color": [], # in device_info
"color_temperature": None, # in device_info "color_temperature": [], # in device_info
"auto_light": [SmartRequest.get_auto_light_info()], "auto_light": [SmartRequest.get_auto_light_info()],
"light_effect": [SmartRequest.get_dynamic_light_effect_rules()], "light_effect": [SmartRequest.get_dynamic_light_effect_rules()],
"bulb_quick_control": None, "bulb_quick_control": [],
"on_off_gradually": [SmartRequest.get_raw_request("get_on_off_gradually_info")], "on_off_gradually": [SmartRequest.get_raw_request("get_on_off_gradually_info")],
"light_strip": None, "light_strip": [],
"light_strip_lighting_effect": [ "light_strip_lighting_effect": [
SmartRequest.get_raw_request("get_lighting_effect") SmartRequest.get_raw_request("get_lighting_effect")
], ],
"music_rhythm": None, # music_rhythm_enable in device_info "music_rhythm": [], # music_rhythm_enable in device_info
"segment": [SmartRequest.get_raw_request("get_device_segment")], "segment": [SmartRequest.get_raw_request("get_device_segment")],
"segment_effect": [SmartRequest.get_raw_request("get_segment_effect_rule")], "segment_effect": [SmartRequest.get_raw_request("get_segment_effect_rule")],
} }

View File

@ -0,0 +1,173 @@
{
"component_nego": {
"component_list": [
{
"id": "device",
"ver_code": 1
},
{
"id": "firmware",
"ver_code": 1
},
{
"id": "quick_setup",
"ver_code": 1
},
{
"id": "time",
"ver_code": 1
},
{
"id": "wireless",
"ver_code": 1
},
{
"id": "schedule",
"ver_code": 1
},
{
"id": "countdown",
"ver_code": 1
},
{
"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
}
]
},
"discovery_result": {
"device_id": "00000000000000000000000000000000",
"device_model": "P100",
"device_type": "SMART.TAPOPLUG",
"factory_default": false,
"ip": "127.0.0.123",
"mac": "1C-3B-F3-00-00-00",
"mgt_encrypt_schm": {
"encrypt_type": "AES",
"http_port": 80,
"is_support_https": false
},
"owner": "00000000000000000000000000000000"
},
"get_antitheft_rules": {
"antitheft_rule_max_count": 1,
"enable": false,
"rule_list": []
},
"get_connect_cloud_state": {
"status": -1001
},
"get_countdown_rules": {
"countdown_rule_max_count": 1,
"enable": false,
"rule_list": []
},
"get_device_info": {
"avatar": "plug",
"device_id": "0000000000000000000000000000000000000000",
"device_on": true,
"fw_id": "00000000000000000000000000000000",
"fw_ver": "1.1.3 Build 20191017 Rel. 57937",
"has_set_location_info": true,
"hw_id": "00000000000000000000000000000000",
"hw_ver": "1.0.0",
"ip": "127.0.0.123",
"latitude": 0,
"location": "hallway",
"longitude": 0,
"mac": "1C-3B-F3-00-00-00",
"model": "P100",
"nickname": "I01BU0tFRF9OQU1FIw==",
"oem_id": "00000000000000000000000000000000",
"on_time": 6868,
"overheated": false,
"signal_level": 2,
"specs": "US",
"ssid": "I01BU0tFRF9TU0lEIw==",
"time_usage_past30": 114,
"time_usage_past7": 114,
"time_usage_today": 114,
"type": "SMART.TAPOPLUG"
},
"get_device_time": {
"region": "Europe/London",
"time_diff": 0,
"timestamp": 1707905077
},
"get_fw_download_state": {
"download_progress": 0,
"reboot_time": 10,
"status": 0,
"upgrade_time": 0
},
"get_latest_fw": {
"fw_size": 786432,
"fw_ver": "1.3.7 Build 20230711 Rel.61904",
"hw_id": "00000000000000000000000000000000",
"need_to_upgrade": true,
"oem_id": "00000000000000000000000000000000",
"release_date": "2023-07-26",
"release_note": "Modifications and Bug fixes:\nEnhanced device security.",
"type": 3
},
"get_led_info": {
"led_rule": "always",
"led_status": true
},
"get_next_event": {
"action": -1,
"e_time": 0,
"id": "0",
"s_time": 0,
"type": 0
},
"get_schedule_rules": {
"enable": false,
"rule_list": [],
"schedule_rule_max_count": 20,
"start_index": 0,
"sum": 0
},
"get_wireless_scan_info": {
"ap_list": [],
"start_index": 0,
"sum": 0,
"wep_supported": false
},
"qs_component_nego": {
"component_list": [
{
"id": "quick_setup",
"ver_code": 1
},
{
"id": "sunrise_sunset",
"ver_code": 1
}
],
"extra_info": {
"device_model": "P100",
"device_type": "SMART.TAPOPLUG"
}
}
}