UNPKG

node-red-contrib-vacation-timer

Version:

A node-red node that simulates a house being occupied, by turning something on and off in a natural way.

465 lines (432 loc) 17.5 kB
/** * Copyright 2017 JBardi * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. **/ module.exports = function(RED) { "use strict"; var CronJob = require('cron').CronJob; var SunCalc = require('suncalc'); function VacationTimer(n) { RED.nodes.createNode(this, n); var node = this; node.stopTab = n.stopTab; node.onTime = n.onTime; node.randMinutes = Number(n.randMinutes); node.offTime = n.offTime; node.randOffMinutes = Number(n.randOffMinutes); node.minOnHours = Number(n.minOnHours); node.minOnMinutes = Number(n.minOnMinutes); node.maxOnHours = Number(n.maxOnHours); node.maxOnMinutes = Number(n.maxOnMinutes); node.onPayloadType = n.onPayloadType || "str"; node.onPayloadVal = n.onPayloadVal || "on"; node.offPayloadType = n.offPayloadType || "str"; node.offPayloadVal = n.offPayloadVal || "off"; node.onTopicVal = n.onTopicVal; node.offTopicVal = n.offTopicVal; node.disableMax = n.disableMax; node.lat = n.lat; node.lon = n.lon; node.sun = n.sun; node.mon = n.mon; node.tue = n.tue; node.wed = n.wed; node.thu = n.thu; node.fri = n.fri; node.sat = n.sat; node.suspended = n.suspended; node.reSchedule = n.midnightSchedule; node.turnOffOnDaysOff = n.turnOffOnDaysOff; node.onMsg = null; node.offMsg = null; if (!node.offTime) { node.offTime = "14:00"; } if (!node.randOffMinutes) { node.randOffMinutes = 0; } if (!node.disableMax) { node.disableMax = false; } if ((node.onPayloadType === "num") && (!isNaN(node.onPayloadVal))) { node.onPayloadVal = Number(node.onPayloadVal); } else if (node.onPayloadVal === 'true' || node.onPayloadVal === 'false') { node.onPayloadVal = Boolean(node.onPayloadVal); } else if (node.onPayloadVal == "null") { node.onPayloadType = 'str'; node.onPayloadVal = "on"; } else { node.onPayloadVal = String(node.onPayloadVal); } if ((node.offPayloadType === "num") && (!isNaN(node.offPayloadVal))) { node.offPayloadVal = Number(node.offPayloadVal); } else if (node.offPayloadVal === 'true' || node.offPayloadVal === 'false') { node.offPayloadVal = Boolean(node.offPayloadVal); } else if (node.offPayloadVal == "null") { node.offPayloadType = 'str'; node.offPayloadVal = "off"; } else { node.offPayloadVal = String(node.offPayloadVal); } var cronStart = null; var cronStop = null; var startjob = null; var stopjob = null; var starttime = ""; var stoptime = ""; var cronStartParts = ""; var cronStopParts = ""; var times = ""; var daysofweek = ""; var dayofweek = ""; var stopDate = null; var startDate = null; var now = null; var thisrun = null; var onTime = ""; var offTime = ""; var fireOnNow = false; var adjAmount = ""; node.on("input", function(msg) { if (msg.payload == "stop" || msg.payload == "STOP" || msg.payload == "off" || msg.payload == "OFF" || msg.payload === 0 || msg.payload == "0" || msg.payload === false || msg.payload == "false") { clearAllJobs(); node.suspended = true; node.status({ fill: "grey", shape: "dot", text: "Suspended" }); } else if (msg.payload == "start" || msg.payload == "START" || msg.payload == "on" || msg.payload == "ON" || msg.payload === 1 || msg.payload == "1" || msg.payload === true || msg.payload == "true") { if (stopjob === null || node.suspended === true) { clearAllJobs(); startSchedule(); } } }); node.on('close', function(done) { node.status({}); clearAllJobs(); done(); }); if (node.suspended === true) { clearAllJobs(); if ((node.onTime.charAt(0) == "d" || node.onTime.charAt(0) == "s") && (node.lat === "" || node.lon === "")) { node.status({ fill: "red", shape: "dot", text: "Error: Lat & Lon must be set for Dawn/Dusk/Sunrise/Sunset" }); } else { node.status({ fill: "grey", shape: "dot", text: "Suspended" }); } } else { node.emit("input", { payload: "start" }); } function startSchedule() { if ((node.onTime.charAt(0) == "d" || node.onTime.charAt(0) == "s") && (node.lat === "" || node.lon === "")) { node.status({ fill: "red", shape: "dot", text: "Error: Lat & Lon must be set for Dawn/Dusk/Sunrise/Sunset" }); } else if (node.stopTab == "tab-2" && (node.offTime.charAt(0) == "d" || node.offTime.charAt(0) == "s") && (node.lat === "" || node.lon === "")) { node.status({ fill: "red", shape: "dot", text: "Error: Lat & Lon must be set for Dawn/Dusk/Sunrise/Sunset" }); } else { daysofweek = ""; if (node.sun === true) { daysofweek = "0"; } if (node.mon === true) { if (daysofweek.length > 0) { daysofweek = daysofweek + ",1"; } else { daysofweek = "1"; } } if (node.tue === true) { if (daysofweek.length > 0) { daysofweek = daysofweek + ",2"; } else { daysofweek = "2"; } } if (node.wed === true) { if (daysofweek.length > 0) { daysofweek = daysofweek + ",3"; } else { daysofweek = "3"; } } if (node.thu === true) { if (daysofweek.length > 0) { daysofweek = daysofweek + ",4"; } else { daysofweek = "4"; } } if (node.fri === true) { if (daysofweek.length > 0) { daysofweek = daysofweek + ",5"; } else { daysofweek = "5"; } } if (node.sat === true) { if (daysofweek.length > 0) { daysofweek = daysofweek + ",6"; } else { daysofweek = "6"; } } if (daysofweek === "") { node.suspended = true; node.status({ fill: "grey", shape: "dot", text: "Suspended - No Days Selected" }); } else { start("today"); } } } function start(myday) { if (myday == "today") { thisrun = new Date(); } now = new Date(); node.suspended = false; if (node.onTime.charAt(0) == "d" || node.onTime.charAt(0) == "s") { times = SunCalc.getTimes(thisrun, node.lat, node.lon); if (node.onTime == "dawn") { onTime = times.dawn.getHours() + ':' + times.dawn.getMinutes(); } if (node.onTime == "dusk") { onTime = times.dusk.getHours() + ':' + times.dusk.getMinutes(); } if (node.onTime == "sunrise") { onTime = times.sunrise.getHours() + ':' + times.sunrise.getMinutes(); } if (node.onTime == "sunset") { onTime = times.sunset.getHours() + ':' + times.sunset.getMinutes(); } } else { onTime = node.onTime; } adjAmount = Math.round(Number(Math.random()) * (0 - node.randMinutes + 1) + node.randMinutes - 0.5); starttime = addMinutes(onTime, adjAmount); cronStart = starttime; if (cronStart.charAt(0) == "0") { cronStart = cronStart.slice(1); } cronStartParts = cronStart.split(":"); startDate = new Date(thisrun.getTime()); startDate.setHours(cronStartParts[0], cronStartParts[1], 0); dayofweek = startDate.getDay(); for (var i = 0; i < 7; i++) { if (daysofweek == "0,1,2,3,4,5,6" || daysofweek.indexOf(dayofweek) > -1) { break; } else { startDate.setDate(startDate.getDate() + 1); dayofweek = startDate.getDay(); } } if (startDate < now) { fireOnNow = true; } else { startjob = new CronJob(startDate, function() { node.onMsg = null; node.onMsg = { "payload": node.onPayloadVal, "topic": node.onTopicVal }; node.send(node.onMsg); startjob.stop(); startjob = null; }); startjob.start(); } if (node.stopTab == "tab-1") { if (node.minOnHours != 0) { node.minOn = (node.minOnHours * 60) + node.minOnMinutes; } else { node.minOn = node.minOnMinutes; } if (node.disableMax === true) { stoptime = addMinutes(starttime, node.minOn); } else { if (node.maxOnHours != 0) { node.maxOn = (node.maxOnHours * 60) + node.maxOnMinutes; } else { node.maxOn = node.maxOnMinutes; } adjAmount = Math.round(Number(Math.random()) * (node.minOn - node.maxOn + 1) + node.maxOn - 0.5); stoptime = addMinutes(starttime, adjAmount); } } else if (node.stopTab == "tab-2") { if (node.offTime.charAt(0) == "d" || node.offTime.charAt(0) == "s") { stopDate = new Date(startDate.getTime()); if (node.offTime == "sunrise" || node.offTime == "dawn") { stopDate.setDate(stopDate.getDate() + 1); } times = SunCalc.getTimes(stopDate, node.lat, node.lon); if (node.offTime == "dawn") { offTime = times.dawn.getHours() + ':' + times.dawn.getMinutes(); } if (node.offTime == "dusk") { offTime = times.dusk.getHours() + ':' + times.dusk.getMinutes(); } if (node.offTime == "sunrise") { offTime = times.sunrise.getHours() + ':' + times.sunrise.getMinutes(); } if (node.offTime == "sunset") { offTime = times.sunset.getHours() + ':' + times.sunset.getMinutes(); } } else { offTime = node.offTime; } adjAmount = Math.round(Number(Math.random()) * (0 - node.randOffMinutes + 1) + node.randOffMinutes - 0.5); stoptime = addMinutes(offTime, adjAmount); } cronStop = stoptime; if (cronStop.charAt(0) == "0") { cronStop = cronStop.slice(1); } cronStopParts = cronStop.split(":"); stopDate = new Date(startDate.getTime()); stopDate.setHours(cronStopParts[0], cronStopParts[1], 0); var addDay = true; if (stopDate < startDate) { stopDate.setDate(stopDate.getDate() + 1); addDay = false; if (stopDate > now) { if (fireOnNow === true) { node.onMsg = null; node.onMsg = { "payload": node.onPayloadVal, "topic": node.onTopicVal }; node.send(node.onMsg); } } } else { if (stopDate > now) { if (fireOnNow === true) { node.onMsg = null; node.onMsg = { "payload": node.onPayloadVal, "topic": node.onTopicVal }; node.send(node.onMsg); } } else { startDate.setDate(startDate.getDate() + 1); stopDate.setDate(stopDate.getDate() + 1); addDay = false; } } fireOnNow = false; stopjob = new CronJob(stopDate, function() { if ((daysofweek == "0,1,2,3,4,5,6" || daysofweek.indexOf(dayofweek) > -1) || node.turnOffOnDaysOff === true) { node.offMsg = null; node.offMsg = { "payload": node.offPayloadVal, "topic": node.offTopicVal }; node.send(node.offMsg); } stopjob.stop(); stopjob = null; clearAllJobs(); if (node.reSchedule === true) { if (addDay === true) { thisrun = new Date(); thisrun.setDate(thisrun.getDate() + 1); start("tomorrow"); } else { start("today"); } } else { node.suspended = true; node.status({ fill: "grey", shape: "dot", text: "Suspended" }); } }); stopjob.start(); var displayStart = displayTime(starttime); var displayStop = displayTime(stoptime); var startMonth = startDate.getMonth() + 1; startMonth = startMonth < 10 ? '0' + startMonth : '' + startMonth; var startDay = startDate.getDate(); startDay = startDay < 10 ? '0' + startDay : '' + startDay; node.status({ fill: "blue", shape: "ring", text: "next: " + startDate.getFullYear() + "-" + startMonth + "-" + startDay + " (" + displayStart + " / " + displayStop + ")" }); } function clearAllJobs() { if (startjob !== null) { startjob.stop(); startjob = null; } if (stopjob !== null) { stopjob.stop(); stopjob = null; } startDate = null; stopDate = null; starttime = ""; stoptime = ""; cronStart = null; cronStop = null; cronStartParts = ""; cronStopParts = ""; now = null; thisrun = null; fireOnNow = false; } function addMinutes(time, minsToAdd) { function z(n) { return (n < 10 ? '0' : '') + n; } var bits = time.split(':'); var mins = bits[0] * 60 + (+bits[1]) + (+minsToAdd); return z(mins % (24 * 60) / 60 | 0) + ':' + z(mins % 60); } function displayTime(time) { var date = new Date("January 01, 2017 " + time + ":00"); var options = { hour: 'numeric', minute: 'numeric', hour12: true }; return date.toLocaleString('en-US', options); } } RED.nodes.registerType("vacation-timer", VacationTimer); };