use std::collections::HashMap;
use std::ops::Add;
use std::sync::Mutex;
use std::time::{Duration, Instant};
use async_trait::async_trait;
use log::trace;
use reqwest::header::HeaderMap;
use crate::api_requests::{GetDevListRequest, GetDevRealKpiRequest, LoginRequest};
use crate::api_responses::{GetDevListData, GetDevListResponse, GetDevRealKpiDataInverterItem, GetDevRealKpiDataItem, GetDevRealKpiDataPowerSensorItem, GetDevRealKpiResponse, GetStationData, GetStationListResponse, GetStationRealKpiResponse, LoginResponse, RealtimeData};
use crate::client::HuaweiClient;
use crate::errors::Error;
use crate::errors::Error::{GetDataError, LoginError};
pub struct HuaweiClientImpl {
base_url: String,
username: String,
password: String,
xsrf_token: Mutex<Option<String>>,
login_expiration: Mutex<Instant>,
client: reqwest::Client,
}
impl HuaweiClientImpl {
pub fn new(base_url: &str, username: &str, password: &str) -> Self {
Self {
base_url: base_url.to_string(),
username: username.to_string(),
password: password.to_string(),
xsrf_token: Mutex::new(None),
login_expiration: Mutex::new(Instant::now()),
client: reqwest::Client::new(),
}
}
pub fn default(username: &str, password: &str) -> Self {
Self::new("https://eu5.fusionsolar.huawei.com", username, password)
}
pub async fn login(&self) -> Result<String, Error> {
let params = LoginRequest::new(self.username.as_str(), self.password.as_str());
let result = self
.client
.post(format!("{}/thirdData/login", self.base_url))
.json(¶ms)
.send()
.await
.map_err(|e| LoginError(e.to_string()))?;
trace!("result: {:?}", result);
let result_headers = result.headers().clone();
let login_response: LoginResponse =
result.json().await.map_err(|e| LoginError(e.to_string()))?;
trace!("body: {:?}", login_response);
if !login_response.success {
return Err(LoginError(
login_response
.message
.unwrap_or(format!("failCode:{}", login_response.fail_code)),
));
}
return if let Some(xsrf_token) = result_headers.get("xsrf-token") {
let token = xsrf_token.to_str().unwrap().to_string();
let mut mut_xsrf_token = self.xsrf_token.lock().unwrap();
*mut_xsrf_token = Some(token.clone());
let mut login_expiration = self.login_expiration.lock().unwrap();
*login_expiration = Instant::now().add(Duration::from_secs(20 * 60));
Ok(token)
} else {
Err(LoginError(
"XSRF token header not found in the response".to_string(),
))
};
}
pub fn xsrf_token(&self) -> Option<String> {
self.xsrf_token.lock().unwrap().clone()
}
async fn ensure_token(&self) -> Result<HeaderMap, Error> {
let mut headers = HeaderMap::new();
let login_expiration = self.login_expiration.lock().unwrap().clone();
if login_expiration > Instant::now() {
if let Some(xsrf_token) = self.xsrf_token() {
headers.insert("XSRF-TOKEN", xsrf_token.parse().unwrap());
return Ok(headers);
}
}
headers.insert("XSRF-TOKEN", self.login().await?.parse().unwrap());
Ok(headers)
}
async fn get_realtime_device_data<T: GetDevRealKpiDataItem>(
&self,
device_id: i64,
) -> Result<T, Error> {
let headers = self.ensure_token().await?;
let params = GetDevRealKpiRequest {
dev_ids: device_id.to_string(),
dev_type_id: T::get_device_type(),
};
let http_response = self
.client
.post(format!("{}/thirdData/getDevRealKpi", self.base_url))
.headers(headers)
.json(¶ms)
.send()
.await
.map_err(|e| GetDataError(e.to_string()))?;
let http_status_code = http_response.status();
if http_status_code != 200 {
let body = http_response.text().await.map_err(|e| GetDataError(e.to_string()))?;
return Err(GetDataError(format!("Http error: status:{}, error:{}", http_status_code, body)));
}
let resp: GetDevRealKpiResponse<T> = http_response
.json()
.await
.map_err(|e| GetDataError(e.to_string()))?;
if !resp.success {
return Err(GetDataError(
resp.message
.unwrap_or(format!("failCode:{}", resp.fail_code)),
));
}
Ok(resp.data.unwrap().first().unwrap().data_item_map.clone())
}
}
#[async_trait]
impl HuaweiClient for HuaweiClientImpl {
async fn get_station_list(&self) -> Result<Vec<GetStationData>, Error> {
let headers = self.ensure_token().await?;
let http_response = self
.client
.post(format!("{}/thirdData/getStationList", self.base_url))
.headers(headers)
.send()
.await
.map_err(|e| GetDataError(e.to_string()))?;
let http_status_code = http_response.status();
if http_status_code != 200 {
let body = http_response.text().await.map_err(|e| GetDataError(e.to_string()))?;
return Err(GetDataError(format!("Http error: status:{}, error:{}", http_status_code, body)));
}
let resp: GetStationListResponse = http_response
.json()
.await
.map_err(|e| GetDataError(e.to_string()))?;
if !resp.success {
return Err(GetDataError(
resp.message
.unwrap_or(format!("failCode:{}", resp.fail_code)),
));
}
Ok(resp.data.unwrap())
}
async fn get_realtime_station_data(&self, station_code: &str) -> Result<RealtimeData, Error> {
let params = HashMap::from([("stationCodes", station_code)]);
let headers = self.ensure_token().await?;
let request = self
.client
.post(format!("{}/thirdData/getStationRealKpi", self.base_url))
.json(¶ms)
.headers(headers)
.build()
.map_err(|e| GetDataError(e.to_string()))?;
trace!("request:{:?}", request);
let http_response = self
.client
.execute(request)
.await
.map_err(|e| GetDataError(e.to_string()))?;
let http_status_code = http_response.status();
if http_status_code != 200 {
let body = http_response.text().await.map_err(|e| GetDataError(e.to_string()))?;
return Err(GetDataError(format!("Http error: status:{}, error:{}", http_status_code, body)));
}
let resp: GetStationRealKpiResponse = http_response
.json()
.await
.map_err(|e| GetDataError(e.to_string()))?;
if !resp.success {
return Err(GetDataError(
resp.message
.unwrap_or(format!("failCode:{}", resp.fail_code)),
));
}
Ok(resp.data.unwrap().first().unwrap().data_item_map.clone())
}
async fn get_device_list(&self, station_code: &str) -> Result<Vec<GetDevListData>, Error> {
let headers = self.ensure_token().await?;
let params = GetDevListRequest {
station_codes: station_code.to_string(),
};
let http_response = self
.client
.post(format!("{}/thirdData/getDevList", self.base_url))
.headers(headers)
.json(¶ms)
.send()
.await
.map_err(|e| GetDataError(e.to_string()))?;
let http_status_code = http_response.status();
if http_status_code != 200 {
let body = http_response.text().await.map_err(|e| GetDataError(e.to_string()))?;
return Err(GetDataError(format!("Http error: status:{}, error:{}", http_status_code, body)));
}
let resp: GetDevListResponse = http_response
.json()
.await
.map_err(|e| GetDataError(e.to_string()))?;
if !resp.success {
return Err(GetDataError(
resp.message
.unwrap_or(format!("failCode:{}", resp.fail_code)),
));
}
Ok(resp.data.unwrap())
}
async fn get_realtime_residential_inverter_data(
&self,
device_id: i64,
) -> Result<GetDevRealKpiDataInverterItem, Error> {
self.get_realtime_device_data(device_id).await
}
async fn get_realtime_power_sensor_data(
&self,
device_id: i64,
) -> Result<GetDevRealKpiDataPowerSensorItem, Error> {
self.get_realtime_device_data(device_id).await
}
}