feat(tapo-agent): add schedule/countdown timer API support

- Fork tapo crate to add missing schedule/timer APIs
- Add get_countdown_rules, get_schedule_rules, get_next_event methods
- New readings: countdown_active, countdown_remain, schedule_count,
  schedule_active_count, next_event_time
- Add local compilation to build script alongside cross-compilation
- Implement offline polling - device collection continues when server disconnects
- Add more device readings: on_time, signal_level, rssi, runtime_today/month, energy_month

Vendored tapo fork in tapo-fork/ with minimal changes to add schedule APIs.
This commit is contained in:
seb
2025-12-23 00:46:42 +01:00
parent f3cca149f9
commit 028763bdb2
274 changed files with 20784 additions and 48 deletions

View File

@@ -0,0 +1,29 @@
mod api_client;
mod child_devices;
mod color_light_handler;
mod discovery;
mod generic_device_handler;
mod hub_handler;
mod light_handler;
mod plug_energy_monitoring_handler;
mod plug_handler;
mod power_strip_energy_monitoring_handler;
mod power_strip_handler;
mod py_handler_ext;
mod rgb_light_strip_handler;
mod rgbic_light_strip_handler;
pub use api_client::*;
pub use child_devices::*;
pub use color_light_handler::*;
pub use discovery::*;
pub use generic_device_handler::*;
pub use hub_handler::*;
pub use light_handler::*;
pub use plug_energy_monitoring_handler::*;
pub use plug_handler::*;
pub use power_strip_energy_monitoring_handler::*;
pub use power_strip_handler::*;
pub use py_handler_ext::*;
pub use rgb_light_strip_handler::*;
pub use rgbic_light_strip_handler::*;

View File

@@ -0,0 +1,165 @@
use std::time::Duration;
use pyo3::prelude::*;
use tapo::{
ApiClient, ColorLightHandler, DeviceDiscovery, GenericDeviceHandler, HubHandler, LightHandler,
PlugEnergyMonitoringHandler, PlugHandler, PowerStripEnergyMonitoringHandler, PowerStripHandler,
RgbLightStripHandler, RgbicLightStripHandler,
};
use crate::call_handler_constructor;
use crate::errors::ErrorWrapper;
use super::{
PyColorLightHandler, PyDeviceDiscovery, PyGenericDeviceHandler, PyHubHandler, PyLightHandler,
PyPlugEnergyMonitoringHandler, PyPlugHandler, PyPowerStripEnergyMonitoringHandler,
PyPowerStripHandler, PyRgbLightStripHandler, PyRgbicLightStripHandler,
};
#[pyclass(name = "ApiClient")]
pub struct PyApiClient {
client: ApiClient,
}
#[pymethods]
impl PyApiClient {
#[new]
#[pyo3(signature = (tapo_username, tapo_password, timeout_s=None))]
pub fn new(
tapo_username: String,
tapo_password: String,
timeout_s: Option<u64>,
) -> Result<Self, ErrorWrapper> {
let client = match timeout_s {
Some(timeout_s) => ApiClient::new(tapo_username, tapo_password)
.with_timeout(Duration::from_secs(timeout_s)),
None => ApiClient::new(tapo_username, tapo_password),
};
Ok(Self { client })
}
pub async fn discover_devices(
&self,
target: String,
timeout_s: u64,
) -> Result<PyDeviceDiscovery, ErrorWrapper> {
let discovery: DeviceDiscovery =
call_handler_constructor!(self, tapo::ApiClient::discover_devices, target, timeout_s);
Ok(PyDeviceDiscovery::new(discovery))
}
pub async fn generic_device(&self, ip_address: String) -> PyResult<PyGenericDeviceHandler> {
let handler: GenericDeviceHandler =
call_handler_constructor!(self, tapo::ApiClient::generic_device, ip_address);
Ok(PyGenericDeviceHandler::new(handler))
}
pub async fn l510(&self, ip_address: String) -> PyResult<PyLightHandler> {
let handler: LightHandler =
call_handler_constructor!(self, tapo::ApiClient::l510, ip_address);
Ok(PyLightHandler::new(handler))
}
pub async fn l520(&self, ip_address: String) -> PyResult<PyLightHandler> {
let handler: LightHandler =
call_handler_constructor!(self, tapo::ApiClient::l520, ip_address);
Ok(PyLightHandler::new(handler))
}
pub async fn l530(&self, ip_address: String) -> PyResult<PyColorLightHandler> {
let handler: ColorLightHandler =
call_handler_constructor!(self, tapo::ApiClient::l530, ip_address);
Ok(PyColorLightHandler::new(handler))
}
pub async fn l535(&self, ip_address: String) -> PyResult<PyColorLightHandler> {
let handler: ColorLightHandler =
call_handler_constructor!(self, tapo::ApiClient::l535, ip_address);
Ok(PyColorLightHandler::new(handler))
}
pub async fn l610(&self, ip_address: String) -> PyResult<PyLightHandler> {
let handler: LightHandler =
call_handler_constructor!(self, tapo::ApiClient::l610, ip_address);
Ok(PyLightHandler::new(handler))
}
pub async fn l630(&self, ip_address: String) -> PyResult<PyColorLightHandler> {
let handler: ColorLightHandler =
call_handler_constructor!(self, tapo::ApiClient::l630, ip_address);
Ok(PyColorLightHandler::new(handler))
}
pub async fn l900(&self, ip_address: String) -> PyResult<PyRgbLightStripHandler> {
let handler: RgbLightStripHandler =
call_handler_constructor!(self, tapo::ApiClient::l900, ip_address);
Ok(PyRgbLightStripHandler::new(handler))
}
pub async fn l920(&self, ip_address: String) -> PyResult<PyRgbicLightStripHandler> {
let handler: RgbicLightStripHandler =
call_handler_constructor!(self, tapo::ApiClient::l920, ip_address);
Ok(PyRgbicLightStripHandler::new(handler))
}
pub async fn l930(&self, ip_address: String) -> PyResult<PyRgbicLightStripHandler> {
let handler: RgbicLightStripHandler =
call_handler_constructor!(self, tapo::ApiClient::l930, ip_address);
Ok(PyRgbicLightStripHandler::new(handler))
}
pub async fn p100(&self, ip_address: String) -> PyResult<PyPlugHandler> {
let handler: PlugHandler =
call_handler_constructor!(self, tapo::ApiClient::p100, ip_address);
Ok(PyPlugHandler::new(handler))
}
pub async fn p105(&self, ip_address: String) -> PyResult<PyPlugHandler> {
let handler: PlugHandler =
call_handler_constructor!(self, tapo::ApiClient::p105, ip_address);
Ok(PyPlugHandler::new(handler))
}
pub async fn p110(&self, ip_address: String) -> PyResult<PyPlugEnergyMonitoringHandler> {
let handler: PlugEnergyMonitoringHandler =
call_handler_constructor!(self, tapo::ApiClient::p110, ip_address);
Ok(PyPlugEnergyMonitoringHandler::new(handler))
}
pub async fn p115(&self, ip_address: String) -> PyResult<PyPlugEnergyMonitoringHandler> {
let handler: PlugEnergyMonitoringHandler =
call_handler_constructor!(self, tapo::ApiClient::p115, ip_address);
Ok(PyPlugEnergyMonitoringHandler::new(handler))
}
pub async fn p300(&self, ip_address: String) -> PyResult<PyPowerStripHandler> {
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<PyPowerStripEnergyMonitoringHandler> {
let handler: PowerStripEnergyMonitoringHandler =
call_handler_constructor!(self, tapo::ApiClient::p304, ip_address);
Ok(PyPowerStripEnergyMonitoringHandler::new(handler))
}
pub async fn p306(&self, ip_address: String) -> PyResult<PyPowerStripHandler> {
let handler: PowerStripHandler =
call_handler_constructor!(self, tapo::ApiClient::p306, ip_address);
Ok(PyPowerStripHandler::new(handler))
}
pub async fn p316(&self, ip_address: String) -> PyResult<PyPowerStripEnergyMonitoringHandler> {
let handler: PowerStripEnergyMonitoringHandler =
call_handler_constructor!(self, tapo::ApiClient::p316, ip_address);
Ok(PyPowerStripEnergyMonitoringHandler::new(handler))
}
pub async fn h100(&self, ip_address: String) -> PyResult<PyHubHandler> {
let handler: HubHandler =
call_handler_constructor!(self, tapo::ApiClient::h100, ip_address);
Ok(PyHubHandler::new(handler))
}
}

View File

@@ -0,0 +1,17 @@
mod ke100_handler;
mod power_strip_plug_energy_monitoring_handler;
mod power_strip_plug_handler;
mod s200b_handler;
mod t100_handler;
mod t110_handler;
mod t300_handler;
mod t31x_handler;
pub use ke100_handler::*;
pub use power_strip_plug_energy_monitoring_handler::*;
pub use power_strip_plug_handler::*;
pub use s200b_handler::*;
pub use t31x_handler::*;
pub use t100_handler::*;
pub use t110_handler::*;
pub use t300_handler::*;

View File

@@ -0,0 +1,101 @@
use std::{ops::Deref, sync::Arc};
use pyo3::{prelude::*, types::PyDict};
use tapo::KE100Handler;
use tapo::responses::{KE100Result, TemperatureUnitKE100};
use crate::call_handler_method;
#[derive(Clone)]
#[pyclass(name = "KE100Handler")]
pub struct PyKE100Handler {
inner: Arc<KE100Handler>,
}
impl PyKE100Handler {
pub fn new(handler: KE100Handler) -> Self {
Self {
inner: Arc::new(handler),
}
}
}
#[pymethods]
impl PyKE100Handler {
pub async fn get_device_info(&self) -> PyResult<KE100Result> {
let handler = self.inner.clone();
call_handler_method!(handler.deref(), KE100Handler::get_device_info)
}
pub async fn get_device_info_json(&self) -> PyResult<Py<PyDict>> {
let handler = self.inner.clone();
let result = call_handler_method!(handler.deref(), KE100Handler::get_device_info_json)?;
Python::attach(|py| tapo::python::serde_object_to_py_dict(py, &result))
}
pub async fn set_child_protection(&self, on: bool) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(handler.deref(), KE100Handler::set_child_protection, on)
}
pub async fn set_frost_protection(&self, on: bool) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(handler.deref(), KE100Handler::set_frost_protection, on)
}
pub async fn set_max_control_temperature(
&self,
value: u8,
unit: TemperatureUnitKE100,
) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(
handler.deref(),
KE100Handler::set_max_control_temperature,
value,
unit
)
}
pub async fn set_min_control_temperature(
&self,
value: u8,
unit: TemperatureUnitKE100,
) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(
handler.deref(),
KE100Handler::set_min_control_temperature,
value,
unit
)
}
pub async fn set_target_temperature(
&self,
value: u8,
unit: TemperatureUnitKE100,
) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(
handler.deref(),
KE100Handler::set_target_temperature,
value,
unit
)
}
pub async fn set_temperature_offset(
&self,
value: i8,
unit: TemperatureUnitKE100,
) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(
handler.deref(),
KE100Handler::set_temperature_offset,
value,
unit
)
}
}

View File

