From 585edc874f6ac10fead9a92e951020302c78f01c Mon Sep 17 00:00:00 2001 From: Stephen Yeargin Date: Sat, 13 Jun 2015 17:32:21 -0500 Subject: [PATCH 01/19] Some initial work on Philips Hue bridge integration. --- .gitignore | 1 + package.json | 2 +- platforms/PhilipsHue.js | 209 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 211 insertions(+), 1 deletion(-) create mode 100644 platforms/PhilipsHue.js diff --git a/.gitignore b/.gitignore index 8ba684b..81a1589 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ # Node node_modules/ npm-debug.log +.node-version # Intellij .idea/ diff --git a/package.json b/package.json index 5d27054..5a09872 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "request": "2.49.x", "node-persist": "0.0.x", "xmldoc": "0.1.x", - + "node-hue-api": "^1.0.5", "carwingsjs": "0.0.x", "sonos": "0.8.x", "wemo": "0.2.x", diff --git a/platforms/PhilipsHue.js b/platforms/PhilipsHue.js new file mode 100644 index 0000000..bb87be8 --- /dev/null +++ b/platforms/PhilipsHue.js @@ -0,0 +1,209 @@ +// Philips Hue Platform Shim for HomeBridge +// +// Remember to add platform to config.json. Example: +// "platforms": [ +// { +// "platform": "PhilipsHue", +// "name": "Philips Hue", +// "ip_address": "127.0.0.1", +// "username": "252deadbeef0bf3f34c7ecb810e832f" +// } +// ], +// +// When you attempt to add a device, it will ask for a "PIN code". +// The default code for all HomeBridge accessories is 031-45-154. +// + +/* jslint node: true */ +/* globals require: false */ +/* globals config: false */ + +"use strict"; + +var types = require("../lib/HAP-NodeJS/accessories/types.js"); + +function PhilipsHuePlatform(log, config) { + this.log = log; + this.ip_address = config["ip_address"]; + this.username = config["username"]; +} + +function PhilipsHueAccessory(log, accessoryName, philipsHueLightID, model, philipsHueLightNumber) { + return { + displayName: accessoryName, + username: philipsHueLightID, + pincode: '031-45-154', + services: [{ + sType: types.ACCESSORY_INFORMATION_STYPE, + characteristics: [{ + cType: types.NAME_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: accessoryName, + supportEvents: false, + supportBonjour: false, + manfDescription: "Name of the accessory", + designedMaxLength: 255 + },{ + cType: types.MANUFACTURER_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: "Philips", + supportEvents: false, + supportBonjour: false, + manfDescription: "Manufacturer", + designedMaxLength: 255 + },{ + cType: types.MODEL_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: model, + supportEvents: false, + supportBonjour: false, + manfDescription: "Model", + designedMaxLength: 255 + },{ + cType: types.IDENTIFY_CTYPE, + onUpdate: function(value) { console.log("Change:",value); execute(accessoryName, philipsHueLightNumber, "identify", value); }, + perms: ["pw"], + format: "bool", + initialValue: false, + supportEvents: false, + supportBonjour: false, + manfDescription: "Identify Accessory", + designedMaxLength: 1 + }] + },{ + sType: types.LIGHTBULB_STYPE, + characteristics: [{ + cType: types.NAME_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: accessoryName, + supportEvents: false, + supportBonjour: false, + manfDescription: "Name of service", + designedMaxLength: 255 + },{ + cType: types.POWER_STATE_CTYPE, + onUpdate: function(value) { console.log("Change:",value); execute(accessoryName, philipsHueLightNumber, "on", value); }, + perms: ["pw","pr","ev"], + format: "bool", + initialValue: false, + supportEvents: false, + supportBonjour: false, + manfDescription: "Turn On the Light", + designedMaxLength: 1 + },{ + cType: types.HUE_CTYPE, + onUpdate: function(value) { console.log("Change:",value); execute(accessoryName, philipsHueLightNumber, "hue", value); }, + perms: ["pw","pr","ev"], + format: "int", + initialValue: 0, + supportEvents: false, + supportBonjour: false, + manfDescription: "Adjust Hue of Light", + designedMinValue: 0, + designedMaxValue: 65535, + designedMinStep: 1, + unit: "arcdegrees" + },{ + cType: types.BRIGHTNESS_CTYPE, + onUpdate: function(value) { console.log("Change:",value); execute(accessoryName, philipsHueLightNumber, "brightness", value); }, + perms: ["pw","pr","ev"], + format: "int", + initialValue: 0, + supportEvents: false, + supportBonjour: false, + manfDescription: "Adjust Brightness of Light", + designedMinValue: 0, + designedMaxValue: 100, + designedMinStep: 1, + unit: "%" + },{ + cType: types.SATURATION_CTYPE, + onUpdate: function(value) { console.log("Change:",value); execute(accessoryName, philipsHueLightNumber, "saturation", value); }, + perms: ["pw","pr","ev"], + format: "int", + initialValue: 0, + supportEvents: false, + supportBonjour: false, + manfDescription: "Adjust Saturation of Light", + designedMinValue: 0, + designedMaxValue: 100, + designedMinStep: 1, + unit: "%" + }] + }] + }; +} + +var execute = function(accessory, lightID, characteristic, value) { + var http = require('http'); + var body = {}; + characteristic = characteristic.toLowerCase(); + if(characteristic === "identify") { + body = {alert:"select"}; + } else if(characteristic === "on") { + body = {on:value}; + } else if(characteristic === "hue") { + body = {hue:value}; + } else if(characteristic === "brightness") { + value = value/100; + value = value*255; + value = Math.round(value); + body = {bri:value}; + } else if(characteristic === "saturation") { + value = value/100; + value = value*255; + value = Math.round(value); + body = {sat:value}; + } + var post_data = JSON.stringify(body); + var post_options = { + host: config["ip_address"], + port: '80', + path: '/api/' + config["username"] + '/lights/' + lightID + '/state/', + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + 'Content-Length': post_data.length + } + }; + var post_req = http.request(post_options, function(res) { + res.setEncoding('utf8'); + res.on('data', function (chunk) { + console.log('Response: ' + chunk); + }); + }); + post_req.write(post_data); + post_req.end(); + console.log("executed accessory: " + accessory + ", and characteristic: " + characteristic + ", with value: " + value + "."); +}; + +PhilipsHuePlatform.prototype = { + accessories: function(callback) { + this.log("Fetching Philips Hue lights..."); + + var that = this; + var foundAccessories = []; + + var HueApi = require("node-hue-api").HueApi; + var api = new HueApi(this.ip_address, this.username); + + // Connect to the API and loop through lights + api.lights(function(err, response) { + response.lights.map(function(s) { + var accessory = new PhilipsHueAccessory(that.log, s.name, s.uniqueid, s.modelid, s.id); + foundAccessories.push(accessory); + }); + callback(foundAccessories); + }); + } +}; + +module.exports.platform = PhilipsHuePlatform; From aa0bdb9014f9ebac73f1ef7b03c1b440d3fb8278 Mon Sep 17 00:00:00 2001 From: Stephen Yeargin Date: Sat, 13 Jun 2015 20:37:30 -0500 Subject: [PATCH 02/19] This is somewhat working now. --- platforms/PhilipsHue.js | 293 ++++++++++++++++++++++++---------------- 1 file changed, 178 insertions(+), 115 deletions(-) diff --git a/platforms/PhilipsHue.js b/platforms/PhilipsHue.js index bb87be8..9456964 100644 --- a/platforms/PhilipsHue.js +++ b/platforms/PhilipsHue.js @@ -20,6 +20,10 @@ "use strict"; +var hue = require("node-hue-api"), + HueApi = hue.HueApi, + lightState = hue.lightState; + var types = require("../lib/HAP-NodeJS/accessories/types.js"); function PhilipsHuePlatform(log, config) { @@ -28,120 +32,16 @@ function PhilipsHuePlatform(log, config) { this.username = config["username"]; } -function PhilipsHueAccessory(log, accessoryName, philipsHueLightID, model, philipsHueLightNumber) { - return { - displayName: accessoryName, - username: philipsHueLightID, - pincode: '031-45-154', - services: [{ - sType: types.ACCESSORY_INFORMATION_STYPE, - characteristics: [{ - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: accessoryName, - supportEvents: false, - supportBonjour: false, - manfDescription: "Name of the accessory", - designedMaxLength: 255 - },{ - cType: types.MANUFACTURER_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "Philips", - supportEvents: false, - supportBonjour: false, - manfDescription: "Manufacturer", - designedMaxLength: 255 - },{ - cType: types.MODEL_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: model, - supportEvents: false, - supportBonjour: false, - manfDescription: "Model", - designedMaxLength: 255 - },{ - cType: types.IDENTIFY_CTYPE, - onUpdate: function(value) { console.log("Change:",value); execute(accessoryName, philipsHueLightNumber, "identify", value); }, - perms: ["pw"], - format: "bool", - initialValue: false, - supportEvents: false, - supportBonjour: false, - manfDescription: "Identify Accessory", - designedMaxLength: 1 - }] - },{ - sType: types.LIGHTBULB_STYPE, - characteristics: [{ - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: accessoryName, - supportEvents: false, - supportBonjour: false, - manfDescription: "Name of service", - designedMaxLength: 255 - },{ - cType: types.POWER_STATE_CTYPE, - onUpdate: function(value) { console.log("Change:",value); execute(accessoryName, philipsHueLightNumber, "on", value); }, - perms: ["pw","pr","ev"], - format: "bool", - initialValue: false, - supportEvents: false, - supportBonjour: false, - manfDescription: "Turn On the Light", - designedMaxLength: 1 - },{ - cType: types.HUE_CTYPE, - onUpdate: function(value) { console.log("Change:",value); execute(accessoryName, philipsHueLightNumber, "hue", value); }, - perms: ["pw","pr","ev"], - format: "int", - initialValue: 0, - supportEvents: false, - supportBonjour: false, - manfDescription: "Adjust Hue of Light", - designedMinValue: 0, - designedMaxValue: 65535, - designedMinStep: 1, - unit: "arcdegrees" - },{ - cType: types.BRIGHTNESS_CTYPE, - onUpdate: function(value) { console.log("Change:",value); execute(accessoryName, philipsHueLightNumber, "brightness", value); }, - perms: ["pw","pr","ev"], - format: "int", - initialValue: 0, - supportEvents: false, - supportBonjour: false, - manfDescription: "Adjust Brightness of Light", - designedMinValue: 0, - designedMaxValue: 100, - designedMinStep: 1, - unit: "%" - },{ - cType: types.SATURATION_CTYPE, - onUpdate: function(value) { console.log("Change:",value); execute(accessoryName, philipsHueLightNumber, "saturation", value); }, - perms: ["pw","pr","ev"], - format: "int", - initialValue: 0, - supportEvents: false, - supportBonjour: false, - manfDescription: "Adjust Saturation of Light", - designedMinValue: 0, - designedMaxValue: 100, - designedMinStep: 1, - unit: "%" - }] - }] - }; +function PhilipsHueAccessory(log, device, api) { + // device info + this.name = device.name; + this.model = device.modelid; + this.device = device; + this.api = api; + this.log = log; } +// @todo Use the node module for all of this var execute = function(accessory, lightID, characteristic, value) { var http = require('http'); var body = {}; @@ -192,13 +92,13 @@ PhilipsHuePlatform.prototype = { var that = this; var foundAccessories = []; - var HueApi = require("node-hue-api").HueApi; var api = new HueApi(this.ip_address, this.username); // Connect to the API and loop through lights api.lights(function(err, response) { - response.lights.map(function(s) { - var accessory = new PhilipsHueAccessory(that.log, s.name, s.uniqueid, s.modelid, s.id); + if (err) throw err; + response.lights.map(function(device) { + var accessory = new PhilipsHueAccessory(that.log, device, api); foundAccessories.push(accessory); }); callback(foundAccessories); @@ -206,4 +106,167 @@ PhilipsHuePlatform.prototype = { } }; +PhilipsHueAccessory.prototype = { + + setPowerState: function(powerOn) { + if (!this.device) { + this.log("No '"+this.name+"' device found (yet?)"); + return; + } + + var that = this; + var state; + + if (powerOn) { + this.log("Setting power state on the '"+this.name+"' to off"); + state = lightState.create().on(); + that.api.setLightState(that.id, state, function(err, result) { + if (err) { + that.log("Error setting power state on for '"+that.name+"'"); + } else { + that.log("Successfully set power state on for '"+that.name+"'"); + } + }); + }else{ + this.log("Setting power state on the '"+this.name+"' to off"); + state = lightState.create().off(); + that.api.setLightState(that.id, state, function(err, result) { + if (err) { + that.log("Error setting power state off for '"+that.name+"'"); + } else { + that.log("Successfully set power state off for '"+that.name+"'"); + } + }); + } + }, + + setBrightness: function(level) { + if (!this.device) { + this.log("No '"+this.name+"' device found (yet?)"); + return; + } + + var that = this; + + this.log("Setting brightness on the '"+this.name+"' to " + level); + this.device.brightness(level, function(response) { + if (response === undefined) { + that.log("Error setting brightness on the '"+that.name+"'"); + } else { + that.log("Successfully set brightness on the '"+that.name+"' to " + level); + } + }); + }, + + getServices: function() { + var that = this; + return [{ + sType: types.ACCESSORY_INFORMATION_STYPE, + characteristics: [{ + cType: types.NAME_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: this.name, + supportEvents: false, + supportBonjour: false, + manfDescription: "Name of the accessory", + designedMaxLength: 255 + },{ + cType: types.MANUFACTURER_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: "Philips", + supportEvents: false, + supportBonjour: false, + manfDescription: "Manufacturer", + designedMaxLength: 255 + },{ + cType: types.MODEL_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: this.model, + supportEvents: false, + supportBonjour: false, + manfDescription: "Model", + designedMaxLength: 255 + },{ + cType: types.IDENTIFY_CTYPE, + onUpdate: function(value) { console.log("Change:",value); execute(this.name, this.id, "identify", value); }, + perms: ["pw"], + format: "bool", + initialValue: false, + supportEvents: false, + supportBonjour: false, + manfDescription: "Identify Accessory", + designedMaxLength: 1 + }] + },{ + sType: types.LIGHTBULB_STYPE, + characteristics: [{ + cType: types.NAME_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: this.name, + supportEvents: false, + supportBonjour: false, + manfDescription: "Name of service", + designedMaxLength: 255 + },{ + cType: types.POWER_STATE_CTYPE, + onUpdate: function(value) { console.log("Change:",value); execute(this.name, this.id, "on", value); }, + perms: ["pw","pr","ev"], + format: "bool", + initialValue: false, + supportEvents: false, + supportBonjour: false, + manfDescription: "Turn On the Light", + designedMaxLength: 1 + },{ + cType: types.HUE_CTYPE, + onUpdate: function(value) { console.log("Change:",value); execute(this.name, this.id, "hue", value); }, + perms: ["pw","pr","ev"], + format: "int", + initialValue: 0, + supportEvents: false, + supportBonjour: false, + manfDescription: "Adjust Hue of Light", + designedMinValue: 0, + designedMaxValue: 65535, + designedMinStep: 1, + unit: "arcdegrees" + },{ + cType: types.BRIGHTNESS_CTYPE, + onUpdate: function(value) { console.log("Change:",value); execute(this.name, this.id, "brightness", value); }, + perms: ["pw","pr","ev"], + format: "int", + initialValue: 0, + supportEvents: false, + supportBonjour: false, + manfDescription: "Adjust Brightness of Light", + designedMinValue: 0, + designedMaxValue: 100, + designedMinStep: 1, + unit: "%" + },{ + cType: types.SATURATION_CTYPE, + onUpdate: function(value) { console.log("Change:",value); execute(this.name, this.id, "saturation", value); }, + perms: ["pw","pr","ev"], + format: "int", + initialValue: 0, + supportEvents: false, + supportBonjour: false, + manfDescription: "Adjust Saturation of Light", + designedMinValue: 0, + designedMaxValue: 100, + designedMinStep: 1, + unit: "%" + }] + }]; + } +}; + module.exports.platform = PhilipsHuePlatform; From 9346688d56c9c8bf2ab02d774b8fc97c1936e0b0 Mon Sep 17 00:00:00 2001 From: Stephen Yeargin Date: Sat, 13 Jun 2015 20:49:28 -0500 Subject: [PATCH 03/19] updating docs --- README.md | 2 +- config-sample.json | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index cf74caa..4a4e98e 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Since Siri supports devices added through HomeKit, this means that with HomeBrid * _Siri, turn off the Speakers._ ([Sonos](http://www.sonos.com)) * _Siri, turn on the Dehumidifier._ ([WeMo](http://www.belkin.com/us/Products/home-automation/c/wemo-home-automation/)) * _Siri, turn on Away Mode._ ([Xfinity Home](http://www.comcast.com/home-security.html)) - * _Siri, turn on the living room lights._ ([Wink](http://www.wink.com), [SmartThings](http://www.smartthings.com), [X10](http://github.com/edc1591/rest-mochad)) + * _Siri, turn on the living room lights._ ([Wink](http://www.wink.com), [SmartThings](http://www.smartthings.com), [X10](http://github.com/edc1591/rest-mochad), [Philips Hue](http://meethue.com)) If you would like to support any other devices, please write a shim and create a pull request and I'd be happy to add it to this official list. diff --git a/config-sample.json b/config-sample.json index 7ff4ddc..3f5beb1 100644 --- a/config-sample.json +++ b/config-sample.json @@ -22,6 +22,12 @@ "server": "127.0.0.1", "port": "8005" } + { + "platform": "PhilipsHue", + "name": "Phillips Hue", + "ip_address": "127.0.0.1", + "username": "252deadbeef0bf3f34c7ecb810e832f" + } ], "accessories": [ From 3cba55560f016dcc065afa4af81a737dee4b0956 Mon Sep 17 00:00:00 2001 From: Stephen Yeargin Date: Mon, 15 Jun 2015 23:10:18 -0500 Subject: [PATCH 04/19] Fix brightness, format some logic. --- platforms/PhilipsHue.js | 43 +++++++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/platforms/PhilipsHue.js b/platforms/PhilipsHue.js index 9456964..967facc 100644 --- a/platforms/PhilipsHue.js +++ b/platforms/PhilipsHue.js @@ -33,7 +33,6 @@ function PhilipsHuePlatform(log, config) { } function PhilipsHueAccessory(log, device, api) { - // device info this.name = device.name; this.model = device.modelid; this.device = device; @@ -46,18 +45,22 @@ var execute = function(accessory, lightID, characteristic, value) { var http = require('http'); var body = {}; characteristic = characteristic.toLowerCase(); - if(characteristic === "identify") { + if (characteristic === "identify") { body = {alert:"select"}; - } else if(characteristic === "on") { + } + else if (characteristic === "on") { body = {on:value}; - } else if(characteristic === "hue") { + } + else if (characteristic === "hue") { body = {hue:value}; - } else if(characteristic === "brightness") { + } + else if (characteristic === "brightness") { value = value/100; value = value*255; value = Math.round(value); body = {bri:value}; - } else if(characteristic === "saturation") { + } + else if (characteristic === "saturation") { value = value/100; value = value*255; value = Math.round(value); @@ -107,7 +110,7 @@ PhilipsHuePlatform.prototype = { }; PhilipsHueAccessory.prototype = { - + // Set Power State setPowerState: function(powerOn) { if (!this.device) { this.log("No '"+this.name+"' device found (yet?)"); @@ -123,23 +126,26 @@ PhilipsHueAccessory.prototype = { that.api.setLightState(that.id, state, function(err, result) { if (err) { that.log("Error setting power state on for '"+that.name+"'"); - } else { + } + else { that.log("Successfully set power state on for '"+that.name+"'"); } }); - }else{ + } + else { this.log("Setting power state on the '"+this.name+"' to off"); state = lightState.create().off(); that.api.setLightState(that.id, state, function(err, result) { if (err) { that.log("Error setting power state off for '"+that.name+"'"); - } else { + } + else { that.log("Successfully set power state off for '"+that.name+"'"); } }); } }, - + // Set Brightness setBrightness: function(level) { if (!this.device) { this.log("No '"+this.name+"' device found (yet?)"); @@ -147,17 +153,20 @@ PhilipsHueAccessory.prototype = { } var that = this; + var state; this.log("Setting brightness on the '"+this.name+"' to " + level); - this.device.brightness(level, function(response) { - if (response === undefined) { - that.log("Error setting brightness on the '"+that.name+"'"); - } else { - that.log("Successfully set brightness on the '"+that.name+"' to " + level); + state = lightState.create().brightness(level); + that.api.setLightState(that.id, state, function(err, result) { + if (err) { + that.log("Error setting brightness for '"+that.name+"'"); + } + else { + that.log("Successfully set brightness for '"+that.name+"'"); } }); }, - + // Get Services getServices: function() { var that = this; return [{ From da5162b358a8c62b67e4284586fcdd9ff46d0ddb Mon Sep 17 00:00:00 2001 From: Stephen Yeargin Date: Tue, 16 Jun 2015 22:59:26 -0500 Subject: [PATCH 05/19] Pass around the light ID. --- platforms/PhilipsHue.js | 1 + 1 file changed, 1 insertion(+) diff --git a/platforms/PhilipsHue.js b/platforms/PhilipsHue.js index 967facc..93bd27e 100644 --- a/platforms/PhilipsHue.js +++ b/platforms/PhilipsHue.js @@ -33,6 +33,7 @@ function PhilipsHuePlatform(log, config) { } function PhilipsHueAccessory(log, device, api) { + this.id = device.id; this.name = device.name; this.model = device.modelid; this.device = device; From 49c4af79ad09528af7a3be72a84fb912b44d4521 Mon Sep 17 00:00:00 2001 From: Stephen Yeargin Date: Tue, 16 Jun 2015 23:08:44 -0500 Subject: [PATCH 06/19] Refactor execute method to use API --- platforms/PhilipsHue.js | 53 ++++++++++++++++------------------------- 1 file changed, 21 insertions(+), 32 deletions(-) diff --git a/platforms/PhilipsHue.js b/platforms/PhilipsHue.js index 93bd27e..8f33017 100644 --- a/platforms/PhilipsHue.js +++ b/platforms/PhilipsHue.js @@ -41,52 +41,41 @@ function PhilipsHueAccessory(log, device, api) { this.log = log; } -// @todo Use the node module for all of this -var execute = function(accessory, lightID, characteristic, value) { - var http = require('http'); - var body = {}; +// Execute changes for various characteristics +var execute = function(api, device, characteristic, value) { + + var state = lightState.create(); + characteristic = characteristic.toLowerCase(); if (characteristic === "identify") { - body = {alert:"select"}; + state.alert('select'); } else if (characteristic === "on") { - body = {on:value}; + state.on(); } else if (characteristic === "hue") { - body = {hue:value}; + state.hue(value); } else if (characteristic === "brightness") { value = value/100; value = value*255; value = Math.round(value); - body = {bri:value}; + state.bri(value); } else if (characteristic === "saturation") { value = value/100; value = value*255; value = Math.round(value); - body = {sat:value}; + state.sat(value); } - var post_data = JSON.stringify(body); - var post_options = { - host: config["ip_address"], - port: '80', - path: '/api/' + config["username"] + '/lights/' + lightID + '/state/', - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - 'Content-Length': post_data.length + api.setLightState(device.id, state, function(err, lights) { + if (!err) { + console.log("executed accessory: " + device.name + ", and characteristic: " + characteristic + ", with value: " + value + "."); + } + else { + console.log(err); } - }; - var post_req = http.request(post_options, function(res) { - res.setEncoding('utf8'); - res.on('data', function (chunk) { - console.log('Response: ' + chunk); - }); }); - post_req.write(post_data); - post_req.end(); - console.log("executed accessory: " + accessory + ", and characteristic: " + characteristic + ", with value: " + value + "."); }; PhilipsHuePlatform.prototype = { @@ -204,7 +193,7 @@ PhilipsHueAccessory.prototype = { designedMaxLength: 255 },{ cType: types.IDENTIFY_CTYPE, - onUpdate: function(value) { console.log("Change:",value); execute(this.name, this.id, "identify", value); }, + onUpdate: function(value) { console.log("Change:",value); execute(this.device, this.id, "identify", value); }, perms: ["pw"], format: "bool", initialValue: false, @@ -227,7 +216,7 @@ PhilipsHueAccessory.prototype = { designedMaxLength: 255 },{ cType: types.POWER_STATE_CTYPE, - onUpdate: function(value) { console.log("Change:",value); execute(this.name, this.id, "on", value); }, + onUpdate: function(value) { console.log("Change:",value); execute(this.api, this.device, "on", value); }, perms: ["pw","pr","ev"], format: "bool", initialValue: false, @@ -237,7 +226,7 @@ PhilipsHueAccessory.prototype = { designedMaxLength: 1 },{ cType: types.HUE_CTYPE, - onUpdate: function(value) { console.log("Change:",value); execute(this.name, this.id, "hue", value); }, + onUpdate: function(value) { console.log("Change:",value); execute(this.api, this.device, "hue", value); }, perms: ["pw","pr","ev"], format: "int", initialValue: 0, @@ -250,7 +239,7 @@ PhilipsHueAccessory.prototype = { unit: "arcdegrees" },{ cType: types.BRIGHTNESS_CTYPE, - onUpdate: function(value) { console.log("Change:",value); execute(this.name, this.id, "brightness", value); }, + onUpdate: function(value) { console.log("Change:",value); execute(this.api, this.device, "brightness", value); }, perms: ["pw","pr","ev"], format: "int", initialValue: 0, @@ -263,7 +252,7 @@ PhilipsHueAccessory.prototype = { unit: "%" },{ cType: types.SATURATION_CTYPE, - onUpdate: function(value) { console.log("Change:",value); execute(this.name, this.id, "saturation", value); }, + onUpdate: function(value) { console.log("Change:",value); execute(this.api, this.device, "saturation", value); }, perms: ["pw","pr","ev"], format: "int", initialValue: 0, From b7d8a561a36c28c682ee39352c8d49174e3141b3 Mon Sep 17 00:00:00 2001 From: madmod Date: Fri, 26 Jun 2015 22:39:33 -0600 Subject: [PATCH 07/19] fixed typo in config-sample.json --- config-sample.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config-sample.json b/config-sample.json index 3f5beb1..891b7cf 100644 --- a/config-sample.json +++ b/config-sample.json @@ -21,7 +21,7 @@ "name": "Domoticz", "server": "127.0.0.1", "port": "8005" - } + }, { "platform": "PhilipsHue", "name": "Phillips Hue", From 54100c247113c90d3797f468e78f40009348bc8f Mon Sep 17 00:00:00 2001 From: Stephen Yeargin Date: Sat, 27 Jun 2015 01:00:59 -0500 Subject: [PATCH 08/19] Clean up some really odd spacing and add SN --- platforms/PhilipsHue.js | 226 +++++++++++++++++++++------------------- 1 file changed, 121 insertions(+), 105 deletions(-) diff --git a/platforms/PhilipsHue.js b/platforms/PhilipsHue.js index 8f33017..b9066b3 100644 --- a/platforms/PhilipsHue.js +++ b/platforms/PhilipsHue.js @@ -159,112 +159,128 @@ PhilipsHueAccessory.prototype = { // Get Services getServices: function() { var that = this; - return [{ - sType: types.ACCESSORY_INFORMATION_STYPE, - characteristics: [{ - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.name, - supportEvents: false, - supportBonjour: false, - manfDescription: "Name of the accessory", - designedMaxLength: 255 + return [ + { + sType: types.ACCESSORY_INFORMATION_STYPE, + characteristics: [ + { + cType: types.NAME_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: this.name, + supportEvents: false, + supportBonjour: false, + manfDescription: "Name of the accessory", + designedMaxLength: 255 + },{ + cType: types.MANUFACTURER_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: "Philips", + supportEvents: false, + supportBonjour: false, + manfDescription: "Manufacturer", + designedMaxLength: 255 + },{ + cType: types.MODEL_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: this.model, + supportEvents: false, + supportBonjour: false, + manfDescription: "Model", + designedMaxLength: 255 + },{ + cType: types.SERIAL_NUMBER_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: this.model + this.id, + supportEvents: false, + supportBonjour: false, + manfDescription: "SN", + designedMaxLength: 255 + },{ + cType: types.IDENTIFY_CTYPE, + onUpdate: function(value) { console.log("Change:",value); execute(this.device, this.id, "identify", value); }, + perms: ["pw"], + format: "bool", + initialValue: false, + supportEvents: false, + supportBonjour: false, + manfDescription: "Identify Accessory", + designedMaxLength: 1 + } + ] },{ - cType: types.MANUFACTURER_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "Philips", - supportEvents: false, - supportBonjour: false, - manfDescription: "Manufacturer", - designedMaxLength: 255 - },{ - cType: types.MODEL_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.model, - supportEvents: false, - supportBonjour: false, - manfDescription: "Model", - designedMaxLength: 255 - },{ - cType: types.IDENTIFY_CTYPE, - onUpdate: function(value) { console.log("Change:",value); execute(this.device, this.id, "identify", value); }, - perms: ["pw"], - format: "bool", - initialValue: false, - supportEvents: false, - supportBonjour: false, - manfDescription: "Identify Accessory", - designedMaxLength: 1 - }] - },{ - sType: types.LIGHTBULB_STYPE, - characteristics: [{ - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.name, - supportEvents: false, - supportBonjour: false, - manfDescription: "Name of service", - designedMaxLength: 255 - },{ - cType: types.POWER_STATE_CTYPE, - onUpdate: function(value) { console.log("Change:",value); execute(this.api, this.device, "on", value); }, - perms: ["pw","pr","ev"], - format: "bool", - initialValue: false, - supportEvents: false, - supportBonjour: false, - manfDescription: "Turn On the Light", - designedMaxLength: 1 - },{ - cType: types.HUE_CTYPE, - onUpdate: function(value) { console.log("Change:",value); execute(this.api, this.device, "hue", value); }, - perms: ["pw","pr","ev"], - format: "int", - initialValue: 0, - supportEvents: false, - supportBonjour: false, - manfDescription: "Adjust Hue of Light", - designedMinValue: 0, - designedMaxValue: 65535, - designedMinStep: 1, - unit: "arcdegrees" - },{ - cType: types.BRIGHTNESS_CTYPE, - onUpdate: function(value) { console.log("Change:",value); execute(this.api, this.device, "brightness", value); }, - perms: ["pw","pr","ev"], - format: "int", - initialValue: 0, - supportEvents: false, - supportBonjour: false, - manfDescription: "Adjust Brightness of Light", - designedMinValue: 0, - designedMaxValue: 100, - designedMinStep: 1, - unit: "%" - },{ - cType: types.SATURATION_CTYPE, - onUpdate: function(value) { console.log("Change:",value); execute(this.api, this.device, "saturation", value); }, - perms: ["pw","pr","ev"], - format: "int", - initialValue: 0, - supportEvents: false, - supportBonjour: false, - manfDescription: "Adjust Saturation of Light", - designedMinValue: 0, - designedMaxValue: 100, - designedMinStep: 1, - unit: "%" - }] - }]; + sType: types.LIGHTBULB_STYPE, + characteristics: [ + { + cType: types.NAME_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: this.name, + supportEvents: false, + supportBonjour: false, + manfDescription: "Name of service", + designedMaxLength: 255 + },{ + cType: types.POWER_STATE_CTYPE, + onUpdate: function(value) { console.log("Change:",value); execute(this.api, this.device, "on", value); }, + perms: ["pw","pr","ev"], + format: "bool", + initialValue: false, + supportEvents: false, + supportBonjour: false, + manfDescription: "Turn On the Light", + designedMaxLength: 1 + },{ + cType: types.HUE_CTYPE, + onUpdate: function(value) { console.log("Change:",value); execute(this.api, this.device, "hue", value); }, + perms: ["pw","pr","ev"], + format: "int", + initialValue: 0, + supportEvents: false, + supportBonjour: false, + manfDescription: "Adjust Hue of Light", + designedMinValue: 0, + designedMaxValue: 65535, + designedMinStep: 1, + unit: "arcdegrees" + },{ + cType: types.BRIGHTNESS_CTYPE, + onUpdate: function(value) { console.log("Change:",value); execute(this.api, this.device, "brightness", value); }, + perms: ["pw","pr","ev"], + format: "int", + initialValue: 0, + supportEvents: false, + supportBonjour: false, + manfDescription: "Adjust Brightness of Light", + designedMinValue: 0, + designedMaxValue: 100, + designedMinStep: 1, + unit: "%" + },{ + cType: types.SATURATION_CTYPE, + onUpdate: function(value) { console.log("Change:",value); execute(this.api, this.device, "saturation", value); }, + perms: ["pw","pr","ev"], + format: "int", + initialValue: 0, + supportEvents: false, + supportBonjour: false, + manfDescription: "Adjust Saturation of Light", + designedMinValue: 0, + designedMaxValue: 100, + designedMinStep: 1, + unit: "%" + } + ] + } + ]; } }; From de84846bcff0217a3ebc1b5c7f74553891af699f Mon Sep 17 00:00:00 2001 From: Stephen Yeargin Date: Sat, 27 Jun 2015 01:18:32 -0500 Subject: [PATCH 09/19] weeeellllll, it's like ... --- platforms/PhilipsHue.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/platforms/PhilipsHue.js b/platforms/PhilipsHue.js index b9066b3..97d991b 100644 --- a/platforms/PhilipsHue.js +++ b/platforms/PhilipsHue.js @@ -205,7 +205,7 @@ PhilipsHueAccessory.prototype = { designedMaxLength: 255 },{ cType: types.IDENTIFY_CTYPE, - onUpdate: function(value) { console.log("Change:",value); execute(this.device, this.id, "identify", value); }, + onUpdate: function(value) { console.log("Change:",value); execute(that.api, that.device, "identify", value); }, perms: ["pw"], format: "bool", initialValue: false, @@ -230,7 +230,7 @@ PhilipsHueAccessory.prototype = { designedMaxLength: 255 },{ cType: types.POWER_STATE_CTYPE, - onUpdate: function(value) { console.log("Change:",value); execute(this.api, this.device, "on", value); }, + onUpdate: function(value) { console.log("Change:",value); execute(that.api, that.device, "on", value); }, perms: ["pw","pr","ev"], format: "bool", initialValue: false, @@ -240,7 +240,7 @@ PhilipsHueAccessory.prototype = { designedMaxLength: 1 },{ cType: types.HUE_CTYPE, - onUpdate: function(value) { console.log("Change:",value); execute(this.api, this.device, "hue", value); }, + onUpdate: function(value) { console.log("Change:",value); execute(that.api, that.device, "hue", value); }, perms: ["pw","pr","ev"], format: "int", initialValue: 0, @@ -253,7 +253,7 @@ PhilipsHueAccessory.prototype = { unit: "arcdegrees" },{ cType: types.BRIGHTNESS_CTYPE, - onUpdate: function(value) { console.log("Change:",value); execute(this.api, this.device, "brightness", value); }, + onUpdate: function(value) { console.log("Change:",value); execute(that.api, that.device, "brightness", value); }, perms: ["pw","pr","ev"], format: "int", initialValue: 0, @@ -266,7 +266,7 @@ PhilipsHueAccessory.prototype = { unit: "%" },{ cType: types.SATURATION_CTYPE, - onUpdate: function(value) { console.log("Change:",value); execute(this.api, this.device, "saturation", value); }, + onUpdate: function(value) { console.log("Change:",value); execute(that.api, that.device, "saturation", value); }, perms: ["pw","pr","ev"], format: "int", initialValue: 0, From 9814ff5525023be5be0448f6a94e19823e457adc Mon Sep 17 00:00:00 2001 From: Stephen Yeargin Date: Sat, 27 Jun 2015 01:24:00 -0500 Subject: [PATCH 10/19] Remove useless code :skull: --- platforms/PhilipsHue.js | 68 ++++++----------------------------------- 1 file changed, 9 insertions(+), 59 deletions(-) diff --git a/platforms/PhilipsHue.js b/platforms/PhilipsHue.js index 97d991b..e72084c 100644 --- a/platforms/PhilipsHue.js +++ b/platforms/PhilipsHue.js @@ -42,6 +42,7 @@ function PhilipsHueAccessory(log, device, api) { } // Execute changes for various characteristics +// @todo Move this into accessory methods var execute = function(api, device, characteristic, value) { var state = lightState.create(); @@ -50,8 +51,13 @@ var execute = function(api, device, characteristic, value) { if (characteristic === "identify") { state.alert('select'); } - else if (characteristic === "on") { - state.on(); + else if (characteristic === "power") { + if (value) { + state.on(); + } + else { + state.off(); + } } else if (characteristic === "hue") { state.hue(value); @@ -100,62 +106,6 @@ PhilipsHuePlatform.prototype = { }; PhilipsHueAccessory.prototype = { - // Set Power State - setPowerState: function(powerOn) { - if (!this.device) { - this.log("No '"+this.name+"' device found (yet?)"); - return; - } - - var that = this; - var state; - - if (powerOn) { - this.log("Setting power state on the '"+this.name+"' to off"); - state = lightState.create().on(); - that.api.setLightState(that.id, state, function(err, result) { - if (err) { - that.log("Error setting power state on for '"+that.name+"'"); - } - else { - that.log("Successfully set power state on for '"+that.name+"'"); - } - }); - } - else { - this.log("Setting power state on the '"+this.name+"' to off"); - state = lightState.create().off(); - that.api.setLightState(that.id, state, function(err, result) { - if (err) { - that.log("Error setting power state off for '"+that.name+"'"); - } - else { - that.log("Successfully set power state off for '"+that.name+"'"); - } - }); - } - }, - // Set Brightness - setBrightness: function(level) { - if (!this.device) { - this.log("No '"+this.name+"' device found (yet?)"); - return; - } - - var that = this; - var state; - - this.log("Setting brightness on the '"+this.name+"' to " + level); - state = lightState.create().brightness(level); - that.api.setLightState(that.id, state, function(err, result) { - if (err) { - that.log("Error setting brightness for '"+that.name+"'"); - } - else { - that.log("Successfully set brightness for '"+that.name+"'"); - } - }); - }, // Get Services getServices: function() { var that = this; @@ -230,7 +180,7 @@ PhilipsHueAccessory.prototype = { designedMaxLength: 255 },{ cType: types.POWER_STATE_CTYPE, - onUpdate: function(value) { console.log("Change:",value); execute(that.api, that.device, "on", value); }, + onUpdate: function(value) { console.log("Change:",value); execute(that.api, that.device, "power", value); }, perms: ["pw","pr","ev"], format: "bool", initialValue: false, From d0b780f623dcaf9d4fd11d4ae9d21410b4019003 Mon Sep 17 00:00:00 2001 From: madmod Date: Sat, 27 Jun 2015 01:37:16 -0600 Subject: [PATCH 11/19] Fix saturation max value --- platforms/PhilipsHue.js | 1 + 1 file changed, 1 insertion(+) diff --git a/platforms/PhilipsHue.js b/platforms/PhilipsHue.js index e72084c..3daf700 100644 --- a/platforms/PhilipsHue.js +++ b/platforms/PhilipsHue.js @@ -225,6 +225,7 @@ PhilipsHueAccessory.prototype = { manfDescription: "Adjust Saturation of Light", designedMinValue: 0, designedMaxValue: 100, + designedMaxValue: 255, designedMinStep: 1, unit: "%" } From 8adf8fc1b0e7bc107f5bacd549eefce49ba9cd40 Mon Sep 17 00:00:00 2001 From: madmod Date: Sat, 27 Jun 2015 12:36:07 -0600 Subject: [PATCH 12/19] remove extra line --- platforms/PhilipsHue.js | 1 - 1 file changed, 1 deletion(-) diff --git a/platforms/PhilipsHue.js b/platforms/PhilipsHue.js index 3daf700..3da6569 100644 --- a/platforms/PhilipsHue.js +++ b/platforms/PhilipsHue.js @@ -224,7 +224,6 @@ PhilipsHueAccessory.prototype = { supportBonjour: false, manfDescription: "Adjust Saturation of Light", designedMinValue: 0, - designedMaxValue: 100, designedMaxValue: 255, designedMinStep: 1, unit: "%" From 805758395e72edbb6033cbb20890826a16f2e6af Mon Sep 17 00:00:00 2001 From: madmod Date: Sat, 27 Jun 2015 17:01:44 -0600 Subject: [PATCH 13/19] improved color accuracy --- platforms/PhilipsHue.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/platforms/PhilipsHue.js b/platforms/PhilipsHue.js index 3da6569..7913f80 100644 --- a/platforms/PhilipsHue.js +++ b/platforms/PhilipsHue.js @@ -60,6 +60,9 @@ var execute = function(api, device, characteristic, value) { } } else if (characteristic === "hue") { + value = value/360; + value = value*65535; + value = Math.round(value); state.hue(value); } else if (characteristic === "brightness") { @@ -198,7 +201,7 @@ PhilipsHueAccessory.prototype = { supportBonjour: false, manfDescription: "Adjust Hue of Light", designedMinValue: 0, - designedMaxValue: 65535, + designedMaxValue: 360, designedMinStep: 1, unit: "arcdegrees" },{ From 0491f9b71ad8b8bf264f6291e14e5269680cb618 Mon Sep 17 00:00:00 2001 From: madmod Date: Sat, 27 Jun 2015 17:48:47 -0600 Subject: [PATCH 14/19] use the percent functions from node-hue-api --- platforms/PhilipsHue.js | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/platforms/PhilipsHue.js b/platforms/PhilipsHue.js index 7913f80..9467dd4 100644 --- a/platforms/PhilipsHue.js +++ b/platforms/PhilipsHue.js @@ -60,22 +60,15 @@ var execute = function(api, device, characteristic, value) { } } else if (characteristic === "hue") { - value = value/360; - value = value*65535; + value = value*182.5487; value = Math.round(value); state.hue(value); } else if (characteristic === "brightness") { - value = value/100; - value = value*255; - value = Math.round(value); - state.bri(value); + state.brightness(value); } else if (characteristic === "saturation") { - value = value/100; - value = value*255; - value = Math.round(value); - state.sat(value); + state.saturation(value); } api.setLightState(device.id, state, function(err, lights) { if (!err) { @@ -227,7 +220,7 @@ PhilipsHueAccessory.prototype = { supportBonjour: false, manfDescription: "Adjust Saturation of Light", designedMinValue: 0, - designedMaxValue: 255, + designedMaxValue: 100, designedMinStep: 1, unit: "%" } From 39a7550d673b89436654efbd3fbab5cc64ff4686 Mon Sep 17 00:00:00 2001 From: madmod Date: Sat, 27 Jun 2015 19:17:19 -0600 Subject: [PATCH 15/19] bridge discovery (wip) --- platforms/PhilipsHue.js | 83 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 73 insertions(+), 10 deletions(-) diff --git a/platforms/PhilipsHue.js b/platforms/PhilipsHue.js index 9467dd4..2f3e187 100644 --- a/platforms/PhilipsHue.js +++ b/platforms/PhilipsHue.js @@ -60,7 +60,7 @@ var execute = function(api, device, characteristic, value) { } } else if (characteristic === "hue") { - value = value*182.5487; + value = value * 182.5487; // Convert degrees to 0-65535 range value = Math.round(value); state.hue(value); } @@ -80,6 +80,55 @@ var execute = function(api, device, characteristic, value) { }); }; + +// Get the ip address of the first available bridge with meethue.com or a network scan. +var locateBridge = function (callback) { + // Report the results of the scan to the user + var getIp = function (err, bridges) { + if (!bridges || bridges.length === 0) { + this.log("No Philips Hue bridges found."); + callback(err || new Error("No bridges found")); + return; + } + + if (bridges.length > 1) { + this.log("Warning: Multiple Philips Hue bridges detected. The first bridge will be used automatically. To use a different bridge set ip_address manually in configuration."); + } + + this.log( + "Philips Hue bridges found:", + bridges.map(function (bridge) { + // Bridge name is only returned from meethue.com so use id instead if it isn't there + return '\t' + (bridge.name || bridge.id) + bridge.ipaddress + '\n'; + }) + ); + + callback(null, bridges[0].ipaddress); + }; + + // Try to discover the bridge ip using meethue.com + this.log("Attempting to discover Philips Hue bridge with network scan."); + api.locateBridges(function (locateError, bridges) { + if (locateError) { + this.log("Philips Hue bridge discovery with meethue.com failed. Register your bridge with the meethue.com for more reiable discovery."); + + this.log("Attempting to discover Philips Hue bridge with network scan."); + + api.searchForBridges(function (searchError, bridges) { + if (err) { + this.log("Philips Hue bridge discovery with network scan failed. Check your network connection or set ip_address manually in configuration."); + getIp(new Error("Scan failed")); + } else { + getIp(null, bridges); + } + }); + } else { + getIp(null, bridges); + } + }); +}; + + PhilipsHuePlatform.prototype = { accessories: function(callback) { this.log("Fetching Philips Hue lights..."); @@ -87,17 +136,31 @@ PhilipsHuePlatform.prototype = { var that = this; var foundAccessories = []; - var api = new HueApi(this.ip_address, this.username); + var getLights = function () { + var api = new HueApi(that.ip_address, that.username); - // Connect to the API and loop through lights - api.lights(function(err, response) { - if (err) throw err; - response.lights.map(function(device) { - var accessory = new PhilipsHueAccessory(that.log, device, api); - foundAccessories.push(accessory); + // Connect to the API and loop through lights + api.lights(function(err, response) { + if (err) throw err; + response.lights.map(function(device) { + var accessory = new PhilipsHueAccessory(that.log, device, api); + foundAccessories.push(accessory); + }); + callback(foundAccessories); }); - callback(foundAccessories); - }); + }; + + // Discover the bridge if needed + if (!this.ip_address) { + locateBridge.call(this, function (err, ip_address) { + // TODO: Find a way to persist this + that.ip_address = ip_address; + that.log("Save the Philips Hue bridge ip address "+ ip_address +" to your config to skip discovery."); + getLights(); + }); + } else { + getLights(); + } } }; From 479dfb63295ca823c64818405d3278b48c84a77d Mon Sep 17 00:00:00 2001 From: madmod Date: Sat, 27 Jun 2015 19:27:39 -0600 Subject: [PATCH 16/19] scope and name fixes removed deprecated node-hue-api methods and change this to that. --- platforms/PhilipsHue.js | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/platforms/PhilipsHue.js b/platforms/PhilipsHue.js index 2f3e187..4ebeafc 100644 --- a/platforms/PhilipsHue.js +++ b/platforms/PhilipsHue.js @@ -83,19 +83,21 @@ var execute = function(api, device, characteristic, value) { // Get the ip address of the first available bridge with meethue.com or a network scan. var locateBridge = function (callback) { + var that = this; + // Report the results of the scan to the user var getIp = function (err, bridges) { if (!bridges || bridges.length === 0) { - this.log("No Philips Hue bridges found."); + that.log("No Philips Hue bridges found."); callback(err || new Error("No bridges found")); return; } if (bridges.length > 1) { - this.log("Warning: Multiple Philips Hue bridges detected. The first bridge will be used automatically. To use a different bridge set ip_address manually in configuration."); + that.log("Warning: Multiple Philips Hue bridges detected. The first bridge will be used automatically. To use a different bridge set ip_address manually in configuration."); } - this.log( + that.log( "Philips Hue bridges found:", bridges.map(function (bridge) { // Bridge name is only returned from meethue.com so use id instead if it isn't there @@ -107,16 +109,16 @@ var locateBridge = function (callback) { }; // Try to discover the bridge ip using meethue.com - this.log("Attempting to discover Philips Hue bridge with network scan."); - api.locateBridges(function (locateError, bridges) { + that.log("Attempting to discover Philips Hue bridge with network scan."); + hue.nupnpSearch(function (locateError, bridges) { if (locateError) { - this.log("Philips Hue bridge discovery with meethue.com failed. Register your bridge with the meethue.com for more reiable discovery."); + that.log("Philips Hue bridge discovery with meethue.com failed. Register your bridge with the meethue.com for more reiable discovery."); - this.log("Attempting to discover Philips Hue bridge with network scan."); + that.log("Attempting to discover Philips Hue bridge with network scan."); - api.searchForBridges(function (searchError, bridges) { + hue.upnpSearch(function (searchError, bridges) { if (err) { - this.log("Philips Hue bridge discovery with network scan failed. Check your network connection or set ip_address manually in configuration."); + that.log("Philips Hue bridge discovery with network scan failed. Check your network connection or set ip_address manually in configuration."); getIp(new Error("Scan failed")); } else { getIp(null, bridges); From 8b71a1c44f7eeeb585ba8f7c0a781058e92dbbb2 Mon Sep 17 00:00:00 2001 From: madmod Date: Sat, 27 Jun 2015 20:23:32 -0600 Subject: [PATCH 17/19] fix network scan and logging --- platforms/PhilipsHue.js | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/platforms/PhilipsHue.js b/platforms/PhilipsHue.js index 4ebeafc..1723725 100644 --- a/platforms/PhilipsHue.js +++ b/platforms/PhilipsHue.js @@ -98,32 +98,36 @@ var locateBridge = function (callback) { } that.log( - "Philips Hue bridges found:", - bridges.map(function (bridge) { + "Philips Hue bridges found:\n" + + (bridges.map(function (bridge) { // Bridge name is only returned from meethue.com so use id instead if it isn't there - return '\t' + (bridge.name || bridge.id) + bridge.ipaddress + '\n'; - }) + return "\t" + bridge.ipaddress + ' - ' + (bridge.name || bridge.id); + })).join("\n") ); callback(null, bridges[0].ipaddress); }; // Try to discover the bridge ip using meethue.com - that.log("Attempting to discover Philips Hue bridge with network scan."); + that.log("Attempting to discover Philips Hue bridge with meethue.com..."); hue.nupnpSearch(function (locateError, bridges) { if (locateError) { that.log("Philips Hue bridge discovery with meethue.com failed. Register your bridge with the meethue.com for more reiable discovery."); - that.log("Attempting to discover Philips Hue bridge with network scan."); + that.log("Attempting to discover Philips Hue bridge with network scan..."); + + // Timeout after one minute + hue.upnpSearch(60000) + .then(function (bridges) { + that.log("Scan complete") - hue.upnpSearch(function (searchError, bridges) { - if (err) { - that.log("Philips Hue bridge discovery with network scan failed. Check your network connection or set ip_address manually in configuration."); - getIp(new Error("Scan failed")); - } else { getIp(null, bridges); - } - }); + }) + .fail(function (scanError) { + that.log("Philips Hue bridge discovery with network scan failed. Check your network connection or set ip_address manually in configuration."); + + getIp(new Error("Scan failed: " + scanError.message)); + }).done(); } else { getIp(null, bridges); } @@ -155,6 +159,8 @@ PhilipsHuePlatform.prototype = { // Discover the bridge if needed if (!this.ip_address) { locateBridge.call(this, function (err, ip_address) { + if (err) throw err; + // TODO: Find a way to persist this that.ip_address = ip_address; that.log("Save the Philips Hue bridge ip address "+ ip_address +" to your config to skip discovery."); From 8e80d7f0e030a37fb5d0c4395897dcae9d1841af Mon Sep 17 00:00:00 2001 From: Stephen Yeargin Date: Sat, 27 Jun 2015 21:39:58 -0500 Subject: [PATCH 18/19] Report initial state, refactor code a bit - The initial state of the device is now reported on connection - Properly detects what should happen with Hue/Hue Lux bulbs (removes the extra chrachteristics) - Scales values a bit more transparently so the color gets a bit closer - Moved `execute` method inside the accessory model for clearer designation - Cleaned up that ridiculous `if/else` block into a switch --- platforms/PhilipsHue.js | 277 ++++++++++++++++++++++------------------ 1 file changed, 155 insertions(+), 122 deletions(-) diff --git a/platforms/PhilipsHue.js b/platforms/PhilipsHue.js index 1723725..94be06e 100644 --- a/platforms/PhilipsHue.js +++ b/platforms/PhilipsHue.js @@ -27,9 +27,9 @@ var hue = require("node-hue-api"), var types = require("../lib/HAP-NodeJS/accessories/types.js"); function PhilipsHuePlatform(log, config) { - this.log = log; - this.ip_address = config["ip_address"]; - this.username = config["username"]; + this.log = log; + this.ip_address = config["ip_address"]; + this.username = config["username"]; } function PhilipsHueAccessory(log, device, api) { @@ -41,46 +41,6 @@ function PhilipsHueAccessory(log, device, api) { this.log = log; } -// Execute changes for various characteristics -// @todo Move this into accessory methods -var execute = function(api, device, characteristic, value) { - - var state = lightState.create(); - - characteristic = characteristic.toLowerCase(); - if (characteristic === "identify") { - state.alert('select'); - } - else if (characteristic === "power") { - if (value) { - state.on(); - } - else { - state.off(); - } - } - else if (characteristic === "hue") { - value = value * 182.5487; // Convert degrees to 0-65535 range - value = Math.round(value); - state.hue(value); - } - else if (characteristic === "brightness") { - state.brightness(value); - } - else if (characteristic === "saturation") { - state.saturation(value); - } - api.setLightState(device.id, state, function(err, lights) { - if (!err) { - console.log("executed accessory: " + device.name + ", and characteristic: " + characteristic + ", with value: " + value + "."); - } - else { - console.log(err); - } - }); -}; - - // Get the ip address of the first available bridge with meethue.com or a network scan. var locateBridge = function (callback) { var that = this; @@ -119,13 +79,11 @@ var locateBridge = function (callback) { // Timeout after one minute hue.upnpSearch(60000) .then(function (bridges) { - that.log("Scan complete") - + that.log("Scan complete"); getIp(null, bridges); }) .fail(function (scanError) { that.log("Philips Hue bridge discovery with network scan failed. Check your network connection or set ip_address manually in configuration."); - getIp(new Error("Scan failed: " + scanError.message)); }).done(); } else { @@ -134,25 +92,26 @@ var locateBridge = function (callback) { }); }; - PhilipsHuePlatform.prototype = { accessories: function(callback) { - this.log("Fetching Philips Hue lights..."); - var that = this; - var foundAccessories = []; - var getLights = function () { + that.log("Fetching Philips Hue lights..."); var api = new HueApi(that.ip_address, that.username); - // Connect to the API and loop through lights api.lights(function(err, response) { if (err) throw err; - response.lights.map(function(device) { - var accessory = new PhilipsHueAccessory(that.log, device, api); - foundAccessories.push(accessory); + response.lights.map(function(light) { + var foundAccessories = []; + // Get the state of each individual light and add to platform + api.lightStatus(light.id, function(err, device) { + if (err) throw err; + device.id = light.id; + var accessory = new PhilipsHueAccessory(that.log, device, api); + foundAccessories.push(accessory); + callback(foundAccessories); + }); }); - callback(foundAccessories); }); }; @@ -173,10 +132,141 @@ PhilipsHuePlatform.prototype = { }; PhilipsHueAccessory.prototype = { + // Convert 0-65535 to 0-360 + hueToArcDegrees: function(value) { + value = value/65535; + value = value*100; + value = Math.round(value); + return value; + }, + // Convert 0-360 to 0-65535 + arcDegreesToHue: function(value) { + value = value/360; + value = value*65535; + value = Math.round(value); + return value; + }, + // Convert 0-255 to 0-100 + bitsToPercentage: function(value) { + value = value/255; + value = value*100; + value = Math.round(value); + return value; + }, + // Create and set a light state + executeChange: function(api, device, characteristic, value) { + var that = this; + var state = lightState.create(); + switch(characteristic.toLowerCase()) { + case 'identify': + state.alert('select'); + break; + case 'power': + if (value) { + state.on(); + } + else { + state.off(); + } + break; + case 'hue': + state.hue(this.arcDegreesToHue(value)); + break; + case 'brightness': + state.brightness(value); + break; + case 'saturation': + state.saturation(value); + break; + } + api.setLightState(device.id, state, function(err, lights) { + if (!err) { + that.log(device.name + ", characteristic: " + characteristic + ", value: " + value + "."); + } + else { + that.log(err); + } + }); + }, // Get Services getServices: function() { var that = this; - return [ + var bulb_characteristics = [ + { + cType: types.NAME_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: this.name, + supportEvents: false, + supportBonjour: false, + manfDescription: "Name of service", + designedMaxLength: 255 + },{ + cType: types.POWER_STATE_CTYPE, + onUpdate: function(value) { + that.executeChange(that.api, that.device, "power", value); + }, + perms: ["pw","pr","ev"], + format: "bool", + initialValue: that.device.state.on, + supportEvents: false, + supportBonjour: false, + manfDescription: "Turn On the Light", + designedMaxLength: 1 + },{ + cType: types.BRIGHTNESS_CTYPE, + onUpdate: function(value) { + that.executeChange(that.api, that.device, "brightness", value); + }, + perms: ["pw","pr","ev"], + format: "int", + initialValue: that.bitsToPercentage(that.device.state.bri), + supportEvents: false, + supportBonjour: false, + manfDescription: "Adjust Brightness of Light", + designedMinValue: 0, + designedMaxValue: 100, + designedMinStep: 1, + unit: "%" + } + ]; + // Handle the Hue/Hue Lux divergence + if (that.device.state.hasOwnProperty('hue') && that.device.state.hasOwnProperty('sat')) { + bulb_characteristics.push({ + cType: types.HUE_CTYPE, + onUpdate: function(value) { + that.executeChange(that.api, that.device, "hue", value); + }, + perms: ["pw","pr","ev"], + format: "int", + initialValue: that.hueToArcDegrees(that.device.state.hue), + supportEvents: false, + supportBonjour: false, + manfDescription: "Adjust Hue of Light", + designedMinValue: 0, + designedMaxValue: 360, + designedMinStep: 1, + unit: "arcdegrees" + }); + bulb_characteristics.push({ + cType: types.SATURATION_CTYPE, + onUpdate: function(value) { + that.executeChange(that.api, that.device, "saturation", value); + }, + perms: ["pw","pr","ev"], + format: "int", + initialValue: that.bitsToPercentage(that.device.state.sat), + supportEvents: false, + supportBonjour: false, + manfDescription: "Adjust Saturation of Light", + designedMinValue: 0, + designedMaxValue: 100, + designedMinStep: 1, + unit: "%" + }); + } + var accessory_data = [ { sType: types.ACCESSORY_INFORMATION_STYPE, characteristics: [ @@ -205,7 +295,7 @@ PhilipsHueAccessory.prototype = { onUpdate: null, perms: ["pr"], format: "string", - initialValue: this.model, + initialValue: that.model, supportEvents: false, supportBonjour: false, manfDescription: "Model", @@ -215,14 +305,16 @@ PhilipsHueAccessory.prototype = { onUpdate: null, perms: ["pr"], format: "string", - initialValue: this.model + this.id, + initialValue: that.device.uniqueid, supportEvents: false, supportBonjour: false, manfDescription: "SN", designedMaxLength: 255 },{ cType: types.IDENTIFY_CTYPE, - onUpdate: function(value) { console.log("Change:",value); execute(that.api, that.device, "identify", value); }, + onUpdate: function(value) { + that.executeChange(that.api, that.device, "identify", value); + }, perms: ["pw"], format: "bool", initialValue: false, @@ -234,70 +326,11 @@ PhilipsHueAccessory.prototype = { ] },{ sType: types.LIGHTBULB_STYPE, - characteristics: [ - { - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.name, - supportEvents: false, - supportBonjour: false, - manfDescription: "Name of service", - designedMaxLength: 255 - },{ - cType: types.POWER_STATE_CTYPE, - onUpdate: function(value) { console.log("Change:",value); execute(that.api, that.device, "power", value); }, - perms: ["pw","pr","ev"], - format: "bool", - initialValue: false, - supportEvents: false, - supportBonjour: false, - manfDescription: "Turn On the Light", - designedMaxLength: 1 - },{ - cType: types.HUE_CTYPE, - onUpdate: function(value) { console.log("Change:",value); execute(that.api, that.device, "hue", value); }, - perms: ["pw","pr","ev"], - format: "int", - initialValue: 0, - supportEvents: false, - supportBonjour: false, - manfDescription: "Adjust Hue of Light", - designedMinValue: 0, - designedMaxValue: 360, - designedMinStep: 1, - unit: "arcdegrees" - },{ - cType: types.BRIGHTNESS_CTYPE, - onUpdate: function(value) { console.log("Change:",value); execute(that.api, that.device, "brightness", value); }, - perms: ["pw","pr","ev"], - format: "int", - initialValue: 0, - supportEvents: false, - supportBonjour: false, - manfDescription: "Adjust Brightness of Light", - designedMinValue: 0, - designedMaxValue: 100, - designedMinStep: 1, - unit: "%" - },{ - cType: types.SATURATION_CTYPE, - onUpdate: function(value) { console.log("Change:",value); execute(that.api, that.device, "saturation", value); }, - perms: ["pw","pr","ev"], - format: "int", - initialValue: 0, - supportEvents: false, - supportBonjour: false, - manfDescription: "Adjust Saturation of Light", - designedMinValue: 0, - designedMaxValue: 100, - designedMinStep: 1, - unit: "%" - } - ] + // `bulb_characteristics` defined based on bulb type + characteristics: bulb_characteristics } ]; + return accessory_data; } }; From b2280c3902c925573b4e5dc2386557b8c9569cf1 Mon Sep 17 00:00:00 2001 From: Stephen Yeargin Date: Sat, 27 Jun 2015 23:24:09 -0500 Subject: [PATCH 19/19] [Trivial] Text cleanup --- platforms/PhilipsHue.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/platforms/PhilipsHue.js b/platforms/PhilipsHue.js index 94be06e..fcd5624 100644 --- a/platforms/PhilipsHue.js +++ b/platforms/PhilipsHue.js @@ -54,7 +54,7 @@ var locateBridge = function (callback) { } if (bridges.length > 1) { - that.log("Warning: Multiple Philips Hue bridges detected. The first bridge will be used automatically. To use a different bridge set ip_address manually in configuration."); + that.log("Warning: Multiple Philips Hue bridges detected. The first bridge will be used automatically. To use a different bridge, set the `ip_address` manually in the configuration."); } that.log( @@ -72,7 +72,7 @@ var locateBridge = function (callback) { that.log("Attempting to discover Philips Hue bridge with meethue.com..."); hue.nupnpSearch(function (locateError, bridges) { if (locateError) { - that.log("Philips Hue bridge discovery with meethue.com failed. Register your bridge with the meethue.com for more reiable discovery."); + that.log("Philips Hue bridge discovery with meethue.com failed. Register your bridge with the meethue.com for more reliable discovery."); that.log("Attempting to discover Philips Hue bridge with network scan..."); @@ -94,9 +94,9 @@ var locateBridge = function (callback) { PhilipsHuePlatform.prototype = { accessories: function(callback) { + this.log("Fetching Philips Hue lights..."); var that = this; var getLights = function () { - that.log("Fetching Philips Hue lights..."); var api = new HueApi(that.ip_address, that.username); // Connect to the API and loop through lights api.lights(function(err, response) {