1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
use std::time::Duration;
use chrono_tz::Tz;
use crate::tapo_vendor::error::Error;
use crate::tapo_vendor::requests::{EnergyDataInterval, PowerDataInterval, ScheduleRule};
use crate::tapo_vendor::responses::{
CurrentPowerResult, DeviceInfoPlugEnergyMonitoringResult, DeviceUsageEnergyMonitoringResult,
EnergyDataResult, EnergyMeterDataResult, EnergyUsageResult, PowerDataResult, PowerState,
ScheduleRuleResult, Timer, TimezoneResult,
};
tapo_handler! {
/// Handler for the [P110](https://www.tapo.com/en/search/?q=P110),
/// [P110M](https://www.tapo.com/en/search/?q=P110M) and
/// [P115](https://www.tapo.com/en/search/?q=P115) devices.
PlugEnergyMonitoringHandler(DeviceInfoPlugEnergyMonitoringResult),
on_off,
device_usage = DeviceUsageEnergyMonitoringResult,
device_management,
}
impl PlugEnergyMonitoringHandler {
/// Returns *current power* as [`CurrentPowerResult`].
pub async fn get_current_power(&self) -> Result<CurrentPowerResult, Error> {
self.client.read().await.get_current_power().await
}
/// Returns *energy data* as [`EnergyDataResult`].
///
/// The dates in `interval` are read in the device's timezone, so the
/// entries line up with the Tapo app wherever this code runs. Entries that
/// start after the device's local time are left out.
///
/// Pass the [`TimezoneResult::timezone`] field from
/// [`PlugEnergyMonitoringHandler::get_timezone`] to read it once for
/// several calls. With `None`, each call reads it, which takes one extra
/// request.
///
/// # Errors
///
/// Returns an error if the device reports a timezone name that is not in
/// the IANA database.
pub async fn get_energy_data(
&self,
interval: EnergyDataInterval,
timezone: Option<Tz>,
) -> Result<EnergyDataResult, Error> {
self.client
.read()
.await
.get_energy_data(interval, timezone)
.await
}
/// Returns *energy meter data* as [`EnergyMeterDataResult`].
pub async fn get_energy_meter_data(&self) -> Result<EnergyMeterDataResult, Error> {
self.client.read().await.get_energy_meter_data().await
}
/// Returns *energy usage* as [`EnergyUsageResult`].
pub async fn get_energy_usage(&self) -> Result<EnergyUsageResult, Error> {
self.client.read().await.get_energy_usage().await
}
/// Returns *power data* as [`PowerDataResult`].
pub async fn get_power_data(
&self,
interval: PowerDataInterval,
) -> Result<PowerDataResult, Error> {
self.client.read().await.get_power_data(interval).await
}
/// Returns the device's *timezone* as [`TimezoneResult`].
///
/// # Errors
///
/// Returns an error if the device reports a timezone name that is not in
/// the IANA database.
pub async fn get_timezone(&self) -> Result<TimezoneResult, Error> {
self.client.read().await.get_timezone().await
}
/// Arms the plug's countdown timer (the "Timer" feature in the
/// Tapo app), replacing any timer that is currently armed.
/// After `delay`, the plug transitions to `desired_state`.
///
/// # Arguments
///
/// * `delay` - between 1 second and 24 hours, the maximum the Tapo app allows.
/// * `desired_state` - the state the plug transitions to when the timer fires.
pub async fn set_timer(
&self,
delay: Duration,
desired_state: PowerState,
) -> Result<Timer, Error> {
self.client
.read()
.await
.set_timer(delay, desired_state)
.await
}
/// Returns the armed timer, or `None` if no timer is armed.
pub async fn get_timer(&self) -> Result<Option<Timer>, Error> {
self.client.read().await.get_timer().await
}
/// Cancels the armed timer (the "Stop" button in the Tapo app),
/// or returns successfully if no timer was armed.
pub async fn clear_timer(&self) -> Result<(), Error> {
self.client.read().await.clear_timer().await
}
/// Adds a new schedule rule to the device. Returns it as a
/// [`ScheduleRuleResult`] carrying the device-assigned `id`. Schedule rules fire on the device
/// itself, so they keep working even if the phone / Wi-Fi router / Tapo
/// cloud is offline.
///
/// # Arguments
///
/// * `rule` - the rule to add; build one with the [`ScheduleRule`]
/// builders. Any `id` it carries is ignored, because the device assigns
/// one.
///
/// The device has a fixed capacity (a P110 stores 32 rules) and returns
/// a device error once it is full.
pub async fn add_schedule_rule(&self, rule: ScheduleRule) -> Result<ScheduleRuleResult, Error> {
self.client.read().await.add_schedule_rule(rule).await
}
/// Edits an existing schedule rule, replacing it with the given one.
///
/// # Arguments
///
/// * `rule` - the replacement rule. Its `id` must be set to the id of the
/// rule to update, either from `add_schedule_rule` /
/// `get_schedule_rules` or via [`ScheduleRule::with_id`]; an unset id is
/// an `Error::Validation`, and an id the device does not know is a
/// device error.
pub async fn edit_schedule_rule(&self, rule: ScheduleRule) -> Result<(), Error> {
self.client.read().await.edit_schedule_rule(rule).await
}
/// Returns every schedule rule currently stored on the device. A rule the
/// library cannot parse is skipped rather than failing the whole listing.
pub async fn get_schedule_rules(&self) -> Result<Vec<ScheduleRuleResult>, Error> {
self.client.read().await.get_schedule_rules().await
}
/// Returns how many schedule rules the device can store in total, as
/// reported by the device itself. A P110 stores 32. Compare against the
/// length of `get_schedule_rules` to tell whether there is room for
/// another rule, because `add_schedule_rule` fails once the device is
/// full.
pub async fn get_max_schedule_rules(&self) -> Result<u32, Error> {
self.client.read().await.get_max_schedule_rules().await
}
/// Removes a single schedule rule, leaving every other rule in place.
///
/// # Arguments
///
/// * `id` - the device-assigned id of the rule to remove.
pub async fn remove_schedule_rule(&self, id: impl Into<String>) -> Result<(), Error> {
self.client
.read()
.await
.remove_schedule_rule(id.into())
.await
}
/// Removes every schedule rule from the device.
pub async fn remove_all_schedule_rules(&self) -> Result<(), Error> {
self.client.read().await.remove_all_schedule_rules().await
}
}