@@ -0,0 +1,132 @@
use std::{ops::Deref, sync::Arc};
use chrono::{DateTime, NaiveDate, Utc};
use pyo3::{prelude::*, types::PyDict};
use tapo::PowerStripPlugEnergyMonitoringHandler;
use tapo::requests::{EnergyDataInterval, PowerDataInterval};
use tapo::responses::{
CurrentPowerResult, DeviceUsageEnergyMonitoringResult, EnergyDataResult, EnergyUsageResult,
PowerDataResult, PowerStripPlugEnergyMonitoringResult,
};
use crate::call_handler_method;
use crate::requests::{PyEnergyDataInterval, PyPowerDataInterval};
#[derive(Clone)]
#[pyclass(name = "PowerStripPlugEnergyMonitoringHandler")]
pub struct PyPowerStripPlugEnergyMonitoringHandler {
inner: Arc<PowerStripPlugEnergyMonitoringHandler>,
}
impl PyPowerStripPlugEnergyMonitoringHandler {
pub fn new(handler: PowerStripPlugEnergyMonitoringHandler) -> Self {
Self {
inner: Arc::new(handler),
}
}
}
#[pymethods]
impl PyPowerStripPlugEnergyMonitoringHandler {
pub async fn get_device_info(&self) -> PyResult<PowerStripPlugEnergyMonitoringResult> {
let handler = self.inner.clone();
call_handler_method!(
handler.deref(),
PowerStripPlugEnergyMonitoringHandler::get_device_info
)
}
pub async fn get_device_info_json(&self) -> PyResult<Py<PyDict>> {
let handler = self.inner.clone();
let result = call_handler_method!(
handler.deref(),
PowerStripPlugEnergyMonitoringHandler::get_device_info_json
)?;
Python::attach(|py| tapo::python::serde_object_to_py_dict(py, &result))
}
pub async fn on(&self) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(handler.deref(), PowerStripPlugEnergyMonitoringHandler::on)
}
pub async fn off(&self) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(handler.deref(), PowerStripPlugEnergyMonitoringHandler::off)
}
pub async fn get_current_power(&self) -> PyResult<CurrentPowerResult> {
let handler = self.inner.clone();
call_handler_method!(
handler.deref(),
PowerStripPlugEnergyMonitoringHandler::get_current_power,
)
}
pub async fn get_device_usage(&self) -> PyResult<DeviceUsageEnergyMonitoringResult> {
let handler = self.inner.clone();
call_handler_method!(
handler.deref(),
PowerStripPlugEnergyMonitoringHandler::get_device_usage,
)
}
pub async fn get_energy_usage(&self) -> PyResult<EnergyUsageResult> {
let handler = self.inner.clone();
call_handler_method!(
handler.deref(),
PowerStripPlugEnergyMonitoringHandler::get_energy_usage,
)
}
#[pyo3(signature = (interval, start_date, end_date=None))]
pub async fn get_energy_data(
&self,
interval: PyEnergyDataInterval,
start_date: NaiveDate,
end_date: Option<NaiveDate>,
) -> PyResult<EnergyDataResult> {
let interval = match interval {
PyEnergyDataInterval::Hourly => EnergyDataInterval::Hourly {
start_date,
end_date: end_date.unwrap_or(start_date),
},
PyEnergyDataInterval::Daily => EnergyDataInterval::Daily { start_date },
PyEnergyDataInterval::Monthly => EnergyDataInterval::Monthly { start_date },
};
let handler = self.inner.clone();
let result = call_handler_method!(
handler.deref(),
PowerStripPlugEnergyMonitoringHandler::get_energy_data,
interval
)?;
Ok(result)
}
pub async fn get_power_data(
&self,
interval: PyPowerDataInterval,
start_date_time: DateTime<Utc>,
end_date_time: DateTime<Utc>,
) -> PyResult<PowerDataResult> {
let interval = match interval {
PyPowerDataInterval::Every5Minutes => PowerDataInterval::Every5Minutes {
start_date_time,
end_date_time,
},
PyPowerDataInterval::Hourly => PowerDataInterval::Hourly {
start_date_time,
end_date_time,
},
};
let handler = self.inner.clone();
let result = call_handler_method!(
handler.deref(),
PowerStripPlugEnergyMonitoringHandler::get_power_data,
interval
)?;
Ok(result)
}
}

View File

@@ -0,0 +1,46 @@
use std::{ops::Deref, sync::Arc};
use pyo3::{prelude::*, types::PyDict};
use tapo::PowerStripPlugHandler;
use tapo::responses::PowerStripPlugResult;
use crate::call_handler_method;
#[derive(Clone)]
#[pyclass(name = "PowerStripPlugHandler")]
pub struct PyPowerStripPlugHandler {
inner: Arc<PowerStripPlugHandler>,
}
impl PyPowerStripPlugHandler {
pub fn new(handler: PowerStripPlugHandler) -> Self {
Self {
inner: Arc::new(handler),
}
}
}
#[pymethods]
impl PyPowerStripPlugHandler {
pub async fn get_device_info(&self) -> PyResult<PowerStripPlugResult> {
let handler = self.inner.clone();
call_handler_method!(handler.deref(), PowerStripPlugHandler::get_device_info)
}
pub async fn get_device_info_json(&self) -> PyResult<Py<PyDict>> {
let handler = self.inner.clone();
let result =
call_handler_method!(handler.deref(), PowerStripPlugHandler::get_device_info_json)?;
Python::attach(|py| tapo::python::serde_object_to_py_dict(py, &result))
}
pub async fn on(&self) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(handler.deref(), PowerStripPlugHandler::on)
}
pub async fn off(&self) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(handler.deref(), PowerStripPlugHandler::off)
}
}

View File

@@ -0,0 +1,51 @@
use std::{ops::Deref, sync::Arc};
use pyo3::{prelude::*, types::PyDict};
use tapo::S200BHandler;
use tapo::responses::S200BResult;
use crate::call_handler_method;
use crate::responses::TriggerLogsS200BResult;
#[derive(Clone)]
#[pyclass(name = "S200BHandler")]
pub struct PyS200BHandler {
inner: Arc<S200BHandler>,
}
impl PyS200BHandler {
pub fn new(handler: S200BHandler) -> Self {
Self {
inner: Arc::new(handler),
}
}
}
#[pymethods]
impl PyS200BHandler {
pub async fn get_device_info(&self) -> PyResult<S200BResult> {
let handler = self.inner.clone();
call_handler_method!(handler.deref(), S200BHandler::get_device_info)
}
pub async fn get_device_info_json(&self) -> PyResult<Py<PyDict>> {
let handler = self.inner.clone();
let result = call_handler_method!(handler.deref(), S200BHandler::get_device_info_json)?;
Python::attach(|py| tapo::python::serde_object_to_py_dict(py, &result))
}
pub async fn get_trigger_logs(
&self,
page_size: u64,
start_id: u64,
) -> PyResult<TriggerLogsS200BResult> {
let handler = self.inner.clone();
call_handler_method!(
handler.deref(),
S200BHandler::get_trigger_logs,
page_size,
start_id
)
.map(|result| result.into())
}
}

View File

@@ -0,0 +1,50 @@
use std::{ops::Deref, sync::Arc};
use pyo3::{prelude::*, types::PyDict};
use tapo::{T100Handler, responses::T100Result};
use crate::call_handler_method;
use crate::responses::TriggerLogsT100Result;
#[derive(Clone)]
#[pyclass(name = "T100Handler")]
pub struct PyT100Handler {
inner: Arc<T100Handler>,
}
impl PyT100Handler {
pub fn new(handler: T100Handler) -> Self {
Self {
inner: Arc::new(handler),
}
}
}
#[pymethods]
impl PyT100Handler {
pub async fn get_device_info(&self) -> PyResult<T100Result> {
let handler = self.inner.clone();
call_handler_method!(handler.deref(), T100Handler::get_device_info)
}
pub async fn get_device_info_json(&self) -> PyResult<Py<PyDict>> {
let handler = self.inner.clone();
let result = call_handler_method!(handler.deref(), T100Handler::get_device_info_json)?;
Python::attach(|py| tapo::python::serde_object_to_py_dict(py, &result))
}
pub async fn get_trigger_logs(
&self,
page_size: u64,
start_id: u64,
) -> PyResult<TriggerLogsT100Result> {
let handler = self.inner.clone();
call_handler_method!(
handler.deref(),
T100Handler::get_trigger_logs,
page_size,
start_id
)
.map(|result| result.into())
}
}

View File

@@ -0,0 +1,51 @@
use std::{ops::Deref, sync::Arc};
use pyo3::{prelude::*, types::PyDict};
use tapo::T110Handler;
use tapo::responses::T110Result;
use crate::call_handler_method;
use crate::responses::TriggerLogsT110Result;
#[derive(Clone)]
#[pyclass(name = "T110Handler")]
pub struct PyT110Handler {
inner: Arc<T110Handler>,
}
impl PyT110Handler {
pub fn new(handler: T110Handler) -> Self {
Self {
inner: Arc::new(handler),
}
}
}
#[pymethods]
impl PyT110Handler {
pub async fn get_device_info(&self) -> PyResult<T110Result> {
let handler = self.inner.clone();
call_handler_method!(handler.deref(), T110Handler::get_device_info)
}
pub async fn get_device_info_json(&self) -> PyResult<Py<PyDict>> {
let handler = self.inner.clone();
let result = call_handler_method!(handler.deref(), T110Handler::get_device_info_json)?;
Python::attach(|py| tapo::python::serde_object_to_py_dict(py, &result))
}
pub async fn get_trigger_logs(
&self,
page_size: u64,
start_id: u64,
) -> PyResult<TriggerLogsT110Result> {
let handler = self.inner.clone();
call_handler_method!(
handler.deref(),
T110Handler::get_trigger_logs,
page_size,
start_id
)
.map(|result| result.into())
}
}

View File

@@ -0,0 +1,51 @@
use std::{ops::Deref, sync::Arc};
use pyo3::{prelude::*, types::PyDict};
use tapo::T300Handler;
use tapo::responses::T300Result;
use crate::call_handler_method;
use crate::responses::TriggerLogsT300Result;
#[derive(Clone)]
#[pyclass(name = "T300Handler")]
pub struct PyT300Handler {
inner: Arc<T300Handler>,
}
impl PyT300Handler {
pub fn new(handler: T300Handler) -> Self {
Self {
inner: Arc::new(handler),
}
}
}
#[pymethods]
impl PyT300Handler {
pub async fn get_device_info(&self) -> PyResult<T300Result> {
let handler = self.inner.clone();
call_handler_method!(handler.deref(), T300Handler::get_device_info)
}
pub async fn get_device_info_json(&self) -> PyResult<Py<PyDict>> {
let handler = self.inner.clone();
let result = call_handler_method!(handler.deref(), T300Handler::get_device_info_json)?;
Python::attach(|py| tapo::python::serde_object_to_py_dict(py, &result))
}
pub async fn get_trigger_logs(
&self,
page_size: u64,
start_id: u64,
) -> PyResult<TriggerLogsT300Result> {
let handler = self.inner.clone();
call_handler_method!(
handler.deref(),
T300Handler::get_trigger_logs,
page_size,
start_id
)
.map(|result| result.into())
}
}

View File

@@ -0,0 +1,43 @@
use std::{ops::Deref, sync::Arc};
use pyo3::{prelude::*, types::PyDict};
use tapo::T31XHandler;
use tapo::responses::{T31XResult, TemperatureHumidityRecords};
use crate::call_handler_method;
#[derive(Clone)]
#[pyclass(name = "T31XHandler")]
pub struct PyT31XHandler {
inner: Arc<T31XHandler>,
}
impl PyT31XHandler {
pub fn new(handler: T31XHandler) -> Self {
Self {
inner: Arc::new(handler),
}
}
}
#[pymethods]
impl PyT31XHandler {
pub async fn get_device_info(&self) -> PyResult<T31XResult> {
let handler = self.inner.clone();
call_handler_method!(handler.deref(), T31XHandler::get_device_info)
}
pub async fn get_device_info_json(&self) -> PyResult<Py<PyDict>> {
let handler = self.inner.clone();
let result = call_handler_method!(handler.deref(), T31XHandler::get_device_info_json)?;
Python::attach(|py| tapo::python::serde_object_to_py_dict(py, &result))
}
pub async fn get_temperature_humidity_records(&self) -> PyResult<TemperatureHumidityRecords> {
let handler = self.inner.clone();
call_handler_method!(
handler.deref(),
T31XHandler::get_temperature_humidity_records
)
}
}

View File

