node-red-contrib-zigbee2mqtt-devices
Version:
Nodes to interact with zigbee2mqtt for Node-RED
341 lines (340 loc) • 13.7 kB
JavaScript
;
/* eslint-disable no-unused-vars */
/* eslint-disable indent */
module.exports = function (RED) {
const utils = require("./lib/utils.js");
const bavaria = utils.bavaria();
function genericLamp(config) {
RED.nodes.createNode(this, config);
var deviceConfig = RED.nodes.getNode(config.device);
var bridgeNode = RED.nodes.getNode(deviceConfig.bridge);
var node = this;
var topic = deviceConfig.deviceName;
var nodeContext = this.context().global;
function getContextName() {
return ("z2mdevice_" + node.id).replace(".", "_");
}
function messageToStatus(msg) {
var statusColor = "grey";
var text = "Lm: " + msg.brightness;
switch (msg.state) {
case "ON":
case "1":
case 1:
case "true":
case true:
statusColor = "green";
break;
case "OFF":
case "0":
case 0:
case "false":
case false:
statusColor = "gray";
break;
}
if (msg.color_temp !== undefined) {
text += " T: " + msg.color_temp;
}
if (msg.color !== undefined) {
var rgb = msg.color;
if (rgb.x !== undefined) {
rgb = bavaria.converter.xyToRgb(msg.color.x, msg.color.y);
}
text += " RGB: (" + rgb.r + ", " + rgb.g + ", " + rgb.b + ")";
}
var status = { fill: statusColor, shape: "dot", text: text };
nodeContext.set(getContextName(), {
status: status
});
node.status(status);
}
if (deviceConfig.genericMqttDevice !== true) {
bridgeNode.subscribeDevice(node.id, deviceConfig.deviceName, function (msg) {
messageToStatus(msg);
});
}
const regId1 = bavaria.observer.register(bridgeNode.id + "_connected", function (_msg) {
if (deviceConfig.genericMqttDevice !== true) {
bridgeNode.refreshDevice(deviceConfig.deviceName);
}
});
if (bridgeNode.isConnected()) {
if (deviceConfig.genericMqttDevice !== true) {
bridgeNode.refreshDevice(deviceConfig.deviceName);
}
}
const regId2 = bavaria.observer.register(topic + "_routeError", function (_msg) {
var status = { fill: "red", shape: "dot", text: "route error" };
nodeContext.set(getContextName(), {
status: status
});
node.status(status);
});
var status = nodeContext.get(getContextName());
if (status && status.status) {
node.status(status.status);
}
else if (deviceConfig.genericMqttDevice !== true) {
node.status({ fill: "gray", shape: "dot", text: "pending" });
}
node.on("close", function () {
bavaria.observer.unregister(regId1);
bavaria.observer.unregister(regId2);
nodeContext.set(getContextName(), undefined);
bridgeNode.unsubscribe(node.id);
});
node.on("input", function (msg) {
if (msg.payload === undefined || typeof msg.payload != "object") {
msg.payload = {};
}
if (msg.payload.devices === undefined) {
msg.payload.devices = [];
}
var device = {
topic: deviceConfig.genericMqttDevice ? deviceConfig.commandTopic : deviceConfig.deviceName,
state: config.state,
delay: config.delay,
target: deviceConfig.genericMqttDevice ? "mqtt" : "z2m"
};
if (deviceConfig.brightnessSupport) {
device.brightness = config.brightness;
device.transition = config.transition;
}
if (deviceConfig.temperatureSupport) {
device.temperature = config.temperature;
}
if (deviceConfig.colorSupport) {
device.color = {
r: config.red,
g: config.green,
b: config.blue
};
}
msg.payload.devices.push(device);
node.send(msg);
});
}
RED.nodes.registerType("generic-lamp", genericLamp);
function sendMessages(config) {
RED.nodes.createNode(this, config);
var node = this;
var bridgeNode = RED.nodes.getNode(config.bridge);
if (!bridgeNode) {
node.status({ fill: "red", text: "no bridge configured" });
return;
}
node.status({ fill: "blue", text: "not connected" });
const regId = bavaria.observer.register(bridgeNode.id + "_connected", function (_msg) {
node.status({ fill: "green", text: "connected" });
});
if (bridgeNode.isConnected()) {
node.status({ fill: "green", text: "connected" });
}
node.on("input", function (msg) {
var messages = [];
msg.payload.devices.forEach(element => {
if (msg.payload.override !== undefined) {
if (msg.payload.override.action) {
element.brightness = undefined;
element.temperature = undefined;
element.color = undefined;
element.delay = undefined;
element.transition = undefined;
element.state = undefined;
element[msg.payload.override.action.name] = msg.payload.override.action.value;
}
else {
const properties = ["state", "brightness", "temperature", "color"];
properties.forEach((name) => {
if (utils.propertyExists(msg.payload.override, name)
&& utils.propertyExists(element, name)) {
element[name] = msg.payload.override[name];
}
});
}
}
messages.push(element);
});
var i = 0;
enqueue();
function sendNextMessage() {
try {
var topic = messages[i].topic;
if (messages[i].target === "z2m" || messages[i].target === undefined) {
topic = bridgeNode.baseTopic + "/" + messages[i].topic + "/set";
}
messages[i].target = undefined;
messages[i].delay = undefined;
var message = {
payload: messages[i],
topic: topic,
};
message.payload.topic = undefined;
if (message.payload.temperature) {
message.payload.color_temp = message.payload.temperature;
message.payload.temperature = undefined;
}
if (message.payload.transition === 0 || message.payload.transition === "0") {
message.payload.transition = undefined;
}
try {
if (message.payload.payloadGenerator && typeof message.payload.payloadGenerator === "function") {
message.payload = message.payload.payloadGenerator(message.payload);
}
else {
message.payload = JSON.stringify(message.payload);
}
if (message.brightness === 0) {
message.state = "OFF";
}
bridgeNode.publish(message.topic, message.payload);
}
catch (err) {
node.error(err);
}
if (++i < messages.length) {
enqueue();
}
}
catch (err) {
node.error(err);
}
}
function enqueue() {
var delay = messages[i].delay;
if (delay > 0) {
setTimeout(sendNextMessage, delay);
}
else {
sendNextMessage();
}
}
});
node.on("close", () => {
bavaria.observer.unregister(regId);
});
}
RED.nodes.registerType("send-messages", sendMessages);
function buttonSwitch(config) {
RED.nodes.createNode(this, config);
var isHolding = false;
var currentCount = 0;
var node = this;
var inputs = {};
const types = ["Pressed", "Hold", "Released", "Double"];
var currentIndex = 0;
types.forEach(type => {
if (config["enable" + type]) {
inputs[type.toLowerCase()] = utils.createButtonOutput(currentIndex, "", "");
currentIndex++;
}
});
function getPayload(data, type) {
try {
switch (type) {
case "num":
return Number.parseFloat(data);
case "bool":
return data == true;
case "json":
return JSON.parse(data);
}
}
catch (err) {
node.error(err);
}
return data;
}
node.on("input", function (msg) {
var actionName = msg.payload.button_type;
if (actionName === undefined && msg.action !== undefined) {
actionName = msg.action.description;
}
if (actionName === "released") {
isHolding = false;
}
if (config.dynamicOutputLabels.every(e => e.toLowerCase() != actionName)) {
// output not enabled
return;
}
var index = inputs[actionName].index;
actionName = actionName.charAt(0).toUpperCase() + actionName.slice(1);
if (config["customPayload" + actionName] === true) {
msg = { payload: getPayload(config["payload" + actionName], config["type" + actionName]) };
}
if (actionName === "Hold" && config.repeatHold) {
currentCount = 0;
isHolding = true;
repeateMessage();
}
else {
utils.sendAt(node, index, msg);
}
function repeateMessage() {
if (isHolding && currentCount < config.repeatHoldMax) {
currentCount++;
utils.sendAt(node, index, msg);
setTimeout(repeateMessage, config.repeatHoldDelay);
}
else {
isHolding = false;
}
}
});
}
RED.nodes.registerType("button-switch", buttonSwitch);
function deviceStatus(config) {
RED.nodes.createNode(this, config);
var node = this;
var bridgeNode = RED.nodes.getNode(config.bridge);
var deviceNode = RED.nodes.getNode(config.device);
if (config.genericMqttDevice === true) {
bridgeNode.subscribe(node.id, deviceNode.statusTopic, subscriptionCallback);
}
else {
bridgeNode.subscribeDevice(node.id, config.deviceName, subscriptionCallback);
}
function subscriptionCallback(msg) {
node.send({
device: config.genericMqttDevice === true ? deviceNode.statusTopic : config.deviceName,
deviceName: config.genericMqttDevice === true ? deviceNode.name : undefined,
payload: msg,
});
}
}
RED.nodes.registerType("device-status", deviceStatus);
function getLampState(config) {
RED.nodes.createNode(this, config);
var node = this;
var deviceNode = RED.nodes.getNode(config.device);
var bridgeNode = RED.nodes.getNode(deviceNode.bridge);
var enableOutput = false;
if (deviceNode.genericMqttDevice === true) {
bridgeNode.subscribe(node.id, deviceNode.statusTopic, subscriptionCallback);
}
else {
bridgeNode.subscribeDevice(node.id, deviceNode.deviceName, subscriptionCallback);
}
function subscriptionCallback(msg) {
if (enableOutput === true) {
enableOutput = false;
node.send({
device: deviceNode.genericMqttDevice === true ? deviceNode.statusTopic : deviceNode.deviceName,
deviceName: deviceNode.name,
payload: msg,
});
}
}
node.on("input", function (msg) {
enableOutput = true;
if (deviceNode.genericMqttDevice === true) {
bridgeNode.publish(deviceNode.refreshTopic, "{}");
}
else {
bridgeNode.refreshDevice(deviceNode.deviceName, true);
}
});
}
RED.nodes.registerType("get-lamp-state", getLampState);
};