-
-
Notifications
You must be signed in to change notification settings - Fork 44
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for the KE100 TRV devices
- Loading branch information
Showing
18 changed files
with
487 additions
and
30 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,7 +4,7 @@ version = "0.7.5" | |
edition = "2021" | ||
license = "MIT" | ||
authors = ["Mihai Dinculescu <[email protected]>"] | ||
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" | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
/// KE100 TRV Example | ||
use std::env; | ||
|
||
use log::{info, LevelFilter}; | ||
use tapo::responses::TemperatureUnitKE100; | ||
use tapo::ApiClient; | ||
|
||
#[tokio::main] | ||
async fn main() -> Result<(), Box<dyn std::error::Error>> { | ||
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()` on the hub device. | ||
let device_id = env::var("DEVICE_ID")?; | ||
let target_temperature: u8 = env::var("TARGET_TEMPERATURE")?.parse()?; | ||
|
||
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:?}"); | ||
|
||
// Set target temperature. | ||
// 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?; | ||
info!("Device info: {device_info:?}"); | ||
|
||
Ok(()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,185 @@ | ||
use crate::api::HubHandler; | ||
use crate::error::{Error, TapoResponseError}; | ||
use crate::requests::{EmptyParams, TapoParams, TapoRequest, TrvSetDeviceInfoParams}; | ||
use crate::responses::{DecodableResultExt, KE100Result, TemperatureUnitKE100}; | ||
|
||
/// 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, | ||
} | ||
|
||
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<KE100Result, Error> { | ||
let request = TapoRequest::GetDeviceInfo(TapoParams::new(EmptyParams)); | ||
|
||
self.hub_handler | ||
.control_child::<KE100Result>(self.device_id.clone(), request) | ||
.await? | ||
.ok_or_else(|| Error::Tapo(TapoResponseError::EmptyResult)) | ||
.map(|result| result.decode())? | ||
} | ||
|
||
/// 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<serde_json::Value, 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)) | ||
} | ||
|
||
/// Sets the *target temperature*. | ||
/// | ||
/// # Arguments | ||
/// | ||
/// * `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 device_info = self.get_device_info().await?; | ||
|
||
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_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 request = TapoRequest::SetDeviceInfo(Box::new(TapoParams::new(json))); | ||
|
||
self.hub_handler | ||
.control_child::<serde_json::Value>(self.device_id.clone(), request) | ||
.await?; | ||
|
||
Ok(()) | ||
} | ||
|
||
/// Sets the *minimal control temperature*. | ||
/// | ||
/// # Arguments | ||
/// | ||
/// * `min_control_temperature` | ||
/// * `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 | ||
.control_child::<serde_json::Value>(self.device_id.clone(), request) | ||
.await?; | ||
|
||
Ok(()) | ||
} | ||
|
||
/// Sets the *maximum control temperature*. | ||
/// | ||
/// # Arguments | ||
/// | ||
/// * `max_control_temperature` | ||
/// * `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 | ||
.control_child::<serde_json::Value>(self.device_id.clone(), request) | ||
.await?; | ||
|
||
Ok(()) | ||
} | ||
|
||
/// 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 request = TapoRequest::SetDeviceInfo(Box::new(TapoParams::new(json))); | ||
|
||
self.hub_handler | ||
.control_child::<serde_json::Value>(self.device_id.clone(), request) | ||
.await?; | ||
|
||
Ok(()) | ||
} | ||
|
||
/// 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))); | ||
|
||
self.hub_handler | ||
.control_child::<serde_json::Value>(self.device_id.clone(), request) | ||
.await?; | ||
|
||
Ok(()) | ||
} | ||
|
||
/// Sets the *temperature offset*. | ||
/// | ||
/// # Arguments | ||
/// | ||
/// * `temperature_offset` - between -10 and 10 | ||
/// * `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 | ||
.control_child::<serde_json::Value>(self.device_id.clone(), request) | ||
.await?; | ||
|
||
Ok(()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.