diff --git a/CHANGELOG.md b/CHANGELOG.md index a238f0c..81e97c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,13 +12,19 @@ file. This change log follows the conventions of ### Changed -- The `openssl` dependency has been replaced with native Rust alternatives to expand cross-compilation options, such as for Android, and to decrease build times. +- The `openssl` dependency has been replaced with native Rust alternatives to expand cross-compilation options, such as for Android, and to decrease build times (thanks to @rbock44). +- `PlugPowerStripHandler` has been renamed to `PowerStripPlugHandler` to be consistent with the rest of the library. +- `PlugPowerStripResult` has been renamed to `PowerStripPlugResult` to be consistent with the rest of the library. ## [Python Unreleased][Unreleased] +### Added + +- Added support for the P300 and P304 power strips. + ### Changed -- The `openssl` dependency has been replaced with native Rust alternatives to expand cross-compilation options, such as for Android, and to decrease build times. +- The `openssl` dependency has been replaced with native Rust alternatives to expand cross-compilation options, such as for Android, and to decrease build times (thanks to @rbock44). ## [Rust v0.7.16][v0.7.16] - 2024-09-27 diff --git a/Cargo.lock b/Cargo.lock index 3fbf4e4..187a9c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -155,9 +155,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.30" +version = "1.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b16803a61b81d9eabb7eae2588776c4c1e584b738ede45fdbb4c972cec1e9945" +checksum = "c2e7962b54006dcfcc61cb72735f4d89bb97061dd6a7ed882ec6b8ee53714c6f" dependencies = [ "shlex", ] @@ -1130,9 +1130,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.130" +version = "1.0.132" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "610f75ff4a8e3cb29b85da56eabdd1bff5b06739059a4b8e2967fef32e5d9944" +checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" dependencies = [ "itoa", "memchr", @@ -1266,9 +1266,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.79" +version = "2.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" +checksum = "198514704ca887dd5a1e408c6c6cdcba43672f9b4062e1b24aa34e74e6d7faae" dependencies = [ "proc-macro2", "quote", diff --git a/README.md b/README.md index a54d334..8512d5d 100644 --- a/README.md +++ b/README.md @@ -31,18 +31,18 @@ Unofficial Tapo API Client. Works with TP-Link Tapo smart devices. Tested with l | Feature


| GenericDevice


| L510
L520
L610 | L530
L535
L630
| L900


| L920
L930

| P100
P105

| P110
P115

| P300
P304

| H100


| | ------------------------------------ | :--------------------------: | :--------------------: | :-------------------------: | :-----------------: | :---------------------: | :---------------------: | :---------------------: | :---------------------: | :-----------------: | | device_reset | | ✅ | ✅ | ✓ | ✓ | ✅ | ✅ | | | -| get_child_device_component_list_json | | | | | | | | ✓ | ✅ | -| get_child_device_list | | | | | | | | ✓ | ✅ | -| get_child_device_list_json | | | | | | | | ✓ | ✅ | +| get_child_device_component_list_json | | | | | | | | ✅ | ✅ | +| get_child_device_list | | | | | | | | ✅ | ✅ | +| get_child_device_list_json | | | | | | | | ✅ | ✅ | | get_current_power | | | | | | | ✅ | | | -| get_device_info | ✅ | ✅ | ✅ | ✓ | ✓ | ✅ | ✅ | ✓ | ✅ | -| get_device_info_json | ✅ | ✅ | ✅ | ✓ | ✓ | ✅ | ✅ | ✓ | ✅ | +| get_device_info | ✅ | ✅ | ✅ | ✓ | ✓ | ✅ | ✅ | ✅ | ✅ | +| get_device_info_json | ✅ | ✅ | ✅ | ✓ | ✓ | ✅ | ✅ | ✅ | ✅ | | get_device_usage | | ✅ | ✅ | ✓ | ✓ | ✅ | ✅ | | | | get_energy_data | | | | | | | ✅ | | | | get_energy_usage | | | | | | | ✅ | | | | off | ✅ | ✅ | ✅ | ✓ | ✓ | ✅ | ✅ | | | | on | ✅ | ✅ | ✅ | ✓ | ✓ | ✅ | ✅ | | | -| refresh_session | ✅ | ✅ | ✅ | ✓ | ✓ | ✅ | ✅ | ✓ | ✅ | +| refresh_session | ✅ | ✅ | ✅ | ✓ | ✓ | ✅ | ✅ | ✅ | ✅ | | set_brightness | | ✅ | ✅ | ✓ | ✓ | | | | | | set_color | | | ✅ | ✓ | ✓ | | | | | | set_color_temperature | | | ✅ | ✓ | ✓ | | | | | @@ -54,6 +54,9 @@ Unofficial Tapo API Client. Works with TP-Link Tapo smart devices. Tested with l ## Hub (H100) Child Devices Support +✓ - Rust only\ +✅ - Rust and Python + | Feature

| KE100

| S200B

| T100

| T110

| T300