@@ -0,0 +1,138 @@
use std::ops::{Deref, DerefMut};
use std::sync::Arc;
use pyo3::prelude::*;
use pyo3::types::PyDict;
use tapo::requests::Color;
use tapo::responses::{DeviceInfoColorLightResult, DeviceUsageEnergyMonitoringResult};
use tapo::{ColorLightHandler, DeviceManagementExt as _, HandlerExt};
use tokio::sync::RwLock;
use crate::api::PyHandlerExt;
use crate::call_handler_method;
use crate::requests::PyColorLightSetDeviceInfoParams;
#[derive(Clone)]
#[pyclass(name = "ColorLightHandler")]
pub struct PyColorLightHandler {
inner: Arc<RwLock<ColorLightHandler>>,
}
impl PyColorLightHandler {
pub fn new(handler: ColorLightHandler) -> Self {
Self {
inner: Arc::new(RwLock::new(handler)),
}
}
}
impl PyHandlerExt for PyColorLightHandler {
fn get_inner_handler(&self) -> Arc<RwLock<impl HandlerExt + 'static>> {
Arc::clone(&self.inner)
}
}
#[pymethods]
impl PyColorLightHandler {
pub async fn refresh_session(&self) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(
handler.write().await.deref_mut(),
ColorLightHandler::refresh_session,
discard_result
)
}
pub async fn on(&self) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(handler.read().await.deref(), ColorLightHandler::on)
}
pub async fn off(&self) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(handler.read().await.deref(), ColorLightHandler::off)
}
pub async fn device_reboot(&self, delay_s: u16) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(
handler.read().await.deref(),
ColorLightHandler::device_reboot,
delay_s
)
}
pub async fn device_reset(&self) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(
handler.read().await.deref(),
ColorLightHandler::device_reset,
)
}
pub async fn get_device_info(&self) -> PyResult<DeviceInfoColorLightResult> {
let handler = self.inner.clone();
call_handler_method!(
handler.read().await.deref(),
ColorLightHandler::get_device_info
)
}
pub async fn get_device_info_json(&self) -> PyResult<Py<PyDict>> {
let handler = self.inner.clone();
let result = call_handler_method!(
handler.read().await.deref(),
ColorLightHandler::get_device_info_json,
)?;
Python::attach(|py| tapo::python::serde_object_to_py_dict(py, &result))
}
pub async fn get_device_usage(&self) -> PyResult<DeviceUsageEnergyMonitoringResult> {
let handler = self.inner.clone();
call_handler_method!(
handler.read().await.deref(),
ColorLightHandler::get_device_usage
)
}
pub fn set(&self) -> PyColorLightSetDeviceInfoParams {
PyColorLightSetDeviceInfoParams::new()
}
pub async fn set_brightness(&self, brightness: u8) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(
handler.read().await.deref(),
ColorLightHandler::set_brightness,
brightness
)
}
pub async fn set_color(&self, color: Color) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(
handler.read().await.deref(),
ColorLightHandler::set_color,
color
)
}
pub async fn set_hue_saturation(&self, hue: u16, saturation: u8) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(
handler.read().await.deref(),
ColorLightHandler::set_hue_saturation,
hue,
saturation
)
}
pub async fn set_color_temperature(&self, color_temperature: u16) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(
handler.read().await.deref(),
ColorLightHandler::set_color_temperature,
color_temperature
)
}
}

View File

@@ -0,0 +1,5 @@
mod device_discovery;
mod discovery_result;
pub use device_discovery::*;
pub use discovery_result::*;

View File

@@ -0,0 +1,87 @@
use std::sync::Arc;
use pyo3::prelude::*;
use tapo::{DeviceDiscovery, StreamExt as _};
use tokio::sync::Mutex;
use super::{PyMaybeDiscoveryResult, convert_result_to_maybe_py};
#[derive(Clone)]
#[pyclass(name = "DeviceDiscovery")]
pub struct PyDeviceDiscovery {
pub inner: Arc<Mutex<DeviceDiscovery>>,
}
impl PyDeviceDiscovery {
pub fn new(inner: DeviceDiscovery) -> Self {
Self {
inner: Arc::new(Mutex::new(inner)),
}
}
}
#[pymethods]
impl PyDeviceDiscovery {
fn __iter__(slf: PyRef<'_, Self>) -> PyResult<PyDeviceDiscoveryIter> {
Ok(PyDeviceDiscoveryIter {
inner: (*slf).inner.clone(),
})
}
fn __aiter__(slf: PyRef<'_, Self>) -> PyResult<PyDeviceDiscoveryIter> {
Ok(PyDeviceDiscoveryIter {
inner: (*slf).inner.clone(),
})
}
}
#[pyclass(name = "DeviceDiscoveryIter")]
pub struct PyDeviceDiscoveryIter {
pub inner: Arc<Mutex<DeviceDiscovery>>,
}
#[pymethods]
impl PyDeviceDiscoveryIter {
fn __iter__(slf: Py<Self>) -> Py<Self> {
slf
}
fn __aiter__(slf: Py<Self>) -> Py<Self> {
slf
}
fn __next__(slf: PyRefMut<'_, Self>) -> PyResult<Option<PyMaybeDiscoveryResult>> {
let inner = (*slf).inner.clone();
let result = Python::attach(|py| {
py.detach(|| {
crate::runtime::tokio().block_on(async {
let mut guard = inner.lock().await;
guard.next().await
})
})
});
if let Some(result) = result {
let result_maybe_py = convert_result_to_maybe_py(result)?;
Ok(Some(result_maybe_py))
} else {
Ok(None)
}
}
fn __anext__<'py>(slf: PyRefMut<'_, Self>, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
let inner = (*slf).inner.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let mut guard = inner.lock().await;
let result = guard.next().await;
match result {
Some(result) => convert_result_to_maybe_py(result),
None => Err(PyErr::new::<pyo3::exceptions::PyStopAsyncIteration, _>(
"No more devices found",
)),
}
})
}
}

View File

@@ -0,0 +1,170 @@
use pyo3::prelude::*;
use tapo::responses::{
DeviceInfoColorLightResult, DeviceInfoGenericResult, DeviceInfoHubResult,
DeviceInfoLightResult, DeviceInfoPlugEnergyMonitoringResult, DeviceInfoPlugResult,
DeviceInfoPowerStripResult, DeviceInfoRgbLightStripResult, DeviceInfoRgbicLightStripResult,
};
use tapo::{DiscoveryResult, Error};
use crate::api::{
PyColorLightHandler, PyGenericDeviceHandler, PyHubHandler, PyLightHandler,
PyPlugEnergyMonitoringHandler, PyPlugHandler, PyPowerStripEnergyMonitoringHandler,
PyPowerStripHandler, PyRgbLightStripHandler, PyRgbicLightStripHandler,
};
use crate::errors::ErrorWrapper;
#[pyclass(name = "DiscoveryResult")]
#[allow(clippy::large_enum_variant)]
pub enum PyDiscoveryResult {
GenericDevice {
device_info: DeviceInfoGenericResult,
handler: PyGenericDeviceHandler,
},
Light {
device_info: DeviceInfoLightResult,
handler: PyLightHandler,
},
ColorLight {
device_info: DeviceInfoColorLightResult,
handler: PyColorLightHandler,
},
RgbLightStrip {
device_info: DeviceInfoRgbLightStripResult,
handler: PyRgbLightStripHandler,
},
RgbicLightStrip {
device_info: DeviceInfoRgbicLightStripResult,
handler: PyRgbicLightStripHandler,
},
Plug {
device_info: DeviceInfoPlugResult,
handler: PyPlugHandler,
},
PlugEnergyMonitoring {
device_info: DeviceInfoPlugEnergyMonitoringResult,
handler: PyPlugEnergyMonitoringHandler,
},
PowerStrip {
device_info: DeviceInfoPowerStripResult,
handler: PyPowerStripHandler,
},
PowerStripEnergyMonitoring {
device_info: DeviceInfoPowerStripResult,
handler: PyPowerStripEnergyMonitoringHandler,
},
Hub {
device_info: DeviceInfoHubResult,
handler: PyHubHandler,
},
}
#[pyclass(name = "MaybeDiscoveryResult")]
pub struct PyMaybeDiscoveryResult {
result: Option<PyDiscoveryResult>,
exception: Option<ErrorWrapper>,
}
#[pymethods]
impl PyMaybeDiscoveryResult {
pub fn get(mut slf: PyRefMut<'_, Self>) -> PyResult<PyDiscoveryResult> {
if let Some(result) = slf.result.take() {
Ok(result)
} else if let Some(exception) = slf.exception.take() {
Err(exception.into())
} else {
Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
"No result or exception available. `get` can only be called once.",
))
}
}
}
pub fn convert_result_to_maybe_py(
result: Result<DiscoveryResult, Error>,
) -> PyResult<PyMaybeDiscoveryResult> {
match result {
Ok(result) => Ok(PyMaybeDiscoveryResult {
result: Some(convert_result_to_py(result)),
exception: None,
}),
Err(e) => Ok(PyMaybeDiscoveryResult {
result: None,
exception: Some(ErrorWrapper::from(e)),
}),
}
}
fn convert_result_to_py(result: DiscoveryResult) -> PyDiscoveryResult {
match result {
DiscoveryResult::GenericDevice {
device_info,
handler,
} => PyDiscoveryResult::GenericDevice {
device_info: *device_info,
handler: PyGenericDeviceHandler::new(handler),
},
DiscoveryResult::Light {
device_info,
handler,
} => PyDiscoveryResult::Light {
device_info: *device_info,
handler: PyLightHandler::new(handler),
},
DiscoveryResult::ColorLight {
device_info,
handler,
} => PyDiscoveryResult::ColorLight {
device_info: *device_info,
handler: PyColorLightHandler::new(handler),
},
DiscoveryResult::RgbLightStrip {
device_info,
handler,
} => PyDiscoveryResult::RgbLightStrip {
device_info: *device_info,
handler: PyRgbLightStripHandler::new(handler),
},
DiscoveryResult::RgbicLightStrip {
device_info,
handler,
} => PyDiscoveryResult::RgbicLightStrip {
device_info: *device_info,
handler: PyRgbicLightStripHandler::new(handler),
},
DiscoveryResult::Plug {
device_info,
handler,
} => PyDiscoveryResult::Plug {
device_info: *device_info,
handler: PyPlugHandler::new(handler),
},
DiscoveryResult::PlugEnergyMonitoring {
device_info,
handler,
} => PyDiscoveryResult::PlugEnergyMonitoring {
device_info: *device_info,
handler: PyPlugEnergyMonitoringHandler::new(handler),
},
DiscoveryResult::PowerStrip {
device_info,
handler,
} => PyDiscoveryResult::PowerStrip {
device_info: *device_info,
handler: PyPowerStripHandler::new(handler),
},
DiscoveryResult::PowerStripEnergyMonitoring {
device_info,
handler,
} => PyDiscoveryResult::PowerStripEnergyMonitoring {
device_info: *device_info,
handler: PyPowerStripEnergyMonitoringHandler::new(handler),
},
DiscoveryResult::Hub {
device_info,
handler,
} => PyDiscoveryResult::Hub {
device_info: *device_info,
handler: PyHubHandler::new(handler),
},
}
}

View File

