node-red-contrib-milight-3
Version:
A Node-RED node to control the MiLight family (limitless led, easybulb) of light.
218 lines (200 loc) • 9.3 kB
JavaScript
module.exports = function (RED) {
"use strict";
var Milight = require('node-milight-promise');
var packageFile = require('./package.json');
var Color = require('tinycolor2');
var url = require('url');
const getIpPort = (node, config, msg) => {
const value = RED.util.evaluateNodeProperty(config.ip, config.ipType, node, msg);
var myUrl = url.parse("http://" + value);
var port;
if (myUrl.port != null) {
port = myUrl.port;
}
return [myUrl.hostname, port];
};
let cachedLightIpPort = [];
let cachedLight;
const getLight = (node, config, msg) => {
const ipPort = getIpPort(node, config, msg);
if (cachedLight && cachedLightIpPort[0] === ipPort[0] && cachedLightIpPort[1] === ipPort[1]) {
return cachedLight;
}
if (cachedLight) {
cachedLight.close();
}
RED.log.info("Milight:" + ipPort[0] + ":" + ipPort[1]);
cachedLightIpPort = ipPort;
cachedLight = new Milight.MilightController({
ip: ipPort[0],
port: ipPort[1],
delayBetweenCommands: (config.bridgetype !== 'v6') ? 200 : 100,
commandRepeat: 1,
type: config.bridgetype,
broadcastMode: config.broadcast
});
return cachedLight;
};
function node(config) {
RED.nodes.createNode(this, config);
var node = this;
if (cachedLight) {
cachedLight.close();
}
// backwards compatibility with previous versions
if (config.bridgetype == null || config.bridgetype === '') {
config.bridgetype = 'legacy'
}
const ipPort = getIpPort(node, config, {});
RED.log.info("Milight:" + ipPort[0] + ":" + ipPort[1]);
var 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, send, doneCb) {
const done = () => doneCb ? doneCb() : () => {};
const error = (err) => doneCb ? doneCb(err) : node.error(err);
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;
}
const light = getLight(node, config, msg);
light.ready().then(function () {
var command = msg.command ? msg.command : msg.topic;
if (commands == null) {
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));
else if (command === 'temperature' && bulb === 'fullColor')
light.sendCommands(
commands.on(zone),
commands.whiteTemperature(zone, value));
else if (config.bridgetype === 'v6' && command === 'mode')
light.sendCommands(commands.on(zone), commands.effectMode(zone, value));
}
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;
}
}
done();
}).catch(function (err) {
error(err);
});
});
this.on('close', function (done) {
const light = getLight(node, config);
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');
};