Skip to content

Commit

Permalink
removed static factory connect with initialize + stabilize klap sessi…
Browse files Browse the repository at this point in the history
…on + fix Try[True] (#135)
  • Loading branch information
petretiandrea committed Oct 7, 2023
1 parent ef96180 commit 1923265
Show file tree
Hide file tree
Showing 18 changed files with 154 additions and 88 deletions.
19 changes: 13 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,22 @@ The purpose of this fork is to provide the library as PyPi package.
import asyncio
import os

from plugp100.api.hub.hub_device import HubDevice
from plugp100.api.light_effect_preset import LightEffectPreset
from plugp100.api.tapo_client import TapoClient, AuthCredential
from plugp100.api.tapo_client import TapoClient
from plugp100.common.credentials import AuthCredential


async def main():
# create generic tapo api
username = os.getenv('USERNAME', '<tapo_email>')
password = os.getenv('PASSWORD', '<tapo_password>')
username = os.getenv("USERNAME", "<tapo_email>")
password = os.getenv("PASSWORD", "<tapo_password>")

credential = AuthCredential(username, password)
client = await TapoClient.connect(credential, "<ip_address>")
credentials = AuthCredential(username, password)
client = TapoClient(credentials, "<tapo_device_ip>")
await client.initialize()

print(await client.get_device_info())
print(await client.get_device_usage())
print(await client.get_energy_usage())
print(await client.get_current_power())
print(await client.get_child_device_list())
Expand All @@ -36,6 +38,11 @@ async def main():
# light = LightDevice(TapoClient(username, password), "<tapo_device_ip>")
# ledstrip = LedStripDevice(TapoClient(username, password), "<tapo_device_ip>")

# - hub example
# hub = HubDevice(client)
# print(await hub.get_children())
# print(await hub.get_state_as_json())


if __name__ == "__main__":
loop = asyncio.new_event_loop()
Expand Down
2 changes: 1 addition & 1 deletion plugp100/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@
from plugp100.api import *
from plugp100.common import *

__version__ = "3.10.3"
__version__ = "3.11.0"
4 changes: 2 additions & 2 deletions plugp100/api/hub/hub_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,14 @@ def __init__(self, api: TapoClient, logger: Logger = None):
self._tracking_subscriptions: List[Callable[[HubDeviceEvent], Any]] = []
self._logger = logger if logger is not None else logging.getLogger("HubDevice")

async def turn_alarm_on(self, alarm: PlayAlarmParams = None) -> Try[True]:
async def turn_alarm_on(self, alarm: PlayAlarmParams = None) -> Try[bool]:
request = TapoRequest(
method="play_alarm",
params=dataclass_encode_json(alarm) if alarm is not None else None,
)
return (await self._api.execute_raw_request(request)).map(lambda _: True)

async def turn_alarm_off(self) -> Try[True]:
async def turn_alarm_off(self) -> Try[bool]:
return (
await self._api.execute_raw_request(
TapoRequest(method="stop_alarm", params=None)
Expand Down
14 changes: 7 additions & 7 deletions plugp100/api/ledstrip_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,33 +21,33 @@ async def get_state(self) -> Try[LedStripDeviceState]:
LedStripDeviceState.try_from_json
)

async def on(self) -> Try[True]:
async def on(self) -> Try[bool]:
return await self._api.set_device_info(SetPlugInfoParams(True))

async def off(self) -> Try[True]:
async def off(self) -> Try[bool]:
return await self._api.set_device_info(SetPlugInfoParams(False))

async def set_brightness(self, brightness: int) -> Try[True]:
async def set_brightness(self, brightness: int) -> Try[bool]:
return await self._api.set_device_info(
LightDeviceInfoParams(brightness=brightness)
)

async def set_hue_saturation(self, hue: int, saturation: int) -> Try[True]:
async def set_hue_saturation(self, hue: int, saturation: int) -> Try[bool]:
return await self._api.set_device_info(
LightColorDeviceInfoParams(hue=hue, saturation=saturation, color_temp=0)
)

async def set_color_temperature(self, color_temperature: int) -> Try[True]:
async def set_color_temperature(self, color_temperature: int) -> Try[bool]:
return await self._api.set_device_info(
LightColorDeviceInfoParams(color_temp=color_temperature)
)

async def set_light_effect(self, effect: LightEffect) -> Try[True]:
async def set_light_effect(self, effect: LightEffect) -> Try[bool]:
return await self._api.set_lighting_effect(effect)

async def set_light_effect_brightness(
self, effect: LightEffect, brightness: int
) -> Try[True]:
) -> Try[bool]:
effect.brightness = brightness
effect.bAdjusted = 1
effect.enable = 1
Expand Down
10 changes: 5 additions & 5 deletions plugp100/api/light_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,21 @@ async def get_state(self) -> Try[LightDeviceState]:
LightDeviceState.try_from_json
)

