python-kasa/devtools/helpers/smartrequests.py

358 lines
11 KiB
Python

"""SmartRequest helper classes and functions for new SMART/TAPO devices.
List of known requests with associated parameter classes.
Other requests that are known but not currently implemented
or tested are:
get_child_device_component_list
get_child_device_list
control_child
get_device_running_info - seems to be a subset of get_device_info
get_tss_info
get_raw_dvi
get_homekit_info
fw_download
sync_env
account_sync
device_reset
close_device_ble
heart_beat
"""
import logging
from dataclasses import asdict, dataclass
from typing import List, Optional, Union
_LOGGER = logging.getLogger(__name__)
logging.getLogger("httpx").propagate = False
class SmartRequest:
"""Class to represent a smart protocol request."""
def __init__(self, method_name: str, params: Optional["SmartRequestParams"] = None):
self.method_name = method_name
if params:
self.params = params.to_dict()
else:
self.params = None
def __repr__(self):
return f"SmartRequest({self.method_name})"
def to_dict(self):
"""Return the request as a dict suitable for passing to query()."""
return {self.method_name: self.params}
@dataclass
class SmartRequestParams:
"""Base class for Smart request params.
The to_dict() method of this class omits null values which
is required by the devices.
"""
def to_dict(self):
"""Return the params as a dict with values of None ommited."""
return asdict(
self, dict_factory=lambda x: {k: v for (k, v) in x if v is not None}
)
@dataclass
class DeviceOnParams(SmartRequestParams):
"""Get Rules Params."""
device_on: bool
@dataclass
class GetRulesParams(SmartRequestParams):
"""Get Rules Params."""
start_index: int = 0
@dataclass
class GetTriggerLogsParams(SmartRequestParams):
"""Trigger Logs params."""
page_size: int = 5
start_id: int = 0
@dataclass
class LedStatusParams(SmartRequestParams):
"""LED Status params."""
led_rule: Optional[str] = None
@staticmethod
def from_bool(state: bool):
"""Set the led_rule from the state."""
rule = "always" if state else "never"
return SmartRequest.LedStatusParams(led_rule=rule)
@dataclass
class LightInfoParams(SmartRequestParams):
"""LightInfo params."""
brightness: Optional[int] = None
color_temp: Optional[int] = None
hue: Optional[int] = None
saturation: Optional[int] = None
@dataclass
class DynamicLightEffectParams(SmartRequestParams):
"""LightInfo params."""
enable: bool
id: Optional[str] = None
@staticmethod
def get_raw_request(
method: str, params: Optional[SmartRequestParams] = None
) -> "SmartRequest":
"""Send a raw request to the device."""
return SmartRequest(method, params)
@staticmethod
def component_nego() -> "SmartRequest":
"""Get quick setup component info."""
return SmartRequest("component_nego")
@staticmethod
def get_device_info() -> "SmartRequest":
"""Get device info."""
return SmartRequest("get_device_info")
@staticmethod
def get_device_usage() -> "SmartRequest":
"""Get device usage."""
return SmartRequest("get_device_usage")
@staticmethod
def device_info_list() -> List["SmartRequest"]:
"""Get device info list."""
return [
SmartRequest.get_device_info(),
SmartRequest.get_device_usage(),
]
@staticmethod
def get_auto_update_info() -> "SmartRequest":
"""Get auto update info."""
return SmartRequest("get_auto_update_info")
@staticmethod
def firmware_info_list() -> List["SmartRequest"]:
"""Get info list."""
return [
SmartRequest.get_auto_update_info(),
SmartRequest.get_raw_request("get_fw_download_state"),
SmartRequest.get_raw_request("get_latest_fw"),
]
@staticmethod
def qs_component_nego() -> "SmartRequest":
"""Get quick setup component info."""
return SmartRequest("qs_component_nego")
@staticmethod
def get_device_time() -> "SmartRequest":
"""Get device time."""
return SmartRequest("get_device_time")
@staticmethod
def get_wireless_scan_info() -> "SmartRequest":
"""Get wireless scan info."""
return SmartRequest("get_wireless_scan_info")
@staticmethod
def get_schedule_rules(params: Optional[GetRulesParams] = None) -> "SmartRequest":
"""Get schedule rules."""
return SmartRequest(
"get_schedule_rules", params or SmartRequest.GetRulesParams()
)
@staticmethod
def get_next_event(params: Optional[GetRulesParams] = None) -> "SmartRequest":
"""Get next scheduled event."""
return SmartRequest("get_next_event", params or SmartRequest.GetRulesParams())
@staticmethod
def schedule_info_list() -> List["SmartRequest"]:
"""Get schedule info list."""
return [
SmartRequest.get_schedule_rules(),
SmartRequest.get_next_event(),
]
@staticmethod
def get_countdown_rules(params: Optional[GetRulesParams] = None) -> "SmartRequest":
"""Get countdown rules."""
return SmartRequest(
"get_countdown_rules", params or SmartRequest.GetRulesParams()
)
@staticmethod
def get_antitheft_rules(params: Optional[GetRulesParams] = None) -> "SmartRequest":
"""Get antitheft rules."""
return SmartRequest(
"get_antitheft_rules", params or SmartRequest.GetRulesParams()
)
@staticmethod
def get_led_info(params: Optional[LedStatusParams] = None) -> "SmartRequest":
"""Get led info."""
return SmartRequest("get_led_info", params or SmartRequest.LedStatusParams())
@staticmethod
def get_auto_off_config(params: Optional[GetRulesParams] = None) -> "SmartRequest":
"""Get auto off config."""
return SmartRequest(
"get_auto_off_config", params or SmartRequest.GetRulesParams()
)
@staticmethod
def get_delay_action_info() -> "SmartRequest":
"""Get delay action info."""
return SmartRequest("get_delay_action_info")
@staticmethod
def auto_off_list() -> List["SmartRequest"]:
"""Get energy usage."""
return [
SmartRequest.get_auto_off_config(),
SmartRequest.get_delay_action_info(), # May not live here
]
@staticmethod
def get_energy_usage() -> "SmartRequest":
"""Get energy usage."""
return SmartRequest("get_energy_usage")
@staticmethod
def energy_monitoring_list() -> List["SmartRequest"]:
"""Get energy usage."""
return [
SmartRequest("get_energy_usage"),
SmartRequest.get_raw_request("get_electricity_price_config"),
]
@staticmethod
def get_current_power() -> "SmartRequest":
"""Get current power."""
return SmartRequest("get_current_power")
@staticmethod
def power_protection_list() -> List["SmartRequest"]:
"""Get power protection info list."""
return [
SmartRequest.get_current_power(),
SmartRequest.get_raw_request("get_max_power"),
SmartRequest.get_raw_request("get_protection_power"),
]
@staticmethod
def get_preset_rules(params: Optional[GetRulesParams] = None) -> "SmartRequest":
"""Get preset rules."""
return SmartRequest("get_preset_rules", params or SmartRequest.GetRulesParams())
@staticmethod
def get_auto_light_info() -> "SmartRequest":
"""Get auto light info."""
return SmartRequest("get_auto_light_info")
@staticmethod
def get_dynamic_light_effect_rules(
params: Optional[GetRulesParams] = None
) -> "SmartRequest":
"""Get dynamic light effect rules."""
return SmartRequest(
"get_dynamic_light_effect_rules", params or SmartRequest.GetRulesParams()
)
@staticmethod
def set_device_on(params: DeviceOnParams) -> "SmartRequest":
"""Set device on state."""
return SmartRequest("set_device_info", params)
@staticmethod
def set_light_info(params: LightInfoParams) -> "SmartRequest":
"""Set color temperature."""
return SmartRequest("set_device_info", params)
@staticmethod
def set_dynamic_light_effect_rule_enable(
params: DynamicLightEffectParams
) -> "SmartRequest":
"""Enable dynamic light effect rule."""
return SmartRequest("set_dynamic_light_effect_rule_enable", params)
@staticmethod
def get_component_info_requests(component_nego_response) -> List["SmartRequest"]:
"""Get a list of requests based on the component info response."""
request_list = []
for component in component_nego_response["component_list"]:
if requests := COMPONENT_REQUESTS.get(component["id"]):
request_list.extend(requests)
return request_list
@staticmethod
def _create_request_dict(
smart_request: Union["SmartRequest", List["SmartRequest"]]
) -> dict:
"""Create request dict to be passed to SmartProtocol.query()."""
if isinstance(smart_request, list):
request = {}
for sr in smart_request:
request[sr.method_name] = sr.params
else:
request = smart_request.to_dict()
return request
COMPONENT_REQUESTS = {
"device": SmartRequest.device_info_list(),
"firmware": SmartRequest.firmware_info_list(),
"quick_setup": [SmartRequest.qs_component_nego()],
"inherit": [SmartRequest.get_raw_request("get_inherit_info")],
"time": [SmartRequest.get_device_time()],
"wireless": [SmartRequest.get_wireless_scan_info()],
"schedule": SmartRequest.schedule_info_list(),
"countdown": [SmartRequest.get_countdown_rules()],
"antitheft": [SmartRequest.get_antitheft_rules()],
"account": None,
"synchronize": None, # sync_env
"sunrise_sunset": None, # for schedules
"led": [SmartRequest.get_led_info()],
"cloud_connect": [SmartRequest.get_raw_request("get_connect_cloud_state")],
"iot_cloud": None,
"device_local_time": None,
"default_states": None, # in device_info
"auto_off": [SmartRequest.get_auto_off_config()],
"localSmart": None,
"energy_monitoring": SmartRequest.energy_monitoring_list(),
"power_protection": SmartRequest.power_protection_list(),
"current_protection": None, # overcurrent in device_info
"matter": None,
"preset": [SmartRequest.get_preset_rules()],
"brightness": None, # in device_info
"color": None, # in device_info
"color_temperature": None, # in device_info
"auto_light": [SmartRequest.get_auto_light_info()],
"light_effect": [SmartRequest.get_dynamic_light_effect_rules()],
"bulb_quick_control": None,
"on_off_gradually": [SmartRequest.get_raw_request("get_on_off_gradually_info")],
"light_strip": None,
"light_strip_lighting_effect": [
SmartRequest.get_raw_request("get_lighting_effect")
],
"music_rhythm": None, # music_rhythm_enable in device_info
"segment": [SmartRequest.get_raw_request("get_device_segment")],
"segment_effect": [SmartRequest.get_raw_request("get_segment_effect_rule")],
}