| T310
T315 | | -------------------------------- | :-------------: | :-------------: | :------------: | :------------: | :------------: | :-----------: | | get_device_info \* | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | diff --git a/tapo-py/examples/tapo_p300.py b/tapo-py/examples/tapo_p300.py new file mode 100644 index 0000000..38a5105 --- /dev/null +++ b/tapo-py/examples/tapo_p300.py @@ -0,0 +1,47 @@ +"""P300 and P304 Example""" + +import asyncio +import os + +from tapo import ApiClient + + +async def main(): + tapo_username = os.getenv("TAPO_USERNAME") + tapo_password = os.getenv("TAPO_PASSWORD") + ip_address = os.getenv("IP_ADDRESS") + + client = ApiClient(tapo_username, tapo_password) + power_strip = await client.p300(ip_address) + + device_info = await power_strip.get_device_info() + print(f"Device info: {device_info.to_dict()}") + + child_device_list = await power_strip.get_child_device_list() + + for child in child_device_list: + print( + "Found plug with nickname: {}, id: {}, state: {}.".format( + child.nickname, + child.device_id, + child.device_on, + ) + ) + + plug = await power_strip.plug(device_id=child.device_id) + + print("Turning device on...") + await plug.on() + + print("Waiting 2 seconds...") + await asyncio.sleep(2) + + print("Turning device off...") + await plug.off() + + print("Waiting 2 seconds...") + await asyncio.sleep(2) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/tapo-py/pyproject.toml b/tapo-py/pyproject.toml index 0d07a8b..eaca73b 100644 --- a/tapo-py/pyproject.toml +++ b/tapo-py/pyproject.toml @@ -1,13 +1,13 @@ [tool.poetry] name = "tapo" version = "0.5.1" -description = "Unofficial Tapo API Client. Works with TP-Link Tapo smart devices. Tested with light bulbs (L510, L520, L530, L535, L610, L630), plugs (P100, P105, P110, P115), hubs (H100), switches (S200B) and sensors (KE100, T100, T110, T300, T310, T315)." +description = "Unofficial Tapo API Client. Works with TP-Link Tapo smart devices. Tested with light bulbs (L510, L520, L530, L535, L610, L630), plugs (P100, P105, P110, P115), power strips (P300, P304), hubs (H100), switches (S200B) and sensors (KE100, T100, T110, T300, T310, T315)." authors = ["Mihai Dinculescu "] [project] name = "tapo" version = "0.5.1" -description = "Unofficial Tapo API Client. Works with TP-Link Tapo smart devices. Tested with light bulbs (L510, L520, L530, L535, L610, L630), plugs (P100, P105, P110, P115), hubs (H100), switches (S200B) and sensors (KE100, T100, T110, T300, T310, T315)." +description = "Unofficial Tapo API Client. Works with TP-Link Tapo smart devices. Tested with light bulbs (L510, L520, L530, L535, L610, L630), plugs (P100, P105, P110, P115), power strips (P300, P304), hubs (H100), switches (S200B) and sensors (KE100, T100, T110, T300, T310, T315)." readme = "README.md" license = { file = "LICENSE" } authors = [ diff --git a/tapo-py/src/api_client.rs b/tapo-py/src/api_client.rs index 903868a..b0d7bbb 100644 --- a/tapo-py/src/api_client.rs +++ b/tapo-py/src/api_client.rs @@ -2,14 +2,14 @@ use pyo3::prelude::*; use std::time::Duration; use tapo::{ ApiClient, ColorLightHandler, GenericDeviceHandler, HubHandler, LightHandler, - PlugEnergyMonitoringHandler, PlugHandler, + PlugEnergyMonitoringHandler, PlugHandler, PowerStripHandler, }; use crate::call_handler_constructor; use crate::errors::ErrorWrapper; use crate::handlers::{ PyColorLightHandler, PyGenericDeviceHandler, PyHubHandler, PyLightHandler, - PyPlugEnergyMonitoringHandler, PyPlugHandler, + PyPlugEnergyMonitoringHandler, PyPlugHandler, PyPowerStripHandler, }; #[pyclass(name = "ApiClient")] @@ -101,6 +101,18 @@ impl PyApiClient { Ok(PyPlugEnergyMonitoringHandler::new(handler)) } + pub async fn p300(&self, ip_address: String) -> PyResult { + let handler: PowerStripHandler = + call_handler_constructor!(self, tapo::ApiClient::p300, ip_address); + Ok(PyPowerStripHandler::new(handler)) + } + + pub async fn p304(&self, ip_address: String) -> PyResult { + let handler: PowerStripHandler = + call_handler_constructor!(self, tapo::ApiClient::p304, ip_address); + Ok(PyPowerStripHandler::new(handler)) + } + pub async fn h100(&self, ip_address: String) -> PyResult { let handler: HubHandler = call_handler_constructor!(self, tapo::ApiClient::h100, ip_address); diff --git a/tapo-py/src/handlers.rs b/tapo-py/src/handlers.rs index 3dbc833..962554d 100644 --- a/tapo-py/src/handlers.rs +++ b/tapo-py/src/handlers.rs @@ -5,6 +5,7 @@ mod hub_handler; mod light_handler; mod plug_energy_monitoring_handler; mod plug_handler; +mod power_strip_handler; pub use child_devices::*; pub use color_light_handler::*; @@ -13,3 +14,4 @@ pub use hub_handler::*; pub use light_handler::*; pub use plug_energy_monitoring_handler::*; pub use plug_handler::*; +pub use power_strip_handler::*; diff --git a/tapo-py/src/handlers/child_devices.rs b/tapo-py/src/handlers/child_devices.rs index 424333f..54d9a8c 100644 --- a/tapo-py/src/handlers/child_devices.rs +++ b/tapo-py/src/handlers/child_devices.rs @@ -1,9 +1,11 @@ +mod power_strip_plug_handler; mod s200b_handler; mod t100_handler; mod t110_handler; mod t300_handler; mod t31x_handler; +pub use power_strip_plug_handler::*; pub use s200b_handler::*; pub use t100_handler::*; pub use t110_handler::*; diff --git a/tapo-py/src/handlers/child_devices/power_strip_plug_handler.rs b/tapo-py/src/handlers/child_devices/power_strip_plug_handler.rs new file mode 100644 index 0000000..9ef3222 --- /dev/null +++ b/tapo-py/src/handlers/child_devices/power_strip_plug_handler.rs @@ -0,0 +1,46 @@ +use std::{ops::Deref, sync::Arc}; + +use pyo3::{prelude::*, types::PyDict}; +use tapo::responses::PowerStripPlugResult; +use tapo::PowerStripPlugHandler; + +use crate::call_handler_method; + +#[derive(Clone)] +#[pyclass(name = "PowerStripPlugHandler")] +pub struct PyPowerStripPlugHandler { + handler: Arc, +} + +impl PyPowerStripPlugHandler { + pub fn new(handler: PowerStripPlugHandler) -> Self { + Self { + handler: Arc::new(handler), + } + } +} + +#[pymethods] +impl PyPowerStripPlugHandler { + pub async fn get_device_info(&self) -> PyResult { + let handler = self.handler.clone(); + call_handler_method!(handler.deref(), PowerStripPlugHandler::get_device_info) + } + + pub async fn get_device_info_json(&self) -> PyResult> { + let handler = self.handler.clone(); + let result = + call_handler_method!(handler.deref(), PowerStripPlugHandler::get_device_info_json)?; + Python::with_gil(|py| tapo::python::serde_object_to_py_dict(py, &result)) + } + + pub async fn on(&self) -> PyResult<()> { + let handler = self.handler.clone(); + call_handler_method!(handler.deref(), PowerStripPlugHandler::on) + } + + pub async fn off(&self) -> PyResult<()> { + let handler = self.handler.clone(); + call_handler_method!(handler.deref(), PowerStripPlugHandler::off) + } +} diff --git a/tapo-py/src/handlers/hub_handler.rs b/tapo-py/src/handlers/hub_handler.rs index cee5dc8..be8b706 100644 --- a/tapo-py/src/handlers/hub_handler.rs +++ b/tapo-py/src/handlers/hub_handler.rs @@ -77,37 +77,37 @@ impl PyHubHandler { for child in children { match child { - ChildDeviceHubResult::KE100(x) => { - let _ = results.append(x.into_py(py)); + ChildDeviceHubResult::KE100(device) => { + results.append(device.into_py(py))?; } - ChildDeviceHubResult::S200B(x) => { - let _ = results.append(x.into_py(py)); + ChildDeviceHubResult::S200B(device) => { + results.append(device.into_py(py))?; } - ChildDeviceHubResult::T100(x) => { - let _ = results.append(x.into_py(py)); + ChildDeviceHubResult::T100(device) => { + results.append(device.into_py(py))?; } - ChildDeviceHubResult::T110(x) => { - let _ = results.append(x.into_py(py)); + ChildDeviceHubResult::T110(device) => { + results.append(device.into_py(py))?; } - ChildDeviceHubResult::T300(x) => { - let _ = results.append(x.into_py(py)); + ChildDeviceHubResult::T300(device) => { + results.append(device.into_py(py))?; } - ChildDeviceHubResult::T310(x) => { - let _ = results.append(x.into_py(py)); + ChildDeviceHubResult::T310(device) => { + results.append(device.into_py(py))?; } - ChildDeviceHubResult::T315(x) => { - let _ = results.append(x.into_py(py)); + ChildDeviceHubResult::T315(device) => { + results.append(device.into_py(py))?; } _ => { - let _ = results.append(py.None()); + results.append(py.None())?; } } } - results.into() + Ok(results.into()) }); - Ok(results) + results } pub async fn get_child_device_list_json(&self) -> PyResult> { diff --git a/tapo-py/src/handlers/power_strip_handler.rs b/tapo-py/src/handlers/power_strip_handler.rs new file mode 100644 index 0000000..83b7321 --- /dev/null +++ b/tapo-py/src/handlers/power_strip_handler.rs @@ -0,0 +1,128 @@ +use std::ops::{Deref, DerefMut}; +use std::sync::Arc; + +use pyo3::prelude::*; +use pyo3::types::{PyDict, PyList}; +use tapo::responses::DeviceInfoPowerStripResult; +use tapo::{Error, Plug, PowerStripHandler}; +use tokio::sync::RwLock; + +use crate::call_handler_method; +use crate::errors::ErrorWrapper; +use crate::handlers::PyPowerStripPlugHandler; + +#[derive(Clone)] +#[pyclass(name = "PowerStripHandler")] +pub struct PyPowerStripHandler { + handler: Arc>, +} + +impl PyPowerStripHandler { + pub fn new(handler: PowerStripHandler) -> Self { + Self { + handler: Arc::new(RwLock::new(handler)), + } + } + + fn parse_identifier( + device_id: Option, + nickname: Option, + position: Option, + ) -> PyResult { + match (device_id, nickname, position) { + (Some(device_id), _, _) => Ok(Plug::ByDeviceId(device_id)), + (None, Some(nickname), _) => Ok(Plug::ByNickname(nickname)), + (None, None, Some(position)) => Ok(Plug::ByPosition(position)), + _ => Err(Into::::into(Error::Validation { + field: "identifier".to_string(), + message: "Either a device_id, nickname, or position must be provided".to_string(), + }) + .into()), + } + } +} + +#[pymethods] +impl PyPowerStripHandler { + pub async fn refresh_session(&self) -> PyResult<()> { + let handler = self.handler.clone(); + call_handler_method!( + handler.write().await.deref_mut(), + PowerStripHandler::refresh_session, + discard_result + ) + } + + pub async fn get_device_info(&self) -> PyResult { + let handler = self.handler.clone(); + call_handler_method!( + handler.read().await.deref(), + PowerStripHandler::get_device_info + ) + } + + pub async fn get_device_info_json(&self) -> PyResult> { + let handler = self.handler.clone(); + let result = call_handler_method!( + handler.read().await.deref(), + PowerStripHandler::get_device_info_json + )?; + Python::with_gil(|py| tapo::python::serde_object_to_py_dict(py, &result)) + } + + pub async fn get_child_device_list(&self) -> PyResult> { + let handler = self.handler.clone(); + let children = call_handler_method!( + handler.read().await.deref(), + PowerStripHandler::get_child_device_list + )?; + + let results = Python::with_gil(|py| { + let results = PyList::empty_bound(py); + + for child in children { + results.append(child.into_py(py))?; + } + + Ok(results.into()) + }); + + results + } + + pub async fn get_child_device_list_json(&self) -> PyResult> { + let handler = self.handler.clone(); + let result = call_handler_method!( + handler.read().await.deref(), + PowerStripHandler::get_child_device_list_json + )?; + Python::with_gil(|py| tapo::python::serde_object_to_py_dict(py, &result)) + } + + pub async fn get_child_device_component_list_json(&self) -> PyResult> { + let handler = self.handler.clone(); + let result = call_handler_method!( + handler.read().await.deref(), + PowerStripHandler::get_child_device_component_list_json + )?; + Python::with_gil(|py| tapo::python::serde_object_to_py_dict(py, &result)) + } + + #[pyo3(signature = (device_id=None, nickname=None, position=None))] + pub async fn plug( + &self, + device_id: Option, + nickname: Option, + position: Option, + ) -> PyResult { + let handler = self.handler.clone(); + let identifier = PyPowerStripHandler::parse_identifier(device_id, nickname, position)?; + + let child_handler = call_handler_method!( + handler.read().await.deref(), + PowerStripHandler::plug, + identifier + )?; + Ok(PyPowerStripPlugHandler::new(child_handler)) + } +} diff --git a/tapo-py/src/lib.rs b/tapo-py/src/lib.rs index 9129bd2..28d8338 100644 --- a/tapo-py/src/lib.rs +++ b/tapo-py/src/lib.rs @@ -9,25 +9,56 @@ use api_client::PyApiClient; use handlers::{ PyColorLightHandler, PyColorLightSetDeviceInfoParams, PyEnergyDataInterval, PyGenericDeviceHandler, PyHubHandler, PyLightHandler, PyPlugEnergyMonitoringHandler, - PyPlugHandler, PyT100Handler, PyT110Handler, PyT300Handler, PyT31XHandler, - TriggerLogsS200BResult, TriggerLogsT100Result, TriggerLogsT110Result, TriggerLogsT300Result, + PyPlugHandler, PyPowerStripHandler, PyPowerStripPlugHandler, PyT100Handler, PyT110Handler, + PyT300Handler, PyT31XHandler, TriggerLogsS200BResult, TriggerLogsT100Result, + TriggerLogsT110Result, TriggerLogsT300Result, }; use tapo::requests::Color; use tapo::responses::{ - ColorLightState, CurrentPowerResult, DefaultBrightnessState, DefaultColorLightState, - DefaultLightState, DefaultPlugState, DefaultPowerType, DefaultStateType, - DeviceInfoColorLightResult, DeviceInfoGenericResult, DeviceInfoHubResult, + AutoOffStatus, ColorLightState, CurrentPowerResult, DefaultBrightnessState, + DefaultColorLightState, DefaultLightState, DefaultPlugState, DefaultPowerType, + DefaultStateType, DeviceInfoColorLightResult, DeviceInfoGenericResult, DeviceInfoHubResult, DeviceInfoLightResult, DeviceInfoPlugEnergyMonitoringResult, DeviceInfoPlugResult, - DeviceUsageEnergyMonitoringResult, DeviceUsageResult, EnergyDataResult, EnergyUsageResult, - KE100Result, OvercurrentStatus, OverheatStatus, PlugState, PowerProtectionStatus, S200BLog, - S200BResult, S200BRotationParams, Status, T100Log, T100Result, T110Log, T110Result, T300Log, - T300Result, T31XResult, TemperatureHumidityRecord, TemperatureHumidityRecords, TemperatureUnit, - TemperatureUnitKE100, UsageByPeriodResult, WaterLeakStatus, + DeviceInfoPowerStripResult, DeviceUsageEnergyMonitoringResult, DeviceUsageResult, + EnergyDataResult, EnergyUsageResult, KE100Result, OvercurrentStatus, OverheatStatus, PlugState, + PowerProtectionStatus, PowerStripPlugResult, S200BLog, S200BResult, S200BRotationParams, + Status, T100Log, T100Result, T110Log, T110Result, T300Log, T300Result, T31XResult, + TemperatureHumidityRecord, TemperatureHumidityRecords, TemperatureUnit, TemperatureUnitKE100, + UsageByPeriodResult, WaterLeakStatus, }; #[pymodule] #[pyo3(name = "tapo")] fn tapo_py(py: Python, module: &Bound<'_, PyModule>) -> PyResult<()> { + let requests = PyModule::new_bound(py, "tapo.requests")?; + let responses = PyModule::new_bound(py, "tapo.responses")?; + + register_handlers(module)?; + register_requests(&requests)?; + register_responses(&responses)?; + register_responses_hub(&responses)?; + register_responses_power_strip(&responses)?; + + module.add_submodule(&requests)?; + module.add_submodule(&responses)?; + + let sys = py.import_bound("sys")?; + let modules = sys.getattr("modules")?; + modules.set_item("tapo.requests", requests)?; + modules.set_item("tapo.responses", responses)?; + + Ok(()) +} + +fn register_requests(module: &Bound<'_, PyModule>) -> Result<(), PyErr> { + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + + Ok(()) +} + +fn register_handlers(module: &Bound<'_, PyModule>) -> Result<(), PyErr> { module.add_class::()?; module.add_class::()?; module.add_class::()?; @@ -41,79 +72,82 @@ fn tapo_py(py: Python, module: &Bound<'_, PyModule>) -> PyResult<()> { module.add_class::()?; module.add_class::()?; - let requests = PyModule::new_bound(py, "tapo.requests")?; - let responses = PyModule::new_bound(py, "tapo.responses")?; + module.add_class::()?; + module.add_class::()?; - // requests - requests.add_class::()?; - requests.add_class::()?; - requests.add_class::()?; - - // responses - responses.add_class::()?; - responses.add_class::()?; - responses.add_class::()?; - responses.add_class::()?; - responses.add_class::()?; - responses.add_class::()?; - responses.add_class::()?; - responses.add_class::()?; - responses.add_class::()?; - responses.add_class::()?; - responses.add_class::()?; - responses.add_class::()?; - - // responses: device info: color light - responses.add_class::()?; - responses.add_class::()?; - responses.add_class::()?; - - // responses: device info: generic - responses.add_class::()?; - - // responses: hub - responses.add_class::()?; - responses.add_class::()?; - responses.add_class::()?; - responses.add_class::()?; - responses.add_class::()?; - responses.add_class::()?; - responses.add_class::()?; - - // responses: hub devices - responses.add_class::()?; - responses.add_class::()?; - responses.add_class::()?; - responses.add_class::()?; - responses.add_class::()?; - responses.add_class::()?; - responses.add_class::()?; - responses.add_class::()?; - responses.add_class::()?; - responses.add_class::()?; - responses.add_class::()?; - responses.add_class::()?; - responses.add_class::()?; - responses.add_class::()?; - responses.add_class::()?; - - // responses: light - responses.add_class::()?; - responses.add_class::()?; - - // responses: plug - responses.add_class::()?; - responses.add_class::()?; - responses.add_class::()?; - responses.add_class::()?; + Ok(()) +} - module.add_submodule(&requests)?; - module.add_submodule(&responses)?; +fn register_responses(module: &Bound<'_, PyModule>) -> Result<(), PyErr> { + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + + // device info: color light + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + + // device info: generic + module.add_class::()?; + + // device info: light + module.add_class::()?; + module.add_class::()?; + + // device info: plugs + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; - let sys = py.import_bound("sys")?; - let modules = sys.getattr("modules")?; - modules.set_item("tapo.requests", requests)?; - modules.set_item("tapo.responses", responses)?; + Ok(()) +} + +fn register_responses_hub(module: &Bound<'_, PyModule>) -> Result<(), PyErr> { + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + + // child devices + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + + Ok(()) +} + +fn register_responses_power_strip(module: &Bound<'_, PyModule>) -> Result<(), PyErr> { + module.add_class::()?; + + // child devices + module.add_class::()?; + module.add_class::()?; Ok(()) } diff --git a/tapo-py/tapo-py/tapo/__init__.pyi b/tapo-py/tapo-py/tapo/__init__.pyi index 5359bbf..c561772 100644 --- a/tapo-py/tapo-py/tapo/__init__.pyi +++ b/tapo-py/tapo-py/tapo/__init__.pyi @@ -5,6 +5,8 @@ from .hub_handler import * from .light_handler import * from .plug_energy_monitoring_handler import * from .plug_handler import * +from .power_strip_handler import * +from .power_strip_plug_handler import * from .requests import * from .responses import * from .s200b_handler import * diff --git a/tapo-py/tapo-py/tapo/api_client.pyi b/tapo-py/tapo-py/tapo/api_client.pyi index ef9aa35..3c43291 100644 --- a/tapo-py/tapo-py/tapo/api_client.pyi +++ b/tapo-py/tapo-py/tapo/api_client.pyi @@ -1,7 +1,7 @@ """Tapo API Client. -Tested with light bulbs (L510, L520, L530, L535, L610, L630), plugs (P100, P105, P110, P115), -hubs (H100), switches (S200B) and sensors (KE100, T100, T110, T300, T310, T315). +Tested with light bulbs (L510, L520, L530, L535, L610, L630), plugs (P100, P105, P110, P115), +power strips (P300, P304), hubs (H100), switches (S200B) and sensors (KE100, T100, T110, T300, T310, T315). Example: ```python @@ -28,12 +28,13 @@ from .hub_handler import HubHandler from .light_handler import LightHandler from .plug_energy_monitoring_handler import PlugEnergyMonitoringHandler from .plug_handler import PlugHandler +from .power_strip_handler import PowerStripHandler class ApiClient: """Tapo API Client. Tested with light bulbs (L510, L520, L530, L535, L610, L630), plugs (P100, P105, P110, P115), - hubs (H100), switches (S200B) and sensors (KE100, T100, T110, T300, T310, T315). + power strips (P300, P304), hubs (H100), switches (S200B) and sensors (KE100, T100, T110, T300, T310, T315). Example: ```python @@ -283,6 +284,44 @@ class ApiClient: ``` """ + async def p300(self, ip_address: str) -> PowerStripHandler: + """Specializes the given `ApiClient` into an authenticated `PowerStripHandler`. + + Args: + ip_address (str): The IP address of the device + + Returns: + PowerStripHandler: Handler for the [P300](https://www.tapo.com/en/search/?q=P300) and [P304](https://www.tp-link.com/uk/search/?q=P304) devices. + + Example: + ```python + client = ApiClient("tapo-username@example.com", "tapo-password") + power_strip = await client.p300("192.168.1.100") + + child_device_list = await power_strip.get_child_device_list() + print(f"Child device list: {child_device_list.to_dict()}") + ``` + """ + + async def p304(self, ip_address: str) -> PowerStripHandler: + """Specializes the given `ApiClient` into an authenticated `PowerStripHandler`. + + Args: + ip_address (str): The IP address of the device + + Returns: + PowerStripHandler: Handler for the [P300](https://www.tapo.com/en/search/?q=P300) and [P304](https://www.tp-link.com/uk/search/?q=P304) devices. + + Example: + ```python + client = ApiClient("tapo-username@example.com", "tapo-password") + power_strip = await client.p300("192.168.1.100") + + child_device_list = await power_strip.get_child_device_list() + print(f"Child device list: {child_device_list.to_dict()}") + ``` + """ + async def h100(self, ip_address: str) -> HubHandler: """Specializes the given `ApiClient` into an authenticated `HubHandler`. diff --git a/tapo-py/tapo-py/tapo/hub_handler.pyi b/tapo-py/tapo-py/tapo/hub_handler.pyi index cc47833..3670347 100644 --- a/tapo-py/tapo-py/tapo/hub_handler.pyi +++ b/tapo-py/tapo-py/tapo/hub_handler.pyi @@ -46,7 +46,7 @@ class HubHandler: ) -> List[ Union[KE100Result, S200BResult, T100Result, T110Result, T300Result, T31XResult, None] ]: - """Returns *child device list* as `ChildDeviceHubResult`. + """Returns *child device list* as `List[Union[KE100Result, S200BResult, T100Result, T110Result, T300Result, T31XResult, None]]`. It is not guaranteed to contain all the properties returned from the Tapo API or to support all the possible devices connected to the hub. If the deserialization fails, or if a property that you care about it's not present, diff --git a/tapo-py/tapo-py/tapo/power_strip_handler.pyi b/tapo-py/tapo-py/tapo/power_strip_handler.pyi new file mode 100644 index 0000000..ec0a770 --- /dev/null +++ b/tapo-py/tapo-py/tapo/power_strip_handler.pyi @@ -0,0 +1,96 @@ +from typing import List, Optional + +from tapo import PowerStripPlugHandler +from tapo.responses import DeviceInfoPowerStripResult, PowerStripPlugResult + +class PowerStripHandler: + """Handler for the [P300](https://www.tapo.com/en/search/?q=P300) and + [P304](https://www.tp-link.com/uk/search/?q=P304) devices. + """ + + def __init__(self, handler: object): + """Private constructor. + It should not be called from outside the tapo library. + """ + + async def refresh_session(self) -> None: + """Refreshes the authentication session.""" + + async def get_device_info(self) -> DeviceInfoPowerStripResult: + """Returns *device info* as `DeviceInfoPowerStripResult`. + It is not guaranteed to contain all the properties returned from the Tapo API. + If the deserialization fails, or if a property that you care about it's not present, + try `HubHandler.get_device_info_json`. + + Returns: + DeviceInfoPowerStripResult: Device info of Tapo P300 and P304. Superset of `DeviceInfoGenericResult`. + """ + + async def get_device_info_json(self) -> dict: + """Returns *device info* as json. + It contains all the properties returned from the Tapo API. + + Returns: + dict: Device info as a dictionary. + """ + + async def get_child_device_list( + self, + ) -> List[PowerStripPlugResult]: + """Returns *child device list* as `List[PowerStripPlugResult]`. + It is not guaranteed to contain all the properties returned from the Tapo API + or to support all the possible devices connected to the hub. + If the deserialization fails, or if a property that you care about it's not present, + try `PowerStripHandler.get_child_device_list_json`. + + Returns: + dict: Device info as a dictionary. + """ + + async def get_child_device_list_json(self) -> dict: + """Returns *child device list* as json. + It contains all the properties returned from the Tapo API. + + Returns: + dict: Device info as a dictionary. + """ + + async def get_child_device_component_list_json(self) -> dict: + """Returns *child device component list* as json. + It contains all the properties returned from the Tapo API. + + Returns: + dict: Device info as a dictionary. + """ + + async def plug( + self, + device_id: Optional[str] = None, + nickname: Optional[str] = None, + position: Optional[int] = None, + ) -> PowerStripPlugHandler: + """Returns a `PowerStripPlugHandler` for the device matching the provided `device_id`, `nickname`, or `position`. + + Args: + device_id (Optional[str]): The Device ID of the device + nickname (Optional[str]): The Nickname of the device + position (Optional[str]): The Position of the device + + Returns: + PowerStripPlugHandler: Handler for the [P300](https://www.tapo.com/en/search/?q=P300) and + [P304](https://www.tp-link.com/uk/search/?q=P304) child plugs. + + Example: + ```python + # Connect to the hub + client = ApiClient("tapo-username@example.com", "tapo-password") + power_strip = await client.p300("192.168.1.100") + + # Get a handler for the child device + device = await power_strip.plug(device_id="0000000000000000000000000000000000000000") + + # Get the device info of the child device + device_info = await device.get_device_info() + print(f"Device info: {device_info.to_dict()}") + ``` + """ diff --git a/tapo-py/tapo-py/tapo/power_strip_plug_handler.pyi b/tapo-py/tapo-py/tapo/power_strip_plug_handler.pyi new file mode 100644 index 0000000..7857951 --- /dev/null +++ b/tapo-py/tapo-py/tapo/power_strip_plug_handler.pyi @@ -0,0 +1,35 @@ +from tapo.responses import PowerStripPlugResult + +class PowerStripPlugHandler: + """Handler for the [P300](https://www.tapo.com/en/search/?q=P300) and + [P304](https://www.tp-link.com/uk/search/?q=P304) child plugs. + """ + + def __init__(self, handler: object): + """Private constructor. + It should not be called from outside the tapo library. + """ + + async def on(self) -> None: + """Turns *on* the device.""" + + async def off(self) -> None: + """Turns *off* the device.""" + + async def get_device_info(self) -> PowerStripPlugResult: + """Returns *device info* as `PowerStripPlugResult`. + It is not guaranteed to contain all the properties returned from the Tapo API. + If the deserialization fails, or if a property that you care about it's not present, + try `PlugHandler.get_device_info_json`. + + Returns: + PowerStripPlugResult: P300 and P304 power strip child plugs. + """ + + async def get_device_info_json(self) -> dict: + """Returns *device info* as json. + It contains all the properties returned from the Tapo API. + + Returns: + dict: Device info as a dictionary. + """ diff --git a/tapo-py/tapo-py/tapo/responses/__init__.pyi b/tapo-py/tapo-py/tapo/responses/__init__.pyi index 049ae9d..bf32631 100644 --- a/tapo-py/tapo-py/tapo/responses/__init__.pyi +++ b/tapo-py/tapo-py/tapo/responses/__init__.pyi @@ -1,4 +1,5 @@ -from .child_device_hub_result import * +from .child_device_list_hub_result import * +from .child_device_list_power_strip_result import * from .current_power_result import * from .device_info_result import * from .device_usage_energy_monitoring_result import * diff --git a/tapo-py/tapo-py/tapo/responses/child_device_hub_result/__init__.py b/tapo-py/tapo-py/tapo/responses/child_device_list_hub_result/__init__.py similarity index 100% rename from tapo-py/tapo-py/tapo/responses/child_device_hub_result/__init__.py rename to tapo-py/tapo-py/tapo/responses/child_device_list_hub_result/__init__.py diff --git a/tapo-py/tapo-py/tapo/responses/child_device_hub_result/__init__.pyi b/tapo-py/tapo-py/tapo/responses/child_device_list_hub_result/__init__.pyi similarity index 100% rename from tapo-py/tapo-py/tapo/responses/child_device_hub_result/__init__.pyi rename to tapo-py/tapo-py/tapo/responses/child_device_list_hub_result/__init__.pyi diff --git a/tapo-py/tapo-py/tapo/responses/child_device_hub_result/hub_result.pyi b/tapo-py/tapo-py/tapo/responses/child_device_list_hub_result/hub_result.pyi similarity index 90% rename from tapo-py/tapo-py/tapo/responses/child_device_hub_result/hub_result.pyi rename to tapo-py/tapo-py/tapo/responses/child_device_list_hub_result/hub_result.pyi index 263fe28..53a0040 100644 --- a/tapo-py/tapo-py/tapo/responses/child_device_hub_result/hub_result.pyi +++ b/tapo-py/tapo-py/tapo/responses/child_device_list_hub_result/hub_result.pyi @@ -1,4 +1,4 @@ -from tapo.responses.child_device_hub_result.status import Status +from tapo.responses.child_device_list_hub_result.status import Status class HubResult: """Hub result. This is an abstract base class for all hub results.""" diff --git a/tapo-py/tapo-py/tapo/responses/child_device_hub_result/ke100_result.pyi b/tapo-py/tapo-py/tapo/responses/child_device_list_hub_result/ke100_result.pyi similarity index 91% rename from tapo-py/tapo-py/tapo/responses/child_device_hub_result/ke100_result.pyi rename to tapo-py/tapo-py/tapo/responses/child_device_list_hub_result/ke100_result.pyi index 6870afa..3e002d1 100644 --- a/tapo-py/tapo-py/tapo/responses/child_device_hub_result/ke100_result.pyi +++ b/tapo-py/tapo-py/tapo/responses/child_device_list_hub_result/ke100_result.pyi @@ -1,6 +1,6 @@ from enum import Enum -from tapo.responses.child_device_hub_result.hub_result import HubResult +from tapo.responses.child_device_list_hub_result.hub_result import HubResult class KE100Result(HubResult): """Device info of Tapo KE100 thermostatic radiator valve (TRV). diff --git a/tapo-py/tapo-py/tapo/responses/child_device_hub_result/s200b_result.pyi b/tapo-py/tapo-py/tapo/responses/child_device_list_hub_result/s200b_result.pyi similarity index 80% rename from tapo-py/tapo-py/tapo/responses/child_device_hub_result/s200b_result.pyi rename to tapo-py/tapo-py/tapo/responses/child_device_list_hub_result/s200b_result.pyi index 636174a..c230574 100644 --- a/tapo-py/tapo-py/tapo/responses/child_device_hub_result/s200b_result.pyi +++ b/tapo-py/tapo-py/tapo/responses/child_device_list_hub_result/s200b_result.pyi @@ -1,4 +1,4 @@ -from tapo.responses.child_device_hub_result.hub_result import HubResult +from tapo.responses.child_device_list_hub_result.hub_result import HubResult class S200BResult(HubResult): """Device info of Tapo S200B button switch. diff --git a/tapo-py/tapo-py/tapo/responses/child_device_hub_result/status.pyi b/tapo-py/tapo-py/tapo/responses/child_device_list_hub_result/status.pyi similarity index 100% rename from tapo-py/tapo-py/tapo/responses/child_device_hub_result/status.pyi rename to tapo-py/tapo-py/tapo/responses/child_device_list_hub_result/status.pyi diff --git a/tapo-py/tapo-py/tapo/responses/child_device_hub_result/t100_result.pyi b/tapo-py/tapo-py/tapo/responses/child_device_list_hub_result/t100_result.pyi similarity index 82% rename from tapo-py/tapo-py/tapo/responses/child_device_hub_result/t100_result.pyi rename to tapo-py/tapo-py/tapo/responses/child_device_list_hub_result/t100_result.pyi index 875acdc..1bfec27 100644 --- a/tapo-py/tapo-py/tapo/responses/child_device_hub_result/t100_result.pyi +++ b/tapo-py/tapo-py/tapo/responses/child_device_list_hub_result/t100_result.pyi @@ -1,4 +1,4 @@ -from tapo.responses.child_device_hub_result.hub_result import HubResult +from tapo.responses.child_device_list_hub_result.hub_result import HubResult class T100Result(HubResult): """Device info of Tapo T100 motion sensor. diff --git a/tapo-py/tapo-py/tapo/responses/child_device_hub_result/t110_result.pyi b/tapo-py/tapo-py/tapo/responses/child_device_list_hub_result/t110_result.pyi similarity index 81% rename from tapo-py/tapo-py/tapo/responses/child_device_hub_result/t110_result.pyi rename to tapo-py/tapo-py/tapo/responses/child_device_list_hub_result/t110_result.pyi index 5d3dbec..14da412 100644 --- a/tapo-py/tapo-py/tapo/responses/child_device_hub_result/t110_result.pyi +++ b/tapo-py/tapo-py/tapo/responses/child_device_list_hub_result/t110_result.pyi @@ -1,4 +1,4 @@ -from tapo.responses.child_device_hub_result.hub_result import HubResult +from tapo.responses.child_device_list_hub_result.hub_result import HubResult class T110Result(HubResult): """Device info of Tapo T110 contact sensor. diff --git a/tapo-py/tapo-py/tapo/responses/child_device_hub_result/t300_result.pyi b/tapo-py/tapo-py/tapo/responses/child_device_list_hub_result/t300_result.pyi similarity index 88% rename from tapo-py/tapo-py/tapo/responses/child_device_hub_result/t300_result.pyi rename to tapo-py/tapo-py/tapo/responses/child_device_list_hub_result/t300_result.pyi index 46ea446..f9b370f 100644 --- a/tapo-py/tapo-py/tapo/responses/child_device_hub_result/t300_result.pyi +++ b/tapo-py/tapo-py/tapo/responses/child_device_list_hub_result/t300_result.pyi @@ -1,5 +1,5 @@ from enum import Enum -from tapo.responses.child_device_hub_result.hub_result import HubResult +from tapo.responses.child_device_list_hub_result.hub_result import HubResult class T300Result(HubResult): """Device info of Tapo T300 water sensor. diff --git a/tapo-py/tapo-py/tapo/responses/child_device_hub_result/t31x_result.pyi b/tapo-py/tapo-py/tapo/responses/child_device_list_hub_result/t31x_result.pyi similarity index 100% rename from tapo-py/tapo-py/tapo/responses/child_device_hub_result/t31x_result.pyi rename to tapo-py/tapo-py/tapo/responses/child_device_list_hub_result/t31x_result.pyi diff --git a/tapo-py/tapo-py/tapo/responses/child_device_hub_result/temperature_unit.pyi b/tapo-py/tapo-py/tapo/responses/child_device_list_hub_result/temperature_unit.pyi similarity index 100% rename from tapo-py/tapo-py/tapo/responses/child_device_hub_result/temperature_unit.pyi rename to tapo-py/tapo-py/tapo/responses/child_device_list_hub_result/temperature_unit.pyi diff --git a/tapo-py/tapo-py/tapo/responses/child_device_list_power_strip_result/__init__.py b/tapo-py/tapo-py/tapo/responses/child_device_list_power_strip_result/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tapo-py/tapo-py/tapo/responses/child_device_list_power_strip_result/__init__.pyi b/tapo-py/tapo-py/tapo/responses/child_device_list_power_strip_result/__init__.pyi new file mode 100644 index 0000000..5b066b0 --- /dev/null +++ b/tapo-py/tapo-py/tapo/responses/child_device_list_power_strip_result/__init__.pyi @@ -0,0 +1 @@ +from .power_strip_plug_result import * diff --git a/tapo-py/tapo-py/tapo/responses/child_device_list_power_strip_result/power_strip_plug_result.pyi b/tapo-py/tapo-py/tapo/responses/child_device_list_power_strip_result/power_strip_plug_result.pyi new file mode 100644 index 0000000..070e2ab --- /dev/null +++ b/tapo-py/tapo-py/tapo/responses/child_device_list_power_strip_result/power_strip_plug_result.pyi @@ -0,0 +1,46 @@ +from enum import Enum +from typing import Optional + +from tapo.responses.device_info_result.power_status import OverheatStatus + +class PowerStripPlugResult: + """P300 and P304 power strip child plugs.""" + + auto_off_remain_time: int + auto_off_status: AutoOffStatus + avatar: str + bind_count: int + category: str + device_id: str + device_on: bool + fw_id: str + fw_ver: str + has_set_location_info: bool + hw_id: str + hw_ver: str + latitude: Optional[int] + longitude: Optional[int] + mac: str + model: str + nickname: str + oem_id: str + on_time: int + """The time in seconds this device has been ON since the last state change (On/Off).""" + original_device_id: str + overheat_status: OverheatStatus + position: int + region: Optional[str] + slot_number: int + status_follow_edge: bool + type: str + + def to_dict(self) -> dict: + """Gets all the properties of this result as a dictionary. + + Returns: + dict: The result as a dictionary. + """ + +class AutoOffStatus(str, Enum): + On = "on" + Off = "off" diff --git a/tapo-py/tapo-py/tapo/responses/device_info_result/__init__.pyi b/tapo-py/tapo-py/tapo/responses/device_info_result/__init__.pyi index d66c7d6..2d3df79 100644 --- a/tapo-py/tapo-py/tapo/responses/device_info_result/__init__.pyi +++ b/tapo-py/tapo-py/tapo/responses/device_info_result/__init__.pyi @@ -6,3 +6,4 @@ from .light_result import * from .plug_energy_monitoring_result import * from .plug_result import * from .power_status import * +from .power_strip_result import * diff --git a/tapo-py/tapo-py/tapo/responses/device_info_result/color_light_result.pyi b/tapo-py/tapo-py/tapo/responses/device_info_result/color_light_result.pyi index aa4e5b0..ba45898 100644 --- a/tapo-py/tapo-py/tapo/responses/device_info_result/color_light_result.pyi +++ b/tapo-py/tapo-py/tapo/responses/device_info_result/color_light_result.pyi @@ -22,7 +22,7 @@ class DeviceInfoColorLightResult: lang: str device_on: bool on_time: int - """The time in seconds this device has been ON since the last state change (ON/OFF).""" + """The time in seconds this device has been ON since the last state change (On/Off).""" nickname: str avatar: str has_set_location_info: bool diff --git a/tapo-py/tapo-py/tapo/responses/device_info_result/generic_result.pyi b/tapo-py/tapo-py/tapo/responses/device_info_result/generic_result.pyi index 7a57da4..0857911 100644 --- a/tapo-py/tapo-py/tapo/responses/device_info_result/generic_result.pyi +++ b/tapo-py/tapo-py/tapo/responses/device_info_result/generic_result.pyi @@ -20,7 +20,7 @@ class DeviceInfoGenericResult: lang: str device_on: Optional[bool] on_time: Optional[int] - """The time in seconds this device has been ON since the last state change (ON/OFF).""" + """The time in seconds this device has been ON since the last state change (On/Off).""" nickname: str avatar: str has_set_location_info: bool diff --git a/tapo-py/tapo-py/tapo/responses/device_info_result/light_result.pyi b/tapo-py/tapo-py/tapo/responses/device_info_result/light_result.pyi index 065cb5a..78ec89c 100644 --- a/tapo-py/tapo-py/tapo/responses/device_info_result/light_result.pyi +++ b/tapo-py/tapo-py/tapo/responses/device_info_result/light_result.pyi @@ -22,7 +22,7 @@ class DeviceInfoLightResult: lang: str device_on: bool on_time: int - """The time in seconds this device has been ON since the last state change (ON/OFF).""" + """The time in seconds this device has been ON since the last state change (On/Off).""" nickname: str avatar: str has_set_location_info: bool diff --git a/tapo-py/tapo-py/tapo/responses/device_info_result/plug_energy_monitoring_result.pyi b/tapo-py/tapo-py/tapo/responses/device_info_result/plug_energy_monitoring_result.pyi index bcb2146..17f3f41 100644 --- a/tapo-py/tapo-py/tapo/responses/device_info_result/plug_energy_monitoring_result.pyi +++ b/tapo-py/tapo-py/tapo/responses/device_info_result/plug_energy_monitoring_result.pyi @@ -27,7 +27,7 @@ class DeviceInfoPlugEnergyMonitoringResult: lang: str device_on: bool on_time: int - """The time in seconds this device has been ON since the last state change (ON/OFF).""" + """The time in seconds this device has been ON since the last state change (On/Off).""" nickname: str avatar: str has_set_location_info: bool diff --git a/tapo-py/tapo-py/tapo/responses/device_info_result/plug_result.pyi b/tapo-py/tapo-py/tapo/responses/device_info_result/plug_result.pyi index 2f3cde2..343e5fb 100644 --- a/tapo-py/tapo-py/tapo/responses/device_info_result/plug_result.pyi +++ b/tapo-py/tapo-py/tapo/responses/device_info_result/plug_result.pyi @@ -20,7 +20,7 @@ class DeviceInfoPlugResult: lang: str device_on: bool on_time: int - """The time in seconds this device has been ON since the last state change (ON/OFF).""" + """The time in seconds this device has been ON since the last state change (On/Off).""" nickname: str avatar: str has_set_location_info: bool diff --git a/tapo-py/tapo-py/tapo/responses/device_info_result/power_strip_result.pyi b/tapo-py/tapo-py/tapo/responses/device_info_result/power_strip_result.pyi new file mode 100644 index 0000000..97d25a0 --- /dev/null +++ b/tapo-py/tapo-py/tapo/responses/device_info_result/power_strip_result.pyi @@ -0,0 +1,34 @@ +from typing import Optional + +class DeviceInfoPowerStripResult: + """Device info of Tapo P300 and P304. Superset of `GenericDeviceInfoResult`.""" + + device_id: str + type: str + model: str + hw_id: str + hw_ver: str + fw_id: str + fw_ver: str + oem_id: str + mac: str + ip: str + ssid: str + signal_level: int + rssi: int + specs: str + lang: str + nickname: str + avatar: str + has_set_location_info: bool + region: Optional[str] + latitude: Optional[float] + longitude: Optional[float] + time_diff: Optional[int] + + def to_dict(self) -> dict: + """Gets all the properties of this result as a dictionary. + + Returns: + dict: The result as a dictionary. + """ diff --git a/tapo/examples/tapo_p300.rs b/tapo/examples/tapo_p300.rs index 44f2521..3e6da85 100644 --- a/tapo/examples/tapo_p300.rs +++ b/tapo/examples/tapo_p300.rs @@ -47,6 +47,7 @@ async fn main() -> Result<(), Box> { plug.off().await?; info!("Waiting 2 seconds..."); + thread::sleep(Duration::from_secs(2)); } Ok(()) diff --git a/tapo/src/api/api_client.rs b/tapo/src/api/api_client.rs index 803f380..ddd11d5 100644 --- a/tapo/src/api/api_client.rs +++ b/tapo/src/api/api_client.rs @@ -431,7 +431,7 @@ impl ApiClient { Ok(PlugEnergyMonitoringHandler::new(self)) } - /// Specializes the given [`ApiClient`] into an authenticated [`PowerStripHandler`]. + /// Specializes the given [`ApiClient`] into an authenticated [`PlugEnergyMonitoringHandler`]. /// /// # Arguments /// @@ -444,17 +444,19 @@ impl ApiClient { /// # #[tokio::main] /// # async fn main() -> Result<(), Box> { /// let device = ApiClient::new("tapo-username@example.com", "tapo-password") - /// .p300("192.168.1.100") + /// .p115("192.168.1.100") /// .await?; - /// let child_device_list = device.get_child_device_list().await?; - /// println!("Child device list: {child_device_list:?}"); + /// device.on().await?; /// # Ok(()) /// # } /// ``` - pub async fn p300(mut self, ip_address: impl Into) -> Result { + pub async fn p115( + mut self, + ip_address: impl Into, + ) -> Result { self.login(ip_address).await?; - Ok(PowerStripHandler::new(self)) + Ok(PlugEnergyMonitoringHandler::new(self)) } /// Specializes the given [`ApiClient`] into an authenticated [`PowerStripHandler`]. @@ -470,20 +472,20 @@ impl ApiClient { /// # #[tokio::main] /// # async fn main() -> Result<(), Box> { /// let device = ApiClient::new("tapo-username@example.com", "tapo-password") - /// .p304("192.168.1.100") + /// .p300("192.168.1.100") /// .await?; /// let child_device_list = device.get_child_device_list().await?; /// println!("Child device list: {child_device_list:?}"); /// # Ok(()) /// # } /// ``` - pub async fn p304(mut self, ip_address: impl Into) -> Result { + pub async fn p300(mut self, ip_address: impl Into) -> Result { self.login(ip_address).await?; Ok(PowerStripHandler::new(self)) } - /// Specializes the given [`ApiClient`] into an authenticated [`PlugEnergyMonitoringHandler`]. + /// Specializes the given [`ApiClient`] into an authenticated [`PowerStripHandler`]. /// /// # Arguments /// @@ -496,19 +498,17 @@ impl ApiClient { /// # #[tokio::main] /// # async fn main() -> Result<(), Box> { /// let device = ApiClient::new("tapo-username@example.com", "tapo-password") - /// .p115("192.168.1.100") + /// .p304("192.168.1.100") /// .await?; - /// device.on().await?; + /// let child_device_list = device.get_child_device_list().await?; + /// println!("Child device list: {child_device_list:?}"); /// # Ok(()) /// # } /// ``` - pub async fn p115( - mut self, - ip_address: impl Into, - ) -> Result { + pub async fn p304(mut self, ip_address: impl Into) -> Result { self.login(ip_address).await?; - Ok(PlugEnergyMonitoringHandler::new(self)) + Ok(PowerStripHandler::new(self)) } /// Specializes the given [`ApiClient`] into an authenticated [`HubHandler`]. diff --git a/tapo/src/api/child_devices.rs b/tapo/src/api/child_devices.rs index 35b3d72..68c847f 100644 --- a/tapo/src/api/child_devices.rs +++ b/tapo/src/api/child_devices.rs @@ -1,5 +1,5 @@ mod ke100_handler; -mod plug_power_strip_handler; +mod power_strip_plug_handler; mod s200b_handler; mod t100_handler; mod t110_handler; @@ -7,7 +7,7 @@ mod t300_handler; mod t31x_handler; pub use ke100_handler::*; -pub use plug_power_strip_handler::*; +pub use power_strip_plug_handler::*; pub use s200b_handler::*; pub use t100_handler::*; pub use t110_handler::*; diff --git a/tapo/src/api/child_devices/plug_power_strip_handler.rs b/tapo/src/api/child_devices/power_strip_plug_handler.rs similarity index 85% rename from tapo/src/api/child_devices/plug_power_strip_handler.rs rename to tapo/src/api/child_devices/power_strip_plug_handler.rs index 37a848f..e33c940 100644 --- a/tapo/src/api/child_devices/plug_power_strip_handler.rs +++ b/tapo/src/api/child_devices/power_strip_plug_handler.rs @@ -5,28 +5,29 @@ use tokio::sync::RwLock; use crate::api::ApiClient; use crate::error::{Error, TapoResponseError}; use crate::requests::{EmptyParams, GenericSetDeviceInfoParams, TapoParams, TapoRequest}; -use crate::responses::{DecodableResultExt, PlugPowerStripResult}; +use crate::responses::{DecodableResultExt, PowerStripPlugResult}; -/// Handler for the [P300](https://www.tapo.com/en/search/?q=P300) child plugs. -pub struct PlugPowerStripHandler { +/// Handler for the [P300](https://www.tapo.com/en/search/?q=P300) and +/// [P304](https://www.tp-link.com/uk/search/?q=P304) child plugs. +pub struct PowerStripPlugHandler { client: Arc>, device_id: String, } -impl PlugPowerStripHandler { +impl PowerStripPlugHandler { pub(crate) fn new(client: Arc>, device_id: String) -> Self { Self { client, device_id } } - /// Returns *device info* as [`PlugPowerStripResult`]. + /// Returns *device info* as [`PowerStripPlugResult`]. /// It is not guaranteed to contain all the properties returned from the Tapo API. - pub async fn get_device_info(&self) -> Result { + pub async fn get_device_info(&self) -> Result { let request = TapoRequest::GetDeviceInfo(TapoParams::new(EmptyParams)); self.client .read() .await - .control_child::(self.device_id.clone(), request) + .control_child::(self.device_id.clone(), request) .await? .ok_or_else(|| Error::Tapo(TapoResponseError::EmptyResult)) .map(|result| result.decode())? diff --git a/tapo/src/api/power_strip_handler.rs b/tapo/src/api/power_strip_handler.rs index 9cc438c..db68c58 100644 --- a/tapo/src/api/power_strip_handler.rs +++ b/tapo/src/api/power_strip_handler.rs @@ -3,10 +3,10 @@ use std::sync::Arc; use tokio::sync::RwLock; use crate::api::ApiClient; -use crate::api::PlugPowerStripHandler; +use crate::api::PowerStripPlugHandler; use crate::error::Error; use crate::responses::{ - ChildDeviceListPowerStripResult, DeviceInfoPowerStripResult, PlugPowerStripResult, + ChildDeviceListPowerStripResult, DeviceInfoPowerStripResult, PowerStripPlugResult, }; /// Handler for the [P300](https://www.tapo.com/en/search/?q=P300) and @@ -42,16 +42,16 @@ impl PowerStripHandler { self.client.read().await.get_device_info().await } - /// Returns *child device list* as [`PlugPowerStripResult`]. + /// Returns *child device list* as [`Vec`]. /// It is not guaranteed to contain all the properties returned from the Tapo API /// or to support all the possible devices connected to the hub. - pub async fn get_child_device_list(&self) -> Result, Error> { + pub async fn get_child_device_list(&self) -> Result, Error> { self.client .read() .await .get_child_device_list::() .await - .map(|r| r.sub_plugs) + .map(|r| r.plugs) } /// Returns *child device list* as [`serde_json::Value`]. @@ -73,7 +73,7 @@ impl PowerStripHandler { /// Child device handler builders. impl PowerStripHandler { - /// Returns a [`PlugPowerStripHandler`] for the given [`Plug`]. + /// Returns a [`PowerStripPlugHandler`] for the given [`Plug`]. /// /// # Arguments /// @@ -97,7 +97,7 @@ impl PowerStripHandler { /// # Ok(()) /// # } /// ``` - pub async fn plug(&self, identifier: Plug) -> Result { + pub async fn plug(&self, identifier: Plug) -> Result { let children = self.get_child_device_list().await?; let device_id = match identifier { @@ -121,7 +121,7 @@ impl PowerStripHandler { .clone(), }; - Ok(PlugPowerStripHandler::new(self.client.clone(), device_id)) + Ok(PowerStripPlugHandler::new(self.client.clone(), device_id)) } } diff --git a/tapo/src/lib.rs b/tapo/src/lib.rs index abf4a03..aae7a59 100644 --- a/tapo/src/lib.rs +++ b/tapo/src/lib.rs @@ -3,7 +3,8 @@ //! Tapo API Client. //! //! Tested with light bulbs (L510, L520, L530, L535, L610, L630), light strips (L900, L920, L930), -//! plugs (P100, P105, P110, P115), power strips (P300, P304), hubs (H100), switches (S200B) and sensors (KE100, T100, T110, T300, T310, T315). +//! plugs (P100, P105, P110, P115), power strips (P300, P304), hubs (H100), switches (S200B) and +//! sensors (KE100, T100, T110, T300, T310, T315). //! //! # Example with L530 //! ```rust,no_run diff --git a/tapo/src/responses/child_device_list_power_strip_result.rs b/tapo/src/responses/child_device_list_power_strip_result.rs index 48eec48..1bbad85 100644 --- a/tapo/src/responses/child_device_list_power_strip_result.rs +++ b/tapo/src/responses/child_device_list_power_strip_result.rs @@ -1,81 +1,3 @@ -use serde::{Deserialize, Serialize}; +mod power_strip_plug_result; -use crate::error::Error; -use crate::responses::device_info_result::OverheatStatus; -use crate::responses::{decode_value, DecodableResultExt, TapoResponseExt}; - -/// Power Strip child device list result. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub(crate) struct ChildDeviceListPowerStripResult { - /// Power Strip child devices - #[serde(rename = "child_device_list")] - pub sub_plugs: Vec, -} - -impl DecodableResultExt for ChildDeviceListPowerStripResult { - fn decode(self) -> Result { - Ok(ChildDeviceListPowerStripResult { - sub_plugs: self - .sub_plugs - .into_iter() - .map(|d| d.decode()) - .collect::, _>>()?, - }) - } -} - -impl TapoResponseExt for ChildDeviceListPowerStripResult {} - -/// P300 power strip child plug. -/// -/// Specific properties: `auto_off_remain_time`, `auto_off_status`, -/// `bind_count`, `overheat_status`, `position`, `slot_number`. -#[derive(Debug, Clone, Serialize, Deserialize)] -#[allow(missing_docs)] -pub struct PlugPowerStripResult { - pub auto_off_remain_time: u64, - pub auto_off_status: AutoOffStatus, - pub avatar: String, - pub bind_count: u8, - pub category: String, - pub device_id: String, - pub device_on: bool, - pub fw_id: String, - pub fw_ver: String, - pub has_set_location_info: bool, - pub hw_id: String, - pub hw_ver: String, - pub latitude: Option, - pub longitude: Option, - pub mac: String, - pub model: String, - pub nickname: String, - pub oem_id: String, - /// The time in seconds this device has been ON since the last state change (ON/OFF). - pub on_time: u64, - pub original_device_id: String, - pub overheat_status: OverheatStatus, - pub position: u8, - pub region: Option, - pub slot_number: u8, - pub status_follow_edge: bool, - pub r#type: String, -} - -impl TapoResponseExt for PlugPowerStripResult {} - -impl DecodableResultExt for PlugPowerStripResult { - fn decode(mut self) -> Result { - self.nickname = decode_value(&self.nickname)?; - Ok(self) - } -} - -/// Auto Off Status. -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -#[allow(missing_docs)] -pub enum AutoOffStatus { - On, - Off, -} +pub use power_strip_plug_result::*; diff --git a/tapo/src/responses/child_device_list_power_strip_result/power_strip_plug_result.rs b/tapo/src/responses/child_device_list_power_strip_result/power_strip_plug_result.rs new file mode 100644 index 0000000..1accfcc --- /dev/null +++ b/tapo/src/responses/child_device_list_power_strip_result/power_strip_plug_result.rs @@ -0,0 +1,95 @@ +use serde::{Deserialize, Serialize}; + +use crate::error::Error; +use crate::responses::device_info_result::OverheatStatus; +use crate::responses::{decode_value, DecodableResultExt, TapoResponseExt}; + +/// Power Strip child device list result. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub(crate) struct ChildDeviceListPowerStripResult { + /// Power Strip child devices + #[serde(rename = "child_device_list")] + pub plugs: Vec, +} + +impl DecodableResultExt for ChildDeviceListPowerStripResult { + fn decode(self) -> Result { + Ok(ChildDeviceListPowerStripResult { + plugs: self + .plugs + .into_iter() + .map(|d| d.decode()) + .collect::, _>>()?, + }) + } +} + +impl TapoResponseExt for ChildDeviceListPowerStripResult {} + +/// P300 and P304 power strip child plugs. +/// +/// Specific properties: `auto_off_remain_time`, `auto_off_status`, +/// `bind_count`, `overheat_status`, `position`, `slot_number`. +#[derive(Debug, Clone, Serialize, Deserialize)] +#[cfg_attr(feature = "python", pyo3::prelude::pyclass(get_all))] +#[allow(missing_docs)] +pub struct PowerStripPlugResult { + pub auto_off_remain_time: u64, + pub auto_off_status: AutoOffStatus, + pub avatar: String, + pub bind_count: u8, + pub category: String, + pub device_id: String, + pub device_on: bool, + pub fw_id: String, + pub fw_ver: String, + pub has_set_location_info: bool, + pub hw_id: String, + pub hw_ver: String, + pub latitude: Option, + pub longitude: Option, + pub mac: String, + pub model: String, + pub nickname: String, + pub oem_id: String, + /// The time in seconds this device has been ON since the last state change (On/Off). + pub on_time: u64, + pub original_device_id: String, + pub overheat_status: OverheatStatus, + pub position: u8, + pub region: Option, + pub slot_number: u8, + pub status_follow_edge: bool, + pub r#type: String, +} + +#[cfg(feature = "python")] +#[pyo3::pymethods] +impl PowerStripPlugResult { + /// Gets all the properties of this result as a dictionary. + pub fn to_dict(&self, py: pyo3::Python) -> pyo3::PyResult> { + let value = serde_json::to_value(self) + .map_err(|e| pyo3::exceptions::PyException::new_err(e.to_string()))?; + + crate::python::serde_object_to_py_dict(py, &value) + } +} + +impl TapoResponseExt for PowerStripPlugResult {} + +impl DecodableResultExt for PowerStripPlugResult { + fn decode(mut self) -> Result { + self.nickname = decode_value(&self.nickname)?; + Ok(self) + } +} + +/// Auto Off Status. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +#[cfg_attr(feature = "python", pyo3::prelude::pyclass(get_all, eq, eq_int))] +#[allow(missing_docs)] +pub enum AutoOffStatus { + On, + Off, +} diff --git a/tapo/src/responses/device_info_result/color_light.rs b/tapo/src/responses/device_info_result/color_light.rs index df31e3f..55cccfd 100644 --- a/tapo/src/responses/device_info_result/color_light.rs +++ b/tapo/src/responses/device_info_result/color_light.rs @@ -27,7 +27,7 @@ pub struct DeviceInfoColorLightResult { pub specs: String, pub lang: String, pub device_on: bool, - /// The time in seconds this device has been ON since the last state change (ON/OFF). + /// The time in seconds this device has been ON since the last state change (On/Off). /// On v2 hardware this is always None. pub on_time: Option, pub nickname: String, diff --git a/tapo/src/responses/device_info_result/generic.rs b/tapo/src/responses/device_info_result/generic.rs index b22003a..994fde1 100644 --- a/tapo/src/responses/device_info_result/generic.rs +++ b/tapo/src/responses/device_info_result/generic.rs @@ -24,7 +24,7 @@ pub struct DeviceInfoGenericResult { pub specs: String, pub lang: String, pub device_on: Option, - /// The time in seconds this device has been ON since the last state change (ON/OFF). + /// The time in seconds this device has been ON since the last state change (On/Off). pub on_time: Option, pub nickname: String, pub avatar: String, diff --git a/tapo/src/responses/device_info_result/light.rs b/tapo/src/responses/device_info_result/light.rs index e6b0630..ca644a9 100644 --- a/tapo/src/responses/device_info_result/light.rs +++ b/tapo/src/responses/device_info_result/light.rs @@ -29,7 +29,7 @@ pub struct DeviceInfoLightResult { pub specs: String, pub lang: String, pub device_on: bool, - /// The time in seconds this device has been ON since the last state change (ON/OFF). + /// The time in seconds this device has been ON since the last state change (On/Off). /// On v2 hardware this is always None. pub on_time: Option, pub nickname: String, diff --git a/tapo/src/responses/device_info_result/plug.rs b/tapo/src/responses/device_info_result/plug.rs index c17fecc..502411d 100644 --- a/tapo/src/responses/device_info_result/plug.rs +++ b/tapo/src/responses/device_info_result/plug.rs @@ -27,7 +27,7 @@ pub struct DeviceInfoPlugResult { pub specs: String, pub lang: String, pub device_on: bool, - /// The time in seconds this device has been ON since the last state change (ON/OFF). + /// The time in seconds this device has been ON since the last state change (On/Off). pub on_time: u64, pub nickname: String, pub avatar: String, diff --git a/tapo/src/responses/device_info_result/plug_energy_monitoring.rs b/tapo/src/responses/device_info_result/plug_energy_monitoring.rs index af6f32a..84dc9d7 100644 --- a/tapo/src/responses/device_info_result/plug_energy_monitoring.rs +++ b/tapo/src/responses/device_info_result/plug_energy_monitoring.rs @@ -30,7 +30,7 @@ pub struct DeviceInfoPlugEnergyMonitoringResult { pub specs: String, pub lang: String, pub device_on: bool, - /// The time in seconds this device has been ON since the last state change (ON/OFF). + /// The time in seconds this device has been ON since the last state change (On/Off). pub on_time: u64, pub nickname: String, pub avatar: String, diff --git a/tapo/src/responses/device_info_result/power_strip.rs b/tapo/src/responses/device_info_result/power_strip.rs index f72bd83..91edc27 100644 --- a/tapo/src/responses/device_info_result/power_strip.rs +++ b/tapo/src/responses/device_info_result/power_strip.rs @@ -5,6 +5,7 @@ use crate::responses::{decode_value, DecodableResultExt, TapoResponseExt}; /// Device info of Tapo P300 and P304. Superset of [`crate::responses::DeviceInfoGenericResult`]. #[derive(Debug, Clone, Serialize, Deserialize)] +#[cfg_attr(feature = "python", pyo3::prelude::pyclass(get_all))] #[allow(missing_docs)] pub struct DeviceInfoPowerStripResult { // @@ -34,6 +35,18 @@ pub struct DeviceInfoPowerStripResult { pub time_diff: Option, } +#[cfg(feature = "python")] +#[pyo3::pymethods] +impl DeviceInfoPowerStripResult { + /// Gets all the properties of this result as a dictionary. + pub fn to_dict(&self, py: pyo3::Python) -> pyo3::PyResult> { + let value = serde_json::to_value(self) + .map_err(|e| pyo3::exceptions::PyException::new_err(e.to_string()))?; + + crate::python::serde_object_to_py_dict(py, &value) + } +} + impl TapoResponseExt for DeviceInfoPowerStripResult {} impl DecodableResultExt for DeviceInfoPowerStripResult {