feat(tapo): add countdown/schedule support and CLI tool
This commit is contained in:
@@ -10,14 +10,14 @@ use tokio::sync::RwLock;
|
||||
|
||||
use crate::error::{Error, TapoResponseError};
|
||||
use crate::requests::{
|
||||
ControlChildParams, DeviceRebootParams, EmptyParams, EnergyDataInterval,
|
||||
GetChildDeviceListParams, GetEnergyDataParams, GetPowerDataParams, GetRulesParams,
|
||||
LightingEffect, MultipleRequestParams, PlayAlarmParams, PowerDataInterval, TapoParams,
|
||||
TapoRequest,
|
||||
AddCountdownRuleParams, ControlChildParams, DeviceRebootParams, EditCountdownRuleParams,
|
||||
EmptyParams, EnergyDataInterval, GetChildDeviceListParams, GetEnergyDataParams,
|
||||
GetPowerDataParams, GetRulesParams, LightingEffect, MultipleRequestParams, PlayAlarmParams,
|
||||
PowerDataInterval, TapoParams, TapoRequest,
|
||||
};
|
||||
use crate::responses::{
|
||||
ControlChildResult, CountdownRulesResult, CurrentPowerResult, DecodableResultExt,
|
||||
EnergyDataResult, EnergyDataResultRaw, EnergyUsageResult, NextEventResult, PowerDataResult,
|
||||
EnergyDataResult, EnergyDataResultRaw, EnergyUsageResult, PowerDataResult,
|
||||
PowerDataResultRaw, ScheduleRulesResult, SupportedAlarmTypeListResult, TapoMultipleResponse,
|
||||
TapoResponseExt, TapoResult, validate_response,
|
||||
};
|
||||
@@ -876,16 +876,35 @@ impl ApiClient {
|
||||
.await?
|
||||
.ok_or_else(|| Error::Tapo(TapoResponseError::EmptyResult))
|
||||
}
|
||||
|
||||
/// Gets next scheduled event.
|
||||
pub(crate) async fn get_next_event(&self) -> Result<NextEventResult, Error> {
|
||||
debug!("Get Next event...");
|
||||
let request = TapoRequest::GetNextEvent(TapoParams::new(EmptyParams));
|
||||
/// Adds or updates a countdown rule.
|
||||
pub(crate) async fn add_countdown_rule(&self, delay: u64, turn_on: bool) -> Result<(), Error> {
|
||||
// Check if a countdown rule already exists
|
||||
let existing = self.get_countdown_rules().await.ok();
|
||||
|
||||
if let Some(countdown) = existing {
|
||||
if let Some(rule) = countdown.rules.first() {
|
||||
// Edit existing rule
|
||||
debug!("Edit Countdown rule: id={}, delay={}, turn_on={}", rule.id, delay, turn_on);
|
||||
let request = TapoRequest::EditCountdownRule(TapoParams::new(
|
||||
EditCountdownRuleParams::new(rule.id.clone(), delay, turn_on),
|
||||
));
|
||||
self.get_protocol()?
|
||||
.execute_request::<serde_json::Value>(request, true)
|
||||
.await?;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
// No existing rule, add new one
|
||||
debug!("Add Countdown rule: delay={}, turn_on={}", delay, turn_on);
|
||||
let request = TapoRequest::AddCountdownRule(TapoParams::new(
|
||||
AddCountdownRuleParams::new(delay, turn_on),
|
||||
));
|
||||
|
||||
self.get_protocol()?
|
||||
.execute_request(request, true)
|
||||
.await?
|
||||
.ok_or_else(|| Error::Tapo(TapoResponseError::EmptyResult))
|
||||
.execute_request::<serde_json::Value>(request, true)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_protocol_mut(&mut self) -> Result<&mut TapoProtocol, Error> {
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::error::Error;
|
||||
use crate::requests::{EnergyDataInterval, GenericSetDeviceInfoParams, PowerDataInterval};
|
||||
use crate::responses::{
|
||||
CountdownRulesResult, CurrentPowerResult, DeviceInfoPlugEnergyMonitoringResult,
|
||||
DeviceUsageEnergyMonitoringResult, EnergyDataResult, EnergyUsageResult, NextEventResult,
|
||||
DeviceUsageEnergyMonitoringResult, EnergyDataResult, EnergyUsageResult,
|
||||
PowerDataResult, ScheduleRulesResult,
|
||||
};
|
||||
|
||||
@@ -98,9 +98,13 @@ impl PlugEnergyMonitoringHandler {
|
||||
self.client.read().await.get_schedule_rules().await
|
||||
}
|
||||
|
||||
/// Returns *next scheduled event* as [`NextEventResult`].
|
||||
pub async fn get_next_event(&self) -> Result<NextEventResult, Error> {
|
||||
self.client.read().await.get_next_event().await
|
||||
/// Sets a countdown rule.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `delay` - Seconds until action
|
||||
/// * `turn_on` - true to turn on, false to turn off when countdown completes
|
||||
pub async fn set_countdown(&self, delay: u64, turn_on: bool) -> Result<(), Error> {
|
||||
self.client.read().await.add_countdown_rule(delay, turn_on).await
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,9 @@ use tokio::sync::{RwLock, RwLockReadGuard};
|
||||
|
||||
use crate::error::Error;
|
||||
use crate::requests::GenericSetDeviceInfoParams;
|
||||
use crate::responses::{DeviceInfoPlugResult, DeviceUsageResult};
|
||||
use crate::responses::{
|
||||
CountdownRulesResult, DeviceInfoPlugResult, DeviceUsageResult, ScheduleRulesResult,
|
||||
};
|
||||
|
||||
use super::{ApiClient, ApiClientExt, DeviceManagementExt, HandlerExt};
|
||||
|
||||
@@ -56,6 +58,25 @@ impl PlugHandler {
|
||||
pub async fn get_device_usage(&self) -> Result<DeviceUsageResult, Error> {
|
||||
self.client.read().await.get_device_usage().await
|
||||
}
|
||||
|
||||
/// Returns *countdown rules* as [`CountdownRulesResult`].
|
||||
pub async fn get_countdown_rules(&self) -> Result<CountdownRulesResult, Error> {
|
||||
self.client.read().await.get_countdown_rules().await
|
||||
}
|
||||
|
||||
/// Returns *schedule rules* as [`ScheduleRulesResult`].
|
||||
pub async fn get_schedule_rules(&self) -> Result<ScheduleRulesResult, Error> {
|
||||
self.client.read().await.get_schedule_rules().await
|
||||
}
|
||||
|
||||
/// Sets a countdown rule.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `delay` - Seconds until action
|
||||
/// * `turn_on` - true to turn on, false to turn off when countdown completes
|
||||
pub async fn set_countdown(&self, delay: u64, turn_on: bool) -> Result<(), Error> {
|
||||
self.client.read().await.add_countdown_rule(delay, turn_on).await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//! Tapo request objects.
|
||||
|
||||
mod add_countdown_rule;
|
||||
mod control_child;
|
||||
mod device_reboot;
|
||||
mod energy_data_interval;
|
||||
@@ -23,6 +24,7 @@ pub use play_alarm::*;
|
||||
pub use power_data_interval::*;
|
||||
pub use set_device_info::*;
|
||||
|
||||
pub(crate) use add_countdown_rule::*;
|
||||
pub(crate) use control_child::*;
|
||||
pub(crate) use device_reboot::*;
|
||||
pub(crate) use get_child_device_list::*;
|
||||
@@ -35,3 +37,4 @@ pub(crate) use login_device::*;
|
||||
pub(crate) use multiple_request::*;
|
||||
pub(crate) use secure_passthrough::*;
|
||||
pub(crate) use tapo_request::*;
|
||||
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
//! Parameters for editing countdown rules
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
/// Parameters for editing a countdown rule
|
||||
#[derive(Debug, Serialize)]
|
||||
pub(crate) struct EditCountdownRuleParams {
|
||||
/// Rule ID to edit
|
||||
pub id: String,
|
||||
/// Delay in seconds
|
||||
pub delay: u64,
|
||||
/// Desired states when countdown completes
|
||||
pub desired_states: CountdownDesiredStates,
|
||||
/// Whether to enable the rule
|
||||
pub enable: bool,
|
||||
}
|
||||
|
||||
/// Desired states for countdown
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub(crate) struct CountdownDesiredStates {
|
||||
/// Whether device should be on
|
||||
pub on: bool,
|
||||
}
|
||||
|
||||
impl EditCountdownRuleParams {
|
||||
pub fn new(id: String, delay: u64, turn_on: bool) -> Self {
|
||||
Self {
|
||||
id,
|
||||
delay,
|
||||
desired_states: CountdownDesiredStates { on: turn_on },
|
||||
enable: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parameters for adding a countdown rule
|
||||
#[derive(Debug, Serialize)]
|
||||
pub(crate) struct AddCountdownRuleParams {
|
||||
/// Delay in seconds
|
||||
pub delay: u64,
|
||||
/// Desired states when countdown completes
|
||||
pub desired_states: CountdownDesiredStates,
|
||||
/// Whether to enable the rule
|
||||
pub enable: bool,
|
||||
}
|
||||
|
||||
impl AddCountdownRuleParams {
|
||||
pub fn new(delay: u64, turn_on: bool) -> Self {
|
||||
Self {
|
||||
delay,
|
||||
desired_states: CountdownDesiredStates { on: turn_on },
|
||||
enable: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ pub(crate) struct GetRulesParams {
|
||||
}
|
||||
|
||||
impl GetRulesParams {
|
||||
#[allow(dead_code)]
|
||||
pub fn new(start_index: u32) -> Self {
|
||||
Self { start_index }
|
||||
}
|
||||
|
||||
@@ -3,9 +3,10 @@ use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use serde::Serialize;
|
||||
|
||||
use super::{
|
||||
ControlChildParams, DeviceRebootParams, GetChildDeviceListParams, GetEnergyDataParams,
|
||||
GetPowerDataParams, GetRulesParams, GetTriggerLogsParams, HandshakeParams, LightingEffect,
|
||||
LoginDeviceParams, MultipleRequestParams, PlayAlarmParams, SecurePassthroughParams,
|
||||
AddCountdownRuleParams, ControlChildParams, DeviceRebootParams, EditCountdownRuleParams,
|
||||
GetChildDeviceListParams, GetEnergyDataParams, GetPowerDataParams, GetRulesParams,
|
||||
GetTriggerLogsParams, HandshakeParams, LightingEffect, LoginDeviceParams,
|
||||
MultipleRequestParams, PlayAlarmParams, SecurePassthroughParams,
|
||||
};
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
@@ -46,9 +47,15 @@ pub(crate) enum TapoRequest {
|
||||
GetCountdownRules(TapoParams<GetRulesParams>),
|
||||
#[serde(rename = "get_schedule_rules")]
|
||||
GetScheduleRules(TapoParams<GetRulesParams>),
|
||||
#[serde(rename = "add_countdown_rule")]
|
||||
AddCountdownRule(TapoParams<AddCountdownRuleParams>),
|
||||
#[serde(rename = "edit_countdown_rule")]
|
||||
EditCountdownRule(TapoParams<EditCountdownRuleParams>),
|
||||
#[serde(rename = "get_next_event")]
|
||||
#[allow(dead_code)]
|
||||
GetNextEvent(TapoParams<EmptyParams>),
|
||||
#[serde(rename = "get_antitheft_rules")]
|
||||
#[allow(dead_code)]
|
||||
GetAntitheftRules(TapoParams<GetRulesParams>),
|
||||
}
|
||||
|
||||
|
||||
@@ -15,15 +15,14 @@ pub struct CountdownRule {
|
||||
pub delay: u64,
|
||||
/// Seconds remaining (if timer is active)
|
||||
pub remain: u64,
|
||||
/// Action when countdown completes: true = turn on, false = turn off
|
||||
#[serde(rename = "desired_states")]
|
||||
pub desired_states: Option<CountdownDesiredState>,
|
||||
/// Action when countdown completes
|
||||
pub desired_states: Option<DesiredState>,
|
||||
}
|
||||
|
||||
/// Desired state for countdown
|
||||
/// Desired state for countdown/schedule
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct CountdownDesiredState {
|
||||
/// Whether device should be on after countdown
|
||||
pub struct DesiredState {
|
||||
/// Whether device should be on
|
||||
pub on: Option<bool>,
|
||||
}
|
||||
|
||||
@@ -34,47 +33,39 @@ pub struct ScheduleRule {
|
||||
pub id: String,
|
||||
/// Whether the rule is enabled
|
||||
pub enable: bool,
|
||||
/// Weekday mask (bits 0-6 for Sun-Sat)
|
||||
/// Weekday mask (bits for days, 127 = all days)
|
||||
#[serde(default)]
|
||||
pub wday: Vec<u8>,
|
||||
pub week_day: u8,
|
||||
/// Start minute of day (0-1439)
|
||||
#[serde(rename = "s_min", default)]
|
||||
pub start_min: u16,
|
||||
/// End minute of day (for duration schedules)
|
||||
#[serde(rename = "e_min", default)]
|
||||
pub end_min: u16,
|
||||
/// Action: true = turn on, false = turn off
|
||||
#[serde(rename = "desired_states")]
|
||||
pub desired_states: Option<ScheduleDesiredState>,
|
||||
}
|
||||
|
||||
/// Desired state for schedule
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct ScheduleDesiredState {
|
||||
/// Whether device should be on
|
||||
pub on: Option<bool>,
|
||||
}
|
||||
|
||||
/// Next scheduled event
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct NextEventResult {
|
||||
/// Schedule type
|
||||
#[serde(rename = "schd_type")]
|
||||
pub schedule_type: Option<String>,
|
||||
/// Timestamp of next event (seconds since epoch)
|
||||
pub timestamp: Option<u64>,
|
||||
/// Action for the event
|
||||
pub action: Option<i32>,
|
||||
#[serde(default)]
|
||||
pub s_min: u16,
|
||||
/// End minute of day
|
||||
#[serde(default)]
|
||||
pub e_min: u16,
|
||||
/// Mode (e.g., "repeat")
|
||||
pub mode: Option<String>,
|
||||
/// Day of month
|
||||
pub day: Option<u8>,
|
||||
/// Month
|
||||
pub month: Option<u8>,
|
||||
/// Year
|
||||
pub year: Option<u16>,
|
||||
/// Action
|
||||
pub desired_states: Option<DesiredState>,
|
||||
}
|
||||
|
||||
/// Result wrapper for countdown rules
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct CountdownRulesResult {
|
||||
/// Whether countdown is enabled globally
|
||||
#[serde(default)]
|
||||
pub enable: bool,
|
||||
/// Max countdown rules
|
||||
#[serde(default)]
|
||||
pub countdown_rule_max_count: u32,
|
||||
/// List of countdown rules
|
||||
#[serde(rename = "countdown_rules")]
|
||||
#[serde(rename = "rule_list", default)]
|
||||
pub rules: Vec<CountdownRule>,
|
||||
/// Sum of rules (for pagination)
|
||||
pub sum: Option<u32>,
|
||||
}
|
||||
|
||||
impl TapoResponseExt for CountdownRulesResult {}
|
||||
@@ -82,13 +73,21 @@ impl TapoResponseExt for CountdownRulesResult {}
|
||||
/// Result wrapper for schedule rules
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct ScheduleRulesResult {
|
||||
/// Whether schedule is enabled globally
|
||||
#[serde(default)]
|
||||
pub enable: bool,
|
||||
/// Max schedule rules
|
||||
#[serde(default)]
|
||||
pub schedule_rule_max_count: u32,
|
||||
/// List of schedule rules
|
||||
#[serde(rename = "schedule_rules")]
|
||||
#[serde(rename = "rule_list", default)]
|
||||
pub rules: Vec<ScheduleRule>,
|
||||
/// Sum of rules (for pagination)
|
||||
pub sum: Option<u32>,
|
||||
/// Total count (for pagination)
|
||||
#[serde(default)]
|
||||
pub sum: u32,
|
||||
/// Start index (for pagination)
|
||||
#[serde(default)]
|
||||
pub start_index: u32,
|
||||
}
|
||||
|
||||
impl TapoResponseExt for ScheduleRulesResult {}
|
||||
|
||||
impl TapoResponseExt for NextEventResult {}
|
||||
|
||||
Reference in New Issue
Block a user