@@ -0,0 +1,63 @@
use std::ops::{Deref, DerefMut};
use std::sync::Arc;
use pyo3::prelude::*;
use pyo3::types::PyDict;
use tapo::GenericDeviceHandler;
use tapo::responses::DeviceInfoGenericResult;
use tokio::sync::RwLock;
use crate::call_handler_method;
#[derive(Clone)]
#[pyclass(name = "GenericDeviceHandler")]
pub struct PyGenericDeviceHandler {
inner: Arc<RwLock<GenericDeviceHandler>>,
}
impl PyGenericDeviceHandler {
pub fn new(handler: GenericDeviceHandler) -> Self {
Self {
inner: Arc::new(RwLock::new(handler)),
}
}
}
#[pymethods]
impl PyGenericDeviceHandler {
pub async fn refresh_session(&self) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(
handler.write().await.deref_mut(),
GenericDeviceHandler::refresh_session,
discard_result
)
}
pub async fn on(&self) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(handler.read().await.deref(), GenericDeviceHandler::on)
}
pub async fn off(&self) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(handler.read().await.deref(), GenericDeviceHandler::off)
}
pub async fn get_device_info(&self) -> PyResult<DeviceInfoGenericResult> {
let handler = self.inner.clone();
call_handler_method!(
handler.read().await.deref(),
GenericDeviceHandler::get_device_info
)
}
pub async fn get_device_info_json(&self) -> PyResult<Py<PyDict>> {
let handler = self.inner.clone();
let result = call_handler_method!(
handler.read().await.deref(),
GenericDeviceHandler::get_device_info_json,
)?;
Python::attach(|py| tapo::python::serde_object_to_py_dict(py, &result))
}
}

View File

@@ -0,0 +1,291 @@
use std::ops::{Deref, DerefMut};
use std::sync::Arc;
use pyo3::prelude::*;
use pyo3::types::{PyDict, PyList};
use tapo::requests::{AlarmDuration, AlarmRingtone, AlarmVolume};
use tapo::responses::{ChildDeviceHubResult, DeviceInfoHubResult};
use tapo::{DeviceManagementExt as _, Error, HubDevice, HubHandler};
use tokio::sync::RwLock;
use crate::api::{
PyKE100Handler, PyS200BHandler, PyT31XHandler, PyT100Handler, PyT110Handler, PyT300Handler,
};
use crate::call_handler_method;
use crate::errors::ErrorWrapper;
use crate::requests::PyAlarmDuration;
#[derive(Clone)]
#[pyclass(name = "HubHandler")]
pub struct PyHubHandler {
inner: Arc<RwLock<HubHandler>>,
}
impl PyHubHandler {
pub fn new(handler: HubHandler) -> Self {
Self {
inner: Arc::new(RwLock::new(handler)),
}
}
fn parse_identifier(
device_id: Option<String>,
nickname: Option<String>,
) -> PyResult<HubDevice> {
match (device_id, nickname) {
(Some(device_id), _) => Ok(HubDevice::ByDeviceId(device_id)),
(None, Some(nickname)) => Ok(HubDevice::ByNickname(nickname)),
_ => Err(Into::<ErrorWrapper>::into(Error::Validation {
field: "identifier".to_string(),
message: "Either a device_id or nickname must be provided".to_string(),
})
.into()),
}
}
}
#[pymethods]
impl PyHubHandler {
pub async fn refresh_session(&self) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(
handler.write().await.deref_mut(),
HubHandler::refresh_session,
discard_result
)
}
pub async fn device_reboot(&self, delay_s: u16) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(
handler.read().await.deref(),
HubHandler::device_reboot,
delay_s
)
}
pub async fn device_reset(&self) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(handler.read().await.deref(), HubHandler::device_reset)
}
pub async fn get_device_info(&self) -> PyResult<DeviceInfoHubResult> {
let handler = self.inner.clone();
call_handler_method!(handler.read().await.deref(), HubHandler::get_device_info)
}
pub async fn get_device_info_json(&self) -> PyResult<Py<PyDict>> {
let handler = self.inner.clone();
let result = call_handler_method!(
handler.read().await.deref(),
HubHandler::get_device_info_json
)?;
Python::attach(|py| tapo::python::serde_object_to_py_dict(py, &result))
}
pub async fn get_child_device_list(&self) -> PyResult<Py<PyList>> {
let handler = self.inner.clone();
let children = call_handler_method!(
handler.read().await.deref(),
HubHandler::get_child_device_list
)?;
Python::attach(|py| {
let results = PyList::empty(py);
for child in children {
match child {
ChildDeviceHubResult::KE100(device) => {
results.append(device.into_pyobject(py)?)?;
}
ChildDeviceHubResult::S200B(device) => {
results.append(device.into_pyobject(py)?)?;
}
ChildDeviceHubResult::T100(device) => {
results.append(device.into_pyobject(py)?)?;
}
ChildDeviceHubResult::T110(device) => {
results.append(device.into_pyobject(py)?)?;
}
ChildDeviceHubResult::T300(device) => {
results.append(device.into_pyobject(py)?)?;
}
ChildDeviceHubResult::T310(device) => {
results.append(device.into_pyobject(py)?)?;
}
ChildDeviceHubResult::T315(device) => {
results.append(device.into_pyobject(py)?)?;
}
_ => {
results.append(py.None())?;
}
}
}
Ok(results.into())
})
}
pub async fn get_child_device_list_json(&self, start_index: u64) -> PyResult<Py<PyDict>> {
let handler = self.inner.clone();
let result = call_handler_method!(
handler.read().await.deref(),
HubHandler::get_child_device_list_json,
start_index
)?;
Python::attach(|py| tapo::python::serde_object_to_py_dict(py, &result))
}
pub async fn get_child_device_component_list_json(&self) -> PyResult<Py<PyDict>> {
let handler = self.inner.clone();
let result = call_handler_method!(
handler.read().await.deref(),
HubHandler::get_child_device_component_list_json
)?;
Python::attach(|py| tapo::python::serde_object_to_py_dict(py, &result))
}
pub async fn get_supported_ringtone_list(&self) -> PyResult<Vec<String>> {
let handler = self.inner.clone();
call_handler_method!(
handler.read().await.deref(),
HubHandler::get_supported_ringtone_list
)
}
#[pyo3(signature = (ringtone, volume, duration, seconds=None))]
pub async fn play_alarm(
&self,
ringtone: AlarmRingtone,
volume: AlarmVolume,
duration: PyAlarmDuration,
seconds: Option<u32>,
) -> PyResult<()> {
let handler = self.inner.clone();
let duration = match duration {
PyAlarmDuration::Continuous => AlarmDuration::Continuous,
PyAlarmDuration::Once => AlarmDuration::Once,
PyAlarmDuration::Seconds => {
if let Some(seconds) = seconds {
AlarmDuration::Seconds(seconds)
} else {
return Err(Into::<ErrorWrapper>::into(Error::Validation {
field: "seconds".to_string(),
message:
"A value must be provided for seconds when duration = AlarmDuration.Seconds"
.to_string(),
})
.into());
}
}
};
call_handler_method!(
handler.read().await.deref(),
HubHandler::play_alarm,
ringtone,
volume,
duration
)
}
pub async fn stop_alarm(&self) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(handler.read().await.deref(), HubHandler::stop_alarm)
}
#[pyo3(signature = (device_id=None, nickname=None))]
pub async fn ke100(
&self,
device_id: Option<String>,
nickname: Option<String>,
) -> PyResult<PyKE100Handler> {
let handler = self.inner.clone();
let identifier = PyHubHandler::parse_identifier(device_id, nickname)?;
let child_handler =
call_handler_method!(handler.read().await.deref(), HubHandler::ke100, identifier)?;
Ok(PyKE100Handler::new(child_handler))
}
#[pyo3(signature = (device_id=None, nickname=None))]
pub async fn s200b(
&self,
device_id: Option<String>,
nickname: Option<String>,
) -> PyResult<PyS200BHandler> {
let handler = self.inner.clone();
let identifier = PyHubHandler::parse_identifier(device_id, nickname)?;
let child_handler =
call_handler_method!(handler.read().await.deref(), HubHandler::s200b, identifier)?;
Ok(PyS200BHandler::new(child_handler))
}
#[pyo3(signature = (device_id=None, nickname=None))]
pub async fn t100(
&self,
device_id: Option<String>,
nickname: Option<String>,
) -> PyResult<PyT100Handler> {
let handler = self.inner.clone();
let identifier = PyHubHandler::parse_identifier(device_id, nickname)?;
let child_handler =
call_handler_method!(handler.read().await.deref(), HubHandler::t100, identifier)?;
Ok(PyT100Handler::new(child_handler))
}
#[pyo3(signature = (device_id=None, nickname=None))]
pub async fn t110(
&self,
device_id: Option<String>,
nickname: Option<String>,
) -> PyResult<PyT110Handler> {
let handler = self.inner.clone();
let identifier = PyHubHandler::parse_identifier(device_id, nickname)?;
let child_handler =
call_handler_method!(handler.read().await.deref(), HubHandler::t110, identifier)?;
Ok(PyT110Handler::new(child_handler))
}
#[pyo3(signature = (device_id=None, nickname=None))]
pub async fn t300(
&self,
device_id: Option<String>,
nickname: Option<String>,
) -> PyResult<PyT300Handler> {
let handler = self.inner.clone();
let identifier = PyHubHandler::parse_identifier(device_id, nickname)?;
let child_handler =
call_handler_method!(handler.read().await.deref(), HubHandler::t300, identifier)?;
Ok(PyT300Handler::new(child_handler))
}
#[pyo3(signature = (device_id=None, nickname=None))]
pub async fn t310(
&self,
device_id: Option<String>,
nickname: Option<String>,
) -> PyResult<PyT31XHandler> {
let handler = self.inner.clone();
let identifier = PyHubHandler::parse_identifier(device_id, nickname)?;
let child_handler =
call_handler_method!(handler.read().await.deref(), HubHandler::t310, identifier)?;
Ok(PyT31XHandler::new(child_handler))
}
#[pyo3(signature = (device_id=None, nickname=None))]
pub async fn t315(
&self,
device_id: Option<String>,
nickname: Option<String>,
) -> PyResult<PyT31XHandler> {
self.t310(device_id, nickname).await
}
}

View File

@@ -0,0 +1,88 @@
use std::ops::{Deref, DerefMut};
use std::sync::Arc;
use pyo3::prelude::*;
use pyo3::types::PyDict;
use tapo::responses::{DeviceInfoLightResult, DeviceUsageEnergyMonitoringResult};
use tapo::{DeviceManagementExt as _, LightHandler};
use tokio::sync::RwLock;
use crate::call_handler_method;
#[derive(Clone)]
#[pyclass(name = "LightHandler")]
pub struct PyLightHandler {
inner: Arc<RwLock<LightHandler>>,
}
impl PyLightHandler {
pub fn new(handler: LightHandler) -> Self {
Self {
inner: Arc::new(RwLock::new(handler)),
}
}
}
#[pymethods]
impl PyLightHandler {
pub async fn refresh_session(&self) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(
handler.write().await.deref_mut(),
LightHandler::refresh_session,
discard_result
)
}
pub async fn on(&self) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(handler.read().await.deref(), LightHandler::on)
}
pub async fn off(&self) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(handler.read().await.deref(), LightHandler::off)
}
pub async fn device_reboot(&self, delay_s: u16) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(
handler.read().await.deref(),
LightHandler::device_reboot,
delay_s
)
}
pub async fn device_reset(&self) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(handler.read().await.deref(), LightHandler::device_reset)
}
pub async fn get_device_info(&self) -> PyResult<DeviceInfoLightResult> {
let handler = self.inner.clone();
call_handler_method!(handler.read().await.deref(), LightHandler::get_device_info)
}
pub async fn get_device_info_json(&self) -> PyResult<Py<PyDict>> {
let handler = self.inner.clone();
let result = call_handler_method!(
handler.read().await.deref(),
LightHandler::get_device_info_json
)?;
Python::attach(|py| tapo::python::serde_object_to_py_dict(py, &result))
}
pub async fn get_device_usage(&self) -> PyResult<DeviceUsageEnergyMonitoringResult> {
let handler = self.inner.clone();
call_handler_method!(handler.read().await.deref(), LightHandler::get_device_usage)
}
pub async fn set_brightness(&self, brightness: u8) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(
handler.read().await.deref(),
LightHandler::set_brightness,
brightness
)
}
}

View File