async def on(self) -> Try[True]:
async def on(self) -> Try[bool]:
"""
The function `on` turns on light
@return: an instance of the `Either` class, which can hold either a `True` value or an `Exception` object.
"""
return await self._api.set_device_info(SetPlugInfoParams(True))

async def off(self) -> Try[True]:
async def off(self) -> Try[bool]:
"""
The function `off` turns off light
@return: an `Either` object, which can either be `True` or an `Exception`.
"""
return await self._api.set_device_info(SetPlugInfoParams(False))

async def set_brightness(self, brightness: int) -> Try[True]:
async def set_brightness(self, brightness: int) -> Try[bool]:
"""
The function sets the brightness of a device using an API call and returns either True if successful or an Exception
if there is an error.
Expand All @@ -54,7 +54,7 @@ async def set_brightness(self, brightness: int) -> Try[True]:
LightDeviceInfoParams(brightness=brightness)
)

async def set_hue_saturation(self, hue: int, saturation: int) -> Try[True]:
async def set_hue_saturation(self, hue: int, saturation: int) -> Try[bool]:
"""
The function sets the hue and saturation of a light device using the provided values.
Expand All @@ -73,7 +73,7 @@ async def set_hue_saturation(self, hue: int, saturation: int) -> Try[True]:
LightColorDeviceInfoParams(hue=hue, saturation=saturation, color_temp=0)
)

async def set_color_temperature(self, color_temperature: int) -> Try[True]:
async def set_color_temperature(self, color_temperature: int) -> Try[bool]:
"""
The function sets the color temperature of a light device using the provided color temperature value.
Expand Down
4 changes: 2 additions & 2 deletions plugp100/api/plug_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@ async def get_state(self) -> Try[PlugDeviceState]:
"""
return (await self._api.get_device_info()).flat_map(PlugDeviceState.try_from_json)

async def on(self) -> Try[True]:
async def on(self) -> Try[bool]:
"""
The function `on` sets the device info to True using the `SetPlugInfoParams` class.
@return: an instance of the `Either` class, which can hold either a `True` value or an `Exception` object.
"""
return await self._api.set_device_info(SetPlugInfoParams(True))

async def off(self) -> Try[True]:
async def off(self) -> Try[bool]:
"""
The function `off` sets the device info to False using the `SetPlugInfoParams` class.
@return: an `Either` object, which can either be `True` or an `Exception`.
Expand Down
4 changes: 2 additions & 2 deletions plugp100/api/power_strip_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,13 @@ async def get_children(self) -> Try[dict[str, PowerStripChild]]:
.map(lambda x: {child.device_id: child for child in x})
)

async def on(self, child_device_id: str) -> Try[True]:
async def on(self, child_device_id: str) -> Try[bool]:
request = TapoRequest.set_device_info(
dataclass_encode_json(SetPlugInfoParams(device_on=True))
)
return (await self._control_child(child_device_id, request)).map(lambda _: True)

async def off(self, child_device_id: str) -> Try[True]:
async def off(self, child_device_id: str) -> Try[bool]:
request = TapoRequest.set_device_info(
dataclass_encode_json(SetPlugInfoParams(device_on=False))
)
Expand Down
122 changes: 78 additions & 44 deletions plugp100/api/tapo_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,63 +24,50 @@
class TapoProtocolType(Enum):
KLAP = 1
PASSTHROUGH = 2
AUTO = 3


class TapoClient:
@staticmethod
async def connect(
auth_credential: AuthCredential,
ip_address: str,
http_session: Optional[aiohttp.ClientSession] = None,
) -> "TapoClient":
# first try default protocol
api = TapoClient(
auth_credential,
ip_address,
TapoProtocolType.PASSTHROUGH,
http_session,
)
response = await api.execute_raw_request(
TapoRequest(method="component_nego", params=None)
)
if response.is_failure():
error = response.error()
if isinstance(error, TapoException) and error.error_code == 1003:
logger.warning("Default protocol not working, fallback to KLAP ;)")
await api.close()
return TapoClient(
auth_credential,
ip_address,
TapoProtocolType.KLAP,
http_session,
)
return api

def __init__(
self,
auth_credential: AuthCredential,
ip_address: str,
protocol_type: TapoProtocolType = TapoProtocolType.PASSTHROUGH,
port: Optional[int] = 80,
http_session: Optional[aiohttp.ClientSession] = None,
protocol_type: TapoProtocolType = TapoProtocolType.AUTO,
):
self._protocol: TapoProtocol = (
KlapProtocol(
auth_credential=auth_credential,
host=ip_address,
http_session=http_session,
self._auth_credential = auth_credential
self._ip_address = ip_address
self._port = port
self._http_session = http_session
self._protocol_type = protocol_type
self._protocol: Optional[TapoProtocol] = None

async def initialize(self):
if self._protocol_type == TapoProtocolType.KLAP:
self._protocol = KlapProtocol(
auth_credential=self._auth_credential,
host=self._ip_address,
port=self._port,
http_session=self._http_session,
)
if protocol_type == TapoProtocolType.KLAP
else PassthroughProtocol(
auth_credential=auth_credential,
host=ip_address,
http_session=http_session,
elif self._protocol_type == TapoProtocolType.PASSTHROUGH:
self._protocol = PassthroughProtocol(
auth_credential=self._auth_credential,
host=self._ip_address,
port=self._port,
http_session=self._http_session,
)
)
else:
await self._guess_protocol()

async def close(self):
await self._protocol.close()

async def execute_raw_request(self, request: "TapoRequest") -> Try[Json]:
assert (
self._protocol is not None
), "You must initialize client before send requests"
return (await self._protocol.send_request(request)).map(lambda x: x.result)

async def get_device_info(self) -> Try[Json]:
Expand All @@ -89,6 +76,9 @@ async def get_device_info(self) -> Try[Json]:
exception.
@return: an `Either` object that contains either a `Json` object or an `Exception`.
"""
assert (
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
Expand All @@ -100,6 +90,9 @@ async def get_energy_usage(self) -> Try[EnergyInfo]:
or an exception.
@return: an `Either` type, which can either contain an `EnergyInfo` object or an `Exception` object.
"""
assert (
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))
Expand All @@ -110,11 +103,14 @@ async def get_current_power(self) -> Try[PowerInfo]:
`PowerInfo` object, or an `Exception` if an error occurs.
@return: an `Either` object that contains either a `PowerInfo` object or an `Exception`.
"""
assert (
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))

