UNPKG

smart-nodes

Version:

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

293 lines (239 loc) 8.87 kB
module.exports = function (RED) { "use strict"; function SchedulerNode(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"); // ##################### // # persistent values # // ##################### var node_settings = { enabled: config.enabled, last_message: null, }; // load or delete saved values if (config.save_state) node_settings = Object.assign(node_settings, smart_context.get(node.id)); else smart_context.del(node.id); // ################## // # Dynamic config # // ################## // ################## // # Runtime values # // ################## // Here the setTimeout return value is stored when the next timeout should happen let timeout = null; // This is date when the next event schould be raised let nextEvent = null; // Initially prepare schedules config object setTimeout(() => { for (let i = 0; i < config.schedules.length; i++) { const schedule = config.schedules[i]; schedule.position = i + 1; schedule.message = helper.evaluateNodeProperty(RED, schedule.message, "json"); schedule.days = schedule.days.split(","); } if (node_settings.enabled) initNextTimeout(); setStatus(); }, 1000); // ############### // # Node events # // ############### node.on("input", function (msg) { handleTopic(msg); setStatus(); if (config.save_state) smart_context.set(node.id, node_settings); }); node.on("close", function () { if (timeout != null) { clearTimeout(timeout); timeout = null; } }); // ##################### // # Private functions # // ##################### // This is the main function which handles all topics that was received. let handleTopic = msg => { let real_topic = helper.getTopicName(msg.topic); if (real_topic.startsWith("set_state")) real_topic = real_topic.replace("set_state", "set"); if (real_topic == "set_inverted") { real_topic = "set"; msg.payload = !msg.payload; } if (real_topic == "set") real_topic = (!!msg.payload) ? "enable" : "disable"; switch (real_topic) { case "debug": helper.nodeDebug(node, { node_settings, }); break; case "enable": node_settings.enabled = true; initNextTimeout(); break; case "disable": node_settings.enabled = false; break; } } // calculate which event should occur nect let initNextTimeout = () => { let minIndex = null; for (let i = 0; i < config.schedules.length; i++) { config.schedules[i].nextEvent = calcNextEvent(i); if (config.schedules[i].nextEvent != null) { if (minIndex == null || config.schedules[i].nextEvent < config.schedules[minIndex].nextEvent) minIndex = i; } } // No events defined if (minIndex == null) { nextEvent = null; return; } // Stop timeout if any if (timeout != null) { clearTimeout(timeout); timeout = null; } nextEvent = config.schedules[minIndex].nextEvent; let waitTime = nextEvent.getTime() - (new Date()).getTime(); timeout = setTimeout(() => { timeout = null; raiseEvent(minIndex); }, waitTime); } // calculates the next time when the scheduled i-th entry should run. let calcNextEvent = i => { const schedule = config.schedules[i]; // If no day is checked it is never raised if (!schedule.days || schedule.days.length == 0) return null; let now = new Date(); let findNextDay = false; // check if the time has already passed today if (now.getHours() > schedule.hour) { findNextDay = true; } else if (now.getHours() == schedule.hour) { if (now.getMinutes() > schedule.minute) { findNextDay = true; } else if (now.getMinutes() == schedule.minute) { findNextDay = now.getSeconds() >= schedule.second; } } // find next day when the event should be raised let possibleDay = schedule.days.filter(d => findNextDay ? d > now.getDay() : d >= now.getDay()); if (possibleDay.length == 0) possibleDay = Math.min(...schedule.days); else possibleDay = Math.min(...possibleDay); let nextEvent = new Date( now.getFullYear(), now.getMonth(), now.getDate() + ( findNextDay ? possibleDay <= now.getDay() ? 7 - now.getDay() + possibleDay : possibleDay - now.getDay() : possibleDay < now.getDay() ? 7 - now.getDay() + possibleDay : possibleDay - now.getDay() ), schedule.hour, schedule.minute, schedule.second ); // helper.log(node, { // i, // findNextDay, // nextEvent // }); return nextEvent; } // Send the i-th entry to the output let raiseEvent = i => { const schedule = config.schedules[i]; if (!node_settings.enabled) return; timeout = null; node.send(helper.cloneObject(schedule.message)); node_settings.last_message = schedule.message; if (config.save_state) smart_context.set(node.id, node_settings); initNextTimeout(); setStatus(); } let setStatus = () => { if (!node_settings.enabled) { node.status({ fill: "red", shape: "dot", text: helper.getCurrentTimeForStatus() + ": Scheduler disabled" }); } else if (nextEvent == null) { node.status({ fill: "red", shape: "dot", text: helper.getCurrentTimeForStatus() + ": No events planned" }); } else { // filter out empty values let time = nextEvent.getTime() - (new Date()).getTime(); time = Math.ceil(time / 1000) * 1000; node.status({ fill: "yellow", shape: "dot", text: helper.getCurrentTimeForStatus() + ": Wait " + helper.formatMsToStatus(time, "until") + " to raise next event" }); } } if (config.save_state && config.resend_on_start && node_settings.last_message != null) { setTimeout(() => { node.send(helper.cloneObject(node_settings.last_message)); }, 10000); } } RED.nodes.registerType("smart_scheduler", SchedulerNode); }