@@ -0,0 +1,167 @@
use std::ops::{Deref, DerefMut};
use std::sync::Arc;
use chrono::{DateTime, NaiveDate, Utc};
use pyo3::prelude::*;
use pyo3::types::PyDict;
use tapo::requests::{EnergyDataInterval, PowerDataInterval};
use tapo::responses::{
CurrentPowerResult, DeviceInfoPlugEnergyMonitoringResult, DeviceUsageEnergyMonitoringResult,
EnergyDataResult, EnergyUsageResult, PowerDataResult,
};
use tapo::{DeviceManagementExt as _, PlugEnergyMonitoringHandler};
use tokio::sync::RwLock;
use crate::call_handler_method;
use crate::requests::{PyEnergyDataInterval, PyPowerDataInterval};
#[derive(Clone)]
#[pyclass(name = "PlugEnergyMonitoringHandler")]
pub struct PyPlugEnergyMonitoringHandler {
inner: Arc<RwLock<PlugEnergyMonitoringHandler>>,
}
impl PyPlugEnergyMonitoringHandler {
pub fn new(handler: PlugEnergyMonitoringHandler) -> Self {
Self {
inner: Arc::new(RwLock::new(handler)),
}
}
}
#[pymethods]
impl PyPlugEnergyMonitoringHandler {
pub async fn refresh_session(&self) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(
handler.write().await.deref_mut(),
PlugEnergyMonitoringHandler::refresh_session,
discard_result
)
}
pub async fn on(&self) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(
handler.read().await.deref(),
PlugEnergyMonitoringHandler::on
)
}
pub async fn off(&self) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(
handler.read().await.deref(),
PlugEnergyMonitoringHandler::off
)
}
pub async fn device_reboot(&self, delay_s: u16) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(
handler.read().await.deref(),
PlugEnergyMonitoringHandler::device_reboot,
delay_s
)
}
pub async fn device_reset(&self) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(
handler.read().await.deref(),
PlugEnergyMonitoringHandler::device_reset,
)
}
pub async fn get_device_info(&self) -> PyResult<DeviceInfoPlugEnergyMonitoringResult> {
let handler = self.inner.clone();
call_handler_method!(
handler.read().await.deref(),
PlugEnergyMonitoringHandler::get_device_info,
)
}
pub async fn get_device_info_json(&self) -> PyResult<Py<PyDict>> {
let handler = self.inner.clone();
let result = call_handler_method!(
handler.read().await.deref(),
PlugEnergyMonitoringHandler::get_device_info_json,
)?;
Python::attach(|py| tapo::python::serde_object_to_py_dict(py, &result))
}
pub async fn get_current_power(&self) -> PyResult<CurrentPowerResult> {
let handler = self.inner.clone();
call_handler_method!(
handler.read().await.deref(),
PlugEnergyMonitoringHandler::get_current_power,
)
}
pub async fn get_device_usage(&self) -> PyResult<DeviceUsageEnergyMonitoringResult> {
let handler = self.inner.clone();
call_handler_method!(
handler.read().await.deref(),
PlugEnergyMonitoringHandler::get_device_usage,
)
}
pub async fn get_energy_usage(&self) -> PyResult<EnergyUsageResult> {
let handler = self.inner.clone();
call_handler_method!(
handler.read().await.deref(),
PlugEnergyMonitoringHandler::get_energy_usage,
)
}
#[pyo3(signature = (interval, start_date, end_date=None))]
pub async fn get_energy_data(
&self,
interval: PyEnergyDataInterval,
start_date: NaiveDate,
end_date: Option<NaiveDate>,
) -> PyResult<EnergyDataResult> {
let interval = match interval {
PyEnergyDataInterval::Hourly => EnergyDataInterval::Hourly {
start_date,
end_date: end_date.unwrap_or(start_date),
},
PyEnergyDataInterval::Daily => EnergyDataInterval::Daily { start_date },
PyEnergyDataInterval::Monthly => EnergyDataInterval::Monthly { start_date },
};
let handler = self.inner.clone();
let result = call_handler_method!(
handler.read().await.deref(),
PlugEnergyMonitoringHandler::get_energy_data,
interval
)?;
Ok(result)
}
pub async fn get_power_data(
&self,
interval: PyPowerDataInterval,
start_date_time: DateTime<Utc>,
end_date_time: DateTime<Utc>,
) -> PyResult<PowerDataResult> {
let interval = match interval {
PyPowerDataInterval::Every5Minutes => PowerDataInterval::Every5Minutes {
start_date_time,
end_date_time,
},
PyPowerDataInterval::Hourly => PowerDataInterval::Hourly {
start_date_time,
end_date_time,
},
};
let handler = self.inner.clone();
let result = call_handler_method!(
handler.read().await.deref(),
PlugEnergyMonitoringHandler::get_power_data,
interval
)?;
Ok(result)
}
}

View File

@@ -0,0 +1,79 @@
use std::ops::{Deref, DerefMut};
use std::sync::Arc;
use pyo3::prelude::*;
use pyo3::types::PyDict;
use tapo::responses::{DeviceInfoPlugResult, DeviceUsageResult};
use tapo::{DeviceManagementExt as _, PlugHandler};
use tokio::sync::RwLock;
use crate::call_handler_method;
#[derive(Clone)]
#[pyclass(name = "PlugHandler")]
pub struct PyPlugHandler {
inner: Arc<RwLock<PlugHandler>>,
}
impl PyPlugHandler {
pub fn new(handler: PlugHandler) -> Self {
Self {
inner: Arc::new(RwLock::new(handler)),
}
}
}
#[pymethods]
impl PyPlugHandler {
pub async fn refresh_session(&self) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(
handler.write().await.deref_mut(),
PlugHandler::refresh_session,
discard_result
)
}
pub async fn on(&self) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(handler.read().await.deref(), PlugHandler::on)
}
pub async fn off(&self) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(handler.read().await.deref(), PlugHandler::off)
}
pub async fn device_reboot(&self, delay_s: u16) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(
handler.read().await.deref(),
PlugHandler::device_reboot,
delay_s
)
}
pub async fn device_reset(&self) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(handler.read().await.deref(), PlugHandler::device_reset)
}
pub async fn get_device_info(&self) -> PyResult<DeviceInfoPlugResult> {
let handler = self.inner.clone();
call_handler_method!(handler.read().await.deref(), PlugHandler::get_device_info)
}
pub async fn get_device_info_json(&self) -> PyResult<Py<PyDict>> {
let handler = self.inner.clone();
let result = call_handler_method!(
handler.read().await.deref(),
PlugHandler::get_device_info_json
)?;
Python::attach(|py| tapo::python::serde_object_to_py_dict(py, &result))
}
pub async fn get_device_usage(&self) -> PyResult<DeviceUsageResult> {
let handler = self.inner.clone();
call_handler_method!(handler.read().await.deref(), PlugHandler::get_device_usage)
}
}

View File

@@ -0,0 +1,144 @@
use std::ops::{Deref, DerefMut};
use std::sync::Arc;
use pyo3::prelude::*;
use pyo3::types::{PyDict, PyList};
use tapo::responses::DeviceInfoPowerStripResult;
use tapo::{DeviceManagementExt as _, Error, Plug, PowerStripEnergyMonitoringHandler};
use tokio::sync::RwLock;
use crate::api::PyPowerStripPlugEnergyMonitoringHandler;
use crate::call_handler_method;
use crate::errors::ErrorWrapper;
#[derive(Clone)]
#[pyclass(name = "PowerStripEnergyMonitoringHandler")]
pub struct PyPowerStripEnergyMonitoringHandler {
inner: Arc<RwLock<PowerStripEnergyMonitoringHandler>>,
}
impl PyPowerStripEnergyMonitoringHandler {
pub fn new(handler: PowerStripEnergyMonitoringHandler) -> Self {
Self {
inner: Arc::new(RwLock::new(handler)),
}
}
fn parse_identifier(
device_id: Option<String>,
nickname: Option<String>,
position: Option<u8>,
) -> PyResult<Plug> {
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::<ErrorWrapper>::into(Error::Validation {
field: "identifier".to_string(),
message: "Either a device_id, nickname, or position must be provided".to_string(),
})
.into()),
}
}
}
#[pymethods]
impl PyPowerStripEnergyMonitoringHandler {
pub async fn refresh_session(&self) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(
handler.write().await.deref_mut(),
PowerStripEnergyMonitoringHandler::refresh_session,
discard_result
)
}
pub async fn device_reboot(&self, delay_s: u16) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(
handler.read().await.deref(),
PowerStripEnergyMonitoringHandler::device_reboot,
delay_s
)
}
pub async fn device_reset(&self) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(
handler.read().await.deref(),
PowerStripEnergyMonitoringHandler::device_reset,
)
}
pub async fn get_device_info(&self) -> PyResult<DeviceInfoPowerStripResult> {
let handler = self.inner.clone();
call_handler_method!(
handler.read().await.deref(),
PowerStripEnergyMonitoringHandler::get_device_info
)
}
pub async fn get_device_info_json(&self) -> PyResult<Py<PyDict>> {
let handler = self.inner.clone();
let result = call_handler_method!(
handler.read().await.deref(),
PowerStripEnergyMonitoringHandler::get_device_info_json
)?;
Python::attach(|py| tapo::python::serde_object_to_py_dict(py, &result))
}
pub async fn get_child_device_list(&self) -> PyResult<Py<PyList>> {
let handler = self.inner.clone();
let children = call_handler_method!(
handler.read().await.deref(),
PowerStripEnergyMonitoringHandler::get_child_device_list
)?;
Python::attach(|py| {
let results = PyList::empty(py);
for child in children {
results.append(child.into_pyobject(py)?)?;
}
Ok(results.into())
})
}
pub async fn get_child_device_list_json(&self) -> PyResult<Py<PyDict>> {
let handler = self.inner.clone();
let result = call_handler_method!(
handler.read().await.deref(),
PowerStripEnergyMonitoringHandler::get_child_device_list_json
)?;
Python::attach(|py| tapo::python::serde_object_to_py_dict(py, &result))
}
pub async fn get_child_device_component_list_json(&self) -> PyResult<Py<PyDict>> {
let handler = self.inner.clone();
let result = call_handler_method!(
handler.read().await.deref(),
PowerStripEnergyMonitoringHandler::get_child_device_component_list_json
)?;
Python::attach(|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<String>,
nickname: Option<String>,
position: Option<u8>,
) -> PyResult<PyPowerStripPlugEnergyMonitoringHandler> {
let handler = self.inner.clone();
let identifier =
PyPowerStripEnergyMonitoringHandler::parse_identifier(device_id, nickname, position)?;
let child_handler = call_handler_method!(
handler.read().await.deref(),
PowerStripEnergyMonitoringHandler::plug,
identifier
)?;
Ok(PyPowerStripPlugEnergyMonitoringHandler::new(child_handler))
}
}

View File

