Fix issue in Hoymiles where total energy decreases
Sometimes it can be that `today_eq` is reset when the day switches but it has not been added to `total_eq` yet. The `total_eq` should always be non-decreasing, so return the last known value until this is corrected (this most suredly happens during the night). Also, allow for `login` and `update` to mutate the state of the service to be able to update things like the last known total produced energy value.
This commit is contained in:
parent
5a2889a0f2
commit
d787c8b3ab
|
@ -44,8 +44,8 @@ pub(crate) trait Service {
|
||||||
fn poll_interval(&self) -> u64;
|
fn poll_interval(&self) -> u64;
|
||||||
|
|
||||||
/// Perfoms a login on the cloud service (if necessary).
|
/// Perfoms a login on the cloud service (if necessary).
|
||||||
async fn login(&self) -> Result<(), reqwest::Error>;
|
async fn login(&mut self) -> Result<(), reqwest::Error>;
|
||||||
|
|
||||||
/// Retrieves a status update using the API of the cloud service.
|
/// Retrieves a status update using the API of the cloud service.
|
||||||
async fn update(&self, timestamp: u64) -> Result<Status, reqwest::Error>;
|
async fn update(&mut self, timestamp: u64) -> Result<Status, reqwest::Error>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,10 +37,12 @@ pub(crate) fn service(config: Config) -> Result<Service, reqwest::Error> {
|
||||||
let client = ClientBuilder::new()
|
let client = ClientBuilder::new()
|
||||||
.cookie_provider(Arc::clone(&cookie_jar))
|
.cookie_provider(Arc::clone(&cookie_jar))
|
||||||
.build()?;
|
.build()?;
|
||||||
|
let total_kwh = 0f32;
|
||||||
let service = Service {
|
let service = Service {
|
||||||
client,
|
client,
|
||||||
config,
|
config,
|
||||||
cookie_jar,
|
cookie_jar,
|
||||||
|
total_kwh,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(service)
|
Ok(service)
|
||||||
|
@ -55,6 +57,8 @@ pub(crate) struct Service {
|
||||||
config: Config,
|
config: Config,
|
||||||
/// The cookie jar used for API requests.
|
/// The cookie jar used for API requests.
|
||||||
cookie_jar: Arc<CookieJar>,
|
cookie_jar: Arc<CookieJar>,
|
||||||
|
/// The last known total produced energy value.
|
||||||
|
total_kwh: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the login URL for the Hoymiles site.
|
/// Returns the login URL for the Hoymiles site.
|
||||||
|
@ -254,7 +258,7 @@ impl super::Service for Service {
|
||||||
/// It mainly stores the acquired cookies in the client's cookie jar and adds the token cookie
|
/// It mainly stores the acquired cookies in the client's cookie jar and adds the token cookie
|
||||||
/// provided by the logins response. The login credentials come from the loaded configuration
|
/// provided by the logins response. The login credentials come from the loaded configuration
|
||||||
/// (see [`Config`]).
|
/// (see [`Config`]).
|
||||||
async fn login(&self) -> Result<(), reqwest::Error> {
|
async fn login(&mut self) -> Result<(), reqwest::Error> {
|
||||||
let base_url = Url::parse(BASE_URL).expect("valid base URL");
|
let base_url = Url::parse(BASE_URL).expect("valid base URL");
|
||||||
let login_url = login_url().expect("valid login URL");
|
let login_url = login_url().expect("valid login URL");
|
||||||
let login_request = ApiLoginRequest::new(&self.config.username, &self.config.password);
|
let login_request = ApiLoginRequest::new(&self.config.username, &self.config.password);
|
||||||
|
@ -284,7 +288,7 @@ impl super::Service for Service {
|
||||||
/// It needs the cookies from the login to be able to perform the action.
|
/// It needs the cookies from the login to be able to perform the action.
|
||||||
/// It uses a endpoint to construct the [`Status`] struct, but it needs to summarize the today
|
/// It uses a endpoint to construct the [`Status`] struct, but it needs to summarize the today
|
||||||
/// value with the total value because Hoymiles only includes it after the day has finished.
|
/// value with the total value because Hoymiles only includes it after the day has finished.
|
||||||
async fn update(&self, last_updated: u64) -> Result<Status, reqwest::Error> {
|
async fn update(&mut self, last_updated: u64) -> Result<Status, reqwest::Error> {
|
||||||
let api_url = api_url().expect("valid API power URL");
|
let api_url = api_url().expect("valid API power URL");
|
||||||
let api_data_request = ApiDataRequest::new(self.config.sid);
|
let api_data_request = ApiDataRequest::new(self.config.sid);
|
||||||
let api_response = self
|
let api_response = self
|
||||||
|
@ -302,7 +306,16 @@ impl super::Service for Service {
|
||||||
Err(err) => return Err(err),
|
Err(err) => return Err(err),
|
||||||
};
|
};
|
||||||
let current_w = api_data.real_power;
|
let current_w = api_data.real_power;
|
||||||
let total_kwh = (api_data.total_eq + api_data.today_eq) / 1000.0;
|
let mut total_kwh = (api_data.total_eq + api_data.today_eq) / 1000.0;
|
||||||
|
|
||||||
|
// Sometimes it can be that `today_eq` is reset when the day switches but it has not been
|
||||||
|
// added to `total_eq` yet. The `total_eq` should always be non-decreasing, so return the
|
||||||
|
// last known value until this is corrected (this most suredly happens during the night).
|
||||||
|
if total_kwh <= self.total_kwh {
|
||||||
|
total_kwh = self.total_kwh
|
||||||
|
} else {
|
||||||
|
self.total_kwh = total_kwh;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(Status {
|
Ok(Status {
|
||||||
current_w,
|
current_w,
|
||||||
|
|
|
@ -86,7 +86,7 @@ impl super::Service for Service {
|
||||||
///
|
///
|
||||||
/// It mainly stores the acquired cookie in the client's cookie jar. The login credentials come
|
/// It mainly stores the acquired cookie in the client's cookie jar. The login credentials come
|
||||||
/// from the loaded configuration (see [`Config`]).
|
/// from the loaded configuration (see [`Config`]).
|
||||||
async fn login(&self) -> Result<(), reqwest::Error> {
|
async fn login(&mut self) -> Result<(), reqwest::Error> {
|
||||||
let params = [
|
let params = [
|
||||||
("username", &self.config.username),
|
("username", &self.config.username),
|
||||||
("password", &self.config.password),
|
("password", &self.config.password),
|
||||||
|
@ -101,7 +101,7 @@ impl super::Service for Service {
|
||||||
///
|
///
|
||||||
/// It needs the cookie from the login to be able to perform the action. It uses both the
|
/// It needs the cookie from the login to be able to perform the action. It uses both the
|
||||||
/// `energy` and `power` endpoint to construct the [`Status`] struct.
|
/// `energy` and `power` endpoint to construct the [`Status`] struct.
|
||||||
async fn update(&self, last_updated: u64) -> Result<Status, reqwest::Error> {
|
async fn update(&mut self, last_updated: u64) -> Result<Status, reqwest::Error> {
|
||||||
// Retrieve the data from the API endpoints.
|
// Retrieve the data from the API endpoints.
|
||||||
let api_energy_url = api_url(&self.config.site_id, "energy").expect("valid API energy URL");
|
let api_energy_url = api_url(&self.config.site_id, "energy").expect("valid API energy URL");
|
||||||
let api_response = self.client.get(api_energy_url).send().await?;
|
let api_response = self.client.get(api_energy_url).send().await?;
|
||||||
|
|
|
@ -15,6 +15,8 @@ use crate::{
|
||||||
/// It updates the mutex-guarded current update [`Status`](crate::Status) struct which can be
|
/// It updates the mutex-guarded current update [`Status`](crate::Status) struct which can be
|
||||||
/// retrieved via Rocket.
|
/// retrieved via Rocket.
|
||||||
pub(super) async fn update_loop(service: Services) -> color_eyre::Result<()> {
|
pub(super) async fn update_loop(service: Services) -> color_eyre::Result<()> {
|
||||||
|
let mut service = service;
|
||||||
|
|
||||||
// Log in on the cloud service.
|
// Log in on the cloud service.
|
||||||
println!("⚡ Logging in...");
|
println!("⚡ Logging in...");
|
||||||
service.login().await?;
|
service.login().await?;
|
||||||
|
|
Loading…
Reference in New Issue