use crate::Client;
use crate::client::api::ApiError;
use crate::client::api::HiveApi;
use crate::client::authentication::Tokens;
use crate::helper::url::{Url, get_base_url};
use chrono::{DateTime, Utc, serde::ts_milliseconds, serde::ts_milliseconds_option};
use reqwest::StatusCode;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use serde_with::{EnumMap, serde_as};
use std::collections::HashMap;
use std::fmt;
use std::fmt::{Debug, Display, Formatter};
use std::ops::Deref;
#[derive(Serialize, Deserialize, Debug)]
#[non_exhaustive]
#[allow(missing_docs)]
pub struct Properties {
#[serde(rename = "zone")]
pub zone_id: Option<String>,
#[serde(rename = "online")]
pub is_online: bool,
#[serde(rename = "working")]
pub is_working: bool,
pub temperature: Option<f32>,
#[serde(flatten)]
#[allow(missing_docs)]
pub extra: HashMap<String, Value>,
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "lowercase")]
#[serde(tag = "type")]
#[non_exhaustive]
pub enum ProductData {
Heating {
id: String,
#[serde(default, with = "ts_milliseconds_option")]
last_seen: Option<DateTime<Utc>>,
#[serde(with = "ts_milliseconds")]
#[serde(rename = "created")]
created_at: DateTime<Utc>,
#[serde(rename = "props")]
properties: Properties,
state: States,
#[serde(flatten)]
#[allow(missing_docs)]
extra: HashMap<String, Value>,
},
HotWater {
id: String,
#[serde(default, with = "ts_milliseconds_option")]
last_seen: Option<DateTime<Utc>>,
#[serde(with = "ts_milliseconds")]
#[serde(rename = "created")]
created_at: DateTime<Utc>,
#[serde(rename = "props")]
properties: Properties,
state: States,
#[serde(flatten)]
#[allow(missing_docs)]
extra: HashMap<String, Value>,
},
#[serde(other)]
Unknown,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum Mode {
Off,
Schedule,
Manual,
}
impl Display for Mode {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::Off => write!(f, "Off"),
Self::Schedule => write!(f, "Schedule"),
Self::Manual => write!(f, "Manual"),
}
}
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub enum State {
#[serde(rename = "target")]
TargetTemperature(f32),
Mode(Mode),
Name(String),
Status(String),
Boost(Option<bool>),
FrostProtection(u32),
OptimumStart(bool),
AutoBoost(String),
AutoBoostTarget(u32),
Schedule(HashMap<String, Value>),
}
impl Display for State {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::TargetTemperature(temp) => write!(f, "{temp}"),
Self::Mode(value) => write!(f, "{value}"),
Self::Name(value) | Self::Status(value) | Self::AutoBoost(value) => {
write!(f, "{value}")
}
Self::Boost(value) => write!(f, "{value:?}"),
Self::FrostProtection(value) | Self::AutoBoostTarget(value) => write!(f, "{value}"),
Self::OptimumStart(value) => write!(f, "{value}"),
Self::Schedule(value) => write!(f, "{value:?}"),
}
}
}
#[serde_as]
#[derive(Debug, Serialize, Deserialize)]
pub struct States(#[serde_as(as = "EnumMap")] pub Vec<State>);
impl Deref for States {
type Target = Vec<State>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
pub struct Product<'a> {
client: &'a Client,
#[allow(missing_docs)]
pub data: ProductData,
}
impl Debug for Product<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("Product").field("data", &self.data).finish()
}
}
impl Product<'_> {
#[must_use]
pub(crate) const fn new(client: &Client, data: ProductData) -> Product<'_> {
Product { client, data }
}
pub async fn set_state(&mut self, states: States) -> Result<bool, ApiError> {
self.client
.set_product_state(
match &self.data {
ProductData::HotWater { id, .. } | ProductData::Heating { id, .. } => id,
ProductData::Unknown => "",
},
match &self.data {
ProductData::Heating { .. } => "heating",
ProductData::HotWater { .. } => "hotwater",
ProductData::Unknown => "unknown",
},
states,
)
.await
}
}
impl HiveApi {
pub(crate) async fn get_product_data(
&self,
tokens: &Tokens,
) -> Result<Vec<ProductData>, ApiError> {
let response = self
.client
.get(get_base_url(&Url::Products))
.header("Authorization", &tokens.id_token)
.send()
.await;
response?
.json::<Vec<ProductData>>()
.await
.map_err(ApiError::from)
}
pub(crate) async fn set_product_state(
&self,
tokens: &Tokens,
id: &str,
r#type: &str,
states: States,
) -> Result<bool, ApiError> {
let response = self
.client
.post(get_base_url(&Url::Node {
id: Some(id),
r#type: Some(r#type),
}))
.body(serde_json::to_string(&states)?)
.header("Authorization", &tokens.id_token)
.send()
.await?;
Ok(response.status() == StatusCode::OK)
}
}