"""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 """ from __future__ import annotations import logging from dataclasses import asdict, dataclass _LOGGER = logging.getLogger(__name__) class SmartRequest: """Class to represent a smart protocol request.""" def __init__(self, method_name: str, params: SmartRequestParams | None = 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 GetScheduleRulesParams(SmartRequestParams): """Get Rules Params.""" start_index: int = 0 schedule_mode: str = "" @dataclass class GetTriggerLogsParams(SmartRequestParams): """Trigger Logs params.""" page_size: int = 5 start_id: int = 0 @dataclass class LedStatusParams(SmartRequestParams): """LED Status params.""" led_rule: str | None = 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: int | None = None color_temp: int | None = None hue: int | None = None saturation: int | None = None @dataclass class DynamicLightEffectParams(SmartRequestParams): """LightInfo params.""" enable: bool id: str | None = None @staticmethod def get_raw_request( method: str, params: SmartRequestParams | None = 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(ver_code) -> list[SmartRequest]: """Get device info list.""" if ver_code == 1: return [SmartRequest.get_device_info()] return [ SmartRequest.get_device_info(), SmartRequest.get_device_usage(), SmartRequest.get_auto_update_info(), ] @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_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_child_device_list() -> SmartRequest: """Get child device list.""" return SmartRequest("get_child_device_list") @staticmethod def get_child_device_component_list() -> SmartRequest: """Get child device component list.""" return SmartRequest("get_child_device_component_list") @staticmethod def get_wireless_scan_info( params: GetRulesParams | None = None, ) -> SmartRequest: """Get wireless scan info.""" return SmartRequest( "get_wireless_scan_info", params or SmartRequest.GetRulesParams() ) @staticmethod def get_schedule_rules(params: GetRulesParams | None = None) -> SmartRequest: """Get schedule rules.""" return SmartRequest( "get_schedule_rules", params or SmartRequest.GetScheduleRulesParams() ) @staticmethod def get_next_event(params: GetRulesParams | None = 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: GetRulesParams | None = None) -> SmartRequest: """Get countdown rules.""" return SmartRequest( "get_countdown_rules", params or SmartRequest.GetRulesParams() ) @staticmethod def get_antitheft_rules(params: GetRulesParams | None = None) -> SmartRequest: """Get antitheft rules.""" return SmartRequest( "get_antitheft_rules", params or SmartRequest.GetRulesParams() ) @staticmethod def get_led_info(params: LedStatusParams | None = None) -> SmartRequest: """Get led info.""" return SmartRequest("get_led_info", params or SmartRequest.LedStatusParams()) @staticmethod def get_auto_off_config(params: GetRulesParams | None = 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_emeter_data"), SmartRequest("get_emeter_vgain_igain"), 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: GetRulesParams | None = None) -> SmartRequest: """Get preset rules.""" return SmartRequest("get_preset_rules", params or SmartRequest.GetRulesParams()) @staticmethod def get_on_off_gradually_info( params: SmartRequestParams | None = None, ) -> SmartRequest: """Get preset rules.""" return SmartRequest( "get_on_off_gradually_info", params or SmartRequest.SmartRequestParams() ) @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: GetRulesParams | None = 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: list[SmartRequest] = [] for component in component_nego_response["component_list"]: if ( requests := get_component_requests( component["id"], int(component["ver_code"]) ) ) is not None: request_list.extend(requests) return request_list @staticmethod def _create_request_dict( smart_request: 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 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 SmartRequest._create_request_dict(cr(ver_code)) return SmartRequest._create_request_dict(cr) 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": [], "synchronize": [], # sync_env "sunrise_sunset": [], # for schedules "led": [SmartRequest.get_led_info()], "cloud_connect": [SmartRequest.get_raw_request("get_connect_cloud_state")], "iot_cloud": [], "device_local_time": [], "default_states": [], # in device_info "auto_off": [SmartRequest.get_auto_off_config()], "localSmart": [], "energy_monitoring": SmartRequest.energy_monitoring_list(), "power_protection": SmartRequest.power_protection_list(), "current_protection": [], # overcurrent in device_info "matter": [SmartRequest.get_raw_request("get_matter_setup_info")], "preset": [SmartRequest.get_preset_rules()], "brightness": [], # in device_info "color": [], # in device_info "color_temperature": [], # in device_info "auto_light": [SmartRequest.get_auto_light_info()], "light_effect": [SmartRequest.get_dynamic_light_effect_rules()], "bulb_quick_control": [], "on_off_gradually": [SmartRequest.get_on_off_gradually_info()], "light_strip": [], "light_strip_lighting_effect": [ SmartRequest.get_raw_request("get_lighting_effect") ], "music_rhythm": [], # music_rhythm_enable in device_info "segment": [SmartRequest.get_raw_request("get_device_segment")], "segment_effect": [SmartRequest.get_raw_request("get_segment_effect_rule")], "device_load": [SmartRequest.get_raw_request("get_device_load_info")], "child_quick_setup": [ SmartRequest.get_raw_request("get_support_child_device_category") ], "alarm": [ SmartRequest.get_raw_request("get_support_alarm_type_list"), SmartRequest.get_raw_request("get_alarm_configure"), ], "alarm_logs": [SmartRequest.get_raw_request("get_alarm_triggers")], "trigger_log": [ SmartRequest.get_raw_request( "get_trigger_logs", SmartRequest.GetTriggerLogsParams() ) ], "double_click": [SmartRequest.get_raw_request("get_double_click_info")], "child_device": [ SmartRequest.get_raw_request("get_child_device_list"), SmartRequest.get_raw_request("get_child_device_component_list"), ], "control_child": [], "homekit": [SmartRequest.get_raw_request("get_homekit_info")], "dimmer_calibration": [], "fan_control": [], "overheat_protection": [], # Vacuum components "clean": [ SmartRequest.get_raw_request("getCleanRecords"), SmartRequest.get_raw_request("getVacStatus"), ], "battery": [SmartRequest.get_raw_request("getBatteryInfo")], "consumables": [SmartRequest.get_raw_request("getConsumablesInfo")], "direction_control": [], "button_and_led": [], "speaker": [ SmartRequest.get_raw_request("getSupportVoiceLanguage"), SmartRequest.get_raw_request("getCurrentVoiceLanguage"), ], "map": [ SmartRequest.get_raw_request("getMapInfo"), SmartRequest.get_raw_request("getMapData"), ], "auto_change_map": [SmartRequest.get_raw_request("getAutoChangeMap")], "dust_bucket": [SmartRequest.get_raw_request("getAutoDustCollection")], "mop": [SmartRequest.get_raw_request("getMopState")], "do_not_disturb": [SmartRequest.get_raw_request("getDoNotDisturb")], "charge_pose_clean": [], "continue_breakpoint_sweep": [], "goto_point": [], }