Skip to content

Commit

Permalink
Add the set API
Browse files Browse the repository at this point in the history
  • Loading branch information
mihai-dinculescu committed Nov 20, 2022
1 parent 6bfafe2 commit df80f63
Show file tree
Hide file tree
Showing 13 changed files with 266 additions and 76 deletions.
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,13 @@ file. This change log follows the conventions of

## [Unreleased]

### Added
- The `set` API allows multiple properties to be set in a single request for the *L510* and *L530* devices.

### Changed

- `GenericDeviceInfoResult::on_time` has been changed from `u64` to `Option<u64>` because some devices (like L930) do not provide this field.
- `tapo::Color` has been moved to `tapo::requests::Color`.
- `GenericDeviceInfoResult::on_time` has been changed from `u64` to `Option<u64>` because some devices (like *L930*) do not provide this field.
- All response structs have been moved under `tapo::responses`.
- The docs have been improved.

Expand Down
16 changes: 8 additions & 8 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,4 @@ time = { version = "0.3", features = ["parsing"] }

[dev-dependencies]
pretty_env_logger = "0.4"
tokio = { version = "1.21", features = ["rt-multi-thread", "macros"] }
tokio = { version = "1.22", features = ["rt-multi-thread", "macros"] }
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ Unofficial Tapo API Client. Works with TP-Link Tapo smart devices. Tested with l
| set_color | &check; | | | | |
| set_hue_saturation | &check; | | | | |
| set_color_temperature | &check; | | | | |
| set() API \* | &check; | &check; | | | |

\* The `set()` API allows multiple properties to be set in a single request.

## Examples

Expand Down
6 changes: 6 additions & 0 deletions examples/tapo_l510.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
info!("Waiting 2 seconds...");
thread::sleep(Duration::from_secs(2));

info!("Using the `set` API to set multiple properties in a single request...");
device.set().on().brightness(50)?.send().await?;

info!("Waiting 2 seconds...");
thread::sleep(Duration::from_secs(2));

info!("Turning device off...");
device.off().await?;

Expand Down
14 changes: 13 additions & 1 deletion examples/tapo_l530.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
use std::{env, thread, time::Duration};

use log::{info, LevelFilter};
use tapo::{ApiClient, Color, L530};
use tapo::{requests::Color, ApiClient, L530};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
Expand Down Expand Up @@ -45,6 +45,18 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
info!("Waiting 2 seconds...");
thread::sleep(Duration::from_secs(2));

info!("Using the `set` API to set multiple properties in a single request...");
device
.set()
.on()
.brightness(50)?
.color(Color::HotPink)?
.send()
.await?;

info!("Waiting 2 seconds...");
thread::sleep(Duration::from_secs(2));

info!("Turning device off...");
device.off().await?;

Expand Down
38 changes: 35 additions & 3 deletions src/api/l510.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,46 @@ use crate::responses::L510DeviceInfoResult;

