tapo/api/api_client.rs
1use std::fmt;
2use std::time::Duration;
3
4use async_trait::async_trait;
5use log::debug;
6use reqwest::Client;
7use serde::de::DeserializeOwned;
8
9use crate::api::protocol::{TapoProtocol, TapoProtocolExt};
10use crate::api::{
11 ColorLightHandler, GenericDeviceHandler, HubHandler, LightHandler, PlugEnergyMonitoringHandler,
12 PlugHandler, PowerStripHandler, RgbLightStripHandler, RgbicLightStripHandler,
13};
14use crate::error::{Error, TapoResponseError};
15use crate::requests::{
16 ControlChildParams, EmptyParams, EnergyDataInterval, GetChildDeviceListParams,
17 GetEnergyDataParams, LightingEffect, MultipleRequestParams, PlayAlarmParams, TapoParams,
18 TapoRequest,
19};
20use crate::responses::{
21 ControlChildResult, CurrentPowerResult, DecodableResultExt, EnergyDataResult,
22 EnergyUsageResult, SupportedAlarmTypeListResult, TapoMultipleResponse, TapoResponseExt,
23 TapoResult, validate_response,
24};
25
26const TERMINAL_UUID: &str = "00-00-00-00-00-00";
27
28/// Implemented by all ApiClient implementations.
29#[async_trait]
30pub trait ApiClientExt: std::fmt::Debug + Send + Sync {
31 /// Sets the device info.
32 async fn set_device_info(&self, device_info_params: serde_json::Value) -> Result<(), Error>;
33}
34
35/// Tapo API Client. See [examples](https://github.com/mihai-dinculescu/tapo/tree/main/tapo/examples).
36///
37/// # Example
38///
39/// ```rust,no_run
40/// use tapo::ApiClient;
41///
42/// #[tokio::main]
43/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
44/// let device = ApiClient::new("tapo-username@example.com", "tapo-password")
45/// .l530("192.168.1.100")
46/// .await?;
47///
48/// device.on().await?;
49///
50/// Ok(())
51/// }
52/// ```
53#[derive(Debug, Clone)]
54pub struct ApiClient {
55 tapo_username: String,
56 tapo_password: String,
57 timeout: Option<Duration>,
58 protocol: Option<TapoProtocol>,
59}
60
61/// Tapo API Client constructor.
62impl ApiClient {
63 /// Returns a new instance of [`ApiClient`].
64 /// It is cheaper to [`ApiClient::clone`] an existing instance than to create a new one when multiple devices need to be controller.
65 /// This is because [`ApiClient::clone`] reuses the underlying [`reqwest::Client`].
66 ///
67 /// # Arguments
68 ///
69 /// * `tapo_username` - the Tapo username
70 /// * `tapo_password` - the Tapo password
71 ///
72 /// Note: The default connection timeout is 30 seconds.
73 /// Use [`ApiClient::with_timeout`] to change it.
74 pub fn new(tapo_username: impl Into<String>, tapo_password: impl Into<String>) -> ApiClient {
75 Self {
76 tapo_username: tapo_username.into(),
77 tapo_password: tapo_password.into(),
78 timeout: None,
79 protocol: None,
80 }
81 }
82
83 /// Changes the connection timeout from the default value to the given value.
84 ///
85 /// # Arguments
86 ///
87 /// * `timeout` - The new connection timeout value.
88 pub fn with_timeout(mut self, timeout: Duration) -> ApiClient {
89 self.timeout = Some(timeout);
90 self
91 }
92}
93
94/// Device handler builders.
95impl ApiClient {
96 /// Specializes the given [`ApiClient`] into an authenticated [`GenericDeviceHandler`].
97 ///
98 /// # Arguments
99 ///
100 /// * `ip_address` - the IP address of the device
101 ///
102 /// # Example
103 ///
104 /// ```rust,no_run
105 /// # use tapo::ApiClient;
106 /// # #[tokio::main]
107 /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
108 /// let device = ApiClient::new("tapo-username@example.com", "tapo-password")
109 /// .generic_device("192.168.1.100")
110 /// .await?;
111 /// device.on().await?;
112 /// # Ok(())
113 /// # }
114 /// ```
115 pub async fn generic_device(
116 mut self,
117 ip_address: impl Into<String>,
118 ) -> Result<GenericDeviceHandler, Error> {
119 self.login(ip_address).await?;
120
121 Ok(GenericDeviceHandler::new(self))
122 }
123
124 /// Specializes the given [`ApiClient`] into an authenticated [`LightHandler`].
125 ///
126 /// # Arguments
127 ///
128 /// * `ip_address` - the IP address of the device
129 ///
130 /// # Example
131 ///
132 /// ```rust,no_run
133 /// # use tapo::ApiClient;
134 /// # #[tokio::main]
135 /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
136 /// let device = ApiClient::new("tapo-username@example.com", "tapo-password")
137 /// .l510("192.168.1.100")
138 /// .await?;
139 /// device.on().await?;
140 /// # Ok(())
141 /// # }
142 /// ```
143 pub async fn l510(mut self, ip_address: impl Into<String>) -> Result<LightHandler, Error> {
144 self.login(ip_address).await?;
145
146 Ok(LightHandler::new(self))
147 }
148
149 /// Specializes the given [`ApiClient`] into an authenticated [`LightHandler`].
150 ///
151 /// # Arguments
152 ///
153 /// * `ip_address` - the IP address of the device
154 ///
155 /// # Example
156 ///
157 /// ```rust,no_run
158 /// # use tapo::ApiClient;
159 /// # #[tokio::main]
160 /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
161 /// let device = ApiClient::new("tapo-username@example.com", "tapo-password")
162 /// .l520("192.168.1.100")
163 /// .await?;
164 /// device.on().await?;
165 /// # Ok(())
166 /// # }
167 /// ```
168 pub async fn l520(mut self, ip_address: impl Into<String>) -> Result<LightHandler, Error> {
169 self.login(ip_address).await?;
170
171 Ok(LightHandler::new(self))
172 }
173
174 /// Specializes the given [`ApiClient`] into an authenticated [`ColorLightHandler`].
175 ///
176 /// # Arguments
177 ///
178 /// * `ip_address` - the IP address of the device
179 ///
180 /// # Example
181 ///
182 /// ```rust,no_run
183 /// # use tapo::ApiClient;
184 /// # #[tokio::main]
185 /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
186 /// let device = ApiClient::new("tapo-username@example.com", "tapo-password")
187 /// .l530("192.168.1.100")
188 /// .await?;
189 /// device.on().await?;
190 /// # Ok(())
191 /// # }
192 /// ```
193 pub async fn l530(mut self, ip_address: impl Into<String>) -> Result<ColorLightHandler, Error> {
194 self.login(ip_address).await?;
195
196 Ok(ColorLightHandler::new(self))
197 }
198
199 /// Specializes the given [`ApiClient`] into an authenticated [`ColorLightHandler`].
200 ///
201 /// # Arguments
202 ///
203 /// * `ip_address` - the IP address of the device
204 ///
205 /// # Example
206 ///
207 /// ```rust,no_run
208 /// # use tapo::ApiClient;
209 /// # #[tokio::main]
210 /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
211 /// let device = ApiClient::new("tapo-username@example.com", "tapo-password")
212 /// .l535("192.168.1.100")
213 /// .await?;
214 /// device.on().await?;
215 /// # Ok(())
216 /// # }
217 /// ```
218 pub async fn l535(mut self, ip_address: impl Into<String>) -> Result<ColorLightHandler, Error> {
219 self.login(ip_address).await?;
220
221 Ok(ColorLightHandler::new(self))
222 }
223
224 /// Specializes the given [`ApiClient`] into an authenticated [`LightHandler`].
225 ///
226 /// # Arguments
227 ///
228 /// * `ip_address` - the IP address of the device
229 ///
230 /// # Example
231 ///
232 /// ```rust,no_run
233 /// # use tapo::ApiClient;
234 /// # #[tokio::main]
235 /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
236 /// let device = ApiClient::new("tapo-username@example.com", "tapo-password")
237 /// .l610("192.168.1.100")
238 /// .await?;
239 /// device.on().await?;
240 /// # Ok(())
241 /// # }
242 /// ```
243 pub async fn l610(mut self, ip_address: impl Into<String>) -> Result<LightHandler, Error> {
244 self.login(ip_address).await?;
245
246 Ok(LightHandler::new(self))
247 }
248
249 /// Specializes the given [`ApiClient`] into an authenticated [`ColorLightHandler`].
250 ///
251 /// # Arguments
252 ///
253 /// * `ip_address` - the IP address of the device
254 ///
255 /// # Example
256 ///
257 /// ```rust,no_run
258 /// # use tapo::ApiClient;
259 /// # #[tokio::main]
260 /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
261 /// let device = ApiClient::new("tapo-username@example.com", "tapo-password")
262 /// .l630("192.168.1.100")
263 /// .await?;
264 /// device.on().await?;
265 /// # Ok(())
266 /// # }
267 /// ```
268 pub async fn l630(mut self, ip_address: impl Into<String>) -> Result<ColorLightHandler, Error> {
269 self.login(ip_address).await?;
270
271 Ok(ColorLightHandler::new(self))
272 }
273
274 /// Specializes the given [`ApiClient`] into an authenticated [`RgbLightStripHandler`].
275 ///
276 /// # Arguments
277 ///
278 /// * `ip_address` - the IP address of the device
279 ///
280 /// # Example
281 ///
282 /// ```rust,no_run
283 /// # use tapo::ApiClient;
284 /// # #[tokio::main]
285 /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
286 /// let device = ApiClient::new("tapo-username@example.com", "tapo-password")
287 /// .l900("192.168.1.100")
288 /// .await?;
289 /// device.on().await?;
290 /// # Ok(())
291 /// # }
292 /// ```
293 pub async fn l900(
294 mut self,
295 ip_address: impl Into<String>,
296 ) -> Result<RgbLightStripHandler, Error> {
297 self.login(ip_address).await?;
298
299 Ok(RgbLightStripHandler::new(self))
300 }
301
302 /// Specializes the given [`ApiClient`] into an authenticated [`RgbicLightStripHandler`].
303 ///
304 /// # Arguments
305 ///
306 /// * `ip_address` - the IP address of the device
307 ///
308 /// # Example
309 ///
310 /// ```rust,no_run
311 /// # use tapo::ApiClient;
312 /// # #[tokio::main]
313 /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
314 /// let device = ApiClient::new("tapo-username@example.com", "tapo-password")
315 /// .l920("192.168.1.100")
316 /// .await?;
317 /// device.on().await?;
318 /// # Ok(())
319 /// # }
320 /// ```
321 pub async fn l920(
322 mut self,
323 ip_address: impl Into<String>,
324 ) -> Result<RgbicLightStripHandler, Error> {
325 self.login(ip_address).await?;
326
327 Ok(RgbicLightStripHandler::new(self))
328 }
329
330 /// Specializes the given [`ApiClient`] into an authenticated [`RgbicLightStripHandler`].
331 ///
332 /// # Arguments
333 ///
334 /// * `ip_address` - the IP address of the device
335 ///
336 /// # Example
337 ///
338 /// ```rust,no_run
339 /// # use tapo::ApiClient;
340 /// # #[tokio::main]
341 /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
342 /// let device = ApiClient::new("tapo-username@example.com", "tapo-password")
343 /// .l930("192.168.1.100")
344 /// .await?;
345 /// device.on().await?;
346 /// # Ok(())
347 /// # }
348 /// ```
349 pub async fn l930(
350 mut self,
351 ip_address: impl Into<String>,
352 ) -> Result<RgbicLightStripHandler, Error> {
353 self.login(ip_address).await?;
354
355 Ok(RgbicLightStripHandler::new(self))
356 }
357
358 /// Specializes the given [`ApiClient`] into an authenticated [`PlugHandler`].
359 ///
360 /// # Arguments
361 ///
362 /// * `ip_address` - the IP address of the device
363 ///
364 /// # Example
365 ///
366 /// ```rust,no_run
367 /// # use tapo::ApiClient;
368 /// # #[tokio::main]
369 /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
370 /// let device = ApiClient::new("tapo-username@example.com", "tapo-password")
371 /// .p100("192.168.1.100")
372 /// .await?;
373 /// device.on().await?;
374 /// # Ok(())
375 /// # }
376 /// ```
377 pub async fn p100(mut self, ip_address: impl Into<String>) -> Result<PlugHandler, Error> {
378 self.login(ip_address).await?;
379
380 Ok(PlugHandler::new(self))
381 }
382
383 /// Specializes the given [`ApiClient`] into an authenticated [`PlugHandler`].
384 ///
385 /// # Arguments
386 ///
387 /// * `ip_address` - the IP address of the device
388 ///
389 /// # Example
390 ///
391 /// ```rust,no_run
392 /// # use tapo::ApiClient;
393 /// # #[tokio::main]
394 /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
395 /// let device = ApiClient::new("tapo-username@example.com", "tapo-password")
396 /// .p105("192.168.1.100")
397 /// .await?;
398 /// device.on().await?;
399 /// # Ok(())
400 /// # }
401 /// ```
402 pub async fn p105(mut self, ip_address: impl Into<String>) -> Result<PlugHandler, Error> {
403 self.login(ip_address).await?;
404
405 Ok(PlugHandler::new(self))
406 }
407
408 /// Specializes the given [`ApiClient`] into an authenticated [`PlugEnergyMonitoringHandler`].
409 ///
410 /// # Arguments
411 ///
412 /// * `ip_address` - the IP address of the device
413 ///
414 /// # Example
415 ///
416 /// ```rust,no_run
417 /// # use tapo::ApiClient;
418 /// # #[tokio::main]
419 /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
420 /// let device = ApiClient::new("tapo-username@example.com", "tapo-password")
421 /// .p110("192.168.1.100")
422 /// .await?;
423 /// device.on().await?;
424 /// # Ok(())
425 /// # }
426 /// ```
427 pub async fn p110(
428 mut self,
429 ip_address: impl Into<String>,
430 ) -> Result<PlugEnergyMonitoringHandler, Error> {
431 self.login(ip_address).await?;
432
433 Ok(PlugEnergyMonitoringHandler::new(self))
434 }
435
436 /// Specializes the given [`ApiClient`] into an authenticated [`PlugEnergyMonitoringHandler`].
437 ///
438 /// # Arguments
439 ///
440 /// * `ip_address` - the IP address of the device
441 ///
442 /// # Example
443 ///
444 /// ```rust,no_run
445 /// # use tapo::ApiClient;
446 /// # #[tokio::main]
447 /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
448 /// let device = ApiClient::new("tapo-username@example.com", "tapo-password")
449 /// .p115("192.168.1.100")
450 /// .await?;
451 /// device.on().await?;
452 /// # Ok(())
453 /// # }
454 /// ```
455 pub async fn p115(
456 mut self,
457 ip_address: impl Into<String>,
458 ) -> Result<PlugEnergyMonitoringHandler, Error> {
459 self.login(ip_address).await?;
460
461 Ok(PlugEnergyMonitoringHandler::new(self))
462 }
463
464 /// Specializes the given [`ApiClient`] into an authenticated [`PowerStripHandler`].
465 ///
466 /// # Arguments
467 ///
468 /// * `ip_address` - the IP address of the device
469 ///
470 /// # Example
471 ///
472 /// ```rust,no_run
473 /// # use tapo::ApiClient;
474 /// # #[tokio::main]
475 /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
476 /// let device = ApiClient::new("tapo-username@example.com", "tapo-password")
477 /// .p300("192.168.1.100")
478 /// .await?;
479 /// let child_device_list = device.get_child_device_list().await?;
480 /// println!("Child device list: {child_device_list:?}");
481 /// # Ok(())
482 /// # }
483 /// ```
484 pub async fn p300(mut self, ip_address: impl Into<String>) -> Result<PowerStripHandler, Error> {
485 self.login(ip_address).await?;
486
487 Ok(PowerStripHandler::new(self))
488 }
489
490 /// Specializes the given [`ApiClient`] into an authenticated [`PowerStripHandler`].
491 ///
492 /// # Arguments
493 ///
494 /// * `ip_address` - the IP address of the device
495 ///
496 /// # Example
497 ///
498 /// ```rust,no_run
499 /// # use tapo::ApiClient;
500 /// # #[tokio::main]
501 /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
502 /// let device = ApiClient::new("tapo-username@example.com", "tapo-password")
503 /// .p304("192.168.1.100")
504 /// .await?;
505 /// let child_device_list = device.get_child_device_list().await?;
506 /// println!("Child device list: {child_device_list:?}");
507 /// # Ok(())
508 /// # }
509 /// ```
510 pub async fn p304(mut self, ip_address: impl Into<String>) -> Result<PowerStripHandler, Error> {
511 self.login(ip_address).await?;
512
513 Ok(PowerStripHandler::new(self))
514 }
515
516 /// Specializes the given [`ApiClient`] into an authenticated [`HubHandler`].
517 ///
518 /// # Arguments
519 ///
520 /// * `ip_address` - the IP address of the device
521 ///
522 /// # Example
523 ///
524 /// ```rust,no_run
525 /// # use tapo::ApiClient;
526 /// # #[tokio::main]
527 /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
528 /// let device = ApiClient::new("tapo-username@example.com", "tapo-password")
529 /// .h100("192.168.1.100")
530 /// .await?;
531 ///
532 /// let child_device_list = device.get_child_device_list().await?;
533 /// println!("Child device list: {child_device_list:?}");
534 /// # Ok(())
535 /// # }
536 /// ```
537 pub async fn h100(mut self, ip_address: impl Into<String>) -> Result<HubHandler, Error> {
538 self.login(ip_address).await?;
539
540 Ok(HubHandler::new(self))
541 }
542}
543
544/// Tapo API Client private methods.
545impl ApiClient {
546 pub(crate) async fn login(&mut self, ip_address: impl Into<String>) -> Result<(), Error> {
547 let url = format!("http://{}/app", ip_address.into());
548 debug!("Device url: {url}");
549
550 let tapo_username = self.tapo_username.clone();
551 let tapo_password = self.tapo_password.clone();
552
553 self.get_protocol_mut()?
554 .login(url, tapo_username, tapo_password)
555 .await
556 }
557
558 pub(crate) async fn refresh_session(&mut self) -> Result<(), Error> {
559 let tapo_username = self.tapo_username.clone();
560 let tapo_password = self.tapo_password.clone();
561
562 self.get_protocol_mut()?
563 .refresh_session(tapo_username, tapo_password)
564 .await
565 }
566
567 pub(crate) async fn get_supported_alarm_type_list(
568 &self,
569 ) -> Result<SupportedAlarmTypeListResult, Error> {
570 let request = TapoRequest::GetSupportedAlarmTypeList(TapoParams::new(EmptyParams));
571
572 self.get_protocol()?
573 .execute_request::<SupportedAlarmTypeListResult>(request, true)
574 .await?
575 .ok_or_else(|| Error::Tapo(TapoResponseError::EmptyResult))
576 }
577
578 pub(crate) async fn play_alarm(&self, params: PlayAlarmParams) -> Result<(), Error> {
579 let request = TapoRequest::PlayAlarm(TapoParams::new(params));
580
581 self.get_protocol()?
582 .execute_request::<serde_json::Value>(request, true)
583 .await?;
584
585 Ok(())
586 }
587
588 pub(crate) async fn stop_alarm(&self) -> Result<(), Error> {
589 let request = TapoRequest::StopAlarm(TapoParams::new(EmptyParams));
590
591 self.get_protocol()?
592 .execute_request::<serde_json::Value>(request, true)
593 .await?;
594
595 Ok(())
596 }
597
598 pub(crate) async fn device_reset(&self) -> Result<(), Error> {
599 debug!("Device reset...");
600 let request = TapoRequest::DeviceReset(TapoParams::new(EmptyParams));
601
602 self.get_protocol()?
603 .execute_request::<serde_json::Value>(request, true)
604 .await?;
605
606 Ok(())
607 }
608
609 pub(crate) async fn get_device_info<R>(&self) -> Result<R, Error>
610 where
611 R: fmt::Debug + DeserializeOwned + TapoResponseExt + DecodableResultExt,
612 {
613 debug!("Get Device info...");
614 let request = TapoRequest::GetDeviceInfo(TapoParams::new(EmptyParams));
615
616 self.get_protocol()?
617 .execute_request::<R>(request, true)
618 .await?
619 .map(|result| result.decode())
620 .ok_or_else(|| Error::Tapo(TapoResponseError::EmptyResult))?
621 }
622
623 pub(crate) async fn get_device_usage<R>(&self) -> Result<R, Error>
624 where
625 R: fmt::Debug + DeserializeOwned + TapoResponseExt,
626 {
627 debug!("Get Device usage...");
628 let request = TapoRequest::GetDeviceUsage(TapoParams::new(EmptyParams));
629
630 self.get_protocol()?
631 .execute_request::<R>(request, true)
632 .await?
633 .ok_or_else(|| Error::Tapo(TapoResponseError::EmptyResult))
634 }
635
636 pub(crate) async fn set_lighting_effect(
637 &self,
638 lighting_effect: LightingEffect,
639 ) -> Result<(), Error> {
640 debug!("Lighting effect will change to: {lighting_effect:?}");
641
642 let request = TapoRequest::SetLightingEffect(Box::new(
643 TapoParams::new(lighting_effect)
644 .set_request_time_mils()?
645 .set_terminal_uuid(TERMINAL_UUID),
646 ));
647
648 self.get_protocol()?
649 .execute_request::<TapoResult>(request, true)
650 .await?;
651
652 Ok(())
653 }
654
655 pub(crate) async fn get_energy_usage(&self) -> Result<EnergyUsageResult, Error> {
656 debug!("Get Energy usage...");
657 let request = TapoRequest::GetEnergyUsage(TapoParams::new(EmptyParams));
658
659 self.get_protocol()?
660 .execute_request::<EnergyUsageResult>(request, true)
661 .await?
662 .ok_or_else(|| Error::Tapo(TapoResponseError::EmptyResult))
663 }
664
665 pub(crate) async fn get_energy_data(
666 &self,
667 interval: EnergyDataInterval,
668 ) -> Result<EnergyDataResult, Error> {
669 debug!("Get Energy data...");
670 let params = GetEnergyDataParams::new(interval);
671 let request = TapoRequest::GetEnergyData(TapoParams::new(params));
672
673 self.get_protocol()?
674 .execute_request::<EnergyDataResult>(request, true)
675 .await?
676 .ok_or_else(|| Error::Tapo(TapoResponseError::EmptyResult))
677 }
678
679 pub(crate) async fn get_current_power(&self) -> Result<CurrentPowerResult, Error> {
680 debug!("Get Current power...");
681 let request = TapoRequest::GetCurrentPower(TapoParams::new(EmptyParams));
682
683 self.get_protocol()?
684 .execute_request::<CurrentPowerResult>(request, true)
685 .await?
686 .ok_or_else(|| Error::Tapo(TapoResponseError::EmptyResult))
687 }
688
689 pub(crate) async fn get_child_device_list<R>(&self, start_index: u64) -> Result<R, Error>
690 where
691 R: fmt::Debug + DeserializeOwned + TapoResponseExt + DecodableResultExt,
692 {
693 debug!("Get Child device list starting with index {start_index}...");
694 let request = TapoRequest::GetChildDeviceList(TapoParams::new(
695 GetChildDeviceListParams::new(start_index),
696 ));
697
698 self.get_protocol()?
699 .execute_request::<R>(request, true)
700 .await?
701 .map(|result| result.decode())
702 .ok_or_else(|| Error::Tapo(TapoResponseError::EmptyResult))?
703 }
704
705 pub(crate) async fn get_child_device_component_list<R>(&self) -> Result<R, Error>
706 where
707 R: fmt::Debug + DeserializeOwned + TapoResponseExt + DecodableResultExt,
708 {
709 debug!("Get Child device component list...");
710 let request = TapoRequest::GetChildDeviceComponentList(TapoParams::new(EmptyParams));
711
712 self.get_protocol()?
713 .execute_request::<R>(request, true)
714 .await?
715 .map(|result| result.decode())
716 .ok_or_else(|| Error::Tapo(TapoResponseError::EmptyResult))?
717 }
718
719 pub(crate) async fn control_child<R>(
720 &self,
721 device_id: String,
722 child_request: TapoRequest,
723 ) -> Result<Option<R>, Error>
724 where
725 R: fmt::Debug + DeserializeOwned + TapoResponseExt,
726 {
727 debug!("Control child...");
728 let params = MultipleRequestParams::new(vec![child_request]);
729 let request = TapoRequest::MultipleRequest(Box::new(TapoParams::new(params)));
730
731 let params = ControlChildParams::new(device_id, request);
732 let request = TapoRequest::ControlChild(Box::new(TapoParams::new(params)));
733
734 let responses = self
735 .get_protocol()?
736 .execute_request::<ControlChildResult<TapoMultipleResponse<R>>>(request, true)
737 .await?
738 .ok_or_else(|| Error::Tapo(TapoResponseError::EmptyResult))?
739 .response_data
740 .result
741 .responses;
742
743 let response = responses
744 .into_iter()
745 .next()
746 .ok_or_else(|| Error::Tapo(TapoResponseError::EmptyResult))?;
747
748 validate_response(&response)?;
749
750 Ok(response.result)
751 }
752
753 fn get_protocol_mut(&mut self) -> Result<&mut TapoProtocol, Error> {
754 if self.protocol.is_none() {
755 let timeout = self.timeout.unwrap_or_else(|| Duration::from_secs(30));
756
757 let client = Client::builder()
758 .http1_title_case_headers()
759 .timeout(timeout)
760 .build()?;
761 let protocol = TapoProtocol::new(client);
762 self.protocol.replace(protocol);
763 }
764
765 self.protocol.as_mut().ok_or_else(|| {
766 Error::Other(anyhow::anyhow!(
767 "The protocol should have been initialized already"
768 ))
769 })
770 }
771
772 fn get_protocol(&self) -> Result<&TapoProtocol, Error> {
773 self.protocol.as_ref().ok_or_else(|| {
774 Error::Other(anyhow::anyhow!(
775 "The protocol should have been initialized already"
776 ))
777 })
778 }
779}
780
781#[async_trait]
782impl ApiClientExt for ApiClient {
783 async fn set_device_info(&self, device_info_params: serde_json::Value) -> Result<(), Error> {
784 debug!("Device info will change to: {device_info_params:?}");
785
786 let set_device_info_request = TapoRequest::SetDeviceInfo(Box::new(
787 TapoParams::new(device_info_params)
788 .set_request_time_mils()?
789 .set_terminal_uuid(TERMINAL_UUID),
790 ));
791
792 self.get_protocol()?
793 .execute_request::<TapoResult>(set_device_info_request, true)
794 .await?;
795
796 Ok(())
797 }
798}