async def set_device_info(self, device_info: Any) -> Try[True]:
async def set_device_info(self, device_info: Any) -> Try[bool]:
"""
The function `set_device_info` encodes the `device_info` object into JSON format and returns either `True` or an
`Exception`.
Expand All @@ -125,7 +121,7 @@ async def set_device_info(self, device_info: Any) -> Try[True]:
"""
return await self._set_device_info(dataclass_encode_json(device_info))

async def set_lighting_effect(self, light_effect: LightEffect) -> Try[True]:
async def set_lighting_effect(self, light_effect: LightEffect) -> Try[bool]:
"""
The function `set_lighting_effect` sets a lighting effect for a device and returns either `True` or an exception.
Expand All @@ -134,6 +130,9 @@ async def set_lighting_effect(self, light_effect: LightEffect) -> Try[True]:
@type light_effect: LightEffect
@return: an `Either` object that contains either a `True` value or an `Exception`.
"""
assert (
self._protocol is not None
), "You must initialize client before send requests"
response = await self._protocol.send_request(
TapoRequest.set_lighting_effect(light_effect)
)
Expand All @@ -145,6 +144,9 @@ async def get_child_device_list(self) -> Try[ChildDeviceList]:
an exception.
@return: an `Either` object, which can contain either a `ChildDeviceList` or an `Exception`.
"""
assert (
self._protocol is not None
), "You must initialize client before send requests"
request = TapoRequest.get_child_device_list()
return (await self._protocol.send_request(request)).map(
lambda x: ChildDeviceList.try_from_json(**x.result)
Expand All @@ -156,6 +158,9 @@ async def get_child_device_component_list(self) -> Try[Json]:
returns either the JSON response or an exception.
@return: an `Either` object, which can contain either a `Json` object or an `Exception`.
"""
assert (
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)

Expand All @@ -171,6 +176,9 @@ async def control_child(self, child_id: str, request: TapoRequest) -> Try[Json]:
@type request: TapoRequest
@return: an instance of the `Either` class, which can contain either a `Json` object or an `Exception`.
"""
assert (
self._protocol is not None
), "You must initialize client before send requests"
multiple_request = TapoRequest.multiple_request(
MultipleRequestParams([request])
).with_request_time_millis(round(time() * 1000))
Expand All @@ -191,8 +199,34 @@ async def control_child(self, child_id: str, request: TapoRequest) -> Try[Json]:
return Failure(e)
return cast(Failure, response)

async def _set_device_info(self, device_info: Json) -> Try[True]:
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(
TapoRequest.set_device_info(device_info)
)
return response.map(lambda _: True)

async def _guess_protocol(self):
self._protocol = PassthroughProtocol(
auth_credential=self._auth_credential,
host=self._ip_address,
port=self._port,
http_session=self._http_session,
)
response = await self.execute_raw_request(
TapoRequest(method="component_nego", params=None)
)
if response.is_failure():
error = response.error()
if isinstance(error, TapoException) and error.error_code == 1003:
logger.warning("Default protocol not working, fallback to KLAP ;)")
self._protocol = KlapProtocol(
auth_credential=self._auth_credential,
host=self._ip_address,
port=self._port,
http_session=self._http_session,
)
else:
raise error
Loading

0 comments on commit 1923265

Please sign in to comment.