Skip to content

Commit

Permalink
tapo-py: Improve the concurrency of device handlers
Browse files Browse the repository at this point in the history
  • Loading branch information
mihai-dinculescu committed Aug 31, 2024
1 parent 945b41b commit ff30406
Show file tree
Hide file tree
Showing 8 changed files with 228 additions and 80 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ file. This change log follows the conventions of
### Fixed

- Resolved an issue that led to unrecoverable process hangs when a device request timed out.
- The concurrency of device handlers has been significantly enhanced by replacing all `Mutex` instances with `RwLock`.
- `DeviceInfoPlugResult` has been updated to correctly support the P100 and P105 devices.

## [Rust v0.7.13][v0.7.13] - 2024-08-26
Expand Down
73 changes: 57 additions & 16 deletions tapo-py/src/handlers/color_light_handler.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
use std::{ops::Deref, sync::Arc};
use std::ops::{Deref, DerefMut};
use std::sync::Arc;

use pyo3::prelude::*;
use pyo3::types::PyDict;
use tapo::requests::{Color, ColorLightSetDeviceInfoParams};
use tapo::responses::{DeviceInfoColorLightResult, DeviceUsageEnergyMonitoringResult};
use tapo::ColorLightHandler;
use tokio::sync::Mutex;
use tokio::sync::RwLock;

use crate::call_handler_method;
use crate::errors::ErrorWrapper;
Expand All @@ -14,67 +15,107 @@ use crate::runtime::tokio;
#[derive(Clone)]
#[pyclass(name = "ColorLightHandler")]
pub struct PyColorLightHandler {
handler: Arc<Mutex<ColorLightHandler>>,
handler: Arc<RwLock<ColorLightHandler>>,
}

impl PyColorLightHandler {
pub fn new(handler: ColorLightHandler) -> Self {
Self {
handler: Arc::new(Mutex::new(handler)),
handler: Arc::new(RwLock::new(handler)),
}
}
}

