diff --git a/README.md b/README.md index 4fae78b..f11dd75 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), [Philips Hue](http://meethue.com), [LimitlessLED/MiLight/Easybulb](http://www.limitlessled.com/)) + * _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), [LimitlessLED/MiLight/Easybulb](http://www.limitlessled.com/), [LIFx](http://www.lifx.com/)) * _Siri, set the movie scene._ ([Logitech Harmony](http://myharmony.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/accessories/MiLight.js b/accessories/MiLight.js deleted file mode 100644 index 85b09d6..0000000 --- a/accessories/MiLight.js +++ /dev/null @@ -1,132 +0,0 @@ -var Service = require("HAP-NodeJS").Service; -var Characteristic = require("HAP-NodeJS").Characteristic; -var Milight = require('node-milight-promise').MilightController; -var commands = require('node-milight-promise').commands; - -module.exports = { - accessory: MiLight -} - -function MiLight(log, config) { - this.log = log; - - // config info - this.ip_address = config["ip_address"]; - this.port = config["port"]; - this.name = config["name"]; - this.zone = config["zone"]; - this.type = config["type"]; - this.delay = config["delay"]; - this.repeat = config["repeat"]; -} - -var light = new Milight({ - ip: this.ip_address, - port: this.port, - delayBetweenCommands: this.delay, - commandRepeat: this.repeat -}); - -MiLight.prototype = { - - setPowerState: function(powerOn, callback) { - if (powerOn) { - light.sendCommands(commands[this.type].on(this.zone)); - this.log("Setting power state to on"); - } else { - light.sendCommands(commands[this.type].off(this.zone)); - this.log("Setting power state to off"); - } - callback(); - }, - - setBrightness: function(level, callback) { - if (level <= 2 && (this.type == "rgbw" || this.type == "white")) { - // If setting brightness to 2 or lower, instead set night mode for lamps that support it - this.log("Setting night mode", level); - - light.sendCommands(commands[this.type].off(this.zone)); - // Not sure if this timing is going to work or not? It's supposed to be 100ms after the off command - light.sendCommands(commands[this.type].nightMode(this.zone)); - } else { - this.log("Setting brightness to %s", level); - - // Send on command to ensure we're addressing the right bulb - light.sendCommands(commands[this.type].on(this.zone)); - - // If this is an rgbw lamp, set the absolute brightness specified - if (this.type == "rgbw") { - light.sendCommands(commands.rgbw.brightness(level)); - } else { - // If this is an rgb or a white lamp, they only support brightness up and down. - // Set brightness up when value is >50 and down otherwise. Not sure how well this works real-world. - if (level >= 50) { - if (this.type == "white" && level == 100) { - // But the white lamps do have a "maximum brightness" command - light.sendCommands(commands.white.maxBright(this.zone)); - } else { - light.sendCommands(commands[this.type].brightUp()); - } - } else { - light.sendCommands(commands[this.type].brightDown()); - } - } - } - callback(); - }, - - setHue: function(value, callback) { - this.log("Setting hue to %s", value); - - // Send on command to ensure we're addressing the right bulb - light.sendCommands(commands[this.type].on(this.zone)); - - if (this.type == "rgbw") { - if (value == 0) { - light.sendCommands(commands.rgbw.whiteMode(this.zone)); - } else { - light.sendCommands(commands.rgbw.hue(commands.rgbw.hsvToMilightColor(Array(value, 0, 0)))); - } - } else if (this.type == "rgb") { - light.sendCommands(commands.rgb.hue(commands.rgbw.hsvToMilightColor(Array(value, 0, 0)))); - } else if (this.type == "white") { - // Again, white lamps don't support setting an absolue colour temp, so trying to do warmer/cooler step at a time based on colour - if (value >= 180) { - light.sendCommands(commands.white.warmer()); - } else { - light.sendCommands(commands.white.cooler()); - } - } - - }, - - identify: function(callback) { - this.log("Identify requested!"); - callback(); // success - }, - - getServices: function() { - var informationService = new Service.AccessoryInformation(); - - informationService - .setCharacteristic(Characteristic.Manufacturer, "MiLight") - .setCharacteristic(Characteristic.Model, this.type) - .setCharacteristic(Characteristic.SerialNumber, "MILIGHT12345"); - - var lightbulbService = new Service.Lightbulb(); - - lightbulbService - .getCharacteristic(Characteristic.On) - .on('set', this.setPowerState.bind(this)); - - lightbulbService - .addCharacteristic(new Characteristic.Brightness()) - .on('set', this.setBrightness.bind(this)); - - lightbulbService - .addCharacteristic(new Characteristic.Hue()) - .on('set', this.setHue.bind(this)); - - return [informationService, lightbulbService]; - } -}; diff --git a/accessories/WeMo.js b/accessories/WeMo.js index 26ef822..df16f56 100644 --- a/accessories/WeMo.js +++ b/accessories/WeMo.js @@ -30,6 +30,30 @@ WeMoAccessory.prototype.search = function() { }.bind(this)); } +WeMoAccessory.prototype.getMotion = function(callback) { + + if (!this.device) { + this.log("No '%s' device found (yet?)", this.wemoName); + callback(new Error("Device not found"), false); + return; + } + + this.log("Getting motion state on the '%s'...", this.wemoName); + + this.device.getBinaryState(function(err, result) { + if (!err) { + var binaryState = parseInt(result); + var powerOn = binaryState > 0; + this.log("Motion state for the '%s' is %s", this.wemoName, binaryState); + callback(null, powerOn); + } + else { + this.log("Error getting motion state on the '%s': %s", this.wemoName, err.message); + callback(err); + } + }.bind(this)); +} + WeMoAccessory.prototype.getPowerOn = function(callback) { if (!this.device) { @@ -122,6 +146,15 @@ WeMoAccessory.prototype.getServices = function() { return [garageDoorService]; } + else if (this.service == "MotionSensor") { + var motionSensorService = new Service.MotionSensor(this.name); + + motionSensorService + .getCharacteristic(Characteristic.MotionDetected) + .on('get', this.getMotion.bind(this)); + + return [motionSensorService]; + } else { throw new Error("Unknown service type '%s'", this.service); } diff --git a/config-sample-knx.json b/config-sample-knx.json index df0de60..a8e52b1 100644 --- a/config-sample-knx.json +++ b/config-sample-knx.json @@ -72,7 +72,7 @@ }, { "accessory_type": "knxdevice", - "description":"sample device with multiple services. Multiple services of different types are widely supported" + "description":"sample device with multiple services. Multiple services of different types are widely supported", "name": "Office", "services": [ { @@ -118,4 +118,4 @@ } ], "accessories": [] -} \ No newline at end of file +} diff --git a/config-sample.json b/config-sample.json index 4245bd5..d684c9d 100644 --- a/config-sample.json +++ b/config-sample.json @@ -71,7 +71,17 @@ "platform": "YamahaAVR", "play_volume": -35, "setMainInputTo": "AirPlay" - } + }, + { + "platform": "MiLight", + "name": "MiLight", + "ip_address": "255.255.255.255", + "port": 8899, + "type": "rgbw", + "delay": 30, + "repeat": 3, + "zones":["Kitchen Lamp","Bedroom Lamp","Living Room Lamp","Hallway Lamp"] + } ], "accessories": [ @@ -152,16 +162,6 @@ "port" : 4999, // Port the SER2SOCK process is running on "pin": "1234" // PIN used for arming / disarming }, - { - "accessory":"MiLight", - "name": "Lamp", - "ip_address": "255.255.255.255", - "port": 8899, - "zone": 1, - "type": "rgbw", - "delay": 35, - "repeat": 3 - }, { "accessory": "Tesla", "name": "Tesla", diff --git a/package.json b/package.json index 9fa4cb5..3732b9d 100644 --- a/package.json +++ b/package.json @@ -14,11 +14,13 @@ "ad2usb": "git+https://github.com/alistairg/node-ad2usb.git#local", "carwingsjs": "0.0.x", "color": "0.10.x", - "elkington": "kevinohara80/elkington", "eibd": "^0.3.1", + "elkington": "kevinohara80/elkington", "hap-nodejs": "git+https://github.com/KhaosT/HAP-NodeJS#6bf0f9eaaa2d87db8d1768114c61f4acbb095c41", "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 new file mode 100644 index 0000000..79988eb --- /dev/null +++ b/platforms/LIFx.js @@ -0,0 +1,302 @@ +'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 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"]; + + lifx_remote = new lifxRemoteObj(this.access_token); + + // use remote or lan api ? + use_lan = config["use_lan"] || false; + + if (use_lan != false) { + lifxLanObj = require('lifx'); + lifx_lan = lifxLanObj.init(); + } + + this.log = log; +} + +LIFxPlatform.prototype = { + accessories: function(callback) { + this.log("Fetching LIFx devices."); + + var that = this; + var foundAccessories = []; + + lifx_remote.listLights("all", function(body) { + var bulbs = JSON.parse(body); + + for(var i = 0; i < bulbs.length; i ++) { + var accessory = new LIFxBulbAccessory(that.log, bulbs[i]); + foundAccessories.push(accessory); + } + callback(foundAccessories) + }); + } +} + +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; +} + +LIFxBulbAccessory.prototype = { + getLan: function(type, callback){ + var that = this; + + 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) { + callback(new Error("Device not found"), false); + return; + } + + switch(type) { + case "power": + callback(null, bulb.power == "on" ? 1 : 0); + break; + case "brightness": + callback(null, Math.round(bulb.brightness * 100)); + break; + case "hue": + callback(null, bulb.color.hue); + break; + case "saturation": + callback(null, Math.round(bulb.color.saturation * 100)); + break; + } + }); + }, + identify: function(callback) { + lifx_remote.breatheEffect("id:"+ this.deviceId, 'green', null, 1, 3, false, true, 0.5, function (body) { + callback(); + }); + }, + 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:" + (value / 100); + break; + case "hue": + color = "hue:" + value; + break; + case "saturation": + color = "saturation:" + (value / 100); + break; + } + + lifx_remote.setColor("id:"+ this.deviceId, color, 0, null, function (body) { + callback(); + }); + }, + setRemotePower: function(state, callback){ + var that = this; + + 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); + + switch(use_lan) { + case true: + case "true": + // gets and sets over the lan api + service + .getCharacteristic(Characteristic.On) + .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.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.getLan("hue", callback);}) + .on('set', function(value, callback) { that.setLanColor("hue", 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('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('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); + + service = new Service.AccessoryInformation(); + + service + .setCharacteristic(Characteristic.Manufacturer, "LIFX") + .setCharacteristic(Characteristic.Model, this.model) + .setCharacteristic(Characteristic.SerialNumber, this.serial); + + services.push(service); + + return services; + } +} + +module.exports.accessory = LIFxBulbAccessory; +module.exports.platform = LIFxPlatform; diff --git a/platforms/MiLight.js b/platforms/MiLight.js new file mode 100644 index 0000000..3869e74 --- /dev/null +++ b/platforms/MiLight.js @@ -0,0 +1,242 @@ +/* + +MiLight platform shim for Homebridge +Written by Sam Edwards (https://samedwards.ca/) + +Uses the node-milight-promise library (https://github.com/mwittig/node-milight-promise) which features some code from +applamp.nl (http://www.applamp.nl/service/applamp-api/) and uses other details from (http://www.limitlessled.com/dev/) + +Configure in config.json as follows: + +"platforms": [ + { + "platform":"MiLight", + "name":"MiLight", + "ip_address": "255.255.255.255", + "port": 8899, + "type": "rgbw", + "delay": 30, + "repeat": 3, + "zones":["Kitchen Lamp","Bedroom Lamp","Living Room Lamp","Hallway Lamp"] + } +] + +Where the parameters are: + *platform (required): This must be "MiLight", and refers to the name of the accessory as exported from this file + *name (optional): The display name used for logging output by Homebridge. Best to set to "MiLight" + *ip_address (optional): The IP address of the WiFi Bridge. Default to the broadcast address of 255.255.255.255 if not specified + *port (optional): Port of the WiFi bridge. Defaults to 8899 if not specified + *type (optional): One of either "rgbw", "rgb", or "white", depending on the type of bulb being controlled. This applies to all zones. Defaults to rgbw. + *delay (optional): Delay between commands sent over UDP. Default 30ms. May cause delays when sending a lot of commands. Try decreasing to improve. + *repeat (optional): Number of times to repeat the UDP command for better reliability. Default 3 + *zones (required): An array of the names of the zones, in order, 1-4. Use null if a zone is skipped. RGB lamps can only have a single zone. + +Tips and Tricks: + *Setting the brightness of an rgbw or a white bulb will set it to "night mode", which is dimmer than the lowest brightness setting + *White and rgb bulbs don't support absolute brightness setting, so we just send a brightness up/brightness down command depending + if we got a percentage above/below 50% respectively + *The only exception to the above is that white bulbs support a "maximum brightness" command, so we send that when we get 100% + *Implemented warmer/cooler for white lamps in a similar way to brightnes, except this time above/below 180 degrees on the colour wheel + *I welcome feedback on a better way to work the brightness/hue for white and rgb bulbs + +Troubleshooting: +The node-milight-promise library provides additional debugging output when the MILIGHT_DEBUG environmental variable is set + +TODO: + *Possibly build in some sort of state logging and persistance so that we can answswer HomeKit status queries to the best of our ability + +*/ + +var Service = require("HAP-NodeJS").Service; +var Characteristic = require("HAP-NodeJS").Characteristic; +var Milight = require('node-milight-promise').MilightController; +var commands = require('node-milight-promise').commands; + +module.exports = { + accessory: MiLightAccessory, + platform: MiLightPlatform +} + +function MiLightPlatform(log, config) { + this.log = log; + + this.config = config; +} + +MiLightPlatform.prototype = { + accessories: function(callback) { + var zones = []; + + // Various error checking + if (this.config.zones) { + var zoneLength = this.config.zones.length; + } else { + this.log("ERROR: Could not read zones from configuration."); + return; + } + + if (!this.config["type"]) { + this.log("INFO: Type not specified, defaulting to rgbw"); + this.config["type"] = "rgbw"; + } + + if (zoneLength == 0) { + this.log("ERROR: No zones found in configuration."); + return; + } else if (this.config["type"] == "rgb" && zoneLength > 1) { + this.log("WARNING: RGB lamps only have a single zone. Only the first defined zone will be used."); + zoneLength = 1; + } else if (zoneLength > 4) { + this.log("WARNING: Only a maximum of 4 zones are supported per bridge. Only recognizing the first 4 zones."); + zoneLength = 4; + } + + // Create lamp accessories for all of the defined zones + for (var i=0; i < zoneLength; i++) { + if (!!this.config.zones[i]) { + this.config["name"] = this.config.zones[i]; + this.config["zone"] = i+1; + lamp = new MiLightAccessory(this.log, this.config); + zones.push(lamp); + } + } + if (zones.length > 0) { + callback(zones); + } else { + this.log("ERROR: Unable to find any valid zones"); + return; + } + } +} + +function MiLightAccessory(log, config) { + this.log = log; + + // config info + this.ip_address = config["ip_address"]; + this.port = config["port"]; + this.name = config["name"]; + this.zone = config["zone"]; + this.type = config["type"]; + this.delay = config["delay"]; + this.repeat = config["repeat"]; + + this.light = new Milight({ + ip: this.ip_address, + port: this.port, + delayBetweenCommands: this.delay, + commandRepeat: this.repeat + }); + +} +MiLightAccessory.prototype = { + + setPowerState: function(powerOn, callback) { + if (powerOn) { + this.log("["+this.name+"] Setting power state to on"); + this.light.sendCommands(commands[this.type].on(this.zone)); + } else { + this.log("["+this.name+"] Setting power state to off"); + this.light.sendCommands(commands[this.type].off(this.zone)); + } + callback(); + }, + + setBrightness: function(level, callback) { + if (level == 0) { + // If brightness is set to 0, turn off the lamp + this.log("["+this.name+"] Setting brightness to 0 (off)"); + this.light.sendCommands(commands[this.type].off(this.zone)); + } else if (level <= 2 && (this.type == "rgbw" || this.type == "white")) { + // If setting brightness to 2 or lower, instead set night mode for lamps that support it + this.log("["+this.name+"] Setting night mode", level); + + this.light.sendCommands(commands[this.type].off(this.zone)); + // Ensure we're pausing for 100ms between these commands as per the spec + this.light.pause(100); + this.light.sendCommands(commands[this.type].nightMode(this.zone)); + + } else { + this.log("["+this.name+"] Setting brightness to %s", level); + + // Send on command to ensure we're addressing the right bulb + this.light.sendCommands(commands[this.type].on(this.zone)); + + // If this is an rgbw lamp, set the absolute brightness specified + if (this.type == "rgbw") { + this.light.sendCommands(commands.rgbw.brightness(level)); + } else { + // If this is an rgb or a white lamp, they only support brightness up and down. + // Set brightness up when value is >50 and down otherwise. Not sure how well this works real-world. + if (level >= 50) { + if (this.type == "white" && level == 100) { + // But the white lamps do have a "maximum brightness" command + this.light.sendCommands(commands.white.maxBright(this.zone)); + } else { + this.light.sendCommands(commands[this.type].brightUp()); + } + } else { + this.light.sendCommands(commands[this.type].brightDown()); + } + } + } + callback(); + }, + + setHue: function(value, callback) { + this.log("["+this.name+"] Setting hue to %s", value); + + var hue = Array(value, 0, 0); + + // Send on command to ensure we're addressing the right bulb + this.light.sendCommands(commands[this.type].on(this.zone)); + + if (this.type == "rgbw") { + if (value == 0) { + this.light.sendCommands(commands.rgbw.whiteMode(this.zone)); + } else { + this.light.sendCommands(commands.rgbw.hue(commands.rgbw.hsvToMilightColor(hue))); + } + } else if (this.type == "rgb") { + this.light.sendCommands(commands.rgb.hue(commands.rgbw.hsvToMilightColor(hue))); + } else if (this.type == "white") { + // Again, white lamps don't support setting an absolue colour temp, so trying to do warmer/cooler step at a time based on colour + if (value >= 180) { + this.light.sendCommands(commands.white.cooler()); + } else { + this.light.sendCommands(commands.white.warmer()); + } + } + callback(); + }, + + identify: function(callback) { + this.log("["+this.name+"] Identify requested!"); + callback(); // success + }, + + getServices: function() { + var informationService = new Service.AccessoryInformation(); + + informationService + .setCharacteristic(Characteristic.Manufacturer, "MiLight") + .setCharacteristic(Characteristic.Model, this.type) + .setCharacteristic(Characteristic.SerialNumber, "MILIGHT12345"); + + var lightbulbService = new Service.Lightbulb(); + + lightbulbService + .getCharacteristic(Characteristic.On) + .on('set', this.setPowerState.bind(this)); + + lightbulbService + .addCharacteristic(new Characteristic.Brightness()) + .on('set', this.setBrightness.bind(this)); + + lightbulbService + .addCharacteristic(new Characteristic.Hue()) + .on('set', this.setHue.bind(this)); + + return [informationService, lightbulbService]; + } +}; diff --git a/platforms/Sonos.js b/platforms/Sonos.js index 67d086e..1d19c2f 100644 --- a/platforms/Sonos.js +++ b/platforms/Sonos.js @@ -6,6 +6,8 @@ function SonosPlatform(log, config){ this.config = config; this.name = config["name"]; this.playVolume = config["play_volume"]; + // timeout for device discovery + this.discoveryTimeout = (config.deviceDiscoveryTimeout || 10)*1000; // assume 10sec as a default } SonosPlatform.prototype = { @@ -16,6 +18,18 @@ SonosPlatform.prototype = { // track found devices so we don't add duplicates var roomNamesFound = {}; + // collector array for the devices from callbacks + var devicesFound = []; + // tell the sonos callbacks if timeout already occured + var timeout = false; + + // the timeout event will push the accessories back + setTimeout(function(){ + timeout=true; + callback(devicesFound); + }, this.discoveryTimeout); + + sonos.search(function (device) { that.log("Found device at " + device.host); @@ -26,9 +40,13 @@ SonosPlatform.prototype = { if (!roomNamesFound[roomName]) { roomNamesFound[roomName] = true; that.log("Found playable device - " + roomName); + if (timeout) { + that.log("Ignored: Discovered after timeout (Set deviceDiscoveryTimeout parameter in Sonos section of config.json)"); + } // device is an instance of sonos.Sonos var accessory = new SonosAccessory(that.log, that.config, device, description); - callback([accessory]); + // add it to the collector array + devicesFound.push(accessory); } else { that.log("Ignoring playable device with duplicate room name - " + roomName);