@@ -0,0 +1,143 @@
use std::ops::{Deref, DerefMut};
use std::sync::Arc;
use pyo3::prelude::*;
use pyo3::types::{PyDict, PyList};
use tapo::responses::DeviceInfoPowerStripResult;
use tapo::{DeviceManagementExt as _, Error, Plug, PowerStripHandler};
use tokio::sync::RwLock;
use crate::api::PyPowerStripPlugHandler;
use crate::call_handler_method;
use crate::errors::ErrorWrapper;
#[derive(Clone)]
#[pyclass(name = "PowerStripHandler")]
pub struct PyPowerStripHandler {
inner: Arc<RwLock<PowerStripHandler>>,
}
impl PyPowerStripHandler {
pub fn new(handler: PowerStripHandler) -> Self {
Self {
inner: Arc::new(RwLock::new(handler)),
}
}
fn parse_identifier(
device_id: Option<String>,
nickname: Option<String>,
position: Option<u8>,
) -> PyResult<Plug> {
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::<ErrorWrapper>::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.inner.clone();
call_handler_method!(
handler.write().await.deref_mut(),
PowerStripHandler::refresh_session,
discard_result
)
}
pub async fn device_reboot(&self, delay_s: u16) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(
handler.read().await.deref(),
PowerStripHandler::device_reboot,
delay_s
)
}
pub async fn device_reset(&self) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(
handler.read().await.deref(),
PowerStripHandler::device_reset,
)
}
pub async fn get_device_info(&self) -> PyResult<DeviceInfoPowerStripResult> {
let handler = self.inner.clone();
call_handler_method!(
handler.read().await.deref(),
PowerStripHandler::get_device_info
)
}
pub async fn get_device_info_json(&self) -> PyResult<Py<PyDict>> {
let handler = self.inner.clone();
let result = call_handler_method!(
handler.read().await.deref(),
PowerStripHandler::get_device_info_json
)?;
Python::attach(|py| tapo::python::serde_object_to_py_dict(py, &result))
}
pub async fn get_child_device_list(&self) -> PyResult<Py<PyList>> {
let handler = self.inner.clone();
let children = call_handler_method!(
handler.read().await.deref(),
PowerStripHandler::get_child_device_list
)?;
Python::attach(|py| {
let results = PyList::empty(py);
for child in children {
results.append(child.into_pyobject(py)?)?;
}
Ok(results.into())
})
}
pub async fn get_child_device_list_json(&self) -> PyResult<Py<PyDict>> {
let handler = self.inner.clone();
let result = call_handler_method!(
handler.read().await.deref(),
PowerStripHandler::get_child_device_list_json
)?;
Python::attach(|py| tapo::python::serde_object_to_py_dict(py, &result))
}
pub async fn get_child_device_component_list_json(&self) -> PyResult<Py<PyDict>> {
let handler = self.inner.clone();
let result = call_handler_method!(
handler.read().await.deref(),
PowerStripHandler::get_child_device_component_list_json
)?;
Python::attach(|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<String>,
nickname: Option<String>,
position: Option<u8>,
) -> PyResult<PyPowerStripPlugHandler> {
let handler = self.inner.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))
}
}

View File

@@ -0,0 +1,8 @@
use std::sync::Arc;
use tapo::HandlerExt;
use tokio::sync::RwLock;
pub trait PyHandlerExt: Send + Sync + Sized {
fn get_inner_handler(&self) -> Arc<RwLock<impl HandlerExt + 'static>>;
}

View File

@@ -0,0 +1,138 @@
use std::ops::{Deref, DerefMut};
use std::sync::Arc;
use pyo3::prelude::*;
use pyo3::types::PyDict;
use tapo::requests::Color;
use tapo::responses::{DeviceInfoRgbLightStripResult, DeviceUsageEnergyMonitoringResult};
use tapo::{DeviceManagementExt as _, HandlerExt, RgbLightStripHandler};
use tokio::sync::RwLock;
use crate::api::PyHandlerExt;
use crate::call_handler_method;
use crate::requests::PyColorLightSetDeviceInfoParams;
#[derive(Clone)]
#[pyclass(name = "RgbLightStripHandler")]
pub struct PyRgbLightStripHandler {
pub inner: Arc<RwLock<RgbLightStripHandler>>,
}
impl PyRgbLightStripHandler {
pub fn new(handler: RgbLightStripHandler) -> Self {
Self {
inner: Arc::new(RwLock::new(handler)),
}
}
}
impl PyHandlerExt for PyRgbLightStripHandler {
fn get_inner_handler(&self) -> Arc<RwLock<impl HandlerExt + 'static>> {
Arc::clone(&self.inner)
}
}
#[pymethods]
impl PyRgbLightStripHandler {
pub async fn refresh_session(&self) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(
handler.write().await.deref_mut(),
RgbLightStripHandler::refresh_session,
discard_result
)
}
pub async fn on(&self) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(handler.read().await.deref(), RgbLightStripHandler::on)
}
pub async fn off(&self) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(handler.read().await.deref(), RgbLightStripHandler::off)
}
pub async fn device_reboot(&self, delay_s: u16) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(
handler.read().await.deref(),
RgbLightStripHandler::device_reboot,
delay_s
)
}
pub async fn device_reset(&self) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(
handler.read().await.deref(),
RgbLightStripHandler::device_reset,
)
}
pub async fn get_device_info(&self) -> PyResult<DeviceInfoRgbLightStripResult> {
let handler = self.inner.clone();
call_handler_method!(
handler.read().await.deref(),
RgbLightStripHandler::get_device_info
)
}
pub async fn get_device_info_json(&self) -> PyResult<Py<PyDict>> {
let handler = self.inner.clone();
let result = call_handler_method!(
handler.read().await.deref(),
RgbLightStripHandler::get_device_info_json,
)?;
Python::attach(|py| tapo::python::serde_object_to_py_dict(py, &result))
}
pub async fn get_device_usage(&self) -> PyResult<DeviceUsageEnergyMonitoringResult> {
let handler = self.inner.clone();
call_handler_method!(
handler.read().await.deref(),
RgbLightStripHandler::get_device_usage
)
}
pub fn set(&self) -> PyColorLightSetDeviceInfoParams {
PyColorLightSetDeviceInfoParams::new()
}
pub async fn set_brightness(&self, brightness: u8) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(
handler.read().await.deref(),
RgbLightStripHandler::set_brightness,
brightness
)
}
pub async fn set_color(&self, color: Color) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(
handler.read().await.deref(),
RgbLightStripHandler::set_color,
color
)
}
pub async fn set_hue_saturation(&self, hue: u16, saturation: u8) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(
handler.read().await.deref(),
RgbLightStripHandler::set_hue_saturation,
hue,
saturation
)
}
pub async fn set_color_temperature(&self, color_temperature: u16) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(
handler.read().await.deref(),
RgbLightStripHandler::set_color_temperature,
color_temperature
)
}
}

View File

@@ -0,0 +1,167 @@
use std::ops::{Deref, DerefMut};
use std::sync::Arc;
use pyo3::exceptions::PyTypeError;
use pyo3::prelude::*;
use pyo3::types::PyDict;
use tapo::requests::{Color, LightingEffect, LightingEffectPreset};
use tapo::responses::{DeviceInfoRgbicLightStripResult, DeviceUsageEnergyMonitoringResult};
use tapo::{DeviceManagementExt as _, HandlerExt, RgbicLightStripHandler};
use tokio::sync::RwLock;
use crate::api::PyHandlerExt;
use crate::call_handler_method;
use crate::requests::{PyColorLightSetDeviceInfoParams, PyLightingEffect};
#[derive(Clone)]
#[pyclass(name = "RgbicLightStripHandler")]
pub struct PyRgbicLightStripHandler {
pub inner: Arc<RwLock<RgbicLightStripHandler>>,
}
impl PyRgbicLightStripHandler {
pub fn new(handler: RgbicLightStripHandler) -> Self {
Self {
inner: Arc::new(RwLock::new(handler)),
}
}
}
impl PyHandlerExt for PyRgbicLightStripHandler {
fn get_inner_handler(&self) -> Arc<RwLock<impl HandlerExt + 'static>> {
Arc::clone(&self.inner)
}
}
#[pymethods]
impl PyRgbicLightStripHandler {
pub async fn refresh_session(&self) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(
handler.write().await.deref_mut(),
RgbicLightStripHandler::refresh_session,
discard_result
)
}
pub async fn on(&self) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(handler.read().await.deref(), RgbicLightStripHandler::on)
}
pub async fn off(&self) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(handler.read().await.deref(), RgbicLightStripHandler::off)
}
pub async fn device_reboot(&self, delay_s: u16) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(
handler.read().await.deref(),
RgbicLightStripHandler::device_reboot,
delay_s
)
}
pub async fn device_reset(&self) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(
handler.read().await.deref(),
RgbicLightStripHandler::device_reset,
)
}
pub async fn get_device_info(&self) -> PyResult<DeviceInfoRgbicLightStripResult> {
let handler = self.inner.clone();
call_handler_method!(
handler.read().await.deref(),
RgbicLightStripHandler::get_device_info
)
}
pub async fn get_device_info_json(&self) -> PyResult<Py<PyDict>> {
let handler = self.inner.clone();
let result = call_handler_method!(
handler.read().await.deref(),
RgbicLightStripHandler::get_device_info_json,
)?;
Python::attach(|py| tapo::python::serde_object_to_py_dict(py, &result))
}
pub async fn get_device_usage(&self) -> PyResult<DeviceUsageEnergyMonitoringResult> {
let handler = self.inner.clone();
call_handler_method!(
handler.read().await.deref(),
RgbicLightStripHandler::get_device_usage
)
}
pub fn set(&self) -> PyColorLightSetDeviceInfoParams {
PyColorLightSetDeviceInfoParams::new()
}
pub async fn set_brightness(&self, brightness: u8) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(
handler.read().await.deref(),
RgbicLightStripHandler::set_brightness,
brightness
)
}
pub async fn set_color(&self, color: Color) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(
handler.read().await.deref(),
RgbicLightStripHandler::set_color,
color
)
}
pub async fn set_hue_saturation(&self, hue: u16, saturation: u8) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(
handler.read().await.deref(),
RgbicLightStripHandler::set_hue_saturation,
hue,
saturation
)
}
pub async fn set_color_temperature(&self, color_temperature: u16) -> PyResult<()> {
let handler = self.inner.clone();
call_handler_method!(
handler.read().await.deref(),
RgbicLightStripHandler::set_color_temperature,
color_temperature
)
}
pub async fn set_lighting_effect(&self, lighting_effect: Py<PyAny>) -> PyResult<()> {
let handler = self.inner.clone();
let lighting_effect = map_lighting_effect(lighting_effect)?;
call_handler_method!(
handler.read().await.deref(),
RgbicLightStripHandler::set_lighting_effect,
lighting_effect
)
}
}
fn map_lighting_effect(lighting_effect: Py<PyAny>) -> PyResult<LightingEffect> {
if let Some(lighting_effect) =
Python::attach(|py| lighting_effect.extract::<LightingEffectPreset>(py).ok())
{
return Ok(lighting_effect.into());
}
if let Some(lighting_effect) =
Python::attach(|py| lighting_effect.extract::<PyLightingEffect>(py).ok())
{
return Ok(lighting_effect.into());
}
Err(PyErr::new::<PyTypeError, _>(
"Invalid lighting effect type. Must be one of `LightingEffect` or `LightingEffectPreset`",
))
}

View File

@@ -0,0 +1,23 @@
use pyo3::PyErr;
use pyo3::exceptions::PyException;
use tapo::Error;
pub struct ErrorWrapper(pub Error);
impl From<Error> for ErrorWrapper {
fn from(err: Error) -> Self {
Self(err)
}
}
impl From<anyhow::Error> for ErrorWrapper {
fn from(err: anyhow::Error) -> Self {
Self(err.into())
}
}
impl From<ErrorWrapper> for PyErr {
fn from(err: ErrorWrapper) -> PyErr {
PyException::new_err(format!("{:?}", err.0))
}
}

View File

