huawei_client/
client_impl.rs

1use 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(&params)
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(&params)
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(&params)
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(&params)
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}