Skip to content

Commit

Permalink
Support for playing the alarm on the H100 hub (#318)
Browse files Browse the repository at this point in the history
Support for playing the alarm on the H100 hub

---------

Co-authored-by: Mihai Dinculescu <[email protected]>
  • Loading branch information
kay and mihai-dinculescu authored Dec 17, 2024
1 parent fa66eb7 commit 504badd
Show file tree
Hide file tree
Showing 13 changed files with 382 additions and 6 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
/target
.vscode
/.idea
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ file. This change log follows the conventions of

### Rust

#### Added

- Added functionality for controlling the alarm on the H100 hub via the `play_alarm` and `stop_alarm` methods in the `H100Handler`. Additionally, `get_supported_ringtone_list` is available to retrieve the list of supported ringtones for debugging purposes. (thanks to @kay)

### Python

## [v0.8.0][v0.8.0] - 2024-12-07
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,19 @@ Unofficial Tapo API Client. Works with TP-Link Tapo smart devices. Tested with l
| get_device_usage | | &#x2705; | &#x2705; | &#x2705; | &#x2705; | &#x2705; | &#x2705; | | |
| get_energy_data | | | | | | | &#x2705; | | |
| get_energy_usage | | | | | | | &#x2705; | | |
| get_supported_ringtone_list | | | | | | | | | &check; |
| off | &#x2705; | &#x2705; | &#x2705; | &#x2705; | &#x2705; | &#x2705; | &#x2705; | | |
| on | &#x2705; | &#x2705; | &#x2705; | &#x2705; | &#x2705; | &#x2705; | &#x2705; | | |
| play_alarm | | | | | | | | | &check; |
| refresh_session | &#x2705; | &#x2705; | &#x2705; | &#x2705; | &#x2705; | &#x2705; | &#x2705; | &#x2705; | &#x2705; |
| set_brightness | | &#x2705; | &#x2705; | &#x2705; | &#x2705; | | | | |
| set_color | | | &#x2705; | &#x2705; | &#x2705; | | | | |
| set_color_temperature | | | &#x2705; | &#x2705; | &#x2705; | | | | |
| set_hue_saturation | | | &#x2705; | &#x2705; | &#x2705; | | | | |
| set_lighting_effect | | | | | &#x2705; | | | | |
| set() API \* | | | &#x2705; | &#x2705; | &#x2705; | | | | |
| stop_alarm | | | | | | | | | &check; |


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

Expand Down
25 changes: 23 additions & 2 deletions tapo/examples/tapo_h100.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
/// H100 Example
use std::env;
//! H100 Example
use log::{info, LevelFilter};
use std::env;
use std::time::Duration;
use tapo::requests::{AlarmDuration, AlarmRingtone, AlarmVolume};
use tapo::responses::ChildDeviceHubResult;
use tapo::{ApiClient, HubDevice};

Expand Down Expand Up @@ -113,5 +115,24 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
}
}

let ringtone = AlarmRingtone::Alarm1;
let volume = AlarmVolume::Low;
let duration = AlarmDuration::Seconds(1);

info!("Triggering the alarm ringtone {ringtone:?} for {duration:?} at a {volume:?} volume");
hub.play_alarm(Some(ringtone), Some(volume), duration)
.await?;

let device_info = hub.get_device_info().await?;
info!("Is device ringing?: {:?}", device_info.in_alarm);

tokio::time::sleep(Duration::from_secs(1)).await;

info!("Stopping the alarm");
hub.stop_alarm().await?;

let device_info = hub.get_device_info().await?;
info!("Is device ringing?: {:?}", device_info.in_alarm);

