From c50204742db2899a6416e008151a83af00c89b38 Mon Sep 17 00:00:00 2001 From: pwoerndle <21156233+pwoerndle@users.noreply.github.com> Date: Mon, 16 Oct 2023 15:01:03 +0000 Subject: [PATCH 01/16] Initial KE100 support. Reading data works, setting temperature and frost protection returns empty result, which creates and EmptyResult error. --- tapo/examples/tapo_ke100.rs | 37 +++++++++ tapo/src/api/child_devices.rs | 2 + tapo/src/api/child_devices/ke100_handler.rs | 79 +++++++++++++++++++ tapo/src/api/hub_handler.rs | 29 ++++++- tapo/src/requests/set_device_info.rs | 2 + tapo/src/requests/set_device_info/trv.rs | 52 ++++++++++++ .../src/responses/child_device_list_result.rs | 5 ++ .../child_device_list_result/ke100_result.rs | 61 ++++++++++++++ 8 files changed, 266 insertions(+), 1 deletion(-) create mode 100644 tapo/examples/tapo_ke100.rs create mode 100644 tapo/src/api/child_devices/ke100_handler.rs create mode 100644 tapo/src/requests/set_device_info/trv.rs create mode 100644 tapo/src/responses/child_device_list_result/ke100_result.rs diff --git a/tapo/examples/tapo_ke100.rs b/tapo/examples/tapo_ke100.rs new file mode 100644 index 0000000..ac3b92f --- /dev/null +++ b/tapo/examples/tapo_ke100.rs @@ -0,0 +1,37 @@ +/// KE100 TRV Example +use std::env; + +use log::{info, LevelFilter}; +use tapo::ApiClient; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let log_level = env::var("RUST_LOG") + .unwrap_or_else(|_| "info".to_string()) + .parse() + .unwrap_or(LevelFilter::Info); + + pretty_env_logger::formatted_timed_builder() + .filter(Some("tapo"), log_level) + .init(); + + let tapo_username = env::var("TAPO_USERNAME")?; + let tapo_password = env::var("TAPO_PASSWORD")?; + let ip_address = env::var("IP_ADDRESS")?; + // ID of the KE100 device. Can be obtained from executing `get_child_device_component_list_json()`` on the hub device. + let device_id = env::var("DEVICE_ID")?; + + let hub = ApiClient::new(tapo_username, tapo_password)? + .h100(ip_address) + .await?; + + // Get a handler for the child device + let device = hub.ke100(device_id); + + // Get the device info of the child device + let device_info = device.get_device_info().await?; + info!("Device info: {device_info:?}"); + + + Ok(()) +} diff --git a/tapo/src/api/child_devices.rs b/tapo/src/api/child_devices.rs index 424333f..da4a2f5 100644 --- a/tapo/src/api/child_devices.rs +++ b/tapo/src/api/child_devices.rs @@ -3,9 +3,11 @@ mod t100_handler; mod t110_handler; mod t300_handler; mod t31x_handler; +mod ke100_handler; pub use s200b_handler::*; pub use t100_handler::*; pub use t110_handler::*; pub use t300_handler::*; pub use t31x_handler::*; +pub use ke100_handler::*; diff --git a/tapo/src/api/child_devices/ke100_handler.rs b/tapo/src/api/child_devices/ke100_handler.rs new file mode 100644 index 0000000..c276204 --- /dev/null +++ b/tapo/src/api/child_devices/ke100_handler.rs @@ -0,0 +1,79 @@ +use crate::api::HubHandler; +use crate::error::Error; +use crate::requests::{EmptyParams, TapoParams, TapoRequest, TrvSetDeviceInfoParams}; +use crate::responses::{KE100Result}; + +/// Handler for the [KE100] device. +pub struct KE100Handler<'h> { + hub_handler: &'h HubHandler, + device_id: String, +} + +impl<'h> KE100Handler<'h> { + pub(crate) fn new(hub_handler: &'h HubHandler, device_id: String) -> Self { + Self { + hub_handler, + device_id, + } + } + + /// Returns *device info* as [`KE100Result`]. + /// It is not guaranteed to contain all the properties returned from the Tapo API. + pub async fn get_device_info(&self) -> Result { + let request = TapoRequest::GetDeviceInfo(TapoParams::new(EmptyParams)); + + self.hub_handler + .control_child(self.device_id.clone(), request) + .await + } + + /// Returns *device info* as [`serde_json::Value`]. + /// It contains all the properties returned from the Tapo API. + pub async fn get_device_info_json(&self) -> Result { + let request = TapoRequest::GetDeviceInfo(TapoParams::new(EmptyParams)); + + self.hub_handler + .control_child(self.device_id.clone(), request) + .await + } + + + /// Sets the *target temperature*, turns *on* the device and turns *off* frost protection. + /// + /// # Arguments + /// + /// * `target_temperature` - between 5.0 and 30.0 + pub async fn set_temperature(&self, target_temperature: u8) -> Result { + let json = serde_json::to_value(TrvSetDeviceInfoParams::new().target_temp(target_temperature)?)?; + let request = TapoRequest::SetDeviceInfo(Box::new(TapoParams::new(json))); + + self.hub_handler + .control_child(self.device_id.clone(), request) + .await + } + + /// Turns *on* the device. + pub async fn device_on(&self) -> Result { + let json = serde_json::to_value(TrvSetDeviceInfoParams::new().device_on(true)?)?; + let request = TapoRequest::SetDeviceInfo(Box::new(TapoParams::new(json))); + + self.hub_handler + .control_child(self.device_id.clone(), request) + .await + } + + /// Sets frost protection on the device to *on* or *off*. + /// + /// # Arguments + /// + /// * `frost_protection_on` - true/false + pub async fn set_frost_protection(&self, frost_protection_on: bool) -> Result { + let json = serde_json::to_value(TrvSetDeviceInfoParams::new().frost_protection_on(frost_protection_on)?)?; + let request = TapoRequest::SetDeviceInfo(Box::new(TapoParams::new(json))); + + self.hub_handler + .control_child(self.device_id.clone(), request) + .await + } + +} diff --git a/tapo/src/api/hub_handler.rs b/tapo/src/api/hub_handler.rs index 2aecf6d..3a20820 100644 --- a/tapo/src/api/hub_handler.rs +++ b/tapo/src/api/hub_handler.rs @@ -3,7 +3,7 @@ use std::fmt; use serde::de::DeserializeOwned; use crate::api::ApiClient; -use crate::api::{S200BHandler, T100Handler, T110Handler, T300Handler, T31XHandler}; +use crate::api::{S200BHandler, T100Handler, T110Handler, T300Handler, T31XHandler, KE100Handler}; use crate::error::Error; use crate::requests::TapoRequest; use crate::responses::{ @@ -238,4 +238,31 @@ impl HubHandler { pub fn t315(&self, device_id: impl Into) -> T31XHandler { T31XHandler::new(self, device_id.into()) } + + /// Returns a [`KE100Handler`] for the given `device_id`. + /// + /// # Arguments + /// + /// * `device_id` - the Device ID of the child device + /// + /// # Example + /// + /// ```rust,no_run + /// # use tapo::ApiClient; + /// # #[tokio::main] + /// # async fn main() -> Result<(), Box> { + /// // Connect to the hub + /// let hub = ApiClient::new("tapo-username@example.com", "tapo-password")? + /// .h100("192.168.1.100") + /// .await?; + /// // Get a handler for the child device + /// let device = hub.ke100("0000000000000000000000000000000000000000"); + /// // Get the device info of the child device + /// let device_info = device.get_device_info().await?; + /// # Ok(()) + /// # } + /// ``` + pub fn ke100(&self, device_id: impl Into) -> KE100Handler { + KE100Handler::new(self, device_id.into()) + } } diff --git a/tapo/src/requests/set_device_info.rs b/tapo/src/requests/set_device_info.rs index 273fb20..70ad713 100644 --- a/tapo/src/requests/set_device_info.rs +++ b/tapo/src/requests/set_device_info.rs @@ -1,8 +1,10 @@ mod color_light; mod generic_device; mod light; +mod trv; pub use color_light::*; pub(crate) use generic_device::*; pub(crate) use light::*; +pub(crate) use trv::*; diff --git a/tapo/src/requests/set_device_info/trv.rs b/tapo/src/requests/set_device_info/trv.rs new file mode 100644 index 0000000..fbb2c0f --- /dev/null +++ b/tapo/src/requests/set_device_info/trv.rs @@ -0,0 +1,52 @@ +use serde::Serialize; + +use crate::error::Error; + +#[derive(Debug, Default, Serialize)] +pub(crate) struct TrvSetDeviceInfoParams { + #[serde(skip_serializing_if = "Option::is_none")] + pub device_on: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub target_temp: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub frost_protection_on: Option, +} + +impl TrvSetDeviceInfoParams { + pub fn device_on(mut self, value: bool) -> Result { + self.device_on = Some(value); + self.validate() + } + pub fn target_temp(mut self, value: u8) -> Result { + self.device_on = Some(true); + self.target_temp = Some(value); + self.validate() + } + pub fn frost_protection_on(mut self, value: bool) -> Result { + self.device_on = Some(true); + self.frost_protection_on = Some(value); + self.validate() + } + +} + +impl TrvSetDeviceInfoParams { + pub(crate) fn new() -> Self { + Self { + device_on: None, + target_temp: None, + frost_protection_on: None, + } + } + + pub fn validate(self) -> Result { + if self.device_on.is_none() { + return Err(Error::Validation { + field: "DeviceInfoParams".to_string(), + message: "requires at least one property".to_string(), + }); + } + + Ok(self) + } +} diff --git a/tapo/src/responses/child_device_list_result.rs b/tapo/src/responses/child_device_list_result.rs index 3f7ddb8..0753d05 100644 --- a/tapo/src/responses/child_device_list_result.rs +++ b/tapo/src/responses/child_device_list_result.rs @@ -3,12 +3,14 @@ mod t100_result; mod t110_result; mod t300_result; mod t31x_result; +mod ke100_result; pub use s200b_result::*; pub use t100_result::*; pub use t110_result::*; pub use t300_result::*; pub use t31x_result::*; +pub use ke100_result::*; use serde::{Deserialize, Serialize}; @@ -62,6 +64,8 @@ pub enum ChildDeviceResult { T310(Box), /// T315 temperature & humidity sensor. T315(Box), + /// KE100 TRV. + KE100(Box), /// Catch-all for currently unsupported devices. /// Please open an issue if you need support for a new device. #[serde(other)] @@ -77,6 +81,7 @@ impl DecodableResultExt for ChildDeviceResult { ChildDeviceResult::T300(device) => Ok(ChildDeviceResult::T300(device.decode()?)), ChildDeviceResult::T310(device) => Ok(ChildDeviceResult::T310(device.decode()?)), ChildDeviceResult::T315(device) => Ok(ChildDeviceResult::T315(device.decode()?)), + ChildDeviceResult::KE100(device) => Ok(ChildDeviceResult::KE100(device.decode()?)), ChildDeviceResult::Other => Ok(ChildDeviceResult::Other), } } diff --git a/tapo/src/responses/child_device_list_result/ke100_result.rs b/tapo/src/responses/child_device_list_result/ke100_result.rs new file mode 100644 index 0000000..b64030f --- /dev/null +++ b/tapo/src/responses/child_device_list_result/ke100_result.rs @@ -0,0 +1,61 @@ +use serde::{Deserialize, Serialize}; + +use crate::error::Error; +use crate::responses::{decode_value, DecodableResultExt, Status, TapoResponseExt}; + +/// Temperature unit. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +#[allow(missing_docs)] +pub enum TemperatureUnit { + Celsius, + Fahrenheit, +} + + +/// KE100 TRV. +/// +/// Specific properties: `detected`. +#[derive(Debug, Clone, Serialize, Deserialize)] +#[allow(missing_docs)] +pub struct KE100Result { + pub at_low_battery: bool, + pub avatar: String, + pub bind_count: u32, + pub category: String, + pub device_id: String, + pub fw_ver: String, + pub hw_id: String, + pub hw_ver: String, + pub jamming_rssi: i16, + pub jamming_signal_level: u8, + pub mac: String, + pub nickname: String, + pub oem_id: String, + pub parent_device_id: String, + pub region: String, + pub rssi: i16, + pub signal_level: u8, + pub specs: String, + pub status: Status, + pub r#type: String, + #[serde(rename = "temp_unit")] + pub temperature_unit: TemperatureUnit, + pub current_temp: f32, + pub target_temp: f32, + pub min_control_temp: f32, + pub max_control_temp: f32, + pub frost_protection_on: bool, + pub location: String, + pub temp_offset: f32, + pub child_protection: bool, +} + +impl TapoResponseExt for KE100Result {} + +impl DecodableResultExt for Box { + fn decode(mut self) -> Result { + self.nickname = decode_value(&self.nickname)?; + Ok(self) + } +} From 92a270298060fafd5e11f412b0a7e5af268ac747 Mon Sep 17 00:00:00 2001 From: pwoerndle <21156233+pwoerndle@users.noreply.github.com> Date: Fri, 20 Oct 2023 15:30:10 +0000 Subject: [PATCH 02/16] Working set temperature for ke100. --- tapo/examples/tapo_ke100.rs | 9 +++++++- tapo/src/api/api_client.rs | 6 ++---- tapo/src/api/child_devices/ke100_handler.rs | 24 +++++++++++++-------- tapo/src/api/child_devices/s200b_handler.rs | 8 ++++--- tapo/src/api/child_devices/t100_handler.rs | 8 ++++--- tapo/src/api/child_devices/t110_handler.rs | 8 ++++--- tapo/src/api/child_devices/t31x_handler.rs | 10 +++++---- tapo/src/api/hub_handler.rs | 2 +- 8 files changed, 47 insertions(+), 28 deletions(-) diff --git a/tapo/examples/tapo_ke100.rs b/tapo/examples/tapo_ke100.rs index ac3b92f..99f14b6 100644 --- a/tapo/examples/tapo_ke100.rs +++ b/tapo/examples/tapo_ke100.rs @@ -20,6 +20,7 @@ async fn main() -> Result<(), Box> { let ip_address = env::var("IP_ADDRESS")?; // ID of the KE100 device. Can be obtained from executing `get_child_device_component_list_json()`` on the hub device. let device_id = env::var("DEVICE_ID")?; + let target_temp: u8 = env::var("TEMPERATURE")?.parse().unwrap(); let hub = ApiClient::new(tapo_username, tapo_password)? .h100(ip_address) @@ -31,7 +32,13 @@ async fn main() -> Result<(), Box> { // Get the device info of the child device let device_info = device.get_device_info().await?; info!("Device info: {device_info:?}"); - + + // Set temperature on target device + device.set_temperature(target_temp).await?; + + // Get the device info of the child device + let device_info = device.get_device_info().await?; + info!("Device info: {device_info:?}"); Ok(()) } diff --git a/tapo/src/api/api_client.rs b/tapo/src/api/api_client.rs index 1c5c2d7..378d3db 100644 --- a/tapo/src/api/api_client.rs +++ b/tapo/src/api/api_client.rs @@ -578,7 +578,7 @@ impl ApiClient { &self, device_id: String, child_request: TapoRequest, - ) -> Result + ) -> Result, Error> where R: fmt::Debug + DeserializeOwned + TapoResponseExt, { @@ -605,9 +605,7 @@ impl ApiClient { validate_response(&response)?; - response - .result - .ok_or_else(|| Error::Tapo(TapoResponseError::EmptyResult)) + Ok(response.result) } } diff --git a/tapo/src/api/child_devices/ke100_handler.rs b/tapo/src/api/child_devices/ke100_handler.rs index c276204..fe119df 100644 --- a/tapo/src/api/child_devices/ke100_handler.rs +++ b/tapo/src/api/child_devices/ke100_handler.rs @@ -1,7 +1,7 @@ use crate::api::HubHandler; -use crate::error::Error; +use crate::error::{Error,TapoResponseError}; use crate::requests::{EmptyParams, TapoParams, TapoRequest, TrvSetDeviceInfoParams}; -use crate::responses::{KE100Result}; +use crate::responses::KE100Result; /// Handler for the [KE100] device. pub struct KE100Handler<'h> { @@ -24,7 +24,8 @@ impl<'h> KE100Handler<'h> { self.hub_handler .control_child(self.device_id.clone(), request) - .await + .await? + .ok_or_else(|| Error::Tapo(TapoResponseError::EmptyResult)) } /// Returns *device info* as [`serde_json::Value`]. @@ -34,7 +35,8 @@ impl<'h> KE100Handler<'h> { self.hub_handler .control_child(self.device_id.clone(), request) - .await + .await? + .ok_or_else(|| Error::Tapo(TapoResponseError::EmptyResult)) } @@ -43,13 +45,15 @@ impl<'h> KE100Handler<'h> { /// # Arguments /// /// * `target_temperature` - between 5.0 and 30.0 - pub async fn set_temperature(&self, target_temperature: u8) -> Result { + pub async fn set_temperature(&self, target_temperature: u8) -> Result, Error> { let json = serde_json::to_value(TrvSetDeviceInfoParams::new().target_temp(target_temperature)?)?; let request = TapoRequest::SetDeviceInfo(Box::new(TapoParams::new(json))); - self.hub_handler + let result = self.hub_handler .control_child(self.device_id.clone(), request) - .await + .await; + + Ok(result.unwrap()) } /// Turns *on* the device. @@ -59,7 +63,8 @@ impl<'h> KE100Handler<'h> { self.hub_handler .control_child(self.device_id.clone(), request) - .await + .await? + .ok_or_else(|| Error::Tapo(TapoResponseError::EmptyResult)) } /// Sets frost protection on the device to *on* or *off*. @@ -73,7 +78,8 @@ impl<'h> KE100Handler<'h> { self.hub_handler .control_child(self.device_id.clone(), request) - .await + .await? + .ok_or_else(|| Error::Tapo(TapoResponseError::EmptyResult)) } } diff --git a/tapo/src/api/child_devices/s200b_handler.rs b/tapo/src/api/child_devices/s200b_handler.rs index 20ad248..4eb3b23 100644 --- a/tapo/src/api/child_devices/s200b_handler.rs +++ b/tapo/src/api/child_devices/s200b_handler.rs @@ -1,5 +1,5 @@ use crate::api::HubHandler; -use crate::error::Error; +use crate::error::{Error,TapoResponseError}; use crate::requests::{EmptyParams, GetTriggerLogsParams, TapoParams, TapoRequest}; use crate::responses::S200BResult; use crate::responses::{S200BLog, TriggerLogsResult}; @@ -25,7 +25,8 @@ impl<'h> S200BHandler<'h> { self.hub_handler .control_child(self.device_id.clone(), request) - .await + .await? + .ok_or_else(|| Error::Tapo(TapoResponseError::EmptyResult)) } /// Returns a list of trigger logs. @@ -46,6 +47,7 @@ impl<'h> S200BHandler<'h> { self.hub_handler .control_child(self.device_id.clone(), child_request) - .await + .await? + .ok_or_else(|| Error::Tapo(TapoResponseError::EmptyResult)) } } diff --git a/tapo/src/api/child_devices/t100_handler.rs b/tapo/src/api/child_devices/t100_handler.rs index 84faf49..5b7a3b2 100644 --- a/tapo/src/api/child_devices/t100_handler.rs +++ b/tapo/src/api/child_devices/t100_handler.rs @@ -1,5 +1,5 @@ use crate::api::HubHandler; -use crate::error::Error; +use crate::error::{Error, TapoResponseError}; use crate::requests::{EmptyParams, GetTriggerLogsParams, TapoParams, TapoRequest}; use crate::responses::T100Result; use crate::responses::{T100Log, TriggerLogsResult}; @@ -25,7 +25,8 @@ impl<'h> T100Handler<'h> { self.hub_handler .control_child(self.device_id.clone(), request) - .await + .await? + .ok_or_else(|| Error::Tapo(TapoResponseError::EmptyResult)) } /// Returns a list of trigger logs. @@ -46,6 +47,7 @@ impl<'h> T100Handler<'h> { self.hub_handler .control_child(self.device_id.clone(), child_request) - .await + .await? + .ok_or_else(|| Error::Tapo(TapoResponseError::EmptyResult)) } } diff --git a/tapo/src/api/child_devices/t110_handler.rs b/tapo/src/api/child_devices/t110_handler.rs index 40411d5..871d29b 100644 --- a/tapo/src/api/child_devices/t110_handler.rs +++ b/tapo/src/api/child_devices/t110_handler.rs @@ -1,5 +1,5 @@ use crate::api::HubHandler; -use crate::error::Error; +use crate::error::{Error, TapoResponseError}; use crate::requests::{EmptyParams, GetTriggerLogsParams, TapoParams, TapoRequest}; use crate::responses::T110Result; use crate::responses::{T110Log, TriggerLogsResult}; @@ -25,7 +25,8 @@ impl<'h> T110Handler<'h> { self.hub_handler .control_child(self.device_id.clone(), request) - .await + .await? + .ok_or_else(|| Error::Tapo(TapoResponseError::EmptyResult)) } /// Returns a list of trigger logs. @@ -46,6 +47,7 @@ impl<'h> T110Handler<'h> { self.hub_handler .control_child(self.device_id.clone(), child_request) - .await + .await? + .ok_or_else(|| Error::Tapo(TapoResponseError::EmptyResult)) } } diff --git a/tapo/src/api/child_devices/t31x_handler.rs b/tapo/src/api/child_devices/t31x_handler.rs index 8d95408..df1a183 100644 --- a/tapo/src/api/child_devices/t31x_handler.rs +++ b/tapo/src/api/child_devices/t31x_handler.rs @@ -1,5 +1,5 @@ use crate::api::HubHandler; -use crate::error::Error; +use crate::error::{Error, TapoResponseError}; use crate::requests::{EmptyParams, TapoParams, TapoRequest}; use crate::responses::{T31XResult, TemperatureHumidityRecords, TemperatureHumidityRecordsRaw}; @@ -24,7 +24,8 @@ impl<'h> T31XHandler<'h> { self.hub_handler .control_child(self.device_id.clone(), request) - .await + .await? + .ok_or_else(|| Error::Tapo(TapoResponseError::EmptyResult)) } /// Returns *temperature and humidity records* from the last 24 hours at 15 minute intervals as [`TemperatureHumidityRecords`]. @@ -37,8 +38,9 @@ impl<'h> T31XHandler<'h> { let result = self .hub_handler .control_child::(self.device_id.clone(), request) - .await?; + .await? + .ok_or_else(|| Error::Tapo(TapoResponseError::EmptyResult)); - Ok(result.try_into()?) + Ok(result.unwrap().try_into()?) } } diff --git a/tapo/src/api/hub_handler.rs b/tapo/src/api/hub_handler.rs index 3a20820..95d5919 100644 --- a/tapo/src/api/hub_handler.rs +++ b/tapo/src/api/hub_handler.rs @@ -67,7 +67,7 @@ impl HubHandler { &self, device_id: String, request_data: TapoRequest, - ) -> Result + ) -> Result, Error> where R: fmt::Debug + DeserializeOwned + TapoResponseExt, { From fa81334cc9e70646ef0ee37db8a3e66af4ac4f69 Mon Sep 17 00:00:00 2001 From: pwoerndle <21156233+pwoerndle@users.noreply.github.com> Date: Sun, 22 Oct 2023 13:56:37 +0000 Subject: [PATCH 03/16] Catch errors when setting target temp. --- tapo/src/api/child_devices/ke100_handler.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tapo/src/api/child_devices/ke100_handler.rs b/tapo/src/api/child_devices/ke100_handler.rs index fe119df..aac396d 100644 --- a/tapo/src/api/child_devices/ke100_handler.rs +++ b/tapo/src/api/child_devices/ke100_handler.rs @@ -53,6 +53,10 @@ impl<'h> KE100Handler<'h> { .control_child(self.device_id.clone(), request) .await; + if result.is_err() { + return result; + } + Ok(result.unwrap()) } From 550ca710c4429752a353c08aa8a7db0cc0c36bd9 Mon Sep 17 00:00:00 2001 From: pwoerndle <21156233+pwoerndle@users.noreply.github.com> Date: Sun, 22 Oct 2023 14:20:27 +0000 Subject: [PATCH 04/16] Removing device_on as it has no effect and handling None result in set_frost_protection_on response --- tapo/src/api/child_devices/ke100_handler.rs | 24 ++++++++------------- tapo/src/requests/set_device_info/trv.rs | 18 +--------------- 2 files changed, 10 insertions(+), 32 deletions(-) diff --git a/tapo/src/api/child_devices/ke100_handler.rs b/tapo/src/api/child_devices/ke100_handler.rs index aac396d..8470bdf 100644 --- a/tapo/src/api/child_devices/ke100_handler.rs +++ b/tapo/src/api/child_devices/ke100_handler.rs @@ -60,30 +60,24 @@ impl<'h> KE100Handler<'h> { Ok(result.unwrap()) } - /// Turns *on* the device. - pub async fn device_on(&self) -> Result { - let json = serde_json::to_value(TrvSetDeviceInfoParams::new().device_on(true)?)?; - let request = TapoRequest::SetDeviceInfo(Box::new(TapoParams::new(json))); - - self.hub_handler - .control_child(self.device_id.clone(), request) - .await? - .ok_or_else(|| Error::Tapo(TapoResponseError::EmptyResult)) - } - /// Sets frost protection on the device to *on* or *off*. /// /// # Arguments /// /// * `frost_protection_on` - true/false - pub async fn set_frost_protection(&self, frost_protection_on: bool) -> Result { + pub async fn set_frost_protection(&self, frost_protection_on: bool) -> Result, Error> { let json = serde_json::to_value(TrvSetDeviceInfoParams::new().frost_protection_on(frost_protection_on)?)?; let request = TapoRequest::SetDeviceInfo(Box::new(TapoParams::new(json))); - self.hub_handler + let result = self.hub_handler .control_child(self.device_id.clone(), request) - .await? - .ok_or_else(|| Error::Tapo(TapoResponseError::EmptyResult)) + .await; + + if result.is_err() { + return result; + } + + Ok(result.unwrap()) } } diff --git a/tapo/src/requests/set_device_info/trv.rs b/tapo/src/requests/set_device_info/trv.rs index fbb2c0f..86eab6f 100644 --- a/tapo/src/requests/set_device_info/trv.rs +++ b/tapo/src/requests/set_device_info/trv.rs @@ -4,8 +4,6 @@ use crate::error::Error; #[derive(Debug, Default, Serialize)] pub(crate) struct TrvSetDeviceInfoParams { - #[serde(skip_serializing_if = "Option::is_none")] - pub device_on: Option, #[serde(skip_serializing_if = "Option::is_none")] pub target_temp: Option, #[serde(skip_serializing_if = "Option::is_none")] @@ -13,17 +11,11 @@ pub(crate) struct TrvSetDeviceInfoParams { } impl TrvSetDeviceInfoParams { - pub fn device_on(mut self, value: bool) -> Result { - self.device_on = Some(value); - self.validate() - } pub fn target_temp(mut self, value: u8) -> Result { - self.device_on = Some(true); self.target_temp = Some(value); self.validate() } pub fn frost_protection_on(mut self, value: bool) -> Result { - self.device_on = Some(true); self.frost_protection_on = Some(value); self.validate() } @@ -33,20 +25,12 @@ impl TrvSetDeviceInfoParams { impl TrvSetDeviceInfoParams { pub(crate) fn new() -> Self { Self { - device_on: None, target_temp: None, frost_protection_on: None, } } - pub fn validate(self) -> Result { - if self.device_on.is_none() { - return Err(Error::Validation { - field: "DeviceInfoParams".to_string(), - message: "requires at least one property".to_string(), - }); - } - + pub fn validate(self) -> Result { Ok(self) } } From 880f44d94a256586cd6a137c28bc30dbedbd3dd7 Mon Sep 17 00:00:00 2001 From: pwoerndle <21156233+pwoerndle@users.noreply.github.com> Date: Sun, 22 Oct 2023 14:49:52 +0000 Subject: [PATCH 05/16] Adding support to set child_protection Fixing documenation for target_temperature to be only u8. Fixing type defintions for min/max_control_temp. --- tapo/src/api/child_devices/ke100_handler.rs | 24 +++++++++++++++++-- tapo/src/requests/set_device_info/trv.rs | 18 +++++++++++++- .../child_device_list_result/ke100_result.rs | 4 ++-- 3 files changed, 41 insertions(+), 5 deletions(-) diff --git a/tapo/src/api/child_devices/ke100_handler.rs b/tapo/src/api/child_devices/ke100_handler.rs index 8470bdf..67a6305 100644 --- a/tapo/src/api/child_devices/ke100_handler.rs +++ b/tapo/src/api/child_devices/ke100_handler.rs @@ -40,11 +40,11 @@ impl<'h> KE100Handler<'h> { } - /// Sets the *target temperature*, turns *on* the device and turns *off* frost protection. + /// Sets the *target temperature*. /// /// # Arguments /// - /// * `target_temperature` - between 5.0 and 30.0 + /// * `target_temperature` - between 5 and 30 pub async fn set_temperature(&self, target_temperature: u8) -> Result, Error> { let json = serde_json::to_value(TrvSetDeviceInfoParams::new().target_temp(target_temperature)?)?; let request = TapoRequest::SetDeviceInfo(Box::new(TapoParams::new(json))); @@ -80,4 +80,24 @@ impl<'h> KE100Handler<'h> { Ok(result.unwrap()) } + /// Sets child protection on the device to *on* or *off*. + /// + /// # Arguments + /// + /// * `child_protection_on` - true/false + pub async fn set_child_protection(&self, child_protection_on: bool) -> Result, Error> { + let json = serde_json::to_value(TrvSetDeviceInfoParams::new().child_protection(child_protection_on)?)?; + let request = TapoRequest::SetDeviceInfo(Box::new(TapoParams::new(json))); + + let result = self.hub_handler + .control_child(self.device_id.clone(), request) + .await; + + if result.is_err() { + return result; + } + + Ok(result.unwrap()) + } + } diff --git a/tapo/src/requests/set_device_info/trv.rs b/tapo/src/requests/set_device_info/trv.rs index 86eab6f..9ca9ca2 100644 --- a/tapo/src/requests/set_device_info/trv.rs +++ b/tapo/src/requests/set_device_info/trv.rs @@ -8,6 +8,8 @@ pub(crate) struct TrvSetDeviceInfoParams { pub target_temp: Option, #[serde(skip_serializing_if = "Option::is_none")] pub frost_protection_on: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub child_protection: Option, } impl TrvSetDeviceInfoParams { @@ -19,6 +21,10 @@ impl TrvSetDeviceInfoParams { self.frost_protection_on = Some(value); self.validate() } + pub fn child_protection(mut self, value: bool) -> Result { + self.child_protection = Some(value); + self.validate() + } } @@ -27,10 +33,20 @@ impl TrvSetDeviceInfoParams { Self { target_temp: None, frost_protection_on: None, + child_protection: None, } } - pub fn validate(self) -> Result { + pub fn validate(self) -> Result { + if let Some(target_temp) = self.target_temp { + if target_temp < 5 || target_temp > 30 { + return Err(Error::Validation { + field: "target_temperature".to_string(), + message: "must be between 5 and 30".to_string(), + + }); + } + } Ok(self) } } diff --git a/tapo/src/responses/child_device_list_result/ke100_result.rs b/tapo/src/responses/child_device_list_result/ke100_result.rs index b64030f..3de6f79 100644 --- a/tapo/src/responses/child_device_list_result/ke100_result.rs +++ b/tapo/src/responses/child_device_list_result/ke100_result.rs @@ -43,8 +43,8 @@ pub struct KE100Result { pub temperature_unit: TemperatureUnit, pub current_temp: f32, pub target_temp: f32, - pub min_control_temp: f32, - pub max_control_temp: f32, + pub min_control_temp: u8, + pub max_control_temp: u8, pub frost_protection_on: bool, pub location: String, pub temp_offset: f32, From 8022e904df3fc1993c23f088c185c422fe2e258c Mon Sep 17 00:00:00 2001 From: pwoerndle <21156233+pwoerndle@users.noreply.github.com> Date: Sun, 22 Oct 2023 14:59:55 +0000 Subject: [PATCH 06/16] Adding support to set temp_offset --- tapo/src/api/child_devices/ke100_handler.rs | 20 +++++++++++++++++++ tapo/src/requests/set_device_info/trv.rs | 17 +++++++++++++++- .../child_device_list_result/ke100_result.rs | 2 +- 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/tapo/src/api/child_devices/ke100_handler.rs b/tapo/src/api/child_devices/ke100_handler.rs index 67a6305..395884d 100644 --- a/tapo/src/api/child_devices/ke100_handler.rs +++ b/tapo/src/api/child_devices/ke100_handler.rs @@ -100,4 +100,24 @@ impl<'h> KE100Handler<'h> { Ok(result.unwrap()) } + /// Sets the *temperature offset*. + /// + /// # Arguments + /// + /// * `temp_offset` - between 5 and 30 + pub async fn set_temp_offset(&self, temp_offset: i8) -> Result, Error> { + let json = serde_json::to_value(TrvSetDeviceInfoParams::new().temp_offset(temp_offset)?)?; + let request = TapoRequest::SetDeviceInfo(Box::new(TapoParams::new(json))); + + let result = self.hub_handler + .control_child(self.device_id.clone(), request) + .await; + + if result.is_err() { + return result; + } + + Ok(result.unwrap()) + } + } diff --git a/tapo/src/requests/set_device_info/trv.rs b/tapo/src/requests/set_device_info/trv.rs index 9ca9ca2..9ed34cb 100644 --- a/tapo/src/requests/set_device_info/trv.rs +++ b/tapo/src/requests/set_device_info/trv.rs @@ -10,6 +10,8 @@ pub(crate) struct TrvSetDeviceInfoParams { pub frost_protection_on: Option, #[serde(skip_serializing_if = "Option::is_none")] pub child_protection: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub temp_offset: Option, } impl TrvSetDeviceInfoParams { @@ -25,7 +27,10 @@ impl TrvSetDeviceInfoParams { self.child_protection = Some(value); self.validate() } - + pub fn temp_offset(mut self, value: i8) -> Result { + self.temp_offset = Some(value); + self.validate() + } } impl TrvSetDeviceInfoParams { @@ -34,6 +39,7 @@ impl TrvSetDeviceInfoParams { target_temp: None, frost_protection_on: None, child_protection: None, + temp_offset: None, } } @@ -44,6 +50,15 @@ impl TrvSetDeviceInfoParams { field: "target_temperature".to_string(), message: "must be between 5 and 30".to_string(), + }); + } + } + if let Some(temp_offset) = self.temp_offset { + if temp_offset < -10 || temp_offset> 10 { + return Err(Error::Validation { + field: "temp_offset".to_string(), + message: "must be between -10 and 10".to_string(), + }); } } diff --git a/tapo/src/responses/child_device_list_result/ke100_result.rs b/tapo/src/responses/child_device_list_result/ke100_result.rs index 3de6f79..38da769 100644 --- a/tapo/src/responses/child_device_list_result/ke100_result.rs +++ b/tapo/src/responses/child_device_list_result/ke100_result.rs @@ -47,7 +47,7 @@ pub struct KE100Result { pub max_control_temp: u8, pub frost_protection_on: bool, pub location: String, - pub temp_offset: f32, + pub temp_offset: i8, pub child_protection: bool, } From 01861e8bb33c013cf518d8ec8f906d83f32a907b Mon Sep 17 00:00:00 2001 From: pwoerndle <21156233+pwoerndle@users.noreply.github.com> Date: Sun, 22 Oct 2023 16:09:18 +0000 Subject: [PATCH 07/16] Added support for min/max_control_temp target_temperature is validated against current min/max_control_temp Added function to set min_temp but currently does not affect device --- tapo/src/api/child_devices/ke100_handler.rs | 83 ++++++++++++++++++++- tapo/src/requests/set_device_info/trv.rs | 30 +++++--- 2 files changed, 103 insertions(+), 10 deletions(-) diff --git a/tapo/src/api/child_devices/ke100_handler.rs b/tapo/src/api/child_devices/ke100_handler.rs index 395884d..80c69f5 100644 --- a/tapo/src/api/child_devices/ke100_handler.rs +++ b/tapo/src/api/child_devices/ke100_handler.rs @@ -44,8 +44,18 @@ impl<'h> KE100Handler<'h> { /// /// # Arguments /// - /// * `target_temperature` - between 5 and 30 + /// * `target_temperature` - between min_control_temp and max_control_temp pub async fn set_temperature(&self, target_temperature: u8) -> Result, Error> { + + let control_range = self.get_control_range().await?; + + if target_temperature < control_range[0] || target_temperature > control_range[1] { + return Err(Error::Validation { + field: "target_temperature".to_string(), + message: format!("Target temperature must be between {} (min_control_temp) and {} (max_control_temp)", control_range[0], control_range[1]), + }); + } + let json = serde_json::to_value(TrvSetDeviceInfoParams::new().target_temp(target_temperature)?)?; let request = TapoRequest::SetDeviceInfo(Box::new(TapoParams::new(json))); @@ -60,6 +70,66 @@ impl<'h> KE100Handler<'h> { Ok(result.unwrap()) } + /// Sets the *min temperature*, which is applied in frost protection mode. + /// + /// # Arguments + /// + /// * `min_temperature` - between 5 and 15 + pub async fn set_min_temperature(&self, min_temperature: u8) -> Result, Error> { + let json = serde_json::to_value(TrvSetDeviceInfoParams::new().min_temp(min_temperature)?)?; + let request = TapoRequest::SetDeviceInfo(Box::new(TapoParams::new(json))); + + let result = self.hub_handler + .control_child(self.device_id.clone(), request) + .await; + + if result.is_err() { + return result; + } + + Ok(result.unwrap()) + } + + /// Sets the *minimal control temperature*. + /// + /// # Arguments + /// + /// * `min_control_temperature` + pub async fn set_min_control_temperature(&self, min_control_temperature: u8) -> Result, Error> { + let json = serde_json::to_value(TrvSetDeviceInfoParams::new().min_control_temp(min_control_temperature)?)?; + let request = TapoRequest::SetDeviceInfo(Box::new(TapoParams::new(json))); + + let result = self.hub_handler + .control_child(self.device_id.clone(), request) + .await; + + if result.is_err() { + return result; + } + + Ok(result.unwrap()) + } + + /// Sets the *maximum control temperature*. + /// + /// # Arguments + /// + /// * `max_control_temperature` + pub async fn set_max_control_temperature(&self, max_control_temperature: u8) -> Result, Error> { + let json = serde_json::to_value(TrvSetDeviceInfoParams::new().max_control_temp(max_control_temperature)?)?; + let request = TapoRequest::SetDeviceInfo(Box::new(TapoParams::new(json))); + + let result = self.hub_handler + .control_child(self.device_id.clone(), request) + .await; + + if result.is_err() { + return result; + } + + Ok(result.unwrap()) + } + /// Sets frost protection on the device to *on* or *off*. /// /// # Arguments @@ -120,4 +190,15 @@ impl<'h> KE100Handler<'h> { Ok(result.unwrap()) } + /// Returns *min_control_temp* and *max_control_temp* as Vec. + async fn get_control_range(&self) -> Result, Error> { + let request = TapoRequest::GetDeviceInfo(TapoParams::new(EmptyParams)); + + self.hub_handler + .control_child::(self.device_id.clone(), request) + .await? + .ok_or_else(|| Error::Tapo(TapoResponseError::EmptyResult)) + .map(|result| vec![result.min_control_temp,result.max_control_temp]) + } + } diff --git a/tapo/src/requests/set_device_info/trv.rs b/tapo/src/requests/set_device_info/trv.rs index 9ed34cb..3126b7b 100644 --- a/tapo/src/requests/set_device_info/trv.rs +++ b/tapo/src/requests/set_device_info/trv.rs @@ -12,6 +12,12 @@ pub(crate) struct TrvSetDeviceInfoParams { pub child_protection: Option, #[serde(skip_serializing_if = "Option::is_none")] pub temp_offset: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub min_temp: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub min_control_temp: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub max_control_temp: Option, } impl TrvSetDeviceInfoParams { @@ -31,6 +37,18 @@ impl TrvSetDeviceInfoParams { self.temp_offset = Some(value); self.validate() } + pub fn min_temp(mut self, value: u8) -> Result { + self.min_temp = Some(value); + self.validate() + } + pub fn min_control_temp(mut self, value: u8) -> Result { + self.min_control_temp = Some(value); + self.validate() + } + pub fn max_control_temp(mut self, value: u8) -> Result { + self.max_control_temp = Some(value); + self.validate() + } } impl TrvSetDeviceInfoParams { @@ -40,19 +58,13 @@ impl TrvSetDeviceInfoParams { frost_protection_on: None, child_protection: None, temp_offset: None, + min_temp: None, + min_control_temp: None, + max_control_temp: None, } } pub fn validate(self) -> Result { - if let Some(target_temp) = self.target_temp { - if target_temp < 5 || target_temp > 30 { - return Err(Error::Validation { - field: "target_temperature".to_string(), - message: "must be between 5 and 30".to_string(), - - }); - } - } if let Some(temp_offset) = self.temp_offset { if temp_offset < -10 || temp_offset> 10 { return Err(Error::Validation { From 6aae0bdc71ab56894bad23911bc6599b98a13a56 Mon Sep 17 00:00:00 2001 From: pwoerndle <21156233+pwoerndle@users.noreply.github.com> Date: Wed, 25 Oct 2023 19:57:53 +0200 Subject: [PATCH 08/16] Cleaner set_temperature function As suggested by mihai-dinculescu Co-authored-by: Mihai Dinculescu --- tapo/src/api/child_devices/ke100_handler.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/tapo/src/api/child_devices/ke100_handler.rs b/tapo/src/api/child_devices/ke100_handler.rs index 80c69f5..c518808 100644 --- a/tapo/src/api/child_devices/ke100_handler.rs +++ b/tapo/src/api/child_devices/ke100_handler.rs @@ -45,7 +45,7 @@ impl<'h> KE100Handler<'h> { /// # Arguments /// /// * `target_temperature` - between min_control_temp and max_control_temp - pub async fn set_temperature(&self, target_temperature: u8) -> Result, Error> { + pub async fn set_temperature(&self, target_temperature: u8) -> Result<(), Error> { let control_range = self.get_control_range().await?; @@ -59,15 +59,11 @@ impl<'h> KE100Handler<'h> { let json = serde_json::to_value(TrvSetDeviceInfoParams::new().target_temp(target_temperature)?)?; let request = TapoRequest::SetDeviceInfo(Box::new(TapoParams::new(json))); - let result = self.hub_handler + self.hub_handler .control_child(self.device_id.clone(), request) - .await; - - if result.is_err() { - return result; - } + .await?; - Ok(result.unwrap()) + Ok(()) } /// Sets the *min temperature*, which is applied in frost protection mode. From 702252118330742f5ae71a13a34574d9ea7e23a6 Mon Sep 17 00:00:00 2001 From: pwoerndle <21156233+pwoerndle@users.noreply.github.com> Date: Sun, 29 Oct 2023 17:26:35 +0000 Subject: [PATCH 09/16] Cleanup for functions with no result. --- tapo/src/api/child_devices/ke100_handler.rs | 86 ++++++++------------- 1 file changed, 31 insertions(+), 55 deletions(-) diff --git a/tapo/src/api/child_devices/ke100_handler.rs b/tapo/src/api/child_devices/ke100_handler.rs index c518808..c2bd47c 100644 --- a/tapo/src/api/child_devices/ke100_handler.rs +++ b/tapo/src/api/child_devices/ke100_handler.rs @@ -60,7 +60,7 @@ impl<'h> KE100Handler<'h> { let request = TapoRequest::SetDeviceInfo(Box::new(TapoParams::new(json))); self.hub_handler - .control_child(self.device_id.clone(), request) + .control_child::(self.device_id.clone(), request) .await?; Ok(()) @@ -71,19 +71,15 @@ impl<'h> KE100Handler<'h> { /// # Arguments /// /// * `min_temperature` - between 5 and 15 - pub async fn set_min_temperature(&self, min_temperature: u8) -> Result, Error> { + pub async fn set_min_temperature(&self, min_temperature: u8) -> Result<(), Error> { let json = serde_json::to_value(TrvSetDeviceInfoParams::new().min_temp(min_temperature)?)?; let request = TapoRequest::SetDeviceInfo(Box::new(TapoParams::new(json))); - let result = self.hub_handler - .control_child(self.device_id.clone(), request) - .await; - - if result.is_err() { - return result; - } + self.hub_handler + .control_child::(self.device_id.clone(), request) + .await?; - Ok(result.unwrap()) + Ok(()) } /// Sets the *minimal control temperature*. @@ -91,19 +87,15 @@ impl<'h> KE100Handler<'h> { /// # Arguments /// /// * `min_control_temperature` - pub async fn set_min_control_temperature(&self, min_control_temperature: u8) -> Result, Error> { + pub async fn set_min_control_temperature(&self, min_control_temperature: u8) -> Result<(), Error> { let json = serde_json::to_value(TrvSetDeviceInfoParams::new().min_control_temp(min_control_temperature)?)?; let request = TapoRequest::SetDeviceInfo(Box::new(TapoParams::new(json))); - let result = self.hub_handler - .control_child(self.device_id.clone(), request) - .await; - - if result.is_err() { - return result; - } + self.hub_handler + .control_child::(self.device_id.clone(), request) + .await?; - Ok(result.unwrap()) + Ok(()) } /// Sets the *maximum control temperature*. @@ -111,19 +103,15 @@ impl<'h> KE100Handler<'h> { /// # Arguments /// /// * `max_control_temperature` - pub async fn set_max_control_temperature(&self, max_control_temperature: u8) -> Result, Error> { + pub async fn set_max_control_temperature(&self, max_control_temperature: u8) -> Result<(), Error> { let json = serde_json::to_value(TrvSetDeviceInfoParams::new().max_control_temp(max_control_temperature)?)?; let request = TapoRequest::SetDeviceInfo(Box::new(TapoParams::new(json))); - let result = self.hub_handler - .control_child(self.device_id.clone(), request) - .await; - - if result.is_err() { - return result; - } + self.hub_handler + .control_child::(self.device_id.clone(), request) + .await?; - Ok(result.unwrap()) + Ok(()) } /// Sets frost protection on the device to *on* or *off*. @@ -131,19 +119,15 @@ impl<'h> KE100Handler<'h> { /// # Arguments /// /// * `frost_protection_on` - true/false - pub async fn set_frost_protection(&self, frost_protection_on: bool) -> Result, Error> { + pub async fn set_frost_protection(&self, frost_protection_on: bool) -> Result<(), Error> { let json = serde_json::to_value(TrvSetDeviceInfoParams::new().frost_protection_on(frost_protection_on)?)?; let request = TapoRequest::SetDeviceInfo(Box::new(TapoParams::new(json))); - let result = self.hub_handler - .control_child(self.device_id.clone(), request) - .await; + self.hub_handler + .control_child::(self.device_id.clone(), request) + .await?; - if result.is_err() { - return result; - } - - Ok(result.unwrap()) + Ok(()) } /// Sets child protection on the device to *on* or *off*. @@ -151,19 +135,15 @@ impl<'h> KE100Handler<'h> { /// # Arguments /// /// * `child_protection_on` - true/false - pub async fn set_child_protection(&self, child_protection_on: bool) -> Result, Error> { + pub async fn set_child_protection(&self, child_protection_on: bool) -> Result<(), Error> { let json = serde_json::to_value(TrvSetDeviceInfoParams::new().child_protection(child_protection_on)?)?; let request = TapoRequest::SetDeviceInfo(Box::new(TapoParams::new(json))); - let result = self.hub_handler - .control_child(self.device_id.clone(), request) - .await; + self.hub_handler + .control_child::(self.device_id.clone(), request) + .await?; - if result.is_err() { - return result; - } - - Ok(result.unwrap()) + Ok(()) } /// Sets the *temperature offset*. @@ -171,19 +151,15 @@ impl<'h> KE100Handler<'h> { /// # Arguments /// /// * `temp_offset` - between 5 and 30 - pub async fn set_temp_offset(&self, temp_offset: i8) -> Result, Error> { + pub async fn set_temp_offset(&self, temp_offset: i8) -> Result<(), Error> { let json = serde_json::to_value(TrvSetDeviceInfoParams::new().temp_offset(temp_offset)?)?; let request = TapoRequest::SetDeviceInfo(Box::new(TapoParams::new(json))); - let result = self.hub_handler - .control_child(self.device_id.clone(), request) - .await; - - if result.is_err() { - return result; - } + self.hub_handler + .control_child::(self.device_id.clone(), request) + .await?; - Ok(result.unwrap()) + Ok(()) } /// Returns *min_control_temp* and *max_control_temp* as Vec. From d1352d882646e19393c7773c5b97f1994106a00f Mon Sep 17 00:00:00 2001 From: pwoerndle <21156233+pwoerndle@users.noreply.github.com> Date: Sun, 5 Nov 2023 11:07:43 +0000 Subject: [PATCH 10/16] Fix review comments - Rename temp to temperature in variable names and add renaming in serde - Add module for TemperatureUnit enum and remove declaration from results - Remove set_min_temperature() function - Replace get_control_range() with min/max from get_device_info() - Replace `unwrap()` with `?` --- tapo/examples/tapo_ke100.rs | 6 +- tapo/src/api/child_devices/ke100_handler.rs | 49 ++++------------ tapo/src/api/child_devices/t31x_handler.rs | 2 +- tapo/src/requests/set_device_info/trv.rs | 56 +++++++++---------- tapo/src/responses.rs | 2 + .../src/responses/child_device_list_result.rs | 2 +- .../child_device_list_result/ke100_result.rs | 26 ++++----- .../child_device_list_result/t31x_result.rs | 11 +--- tapo/src/responses/temperature_unit.rs | 10 ++++ 9 files changed, 66 insertions(+), 98 deletions(-) create mode 100644 tapo/src/responses/temperature_unit.rs diff --git a/tapo/examples/tapo_ke100.rs b/tapo/examples/tapo_ke100.rs index 99f14b6..1de02f9 100644 --- a/tapo/examples/tapo_ke100.rs +++ b/tapo/examples/tapo_ke100.rs @@ -18,9 +18,9 @@ async fn main() -> Result<(), Box> { let tapo_username = env::var("TAPO_USERNAME")?; let tapo_password = env::var("TAPO_PASSWORD")?; let ip_address = env::var("IP_ADDRESS")?; - // ID of the KE100 device. Can be obtained from executing `get_child_device_component_list_json()`` on the hub device. + // ID of the KE100 device. Can be obtained from executing `get_child_device_component_list()`` on the hub device. let device_id = env::var("DEVICE_ID")?; - let target_temp: u8 = env::var("TEMPERATURE")?.parse().unwrap(); + let target_temperature: u8 = env::var("TARGET_TEMPERATURE")?.parse()?; let hub = ApiClient::new(tapo_username, tapo_password)? .h100(ip_address) @@ -34,7 +34,7 @@ async fn main() -> Result<(), Box> { info!("Device info: {device_info:?}"); // Set temperature on target device - device.set_temperature(target_temp).await?; + device.set_target_temperature(target_temperature).await?; // Get the device info of the child device let device_info = device.get_device_info().await?; diff --git a/tapo/src/api/child_devices/ke100_handler.rs b/tapo/src/api/child_devices/ke100_handler.rs index c2bd47c..afabcd5 100644 --- a/tapo/src/api/child_devices/ke100_handler.rs +++ b/tapo/src/api/child_devices/ke100_handler.rs @@ -45,34 +45,19 @@ impl<'h> KE100Handler<'h> { /// # Arguments /// /// * `target_temperature` - between min_control_temp and max_control_temp - pub async fn set_temperature(&self, target_temperature: u8) -> Result<(), Error> { + pub async fn set_target_temperature(&self, target_temperature: u8) -> Result<(), Error> { - let control_range = self.get_control_range().await?; + //let control_range = self.get_control_range().await?; + let device_info = self.get_device_info().await?; - if target_temperature < control_range[0] || target_temperature > control_range[1] { + if target_temperature < device_info.min_control_temperature || target_temperature > device_info.max_control_temperature { return Err(Error::Validation { field: "target_temperature".to_string(), - message: format!("Target temperature must be between {} (min_control_temp) and {} (max_control_temp)", control_range[0], control_range[1]), + message: format!("Target temperature must be between {} (min_control_temp) and {} (max_control_temp)", device_info.min_control_temperature, device_info.max_control_temperature), }); } - let json = serde_json::to_value(TrvSetDeviceInfoParams::new().target_temp(target_temperature)?)?; - let request = TapoRequest::SetDeviceInfo(Box::new(TapoParams::new(json))); - - self.hub_handler - .control_child::(self.device_id.clone(), request) - .await?; - - Ok(()) - } - - /// Sets the *min temperature*, which is applied in frost protection mode. - /// - /// # Arguments - /// - /// * `min_temperature` - between 5 and 15 - pub async fn set_min_temperature(&self, min_temperature: u8) -> Result<(), Error> { - let json = serde_json::to_value(TrvSetDeviceInfoParams::new().min_temp(min_temperature)?)?; + let json = serde_json::to_value(TrvSetDeviceInfoParams::new().target_temperature(target_temperature)?)?; let request = TapoRequest::SetDeviceInfo(Box::new(TapoParams::new(json))); self.hub_handler @@ -88,7 +73,7 @@ impl<'h> KE100Handler<'h> { /// /// * `min_control_temperature` pub async fn set_min_control_temperature(&self, min_control_temperature: u8) -> Result<(), Error> { - let json = serde_json::to_value(TrvSetDeviceInfoParams::new().min_control_temp(min_control_temperature)?)?; + let json = serde_json::to_value(TrvSetDeviceInfoParams::new().min_control_temperature(min_control_temperature)?)?; let request = TapoRequest::SetDeviceInfo(Box::new(TapoParams::new(json))); self.hub_handler @@ -104,7 +89,7 @@ impl<'h> KE100Handler<'h> { /// /// * `max_control_temperature` pub async fn set_max_control_temperature(&self, max_control_temperature: u8) -> Result<(), Error> { - let json = serde_json::to_value(TrvSetDeviceInfoParams::new().max_control_temp(max_control_temperature)?)?; + let json = serde_json::to_value(TrvSetDeviceInfoParams::new().max_control_temperature(max_control_temperature)?)?; let request = TapoRequest::SetDeviceInfo(Box::new(TapoParams::new(json))); self.hub_handler @@ -150,9 +135,9 @@ impl<'h> KE100Handler<'h> { /// /// # Arguments /// - /// * `temp_offset` - between 5 and 30 - pub async fn set_temp_offset(&self, temp_offset: i8) -> Result<(), Error> { - let json = serde_json::to_value(TrvSetDeviceInfoParams::new().temp_offset(temp_offset)?)?; + /// * `temperature_offset` - between 5 and 30 + pub async fn set_temperature_offset(&self, temperature_offset: i8) -> Result<(), Error> { + let json = serde_json::to_value(TrvSetDeviceInfoParams::new().temperature_offset(temperature_offset)?)?; let request = TapoRequest::SetDeviceInfo(Box::new(TapoParams::new(json))); self.hub_handler @@ -161,16 +146,4 @@ impl<'h> KE100Handler<'h> { Ok(()) } - - /// Returns *min_control_temp* and *max_control_temp* as Vec. - async fn get_control_range(&self) -> Result, Error> { - let request = TapoRequest::GetDeviceInfo(TapoParams::new(EmptyParams)); - - self.hub_handler - .control_child::(self.device_id.clone(), request) - .await? - .ok_or_else(|| Error::Tapo(TapoResponseError::EmptyResult)) - .map(|result| vec![result.min_control_temp,result.max_control_temp]) - } - } diff --git a/tapo/src/api/child_devices/t31x_handler.rs b/tapo/src/api/child_devices/t31x_handler.rs index df1a183..81950e2 100644 --- a/tapo/src/api/child_devices/t31x_handler.rs +++ b/tapo/src/api/child_devices/t31x_handler.rs @@ -41,6 +41,6 @@ impl<'h> T31XHandler<'h> { .await? .ok_or_else(|| Error::Tapo(TapoResponseError::EmptyResult)); - Ok(result.unwrap().try_into()?) + Ok(result?.try_into()?) } } diff --git a/tapo/src/requests/set_device_info/trv.rs b/tapo/src/requests/set_device_info/trv.rs index 3126b7b..ab09490 100644 --- a/tapo/src/requests/set_device_info/trv.rs +++ b/tapo/src/requests/set_device_info/trv.rs @@ -4,25 +4,25 @@ use crate::error::Error; #[derive(Debug, Default, Serialize)] pub(crate) struct TrvSetDeviceInfoParams { + #[serde(skip_serializing_if = "Option::is_none", rename = "target_temp")] + target_temperature: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub target_temp: Option, + frost_protection_on: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub frost_protection_on: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub child_protection: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub temp_offset: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub min_temp: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub min_control_temp: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub max_control_temp: Option, + child_protection: Option, + #[serde(skip_serializing_if = "Option::is_none", rename = "temp_offset")] + temperature_offset: Option, + #[serde(skip_serializing_if = "Option::is_none", rename = "min_temp")] + min_temperature: Option, + #[serde(skip_serializing_if = "Option::is_none", rename = "min_control_temp")] + min_control_temperature: Option, + #[serde(skip_serializing_if = "Option::is_none", rename = "max_control_temp")] + max_control_temperature: Option, } impl TrvSetDeviceInfoParams { - pub fn target_temp(mut self, value: u8) -> Result { - self.target_temp = Some(value); + pub fn target_temperature(mut self, value: u8) -> Result { + self.target_temperature = Some(value); self.validate() } pub fn frost_protection_on(mut self, value: bool) -> Result { @@ -33,20 +33,16 @@ impl TrvSetDeviceInfoParams { self.child_protection = Some(value); self.validate() } - pub fn temp_offset(mut self, value: i8) -> Result { - self.temp_offset = Some(value); - self.validate() - } - pub fn min_temp(mut self, value: u8) -> Result { - self.min_temp = Some(value); + pub fn temperature_offset(mut self, value: i8) -> Result { + self.temperature_offset = Some(value); self.validate() } - pub fn min_control_temp(mut self, value: u8) -> Result { - self.min_control_temp = Some(value); + pub fn min_control_temperature(mut self, value: u8) -> Result { + self.min_control_temperature = Some(value); self.validate() } - pub fn max_control_temp(mut self, value: u8) -> Result { - self.max_control_temp = Some(value); + pub fn max_control_temperature(mut self, value: u8) -> Result { + self.max_control_temperature = Some(value); self.validate() } } @@ -54,18 +50,18 @@ impl TrvSetDeviceInfoParams { impl TrvSetDeviceInfoParams { pub(crate) fn new() -> Self { Self { - target_temp: None, + target_temperature: None, frost_protection_on: None, child_protection: None, - temp_offset: None, - min_temp: None, - min_control_temp: None, - max_control_temp: None, + temperature_offset: None, + min_temperature: None, + min_control_temperature: None, + max_control_temperature: None, } } pub fn validate(self) -> Result { - if let Some(temp_offset) = self.temp_offset { + if let Some(temp_offset) = self.temperature_offset { if temp_offset < -10 || temp_offset> 10 { return Err(Error::Validation { field: "temp_offset".to_string(), diff --git a/tapo/src/responses.rs b/tapo/src/responses.rs index df68dc7..43b86a3 100644 --- a/tapo/src/responses.rs +++ b/tapo/src/responses.rs @@ -14,6 +14,7 @@ mod tapo_response; mod tapo_result; mod token_result; mod trigger_logs_result; +mod temperature_unit; pub use child_device_list_result::*; pub use current_power_result::*; @@ -23,6 +24,7 @@ pub use device_usage_result::*; pub use energy_data_result::*; pub use energy_usage_result::*; pub use trigger_logs_result::*; +pub use temperature_unit::*; pub(crate) use control_child_result::*; pub(crate) use decodable_result_ext::*; diff --git a/tapo/src/responses/child_device_list_result.rs b/tapo/src/responses/child_device_list_result.rs index 0753d05..f353e45 100644 --- a/tapo/src/responses/child_device_list_result.rs +++ b/tapo/src/responses/child_device_list_result.rs @@ -64,7 +64,7 @@ pub enum ChildDeviceResult { T310(Box), /// T315 temperature & humidity sensor. T315(Box), - /// KE100 TRV. + /// KE100 thermostatic radiator valve (TRV). KE100(Box), /// Catch-all for currently unsupported devices. /// Please open an issue if you need support for a new device. diff --git a/tapo/src/responses/child_device_list_result/ke100_result.rs b/tapo/src/responses/child_device_list_result/ke100_result.rs index 38da769..87b7a79 100644 --- a/tapo/src/responses/child_device_list_result/ke100_result.rs +++ b/tapo/src/responses/child_device_list_result/ke100_result.rs @@ -1,16 +1,7 @@ use serde::{Deserialize, Serialize}; use crate::error::Error; -use crate::responses::{decode_value, DecodableResultExt, Status, TapoResponseExt}; - -/// Temperature unit. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -#[allow(missing_docs)] -pub enum TemperatureUnit { - Celsius, - Fahrenheit, -} +use crate::responses::{decode_value, DecodableResultExt, Status, TapoResponseExt, TemperatureUnit}; /// KE100 TRV. @@ -41,13 +32,18 @@ pub struct KE100Result { pub r#type: String, #[serde(rename = "temp_unit")] pub temperature_unit: TemperatureUnit, - pub current_temp: f32, - pub target_temp: f32, - pub min_control_temp: u8, - pub max_control_temp: u8, + #[serde(rename = "current_temp")] + pub current_temperature: f32, + #[serde(rename = "target_temp")] + pub target_temperature: f32, + #[serde(rename = "min_control_temp")] + pub min_control_temperature: u8, + #[serde(rename = "max_control_temp")] + pub max_control_temperature: u8, pub frost_protection_on: bool, pub location: String, - pub temp_offset: i8, + #[serde(rename = "temp_offset")] + pub temperature_offset: i8, pub child_protection: bool, } diff --git a/tapo/src/responses/child_device_list_result/t31x_result.rs b/tapo/src/responses/child_device_list_result/t31x_result.rs index dff3583..aa8eae5 100644 --- a/tapo/src/responses/child_device_list_result/t31x_result.rs +++ b/tapo/src/responses/child_device_list_result/t31x_result.rs @@ -3,16 +3,7 @@ use itertools::izip; use serde::{Deserialize, Serialize}; use crate::error::Error; -use crate::responses::{decode_value, DecodableResultExt, Status, TapoResponseExt}; - -/// Temperature unit. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -#[allow(missing_docs)] -pub enum TemperatureUnit { - Celsius, - Fahrenheit, -} +use crate::responses::{decode_value, DecodableResultExt, Status, TapoResponseExt, TemperatureUnit}; /// T310/T315 temperature & humidity sensor. /// diff --git a/tapo/src/responses/temperature_unit.rs b/tapo/src/responses/temperature_unit.rs new file mode 100644 index 0000000..30beeb8 --- /dev/null +++ b/tapo/src/responses/temperature_unit.rs @@ -0,0 +1,10 @@ +use serde::{Deserialize,Serialize}; + +/// Temperature unit. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +#[allow(missing_docs)] +pub enum TemperatureUnit { + Celsius, + Fahrenheit, +} \ No newline at end of file From 10c919a0282f37fa9069237f4cf64c4a7eba9d5a Mon Sep 17 00:00:00 2001 From: pwoerndle <21156233+pwoerndle@users.noreply.github.com> Date: Sun, 5 Nov 2023 11:59:10 +0000 Subject: [PATCH 11/16] Fix incompatibiity with empth result handling. hub_handler returns Result, Error> for control_child(). --- tapo/src/api/child_devices/t300_handler.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/tapo/src/api/child_devices/t300_handler.rs b/tapo/src/api/child_devices/t300_handler.rs index b7b387e..cffe57d 100644 --- a/tapo/src/api/child_devices/t300_handler.rs +++ b/tapo/src/api/child_devices/t300_handler.rs @@ -1,5 +1,5 @@ use crate::api::HubHandler; -use crate::error::Error; +use crate::error::{Error,TapoResponseError}; use crate::requests::{EmptyParams, GetTriggerLogsParams, TapoParams, TapoRequest}; use crate::responses::T300Result; use crate::responses::{T300Log, TriggerLogsResult}; @@ -25,7 +25,8 @@ impl<'h> T300Handler<'h> { self.hub_handler .control_child(self.device_id.clone(), request) - .await + .await? + .ok_or_else(|| Error::Tapo(TapoResponseError::EmptyResult)) } /// Returns a list of trigger logs. @@ -44,8 +45,12 @@ impl<'h> T300Handler<'h> { let child_params = GetTriggerLogsParams::new(page_size, start_id); let child_request = TapoRequest::GetTriggerLogs(Box::new(TapoParams::new(child_params))); - self.hub_handler - .control_child(self.device_id.clone(), child_request) - .await + let result = self + .hub_handler + .control_child::>(self.device_id.clone(), child_request) + .await? + .ok_or_else(|| Error::Tapo(TapoResponseError::EmptyResult)); + + Ok(result?) } } From a9ebad1040a2861c931b93b6e11a153d28c49c75 Mon Sep 17 00:00:00 2001 From: pwoerndle <21156233+pwoerndle@users.noreply.github.com> Date: Thu, 23 Nov 2023 16:28:12 +0000 Subject: [PATCH 12/16] Add temperature_unit param for setting temperatures - Enforce single value TemperatureUnit enum for KE100 with Celsius - Revert previous change to global TemperatureUnit enum --- tapo/examples/tapo_ke100.rs | 5 ++-- tapo/src/api/child_devices/ke100_handler.rs | 24 +++++++++++-------- tapo/src/requests/set_device_info/trv.rs | 17 +++++++++---- tapo/src/responses.rs | 2 -- .../child_device_list_result/ke100_result.rs | 11 ++++++++- .../child_device_list_result/t31x_result.rs | 11 ++++++++- tapo/src/responses/temperature_unit.rs | 10 -------- 7 files changed, 50 insertions(+), 30 deletions(-) delete mode 100644 tapo/src/responses/temperature_unit.rs diff --git a/tapo/examples/tapo_ke100.rs b/tapo/examples/tapo_ke100.rs index 1de02f9..fd245f9 100644 --- a/tapo/examples/tapo_ke100.rs +++ b/tapo/examples/tapo_ke100.rs @@ -33,8 +33,9 @@ async fn main() -> Result<(), Box> { let device_info = device.get_device_info().await?; info!("Device info: {device_info:?}"); - // Set temperature on target device - device.set_target_temperature(target_temperature).await?; + // Set temperature on target device and set temperature unit to Celsius. + // KE100 currently only supports Celsius as temperature unti. + device.set_target_temperature(target_temperature, tapo::responses::TemperatureUnit::Celsius).await?; // Get the device info of the child device let device_info = device.get_device_info().await?; diff --git a/tapo/src/api/child_devices/ke100_handler.rs b/tapo/src/api/child_devices/ke100_handler.rs index afabcd5..6e71e0e 100644 --- a/tapo/src/api/child_devices/ke100_handler.rs +++ b/tapo/src/api/child_devices/ke100_handler.rs @@ -1,7 +1,7 @@ use crate::api::HubHandler; use crate::error::{Error,TapoResponseError}; use crate::requests::{EmptyParams, TapoParams, TapoRequest, TrvSetDeviceInfoParams}; -use crate::responses::KE100Result; +use crate::responses::{KE100Result, TemperatureUnit}; /// Handler for the [KE100] device. pub struct KE100Handler<'h> { @@ -45,7 +45,8 @@ impl<'h> KE100Handler<'h> { /// # Arguments /// /// * `target_temperature` - between min_control_temp and max_control_temp - pub async fn set_target_temperature(&self, target_temperature: u8) -> Result<(), Error> { + /// * `temperature_unit` - enum with one value: Celsius + pub async fn set_target_temperature(&self, target_temperature: u8, temperature_unit: TemperatureUnit) -> Result<(), Error> { //let control_range = self.get_control_range().await?; let device_info = self.get_device_info().await?; @@ -57,7 +58,7 @@ impl<'h> KE100Handler<'h> { }); } - let json = serde_json::to_value(TrvSetDeviceInfoParams::new().target_temperature(target_temperature)?)?; + let json = serde_json::to_value(TrvSetDeviceInfoParams::new().target_temperature(target_temperature, temperature_unit)?)?; let request = TapoRequest::SetDeviceInfo(Box::new(TapoParams::new(json))); self.hub_handler @@ -72,8 +73,9 @@ impl<'h> KE100Handler<'h> { /// # Arguments /// /// * `min_control_temperature` - pub async fn set_min_control_temperature(&self, min_control_temperature: u8) -> Result<(), Error> { - let json = serde_json::to_value(TrvSetDeviceInfoParams::new().min_control_temperature(min_control_temperature)?)?; + /// * `temperature_unit` - enum with one value: Celsius + pub async fn set_min_control_temperature(&self, min_control_temperature: u8, temperature_unit: TemperatureUnit) -> Result<(), Error> { + let json = serde_json::to_value(TrvSetDeviceInfoParams::new().min_control_temperature(min_control_temperature, temperature_unit)?)?; let request = TapoRequest::SetDeviceInfo(Box::new(TapoParams::new(json))); self.hub_handler @@ -88,8 +90,9 @@ impl<'h> KE100Handler<'h> { /// # Arguments /// /// * `max_control_temperature` - pub async fn set_max_control_temperature(&self, max_control_temperature: u8) -> Result<(), Error> { - let json = serde_json::to_value(TrvSetDeviceInfoParams::new().max_control_temperature(max_control_temperature)?)?; + /// * `temperature_unit` - enum with one value: Celsius + pub async fn set_max_control_temperature(&self, max_control_temperature: u8, temperature_unit: TemperatureUnit) -> Result<(), Error> { + let json = serde_json::to_value(TrvSetDeviceInfoParams::new().max_control_temperature(max_control_temperature, temperature_unit)?)?; let request = TapoRequest::SetDeviceInfo(Box::new(TapoParams::new(json))); self.hub_handler @@ -135,9 +138,10 @@ impl<'h> KE100Handler<'h> { /// /// # Arguments /// - /// * `temperature_offset` - between 5 and 30 - pub async fn set_temperature_offset(&self, temperature_offset: i8) -> Result<(), Error> { - let json = serde_json::to_value(TrvSetDeviceInfoParams::new().temperature_offset(temperature_offset)?)?; + /// * `temperature_offset` - between -10 and 10 + /// * `temperature_unit` - enum with one value: Celsius + pub async fn set_temperature_offset(&self, temperature_offset: i8, temperature_unit: TemperatureUnit) -> Result<(), Error> { + let json = serde_json::to_value(TrvSetDeviceInfoParams::new().temperature_offset(temperature_offset, temperature_unit)?)?; let request = TapoRequest::SetDeviceInfo(Box::new(TapoParams::new(json))); self.hub_handler diff --git a/tapo/src/requests/set_device_info/trv.rs b/tapo/src/requests/set_device_info/trv.rs index ab09490..a13448e 100644 --- a/tapo/src/requests/set_device_info/trv.rs +++ b/tapo/src/requests/set_device_info/trv.rs @@ -2,6 +2,8 @@ use serde::Serialize; use crate::error::Error; +use crate::responses::TemperatureUnit; + #[derive(Debug, Default, Serialize)] pub(crate) struct TrvSetDeviceInfoParams { #[serde(skip_serializing_if = "Option::is_none", rename = "target_temp")] @@ -18,11 +20,14 @@ pub(crate) struct TrvSetDeviceInfoParams { min_control_temperature: Option, #[serde(skip_serializing_if = "Option::is_none", rename = "max_control_temp")] max_control_temperature: Option, + #[serde(skip_serializing_if = "Option::is_none", rename = "temp_unit")] + temperature_unit: Option, } impl TrvSetDeviceInfoParams { - pub fn target_temperature(mut self, value: u8) -> Result { + pub fn target_temperature(mut self, value: u8, unit: TemperatureUnit) -> Result { self.target_temperature = Some(value); + self.temperature_unit = Some(unit); self.validate() } pub fn frost_protection_on(mut self, value: bool) -> Result { @@ -33,16 +38,19 @@ impl TrvSetDeviceInfoParams { self.child_protection = Some(value); self.validate() } - pub fn temperature_offset(mut self, value: i8) -> Result { + pub fn temperature_offset(mut self, value: i8, unit: TemperatureUnit) -> Result { self.temperature_offset = Some(value); + self.temperature_unit = Some(unit); self.validate() } - pub fn min_control_temperature(mut self, value: u8) -> Result { + pub fn min_control_temperature(mut self, value: u8, unit: TemperatureUnit) -> Result { self.min_control_temperature = Some(value); + self.temperature_unit = Some(unit); self.validate() } - pub fn max_control_temperature(mut self, value: u8) -> Result { + pub fn max_control_temperature(mut self, value: u8, unit: TemperatureUnit) -> Result { self.max_control_temperature = Some(value); + self.temperature_unit = Some(unit); self.validate() } } @@ -57,6 +65,7 @@ impl TrvSetDeviceInfoParams { min_temperature: None, min_control_temperature: None, max_control_temperature: None, + temperature_unit: None, } } diff --git a/tapo/src/responses.rs b/tapo/src/responses.rs index 43b86a3..df68dc7 100644 --- a/tapo/src/responses.rs +++ b/tapo/src/responses.rs @@ -14,7 +14,6 @@ mod tapo_response; mod tapo_result; mod token_result; mod trigger_logs_result; -mod temperature_unit; pub use child_device_list_result::*; pub use current_power_result::*; @@ -24,7 +23,6 @@ pub use device_usage_result::*; pub use energy_data_result::*; pub use energy_usage_result::*; pub use trigger_logs_result::*; -pub use temperature_unit::*; pub(crate) use control_child_result::*; pub(crate) use decodable_result_ext::*; diff --git a/tapo/src/responses/child_device_list_result/ke100_result.rs b/tapo/src/responses/child_device_list_result/ke100_result.rs index 87b7a79..cffc219 100644 --- a/tapo/src/responses/child_device_list_result/ke100_result.rs +++ b/tapo/src/responses/child_device_list_result/ke100_result.rs @@ -1,8 +1,17 @@ use serde::{Deserialize, Serialize}; use crate::error::Error; -use crate::responses::{decode_value, DecodableResultExt, Status, TapoResponseExt, TemperatureUnit}; +use crate::responses::{decode_value, DecodableResultExt, Status, TapoResponseExt}; +/// Temperature unit. +/// Currently only "Celsius" is supported by KE100 +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +#[allow(missing_docs)] +pub enum TemperatureUnit { + Celsius, + +} /// KE100 TRV. /// diff --git a/tapo/src/responses/child_device_list_result/t31x_result.rs b/tapo/src/responses/child_device_list_result/t31x_result.rs index aa8eae5..dff3583 100644 --- a/tapo/src/responses/child_device_list_result/t31x_result.rs +++ b/tapo/src/responses/child_device_list_result/t31x_result.rs @@ -3,7 +3,16 @@ use itertools::izip; use serde::{Deserialize, Serialize}; use crate::error::Error; -use crate::responses::{decode_value, DecodableResultExt, Status, TapoResponseExt, TemperatureUnit}; +use crate::responses::{decode_value, DecodableResultExt, Status, TapoResponseExt}; + +/// Temperature unit. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +#[allow(missing_docs)] +pub enum TemperatureUnit { + Celsius, + Fahrenheit, +} /// T310/T315 temperature & humidity sensor. /// diff --git a/tapo/src/responses/temperature_unit.rs b/tapo/src/responses/temperature_unit.rs deleted file mode 100644 index 30beeb8..0000000 --- a/tapo/src/responses/temperature_unit.rs +++ /dev/null @@ -1,10 +0,0 @@ -use serde::{Deserialize,Serialize}; - -/// Temperature unit. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -#[allow(missing_docs)] -pub enum TemperatureUnit { - Celsius, - Fahrenheit, -} \ No newline at end of file From 410f6f820bf991d8161e152bac2f02621fdbcfa9 Mon Sep 17 00:00:00 2001 From: pwoerndle <21156233+pwoerndle@users.noreply.github.com> Date: Thu, 23 Nov 2023 17:53:05 +0000 Subject: [PATCH 13/16] Add KE100 child support in H100 example --- tapo/examples/tapo_h100.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tapo/examples/tapo_h100.rs b/tapo/examples/tapo_h100.rs index 40818ea..daadfa8 100644 --- a/tapo/examples/tapo_h100.rs +++ b/tapo/examples/tapo_h100.rs @@ -85,6 +85,17 @@ async fn main() -> Result<(), Box> { temperature_humidity_records.records.first() ); } + ChildDeviceResult::KE100(device) => { + info!( + "Found KE100 child device with nickname: {}, id: {}, current temperature: {} {:?} and target temperature: {} {:?}.", + device.nickname, + device.device_id, + device.current_temperature, + device.temperature_unit, + device.target_temperature, + device.temperature_unit, + ); + } _ => { info!("Found unsupported device.") } From 07cf962151f38b7eca6bb95f94d2f68dcc0eb6bb Mon Sep 17 00:00:00 2001 From: Mihai Dinculescu Date: Thu, 23 Nov 2023 20:34:52 +0000 Subject: [PATCH 14/16] Minor tweaks --- tapo/examples/tapo_h100.rs | 22 ++--- tapo/examples/tapo_ke100.rs | 11 ++- tapo/src/api/child_devices.rs | 4 +- tapo/src/api/child_devices/ke100_handler.rs | 84 +++++++++++++------ tapo/src/api/child_devices/s200b_handler.rs | 2 +- tapo/src/api/child_devices/t300_handler.rs | 9 +- tapo/src/api/hub_handler.rs | 2 +- tapo/src/requests/set_device_info/trv.rs | 50 ++++++----- .../src/responses/child_device_list_result.rs | 14 ++-- .../child_device_list_result/ke100_result.rs | 36 ++++---- 10 files changed, 138 insertions(+), 96 deletions(-) diff --git a/tapo/examples/tapo_h100.rs b/tapo/examples/tapo_h100.rs index daadfa8..47fe529 100644 --- a/tapo/examples/tapo_h100.rs +++ b/tapo/examples/tapo_h100.rs @@ -31,6 +31,17 @@ async fn main() -> Result<(), Box> { for child in child_device_list { match child { + ChildDeviceResult::KE100(device) => { + info!( + "Found KE100 child device with nickname: {}, id: {}, current temperature: {} {:?} and target temperature: {} {:?}.", + device.nickname, + device.device_id, + device.current_temperature, + device.temperature_unit, + device.target_temperature, + device.temperature_unit, + ); + } ChildDeviceResult::S200B(device) => { let s200b = hub.s200b(&device.device_id); let trigger_logs = s200b.get_trigger_logs(5, 0).await?; @@ -85,17 +96,6 @@ async fn main() -> Result<(), Box> { temperature_humidity_records.records.first() ); } - ChildDeviceResult::KE100(device) => { - info!( - "Found KE100 child device with nickname: {}, id: {}, current temperature: {} {:?} and target temperature: {} {:?}.", - device.nickname, - device.device_id, - device.current_temperature, - device.temperature_unit, - device.target_temperature, - device.temperature_unit, - ); - } _ => { info!("Found unsupported device.") } diff --git a/tapo/examples/tapo_ke100.rs b/tapo/examples/tapo_ke100.rs index fd245f9..3f5f26b 100644 --- a/tapo/examples/tapo_ke100.rs +++ b/tapo/examples/tapo_ke100.rs @@ -2,6 +2,7 @@ use std::env; use log::{info, LevelFilter}; +use tapo::responses::TemperatureUnitKE100; use tapo::ApiClient; #[tokio::main] @@ -18,7 +19,8 @@ async fn main() -> Result<(), Box> { let tapo_username = env::var("TAPO_USERNAME")?; let tapo_password = env::var("TAPO_PASSWORD")?; let ip_address = env::var("IP_ADDRESS")?; - // ID of the KE100 device. Can be obtained from executing `get_child_device_component_list()`` on the hub device. + // ID of the KE100 device. + // Can be obtained from executing `get_child_device_component_list()` on the hub device. let device_id = env::var("DEVICE_ID")?; let target_temperature: u8 = env::var("TARGET_TEMPERATURE")?.parse()?; @@ -34,8 +36,11 @@ async fn main() -> Result<(), Box> { info!("Device info: {device_info:?}"); // Set temperature on target device and set temperature unit to Celsius. - // KE100 currently only supports Celsius as temperature unti. - device.set_target_temperature(target_temperature, tapo::responses::TemperatureUnit::Celsius).await?; + // KE100 currently only supports Celsius as temperature unit. + info!("Setting target temperature to {target_temperature} degrees Celsius..."); + device + .set_target_temperature(target_temperature, TemperatureUnitKE100::Celsius) + .await?; // Get the device info of the child device let device_info = device.get_device_info().await?; diff --git a/tapo/src/api/child_devices.rs b/tapo/src/api/child_devices.rs index da4a2f5..fc0c29d 100644 --- a/tapo/src/api/child_devices.rs +++ b/tapo/src/api/child_devices.rs @@ -1,13 +1,13 @@ +mod ke100_handler; mod s200b_handler; mod t100_handler; mod t110_handler; mod t300_handler; mod t31x_handler; -mod ke100_handler; +pub use ke100_handler::*; pub use s200b_handler::*; pub use t100_handler::*; pub use t110_handler::*; pub use t300_handler::*; pub use t31x_handler::*; -pub use ke100_handler::*; diff --git a/tapo/src/api/child_devices/ke100_handler.rs b/tapo/src/api/child_devices/ke100_handler.rs index 1fbafa7..61aa9b0 100644 --- a/tapo/src/api/child_devices/ke100_handler.rs +++ b/tapo/src/api/child_devices/ke100_handler.rs @@ -1,9 +1,9 @@ use crate::api::HubHandler; -use crate::error::{Error,TapoResponseError}; +use crate::error::{Error, TapoResponseError}; use crate::requests::{EmptyParams, TapoParams, TapoRequest, TrvSetDeviceInfoParams}; -use crate::responses::{KE100Result, TemperatureUnit, DecodableResultExt}; +use crate::responses::{DecodableResultExt, KE100Result, TemperatureUnitKE100}; -/// Handler for the [KE100] device. +/// Handler for the [KE100](https://www.tp-link.com/en/search/?q=KE100) devices. pub struct KE100Handler<'h> { hub_handler: &'h HubHandler, device_id: String, @@ -40,26 +40,33 @@ impl<'h> KE100Handler<'h> { .ok_or_else(|| Error::Tapo(TapoResponseError::EmptyResult)) } - /// Sets the *target temperature*. /// /// # Arguments /// - /// * `target_temperature` - between min_control_temp and max_control_temp - /// * `temperature_unit` - enum with one value: Celsius - pub async fn set_target_temperature(&self, target_temperature: u8, temperature_unit: TemperatureUnit) -> Result<(), Error> { - + /// * `target_temperature` - between `min_control_temperature` and `max_control_temperature` + /// * `temperature_unit` + pub async fn set_target_temperature( + &self, + target_temperature: u8, + temperature_unit: TemperatureUnitKE100, + ) -> Result<(), Error> { //let control_range = self.get_control_range().await?; let device_info = self.get_device_info().await?; - if target_temperature < device_info.min_control_temperature || target_temperature > device_info.max_control_temperature { + if target_temperature < device_info.min_control_temperature + || target_temperature > device_info.max_control_temperature + { return Err(Error::Validation { field: "target_temperature".to_string(), - message: format!("Target temperature must be between {} (min_control_temp) and {} (max_control_temp)", device_info.min_control_temperature, device_info.max_control_temperature), + message: format!("Target temperature must be between {} (min_control_temperature) and {} (max_control_temperature)", device_info.min_control_temperature, device_info.max_control_temperature), }); } - let json = serde_json::to_value(TrvSetDeviceInfoParams::new().target_temperature(target_temperature, temperature_unit)?)?; + let json = serde_json::to_value( + TrvSetDeviceInfoParams::new() + .target_temperature(target_temperature, temperature_unit)?, + )?; let request = TapoRequest::SetDeviceInfo(Box::new(TapoParams::new(json))); self.hub_handler @@ -74,9 +81,16 @@ impl<'h> KE100Handler<'h> { /// # Arguments /// /// * `min_control_temperature` - /// * `temperature_unit` - enum with one value: Celsius - pub async fn set_min_control_temperature(&self, min_control_temperature: u8, temperature_unit: TemperatureUnit) -> Result<(), Error> { - let json = serde_json::to_value(TrvSetDeviceInfoParams::new().min_control_temperature(min_control_temperature, temperature_unit)?)?; + /// * `temperature_unit` + pub async fn set_min_control_temperature( + &self, + min_control_temperature: u8, + temperature_unit: TemperatureUnitKE100, + ) -> Result<(), Error> { + let json = serde_json::to_value( + TrvSetDeviceInfoParams::new() + .min_control_temperature(min_control_temperature, temperature_unit)?, + )?; let request = TapoRequest::SetDeviceInfo(Box::new(TapoParams::new(json))); self.hub_handler @@ -91,9 +105,16 @@ impl<'h> KE100Handler<'h> { /// # Arguments /// /// * `max_control_temperature` - /// * `temperature_unit` - enum with one value: Celsius - pub async fn set_max_control_temperature(&self, max_control_temperature: u8, temperature_unit: TemperatureUnit) -> Result<(), Error> { - let json = serde_json::to_value(TrvSetDeviceInfoParams::new().max_control_temperature(max_control_temperature, temperature_unit)?)?; + /// * `temperature_unit` + pub async fn set_max_control_temperature( + &self, + max_control_temperature: u8, + temperature_unit: TemperatureUnitKE100, + ) -> Result<(), Error> { + let json = serde_json::to_value( + TrvSetDeviceInfoParams::new() + .max_control_temperature(max_control_temperature, temperature_unit)?, + )?; let request = TapoRequest::SetDeviceInfo(Box::new(TapoParams::new(json))); self.hub_handler @@ -103,15 +124,17 @@ impl<'h> KE100Handler<'h> { Ok(()) } - /// Sets frost protection on the device to *on* or *off*. + /// Sets *frost protection* on the device to *on* or *off*. /// /// # Arguments /// /// * `frost_protection_on` - true/false pub async fn set_frost_protection(&self, frost_protection_on: bool) -> Result<(), Error> { - let json = serde_json::to_value(TrvSetDeviceInfoParams::new().frost_protection_on(frost_protection_on)?)?; + let json = serde_json::to_value( + TrvSetDeviceInfoParams::new().frost_protection_on(frost_protection_on)?, + )?; let request = TapoRequest::SetDeviceInfo(Box::new(TapoParams::new(json))); - + self.hub_handler .control_child::(self.device_id.clone(), request) .await?; @@ -119,15 +142,17 @@ impl<'h> KE100Handler<'h> { Ok(()) } - /// Sets child protection on the device to *on* or *off*. + /// Sets *child protection* on the device to *on* or *off*. /// /// # Arguments /// /// * `child_protection_on` - true/false pub async fn set_child_protection(&self, child_protection_on: bool) -> Result<(), Error> { - let json = serde_json::to_value(TrvSetDeviceInfoParams::new().child_protection(child_protection_on)?)?; + let json = serde_json::to_value( + TrvSetDeviceInfoParams::new().child_protection(child_protection_on)?, + )?; let request = TapoRequest::SetDeviceInfo(Box::new(TapoParams::new(json))); - + self.hub_handler .control_child::(self.device_id.clone(), request) .await?; @@ -140,9 +165,16 @@ impl<'h> KE100Handler<'h> { /// # Arguments /// /// * `temperature_offset` - between -10 and 10 - /// * `temperature_unit` - enum with one value: Celsius - pub async fn set_temperature_offset(&self, temperature_offset: i8, temperature_unit: TemperatureUnit) -> Result<(), Error> { - let json = serde_json::to_value(TrvSetDeviceInfoParams::new().temperature_offset(temperature_offset, temperature_unit)?)?; + /// * `temperature_unit` + pub async fn set_temperature_offset( + &self, + temperature_offset: i8, + temperature_unit: TemperatureUnitKE100, + ) -> Result<(), Error> { + let json = serde_json::to_value( + TrvSetDeviceInfoParams::new() + .temperature_offset(temperature_offset, temperature_unit)?, + )?; let request = TapoRequest::SetDeviceInfo(Box::new(TapoParams::new(json))); self.hub_handler diff --git a/tapo/src/api/child_devices/s200b_handler.rs b/tapo/src/api/child_devices/s200b_handler.rs index a8629c7..651f298 100644 --- a/tapo/src/api/child_devices/s200b_handler.rs +++ b/tapo/src/api/child_devices/s200b_handler.rs @@ -1,5 +1,5 @@ use crate::api::HubHandler; -use crate::error::{Error,TapoResponseError}; +use crate::error::{Error, TapoResponseError}; use crate::requests::{EmptyParams, GetTriggerLogsParams, TapoParams, TapoRequest}; use crate::responses::{DecodableResultExt, S200BResult}; use crate::responses::{S200BLog, TriggerLogsResult}; diff --git a/tapo/src/api/child_devices/t300_handler.rs b/tapo/src/api/child_devices/t300_handler.rs index 5aed67c..0983e2a 100644 --- a/tapo/src/api/child_devices/t300_handler.rs +++ b/tapo/src/api/child_devices/t300_handler.rs @@ -1,5 +1,5 @@ use crate::api::HubHandler; -use crate::error::{Error,TapoResponseError}; +use crate::error::{Error, TapoResponseError}; use crate::requests::{EmptyParams, GetTriggerLogsParams, TapoParams, TapoRequest}; use crate::responses::{DecodableResultExt, T300Result}; use crate::responses::{T300Log, TriggerLogsResult}; @@ -46,12 +46,9 @@ impl<'h> T300Handler<'h> { let child_params = GetTriggerLogsParams::new(page_size, start_id); let child_request = TapoRequest::GetTriggerLogs(Box::new(TapoParams::new(child_params))); - let result = self - .hub_handler + self.hub_handler .control_child::>(self.device_id.clone(), child_request) .await? - .ok_or_else(|| Error::Tapo(TapoResponseError::EmptyResult)); - - Ok(result?) + .ok_or_else(|| Error::Tapo(TapoResponseError::EmptyResult)) } } diff --git a/tapo/src/api/hub_handler.rs b/tapo/src/api/hub_handler.rs index 95d5919..1536dc9 100644 --- a/tapo/src/api/hub_handler.rs +++ b/tapo/src/api/hub_handler.rs @@ -3,7 +3,7 @@ use std::fmt; use serde::de::DeserializeOwned; use crate::api::ApiClient; -use crate::api::{S200BHandler, T100Handler, T110Handler, T300Handler, T31XHandler, KE100Handler}; +use crate::api::{KE100Handler, S200BHandler, T100Handler, T110Handler, T300Handler, T31XHandler}; use crate::error::Error; use crate::requests::TapoRequest; use crate::responses::{ diff --git a/tapo/src/requests/set_device_info/trv.rs b/tapo/src/requests/set_device_info/trv.rs index a13448e..52df4c5 100644 --- a/tapo/src/requests/set_device_info/trv.rs +++ b/tapo/src/requests/set_device_info/trv.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::error::Error; -use crate::responses::TemperatureUnit; +use crate::responses::TemperatureUnitKE100; #[derive(Debug, Default, Serialize)] pub(crate) struct TrvSetDeviceInfoParams { @@ -21,11 +21,15 @@ pub(crate) struct TrvSetDeviceInfoParams { #[serde(skip_serializing_if = "Option::is_none", rename = "max_control_temp")] max_control_temperature: Option, #[serde(skip_serializing_if = "Option::is_none", rename = "temp_unit")] - temperature_unit: Option, + temperature_unit: Option, } impl TrvSetDeviceInfoParams { - pub fn target_temperature(mut self, value: u8, unit: TemperatureUnit) -> Result { + pub fn target_temperature( + mut self, + value: u8, + unit: TemperatureUnitKE100, + ) -> Result { self.target_temperature = Some(value); self.temperature_unit = Some(unit); self.validate() @@ -38,17 +42,29 @@ impl TrvSetDeviceInfoParams { self.child_protection = Some(value); self.validate() } - pub fn temperature_offset(mut self, value: i8, unit: TemperatureUnit) -> Result { + pub fn temperature_offset( + mut self, + value: i8, + unit: TemperatureUnitKE100, + ) -> Result { self.temperature_offset = Some(value); self.temperature_unit = Some(unit); self.validate() } - pub fn min_control_temperature(mut self, value: u8, unit: TemperatureUnit) -> Result { + pub fn min_control_temperature( + mut self, + value: u8, + unit: TemperatureUnitKE100, + ) -> Result { self.min_control_temperature = Some(value); self.temperature_unit = Some(unit); self.validate() } - pub fn max_control_temperature(mut self, value: u8, unit: TemperatureUnit) -> Result { + pub fn max_control_temperature( + mut self, + value: u8, + unit: TemperatureUnitKE100, + ) -> Result { self.max_control_temperature = Some(value); self.temperature_unit = Some(unit); self.validate() @@ -57,28 +73,18 @@ impl TrvSetDeviceInfoParams { impl TrvSetDeviceInfoParams { pub(crate) fn new() -> Self { - Self { - target_temperature: None, - frost_protection_on: None, - child_protection: None, - temperature_offset: None, - min_temperature: None, - min_control_temperature: None, - max_control_temperature: None, - temperature_unit: None, - } + Self::default() } - + pub fn validate(self) -> Result { - if let Some(temp_offset) = self.temperature_offset { - if temp_offset < -10 || temp_offset> 10 { + if let Some(temperature_offset) = self.temperature_offset { + if !(-10..=10).contains(&temperature_offset) { return Err(Error::Validation { - field: "temp_offset".to_string(), + field: "temperature_offset".to_string(), message: "must be between -10 and 10".to_string(), - }); } - } + } Ok(self) } } diff --git a/tapo/src/responses/child_device_list_result.rs b/tapo/src/responses/child_device_list_result.rs index f6cf680..1380b4d 100644 --- a/tapo/src/responses/child_device_list_result.rs +++ b/tapo/src/responses/child_device_list_result.rs @@ -1,16 +1,16 @@ +mod ke100_result; mod s200b_result; mod t100_result; mod t110_result; mod t300_result; mod t31x_result; -mod ke100_result; +pub use ke100_result::*; pub use s200b_result::*; pub use t100_result::*; pub use t110_result::*; pub use t300_result::*; pub use t31x_result::*; -pub use ke100_result::*; use serde::{Deserialize, Serialize}; @@ -52,6 +52,8 @@ pub enum Status { #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "model")] pub enum ChildDeviceResult { + /// KE100 thermostatic radiator valve (TRV). + KE100(Box), /// S200B button switch. S200B(Box), /// T100 motion sensor. @@ -64,8 +66,6 @@ pub enum ChildDeviceResult { T310(Box), /// T315 temperature & humidity sensor. T315(Box), - /// KE100 thermostatic radiator valve (TRV). - KE100(Box), /// Catch-all for currently unsupported devices. /// Please open an issue if you need support for a new device. #[serde(other)] @@ -75,6 +75,9 @@ pub enum ChildDeviceResult { impl DecodableResultExt for ChildDeviceResult { fn decode(self) -> Result { match self { + ChildDeviceResult::KE100(device) => { + Ok(ChildDeviceResult::KE100(Box::new(device.decode()?))) + } ChildDeviceResult::S200B(device) => { Ok(ChildDeviceResult::S200B(Box::new(device.decode()?))) } @@ -93,9 +96,6 @@ impl DecodableResultExt for ChildDeviceResult { ChildDeviceResult::T315(device) => { Ok(ChildDeviceResult::T315(Box::new(device.decode()?))) } - ChildDeviceResult::KE100(device) => { - Ok(ChildDeviceResult::KE100(Box::new(device.decode()?))) - } ChildDeviceResult::Other => Ok(ChildDeviceResult::Other), } } diff --git a/tapo/src/responses/child_device_list_result/ke100_result.rs b/tapo/src/responses/child_device_list_result/ke100_result.rs index 601cd85..f89a70e 100644 --- a/tapo/src/responses/child_device_list_result/ke100_result.rs +++ b/tapo/src/responses/child_device_list_result/ke100_result.rs @@ -3,19 +3,20 @@ use serde::{Deserialize, Serialize}; use crate::error::Error; use crate::responses::{decode_value, DecodableResultExt, Status, TapoResponseExt}; -/// Temperature unit. -/// Currently only "Celsius" is supported by KE100 +/// Temperature unit for KE100 devices. +/// Currently *Celsius* is the only unit supported by KE100. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[allow(missing_docs)] -pub enum TemperatureUnit { +pub enum TemperatureUnitKE100 { Celsius, - } /// KE100 TRV. /// -/// Specific properties: `detected`. +/// Specific properties: `temperature_unit`, `current_temperature`, `target_temperature`, +/// `min_control_temperature, `max_control_temperature`, `temperature_offset`, +/// `child_protection_on`, `frost_protection_on`. #[derive(Debug, Clone, Serialize, Deserialize)] #[allow(missing_docs)] pub struct KE100Result { @@ -23,37 +24,38 @@ pub struct KE100Result { pub avatar: String, pub bind_count: u32, pub category: String, + #[serde(rename = "child_protection")] + pub child_protection_on: bool, + #[serde(rename = "current_temp")] + pub current_temperature: f32, pub device_id: String, + pub frost_protection_on: bool, pub fw_ver: String, pub hw_id: String, pub hw_ver: String, pub jamming_rssi: i16, pub jamming_signal_level: u8, + pub location: String, pub mac: String, + #[serde(rename = "max_control_temp")] + pub max_control_temperature: u8, + #[serde(rename = "min_control_temp")] + pub min_control_temperature: u8, pub nickname: String, pub oem_id: String, pub parent_device_id: String, + pub r#type: String, pub region: String, pub rssi: i16, pub signal_level: u8, pub specs: String, pub status: Status, - pub r#type: String, - #[serde(rename = "temp_unit")] - pub temperature_unit: TemperatureUnit, - #[serde(rename = "current_temp")] - pub current_temperature: f32, #[serde(rename = "target_temp")] pub target_temperature: f32, - #[serde(rename = "min_control_temp")] - pub min_control_temperature: u8, - #[serde(rename = "max_control_temp")] - pub max_control_temperature: u8, - pub frost_protection_on: bool, - pub location: String, #[serde(rename = "temp_offset")] pub temperature_offset: i8, - pub child_protection: bool, + #[serde(rename = "temp_unit")] + pub temperature_unit: TemperatureUnitKE100, } impl TapoResponseExt for KE100Result {} From 81dde27d1b831db251f34301e432a47ce1eb9b15 Mon Sep 17 00:00:00 2001 From: Mihai Dinculescu Date: Thu, 23 Nov 2023 20:41:45 +0000 Subject: [PATCH 15/16] Update docs --- README.md | 19 +++++++++++++------ tapo/Cargo.toml | 2 +- tapo/src/lib.rs | 2 +- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index d8f5ca1..442b843 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ [![PyPI][pypi_badge]][pypi] [![Python][pypi_versions_badge]][pypi] [![PyPI][pypi_downloads_badge]][pypi]\ -Unofficial Tapo API Client. Works with TP-Link Tapo smart devices. Tested with light bulbs (L510, L520, L530, L610, L630), light strips (L900, L920, L930), plugs (P100, P105, P110, P115), hubs (H100), switches (S200B) and sensors (T100, T110, T300, T310, T315). +Unofficial Tapo API Client. Works with TP-Link Tapo smart devices. Tested with light bulbs (L510, L520, L530, L610, L630), light strips (L900, L920, L930), plugs (P100, P105, P110, P115), hubs (H100), switches (S200B) and sensors (KE100, T100, T110, T300, T310, T315). [license_badge]: https://img.shields.io/crates/l/tapo.svg [license]: https://github.com/mihai-dinculescu/tapo/blob/main/LICENSE @@ -49,11 +49,18 @@ Unofficial Tapo API Client. Works with TP-Link Tapo smart devices. Tested with l ## Hub (H100) Support -| Feature | S200B | T100 | T110 | T300 | T310, T315 | -| -------------------------------- | ------: | ------: | ------: | ------: | ---------: | -| get_device_info \* | ✓ | ✓ | ✓ | ✓ | ✓ | -| get_temperature_humidity_records | | | | | ✓ | -| get_trigger_logs | ✓ | ✓ | ✓ | ✓ | | +| Feature | KE100 | S200B | T100 | T110 | T300 | T310, T315 | +| -------------------------------- | ------: | ------: | ------: | ------: | ------: | ---------: | +| get_device_info \* | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| get_device_info_json | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| get_temperature_humidity_records | | | | | | ✓ | +| get_trigger_logs | | ✓ | ✓ | ✓ | ✓ | | +| set_target_temperature | ✓ | | | | | | +| set_min_control_temperature | ✓ | | | | | | +| set_max_control_temperature | ✓ | | | | | | +| set_temperature_offset | ✓ | | | | | | +| set_frost_protection | ✓ | | | | | | +| set_child_protection | ✓ | | | | | | \* Obtained by calling `get_child_device_list` on the hub device or `get_device_info` on a child handler. diff --git a/tapo/Cargo.toml b/tapo/Cargo.toml index a683485..3b1987b 100644 --- a/tapo/Cargo.toml +++ b/tapo/Cargo.toml @@ -4,7 +4,7 @@ version = "0.7.5" edition = "2021" license = "MIT" authors = ["Mihai Dinculescu "] -description = "Unofficial Tapo API Client. Works with TP-Link Tapo smart devices. Tested with light bulbs (L510, L520, L530, L610, L630), light strips (L900, L920, L930), plugs (P100, P105, P110, P115), hubs (H100), switches (S200B) and sensors (T100, T110, T300, T310, T315)." +description = "Unofficial Tapo API Client. Works with TP-Link Tapo smart devices. Tested with light bulbs (L510, L520, L530, L610, L630), light strips (L900, L920, L930), plugs (P100, P105, P110, P115), hubs (H100), switches (S200B) and sensors (KE100, T100, T110, T300, T310, T315)." keywords = ["IOT", "tapo", "smart-home", "smart-bulb", "smart-plug"] categories = ["hardware-support", "embedded", "development-tools"] readme = "README.md" diff --git a/tapo/src/lib.rs b/tapo/src/lib.rs index dccf367..59ef0cd 100644 --- a/tapo/src/lib.rs +++ b/tapo/src/lib.rs @@ -3,7 +3,7 @@ //! Tapo API Client. //! //! Tested with light bulbs (L510, L520, L530, L610, L630), light strips (L900, L920, L930), -//! plugs (P100, P105, P110, P115), hubs (H100), switches (S200B) and sensors (T100, T110, T300, T310, T315). +//! plugs (P100, P105, P110, P115), hubs (H100), switches (S200B) and sensors (KE100, T100, T110, T300, T310, T315). //! //! # Example with L530 //! ```rust,no_run From f1ea8554701af6fcb8b9bbe91c8d48ae87b7dd09 Mon Sep 17 00:00:00 2001 From: Mihai Dinculescu Date: Sat, 25 Nov 2023 17:59:36 +0000 Subject: [PATCH 16/16] Tweaks --- tapo/examples/tapo_ke100.rs | 2 +- tapo/src/api/child_devices/ke100_handler.rs | 1 - tapo/src/api/child_devices/t31x_handler.rs | 4 ++-- tapo/src/responses/child_device_list_result/ke100_result.rs | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/tapo/examples/tapo_ke100.rs b/tapo/examples/tapo_ke100.rs index 3f5f26b..edeba1b 100644 --- a/tapo/examples/tapo_ke100.rs +++ b/tapo/examples/tapo_ke100.rs @@ -35,7 +35,7 @@ async fn main() -> Result<(), Box> { let device_info = device.get_device_info().await?; info!("Device info: {device_info:?}"); - // Set temperature on target device and set temperature unit to Celsius. + // Set target temperature. // KE100 currently only supports Celsius as temperature unit. info!("Setting target temperature to {target_temperature} degrees Celsius..."); device diff --git a/tapo/src/api/child_devices/ke100_handler.rs b/tapo/src/api/child_devices/ke100_handler.rs index 61aa9b0..e6e3316 100644 --- a/tapo/src/api/child_devices/ke100_handler.rs +++ b/tapo/src/api/child_devices/ke100_handler.rs @@ -51,7 +51,6 @@ impl<'h> KE100Handler<'h> { target_temperature: u8, temperature_unit: TemperatureUnitKE100, ) -> Result<(), Error> { - //let control_range = self.get_control_range().await?; let device_info = self.get_device_info().await?; if target_temperature < device_info.min_control_temperature diff --git a/tapo/src/api/child_devices/t31x_handler.rs b/tapo/src/api/child_devices/t31x_handler.rs index 3f3277c..931d251 100644 --- a/tapo/src/api/child_devices/t31x_handler.rs +++ b/tapo/src/api/child_devices/t31x_handler.rs @@ -42,8 +42,8 @@ impl<'h> T31XHandler<'h> { .hub_handler .control_child::(self.device_id.clone(), request) .await? - .ok_or_else(|| Error::Tapo(TapoResponseError::EmptyResult)); + .ok_or_else(|| Error::Tapo(TapoResponseError::EmptyResult))?; - Ok(result?.try_into()?) + Ok(result.try_into()?) } } diff --git a/tapo/src/responses/child_device_list_result/ke100_result.rs b/tapo/src/responses/child_device_list_result/ke100_result.rs index f89a70e..94258e2 100644 --- a/tapo/src/responses/child_device_list_result/ke100_result.rs +++ b/tapo/src/responses/child_device_list_result/ke100_result.rs @@ -12,7 +12,7 @@ pub enum TemperatureUnitKE100 { Celsius, } -/// KE100 TRV. +/// KE100 thermostatic radiator valve (TRV). /// /// Specific properties: `temperature_unit`, `current_temperature`, `target_temperature`, /// `min_control_temperature, `max_control_temperature`, `temperature_offset`,