Skip to content

Commit

Permalink
feat: component negotiation (#162)
Browse files Browse the repository at this point in the history
* added get components feature

* make __init__ for responses

* make __init__ for requests and protocol

* import init api module and hub

* restore init
  • Loading branch information
petretiandrea authored Dec 13, 2023
1 parent 0c68ad9 commit 979ef64
Show file tree
Hide file tree
Showing 25 changed files with 194 additions and 42 deletions.
3 changes: 2 additions & 1 deletion plugp100/api/base_tapo_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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()
8 changes: 8 additions & 0 deletions plugp100/api/hub/hub_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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)
8 changes: 8 additions & 0 deletions plugp100/api/hub/ke100_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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)
8 changes: 8 additions & 0 deletions plugp100/api/hub/s200b_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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):
Expand Down
8 changes: 8 additions & 0 deletions plugp100/api/hub/switch_child_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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)
8 changes: 8 additions & 0 deletions plugp100/api/hub/t100_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)
13 changes: 9 additions & 4 deletions plugp100/api/hub/t110_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand All @@ -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)
8 changes: 8 additions & 0 deletions plugp100/api/hub/t31x_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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)
4 changes: 1 addition & 3 deletions plugp100/api/ledstrip_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 1 addition & 3 deletions plugp100/api/light_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
8 changes: 8 additions & 0 deletions plugp100/api/power_strip_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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)
41 changes: 19 additions & 22 deletions plugp100/api/tapo_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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]:
Expand All @@ -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]:
"""
Expand All @@ -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]:
"""
Expand All @@ -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]:
"""
Expand All @@ -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)
Expand All @@ -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():
Expand All @@ -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
Expand All @@ -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]:
"""
Expand Down Expand Up @@ -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)
Expand All @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion plugp100/protocol/tapo_protocol.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down
10 changes: 10 additions & 0 deletions plugp100/requests/tapo_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
24 changes: 24 additions & 0 deletions plugp100/responses/components.py
Original file line number Diff line number Diff line change
@@ -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()]
7 changes: 3 additions & 4 deletions plugp100/responses/hub_childs/ke100_device_state.py
Original file line number Diff line number Diff line change
@@ -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):
Expand Down
1 change: 0 additions & 1 deletion plugp100/responses/hub_childs/t31x_device_state.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import enum
from dataclasses import dataclass
from datetime import datetime
from typing import Any
Expand Down
Loading

0 comments on commit 979ef64

Please sign in to comment.