Ok(())
}
36 changes: 34 additions & 2 deletions tapo/src/api/api_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@ use crate::api::{
use crate::error::{Error, TapoResponseError};
use crate::requests::{
ControlChildParams, EmptyParams, EnergyDataInterval, GetEnergyDataParams, LightingEffect,
MultipleRequestParams, TapoParams, TapoRequest,
MultipleRequestParams, PlayAlarmParams, TapoParams, TapoRequest,
};
use crate::responses::{
validate_response, ControlChildResult, CurrentPowerResult, DecodableResultExt,
EnergyDataResult, EnergyUsageResult, TapoMultipleResponse, TapoResponseExt, TapoResult,
EnergyDataResult, EnergyUsageResult, SupportedAlarmTypeListResult, TapoMultipleResponse,
TapoResponseExt, TapoResult,
};

const TERMINAL_UUID: &str = "00-00-00-00-00-00";
Expand Down Expand Up @@ -562,6 +563,37 @@ impl ApiClient {
.await
}

pub(crate) async fn get_supported_alarm_type_list(
&self,
) -> Result<SupportedAlarmTypeListResult, Error> {
let request = TapoRequest::GetSupportedAlarmTypeList(TapoParams::new(EmptyParams));

self.get_protocol()?
.execute_request::<SupportedAlarmTypeListResult>(request, true)
.await?
.ok_or_else(|| Error::Tapo(TapoResponseError::EmptyResult))
}

pub(crate) async fn play_alarm(&self, params: PlayAlarmParams) -> Result<(), Error> {
let request = TapoRequest::PlayAlarm(TapoParams::new(params));

self.get_protocol()?
.execute_request::<serde_json::Value>(request, true)
.await?;

Ok(())
}

pub(crate) async fn stop_alarm(&self) -> Result<(), Error> {
let request = TapoRequest::StopAlarm(TapoParams::new(EmptyParams));

self.get_protocol()?
.execute_request::<serde_json::Value>(request, true)
.await?;

Ok(())
}

pub(crate) async fn device_reset(&self) -> Result<(), Error> {
debug!("Device reset...");
let request = TapoRequest::DeviceReset(TapoParams::new(EmptyParams));
Expand Down
34 changes: 33 additions & 1 deletion tapo/src/api/hub_handler.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use std::sync::Arc;

use tokio::sync::RwLock;

use crate::api::ApiClient;
use crate::api::{KE100Handler, S200BHandler, T100Handler, T110Handler, T300Handler, T31XHandler};
use crate::error::Error;
use crate::requests::{AlarmDuration, AlarmRingtone, AlarmVolume, PlayAlarmParams};
use crate::responses::{ChildDeviceHubResult, ChildDeviceListHubResult, DeviceInfoHubResult};

macro_rules! get_device_id {
Expand Down Expand Up @@ -58,6 +58,38 @@ impl HubHandler {
self.client.read().await.get_device_info().await
}

/// Returns a list of ringtones (alarm types) supported by the hub.
/// Used for debugging only.
pub async fn get_supported_ringtone_list(&self) -> Result<Vec<String>, Error> {
self.client
.read()
.await
.get_supported_alarm_type_list()
.await
.map(|response| response.alarm_type_list)
}

/// Start playing the hub alarm.
/// By default, this uses the configured alarm settings on the hub.
/// Each of the settings can be overridden by passing `Some` as a parameter.
pub async fn play_alarm(
&self,
ringtone: Option<AlarmRingtone>,
volume: Option<AlarmVolume>,
duration: AlarmDuration,
) -> Result<(), Error> {
self.client
.read()
.await
.play_alarm(PlayAlarmParams::new(ringtone, volume, duration)?)
.await
}

/// Stop playing the hub alarm if currently playing
pub async fn stop_alarm(&self) -> Result<(), Error> {
self.client.read().await.stop_alarm().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<serde_json::Value, Error> {
Expand Down
2 changes: 2 additions & 0 deletions tapo/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ pub enum TapoResponseError {
InvalidResponse,
/// Malformed request.
MalformedRequest,
/// Parameters were invalid
InvalidParameters,
/// Invalid public key.
InvalidPublicKey,
/// The credentials provided were invalid.
Expand Down
2 changes: 2 additions & 0 deletions tapo/src/requests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ mod get_trigger_logs;
mod handshake;
mod login_device;
mod multiple_request;
mod play_alarm;
mod secure_passthrough;
mod set_device_info;
mod tapo_request;

pub use crate::responses::TemperatureUnitKE100;
pub use energy_data_interval::*;
pub use play_alarm::*;
pub use set_device_info::*;

pub(crate) use control_child::*;
Expand Down
Loading

0 comments on commit 504badd

Please sign in to comment.