1use crate::api;
2use crate::error::{FritzError, Result};
3use crate::fritz_xml;
4use crate::AVMDevice;
5
6#[derive(Clone)]
8pub struct FritzClient {
9 user: String,
10 password: String,
11 sid: Option<String>,
12}
13
14impl FritzClient {
15 pub fn new(user: impl ToString, password: impl ToString) -> Self {
16 FritzClient {
17 user: user.to_string(),
18 password: password.to_string(),
19 sid: None,
20 }
21 }
22
23 pub fn list_devices(&mut self) -> Result<Vec<AVMDevice>> {
25 let xml = self.request(api::Commands::GetDeviceListInfos)?;
26 let devices = fritz_xml::parse_device_infos(xml)?;
27 Ok(devices
28 .into_iter()
29 .map(AVMDevice::from_xml_device)
30 .collect())
31 }
32
33 pub fn device_stats(&mut self, ain: impl ToString) -> Result<Vec<crate::stats::DeviceStats>> {
34 let ain = ain.to_string();
35 let xml = self.request(api::Commands::GetBasicDeviceStats { ain })?;
36 fritz_xml::parse_device_stats(xml)
37 }
38
39 pub fn turn_on(&mut self, ain: impl ToString) -> Result<()> {
40 let ain = ain.to_string();
41 self.request(api::Commands::SetSwitchOn { ain })?;
42 Ok(())
43 }
44
45 pub fn turn_off(&mut self, ain: impl ToString) -> Result<()> {
46 let ain = ain.to_string();
47 self.request(api::Commands::SetSwitchOff { ain })?;
48 Ok(())
49 }
50
51 pub fn toggle(&mut self, ain: impl ToString) -> Result<()> {
52 let ain = ain.to_string();
53 self.request(api::Commands::SetSwitchToggle { ain })?;
54 Ok(())
55 }
56
57 pub fn trigger_high_refresh_rate(&mut self) -> Result<()> {
90 let sid = match self.sid.clone().or_else(|| self.update_sid().ok()) {
91 None => return Err(FritzError::Forbidden),
92 Some(sid) => sid,
93 };
94 let mut params = std::collections::HashMap::new();
95 params.insert("sid", sid.as_ref());
96 params.insert("c", "smarthome");
97 params.insert("a", "getData");
98 let client = reqwest::blocking::Client::builder()
99 .redirect(reqwest::redirect::Policy::none())
100 .build()?
101 .post("http://fritz.box/myfritz/api/data.lua")
102 .form(¶ms);
103 let response = client.send()?;
104 let status = response.status();
105
106 if status != 200 {
107 return Err(FritzError::TriggerHighRefreshRateError(status));
108 }
109 Ok(())
110 }
111
112 fn update_sid(&mut self) -> Result<String> {
115 let sid = api::get_sid(&self.user, &self.password)?;
116 self.sid = Some(sid.clone());
117 Ok(sid)
118 }
119
120 #[instrument(level = "trace", skip(self))]
121 fn request(&mut self, cmd: api::Commands) -> Result<String> {
122 self.request_attempt(cmd, 0)
123 }
124
125 #[instrument(level = "trace", skip(self))]
126 fn request_attempt(&mut self, cmd: api::Commands, request_count: usize) -> Result<String> {
127 let sid = match self.sid.clone().or_else(|| self.update_sid().ok()) {
128 None => return Err(FritzError::Forbidden),
129 Some(sid) => sid,
130 };
131 match api::request(cmd.clone(), sid) {
132 Err(FritzError::Forbidden) if request_count == 0 => {
133 let _ = self.update_sid();
134 self.request_attempt(cmd, request_count + 1)
135 }
136 result => result,
137 }
138 }
139}