UNPKG

smart-nodes

Version:

Controls light, shutters and more. Includes common used logic and statistic nodes to control your home.

289 lines (235 loc) 9.29 kB
module.exports = function (RED) { "use strict"; function ShutterControlNode(config) { const node = this; RED.nodes.createNode(node, config); // ################### // # Class constants # // ################### // ####################### // # Global help objects # // ####################### const smart_context = require("../persistence.js")(RED); const helper = require("../smart_helper.js"); // ############################ // # Used from text-exec node # // ############################ if (typeof config.exec_text_names == "string") node.exec_text_names = config.exec_text_names.split(",").map(n => n.trim().toLowerCase()); else node.exec_text_names = []; // ##################### // # persistent values # // ##################### var node_settings = helper.cloneObject({ last_position: 0, // 0 = opened, 100 = closed last_direction_up: true, // remember last direction for toggle action }, smart_context.get(node.id)); // ################## // # Dynamic config # // ################## let short_time_on_ms = parseInt(config.short_time_on_ms || 200, 10); // ################## // # Runtime values # // ################## // Here the setTimeout return value is stored to stop the shutter. // That means if it is null, the shutter is stopped. let timeout = null; // This is set to true if a command to start the shutter is recognized. let is_running = false; // ######################### // # Central node handling # // ######################### var event = "node:" + config.id; var handler = function (msg) { node.receive(msg); } RED.events.on(event, handler); // ############### // # Node events # // ############### node.on("input", function (msg) { handleTopic(msg); setStatus(); smart_context.set(node.id, node_settings); }); node.on("close", function () { startAction(ACTION_STOP); RED.events.off(event, handler); }); // ##################### // # Private functions # // ##################### // This is the main function which handles all topics that was received. let handleTopic = msg => { helper.log(node, "handle topic:", msg); var resultUpDown = null; var resultStop = null; var resultPosition = null; let real_topic = helper.getRealTopic(msg.topic, "toggle", ["status", "status_position", "up_down", "up", "up_stop", "down", "down_stop", "stop", "toggle", "position", "short_up_down"]); // skip if button is released if (msg.payload === false && ["up", "up_stop", "down", "down_stop", "stop", "toggle"].includes(real_topic)) return; // Correct next topic to avoid handling up_stop, down_stop or toggle separately. if (real_topic == "short_up_down") { real_topic = msg.payload ? "down" : "up"; if (msg.time_on == null) msg.time_on = short_time_on_ms; } if ((timeout != null || is_running) && (real_topic == "up_stop" || real_topic == "down_stop" || real_topic == "toggle")) { real_topic = "stop"; } else if (timeout == null && !is_running) { // shutter is not running, set next command depending on topic if (real_topic == "up_stop") real_topic = "up"; else if (real_topic == "down_stop") real_topic = "down"; else if (node_settings.last_direction_up && real_topic == "toggle") real_topic = "down"; else if (!node_settings.last_direction_up && real_topic == "toggle") real_topic = "up"; } helper.log(node, "handle real topic: " + real_topic); switch (real_topic) { case "debug": helper.nodeDebug(node, { node_settings, short_time_on_ms, }); break; case "status": case "status_position": node_settings.last_direction_up = node_settings.last_position > msg.payload; node_settings.last_position = msg.payload; if (is_running && (msg.payload == 0 || msg.payload == 100)) is_running = false; return; case "up_down": // This is only used to track starting of the shutter node_settings.last_direction_up = !msg.payload; is_running = true; return; case "up": node_settings.last_direction_up = true; is_running = true; resultUpDown = false; startAutoOffIfNeeded(msg); break; case "stop": is_running = false; resultStop = true; stopAutoOff(); break; case "down": node_settings.last_direction_up = false; is_running = true; resultUpDown = true; startAutoOffIfNeeded(msg); break; case "position": let value = parseFloat(msg.payload); if (value < 0) value = 0; if (value > 100) value = 100; // is_running = true; // Not guaranteed that the shutter starts running. resultPosition = value; break; } if (resultUpDown != null) { node.send([{ payload: resultUpDown }, null, null]); notifyCentral(true); } else if (resultStop != null) { node.send([null, { payload: resultStop }, null]); notifyCentral(false); } else if (resultPosition != null) { node.send([null, null, { payload: resultPosition }]); } }; /** * This function sets a timeout to stop the shutter after the defined time is over. * @param {*} msg The original message object */ let startAutoOffIfNeeded = msg => { if (msg.time_on == null || msg.time_on == 0) return; // calculate needed time let timeMs = helper.getTimeInMsFromString(msg.time_on); if (isNaN(timeMs) || timeMs <= 0) { helper.warn(this, "Invalid msg.time_on value was sent.", msg); return; } // Stop if any timeout is set stopAutoOff(); timeout = setTimeout(() => { is_running = false; timeout = null; node.send([null, { payload: true }, null]); notifyCentral(false); smart_context.set(node.id, node_settings); setStatus(); }, timeMs); }; /** * Stops the current running timeout */ let stopAutoOff = () => { if (timeout != null) { clearTimeout(timeout); timeout = null; } }; /** * Set the current node status */ let setStatus = () => { let fill = "green"; let shape = (timeout != null || is_running) ? "ring" : "dot"; // collect all texts and join later with a comma let texts = []; if (timeout == null && !is_running) texts.push("Stopped"); else if (node_settings.last_direction_up) texts.push("Up"); else texts.push("Down"); texts.push("Position: " + node_settings.last_position?.toFixed(0) + "%"); node.status({ fill, shape, text: helper.getCurrentTimeForStatus() + ": " + texts.join(", ") }); } /** * Notify all connected central nodes * @param {boolean} state The state if the shutter is running */ let notifyCentral = state => { if (!config.links) return; config.links.forEach(link => { helper.log(node, link, { source: node.id, state: state }); RED.events.emit("node:" + link, { source: node.id, state: state }); }); }; } RED.nodes.registerType("smart_shutter-control", ShutterControlNode); };