#[pymethods]
impl PyColorLightHandler {
pub async fn refresh_session(&self) -> PyResult<()> {
call_handler_method!(self, ColorLightHandler::refresh_session, discard_result)
let handler = self.handler.clone();
call_handler_method!(
handler.write().await.deref_mut(),
ColorLightHandler::refresh_session,
discard_result
)
}

pub async fn on(&self) -> PyResult<()> {
call_handler_method!(self, ColorLightHandler::on)
let handler = self.handler.clone();
call_handler_method!(handler.read().await.deref(), ColorLightHandler::on)
}

pub async fn off(&self) -> PyResult<()> {
call_handler_method!(self, ColorLightHandler::off)
let handler = self.handler.clone();
call_handler_method!(handler.read().await.deref(), ColorLightHandler::off)
}

pub async fn device_reset(&self) -> PyResult<()> {
call_handler_method!(self, ColorLightHandler::device_reset)
let handler = self.handler.clone();
call_handler_method!(
handler.read().await.deref(),
ColorLightHandler::device_reset
)
}

pub async fn get_device_info(&self) -> PyResult<DeviceInfoColorLightResult> {
call_handler_method!(self, ColorLightHandler::get_device_info)
let handler = self.handler.clone();
call_handler_method!(
handler.read().await.deref(),
ColorLightHandler::get_device_info
)
}

pub async fn get_device_info_json(&self) -> PyResult<Py<PyDict>> {
let result = call_handler_method!(self, ColorLightHandler::get_device_info_json)?;
let handler = self.handler.clone();
let result = call_handler_method!(
handler.read().await.deref(),
ColorLightHandler::get_device_info_json,
)?;
Python::with_gil(|py| tapo::python::serde_object_to_py_dict(py, &result))
}

pub async fn get_device_usage(&self) -> PyResult<DeviceUsageEnergyMonitoringResult> {
call_handler_method!(self, ColorLightHandler::get_device_usage)
let handler = self.handler.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<()> {
call_handler_method!(self, ColorLightHandler::set_brightness, brightness)
let handler = self.handler.clone();
call_handler_method!(
handler.read().await.deref(),
ColorLightHandler::set_brightness,
brightness
)
}

pub async fn set_color(&self, color: Color) -> PyResult<()> {
call_handler_method!(self, ColorLightHandler::set_color, color)
let handler = self.handler.clone();
call_handler_method!(
handler.read().await.deref(),
ColorLightHandler::set_color,
color
)
}

pub async fn set_hue_saturation(&self, hue: u16, saturation: u8) -> PyResult<()> {
call_handler_method!(self, ColorLightHandler::set_hue_saturation, hue, saturation)
let handler = self.handler.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.handler.clone();
call_handler_method!(
self,
handler.read().await.deref(),
ColorLightHandler::set_color_temperature,
color_temperature
)
Expand Down Expand Up @@ -138,7 +179,7 @@ impl PyColorLightSetDeviceInfoParams {

tokio()
.spawn(async move {
let handler_lock = handler.handler.lock().await;
let handler_lock = handler.handler.read().await;

params
.send(handler_lock.deref())
Expand Down
32 changes: 24 additions & 8 deletions tapo-py/src/handlers/generic_device_handler.rs
Original file line number Diff line number Diff line change
@@ -1,48 +1,64 @@
use std::ops::{Deref, DerefMut};
use std::sync::Arc;

use pyo3::prelude::*;
use pyo3::types::PyDict;
use tapo::responses::DeviceInfoGenericResult;
use tapo::GenericDeviceHandler;
use tokio::sync::Mutex;
use tokio::sync::RwLock;

use crate::call_handler_method;
use crate::errors::ErrorWrapper;

#[derive(Clone)]
#[pyclass(name = "GenericDeviceHandler")]
pub struct PyGenericDeviceHandler {
handler: Arc<Mutex<GenericDeviceHandler>>,
handler: Arc<RwLock<GenericDeviceHandler>>,
}

impl PyGenericDeviceHandler {
pub fn new(handler: GenericDeviceHandler) -> Self {
Self {
handler: Arc::new(Mutex::new(handler)),
handler: Arc::new(RwLock::new(handler)),
}
}
}

#[pymethods]
impl PyGenericDeviceHandler {
pub async fn refresh_session(&self) -> PyResult<()> {
call_handler_method!(self, GenericDeviceHandler::refresh_session, discard_result)
let handler = self.handler.clone();
call_handler_method!(
handler.write().await.deref_mut(),
GenericDeviceHandler::refresh_session,
discard_result
)
}

pub async fn on(&self) -> PyResult<()> {
call_handler_method!(self, GenericDeviceHandler::on)
let handler = self.handler.clone();
call_handler_method!(handler.read().await.deref(), GenericDeviceHandler::on)
}

pub async fn off(&self) -> PyResult<()> {
call_handler_method!(self, GenericDeviceHandler::off)
let handler = self.handler.clone();
call_handler_method!(handler.read().await.deref(), GenericDeviceHandler::off)
}

pub async fn get_device_info(&self) -> PyResult<DeviceInfoGenericResult> {
call_handler_method!(self, GenericDeviceHandler::get_device_info)
let handler = self.handler.clone();
call_handler_method!(
handler.read().await.deref(),
GenericDeviceHandler::get_device_info
)
}

pub async fn get_device_info_json(&self) -> PyResult<Py<PyDict>> {
let result = call_handler_method!(self, GenericDeviceHandler::get_device_info_json)?;
let handler = self.handler.clone();
let result = call_handler_method!(
handler.read().await.deref(),
GenericDeviceHandler::get_device_info_json,
)?;
Python::with_gil(|py| tapo::python::serde_object_to_py_dict(py, &result))
}
}
41 changes: 32 additions & 9 deletions tapo-py/src/handlers/hub_handler.rs
Original file line number Diff line number Diff line change
@@ -1,45 +1,60 @@
use std::ops::{Deref, DerefMut};
use std::sync::Arc;

use pyo3::prelude::*;
use pyo3::types::{PyDict, PyList};
use tapo::responses::{ChildDeviceHubResult, DeviceInfoHubResult};
use tapo::HubHandler;
use tokio::sync::Mutex;
use tokio::sync::RwLock;

use crate::call_handler_method;
use crate::errors::ErrorWrapper;

#[derive(Clone)]
#[pyclass(name = "HubHandler")]
pub struct PyHubHandler {
handler: Arc<Mutex<HubHandler>>,
handler: Arc<RwLock<HubHandler>>,
}

impl PyHubHandler {
pub fn new(handler: HubHandler) -> Self {
Self {
handler: Arc::new(Mutex::new(handler)),
handler: Arc::new(RwLock::new(handler)),
}
}
}

#[pymethods]
impl PyHubHandler {
pub async fn refresh_session(&self) -> PyResult<()> {
call_handler_method!(self, HubHandler::refresh_session, discard_result)
let handler = self.handler.clone();
call_handler_method!(
handler.write().await.deref_mut(),
HubHandler::refresh_session,
discard_result
)
}

pub async fn get_device_info(&self) -> PyResult<DeviceInfoHubResult> {
call_handler_method!(self, HubHandler::get_device_info)
let handler = self.handler.clone();
call_handler_method!(handler.read().await.deref(), HubHandler::get_device_info)
}

pub async fn get_device_info_json(&self) -> PyResult<Py<PyDict>> {
let result = call_handler_method!(self, HubHandler::get_device_info_json)?;
let handler = self.handler.clone();
let result = call_handler_method!(
handler.read().await.deref(),
HubHandler::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<Py<PyList>> {
let children = call_handler_method!(self, HubHandler::get_child_device_list)?;
let handler = self.handler.clone();
let children = call_handler_method!(
handler.read().await.deref(),
HubHandler::get_child_device_list
)?;

let results = Python::with_gil(|py| {
let results = PyList::empty_bound(py);
Expand Down Expand Up @@ -80,12 +95,20 @@ impl PyHubHandler {
}

pub async fn get_child_device_list_json(&self) -> PyResult<Py<PyDict>> {
let result = call_handler_method!(self, HubHandler::get_child_device_list_json)?;
let handler = self.handler.clone();
let result = call_handler_method!(
handler.read().await.deref(),
HubHandler::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<Py<PyDict>> {
let result = call_handler_method!(self, HubHandler::get_child_device_component_list_json)?;
let handler = self.handler.clone();
let result = call_handler_method!(
handler.read().await.deref(),
HubHandler::get_child_device_component_list_json
)?;
Python::with_gil(|py| tapo::python::serde_object_to_py_dict(py, &result))
}
}
Loading

0 comments on commit ff30406

Please sign in to comment.