hust/
bridge.rs

1use crate::error::{ApiError, Result};
2use crate::lights::Light;
3use reqwest::blocking::get;
4use std::collections::HashMap;
5
6#[derive(Deserialize, Serialize, Debug)]
7/// Core defice infoormation about a bridge
8pub struct BridgeDevice {
9    #[serde(rename = "UDN")]
10    pub udn: String,
11    #[serde(rename = "deviceType")]
12    pub device_type: String,
13    pub manufacturer: String,
14    #[serde(rename = "modelName")]
15    pub model_name: String,
16    #[serde(rename = "modelDescription")]
17    pub model_description: String,
18    #[serde(rename = "serialNumber")]
19    pub serial_number: String,
20    #[serde(rename = "friendlyName")]
21    pub friendly_name: String,
22}
23
24/// An object to communicate with a bridge.
25/// 
26/// The struct attributes contain the static properties of the bridge.
27/// 
28/// The methods can be used for communication like commands.
29#[derive(Deserialize, Serialize, Debug)]
30pub struct Bridge {
31    #[serde(rename = "URLBase")]
32    /// The base URL of the bridge, which all `/api/` resources are below of.
33    pub url_base: String,
34    /// The device properties of this bridge.
35    pub device: BridgeDevice,
36}
37
38#[derive(Deserialize, Debug)]
39/// Basic element of a response from a bridge
40/// 
41/// Bridges will send back a list of this, serialized as JSON
42pub enum ApiResponseSection {
43    #[serde(rename = "error")]
44    Err(ApiError),
45    #[serde(rename = "success")]
46    Success(HashMap<String, serde_json::Value>),
47}
48
49impl Bridge {
50    /// Creates a Bridge object from a description URL like returned in SSDP discovery.
51    pub fn from_description_url(url: String) -> Result<Bridge> {
52        let response = get(&url)?.text()?;
53        let bridge: Bridge = serde_xml_rs::from_str(&response)?;
54        Ok(bridge)
55    }
56
57    /// The unique but user-friendly name of the bridge.
58    pub fn user_readable_identifier(&self) -> &str {
59        &self.device.friendly_name
60    }
61
62    /// Registers a user and return its name.
63    /// 
64    /// Save it to communicate further with the bridge, e.g. to switch lights.
65    /// 
66    /// Note that the button of the bridge has to be pressed.
67    pub fn register_user(&self) -> Result<String> {
68        let client = reqwest::blocking::Client::new();
69		let mut url = self.url_base.clone();
70		url.push_str("api");
71        let mut params = HashMap::new();
72        params.insert("devicetype", "Hust Hue API client");
73        let response = client.post(&url).json(&params).send()?;
74        let response: Vec<ApiResponseSection> = serde_json::from_reader(response)?;
75        // Now, analyze the response to measure success or failure.
76        let mut errors = vec![];
77        let mut success = None;
78        for section in response {
79            match section {
80                ApiResponseSection::Err(e) => errors.push(e),
81                ApiResponseSection::Success(hashmap) => success = Some(hashmap),
82            }
83        }
84        if let Some(hashmap) = success {
85            if let Some(username) = hashmap.get("username") {
86                return Ok(username.to_string());
87            }
88        }
89		Err(errors)?
90    }
91
92    /// Analyzes the response to a light changing request
93    /// 
94    /// To measure success or failure of an operation that tried to modify
95    /// a light, its response has to be looked over
96    fn light_change_result(&self, response: Vec<ApiResponseSection>) -> Result<()> {
97        let mut errors = vec![];
98        let success = response
99            .into_iter()
100            .any(|section| // Does any part of the response indicate failure?
101                match section {
102                    ApiResponseSection::Success(_) => true,
103                    ApiResponseSection::Err(e) => {
104                        errors.push(e);
105                        false
106                    }
107                });
108        if success {
109            return Ok(())
110        } 
111        Err(errors)?
112    }
113
114    /// Set an attribute of a light.
115    /// 
116    /// `user` is the user you had to register with `register_user`.
117    /// 
118    /// `light` is the identifier of the light. All identifiers can
119    /// be obtained by listing the HashMap keys of `get_all_lights`.
120    /// 
121    /// `key` can be any attribute of [`crate::lights::LightState`].
122    pub fn modify_light<T: serde::ser::Serialize>(&self, user: &str, light: &str, key: &str, value: T) -> Result<()> {
123        let client = reqwest::blocking::Client::new();
124        let url = format!("{}api/{}/lights/{}/state", self.url_base, user, light);
125        let mut params = HashMap::new();
126        params.insert(key, value);
127        let response = client
128            .put(&url)
129            .json(&params)
130            .send()?;
131        let response: Vec<ApiResponseSection> = serde_json::from_reader(response)?;
132        self.light_change_result(response)
133    }
134    
135    /// List all lights connected to this bridge
136    /// 
137    /// The listed lights are bundled with their state. You have to
138    /// specify a user in order to be authenticated.
139    pub fn get_all_lights(&self, user: &str) -> Result<HashMap<String, Light>> {
140        let url = format!("{}api/{}/lights", self.url_base, user);
141        let response = get(&url)?;
142        Ok(serde_json::from_reader(response)?)
143    }
144
145    /// Switch light on / off.
146    /// 
147    /// `user` is the user you had to register with `register_user`.
148    /// 
149    /// `light` is the identifier of the light. All identifiers can
150    /// be obtained by listing the HashMap keys of `get_all_lights`.
151    /// 
152    /// To switch the light off, specify `on` as `false`.
153    pub fn switch_light(&self, user: &str, light: &str, on: bool) -> Result<()> {
154        self.modify_light(user, light, "on", on)
155    }
156}