/// The functionality of [`crate::ApiClient<L510>`] that applies to [`crate::L510`]. Superset of [`crate::ApiClient<D>`].
impl ApiClient<L510> {
/// Sets the light bulb's *brightness*.
/// Returns a [`crate::requests::L510SetDeviceInfoParams`] builder that allows multiple properties to be set in a single request.
/// `send` must be called at the end to apply the changes.
///
/// # Example
/// ```rust,no_run
/// use tapo::{ApiClient, L510};
///
/// #[tokio::main]
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let device = ApiClient::<L510>::new(
/// "192.168.1.100".to_string(),
/// "[email protected]".to_string(),
/// "tapo-password".to_string(),
/// true,
/// ).await?;
///
/// device
/// .set()
/// .on()
/// .brightness(50)?
/// .send()
/// .await?;
///
/// Ok(())
/// }
/// ```
pub fn set(&self) -> L510SetDeviceInfoParams {
L510SetDeviceInfoParams::new(self)
}

/// Sets the *brightness*.
///
/// # Arguments
///
/// * `brightness` - *u8*; between 1 and 100
pub async fn set_brightness(&self, brightness: u8) -> anyhow::Result<()> {
let json = serde_json::to_value(&L510SetDeviceInfoParams::brightness(brightness)?)?;
self.set_device_info_internal(json).await
L510SetDeviceInfoParams::new(self)
.brightness(brightness)?
.send()
.await
}

/// Gets *device info* as [`crate::responses::L510DeviceInfoResult`].
Expand Down
73 changes: 55 additions & 18 deletions src/api/l530.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,48 +5,85 @@ use crate::responses::L530DeviceInfoResult;

/// The functionality of [`crate::ApiClient<L530>`] that applies to [`crate::L530`]. Superset of [`crate::ApiClient<D>`].
impl ApiClient<L530> {
/// Sets the light bulb's *color*.
/// Returns a [`crate::requests::L530SetDeviceInfoParams`] builder that allows multiple properties to be set in a single request.
/// `send` must be called at the end to apply the changes.
///
/// # Arguments
/// # Example
/// ```rust,no_run
/// use tapo::{ApiClient, L530};
/// use tapo::requests::Color;
///
/// * `color` - [crate::Color]
pub async fn set_color(&self, color: Color) -> anyhow::Result<()> {
let json = serde_json::to_value(&L530SetDeviceInfoParams::color(color)?)?;
self.set_device_info_internal(json).await
/// #[tokio::main]
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let device = ApiClient::<L530>::new(
/// "192.168.1.100".to_string(),
/// "[email protected]".to_string(),
/// "tapo-password".to_string(),
/// true,
/// ).await?;
///
/// device
/// .set()
/// .on()
/// .brightness(50)?
/// .color(Color::HotPink)?
/// .send()
/// .await?;
///
/// Ok(())
/// }
/// ```
pub fn set(&self) -> L530SetDeviceInfoParams {
L530SetDeviceInfoParams::new(self)
}

/// Sets the light bulb's *brightness*.
/// Sets the *brightness*.
///
/// # Arguments
///
/// * `brightness` - *u8*; between 1 and 100
pub async fn set_brightness(&self, brightness: u8) -> anyhow::Result<()> {
let json = serde_json::to_value(&L530SetDeviceInfoParams::brightness(brightness)?)?;
self.set_device_info_internal(json).await
L530SetDeviceInfoParams::new(self)
.brightness(brightness)?
.send()
.await
}

/// Sets the *color*.
///
/// # Arguments
///
/// * `color` - [crate::requests::Color]
pub async fn set_color(&self, color: Color) -> anyhow::Result<()> {
L530SetDeviceInfoParams::new(self)
.color(color)?
.send()
.await
}

/// Sets the light bulb's *hue* and *saturation*.
/// Sets the *hue* and *saturation*.
///
/// # Arguments
///
/// * `hue` - *u16* between 1 and 360
/// * `saturation` - *u8*; between 1 and 100
pub async fn set_hue_saturation(&self, hue: u16, saturation: u8) -> anyhow::Result<()> {
let json =
serde_json::to_value(&L530SetDeviceInfoParams::hue_saturation(hue, saturation)?)?;
self.set_device_info_internal(json).await
L530SetDeviceInfoParams::new(self)
.hue_saturation(hue, saturation)?
.send()
.await
}

/// Sets the light bulb's *color temperature*.
/// Sets the *color temperature*.
///
/// # Arguments
///
/// * `color_temperature` - *u16*; between 2500 and 6500
pub async fn set_color_temperature(&self, color_temperature: u16) -> anyhow::Result<()> {
let json = serde_json::to_value(&L530SetDeviceInfoParams::color_temperature(
color_temperature,
)?)?;
self.set_device_info_internal(json).await
L530SetDeviceInfoParams::new(self)
.color_temperature(color_temperature)?
.send()
.await
}

/// Gets *device info* as [`crate::responses::L530DeviceInfoResult`].
Expand Down
17 changes: 14 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
//! use std::{env, thread, time::Duration};
//!
//! use log::{info, LevelFilter};
//! use tapo::{ApiClient, Color, L530};
//! use tapo::{requests::Color, ApiClient, L530};
//!
//! #[tokio::main]
//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
Expand Down Expand Up @@ -49,6 +49,18 @@
//! info!("Waiting 2 seconds...");
//! thread::sleep(Duration::from_secs(2));
//!
//! info!("Using the `set` API to change multiple properties in a single request...");
//! device
//! .set()
//! .on()
//! .brightness(50)?
//! .color(Color::HotPink)?
//! .send()
//! .await?;
//!
//! info!("Waiting 2 seconds...");
//! thread::sleep(Duration::from_secs(2));
//!
//! info!("Turning device off...");
//! device.off().await?;
//!
Expand All @@ -67,11 +79,10 @@
mod api;
mod devices;
mod encryption;
mod requests;
mod tapo_date_format;

pub mod requests;
pub mod responses;

pub use api::*;
pub use devices::*;
pub use requests::*;
2 changes: 1 addition & 1 deletion src/requests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ mod set_device_info;
mod tapo_request;

pub use color::*;
pub use set_device_info::*;

pub(crate) use get_device_info::*;
pub(crate) use get_device_usage::*;
pub(crate) use get_energy_usage::*;
pub(crate) use handshake::*;
pub(crate) use login_device::*;
pub(crate) use secure_passthrough::*;
pub(crate) use set_device_info::*;
pub(crate) use tapo_request::*;
52 changes: 44 additions & 8 deletions src/requests/set_device_info/l510.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,59 @@
use serde::Serialize;

#[derive(Debug, Default, Serialize)]
pub(crate) struct L510SetDeviceInfoParams {
use crate::api::ApiClient;
use crate::devices::L510;

/// Builder that is used by the [`crate::ApiClient<L510>::set`] API to set multiple properties in a single request.
#[derive(Debug, Serialize)]
pub struct L510SetDeviceInfoParams<'a> {
#[serde(skip)]
client: &'a ApiClient<L510>,
#[serde(skip_serializing_if = "Option::is_none")]
device_on: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
brightness: Option<u8>,
}

impl L510SetDeviceInfoParams {
pub fn brightness(value: u8) -> anyhow::Result<Self> {
impl<'a> L510SetDeviceInfoParams<'a> {
/// Turns *on* the device. `send` must be called at the end to apply the changes.
pub fn on(mut self) -> Self {
self.device_on = Some(true);
self
}

/// Turns *off* the device. `send` must be called at the end to apply the changes.
pub fn off(mut self) -> Self {
self.device_on = Some(false);
self
}

/// Sets the *brightness*. `send` must be called at the end to apply the changes.
///
/// # Arguments
///
/// * `brightness` - *u8*; between 1 and 100
pub fn brightness(mut self, value: u8) -> anyhow::Result<Self> {
self.brightness = Some(value);
self.validate()
}

/// Performs a request to apply the changes to the device.
pub async fn send(self) -> anyhow::Result<()> {
let json = serde_json::to_value(&self)?;
self.client.set_device_info_internal(json).await
}
}

impl<'a> L510SetDeviceInfoParams<'a> {
pub(crate) fn new(client: &'a ApiClient<L510>) -> Self {
Self {
brightness: Some(value),
..Default::default()
client,
device_on: None,
brightness: None,
}
.validate()
}

pub fn validate(self) -> anyhow::Result<Self> {
fn validate(self) -> anyhow::Result<Self> {
if self.brightness.is_none() {
return Err(anyhow::anyhow!(
"DeviceInfoParams requires at least one property"
Expand Down
Loading

0 comments on commit df80f63

Please sign in to comment.