diff --git a/MiLight.html b/MiLight.html index b92983d..e27485e 100644 --- a/MiLight.html +++ b/MiLight.html @@ -1,120 +1,89 @@ - - - - - - + + \ No newline at end of file diff --git a/MiLight.js b/MiLight.js index 4461d0a..300aa79 100644 --- a/MiLight.js +++ b/MiLight.js @@ -1,60 +1,168 @@ -module.exports = function(RED) { - function MiLightPower(config) { - RED.nodes.createNode(this,config); - var node = this; - var MiLight = require('milight'); - var milight = new MiLight({ - host: config.ip, - broadcast: config.broadcast - }); - var zone = config.zone; - this.on('input', function(msg) { - if (msg.payload == "off") milight.zone(zone).off(); - if (msg.payload == "on") milight.zone(zone).on(); - }); - }; - RED.nodes.registerType("MiLightPower",MiLightPower); - - function MiLightRGB(config) { - RED.nodes.createNode(this,config); - var node = this; - var MiLight = require('milight'); - - var milight = new MiLight({ - host: config.ip, - broadcast: config.broadcast - }); - var zone = config.zone; - - - this.on('input', function(msg) { - if (typeof msg.payload == "string") { - milight.zone(zone).rgb(msg.payload); - } else { - milight.zone(zone).rgb(msg.payload.rgb || "#FFFFFF"); - milight.zone(zone).brightness(msg.payload.brightness) || 100; - } - }); - }; - RED.nodes.registerType("MiLightRGB",MiLightRGB); - - function MiLightWhite(config) { - RED.nodes.createNode(this,config); - var node = this; - var MiLight = require('milight'); - - var milight = new MiLight({ - host: config.ip, - broadcast: config.broadcast - }); - var zone = config.zone; - +module.exports = function (RED) { + "use strict"; + + var Milight = require('node-milight-promise'); + var packageFile = require('./package.json'); + var Color = require('tinycolor2'); + + function node(config) { + + RED.nodes.createNode(this, config); + var node = this; - this.on('input', function(msg) { - milight.zone(zone).white(msg.payload); - }); - }; - RED.nodes.registerType("MiLightWhite",MiLightWhite); + // backwards compatibility with previous versions + if (config.bridgetype == null || config.bridgetype === '') { + config.bridgetype = 'legacy' + } + var light = new Milight.MilightController({ + ip: config.ip, + delayBetweenCommands: (config.bridgetype !== 'v6') ? 200 : 100, + commandRepeat: 1, + type: config.bridgetype, + broadcastMode: config.broadcast + }), + zone = Number(config.zone), + bulb = config.bulbtype; + if (config.bridgetype === 'v6') { + var commands = Milight.commandsV6[bulb]; + } + else if (bulb === 'white') { + var commands = Milight.commands[bulb]; + } + else { + var commands = Milight.commands2[bulb]; + } + + this.on('input', function (msg) { + function argsHelper(vargs) { + var argsArray = [].slice.call(arguments); + if (config.bridgetype === 'v6' && bulb !== 'bridge') { + return [zone].concat(argsArray); + } + return argsArray; + } + function getSelectedObjectValues(sourceObject, keys) { + var values = []; + keys.forEach(function(key) { values.push(sourceObject[key]) }); + return values; + } + + light.ready().then(function () { + var command = msg.command ? msg.command : msg.topic; + if (commands == null) { + node.error("Selected combination of bridge type and bulb type is not supported"); + return; + } + if (bulb !== 'white') { + switch (msg.payload) { + case 'off': + light.sendCommands(commands.off(zone)); + break; + case 'on': + light.sendCommands(commands.on(zone)); + break; + case 'disco': + light.sendCommands(commands.on(zone)); + for (var x = 0; x < 256; x += 5) { + light.sendCommands( + commands.hue.apply(commands, argsHelper(x))); + light.pause(100); + } + break; + case 'mode': + light.sendCommands(commands.on(zone), commands.effectModeNext(zone)); + break; + case 'speed_up': + light.sendCommands(commands.on(zone), commands.effectSpeedUp(zone)); + break; + case 'speed_down': + light.sendCommands(commands.on(zone), commands.effectSpeedDown(zone)); + break; + case 'white': + light.sendCommands(commands.on(zone), commands.whiteMode(zone)); + break; + case 'night': + // nightMode command needs to be sent twice with some bulb types + light.sendCommands(commands.nightMode(zone), commands.nightMode(zone)); + break; + default: + var value = Number(msg.payload); + if (command === 'rgb') { + var color = new Color(msg.payload); + if (color.isValid()) { + var args = argsHelper.apply( + node, + getSelectedObjectValues(color.toRgb(), ['r', 'g', 'b'])); + light.sendCommands(commands.on(zone), + commands.rgb.apply(commands, args)); + } + else { + throw(new Error("Invalid color value: " + msg.payload)) + } + } + else if (!isNaN(value)) { + if (command === 'brightness') + light.sendCommands( + commands.on(zone), + commands.brightness.apply(commands, argsHelper(value))); + else if (command === 'color') + light.sendCommands( + commands.on(zone), + commands.hue.apply(commands, argsHelper(value, true))); + else if (command === 'saturation' && bulb === 'fullColor') + light.sendCommands( + commands.on(zone), + commands.saturation(zone, value, true)); + } + break; + } + } else { + switch (msg.payload) { + case 'off': + light.sendCommands(commands.off(zone)); + break; + case 'on': + light.sendCommands(commands.on(zone)); + break; + case 'bright_up': + light.sendCommands(commands.brightUp(zone)); + break; + case 'bright_down': + light.sendCommands(commands.brightDown(zone)); + break; + case 'cooler': + light.sendCommands(commands.cooler(zone)); + break; + case 'warmer': + light.sendCommands(commands.warmer(zone)); + break; + case 'bright_max': + light.sendCommands(commands.maxBright(zone)); + break; + case 'night': + light.sendCommands(commands.nightMode(zone)); + break; + } + } + }).catch(function (error) { + node.error('Milight error: ' + error); + }); + }); + + this.on('close', function (done) { + light.close() + .catch(function (error) { + // just log the error as a normal log message + // as it is safe to ignore the error at this point + node.log(error) + }) + .finally(function () { + done() + }); + }); + } -} + RED.nodes.registerType("MiLight", node); + RED.log.info(packageFile.name + '@' + packageFile.version + ' started'); +}; \ No newline at end of file diff --git a/README.md b/README.md index 81fa9be..51ba495 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,42 @@ # node-red-contrib-milight -A Node Red wrapper for https://github.com/oeuillot/node-milight.git -Currently presents 3 Nodes -* Milight RGB (set RGB colors) -* Milight White (set white mode brightness) -* Milight Power (on/off control) -## To Do -Create a single Node which encompasses all functionality +A Node Red nodes to control all bulb types Milight LED bulbs and OEM equivalents such as Rocket LED, Limitless LED Applamp, Easybulb, s'luce, iLight, iBulb, and Kreuzer. -## Known Issues +## Install -* MiLightRGB has brightness controls, however causes an odd flicker when changing +```npm i node-red-contrib-milight``` +## Example Flow + [{"id":"b5ab74d6.08edc8","type":"MiLight","z":"44cc4fb7.965b3","name":"White Bulb / Legacy Bridge","bridgetype":"legacy","bulbtype":"white","zone":1,"ip":"255.255.255.255","broadcast":true,"x":1035.765625,"y":297,"wires":[]},{"id":"51aa22c0.74d05c","type":"MiLight","z":"44cc4fb7.965b3","name":"Color Bulb / Legacy Bridge","bridgetype":"legacy","bulbtype":"rgbw","zone":1,"ip":"255.255.255.255","broadcast":true,"x":1036.7656211853027,"y":619.0000314712524,"wires":[]},{"id":"26ce02ea.cd2e6e","type":"inject","z":"44cc4fb7.965b3","name":"Off","topic":"","payload":"off","payloadType":"str","repeat":"","crontab":"","once":false,"x":534.7656402587891,"y":553.0000228881836,"wires":[["51aa22c0.74d05c"]]},{"id":"17bef0b.fe9ce0f","type":"inject","z":"44cc4fb7.965b3","name":"On","topic":"","payload":"on","payloadType":"str","repeat":"","crontab":"","once":false,"x":534.7656555175781,"y":607.0000152587891,"wires":[["51aa22c0.74d05c"]]},{"id":"44e9d122.6a83b","type":"inject","z":"44cc4fb7.965b3","name":"white","topic":"","payload":"white","payloadType":"str","repeat":"","crontab":"","once":false,"x":536.765625,"y":662.9999933242798,"wires":[["51aa22c0.74d05c"]]},{"id":"b477791.b31d088","type":"inject","z":"44cc4fb7.965b3","name":"Brightness 10%","topic":"","payload":"10","payloadType":"num","repeat":"","crontab":"","once":false,"x":568.765625,"y":732,"wires":[["e01cd3d0.06b27"]]},{"id":"e01cd3d0.06b27","type":"function","z":"44cc4fb7.965b3","name":"","func":"msg.command = 'brightness';\nreturn msg;","outputs":1,"noerr":0,"x":741.7656478881836,"y":757.0000553131104,"wires":[["51aa22c0.74d05c"]]},{"id":"865a0a9b.be0848","type":"inject","z":"44cc4fb7.965b3","name":"Color","topic":"","payload":"20","payloadType":"num","repeat":"","crontab":"","once":false,"x":536.7656307220459,"y":843.0000419616699,"wires":[["f578b6a1.3d7948"]]},{"id":"f578b6a1.3d7948","type":"function","z":"44cc4fb7.965b3","name":"","func":"msg.command = 'color';\nreturn msg;","outputs":1,"noerr":0,"x":738.7657089233398,"y":845.000057220459,"wires":[["51aa22c0.74d05c"]]},{"id":"2cc2747e.452f4c","type":"inject","z":"44cc4fb7.965b3","name":"On","topic":"","payload":"on","payloadType":"str","repeat":"","crontab":"","once":false,"x":540.7656326293945,"y":168.00000381469727,"wires":[["b5ab74d6.08edc8"]]},{"id":"c8b4fa4d.e53078","type":"inject","z":"44cc4fb7.965b3","name":"Off","topic":"","payload":"off","payloadType":"str","repeat":"","crontab":"","once":false,"x":540.2656383514404,"y":215.00000190734863,"wires":[["b5ab74d6.08edc8"]]},{"id":"9fb07ff8.51bba","type":"inject","z":"44cc4fb7.965b3","name":"Warmer","topic":"","payload":"warmer","payloadType":"str","repeat":"","crontab":"","once":false,"x":538.7656211853027,"y":258.00000381469727,"wires":[["b5ab74d6.08edc8"]]},{"id":"38e7a6b0.c484ca","type":"inject","z":"44cc4fb7.965b3","name":"Cooler","topic":"","payload":"cooler","payloadType":"str","repeat":"","crontab":"","once":false,"x":542.2656402587891,"y":303.00001525878906,"wires":[["b5ab74d6.08edc8"]]},{"id":"287ae328.5c945c","type":"inject","z":"44cc4fb7.965b3","name":"Bright Up","topic":"","payload":"bright_up","payloadType":"str","repeat":"","crontab":"","once":false,"x":548.7656555175781,"y":346.00002670288086,"wires":[["b5ab74d6.08edc8"]]},{"id":"268eff9f.0e175","type":"inject","z":"44cc4fb7.965b3","name":"Bright Down","topic":"","payload":"bright_down","payloadType":"str","repeat":"","crontab":"","once":false,"x":557.7656326293945,"y":392.99999809265137,"wires":[["b5ab74d6.08edc8"]]},{"id":"7f19fef2.a20dc","type":"inject","z":"44cc4fb7.965b3","name":"Bright Max","topic":"","payload":"bright_max","payloadType":"str","repeat":"","crontab":"","once":false,"x":547.7656383514404,"y":438.0000286102295,"wires":[["b5ab74d6.08edc8"]]},{"id":"afe72b66.8856e8","type":"inject","z":"44cc4fb7.965b3","name":"Night","topic":"","payload":"night","payloadType":"str","repeat":"","crontab":"","once":false,"x":539.7656536102295,"y":485.00002098083496,"wires":[["b5ab74d6.08edc8"]]},{"id":"1c0123a9.9f827c","type":"inject","z":"44cc4fb7.965b3","name":"Brightness 100%","topic":"","payload":"100","payloadType":"num","repeat":"","crontab":"","once":false,"x":568.7656555175781,"y":778.750057220459,"wires":[["e01cd3d0.06b27"]]}] + +The following bulb types are supported: + - WW/CW, aka. "white" + - RGB WW, aka. "color" + - RGB WW/CW, aka. "full color" (iBox1/iBox2 bridges) + - RGB CW bridge light (iBox1 bridge) + +To control the bulb pass the command to `msg.payload` as follows: + + - 'on' - Turns the bulb on (all bulb types) + - 'off' - Turns the bulb off (all bulb types) + - 'night' - Turn the night mode (all bulb types) + - 'white' - Sets a color bulb to white (color bulb types only) + - 'disco' - Cycles a bulb through all the colors (color bulb types, only) + - 'mode' - Cycles through the effect modes (color bulb types only) + - 'speed_up' - Increase the speed of effect mode (color bulb types, only) + - 'speed_down' - Decrease the speed of effect mode (color bulb types, only) + - 'bright_up' - Increase the brightness of the bulb (white bulb, only) + - 'bright_down' - Decrease the brightness of the bulb (white bulb, only) + - 'cooler' - Make the bulb cooler (white bulb, only) + - 'warmer' - Make the bulb warmer (white bulb, only) + - 'bright_max' - Make the bulb brightness maximum (white bulb, only) + - *Number* - If a number is provided the brightness, color, or saturation value of the color bulb can be controlled + by assigning a command verb to `msg.command` or `msg.topic` as follows + - 'brightness' - Set brightness, `msg.payload` must contain a number in 0 - 100 + - 'color' - Set color, `msg.payload` must contain a number in 0 - 255 + - 'saturation' - Set color saturation (full color bulb type, only), `msg.payload` must contain a number in 0 - 100 + - *Color String* - If a color string, e.g. "blue" or "rgb(255, 128, 128)" is provided and the command verb "rgb" is + assigned to `msg.command` or `msg.topic` the RGB color can be set. Note, however, for "color" and "bridge" type + bulbs the Milight hue will be set, only. For "full color" bulbs the saturation and brightness will be also set. diff --git a/package.json b/package.json index eb50852..c2bacd9 100644 --- a/package.json +++ b/package.json @@ -1,26 +1,47 @@ { - "name" : "node-red-contrib-milight", - "version" : "0.0.2", - "description" : "A Node-RED node to control the MiLight family (limitless led etc) of light globes. A wrapper for https://github.com/oeuillot/node-milight.git", - "license": "Apache-2.0", - "keywords": [ "node-red", "milight", "limitless led", "scheduler" ], - "node-red" : { - "nodes" : { - "MiLightRGB": "MiLight.js", - "MiLightWhite": "MiLight.js", - "MiLightPower": "MiLight.js" - } - }, - "dependencies": { - "milight" : "1.0.0" + "name": "node-red-contrib-milight", + "version": "1.0.2", + "description": "A Node-RED node to control the MiLight family (limitless led, easybulb) of light.", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "predev": "npm i -g", + "dev": "node-red -v" }, "repository": { "type": "git", - "url": "https://github.com/Daniel-t/node-red-contrib-milight.git" + "url": "https://github.com/stephenkeep/node-red-contrib-milight.git" + }, + "contributors": [ + { + "name": "Stephen Keep", + "email": "stephenkeep@gmail.com" + }, + { + "name": "Bilal Al-Saeedi", + "email": "bilal_alsaidi@yahoo.com" + }, + { + "name": "Marcus Wittig", + "email": "wittigmarcus@gmail.com" + } + ], + "author": { + "name": "Stephen Keep" }, - "author": { - "name": "Daniel Thomas", - "email": "daniel@networklighthouse.com", - "url": "https://github.com/Daniel-t" + "license": "Apache-2.0", + "keywords": [ + "node-red", + "milight", + "limitless led", + "easybulb" + ], + "node-red": { + "nodes": { + "MiLight": "MiLight.js" } + }, + "dependencies": { + "node-milight-promise": "^0.2.31", + "tinycolor2": "^1.4.1" + } }