From 17fc8f1829ee884c097cda3127122858220c509a Mon Sep 17 00:00:00 2001 From: David Parry Date: Fri, 11 Sep 2015 13:04:09 +1000 Subject: [PATCH] implement the LiFX LAN API as a configurable option for higher lantency connections --- package.json | 1 + platforms/LIFx.js | 245 +++++++++++++++++++++++++++++++++++++--------- 2 files changed, 200 insertions(+), 46 deletions(-) diff --git a/package.json b/package.json index 8427666..3732b9d 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "harmonyhubjs-client": "^1.1.4", "harmonyhubjs-discover": "git+https://github.com/swissmanu/harmonyhubjs-discover.git", "lifx-api": "^1.0.1", + "lifx": "https://github.com/magicmonkey/lifxjs.git", "mdns": "^2.2.4", "node-hue-api": "^1.0.5", "node-icontrol": "^0.1.4", diff --git a/platforms/LIFx.js b/platforms/LIFx.js index 8f4ef0d..62167f6 100644 --- a/platforms/LIFx.js +++ b/platforms/LIFx.js @@ -1,16 +1,45 @@ +'use strict'; + +// LiFX Platform Shim for HomeBridge +// +// Remember to add platform to config.json. Example: +// "platforms": [ +// { +// "platform": "LIFx", // required +// "name": "LIFx", // required +// "access_token": "access token", // required +// "use_lan": "true" // optional set to "true" (gets and sets over the lan) or "get" (gets only over the lan) +// } +// ], +// +// 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. +// + var Service = require("HAP-NodeJS").Service; var Characteristic = require("HAP-NodeJS").Characteristic; -var lifxObj = require('lifx-api'); -var lifx; +var lifxRemoteObj = require('lifx-api'); +var lifx_remote; + +var lifxLanObj; +var lifx_lan; +var use_lan; function LIFxPlatform(log, config){ + // auth info + this.access_token = config["access_token"]; - // auth info - this.access_token = config["access_token"]; + lifx_remote = new lifxRemoteObj(this.access_token); - lifx = new lifxObj(this.access_token); + // use remote or lan api ? + use_lan = config["use_lan"] || false; - this.log = log; + if (use_lan != false) { + lifxLanObj = require('lifx'); + lifx_lan = lifxLanObj.init(); + } + + this.log = log; } LIFxPlatform.prototype = { @@ -20,7 +49,7 @@ LIFxPlatform.prototype = { var that = this; var foundAccessories = []; - lifx.listLights("all", function(body) { + lifx_remote.listLights("all", function(body) { var bulbs = JSON.parse(body); for(var i = 0; i < bulbs.length; i ++) { @@ -33,20 +62,54 @@ LIFxPlatform.prototype = { } function LIFxBulbAccessory(log, bulb) { - // device info - this.name = bulb.label; - this.model = bulb.product_name; - this.deviceId = bulb.id; - this.serial = bulb.uuid; - this.capabilities = bulb.capabilities; - this.log = log; + // device info + this.name = bulb.label; + this.model = bulb.product_name; + this.deviceId = bulb.id; + this.serial = bulb.uuid; + this.capabilities = bulb.capabilities; + this.log = log; } LIFxBulbAccessory.prototype = { - get: function(type, callback){ + getLan: function(type, callback){ var that = this; - lifx.listLights("id:"+ that.deviceId, function(body) { + if (!lifx_lan.bulbs[this.deviceId]) { + callback(new Error("Device not found"), false); + return; + } + + lifx_lan.requestStatus(); + lifx_lan.on('bulbstate', function(bulb) { + if (callback == null) { + return; + } + + if (bulb.addr.toString('hex') == that.deviceId) { + switch(type) { + case "power": + callback(null, bulb.state.power > 0); + break; + case "brightness": + callback(null, Math.round(bulb.state.brightness * 100 / 65535)); + break; + case "hue": + callback(null, Math.round(bulb.state.hue * 360 / 65535)); + break; + case "saturation": + callback(null, Math.round(bulb.state.saturation * 100 / 65535)); + break; + } + + callback = null + } + }); + }, + getRemote: function(type, callback){ + var that = this; + + lifx_remote.listLights("id:"+ that.deviceId, function(body) { var bulb = JSON.parse(body); if (bulb.connected != true) { @@ -71,66 +134,156 @@ LIFxBulbAccessory.prototype = { }); }, identify: function(callback) { - var that = this; - - lifx.breatheEffect("id:"+ that.deviceId, 'green', null, 1, 3, false, true, 0.5, function (body) { + lifx_remote.breatheEffect("id:"+ this.deviceId, 'green', null, 1, 3, false, true, 0.5, function (body) { callback(); }); }, - setColor: function(type, state, callback){ - var that = this; + setLanColor: function(type, value, callback){ + var bulb = lifx_lan.bulbs[this.deviceId]; + + if (!bulb) { + callback(new Error("Device not found"), false); + return; + } + + var state = { + hue: bulb.state.hue, + saturation: bulb.state.saturation, + brightness: bulb.state.brightness, + kelvin: bulb.state.kelvin + }; + + var scale = type == "hue" ? 360 : 100; + + state[type] = Math.round(value * 65535 / scale) & 0xffff; + lifx_lan.lightsColour(state.hue, state.saturation, state.brightness, state.kelvin, 0, bulb); + + callback(null); + }, + setLanPower: function(state, callback){ + var bulb = lifx_lan.bulbs[this.deviceId]; + + if (!bulb) { + callback(new Error("Device not found"), false); + return; + } + + if (state) { + lifx_lan.lightsOn(bulb); + } + else { + lifx_lan.lightsOff(bulb); + } + + callback(null); + }, + setRemoteColor: function(type, value, callback){ var color; switch(type) { case "brightness": - color = "brightness:" + (state / 100); + color = "brightness:" + (value / 100); break; case "hue": - color = "hue:" + state; + color = "hue:" + value; break; case "saturation": - color = "saturation:" + (state / 100); + color = "saturation:" + (value / 100); break; } - lifx.setColor("id:"+ that.deviceId, color, 0, null, function (body) { + lifx_remote.setColor("id:"+ this.deviceId, color, 0, null, function (body) { callback(); }); }, - setPower: function(state, callback){ + setRemotePower: function(state, callback){ var that = this; - lifx.setPower("id:"+ that.deviceId, (state == 1 ? "on" : "off"), 0, function (body) { + lifx_remote.setPower("id:"+ that.deviceId, (state == 1 ? "on" : "off"), 0, function (body) { callback(); }); }, - getServices: function() { var that = this; var services = [] var service = new Service.Lightbulb(this.name); - service - .getCharacteristic(Characteristic.On) - .on('identify', function(callback) {}) - .on('get', function(callback) { that.get("power", callback);}) - .on('set', function(value, callback) {that.setPower(value, callback);}); + switch(use_lan) { + case true: + case "true": + // gets and sets over the lan api + service + .getCharacteristic(Characteristic.On) + .on('identify', function(callback) {}) + .on('get', function(callback) { that.getLan("power", callback);}) + .on('set', function(value, callback) {that.setLanPower(value, callback);}); - service - .addCharacteristic(Characteristic.Brightness) - .on('get', function(callback) { that.get("brightness", callback);}) - .on('set', function(value, callback) { that.setColor("brightness", value, callback);}); + service + .addCharacteristic(Characteristic.Brightness) + .on('get', function(callback) { that.getLan("brightness", callback);}) + .on('set', function(value, callback) { that.setLanColor("brightness", value, callback);}); - if (this.capabilities.has_color == true) { - service - .addCharacteristic(Characteristic.Hue) - .on('get', function(callback) { that.get("hue", callback);}) - .on('set', function(value, callback) { that.setColor("hue", value, callback);}); + if (this.capabilities.has_color == true) { + service + .addCharacteristic(Characteristic.Hue) + .on('get', function(callback) { that.getLan("hue", callback);}) + .on('set', function(value, callback) { that.setLanColor("hue", value, callback);}); - service - .addCharacteristic(Characteristic.Saturation) - .on('get', function(callback) { that.get("saturation", callback);}) - .on('set', function(value, callback) { that.setColor("saturation", value, callback);}); + service + .addCharacteristic(Characteristic.Saturation) + .on('get', function(callback) { that.getLan("saturation", callback);}) + .on('set', function(value, callback) { that.setLanColor("saturation", value, callback);}); + } + break; + case "get": + // gets over the lan api, sets over the remote api + service + .getCharacteristic(Characteristic.On) + .on('identify', function(callback) {}) + .on('get', function(callback) { that.getLan("power", callback);}) + .on('set', function(value, callback) {that.setRemotePower(value, callback);}); + + service + .addCharacteristic(Characteristic.Brightness) + .on('get', function(callback) { that.getLan("brightness", callback);}) + .on('set', function(value, callback) { that.setRemoteColor("brightness", value, callback);}); + + if (this.capabilities.has_color == true) { + service + .addCharacteristic(Characteristic.Hue) + .on('get', function(callback) { that.getLan("hue", callback);}) + .on('set', function(value, callback) { that.setRemoteColor("hue", value, callback);}); + + service + .addCharacteristic(Characteristic.Saturation) + .on('get', function(callback) { that.getLan("saturation", callback);}) + .on('set', function(value, callback) { that.setRemoteColor("saturation", value, callback);}); + } + break; + default: + // gets and sets over the remote api + service + .getCharacteristic(Characteristic.On) + .on('identify', function(callback) {}) + .on('get', function(callback) { that.getRemote("power", callback);}) + .on('set', function(value, callback) {that.setRemotePower(value, callback);}); + + service + .addCharacteristic(Characteristic.Brightness) + .on('get', function(callback) { that.getRemote("brightness", callback);}) + .on('set', function(value, callback) { that.setRemoteColor("brightness", value, callback);}); + + if (this.capabilities.has_color == true) { + service + .addCharacteristic(Characteristic.Hue) + .on('get', function(callback) { that.getRemote("hue", callback);}) + .on('set', function(value, callback) { that.setRemoteColor("hue", value, callback);}); + + service + .addCharacteristic(Characteristic.Saturation) + .on('get', function(callback) { that.getRemote("saturation", callback);}) + .on('set', function(value, callback) { that.setRemoteColor("saturation", value, callback);}); + } } services.push(service);