From 36098a124a1d62a3e86be3f4a5032d121c058fc2 Mon Sep 17 00:00:00 2001 From: Christiane Ruetten Date: Wed, 13 Apr 2016 19:47:25 +0200 Subject: [PATCH] Adding Color support for Philips Hue --- docs/scripts/huedemo.sh | 70 ------------- src/adapters/philips_hue/hub_api.rs | 15 ++- src/adapters/philips_hue/lights.rs | 148 +++++++++++++++++++++++++++- src/adapters/philips_hue/mod.rs | 18 +++- tools/scripts/huedemo.sh | 107 ++++++++++++++++++++ 5 files changed, 281 insertions(+), 77 deletions(-) delete mode 100755 docs/scripts/huedemo.sh create mode 100755 tools/scripts/huedemo.sh diff --git a/docs/scripts/huedemo.sh b/docs/scripts/huedemo.sh deleted file mode 100755 index 7412610a..00000000 --- a/docs/scripts/huedemo.sh +++ /dev/null @@ -1,70 +0,0 @@ -#!/bin/bash - -# This is a quick&dirty demo of how to talk to the Philips HUe adapter. -# You must disable authenticaion before using this script. - -URL="http://localhost:3000" -API="$URL/api/v1" -SERVICES="$API/services" -GET="$API/channels/get" -SET="$API/channels/set" - -function channels() { - curl -s -X GET "$SERVICES" \ - | tr '"' \\012 \ - | grep "etter:" \ - | grep philips_hue \ - | sort -u -} - -function getters() { - channels | grep ^getter: -} - -function setters() { - channels | grep ^setter: -} - -function get() { - curl -s -X PUT "$GET" -d "{\"id\":\"$1\"}" -} - -function set() { - curl -s -X PUT "$SET" -d "{\"select\":{\"id\":\"$1\"},\"value\":{$2}}" -} - - -for g in `getters | grep available` -do - echo -n $g: - get $g - echo -done - -function all_on() { - for g in `setters | grep power` - do - echo -n $g: - set $g '"OnOff":"On"' - echo - done -} - -function all_off() { - for g in `setters | grep power` - do - echo -n $g: - set $g '"OnOff":"Off"' - echo - done -} - -for x in `seq 3` -do - all_on - sleep 1 - all_off - sleep 1 -done - - diff --git a/src/adapters/philips_hue/hub_api.rs b/src/adapters/philips_hue/hub_api.rs index 98bfbd4e..9b6d395d 100644 --- a/src/adapters/philips_hue/hub_api.rs +++ b/src/adapters/philips_hue/hub_api.rs @@ -168,17 +168,22 @@ impl HubApi { structs::parse_json(&res).unwrap() // TODO no unwrap } - #[allow(dead_code)] - pub fn set_light_color(&self, light_id: &str, hue: u32, sat: u32, val: u32, on: bool) { + pub fn set_light_power(&self, light_id: &str, on: bool) { let url = format!("lights/{}/state", light_id); - let cmd = json!({ hue: hue, sat: sat, bri: val, on: on }); + let cmd = json!({ on: on }); let _ = self.put(&url, &cmd); } - pub fn set_light_power(&self, light_id: &str, on: bool) { + pub fn set_light_color(&self, light_id: &str, hsv: (u32, u32, u32)) { + let (hue, sat, val) = hsv; let url = format!("lights/{}/state", light_id); - let cmd = json!({ on: on }); + let cmd = json!({ hue: hue, sat: sat, bri: val }); let _ = self.put(&url, &cmd); } + pub fn set_light_brightness(&self, light_id: &str, bri: u32) { + let url = format!("lights/{}/state", light_id); + let cmd = json!({ bri: bri }); + let _ = self.put(&url, &cmd); + } } diff --git a/src/adapters/philips_hue/lights.rs b/src/adapters/philips_hue/lights.rs index 4f475d78..065dc598 100644 --- a/src/adapters/philips_hue/lights.rs +++ b/src/adapters/philips_hue/lights.rs @@ -31,6 +31,8 @@ pub struct Light { pub get_available_id: Id, pub get_power_id: Id, pub set_power_id: Id, + pub get_color_id: Id, + pub set_color_id: Id, } impl Light { @@ -45,6 +47,8 @@ impl Light { get_available_id: create_getter_id("available", &hub_id, &light_id), get_power_id: create_getter_id("power", &hub_id, &light_id), set_power_id: create_setter_id("power", &hub_id, &light_id), + get_color_id: create_getter_id("color", &hub_id, &light_id), + set_color_id: create_setter_id("color", &hub_id, &light_id), } } pub fn start(&self) { @@ -58,9 +62,12 @@ impl Light { { let adapter_id = create_adapter_id(); let status = self.api.lock().unwrap().get_light_status(&self.light_id); + if status.lighttype == "Extended color light" { - info!("New Philips Hue Extended Color Light service for light {} on bridge {}", + + info!("New Philips Hue `Extended Color Light` service for light {} on bridge {}", self.light_id, self.hub_id); + let mut service = Service::empty(self.service_id.clone(), adapter_id.clone()); service.properties.insert(CUSTOM_PROPERTY_MANUFACTURER.to_owned(), status.manufacturername.to_owned()); @@ -119,6 +126,104 @@ impl Light { }, })); + try!(manager.add_getter(Channel { + tags: HashSet::new(), + adapter: adapter_id.clone(), + id: self.get_color_id.clone(), + last_seen: None, + service: self.service_id.clone(), + mechanism: Getter { + kind: ChannelKind::Extension { + vendor: Id::new("foxlink@mozilla.com"), + adapter: Id::new("Philips Hue Adapter"), + kind: Id::new("Color"), + typ: Type::Color, + }, + updated: None, + }, + })); + + try!(manager.add_setter(Channel { + tags: HashSet::new(), + adapter: adapter_id.clone(), + id: self.set_color_id.clone(), + last_seen: None, + service: self.service_id.clone(), + mechanism: Setter { + kind: ChannelKind::Extension { + vendor: Id::new("foxlink@mozilla.com"), + adapter: Id::new("Philips Hue Adapter"), + kind: Id::new("Color"), + typ: Type::Color, + }, + updated: None, + }, + })); + + let mut services_lock = services.lock().unwrap(); + services_lock.getters.insert(self.get_available_id.clone(), self.clone()); + services_lock.getters.insert(self.get_power_id.clone(), self.clone()); + services_lock.setters.insert(self.set_power_id.clone(), self.clone()); + services_lock.getters.insert(self.get_color_id.clone(), self.clone()); + services_lock.setters.insert(self.set_color_id.clone(), self.clone()); + + } else if status.lighttype == "Dimmable light" { + info!("New Philips Hue `Dimmable Light` service for light {} on bridge {}", + self.light_id, self.hub_id); + let mut service = Service::empty(self.service_id.clone(), adapter_id.clone()); + service.properties.insert(CUSTOM_PROPERTY_MANUFACTURER.to_owned(), + status.manufacturername.to_owned()); + service.properties.insert(CUSTOM_PROPERTY_MODEL.to_owned(), + status.modelid.to_owned()); + service.properties.insert(CUSTOM_PROPERTY_NAME.to_owned(), + status.name.to_owned()); + service.properties.insert(CUSTOM_PROPERTY_TYPE.to_owned(), + "Light/DimmerLight".to_owned()); + service.tags.insert(tag_id!("type:Light/DimmerLight")); + + try!(manager.add_service(service)); + + try!(manager.add_getter(Channel { + tags: HashSet::new(), + adapter: adapter_id.clone(), + id: self.get_available_id.clone(), + last_seen: None, + service: self.service_id.clone(), + mechanism: Getter { + kind: ChannelKind::Extension { + vendor: Id::new("foxlink@mozilla.com"), + adapter: Id::new("Philips Hue Adapter"), + kind: Id::new("available"), + typ: Type::OnOff, + }, + updated: None, + }, + })); + + try!(manager.add_getter(Channel { + tags: HashSet::new(), + adapter: adapter_id.clone(), + id: self.get_power_id.clone(), + last_seen: None, + service: self.service_id.clone(), + mechanism: Getter { + kind: ChannelKind::LightOn, + updated: None, + }, + })); + + try!(manager.add_setter(Channel { + tags: HashSet::new(), + adapter: adapter_id.clone(), + id: self.set_power_id.clone(), + last_seen: None, + service: self.service_id.clone(), + mechanism: Setter { + kind: ChannelKind::LightOn, + updated: None, + }, + })); + let mut services_lock = services.lock().unwrap(); services_lock.getters.insert(self.get_available_id.clone(), self.clone()); services_lock.getters.insert(self.get_power_id.clone(), self.clone()); @@ -145,4 +250,45 @@ impl Light { self.api.lock().unwrap().set_light_power(&self.light_id, on); } + #[allow(dead_code)] + pub fn get_brightness(&self) -> f64 { + // Hue API gives brightness value in [0, 254] + let ls = self.api.lock().unwrap().get_light_status(&self.light_id); + ls.state.bri as f64 / 254f64 + } + + #[allow(dead_code)] + pub fn set_brightness(&self, bri: f64) { + // Hue API takes brightness value in [0, 254] + let bri = bri.max(0f64).min(1f64); // [0,1] + + // convert to value space used by Hue + let bri: u32 = (bri * 254f64) as u32; + + self.api.lock().unwrap().set_light_brightness(&self.light_id, bri); + } + + pub fn get_color(&self) -> (f64, f64, f64) { + // Hue API gives hue angle in [0, 65535], and sat and val in [0, 254] + let ls = self.api.lock().unwrap().get_light_status(&self.light_id); + let hue: f64 = ls.state.hue.unwrap_or(0) as f64 / 65536f64 * 360f64; + let sat: f64 = ls.state.sat.unwrap_or(0) as f64 / 254f64; + let val: f64 = ls.state.bri as f64 / 254f64; + (hue, sat, val) + } + + pub fn set_color(&self, hsv: (f64, f64, f64)) { + // Hue API takes hue angle in [0, 65535], and sat and val in [0, 254] + let (hue, sat, val) = hsv; + let hue = ((hue % 360f64) + 360f64) % 360f64; // [0,360) + let sat = sat.max(0f64).min(1f64); // [0,1] + let val = val.max(0f64).min(1f64); // [0,1] + + // convert to value space used by Hue + let hue: u32 = (hue * 65536f64 / 360f64) as u32; + let sat: u32 = (sat * 254f64) as u32; + let val: u32 = (val * 254f64) as u32; + + self.api.lock().unwrap().set_light_color(&self.light_id, (hue, sat, val)); + } } diff --git a/src/adapters/philips_hue/mod.rs b/src/adapters/philips_hue/mod.rs index 7c13ea37..811b2b45 100644 --- a/src/adapters/philips_hue/mod.rs +++ b/src/adapters/philips_hue/mod.rs @@ -20,7 +20,7 @@ pub mod structs; use foxbox_taxonomy::api::{ Error, InternalError, User }; use foxbox_taxonomy::manager::*; use foxbox_taxonomy::services::*; -use foxbox_taxonomy::values::{ OnOff, Type, TypeError, Value }; +use foxbox_taxonomy::values::{ Color, OnOff, Type, TypeError, Value }; use std::collections::HashMap; use std::sync::{ Arc, Mutex }; @@ -250,6 +250,10 @@ impl Adapter for PhilipsHueAdapter { return (id, Ok(Some(Value::OnOff(OnOff::Off)))); } } + if id == light.get_color_id { + let (h, s, v) = light.get_color(); + return (id, Ok(Some(Value::Color(Color::HSV(h, s, v))))); + } (id.clone(), Err(Error::InternalError(InternalError::NoSuchGetter(id)))) }).collect() @@ -277,6 +281,18 @@ impl Adapter for PhilipsHueAdapter { } return (id, Ok(())); } + if id == light.set_color_id { + match value { + Value::Color(Color::HSV(h, s, v)) => { light.set_color((h, s, v)); }, + _ => { + return (id, Err(Error::TypeError(TypeError { + got: value.get_type(), + expected: Type::Color + }))); + } + } + return (id, Ok(())); + } (id.clone(), Err(Error::InternalError(InternalError::NoSuchSetter(id)))) }).collect() diff --git a/tools/scripts/huedemo.sh b/tools/scripts/huedemo.sh new file mode 100755 index 00000000..26c0a5b7 --- /dev/null +++ b/tools/scripts/huedemo.sh @@ -0,0 +1,107 @@ +#!/bin/bash + +# This is a quick&dirty demo of how to talk to the Philips HUe adapter. +# You must disable authenticaion before using this script. + +BRIGHTNESS=0.5 +URL="http://localhost:3000" +API="$URL/api/v1" +SERVICES="$API/services" +GET="$API/channels/get" +SET="$API/channels/set" + +function channels() { + curl -s -X GET "$SERVICES" \ + | tr '"' \\012 \ + | grep "etter:" \ + | grep philips_hue \ + | sort -u +} + +function getters() { + channels | grep ^getter: +} + +function setters() { + channels | grep ^setter: +} + +function get() { + curl -s -X PUT "$GET" -d "{\"id\":\"$1\"}" +} + +function set() { + curl -s -X PUT "$SET" -d "{\"select\":{\"id\":\"$1\"},\"value\":{$2}}" +} + +function all_on() { + for s in `setters | grep power` + do + echo -n $s: + set $s '"OnOff":"On"' + echo + done +} + +function all_off() { + for s in `setters | grep power` + do + echo -n $s: + set $s '"OnOff":"Off"' + echo + done +} + +function all_color() { + for s in `setters | grep color` + do + echo -n $s: + set $s '"Color":{"h":'${1-0}',"s":'${2-1}',"v":'${3-1}'}' + echo + done +} + +function clean_up() { + all_off &>/dev/null + all_color 0 0 $BRIGHTNESS &>/dev/null + exit +} + +case "$1" in + "list") + channels + ;; + "on") + all_on &>/dev/null + ;; + "off") + all_off &>/dev/null + ;; + "color") + all_on &>/dev/null + all_color $2 $3 $4 &>/dev/null + ;; + "disco") + trap clean_up SIGHUP SIGINT SIGTERM + all_on &>/dev/null + while true + do + all_color 0 1 $BRIGHTNESS &>/dev/null + sleep 1 + all_color 60 1 $BRIGHTNESS &>/dev/null + sleep 1 + all_color 120 1 $BRIGHTNESS &>/dev/null + sleep 1 + all_color 180 1 $BRIGHTNESS &>/dev/null + sleep 1 + all_color 240 1 $BRIGHTNESS &>/dev/null + sleep 1 + all_color 300 1 $BRIGHTNESS &>/dev/null + sleep 1 + done + ;; + *) + echo "usage: $0 list|on|off|color|disco" + ;; +esac +