@@ -0,0 +1,209 @@
mod api;
mod errors;
mod requests;
mod responses;
mod runtime;
use log::LevelFilter;
use pyo3::prelude::*;
use pyo3_log::{Caching, Logger};
use tapo::requests::{AlarmRingtone, AlarmVolume, Color, LightingEffectPreset, LightingEffectType};
use tapo::responses::{
AutoOffStatus, ColorLightState, CurrentPowerResult, DefaultBrightnessState,
DefaultColorLightState, DefaultLightState, DefaultPlugState, DefaultPowerType,
DefaultRgbLightStripState, DefaultRgbicLightStripState, DefaultStateType,
DeviceInfoColorLightResult, DeviceInfoGenericResult, DeviceInfoHubResult,
DeviceInfoLightResult, DeviceInfoPlugEnergyMonitoringResult, DeviceInfoPlugResult,
DeviceInfoPowerStripResult, DeviceInfoRgbLightStripResult, DeviceInfoRgbicLightStripResult,
DeviceUsageEnergyMonitoringResult, DeviceUsageResult, EnergyDataIntervalResult,
EnergyDataResult, EnergyUsageResult, KE100Result, OvercurrentStatus, OverheatStatus, PlugState,
PowerDataIntervalResult, PowerDataResult, PowerProtectionStatus,
PowerStripPlugEnergyMonitoringResult, PowerStripPlugResult, RgbLightStripState,
RgbicLightStripState, S200BLog, S200BResult, S200BRotationParams, Status, T31XResult, T100Log,
T100Result, T110Log, T110Result, T300Log, T300Result, TemperatureHumidityRecord,
TemperatureHumidityRecords, TemperatureUnit, TemperatureUnitKE100, UsageByPeriodResult,
WaterLeakStatus,
};
use api::{
PyApiClient, PyColorLightHandler, PyDeviceDiscovery, PyDeviceDiscoveryIter, PyDiscoveryResult,
PyGenericDeviceHandler, PyHubHandler, PyKE100Handler, PyLightHandler, PyMaybeDiscoveryResult,
PyPlugEnergyMonitoringHandler, PyPlugHandler, PyPowerStripEnergyMonitoringHandler,
PyPowerStripHandler, PyPowerStripPlugEnergyMonitoringHandler, PyPowerStripPlugHandler,
PyRgbLightStripHandler, PyRgbicLightStripHandler, PyT31XHandler, PyT100Handler, PyT110Handler,
PyT300Handler,
};
use requests::{
PyAlarmDuration, PyColorLightSetDeviceInfoParams, PyEnergyDataInterval, PyLightingEffect,
PyPowerDataInterval,
};
use responses::{
TriggerLogsS200BResult, TriggerLogsT100Result, TriggerLogsT110Result, TriggerLogsT300Result,
};
#[pymodule]
#[pyo3(name = "tapo")]
fn tapo_py(py: Python, module: &Bound<'_, PyModule>) -> PyResult<()> {
Logger::new(py, Caching::LoggersAndLevels)?
.filter(LevelFilter::Trace)
.install()
.expect("Failed to install the logger");
let requests = PyModule::new(py, "tapo.requests")?;
let responses = PyModule::new(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("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::<Color>()?;
module.add_class::<PyLightingEffect>()?;
module.add_class::<LightingEffectPreset>()?;
module.add_class::<LightingEffectType>()?;
module.add_class::<PyColorLightSetDeviceInfoParams>()?;
module.add_class::<PyEnergyDataInterval>()?;
module.add_class::<PyPowerDataInterval>()?;
// hub requests
module.add_class::<AlarmRingtone>()?;
module.add_class::<AlarmVolume>()?;
module.add_class::<PyAlarmDuration>()?;
module.add_class::<TemperatureUnitKE100>()?;
Ok(())
}
fn register_handlers(module: &Bound<'_, PyModule>) -> Result<(), PyErr> {
module.add_class::<PyApiClient>()?;
module.add_class::<PyColorLightHandler>()?;
module.add_class::<PyGenericDeviceHandler>()?;
module.add_class::<PyLightHandler>()?;
module.add_class::<PyPlugEnergyMonitoringHandler>()?;
module.add_class::<PyPlugHandler>()?;
module.add_class::<PyRgbLightStripHandler>()?;
module.add_class::<PyRgbicLightStripHandler>()?;
module.add_class::<PyHubHandler>()?;
module.add_class::<PyKE100Handler>()?;
module.add_class::<PyT100Handler>()?;
module.add_class::<PyT110Handler>()?;
module.add_class::<PyT300Handler>()?;
module.add_class::<PyT31XHandler>()?;
module.add_class::<PyPowerStripHandler>()?;
module.add_class::<PyPowerStripEnergyMonitoringHandler>()?;
module.add_class::<PyPowerStripPlugHandler>()?;
module.add_class::<PyPowerStripPlugEnergyMonitoringHandler>()?;
module.add_class::<PyDeviceDiscovery>()?;
module.add_class::<PyDeviceDiscoveryIter>()?;
module.add_class::<PyDiscoveryResult>()?;
module.add_class::<PyMaybeDiscoveryResult>()?;
Ok(())
}
fn register_responses(module: &Bound<'_, PyModule>) -> Result<(), PyErr> {
module.add_class::<CurrentPowerResult>()?;
module.add_class::<DefaultBrightnessState>()?;
module.add_class::<DefaultPowerType>()?;
module.add_class::<DefaultStateType>()?;
module.add_class::<DeviceUsageEnergyMonitoringResult>()?;
module.add_class::<DeviceUsageResult>()?;
module.add_class::<EnergyDataIntervalResult>()?;
module.add_class::<EnergyDataResult>()?;
module.add_class::<EnergyUsageResult>()?;
module.add_class::<OvercurrentStatus>()?;
module.add_class::<OverheatStatus>()?;
module.add_class::<PowerDataIntervalResult>()?;
module.add_class::<PowerDataResult>()?;
module.add_class::<PowerProtectionStatus>()?;
module.add_class::<UsageByPeriodResult>()?;
// device info: generic
module.add_class::<DeviceInfoGenericResult>()?;
// device info: light
module.add_class::<DeviceInfoLightResult>()?;
module.add_class::<DefaultLightState>()?;
// device info: color light
module.add_class::<DeviceInfoColorLightResult>()?;
module.add_class::<DefaultColorLightState>()?;
module.add_class::<ColorLightState>()?;
// device info: rgb light strip
module.add_class::<DeviceInfoRgbLightStripResult>()?;
module.add_class::<DefaultRgbLightStripState>()?;
module.add_class::<RgbLightStripState>()?;
// device info: rgbic light strip
module.add_class::<DeviceInfoRgbicLightStripResult>()?;
module.add_class::<DefaultRgbicLightStripState>()?;
module.add_class::<RgbicLightStripState>()?;
module.add_class::<PyLightingEffect>()?;
module.add_class::<LightingEffectType>()?;
// device info: plugs
module.add_class::<DefaultPlugState>()?;
module.add_class::<DeviceInfoPlugEnergyMonitoringResult>()?;
module.add_class::<DeviceInfoPlugResult>()?;
module.add_class::<PlugState>()?;
Ok(())
}
fn register_responses_hub(module: &Bound<'_, PyModule>) -> Result<(), PyErr> {
module.add_class::<DeviceInfoHubResult>()?;
module.add_class::<KE100Result>()?;
module.add_class::<S200BResult>()?;
module.add_class::<T100Result>()?;
module.add_class::<T110Result>()?;
module.add_class::<T300Result>()?;
module.add_class::<T31XResult>()?;
// child devices
module.add_class::<S200BLog>()?;
module.add_class::<S200BRotationParams>()?;
module.add_class::<Status>()?;
module.add_class::<T100Log>()?;
module.add_class::<T110Log>()?;
module.add_class::<T300Log>()?;
module.add_class::<TemperatureHumidityRecord>()?;
module.add_class::<TemperatureHumidityRecords>()?;
module.add_class::<TemperatureUnit>()?;
module.add_class::<TemperatureUnitKE100>()?;
module.add_class::<TriggerLogsS200BResult>()?;
module.add_class::<TriggerLogsT100Result>()?;
module.add_class::<TriggerLogsT110Result>()?;
module.add_class::<TriggerLogsT300Result>()?;
module.add_class::<WaterLeakStatus>()?;
Ok(())
}
fn register_responses_power_strip(module: &Bound<'_, PyModule>) -> Result<(), PyErr> {
module.add_class::<DeviceInfoPowerStripResult>()?;
// child devices
module.add_class::<AutoOffStatus>()?;
module.add_class::<PowerStripPlugResult>()?;
module.add_class::<PowerStripPlugEnergyMonitoringResult>()?;
Ok(())
}

View File

@@ -0,0 +1,9 @@
mod energy_data_interval;
mod play_alarm;
mod power_data_interval;
mod set_device_info;
pub use energy_data_interval::*;
pub use play_alarm::*;
pub use power_data_interval::*;
pub use set_device_info::*;

View File

@@ -0,0 +1,9 @@
use pyo3::prelude::*;
#[derive(Clone, PartialEq)]
#[pyclass(name = "EnergyDataInterval", eq, eq_int)]
pub enum PyEnergyDataInterval {
Hourly,
Daily,
Monthly,
}

View File

@@ -0,0 +1,9 @@
use pyo3::prelude::*;
#[derive(Debug, Clone, PartialEq)]
#[pyclass(name = "AlarmDuration", eq)]
pub enum PyAlarmDuration {
Continuous,
Once,
Seconds,
}

View File

@@ -0,0 +1,8 @@
use pyo3::prelude::*;
#[derive(Clone, PartialEq)]
#[pyclass(name = "PowerDataInterval", eq, eq_int)]
pub enum PyPowerDataInterval {
Every5Minutes,
Hourly,
}

View File

@@ -0,0 +1,5 @@
mod color_light;
mod lighting_effect;
pub use color_light::*;
pub use lighting_effect::*;

View File

@@ -0,0 +1,108 @@
use std::ops::Deref;
use pyo3::prelude::*;
use tapo::requests::{Color, ColorLightSetDeviceInfoParams};
use crate::api::{
PyColorLightHandler, PyHandlerExt, PyRgbLightStripHandler, PyRgbicLightStripHandler,
};
use crate::errors::ErrorWrapper;
use crate::runtime::tokio;
#[derive(Clone)]
#[pyclass(name = "LightSetDeviceInfoParams")]
pub struct PyColorLightSetDeviceInfoParams {
params: ColorLightSetDeviceInfoParams,
}
impl PyColorLightSetDeviceInfoParams {
pub(crate) fn new() -> Self {
Self {
params: ColorLightSetDeviceInfoParams::new(),
}
}
async fn _send_to_inner_handler(&self, handler: impl PyHandlerExt) -> PyResult<()> {
let params = self.params.clone();
let handler = handler.get_inner_handler();
tokio()
.spawn(async move {
let handler_lock = handler.read().await;
params
.send(handler_lock.deref())
.await
.map_err(ErrorWrapper)?;
Ok::<_, ErrorWrapper>(())
})
.await
.map_err(anyhow::Error::from)
.map_err(ErrorWrapper::from)??;
Ok(())
}
}
#[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),
}
}
async fn send(&self, handler: Py<PyAny>) -> PyResult<()> {
if let Some(handler) = Python::attach(|py| handler.extract::<PyColorLightHandler>(py).ok())
{
return self._send_to_inner_handler(handler).await;
}
if let Some(handler) =
Python::attach(|py| handler.extract::<PyRgbLightStripHandler>(py).ok())
{
return self._send_to_inner_handler(handler).await;
}
if let Some(handler) =
Python::attach(|py| handler.extract::<PyRgbicLightStripHandler>(py).ok())
{
return self._send_to_inner_handler(handler).await;
}
Err(PyErr::new::<pyo3::exceptions::PyTypeError, _>(
"Invalid handler type. Must be one of `PyColorLightHandler`, `PyRgbLightStripHandler` or `PyRgbicLightStripHandler`",
))
}
}

View File

