hive_client/client/api/
products.rs

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