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
JavaScript
/**
* 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);
};