@@ -0,0 +1,201 @@
use pyo3::prelude::*;
use tapo::requests::{LightingEffect, LightingEffectType};
#[derive(Clone)]
#[pyclass(name = "LightingEffect")]
pub struct PyLightingEffect {
inner: LightingEffect,
}
#[pymethods]
impl PyLightingEffect {
#[new]
fn new(
name: String,
r#type: LightingEffectType,
is_custom: bool,
enabled: bool,
brightness: u8,
display_colors: Vec<[u16; 3]>,
) -> Self {
Self {
inner: LightingEffect::new(
name,
r#type,
is_custom,
enabled,
brightness,
display_colors,
),
}
}
pub fn with_brightness(mut slf: PyRefMut<'_, Self>, brightness: u8) -> PyRefMut<'_, Self> {
(*slf).inner.brightness = brightness;
slf
}
pub fn with_is_custom(mut slf: PyRefMut<'_, Self>, is_custom: bool) -> PyRefMut<'_, Self> {
(*slf).inner.is_custom = is_custom;
slf
}
pub fn with_display_colors(
mut slf: PyRefMut<'_, Self>,
display_colors: Vec<[u16; 3]>,
) -> PyRefMut<'_, Self> {
(*slf).inner.display_colors = display_colors;
slf
}
pub fn with_enabled(mut slf: PyRefMut<'_, Self>, enabled: bool) -> PyRefMut<'_, Self> {
(*slf).inner.enabled = enabled;
slf
}
pub fn with_id(mut slf: PyRefMut<'_, Self>, id: String) -> PyRefMut<'_, Self> {
(*slf).inner.id = id;
slf
}
pub fn with_name(mut slf: PyRefMut<'_, Self>, name: String) -> PyRefMut<'_, Self> {
(*slf).inner.name = name;
slf
}
pub fn with_type(
mut slf: PyRefMut<'_, Self>,
r#type: LightingEffectType,
) -> PyRefMut<'_, Self> {
(*slf).inner.r#type = r#type;
slf
}
pub fn with_backgrounds(
mut slf: PyRefMut<'_, Self>,
backgrounds: Vec<[u16; 3]>,
) -> PyRefMut<'_, Self> {
(*slf).inner.backgrounds = Some(backgrounds);
slf
}
pub fn with_brightness_range(
mut slf: PyRefMut<'_, Self>,
brightness_range: [u8; 2],
) -> PyRefMut<'_, Self> {
(*slf).inner.brightness_range = Some(brightness_range.to_vec());
slf
}
pub fn with_direction(mut slf: PyRefMut<'_, Self>, direction: u8) -> PyRefMut<'_, Self> {
(*slf).inner.direction = Some(direction);
slf
}
pub fn with_duration(mut slf: PyRefMut<'_, Self>, duration: u64) -> PyRefMut<'_, Self> {
(*slf).inner.duration = Some(duration);
slf
}
pub fn with_expansion_strategy(
mut slf: PyRefMut<'_, Self>,
expansion_strategy: u8,
) -> PyRefMut<'_, Self> {
(*slf).inner.expansion_strategy = Some(expansion_strategy);
slf
}
pub fn with_fade_off(mut slf: PyRefMut<'_, Self>, fade_off: u16) -> PyRefMut<'_, Self> {
(*slf).inner.fade_off = Some(fade_off);
slf
}
pub fn with_hue_range(mut slf: PyRefMut<'_, Self>, hue_range: [u16; 2]) -> PyRefMut<'_, Self> {
(*slf).inner.hue_range = Some(hue_range);
slf
}
pub fn with_init_states(
mut slf: PyRefMut<'_, Self>,
init_states: Vec<[u16; 3]>,
) -> PyRefMut<'_, Self> {
(*slf).inner.init_states = Some(init_states);
slf
}
pub fn with_random_seed(mut slf: PyRefMut<'_, Self>, random_seed: u64) -> PyRefMut<'_, Self> {
(*slf).inner.random_seed = Some(random_seed);
slf
}
pub fn with_repeat_times(mut slf: PyRefMut<'_, Self>, repeat_times: u8) -> PyRefMut<'_, Self> {
(*slf).inner.repeat_times = Some(repeat_times);
slf
}
pub fn with_run_time(mut slf: PyRefMut<'_, Self>, run_time: u64) -> PyRefMut<'_, Self> {
(*slf).inner.run_time = Some(run_time);
slf
}
pub fn with_saturation_range(
mut slf: PyRefMut<'_, Self>,
saturation_range: [u8; 2],
) -> PyRefMut<'_, Self> {
(*slf).inner.saturation_range = Some(saturation_range);
slf
}
pub fn with_segment_length(
mut slf: PyRefMut<'_, Self>,
segment_length: u8,
) -> PyRefMut<'_, Self> {
(*slf).inner.segment_length = Some(segment_length);
slf
}
pub fn with_segments(mut slf: PyRefMut<'_, Self>, segments: Vec<u8>) -> PyRefMut<'_, Self> {
(*slf).inner.segments = Some(segments);
slf
}
pub fn with_sequence(
mut slf: PyRefMut<'_, Self>,
sequence: Vec<[u16; 3]>,
) -> PyRefMut<'_, Self> {
(*slf).inner.sequence = Some(sequence);
slf
}
pub fn with_spread(mut slf: PyRefMut<'_, Self>, spread: u8) -> PyRefMut<'_, Self> {
(*slf).inner.spread = Some(spread);
slf
}
pub fn with_transition(mut slf: PyRefMut<'_, Self>, transition: u16) -> PyRefMut<'_, Self> {
(*slf).inner.transition = Some(transition);
slf
}
pub fn with_transition_range(
mut slf: PyRefMut<'_, Self>,
transition_range: [u16; 2],
) -> PyRefMut<'_, Self> {
(*slf).inner.transition_range = Some(transition_range);
slf
}
pub fn with_transition_sequence(
mut slf: PyRefMut<'_, Self>,
transition_sequence: Vec<u16>,
) -> PyRefMut<'_, Self> {
(*slf).inner.transition_sequence = Some(transition_sequence);
slf
}
}
impl From<PyLightingEffect> for LightingEffect {
fn from(effect: PyLightingEffect) -> Self {
effect.inner
}
}

View File

@@ -0,0 +1,3 @@
mod child_device_list_hub_result;
pub use child_device_list_hub_result::*;

View File

@@ -0,0 +1,9 @@
mod s200b_result;
mod t100_result;
mod t110_result;
mod t300_result;
pub use s200b_result::*;
pub use t100_result::*;
pub use t110_result::*;
pub use t300_result::*;

View File

@@ -0,0 +1,32 @@
use pyo3::prelude::*;
use serde::{Deserialize, Serialize};
use tapo::responses::{S200BLog, TriggerLogsResult};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[pyclass(get_all)]
#[allow(missing_docs)]
pub struct TriggerLogsS200BResult {
start_id: u64,
sum: u64,
logs: Vec<S200BLog>,
}
impl From<TriggerLogsResult<S200BLog>> for TriggerLogsS200BResult {
fn from(result: TriggerLogsResult<S200BLog>) -> Self {
Self {
start_id: result.start_id,
sum: result.sum,
logs: result.logs,
}
}
}
#[pyo3::pymethods]
impl TriggerLogsS200BResult {
pub fn to_dict(&self, py: pyo3::Python) -> pyo3::PyResult<pyo3::Py<pyo3::types::PyDict>> {
let value = serde_json::to_value(self)
.map_err(|e| pyo3::exceptions::PyException::new_err(e.to_string()))?;
tapo::python::serde_object_to_py_dict(py, &value)
}
}

View File

@@ -0,0 +1,32 @@
use pyo3::prelude::*;
use serde::{Deserialize, Serialize};
use tapo::responses::{T100Log, TriggerLogsResult};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[pyclass(get_all)]
#[allow(missing_docs)]
pub struct TriggerLogsT100Result {
start_id: u64,
sum: u64,
logs: Vec<T100Log>,
}
impl From<TriggerLogsResult<T100Log>> for TriggerLogsT100Result {
fn from(result: TriggerLogsResult<T100Log>) -> Self {
Self {
start_id: result.start_id,
sum: result.sum,
logs: result.logs,
}
}
}
#[pyo3::pymethods]
impl TriggerLogsT100Result {
pub fn to_dict(&self, py: pyo3::Python) -> pyo3::PyResult<pyo3::Py<pyo3::types::PyDict>> {
let value = serde_json::to_value(self)
.map_err(|e| pyo3::exceptions::PyException::new_err(e.to_string()))?;
tapo::python::serde_object_to_py_dict(py, &value)
}
}

View File

@@ -0,0 +1,32 @@
use pyo3::prelude::*;
use serde::{Deserialize, Serialize};
use tapo::responses::{T110Log, TriggerLogsResult};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[pyclass(get_all)]
#[allow(missing_docs)]
pub struct TriggerLogsT110Result {
start_id: u64,
sum: u64,
logs: Vec<T110Log>,
}
impl From<TriggerLogsResult<T110Log>> for TriggerLogsT110Result {
fn from(result: TriggerLogsResult<T110Log>) -> Self {
Self {
start_id: result.start_id,
sum: result.sum,
logs: result.logs,
}
}
}
#[pyo3::pymethods]
impl TriggerLogsT110Result {
pub fn to_dict(&self, py: pyo3::Python) -> pyo3::PyResult<pyo3::Py<pyo3::types::PyDict>> {
let value = serde_json::to_value(self)
.map_err(|e| pyo3::exceptions::PyException::new_err(e.to_string()))?;
tapo::python::serde_object_to_py_dict(py, &value)
}
}

View File

@@ -0,0 +1,32 @@
use pyo3::prelude::*;
use serde::{Deserialize, Serialize};
use tapo::responses::{T300Log, TriggerLogsResult};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[pyclass(get_all)]
#[allow(missing_docs)]
pub struct TriggerLogsT300Result {
start_id: u64,
sum: u64,
logs: Vec<T300Log>,
}
impl From<TriggerLogsResult<T300Log>> for TriggerLogsT300Result {
fn from(result: TriggerLogsResult<T300Log>) -> Self {
Self {
start_id: result.start_id,
sum: result.sum,
logs: result.logs,
}
}
}
#[pyo3::pymethods]
impl TriggerLogsT300Result {
pub fn to_dict(&self, py: pyo3::Python) -> pyo3::PyResult<pyo3::Py<pyo3::types::PyDict>> {
let value = serde_json::to_value(self)
.map_err(|e| pyo3::exceptions::PyException::new_err(e.to_string()))?;
tapo::python::serde_object_to_py_dict(py, &value)
}
}

View File

@@ -0,0 +1,60 @@
pub fn tokio() -> &'static tokio::runtime::Runtime {
use std::sync::OnceLock;
use tokio::runtime::Runtime;
static RT: std::sync::OnceLock<Runtime> = OnceLock::new();
RT.get_or_init(|| Runtime::new().expect("Failed to create tokio runtime"))
}
#[macro_export]
macro_rules! call_handler_constructor {
($self:ident, $constructor:path, $($params:expr),*) => {{
let client = $self.client.clone();
let handler = $crate::runtime::tokio()
.spawn(async move {
$constructor(client, $($params),*)
.await
.map_err($crate::errors::ErrorWrapper)
})
.await
.map_err(anyhow::Error::from)
.map_err($crate::errors::ErrorWrapper::from)??;
handler
}};
}
#[macro_export]
macro_rules! call_handler_method {
($handler:expr, $method:path) => (call_handler_method!($handler, $method,));
($handler:expr, $method:path, discard_result) => (call_handler_method!($handler, $method, discard_result,));
($handler:expr, $method:path, $($param:expr),*) => {{
let result = $crate::runtime::tokio()
.spawn(async move {
let result = $method($handler, $($param),*)
.await
.map_err($crate::errors::ErrorWrapper)?;
Ok::<_, $crate::errors::ErrorWrapper>(result)
})
.await
.map_err(anyhow::Error::from)
.map_err($crate::errors::ErrorWrapper::from)??;
Ok::<_, PyErr>(result)
}};
($handler:expr, $method:path, discard_result, $($param:expr),*) => {{
let result = $crate::runtime::tokio()
.spawn(async move {
$method($handler, $($param),*)
.await
.map_err($crate::errors::ErrorWrapper)?;
Ok::<_, $crate::errors::ErrorWrapper>(())
})
.await
.map_err(anyhow::Error::from)
.map_err($crate::errors::ErrorWrapper::from)??;
Ok(result)
}};
}