Skip to main content

hive_client/client/api/
products.rs

1use crate::Client;
2use crate::client::api::ApiError;
3use crate::client::api::HiveApi;
4use crate::client::authentication::Tokens;
5use crate::helper::url::{Url, get_base_url};
6use chrono::{DateTime, Utc, serde::ts_milliseconds, serde::ts_milliseconds_option};
7use reqwest::StatusCode;
8use serde::{Deserialize, Serialize};
9use serde_json::Value;
10use serde_with::{EnumMap, serde_as};
11use std::collections::HashMap;
12use std::fmt;
13use std::fmt::{Debug, Display, Formatter};
14use std::ops::Deref;
15
16#[derive(Serialize, Deserialize, Debug)]
17#[non_exhaustive]
18#[allow(missing_docs)]
19pub struct Properties {
20    #[serde(rename = "zone")]
21    /// The ID of the zone the device is located in (if applicable).
22    pub zone_id: Option<String>,
23
24    #[serde(rename = "online")]
25    /// Whether the device is currently online or not.
26    pub is_online: bool,
27
28    #[serde(rename = "working")]
29    /// Whether the device is currently running or not.
30    pub is_working: bool,
31
32    /// The current temperature by the Hive product.
33    pub temperature: Option<f32>,
34
35    #[serde(flatten)]
36    #[allow(missing_docs)]
37    pub extra: HashMap<String, Value>,
38}
39
40#[derive(Serialize, Deserialize, Debug)]
41#[serde(rename_all = "lowercase")]
42#[serde(tag = "type")]
43#[non_exhaustive]
44/// Data about a Hive product.
45pub enum ProductData {
46    /// A Hive Heating product.
47    Heating {
48        /// The unique ID of the Hive Heating product.
49        id: String,
50
51        #[serde(default, with = "ts_milliseconds_option")]
52        /// The date and time when the Hive Heating product last communicated with the Hive servers.
53        last_seen: Option<DateTime<Utc>>,
54
55        #[serde(with = "ts_milliseconds")]
56        #[serde(rename = "created")]
57        /// The date and time when the Hive Heating product was first created.
58        created_at: DateTime<Utc>,
59
60        #[serde(rename = "props")]
61        /// The properties of the Hive Heating product.
62        properties: Properties,
63
64        /// The current state of the Hive Heating product.
65        state: States,
66
67        #[serde(flatten)]
68        #[allow(missing_docs)]
69        extra: HashMap<String, Value>,
70    },
71
72    /// A Hive Hot Water product.
73    HotWater {
74        /// The unique ID of the Hive Hot Water product.
75        id: String,
76
77        #[serde(default, with = "ts_milliseconds_option")]
78        /// The date and time when the Hive Hot Water product last communicated with the Hive servers.
79        last_seen: Option<DateTime<Utc>>,
80
81        #[serde(with = "ts_milliseconds")]
82        #[serde(rename = "created")]
83        /// The date and time when the Hive Hot Water product was first created.
84        created_at: DateTime<Utc>,
85
86        #[serde(rename = "props")]
87        /// The properties of the Hive Hot Water product.
88        properties: Properties,
89
90        /// The current state of the Hive Hot Water product.
91        state: States,
92
93        #[serde(flatten)]
94        #[allow(missing_docs)]
95        extra: HashMap<String, Value>,
96    },
97
98    #[serde(other)]
99    /// A product which is yet to be mapped by the crate.
100    Unknown,
101}
102
103#[derive(Debug, Serialize, Deserialize)]
104#[serde(rename_all = "UPPERCASE")]
105/// The mode of a Hive product.
106///
107/// This applies to both [`ProductData::Heating`] and [`ProductData::HotWater`], which can be
108/// either in `Off`, `Schedule` or `Manual` mode.
109pub enum Mode {
110    /// The product is turned off.
111    Off,
112
113    /// The product is in schedule mode.
114    Schedule,
115
116    /// The product is in manual mode.
117    Manual,
118}
119
120impl Display for Mode {
121    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
122        match self {
123            Self::Off => write!(f, "Off"),
124            Self::Schedule => write!(f, "Schedule"),
125            Self::Manual => write!(f, "Manual"),
126        }
127    }
128}
129
130#[derive(Debug, Serialize, Deserialize)]
131#[serde(rename_all = "camelCase")]
132#[non_exhaustive]
133/// The state of a particular facet of a Hive product.
134///
135/// Not all products will have all states - for example [`ProductData::HotWater`] will not have
136/// [`State::TargetTemperature`]
137pub enum State {
138    #[serde(rename = "target")]
139    /// The target temperature of the Hive product.
140    TargetTemperature(f32),
141
142    /// The mode of the Hive product.
143    Mode(Mode),
144
145    /// The name of the Hive product.
146    Name(String),
147
148    /// The status of the Hive product.
149    Status(String),
150
151    /// Whether the Hive product is currently boosted or not.
152    Boost(Option<bool>),
153
154    /// The temperature of the Frost Protection mode.
155    FrostProtection(u32),
156
157    /// Whether the Hive product will choose an Optimum Start time or not when
158    /// in scheduled mode.
159    OptimumStart(bool),
160
161    /// Whether the Hive product is currently in Auto Boost mode or not.
162    AutoBoost(String),
163
164    /// The target temperature of the Auto Boost mode.
165    AutoBoostTarget(u32),
166
167    /// The schedule for the Hive product, when it is in [`Mode::Schedule`].
168    Schedule(HashMap<String, Value>),
169}
170
171impl Display for State {
172    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
173        match self {
174            Self::TargetTemperature(temp) => write!(f, "{temp}"),
175            Self::Mode(value) => write!(f, "{value}"),
176            Self::Name(value) | Self::Status(value) | Self::AutoBoost(value) => {
177                write!(f, "{value}")
178            }
179            Self::Boost(value) => write!(f, "{value:?}"),
180            Self::FrostProtection(value) | Self::AutoBoostTarget(value) => write!(f, "{value}"),
181            Self::OptimumStart(value) => write!(f, "{value}"),
182            Self::Schedule(value) => write!(f, "{value:?}"),
183        }
184    }
185}
186
187#[serde_as]
188#[derive(Debug, Serialize, Deserialize)]
189/// A collection of states for a Hive product.
190pub struct States(#[serde_as(as = "EnumMap")] pub Vec<State>);
191
192impl Deref for States {
193    type Target = Vec<State>;
194
195    fn deref(&self) -> &Self::Target {
196        &self.0
197    }
198}
199
200/// A Product which is enabled in a Hive account.
201///
202/// For example, a [`ProductData::Heating`], a [`ProductData::HotWater`], etc.
203pub struct Product<'a> {
204    client: &'a Client,
205
206    #[allow(missing_docs)]
207    pub data: ProductData,
208}
209
210impl Debug for Product<'_> {
211    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
212        f.debug_struct("Product").field("data", &self.data).finish()
213    }
214}
215
216impl Product<'_> {
217    #[must_use]
218    pub(crate) const fn new(client: &Client, data: ProductData) -> Product<'_> {
219        Product { client, data }
220    }
221
222    /// Set the state of a product.
223    ///
224    /// For example, setting the target temperature of the Heating product, set the mode
225    /// ([`crate::products::State::Mode`]) of a Hot Water product, etc.
226    ///
227    /// # Errors
228    ///
229    /// Returns an error if the state could not be set for the product.
230    pub async fn set_state(&mut self, states: States) -> Result<bool, ApiError> {
231        self.client
232            .set_product_state(
233                match &self.data {
234                    ProductData::HotWater { id, .. } | ProductData::Heating { id, .. } => id,
235                    ProductData::Unknown => "",
236                },
237                match &self.data {
238                    ProductData::Heating { .. } => "heating",
239                    ProductData::HotWater { .. } => "hotwater",
240                    ProductData::Unknown => "unknown",
241                },
242                states,
243            )
244            .await
245    }
246}
247
248impl HiveApi {
249    pub(crate) async fn get_product_data(
250        &self,
251        tokens: &Tokens,
252    ) -> Result<Vec<ProductData>, ApiError> {
253        let response = self
254            .client
255            .get(get_base_url(&Url::Products))
256            .header("Authorization", &tokens.id_token)
257            .send()
258            .await;
259
260        response?
261            .json::<Vec<ProductData>>()
262            .await
263            .map_err(ApiError::from)
264    }
265
266    pub(crate) async fn set_product_state(
267        &self,
268        tokens: &Tokens,
269        id: &str,
270        r#type: &str,
271        states: States,
272    ) -> Result<bool, ApiError> {
273        let response = self
274            .client
275            .post(get_base_url(&Url::Node {
276                id: Some(id),
277                r#type: Some(r#type),
278            }))
279            .body(serde_json::to_string(&states)?)
280            .header("Authorization", &tokens.id_token)
281            .send()
282            .await?;
283
284        Ok(response.status() == StatusCode::OK)
285    }
286}