diff --git a/accessories/MiLight.js b/accessories/MiLight.js index 85b09d6..c5ce67c 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,57 +68,63 @@ function MiLight(log, config) { 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 + }); + } - -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"); + 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)); - // 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)); + 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("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()); } } } @@ -78,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()); } }