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:
Paul van Tilburg 2023-01-15 13:38:33 +01:00
parent 5a2889a0f2
commit d787c8b3ab
Signed by: paul
GPG Key ID: C6DE073EDA9EEC4D
4 changed files with 22 additions and 7 deletions

View File

@ -44,8 +44,8 @@ pub(crate) trait Service {
fn poll_interval(&self) -> u64;
/// 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.
async fn update(&self, timestamp: u64) -> Result<Status, reqwest::Error>;
async fn update(&mut self, timestamp: u64) -> Result<Status, reqwest::Error>;
}

View File

@ -37,10 +37,12 @@ pub(crate) fn service(config: Config) -> Result<Service, reqwest::Error> {
let client = ClientBuilder::new()
.cookie_provider(Arc::clone(&cookie_jar))
.build()?;
let total_kwh = 0f32;
let service = Service {
client,
config,
cookie_jar,
total_kwh,
};
Ok(service)
@ -55,6 +57,8 @@ pub(crate) struct Service {
config: Config,
/// The cookie jar used for API requests.
cookie_jar: Arc<CookieJar>,
/// The last known total produced energy value.
total_kwh: f32,
}
/// 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
/// provided by the logins response. The login credentials come from the loaded configuration
/// (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 login_url = login_url().expect("valid login URL");
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 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.
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_data_request = ApiDataRequest::new(self.config.sid);
let api_response = self
@ -302,7 +306,16 @@ impl super::Service for Service {
Err(err) => return Err(err),
};
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 {
current_w,

View File

@ -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
/// from the loaded configuration (see [`Config`]).
async fn login(&self) -> Result<(), reqwest::Error> {
async fn login(&mut self) -> Result<(), reqwest::Error> {
let params = [
("username", &self.config.username),
("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
/// `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.
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?;

View File

@ -15,6 +15,8 @@ use crate::{
/// It updates the mutex-guarded current update [`Status`](crate::Status) struct which can be
/// retrieved via Rocket.
pub(super) async fn update_loop(service: Services) -> color_eyre::Result<()> {
let mut service = service;
// Log in on the cloud service.
println!("⚡ Logging in...");
service.login().await?;