diff --git a/plugp100/api/base_tapo_device.py b/plugp100/api/base_tapo_device.py index 15384c5..a6ba3ca 100644 --- a/plugp100/api/base_tapo_device.py +++ b/plugp100/api/base_tapo_device.py @@ -2,6 +2,7 @@ from plugp100.common.functional.tri import Try from plugp100.common.utils.json_utils import Json from plugp100.requests.tapo_request import TapoRequest +from plugp100.responses.components import Components from plugp100.responses.device_usage_info import DeviceUsageInfo from plugp100.responses.time_info import TimeInfo @@ -39,5 +40,5 @@ async def get_device_time(self) -> Try[TimeInfo]: async def get_state_as_json(self) -> Try[Json]: return await self._api.get_device_info() - async def get_component_negotiation(self) -> Try[Json]: + async def get_component_negotiation(self) -> Try[Components]: return await self._api.get_component_negotiation() diff --git a/plugp100/api/hub/hub_device.py b/plugp100/api/hub/hub_device.py index c0b2a16..1819847 100644 --- a/plugp100/api/hub/hub_device.py +++ b/plugp100/api/hub/hub_device.py @@ -15,6 +15,7 @@ from plugp100.requests.tapo_request import TapoRequest from plugp100.responses.alarm_type_list import AlarmTypeList from plugp100.responses.child_device_list import ChildDeviceList +from plugp100.responses.components import Components from plugp100.responses.device_state import HubDeviceState HubSubscription = Callable[[], Any] @@ -99,3 +100,10 @@ async def _poll_device_list(self, last_state: Set[str]) -> Set[str]: .map(lambda x: x.get_device_ids()) .get_or_else(set()) ) + + async def get_component_negotiation_child(self, child_device_id) -> Try[Components]: + return ( + await self._api.control_child( + child_device_id, TapoRequest.component_negotiation() + ) + ).map(Components.try_from_json) diff --git a/plugp100/api/hub/ke100_device.py b/plugp100/api/hub/ke100_device.py index fb51534..49f3b98 100644 --- a/plugp100/api/hub/ke100_device.py +++ b/plugp100/api/hub/ke100_device.py @@ -5,6 +5,7 @@ from plugp100.common.utils.json_utils import dataclass_encode_json from plugp100.requests.set_device_info.set_trv_info_params import TRVDeviceInfoParams from plugp100.requests.tapo_request import TapoRequest +from plugp100.responses.components import Components from plugp100.responses.hub_childs.ke100_device_state import KE100DeviceState @@ -51,3 +52,10 @@ async def send_trv_control_request(self, params: TRVDeviceInfoParams) -> Try[boo return (await self._hub.control_child(self._device_id, request)).map( lambda _: True ) + + async def get_component_negotiation(self) -> Try[Components]: + return ( + await self._hub.control_child( + self._device_id, TapoRequest.component_negotiation() + ) + ).map(Components.try_from_json) diff --git a/plugp100/api/hub/s200b_device.py b/plugp100/api/hub/s200b_device.py index 51f6f39..796db04 100644 --- a/plugp100/api/hub/s200b_device.py +++ b/plugp100/api/hub/s200b_device.py @@ -9,6 +9,7 @@ from plugp100.common.state_tracker import StateTracker from plugp100.requests.tapo_request import TapoRequest from plugp100.requests.trigger_logs_params import GetTriggerLogsParams +from plugp100.responses.components import Components from plugp100.responses.hub_childs.s200b_device_state import ( S200BDeviceState, S200BEvent, @@ -85,6 +86,13 @@ async def _poll_event_logs( response = await self.get_event_logs(self._DEFAULT_POLLING_PAGE_SIZE, 0) return response.get_or_else(TriggerLogResponse(0, 0, [])) + async def get_component_negotiation(self) -> Try[Components]: + return ( + await self._hub.control_child( + self._device_id, TapoRequest.component_negotiation() + ) + ).map(Components.try_from_json) + class _EventLogsStateTracker(StateTracker[TriggerLogResponse[S200BEvent], S200BEvent]): def __init__(self, debounce_millis: int, logger: Logger = None): diff --git a/plugp100/api/hub/switch_child_device.py b/plugp100/api/hub/switch_child_device.py index da0c4fd..6c34c27 100644 --- a/plugp100/api/hub/switch_child_device.py +++ b/plugp100/api/hub/switch_child_device.py @@ -4,6 +4,7 @@ from plugp100.common.utils.json_utils import dataclass_encode_json from plugp100.requests.set_device_info.set_plug_info_params import SetPlugInfoParams from plugp100.requests.tapo_request import TapoRequest +from plugp100.responses.components import Components from plugp100.responses.hub_childs.switch_child_device_state import SwitchChildDeviceState @@ -38,3 +39,10 @@ async def off(self) -> Try[bool]: return (await self._hub.control_child(self._device_id, request)).map( lambda _: True ) + + async def get_component_negotiation(self) -> Try[Components]: + return ( + await self._hub.control_child( + self._device_id, TapoRequest.component_negotiation() + ) + ).map(Components.try_from_json) diff --git a/plugp100/api/hub/t100_device.py b/plugp100/api/hub/t100_device.py index a6a444f..dd67c93 100644 --- a/plugp100/api/hub/t100_device.py +++ b/plugp100/api/hub/t100_device.py @@ -3,6 +3,7 @@ from plugp100.common.functional.tri import Try from plugp100.requests.tapo_request import TapoRequest from plugp100.requests.trigger_logs_params import GetTriggerLogsParams +from plugp100.responses.components import Components from plugp100.responses.hub_childs.t100_device_state import ( T100MotionSensorState, T100Event, @@ -32,3 +33,10 @@ async def get_event_logs( return (await self._hub.control_child(self._device_id, request)).flat_map( lambda x: TriggerLogResponse[T100Event].try_from_json(x, parse_t100_event) ) + + async def get_component_negotiation(self) -> Try[Components]: + return ( + await self._hub.control_child( + self._device_id, TapoRequest.component_negotiation() + ) + ).map(Components.try_from_json) diff --git a/plugp100/api/hub/t110_device.py b/plugp100/api/hub/t110_device.py index 1053d69..3cdcc8f 100644 --- a/plugp100/api/hub/t110_device.py +++ b/plugp100/api/hub/t110_device.py @@ -3,11 +3,9 @@ from plugp100.common.functional.tri import Try from plugp100.requests.tapo_request import TapoRequest from plugp100.requests.trigger_logs_params import GetTriggerLogsParams +from plugp100.responses.components import Components from plugp100.responses.hub_childs.t100_device_state import parse_t100_event -from plugp100.responses.hub_childs.t110_device_state import ( - T110SmartDoorState, - T110Event, -) +from plugp100.responses.hub_childs.t110_device_state import T110SmartDoorState, T110Event from plugp100.responses.hub_childs.trigger_log_response import TriggerLogResponse @@ -33,3 +31,10 @@ async def get_event_logs( return response.flat_map( lambda x: TriggerLogResponse[T110Event].try_from_json(x, parse_t100_event) ) + + async def get_component_negotiation(self) -> Try[Components]: + return ( + await self._hub.control_child( + self._device_id, TapoRequest.component_negotiation() + ) + ).map(Components.try_from_json) diff --git a/plugp100/api/hub/t31x_device.py b/plugp100/api/hub/t31x_device.py index b76125c..ecb9efc 100644 --- a/plugp100/api/hub/t31x_device.py +++ b/plugp100/api/hub/t31x_device.py @@ -2,6 +2,7 @@ from plugp100.common.functional.tri import Try from plugp100.requests.tapo_request import TapoRequest +from plugp100.responses.components import Components from plugp100.responses.hub_childs.t31x_device_state import ( T31DeviceState, TemperatureHumidityRecordsRaw, @@ -24,3 +25,10 @@ async def get_temperature_humidity_records( request = TapoRequest.get_temperature_humidity_records() response = await self._hub.control_child(self._device_id, request) return response.flat_map(TemperatureHumidityRecordsRaw.from_json) + + async def get_component_negotiation(self) -> Try[Components]: + return ( + await self._hub.control_child( + self._device_id, TapoRequest.component_negotiation() + ) + ).map(Components.try_from_json) diff --git a/plugp100/api/ledstrip_device.py b/plugp100/api/ledstrip_device.py index e851032..63c03e6 100644 --- a/plugp100/api/ledstrip_device.py +++ b/plugp100/api/ledstrip_device.py @@ -5,9 +5,7 @@ from plugp100.requests.set_device_info.set_light_color_info_params import ( LightColorDeviceInfoParams, ) -from plugp100.requests.set_device_info.set_light_info_params import ( - LightDeviceInfoParams, -) +from plugp100.requests.set_device_info.set_light_info_params import LightDeviceInfoParams from plugp100.requests.set_device_info.set_plug_info_params import SetPlugInfoParams from plugp100.responses.device_state import LedStripDeviceState diff --git a/plugp100/api/light_device.py b/plugp100/api/light_device.py index 416c330..d33d8e7 100644 --- a/plugp100/api/light_device.py +++ b/plugp100/api/light_device.py @@ -4,9 +4,7 @@ from plugp100.requests.set_device_info.set_light_color_info_params import ( LightColorDeviceInfoParams, ) -from plugp100.requests.set_device_info.set_light_info_params import ( - LightDeviceInfoParams, -) +from plugp100.requests.set_device_info.set_light_info_params import LightDeviceInfoParams from plugp100.requests.set_device_info.set_plug_info_params import SetPlugInfoParams from plugp100.responses.device_state import LightDeviceState diff --git a/plugp100/api/power_strip_device.py b/plugp100/api/power_strip_device.py index 1dde40f..69498e7 100644 --- a/plugp100/api/power_strip_device.py +++ b/plugp100/api/power_strip_device.py @@ -5,6 +5,7 @@ from plugp100.requests.set_device_info.set_plug_info_params import SetPlugInfoParams from plugp100.requests.tapo_request import TapoRequest from plugp100.responses.child_device_list import PowerStripChild +from plugp100.responses.components import Components from plugp100.responses.device_state import PlugDeviceState @@ -44,3 +45,10 @@ async def off(self, child_device_id: str) -> Try[bool]: async def _control_child(self, device_id: str, request: TapoRequest) -> Try[Json]: return await self._api.control_child(device_id, request) + + async def get_component_negotiation_child(self, child_device_id) -> Try[Components]: + return ( + await self._control_child( + child_device_id, TapoRequest.component_negotiation() + ) + ).map(Components.try_from_json) diff --git a/plugp100/api/tapo_client.py b/plugp100/api/tapo_client.py index 2ccde59..e429a69 100644 --- a/plugp100/api/tapo_client.py +++ b/plugp100/api/tapo_client.py @@ -7,13 +7,14 @@ from plugp100.api.light_effect import LightEffect from plugp100.common.credentials import AuthCredential -from plugp100.common.functional.tri import Try, Success, Failure -from plugp100.common.utils.json_utils import dataclass_encode_json, Json +from plugp100.common.functional.tri import Try, Failure, Success +from plugp100.common.utils.json_utils import Json, dataclass_encode_json from plugp100.protocol.klap_protocol import KlapProtocol from plugp100.protocol.passthrough_protocol import PassthroughProtocol from plugp100.protocol.tapo_protocol import TapoProtocol from plugp100.requests.tapo_request import TapoRequest, MultipleRequestParams from plugp100.responses.child_device_list import ChildDeviceList +from plugp100.responses.components import Components from plugp100.responses.energy_info import EnergyInfo from plugp100.responses.power_info import PowerInfo from plugp100.responses.tapo_exception import TapoException @@ -78,9 +79,9 @@ async def execute_raw_request(self, request: "TapoRequest") -> Try[Json]: ), "You must initialize client before send requests" return (await self._protocol.send_request(request)).map(lambda x: x.result) - async def get_component_negotiation(self) -> Try[Json]: - return await self.execute_raw_request( - TapoRequest(method="component_nego", params=None) + async def get_component_negotiation(self) -> Try[Components]: + return (await self.execute_raw_request(TapoRequest.component_negotiation())).map( + Components.try_from_json ) async def get_device_info(self) -> Try[Json]: @@ -93,9 +94,7 @@ async def get_device_info(self) -> Try[Json]: self._protocol is not None ), "You must initialize client before send requests" get_info_request = TapoRequest.get_device_info() - return (await self._protocol.send_request(get_info_request)).map( - lambda x: x.result - ) + return await self.execute_raw_request(get_info_request) async def get_energy_usage(self) -> Try[EnergyInfo]: """ @@ -107,8 +106,8 @@ async def get_energy_usage(self) -> Try[EnergyInfo]: self._protocol is not None ), "You must initialize client before send requests" get_energy_request = TapoRequest.get_energy_usage() - response = await self._protocol.send_request(get_energy_request) - return response.map(lambda x: EnergyInfo(x.result)) + response = await self.execute_raw_request(get_energy_request) + return response.map(EnergyInfo) async def get_current_power(self) -> Try[PowerInfo]: """ @@ -120,8 +119,8 @@ async def get_current_power(self) -> Try[PowerInfo]: self._protocol is not None ), "You must initialize client before send requests" get_current_power = TapoRequest.get_current_power() - response = await self._protocol.send_request(get_current_power) - return response.map(lambda x: PowerInfo(x.result)) + response = await self.execute_raw_request(get_current_power) + return response.map(PowerInfo) async def set_device_info(self, device_info: Any) -> Try[bool]: """ @@ -146,7 +145,7 @@ async def set_lighting_effect(self, light_effect: LightEffect) -> Try[bool]: assert ( self._protocol is not None ), "You must initialize client before send requests" - response = await self._protocol.send_request( + response = await self.execute_raw_request( TapoRequest.set_lighting_effect(light_effect) ) return response.map(lambda _: True) @@ -161,8 +160,8 @@ async def get_child_device_list(self, all_pages: bool = True) -> Try[ChildDevice self._protocol is not None ), "You must initialize client before send requests" request = TapoRequest.get_child_device_list(0) - response = (await self._protocol.send_request(request)).map( - lambda x: ChildDeviceList.try_from_json(**x.result) + response = (await self.execute_raw_request(request)).map( + lambda x: ChildDeviceList.try_from_json(**x) ) if all_pages and response.is_success(): @@ -176,8 +175,8 @@ async def _get_all_pagination(self, head: ChildDeviceList) -> Try[ChildDeviceLis previous_head = current_head.get() request = TapoRequest.get_child_device_list(previous_head.get_next_index()) current_head = ( - (await self._protocol.send_request(request)) - .map(lambda x: ChildDeviceList.try_from_json(**x.result)) + (await self.execute_raw_request(request)) + .map(lambda x: ChildDeviceList.try_from_json(**x)) .map(lambda x: previous_head.merge(x)) ) return current_head @@ -192,7 +191,7 @@ async def get_child_device_component_list(self) -> Try[Json]: self._protocol is not None ), "You must initialize client before send requests" request = TapoRequest.get_child_device_component_list() - return (await self._protocol.send_request(request)).map(lambda x: x.result) + return (await self.execute_raw_request(request)).map(lambda x: x) async def control_child(self, child_id: str, request: TapoRequest) -> Try[Json]: """ @@ -233,7 +232,7 @@ async def _set_device_info(self, device_info: Json) -> Try[bool]: assert ( self._protocol is not None ), "You must initialize client before send requests" - response = await self._protocol.send_request( + response = await self.execute_raw_request( TapoRequest.set_device_info(device_info) ) return response.map(lambda _: True) @@ -244,9 +243,7 @@ async def _guess_protocol(self): url=self._url, http_session=self._http_session, ) - response = await self.execute_raw_request( - TapoRequest(method="component_nego", params=None) - ) + response = await self.get_component_negotiation() if response.is_failure(): error = response.error() if isinstance(error, TapoException) and error.error_code == 1003: diff --git a/plugp100/protocol/tapo_protocol.py b/plugp100/protocol/tapo_protocol.py index 9103257..f3bf8f0 100644 --- a/plugp100/protocol/tapo_protocol.py +++ b/plugp100/protocol/tapo_protocol.py @@ -1,5 +1,5 @@ import abc -from typing import Optional, Any +from typing import Any from plugp100.common.functional.tri import Try from plugp100.requests.tapo_request import TapoRequest diff --git a/plugp100/requests/tapo_request.py b/plugp100/requests/tapo_request.py index 3fbe23d..b5256d7 100644 --- a/plugp100/requests/tapo_request.py +++ b/plugp100/requests/tapo_request.py @@ -81,6 +81,10 @@ def get_child_event_logs(trigger_log_params: GetTriggerLogsParams) -> "TapoReque def get_temperature_humidity_records() -> "TapoRequest": return TapoRequest(method="get_temp_humidity_records", params=None) + @staticmethod + def component_negotiation() -> "TapoRequest": + return TapoRequest(method="component_nego", params=None) + def __init__(self, method: str, params): self.method = method self.params = params @@ -103,6 +107,12 @@ def get_params(self): def get_method(self): return self.method + def __eq__(self, other) -> bool: + if not isinstance(other, TapoRequest): + return False + + return self.method == other.method and self.params == other.params + # moved here to avoid circular import in python @dataclass diff --git a/plugp100/responses/components.py b/plugp100/responses/components.py new file mode 100644 index 0000000..127d9ef --- /dev/null +++ b/plugp100/responses/components.py @@ -0,0 +1,24 @@ +import dataclasses +from typing import Any, Optional + + +@dataclasses.dataclass +class Components: + component_list: dict[str, Any] + + @staticmethod + def try_from_json(data: dict[str, Any]) -> "Components": + components = data.get("component_list", []) + return Components({c["id"]: c["ver_code"] for c in components}) + + def __contains__(self, item): + return self.has(item) + + def has(self, component_name: str) -> bool: + return self.get_version(component_name) is not None + + def get_version(self, component_name: str) -> Optional[int]: + return self.component_list.get(component_name, None) + + def as_list(self) -> list[str]: + return [c for c in self.component_list.keys()] diff --git a/plugp100/responses/hub_childs/ke100_device_state.py b/plugp100/responses/hub_childs/ke100_device_state.py index b7ffe4a..ee49ae8 100644 --- a/plugp100/responses/hub_childs/ke100_device_state.py +++ b/plugp100/responses/hub_childs/ke100_device_state.py @@ -1,11 +1,10 @@ -from enum import Enum from dataclasses import dataclass -from datetime import datetime +from enum import Enum from typing import Any -from plugp100.responses.temperature_unit import TemperatureUnit -from plugp100.responses.hub_childs.hub_child_base_info import HubChildBaseInfo from plugp100.common.functional.tri import Try +from plugp100.responses.hub_childs.hub_child_base_info import HubChildBaseInfo +from plugp100.responses.temperature_unit import TemperatureUnit class TRVState(Enum): diff --git a/plugp100/responses/hub_childs/t31x_device_state.py b/plugp100/responses/hub_childs/t31x_device_state.py index fcd4b93..677cf7e 100644 --- a/plugp100/responses/hub_childs/t31x_device_state.py +++ b/plugp100/responses/hub_childs/t31x_device_state.py @@ -1,4 +1,3 @@ -import enum from dataclasses import dataclass from datetime import datetime from typing import Any diff --git a/tests/integration/test_button_t310.py b/tests/integration/test_button_t310.py index d4bebcd..ab26e79 100644 --- a/tests/integration/test_button_t310.py +++ b/tests/integration/test_button_t310.py @@ -51,3 +51,13 @@ async def test_should_get_temperature_humidity_records(self): self.assertTrue(len(state.past24h_humidity) > 0) self.assertTrue(len(state.past24_temperature_exceptions) > 0) self.assertTrue(len(state.past24h_humidity_exceptions) > 0) + + async def test_has_components(self): + state = (await self._device.get_component_negotiation()).get_or_raise() + self.assertTrue(len(state.as_list()) > 0) + self.assertTrue(state.has("humidity")) + self.assertTrue(state.has("temperature")) + self.assertTrue(state.has("temp_humidity_record")) + self.assertTrue(state.has("comfort_temperature")) + self.assertTrue(state.has("comfort_humidity")) + self.assertTrue(state.has("battery_detect")) diff --git a/tests/integration/test_hub.py b/tests/integration/test_hub.py index 4c86dff..a0e00ea 100644 --- a/tests/integration/test_hub.py +++ b/tests/integration/test_hub.py @@ -61,3 +61,18 @@ async def test_should_subscribe_to_association_changes(self): unsub = self._device.subscribe_device_association(lambda x: print(x)) await asyncio.sleep(10) unsub() + + async def test_has_components(self): + state = (await self._device.get_component_negotiation()).get_or_raise() + self.assertTrue(len(state.as_list()) > 0) + self.assertTrue(state.has("child_device")) + self.assertTrue(state.has("control_child")) + self.assertTrue(state.has("alarm")) + + async def test_children_has_components(self): + children = (await self._device.get_children()).get_or_raise() + for child in children.get_children_base_info(): + state = ( + await self._device.get_component_negotiation_child(child.device_id) + ).get_or_raise() + self.assertTrue(len(state.as_list()) > 0) diff --git a/tests/integration/test_ledstrip.py b/tests/integration/test_ledstrip.py index 91fc342..3d84995 100644 --- a/tests/integration/test_ledstrip.py +++ b/tests/integration/test_ledstrip.py @@ -75,3 +75,11 @@ async def test_should_set_brightness_of_light_effect(self): state = (await self._device.get_state()).get_or_raise() self.assertEqual(40, state.brightness) self.assertEqual(LightEffect.aurora().name, state.lighting_effect.name) + + async def test_has_components(self): + state = (await self._device.get_component_negotiation()).get_or_raise() + self.assertTrue(len(state.as_list()) > 0) + self.assertTrue(state.has("brightness")) + self.assertTrue(state.has("color")) + self.assertTrue(state.has("color_temperature")) + self.assertTrue(state.has("light_strip_lighting_effect")) diff --git a/tests/integration/test_light.py b/tests/integration/test_light.py index 324f0f8..76bd598 100644 --- a/tests/integration/test_light.py +++ b/tests/integration/test_light.py @@ -55,3 +55,11 @@ async def test_should_set_color_temperature(self): await self._device.set_color_temperature(2780) state = (await self._device.get_state()).get_or_raise() self.assertEqual(2780, state.color_temp) + + async def test_has_components(self): + state = (await self._device.get_component_negotiation()).get_or_raise() + self.assertTrue(len(state.as_list()) > 0) + self.assertTrue(state.has("brightness")) + self.assertTrue(state.has("color")) + self.assertTrue(state.has("color_temperature")) + self.assertTrue(state.has("light_effect")) diff --git a/tests/integration/test_plug.py b/tests/integration/test_plug.py index c57318b..373ee89 100644 --- a/tests/integration/test_plug.py +++ b/tests/integration/test_plug.py @@ -38,3 +38,7 @@ async def test_should_turn_off(self): await self._device.off() state = (await self._device.get_state()).get_or_raise() self.assertEqual(False, state.device_on) + + async def test_has_components(self): + state = (await self._device.get_component_negotiation()).get_or_raise() + self.assertTrue(len(state.as_list()) > 0) diff --git a/tests/integration/test_power_strip.py b/tests/integration/test_power_strip.py index 059d881..11964ee 100644 --- a/tests/integration/test_power_strip.py +++ b/tests/integration/test_power_strip.py @@ -54,3 +54,17 @@ async def test_should_expose_sub_info_each_socket(self): self.assertIsNot(socket.nickname, "") self.assertIsNotNone(socket.device_id) self.assertIsNotNone(socket.original_device_id) + + async def test_has_components(self): + state = (await self._device.get_component_negotiation()).get_or_raise() + self.assertTrue(len(state.as_list()) > 0) + self.assertTrue(state.has("control_child")) + self.assertTrue(state.has("child_device")) + + async def test_children_has_components(self): + children = (await self._device.get_children()).get_or_raise() + for socket_id, _ in children.items(): + state = ( + await self._device.get_component_negotiation_child(socket_id) + ).get_or_raise() + self.assertTrue(len(state.as_list()) > 0) diff --git a/tests/integration/test_sensor_s200b.py b/tests/integration/test_sensor_s200b.py index abf6da2..7d514fa 100644 --- a/tests/integration/test_sensor_s200b.py +++ b/tests/integration/test_sensor_s200b.py @@ -64,3 +64,10 @@ async def test_should_poll_button_events(self): unsub = self._device.subscribe_event_logs(lambda event: print(event)) await asyncio.sleep(60) unsub() + + async def test_has_components(self): + state = (await self._device.get_component_negotiation()).get_or_raise() + self.assertTrue(len(state.as_list()) > 0) + self.assertTrue(state.has("trigger_log")) + self.assertTrue(state.has("battery_detect")) + self.assertTrue(state.has("double_click")) diff --git a/tests/integration/test_tapo_discovery.py b/tests/integration/test_tapo_discovery.py index 319bb6c..d3aeae1 100755 --- a/tests/integration/test_tapo_discovery.py +++ b/tests/integration/test_tapo_discovery.py @@ -1,8 +1,7 @@ -import unittest import socket import threading -import json -import base64 +import unittest + import select from plugp100.discovery.tapo_discovery import (