node-red-contrib-rainbird
Version:
Node-RED nodes for controlling RainBird sprinkler controller (like: ESP-ME3) via local LNK2 wifi module connection. This wraps the RainBird SIP protocol using AES encryption. It uses a slightly modified version of node-rainbird originally developed by @bb
91 lines (72 loc) • 2.27 kB
JavaScript
module.exports = function (RED) {
const MAX_SPRINKLERS = 6; // max number of zones in homekit mode. tested up to 8
function RainbirdNode(config) {
RED.nodes.createNode(this, config);
const node = this;
node.log("Starting rainbird LNK2 rainbird-active node.");
this.server = RED.nodes.getNode(config.server);
this.mode = config.mode || "raw";
this.name = config.name;
if (!this.server || !this.server.rainIp || !this.server.rainKey) {
this.error("Server configuration is missing or invalid.");
return;
}
// auto-refresh when zone started/closed
this.server.registerOnZoneStart((zoneId, duration) => {
node.debug(`Zone ${zoneId} started. Forcing update.`);
setTimeout(() => node.emit("input", {}), 600);
setTimeout(() => node.emit("input", {}), 2000);
});
const rainbird = this.server.getInstance();
node.on("input", function (msg) {
node.status({ fill: "yellow", shape: "dot", text: "Querying..." });
rainbird
.getActiveZones()
.then((result) => {
node.status({ fill: "green", shape: "dot", text: "OK" });
if (node.mode === "homekit") {
const fullState = processHomekitMode(result);
node.log("Sending full state update (homekit mode).");
fullState.forEach((msgItem) => node.send(msgItem));
setTimeout(() => node.status({}), 5000);
return;
}
// RAW mode
const payload = { ...result };
delete payload._type;
msg.payload = payload;
node.send(msg);
setTimeout(() => node.status({}), 5000);
})
.catch((err) => {
node.error("LNK2 Rainbird call error: " + err.message);
node.status({ fill: "red", shape: "ring", text: "Error" });
});
});
}
RED.nodes.registerType("rainbird-active", RainbirdNode);
function processHomekitMode(result) {
const maxSprinklers = MAX_SPRINKLERS;
const sprinkler = parseInt(result.activeZones?.[0]) || 0;
const out = [];
for (let i = 0; i < maxSprinklers; i++) {
out.push({
payload: {
Active: 0,
InUse: 0,
},
topic: (i + 1).toString(),
});
}
if (sprinkler > 0 && sprinkler <= maxSprinklers) {
out[sprinkler - 1] = {
payload: {
Active: 1,
InUse: 1,
},
topic: sprinkler.toString(),
};
}
return out;
}
};