huawei_client/
client_impl.rs1use std::collections::HashMap;
2use std::ops::Add;
3
4use std::sync::Mutex;
5use std::time::{Duration, Instant};
6
7use async_trait::async_trait;
8use log::trace;
9use reqwest::header::HeaderMap;
10use crate::api_requests::{GetDevListRequest, GetDevRealKpiRequest, LoginRequest};
11use crate::api_responses::{GetDevListData, GetDevListResponse, GetDevRealKpiDataInverterItem, GetDevRealKpiDataItem, GetDevRealKpiDataPowerSensorItem, GetDevRealKpiResponse, GetStationData, GetStationListResponse, GetStationRealKpiResponse, LoginResponse, RealtimeData};
12use crate::client::HuaweiClient;
13use crate::errors::Error;
14use crate::errors::Error::{GetDataError, LoginError};
15
16pub struct HuaweiClientImpl {
17 base_url: String,
18 username: String,
19 password: String,
20 xsrf_token: Mutex<Option<String>>,
21 login_expiration: Mutex<Instant>,
22 client: reqwest::Client,
23}
24
25impl HuaweiClientImpl {
26 pub fn new(base_url: &str, username: &str, password: &str) -> Self {
27 Self {
28 base_url: base_url.to_string(),
29 username: username.to_string(),
30 password: password.to_string(),
31 xsrf_token: Mutex::new(None),
32 login_expiration: Mutex::new(Instant::now()),
33 client: reqwest::Client::new(),
34 }
35 }
36 pub fn default(username: &str, password: &str) -> Self {
37 Self::new("https://eu5.fusionsolar.huawei.com", username, password)
38 }
39
40 pub async fn login(&self) -> Result<String, Error> {
41 let params = LoginRequest::new(self.username.as_str(), self.password.as_str());
42 let result = self
43 .client
44 .post(format!("{}/thirdData/login", self.base_url))
45 .json(¶ms)
46 .send()
47 .await
48 .map_err(|e| LoginError(e.to_string()))?;
49
50 trace!("result: {:?}", result);
51 let result_headers = result.headers().clone();
52
53 let login_response: LoginResponse =
54 result.json().await.map_err(|e| LoginError(e.to_string()))?;
55 trace!("body: {:?}", login_response);
56 if !login_response.success {
57 return Err(LoginError(
58 login_response
59 .message
60 .unwrap_or(format!("failCode:{}", login_response.fail_code)),
61 ));
62 }
63
64 return if let Some(xsrf_token) = result_headers.get("xsrf-token") {
65 let token = xsrf_token.to_str().unwrap().to_string();
66 let mut mut_xsrf_token = self.xsrf_token.lock().unwrap();
67 *mut_xsrf_token = Some(token.clone());
68
69 let mut login_expiration = self.login_expiration.lock().unwrap();
70 *login_expiration = Instant::now().add(Duration::from_secs(20 * 60));
71
72 Ok(token)
73 } else {
74 Err(LoginError(
75 "XSRF token header not found in the response".to_string(),
76 ))
77 };
78 }
79
80 pub fn xsrf_token(&self) -> Option<String> {
81 self.xsrf_token.lock().unwrap().clone()
82 }
83
84 async fn ensure_token(&self) -> Result<HeaderMap, Error> {
85 let mut headers = HeaderMap::new();
86 let login_expiration = self.login_expiration.lock().unwrap().clone();
87 if login_expiration > Instant::now() {
88 if let Some(xsrf_token) = self.xsrf_token() {
89 headers.insert("XSRF-TOKEN", xsrf_token.parse().unwrap());
90 return Ok(headers);
91 }
92 }
93 headers.insert("XSRF-TOKEN", self.login().await?.parse().unwrap());
94
95 Ok(headers)
96 }
97
98 async fn get_realtime_device_data<T: GetDevRealKpiDataItem>(
99 &self,
100 device_id: i64,
101 ) -> Result<T, Error> {
102 let headers = self.ensure_token().await?;
103 let params = GetDevRealKpiRequest {
104 dev_ids: device_id.to_string(),
105 dev_type_id: T::get_device_type(),
106 };
107 let http_response = self
108 .client
109 .post(format!("{}/thirdData/getDevRealKpi", self.base_url))
110 .headers(headers)
111 .json(¶ms)
112 .send()
113 .await
114 .map_err(|e| GetDataError(e.to_string()))?;
115
116 let http_status_code = http_response.status();
117 if http_status_code != 200 {
118 let body = http_response.text().await.map_err(|e| GetDataError(e.to_string()))?;
119 return Err(GetDataError(format!("Http error: status:{}, error:{}", http_status_code, body)));
120 }
121
122 let resp: GetDevRealKpiResponse<T> = http_response
123 .json()
124 .await
125 .map_err(|e| GetDataError(e.to_string()))?;
126
127 if !resp.success {
128 return Err(GetDataError(
129 resp.message
130 .unwrap_or(format!("failCode:{}", resp.fail_code)),
131 ));
132 }
133 Ok(resp.data.unwrap().first().unwrap().data_item_map.clone())
134 }
135}
136
137#[async_trait]
138impl HuaweiClient for HuaweiClientImpl {
139 async fn get_station_list(&self) -> Result<Vec<GetStationData>, Error> {
140 let headers = self.ensure_token().await?;
141
142 let http_response = self
143 .client
144 .post(format!("{}/thirdData/getStationList", self.base_url))
145 .headers(headers)
146 .send()
147 .await
148 .map_err(|e| GetDataError(e.to_string()))?;
149
150 let http_status_code = http_response.status();
151 if http_status_code != 200 {
152 let body = http_response.text().await.map_err(|e| GetDataError(e.to_string()))?;
153 return Err(GetDataError(format!("Http error: status:{}, error:{}", http_status_code, body)));
154 }
155
156 let resp: GetStationListResponse = http_response
157 .json()
158 .await
159 .map_err(|e| GetDataError(e.to_string()))?;
160
161 if !resp.success {
162 return Err(GetDataError(
163 resp.message
164 .unwrap_or(format!("failCode:{}", resp.fail_code)),
165 ));
166 }
167 Ok(resp.data.unwrap())
168 }
169
170 async fn get_realtime_station_data(&self, station_code: &str) -> Result<RealtimeData, Error> {
171 let params = HashMap::from([("stationCodes", station_code)]);
172 let headers = self.ensure_token().await?;
173
174 let request = self
175 .client
176 .post(format!("{}/thirdData/getStationRealKpi", self.base_url))
177 .json(¶ms)
178 .headers(headers)
179 .build()
180 .map_err(|e| GetDataError(e.to_string()))?;
181 trace!("request:{:?}", request);
182
183 let http_response = self
184 .client
185 .execute(request)
186 .await
187 .map_err(|e| GetDataError(e.to_string()))?;
188
189 let http_status_code = http_response.status();
190 if http_status_code != 200 {
191 let body = http_response.text().await.map_err(|e| GetDataError(e.to_string()))?;
192 return Err(GetDataError(format!("Http error: status:{}, error:{}", http_status_code, body)));
193 }
194
195 let resp: GetStationRealKpiResponse = http_response
196 .json()
197 .await
198 .map_err(|e| GetDataError(e.to_string()))?;
199
200 if !resp.success {
201 return Err(GetDataError(
202 resp.message
203 .unwrap_or(format!("failCode:{}", resp.fail_code)),
204 ));
205 }
206 Ok(resp.data.unwrap().first().unwrap().data_item_map.clone())
207 }
208
209 async fn get_device_list(&self, station_code: &str) -> Result<Vec<GetDevListData>, Error> {
210 let headers = self.ensure_token().await?;
211 let params = GetDevListRequest {
212 station_codes: station_code.to_string(),
213 };
214 let http_response = self
215 .client
216 .post(format!("{}/thirdData/getDevList", self.base_url))
217 .headers(headers)
218 .json(¶ms)
219 .send()
220 .await
221 .map_err(|e| GetDataError(e.to_string()))?;
222
223 let http_status_code = http_response.status();
224 if http_status_code != 200 {
225 let body = http_response.text().await.map_err(|e| GetDataError(e.to_string()))?;
226 return Err(GetDataError(format!("Http error: status:{}, error:{}", http_status_code, body)));
227 }
228
229 let resp: GetDevListResponse = http_response
230 .json()
231 .await
232 .map_err(|e| GetDataError(e.to_string()))?;
233
234 if !resp.success {
235 return Err(GetDataError(
236 resp.message
237 .unwrap_or(format!("failCode:{}", resp.fail_code)),
238 ));
239 }
240 Ok(resp.data.unwrap())
241 }
242
243 async fn get_realtime_residential_inverter_data(
244 &self,
245 device_id: i64,
246 ) -> Result<GetDevRealKpiDataInverterItem, Error> {
247 self.get_realtime_device_data(device_id).await
248 }
249
250 async fn get_realtime_power_sensor_data(
251 &self,
252 device_id: i64,
253 ) -> Result<GetDevRealKpiDataPowerSensorItem, Error> {
254 self.get_realtime_device_data(device_id).await
255 }
256}