From b44065fa489a40a3db9dc80a233be72b5391580d Mon Sep 17 00:00:00 2001 From: Dinculescu Date: Sat, 27 Jan 2024 08:38:31 +0000 Subject: [PATCH] tapo-py: Add support for the `.set()` API --- README.md | 2 +- tapo-py/examples/tapo_l530.py | 6 ++ tapo-py/src/handlers/color_light_handler.rs | 80 ++++++++++++++++++++- tapo-py/tapo.pyi | 63 ++++++++++++++++ 4 files changed, 147 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 2330b2e..4c61be4 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ Unofficial Tapo API Client. Works with TP-Link Tapo smart devices. Tested with l | set_color_temperature | | | ✅ | ✓ | | | | set_hue_saturation | | | ✅ | ✓ | | | | set_lighting_effect | | | | ✓ | | | -| set() API \* | | | ✓ | ✓ | | | +| set() API \* | | | ✅ | ✓ | | | \* The `set()` API allows multiple properties to be set in a single request. diff --git a/tapo-py/examples/tapo_l530.py b/tapo-py/examples/tapo_l530.py index ac7faa1..e72a517 100644 --- a/tapo-py/examples/tapo_l530.py +++ b/tapo-py/examples/tapo_l530.py @@ -41,6 +41,12 @@ async def main(): print("Waiting 2 seconds...") await asyncio.sleep(2) + print("Using the `set` API to set multiple properties in a single request...") + await device.set().brightness(50).color(Color.HotPink).send(device) + + print("Waiting 2 seconds...") + await asyncio.sleep(2) + print("Turning device off...") await device.off() diff --git a/tapo-py/src/handlers/color_light_handler.rs b/tapo-py/src/handlers/color_light_handler.rs index 467a1d7..6fb7aa3 100644 --- a/tapo-py/src/handlers/color_light_handler.rs +++ b/tapo-py/src/handlers/color_light_handler.rs @@ -1,13 +1,16 @@ -use std::sync::Arc; +use std::{ops::Deref, sync::Arc}; use pyo3::prelude::*; -use tapo::{requests::Color, ColorLightHandler}; +use tapo::{ + requests::{Color, ColorLightSetDeviceInfoParams}, + ColorLightHandler, +}; use tokio::sync::Mutex; use crate::errors::ErrorWrapper; #[derive(Clone)] -#[pyclass(name = "ColorColorLightHandler")] +#[pyclass(name = "ColorLightHandler")] pub struct PyColorLightHandler { handler: Arc>, } @@ -105,6 +108,10 @@ impl PyColorLightHandler { }) } + pub fn set(&self) -> PyColorLightSetDeviceInfoParams { + PyColorLightSetDeviceInfoParams::new() + } + pub fn set_brightness<'a>(&'a self, py: Python<'a>, brightness: u8) -> PyResult<&'a PyAny> { let handler = self.handler.clone(); pyo3_asyncio::tokio::future_into_py(py, async move { @@ -166,3 +173,70 @@ impl PyColorLightHandler { }) } } + +#[derive(Clone)] +#[pyclass(name = "LightSetDeviceInfoParams")] +pub struct PyColorLightSetDeviceInfoParams { + params: ColorLightSetDeviceInfoParams, +} + +impl PyColorLightSetDeviceInfoParams { + pub(crate) fn new() -> Self { + Self { + params: ColorLightSetDeviceInfoParams::new(), + } + } +} + +#[pymethods] +impl PyColorLightSetDeviceInfoParams { + pub fn on(&self) -> Self { + Self { + params: self.params.clone().on(), + } + } + + pub fn off(&self) -> Self { + Self { + params: self.params.clone().off(), + } + } + + pub fn brightness(&self, brightness: u8) -> Self { + Self { + params: self.params.clone().brightness(brightness), + } + } + + pub fn color(&self, color: Color) -> Self { + Self { + params: self.params.clone().color(color), + } + } + + pub fn hue_saturation(&self, hue: u16, saturation: u8) -> Self { + Self { + params: self.params.clone().hue_saturation(hue, saturation), + } + } + + pub fn color_temperature(&self, color_temperature: u16) -> Self { + Self { + params: self.params.clone().color_temperature(color_temperature), + } + } + + pub fn send<'a>(&'a self, py: Python<'a>, handler: PyColorLightHandler) -> PyResult<&'a PyAny> { + let params = self.params.clone(); + + pyo3_asyncio::tokio::future_into_py(py, async move { + let handler_lock = handler.handler.lock().await; + params + .send(handler_lock.deref()) + .await + .map_err(ErrorWrapper)?; + + Ok(()) + }) + } +} diff --git a/tapo-py/tapo.pyi b/tapo-py/tapo.pyi index cd6fc4f..fc79f2f 100644 --- a/tapo-py/tapo.pyi +++ b/tapo-py/tapo.pyi @@ -399,6 +399,15 @@ class ColorLightHandler: Returns: DeviceUsageResult: Contains the time usage. """ + def set(self) -> ColorLightSetDeviceInfoParams: + """Returns a `ColorLightSetDeviceInfoParams` builder that allows + multiple properties to be set in a single request. + `ColorLightSetDeviceInfoParams.send` must be called at the end to apply the changes. + + Returns: + ColorLightSetDeviceInfoParams: Builder that is used by the + `ColorLightHandler.set` API to set multiple properties in a single request. + """ async def set_brightness(self, brightness: int) -> None: """Sets the *brightness* and turns *on* the device. @@ -425,6 +434,60 @@ class ColorLightHandler: color_temperature (int): between 2500 and 6500 """ +class ColorLightSetDeviceInfoParams: + """Builder that is used by the `ColorLightHandler.set` API to set + multiple properties in a single request. + """ + + def on(self) -> ColorLightSetDeviceInfoParams: + """Turns *on* the device. + `ColorLightSetDeviceInfoParams.send` must be called at the end to apply the changes. + """ + def off(self) -> ColorLightSetDeviceInfoParams: + """Turns *off* the device. + `ColorLightSetDeviceInfoParams.send` must be called at the end to apply the changes. + """ + def brightness(self, brightness: int) -> ColorLightSetDeviceInfoParams: + """Sets the *brightness*. + `ColorLightSetDeviceInfoParams.send` must be called at the end to apply the changes. + The device will also be turned *on*, unless `ColorLightSetDeviceInfoParams.off` is called. + + Args: + brightness (int): between 1 and 100 + """ + def color(self, color: Color) -> ColorLightSetDeviceInfoParams: + """Sets the *color*. + `ColorLightSetDeviceInfoParams.send` must be called at the end to apply the changes. + The device will also be turned *on*, unless `ColorLightSetDeviceInfoParams.off` is called. + + Args: + color (Color): one of `tapo.Color` as defined in the Google Home app. + """ + def hue_saturation( + self, hue: int, saturation: int + ) -> ColorLightSetDeviceInfoParams: + """Sets the *hue* and *saturation*. + `ColorLightSetDeviceInfoParams.send` must be called at the end to apply the changes. + The device will also be turned *on*, unless `ColorLightSetDeviceInfoParams.off` is called. + + Args: + hue (int): between 1 and 360 + saturation (int): between 1 and 100 + """ + def color_temperature( + self, color_temperature: int + ) -> ColorLightSetDeviceInfoParams: + """ + Sets the *color temperature*. + `ColorLightSetDeviceInfoParams.send` must be called at the end to apply the changes. + The device will also be turned *on*, unless `ColorLightSetDeviceInfoParams.off` is called. + + Args: + color_temperature (int): between 2500 and 6500 + """ + async def send(self) -> None: + """Performs a request to apply the changes to the device.""" + class PlugHandler: """Handler for the [P100](https://www.tapo.com/en/search/?q=P100) & [P105](https://www.tapo.com/en/search/?q=P105) devices."""