From a4c3f73eb54620abfe73ad95627110d34f8df0bb Mon Sep 17 00:00:00 2001 From: Sam Edwards Date: Mon, 7 Sep 2015 13:29:51 -0700 Subject: [PATCH 1/2] [MiLight] Fix scope issue that prevented config from working, added documentation, and added correct pause for night mode commands --- accessories/MiLight.js | 68 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 61 insertions(+), 7 deletions(-) diff --git a/accessories/MiLight.js b/accessories/MiLight.js index 85b09d6..cfe339a 100644 --- a/accessories/MiLight.js +++ b/accessories/MiLight.js @@ -1,3 +1,53 @@ +/* + +MiLight accessory 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: + +"accessories": [ + { + "accessory":"MiLight", + "name": "Lamp", + "ip_address": "255.255.255.255", + "port": 8899, + "zone": 1, + "type": "rgbw", + "delay": 30, + "repeat": 3 + } +] + +Where the parameters are: + *accessory (required): This must be "MiLight", and refers to the name of the accessory as exported from this file + *name (required): The name for this light/zone, as passed on to Homebridge and HomeKit + *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 + *zone (required): The zone to target with this accessory. "0" for all zones on the bridge, otherwise 1-4 for a specific zone + *type (required): One of either "rgbw", "rgb", or "white", depending on the type of bulb being controlled + *delay (optional): Delay between commands sent over UDP. Default 30ms + *repeat (optional): Number of times to repeat the UDP command for better reliability. Default 3 + +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: + *Probably convert this module to a platform that can configure an entire bridge at once, just passing a name for each zone + *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; @@ -18,15 +68,15 @@ function MiLight(log, config) { 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 + var light = new Milight({ + ip: this.ip_address, + port: this.port, + delayBetweenCommands: this.delay, + commandRepeat: this.repeat }); +} MiLight.prototype = { setPowerState: function(powerOn, callback) { @@ -42,12 +92,15 @@ MiLight.prototype = { 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 + // Ensure we're pausing for 100ms between these commands as per the spec + light.pause(100); light.sendCommands(commands[this.type].nightMode(this.zone)); + } else { this.log("Setting brightness to %s", level); @@ -58,6 +111,7 @@ MiLight.prototype = { 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) { From 2710412ca669055a042338bebd12c3adf1671893 Mon Sep 17 00:00:00 2001 From: Sam Edwards Date: Mon, 7 Sep 2015 16:45:27 -0700 Subject: [PATCH 2/2] [MiLight] Correctly reference light object, and also fix bug with brightness=0 setting the lamp to night mode --- accessories/MiLight.js | 46 +++++++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/accessories/MiLight.js b/accessories/MiLight.js index cfe339a..c5ce67c 100644 --- a/accessories/MiLight.js +++ b/accessories/MiLight.js @@ -69,60 +69,62 @@ function MiLight(log, config) { this.delay = config["delay"]; this.repeat = config["repeat"]; - var light = new Milight({ + this.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"); + this.light.sendCommands(commands[this.type].on(this.zone)); } else { - light.sendCommands(commands[this.type].off(this.zone)); this.log("Setting power state to off"); + this.light.sendCommands(commands[this.type].off(this.zone)); } callback(); }, setBrightness: function(level, callback) { - if (level <= 2 && (this.type == "rgbw" || this.type == "white")) { - + if (level == 0) { + // If brightness is set to 0, turn off the lamp + this.log("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("Setting night mode", level); - light.sendCommands(commands[this.type].off(this.zone)); + this.light.sendCommands(commands[this.type].off(this.zone)); // Ensure we're pausing for 100ms between these commands as per the spec - light.pause(100); - light.sendCommands(commands[this.type].nightMode(this.zone)); + this.light.pause(100); + this.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)); + this.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)); + 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 - light.sendCommands(commands.white.maxBright(this.zone)); + this.light.sendCommands(commands.white.maxBright(this.zone)); } else { - light.sendCommands(commands[this.type].brightUp()); + this.light.sendCommands(commands[this.type].brightUp()); } } else { - light.sendCommands(commands[this.type].brightDown()); + this.light.sendCommands(commands[this.type].brightDown()); } } } @@ -132,23 +134,25 @@ MiLight.prototype = { setHue: function(value, callback) { this.log("Setting hue to %s", value); + var hue = Array(value, 0, 0); + // Send on command to ensure we're addressing the right bulb - light.sendCommands(commands[this.type].on(this.zone)); + this.light.sendCommands(commands[this.type].on(this.zone)); if (this.type == "rgbw") { if (value == 0) { - light.sendCommands(commands.rgbw.whiteMode(this.zone)); + this.light.sendCommands(commands.rgbw.whiteMode(this.zone)); } else { - light.sendCommands(commands.rgbw.hue(commands.rgbw.hsvToMilightColor(Array(value, 0, 0)))); + this.light.sendCommands(commands.rgbw.hue(commands.rgbw.hsvToMilightColor(hue))); } } else if (this.type == "rgb") { - light.sendCommands(commands.rgb.hue(commands.rgbw.hsvToMilightColor(Array(value, 0, 0)))); + 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) { - light.sendCommands(commands.white.warmer()); + this.light.sendCommands(commands.white.warmer()); } else { - light.sendCommands(commands.white.cooler()); + this.light.sendCommands(commands.white.cooler()); } }