node-red-contrib-huemagic
Version:
Philips Hue node to control bridges, lights, groups, scenes, rules, taps, switches, buttons, motion sensors, temperature sensors and Lux sensors using Node-RED.
160 lines (140 loc) • 4.05 kB
JavaScript
module.exports = function(RED)
{
"use strict";
function HueScene(config)
{
RED.nodes.createNode(this, config);
const scope = this;
const bridge = RED.nodes.getNode(config.bridge);
const async = require('async');
// GET TARGET WIRED GROUPS
this.targetGroups = {};
for (var w = scope.wires[0].length - 1; w >= 0; w--)
{
let oneWiredNode = RED.nodes.getNode(scope.wires[0][w]);
if(oneWiredNode && oneWiredNode.type == "hue-group" && oneWiredNode.exportedConfig && oneWiredNode.exportedConfig.groupid && oneWiredNode.exportedConfig.groupid.length > 1)
{
this.targetGroups[oneWiredNode.exportedConfig.groupid] = true;
}
}
//
// CHECK CONFIG
if(bridge == null)
{
this.status({fill: "red", shape: "ring", text: "hue-scene.node.not-configured"});
return false;
}
//
// ENABLE SCENE
this.on('input', function(msg, send, done)
{
// REDEFINE SEND AND DONE IF NOT AVAILABLE
done = done || function() { scope.done.apply(scope,arguments); }
// TARGET GROUP & SCENE
const groupIDS = (typeof msg.payload != 'undefined' && typeof msg.payload.group != 'undefined') ? msg.payload.group : [];
const sceneDef = (typeof msg.payload != 'undefined' && typeof msg.payload.scene != 'undefined') ? msg.payload.scene : config.sceneid;
// PREPARE TARGET GROUPS
let copyOfTargetGroups = Object.keys(scope.targetGroups);
if(typeof groupIDS == 'string') { copyOfTargetGroups.push(groupIDS); }
else if(typeof groupIDS == 'object') { copyOfTargetGroups.concat(groupIDS); }
else { return false; }
// CREATE PATCH
let patchObject = {};
// NO SCENE?
if(!sceneDef)
{
// ERROR
this.status({fill: "red", shape: "ring", text: "hue-scene.node.no-id"});
scope.error("Scene ID not found");
return false;
}
// RECALL ON GROUP? -> USE API v1
if(copyOfTargetGroups.length > 0)
{
let targetSceneID = bridge.resources[sceneDef].id_v1.replace("/scenes/", "");
// SEND STATUS
scope.status({fill: "blue", shape: "dot", text: "hue-scene.node.recalled-on-group"})
// FIND GROUP AND RECALL SCENE
for (var g = copyOfTargetGroups.length - 1; g >= 0; g--)
{
let targetGroup = bridge.get("group", copyOfTargetGroups[g]);
if(targetGroup)
{
// PATCH!
async.retry({
times: 3,
errorFilter: function(err) {
return (err.status == 503);
},
interval: function(retryCount) { return retryCount*2000; }
},
function(callback, results)
{
bridge.patch("group", targetGroup.info.idV1 + "/action", { "scene": targetSceneID }, 1)
.then(function() { callback(null, true); })
.catch(function(errors) {
callback(errors, null);
});
},
function(errors, success)
{
if(errors)
{
scope.error(errors);
}
else if(done)
{
done();
}
});
}
}
// RESET STATUS AFTER 2 SECONDS
setTimeout(function() {
scope.status({});
}, 2000);
}
else
{
// RECALL SCENE
patchObject["recall"] = { action: "active" };
// CHANGE NODE UI STATE
scope.status({fill: "grey", shape: "ring", text: "hue-scene.node.command"});
// PATCH!
async.retry({
times: 3,
errorFilter: function(err) {
return (err.status == 503);
},
interval: function(retryCount) { return retryCount*2000; }
},
function(callback, results)
{
bridge.patch("scene", sceneDef, patchObject)
.then(function()
{
scope.status({fill: "blue", shape: "dot", text: "hue-scene.node.recalled"});
// RESET STATUS AFTER 1 SECOND
setTimeout(function() {
scope.status({});
}, 1000);
callback(null, true);
})
.catch(function(errors) { callback(errors, null); });
},
function(errors, success)
{
if(errors)
{
scope.error(errors);
}
else if(done)
{
done();
}
});
}
});
}
RED.nodes.registerType("hue-scene", HueScene);
}