use super::http_client::HttpClient;
use crate::home_assistant::{http_client, schemas::StateCreateOrUpdate};
use chrono::DateTime;
use reqwest::Url;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("HTTP client error: {0}")]
HttpClientError(#[from] http_client::Error),
#[error("JSON serialization error: {0}")]
SerializationError(#[from] serde_json::Error),
}
type Result<T> = std::result::Result<T, Error>;
pub struct Client {
http: HttpClient,
}
impl Client {
pub fn new(url: &Url, token: &str) -> Self {
let http = HttpClient::new(url, token);
Client { http }
}
pub async fn set_solar_energy(
&self,
energy_today: i64,
last_reset: &DateTime<chrono::Local>,
) -> Result<()> {
let state = Self::create_solar_energy_state(energy_today, last_reset);
self.http.set_state("sensor.solar_energy", &state).await?;
Ok(())
}
pub async fn set_solar_current_power(&self, power: i64) -> Result<()> {
let state = Self::create_solar_current_power_state(power);
self.http.set_state("sensor.solar_power", &state).await?;
Ok(())
}
pub async fn set_solar_status(&self, status: &str) -> Result<()> {
let state = Self::create_solar_status_state(status);
self.http.set_state("sensor.solar_status", &state).await?;
Ok(())
}
fn create_solar_current_power_state(power: i64) -> StateCreateOrUpdate {
StateCreateOrUpdate {
state: power.to_string(),
attributes: Some(
[
("unit_of_measurement".to_string(), "W".to_string()),
("friendly_name".to_string(), "Solar Power".to_string()),
("state_class".to_string(), "measurement".to_string()),
]
.into_iter()
.collect(),
),
}
}
fn create_solar_energy_state<Tz: chrono::TimeZone>(
energy_today: i64,
last_reset: &DateTime<Tz>,
) -> StateCreateOrUpdate {
let kwh = energy_today as f64 / 1000.0; StateCreateOrUpdate {
state: kwh.to_string(),
attributes: Some(
[
("unit_of_measurement".to_string(), "kWh".to_string()),
("friendly_name".to_string(), "Solar Energy".to_string()),
("device_class".to_string(), "energy".to_string()),
("state_class".to_string(), "total_increasing".to_string()),
("last_reset".to_string(), last_reset.to_rfc3339()),
]
.into_iter()
.collect(),
),
}
}
fn create_solar_status_state(status: &str) -> StateCreateOrUpdate {
StateCreateOrUpdate {
state: status.to_string(),
attributes: Some(
[("friendly_name".to_string(), "Solar Status".to_string())]
.into_iter()
.collect(),
),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use rstest::rstest;
#[tokio::test]
async fn test_new() {
let url = Url::parse("http://localhost:8123").unwrap();
let token = "test_token";
Client::new(&url, token);
}
#[rstest]
#[case(1234, "1234")]
#[case(0, "0")]
#[case(-567, "-567")]
fn test_create_solar_current_power_state(#[case] power: i64, #[case] expected_state: &str) {
let expected = StateCreateOrUpdate {
state: expected_state.to_string(),
attributes: Some(
[
("unit_of_measurement".to_string(), "W".to_string()),
("friendly_name".to_string(), "Solar Power".to_string()),
("state_class".to_string(), "measurement".to_string()),
]
.into_iter()
.collect(),
),
};
let state = Client::create_solar_current_power_state(power);
assert_eq!(state, expected);
}
#[rstest]
#[case(5000, "5")]
#[case(0, "0")]
#[case(-5000, "-5")]
#[case(1234, "1.234")]
#[case(-1234, "-1.234")]
#[case(123, "0.123")]
#[case(-123, "-0.123")]
fn test_create_solar_energy_state(#[case] energy_today: i64, #[case] expected_state: &str) {
let last_reset = DateTime::parse_from_rfc3339("2023-10-01T00:00:00+01:00")
.unwrap()
.with_timezone(&chrono::FixedOffset::east_opt(3600).unwrap());
let expected = StateCreateOrUpdate {
state: expected_state.to_string(),
attributes: Some(
[
("unit_of_measurement".to_string(), "kWh".to_string()),
("friendly_name".to_string(), "Solar Energy".to_string()),
("device_class".to_string(), "energy".to_string()),
("state_class".to_string(), "total_increasing".to_string()),
(
"last_reset".to_string(),
"2023-10-01T00:00:00+01:00".to_string(),
),
]
.into_iter()
.collect(),
),
};
let state = Client::create_solar_energy_state(energy_today, &last_reset);
assert_eq!(state, expected);
}
#[rstest]
#[case("On-grid")]
#[case("Idle No irradiation")]
fn test_create_solar_status_state(#[case] status: &str) {
let expected = StateCreateOrUpdate {
state: status.to_string(),
attributes: Some(
[("friendly_name".to_string(), "Solar Status".to_string())]
.into_iter()
.collect(),
),
};
let state = Client::create_solar_status_state(status);
assert_eq!(state, expected);
}
}