node-red-contrib-bigtimer
Version:
The ultimate Node-Red Timer with dusk, dawn (and variations inc. sunrise, sunset, moonrise and moonset), months, days, manual override, schedule pause, random or fixed offsets, special days and much more. Using STOP now turns the output off.
1,136 lines (999 loc) • 39.2 kB
JavaScript
/**
* This node is copyright (c) 2017-2024 Peter Scargill. Please consider
* it free to use for whatever timing purpose you like. If you wish to make
* changes please note you have the full source when you install BigTimer which
* essentially is just 2 files (html and js). I maintain BigTimer via
* https://tech.scargill.net/big-timer and will look at any code with a view to
* incorporating in the main BigTimer. I will not however support or comment on
* any unofficial "github repositories". I do not use Github for this as I'd
* rather encourage people to send code to me to test and release rather than confuse
* any of the many users of BigTimer with various clones and versions. See version
* number in package.json
*
* If you find BigTimer REALLY useful - on the blog (right column) is a PAYPAL link to
* help support the blog and fund my need for new gadgets.
* away added 1/1/2022
* February 10 2024 commented out 2 lines around line 1097 - put back in if you have difficulties on OFF days
*/
module.exports = function (RED) {
"use strict";
var SunCalc = require('suncalc');
function pad(n, width, z) {
z = z || '0';
n = n + '';
return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n;
}
function randomInt(low, high) {
var m = Math.floor(Math.random() * (Math.abs(high) - low) + low);
if (high <= 0) return -m; else return m;
}
function dayinmonth(date, weekday, n) // date, weekday (1-7) week of the month (1-5)
{
if (n > 0) {
return ((Math.ceil((date.getDate()) / 7) == n) && (date.getDay() == weekday - 1));
}
else {
var last = new Date(date.getFullYear(), date.getMonth() + 1, 0);
return (Math.ceil(last.getDate() / 7) == Math.ceil(date.getDate() / 7) && (date.getDay() == weekday - 1));
}
}
function bigTimerNode(n) {
RED.nodes.createNode(this, n);
var node = this;
var oneMinute = 60000;
var precision = 0;
var onOverride = -1;
var offOverride = -1;
var onOffsetOverride = -1; //DJL
var offOffsetOverride = -1; //DJL
var lonOverride=-1;
var latOverride=-1;
var stopped = 0;
var awayMinutes = 0;
var awayDisp = 0;
var awayMod="mins";
var ismanual = -1;
var timeout = 0;
var startDone = 0;
var onlyManual = 0;
node.name = n.name;
node.lat = n.lat;
node.lon = n.lon;
node.offs = n.offs;
node.startT = n.starttime;
node.endT = n.endtime;
node.startT2 = n.starttime2;
node.endT2 = n.endtime2;
node.startOff = n.startoff;
node.endOff = n.endoff;
node.startOff2 = n.startoff2;
node.endOff2 = n.endoff2;
node.outtopic = n.outtopic;
node.outPayload1 = n.outpayload1;
node.outPayload2 = n.outpayload2;
node.outText1 = n.outtext1;
node.outText2 = n.outtext2;
node.timeout = n.timeout;
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.jan = n.jan;
node.feb = n.feb;
node.mar = n.mar;
node.apr = n.apr;
node.may = n.may;
node.jun = n.jun;
node.jul = n.jul;
node.aug = n.aug;
node.sep = n.sep;
node.oct = n.oct;
node.nov = n.nov;
node.dec = n.dec;
node.suspend = n.suspend;
node.random = n.random;
node.randon1 = n.randon1;
node.randoff1 = n.randoff1;
node.randon2 = n.randon2;
node.randoff2 = n.randoff2;
node.repeat = n.repeat;
node.atStart = n.atstart;
node.odd = n.odd;
node.even = n.even;
node.day1 = n.day1;
node.month1 = n.month1;
node.day2 = n.day2;
node.month2 = n.month2;
node.day3 = n.day3;
node.month3 = n.month3;
node.day4 = n.day4;
node.month4 = n.month4;
node.day5 = n.day5;
node.month5 = n.month5;
node.day6 = n.day6;
node.month6 = n.month6;
node.day7 = n.day7;
node.month7 = n.month7;
node.day8 = n.day8;
node.month8 = n.month8;
node.day9 = n.day9;
node.month9 = n.month9;
node.day10 = n.day10;
node.month10 = n.month10;
node.day11 = n.day11;
node.month11 = n.month11;
node.day12 = n.day12;
node.month12 = n.month12;
node.xday1 = n.xday1;
node.xmonth1 = n.xmonth1;
node.xday2 = n.xday2;
node.xmonth2 = n.xmonth2;
node.xday3 = n.xday3;
node.xmonth3 = n.xmonth3;
node.xday4 = n.xday4;
node.xmonth4 = n.xmonth4;
node.xday5 = n.xday5;
node.xmonth5 = n.xmonth5;
node.xday6 = n.xday6;
node.xmonth6 = n.xmonth6;
node.xday7 = n.xday7;
node.xmonth7 = n.xmonth7;
node.xday8 = n.xday8;
node.xmonth8 = n.xmonth8;
node.xday9 = n.xday9;
node.xmonth9 = n.xmonth9;
node.xday10 = n.xday10;
node.xmonth10 = n.xmonth10;
node.xday11 = n.xday11;
node.xmonth11 = n.xmonth11;
node.xday12 = n.xday12;
node.xmonth12 = n.xmonth12;
node.d1 = n.d1;
node.w1 = n.w1;
node.d2 = n.d2;
node.w2 = n.w2;
node.d3 = n.d3;
node.w3 = n.w3;
node.d4 = n.d4;
node.w4 = n.w4;
node.d5 = n.d5;
node.w5 = n.w5;
node.d6 = n.d6;
node.w6 = n.w6;
node.xd1 = n.xd1;
node.xw1 = n.xw1;
node.xd2 = n.xd2;
node.xw2 = n.xw2;
node.xd3 = n.xd3;
node.xw3 = n.xw3;
node.xd4 = n.xd4;
node.xw4 = n.xw4;
node.xd5 = n.xd5;
node.xw5 = n.xw5;
node.xd6 = n.xd6;
node.xw6 = n.xw6;
// doesn't seem needed - node.xw5 = n.xw5 || 0;
var goodDay = 0;
var temporaryManual = 0;
var permanentManual = 0;
var playit = 0;
var newEndTime = 0;
var actualStartOffset = 0;
var actualEndOffset = 0;
var actualStartOffset2 = 0;
var actualEndOffset2 = 0;
var actualStartTime = 0;
var actualEndTime = 0;
var actualStartTime2 = 0;
var actualEndTime2 = 0;
var manualState = 0;
var autoState = 0;
var lastState = -1;
var actualState = 0;
var change = 0;
node
.on(
"input",
function (inmsg) {
if (awayMinutes) awayMinutes--;
if (awayMod=="mins") { awayDisp=0; if (awayMinutes) awayMinutes--; }
else
if (awayMod=="hrs") { awayDisp++; if (awayDisp>=60) { awayDisp=0; if (awayMinutes) awayMinutes--; } }
else
if (awayMod=="days") { awayDisp++; if (awayDisp>=1440) { awayDisp=0; if (awayMinutes) awayMinutes--; } }
if ((lonOverride != -1) && (latOverride!=-1)) { node.lon=lonOverride; node.lat=latOverride; } else { node.lon=n.lon; node.lat=n.lat; }
var now = new Date(); // UTC time - not local time
// this is the place to add an offset
now.setHours(now.getHours() + parseInt(node.offs, 10));
//var nowOff = -now.getTimezoneOffset() * 60000; // local offset
var times = SunCalc.getTimes(now, node.lat, node.lon); // get this from UTC, not local time
var moons = SunCalc.getMoonTimes(now, node.lat, node.lon); // moon up and down times - moons.rise, moons.set
var dawn = (times.dawn.getHours() * 60) + times.dawn.getMinutes();
var dusk = (times.dusk.getHours() * 60) + times.dusk.getMinutes();
var solarNoon = (times.solarNoon.getHours() * 60) + times.solarNoon.getMinutes();
var sunrise = (times.sunrise.getHours() * 60) + times.sunrise.getMinutes();
var sunset = (times.sunset.getHours() * 60) + times.sunset.getMinutes();
var date2 = new Date;
var date3 = new Date;
var moonrise;
var moonset;
if (typeof moons.rise==='undefined') moonrise=1440; else { date2=moons.rise; moonrise = (date2.getHours() * 60) + date2.getMinutes(); }
if (typeof moons.set==='undefined') moonset=0; else { date3=moons.set; moonset = (date3.getHours() * 60) + date3.getMinutes(); }
var night = (times.night.getHours() * 60) + times.night.getMinutes();
var nightEnd = (times.nightEnd.getHours() * 60) + times.nightEnd.getMinutes();
// now=new Date(now+nowOff); // from now on we're working on local time
var today = (now.getHours() * 60) + now.getMinutes();
var startTime = parseInt(node.startT, 10);
var endTime = parseInt(node.endT, 10);
var startTime2 = parseInt(node.startT2, 10);
var endTime2 = parseInt(node.endT2, 10);
var statusText="";
var outmsg1 = {
payload: "",
topic: ""
};
var outmsg2 = {
payload: "",
reference: node.outtopic + ":" + node.outPayload1 + ":" + node.outPayload2 + ":" + today,
topic: "status",
state: "",
time: "",
name: ""
};
var outmsg3 = {
payload: "",
topic: ""
};
// autoState is 1 or 0 or would be on auto.... has anything changed...
change = 0;
if (actualStartOffset == 0)
{ if (node.random) actualStartOffset = randomInt(0, node.startOff); else actualStartOffset = node.startOff;
if (node.randon1) actualStartOffset = randomInt(0, node.startOff);
}
if (actualEndOffset == 0)
{ if (node.random) actualEndOffset = randomInt(0, node.endOff); else actualEndOffset = node.endOff;
if (node.randoff1) actualEndOffset = randomInt(0, node.endOff);
}
if (actualStartOffset2 == 0)
{ if (node.random) actualStartOffset2 = randomInt(0, node.startOff2); else actualStartOffset2 = node.startOff2;
if (node.randon2) actualStartOffset2 = randomInt(0, node.startOff2);
}
if (actualEndOffset2 == 0)
{ if (node.random) actualEndOffset2 = randomInt(0, node.endOff2); else actualEndOffset2 = node.endOff2;
if (node.randoff2) actualEndOffset2 = randomInt(0, node.endOff2);
}
// manual override
if ((inmsg.payload==1) || (inmsg.payload===0)) inmsg.payload=inmsg.payload.toString();
if (inmsg.payload > "") {
inmsg.payload=inmsg.payload.toString().replace(/ +(?= )/g,'');
var theSwitch = inmsg.payload.toLowerCase().split(" ");
switch (theSwitch[0]) {
case "geo_override" : change=1;
switch (theSwitch.length) {
case 3:
lonOverride = Number(theSwitch[1]); latOverride=Number(theSwitch[2]); break;
default: lonOverride = -1; latOverride=-1; break;
}
break;
case "away":
if (typeof theSwitch[1] === 'undefined') awayMinutes=0; else awayMinutes=Number(theSwitch[1]);
if (typeof theSwitch[2] === 'undefined') { awayMod="mins"; awayDisp=0; }
else if (theSwitch[2].toLowerCase().substr(0,1)=='h') { awayMod="hrs"; awayDisp=0; }
else if (theSwitch[2].toLowerCase().substr(0,1)=='d') { awayMod="days"; awayDisp=0; }
else { awayMinutes=Number(theSwitch[1]); awayDisp=0; }
// }
awayMinutes++;
break;
case "sync": goodDay = 1; change = 1; break;
case "toggle" :
if (actualState==0)
{
if (permanentManual == 0) temporaryManual = 1;
timeout = node.timeout; change = 1; manualState = 1; stopped = 0; goodDay = 1;
}
else
{
if (permanentManual == 0) temporaryManual = 1;
timeout = node.timeout; change = 1; manualState = 0; stopped = 0; goodDay = 1;
}
break;
case "on":
case 1 :
case "1":
// bodge to kill timer
precision=0;
oneMinute=60000; temporaryManual = 0;
clearInterval(tick);
tick = setInterval(function () {
node.emit("input", {});
}, oneMinute); // trigger every 60 secs
temporaryManual = 1;
if (permanentManual == 0) temporaryManual = 1;
timeout = node.timeout; change = 1; manualState = 1; stopped = 0; goodDay = 1;
break;
case "off":
case 0 :
case "0":
// bodge to kill timer
precision=0;
oneMinute=60000; temporaryManual = 0;
clearInterval(tick);
tick = setInterval(function () {
node.emit("input", {});
}, oneMinute); // trigger every 60 secs
if (permanentManual == 0) temporaryManual = 1;
timeout = node.timeout; change = 1; manualState = 0; stopped = 0; goodDay = 1;
break;
case "default":
case "auto":
// bodge to kill timer
precision=0;
oneMinute=60000; temporaryManual = 0;
clearInterval(tick);
tick = setInterval(function () {
node.emit("input", {});
}, oneMinute); // trigger every 60 secs
temporaryManual = 0; permanentManual = 0; change = 1; stopped = 0; goodDay = 1; precision=0; break;
case "manual": if ((temporaryManual == 0))
{
manualState = autoState;
switch (theSwitch[1])
{
case 1:
case "1":
case "on": manualState=1; break;
case 0:
case "0":
case "off": manualState=0; break;
}
}
temporaryManual = 0; permanentManual = 1; change = 1; stopped = 0; break;
case "stop": stopped = 1; change = 1; manualState=0; permanentManual=1; break;
case "quiet": stopped = 1; change = 0; break;
case "on_override": change=1; switch (theSwitch.length) {
case 1: onOverride = -1; break;
case 2: var switch2 = theSwitch[1].split(":");
if (switch2.length==2) onOverride = (Number(switch2[0]) * 60) + Number(switch2[1]);
else
{
switch(theSwitch[1])
{
case 'dawn' : onOverride=5000; break;
case 'dusk' : onOverride=5001; break;
case 'solarnoon' : onOverride=5002; break;
case 'sunrise' : onOverride=5003; break;
case 'sunset' : onOverride=5004; break;
case 'night' : onOverride=5005; break;
case 'nightend' : onOverride=5006; break;
case 'moonrise' : onOverride=5007; break;
case 'moonset' : onOverride=5008; break;
default: onOverride = Number(theSwitch[1]); break;
}
}
break;
case 3: onOverride = (Number(theSwitch[1]) * 60) + Number(theSwitch[2]); break;
}
break;
case "off_override": change=1; switch (theSwitch.length) {
case 1: offOverride = -1; break;
case 2: var switch2 = theSwitch[1].split(":");
if (switch2.length==2) offOverride = (Number(switch2[0]) * 60) + Number(switch2[1]);
else
{
switch(theSwitch[1])
{
case 'dawn' : offOverride=5000; break;
case 'dusk' : offOverride=5001; break;
case 'solarnoon' : offOverride=5002; break;
case 'sunrise' : offOverride=5003; break;
case 'sunset' : offOverride=5004; break;
case 'night' : offOverride=5005; break;
case 'nightend' : offOverride=5006; break;
case 'moonrise' : offOverride=5007; break;
case 'moonset' : offOverride=5008; break;
default: offOverride = Number(theSwitch[1]); break;
}
}
break;
case 3: offOverride = (Number(theSwitch[1]) * 60) + Number(theSwitch[2]); break;
}
break;
case "on_offset_override": change=1;
switch (theSwitch.length) { //DJL this case block
case 1: onOffsetOverride = -1; break;
case 2: var switch2 = theSwitch[1].split(":");
if (switch2.length==2) onOffsetOverride = (Number(switch2[0]) * 60) + Number(switch2[1]);
else
{
switch(theSwitch[1])
{
case 'dawn' : onOffsetOverride=5000; break;
case 'dusk' : onOffsetOverride=5001; break;
case 'solarnoon' : onOffsetOverride=5002; break;
case 'sunrise' : onOffsetOverride=5003; break;
case 'sunset' : onOffsetOverride=5004; break;
case 'night' : onOffsetOverride=5005; break;
case 'nightend' : onOffsetOverride=5006; break;
case 'moonrise' : onOffsetOverride=5007; break;
case 'moonset' : onOffsetOverride=5008; break;
default: onOffsetOverride = Number(theSwitch[1]); break;
}
}
break;
case 3: onOffsetOverride = (Number(theSwitch[1]) * 60) + Number(theSwitch[2]); break;
}
break;
case "off_offset_override": change=1;
switch (theSwitch.length) { //DJL this case block
case 1: offOffsetOverride = -1; break;
case 2: var switch2 = theSwitch[1].split(":");
if (switch2.length==2) offOffsetOverride = (Number(switch2[0]) * 60) + Number(switch2[1]);
else
{
switch(theSwitch[1])
{
case 'dawn' : offOffsetOverride=5000; break;
case 'dusk' : offOffsetOverride=5001; break;
case 'solarnoon' : offOffsetOverride=5002; break;
case 'sunrise' : offOffsetOverride=5003; break;
case 'sunset' : offOffsetOverride=5004; break;
case 'night' : offOffsetOverride=5005; break;
case 'nightend' : offOffsetOverride=5006; break;
case 'moonrise' : offOffsetOverride=5007; break;
case 'moonset' : offOffsetOverride=5008; break;
default: offOffsetOverride = Number(theSwitch[1]); break;
}
}
break;
case 3: offOffsetOverride = (Number(theSwitch[1]) * 60) + Number(theSwitch[2]); break;
}
break;
case "timer" :
precision=Number(theSwitch[1]);
if (precision) {
oneMinute=1000; // dec 2018
precision++;
if (theSwitch[2]>"")
{
if (theSwitch[2].toLowerCase().substr(0,1)=='m') { oneMinute=60000; precision*=60; }
}
if (permanentManual == 0) temporaryManual = 1;
timeout = node.timeout; change = 1; manualState = 1; stopped = 0; goodDay = 1;
}
else { oneMinute=60000; temporaryManual = 0; // permanentManual = 0; // apr 16 2018
change = 1; stopped = 0; goodDay = 1; }
clearInterval(tick);
tick = setInterval(function () {
node.emit("input", {});
}, oneMinute); // trigger every 60 secs
break;
case "timeoff" :
precision=Number(theSwitch[1]);
if (precision) {
oneMinute=1000; // dec 2018
precision++;
if (theSwitch[2]>"")
{
if (theSwitch[2].toLowerCase().substr(0,1)=='m') { oneMinute=60000; precision*=60; }
}
if (permanentManual == 0) temporaryManual = 1;
timeout = node.timeout; change = 1; manualState = 0; stopped = 0; goodDay = 1;
}
else { oneMinute=60000; temporaryManual = 0; // permanentManual = 0; // apr 16 2018
change = 1; stopped = 0; goodDay = 1; }
clearInterval(tick);
tick = setInterval(function () {
node.emit("input", {});
}, oneMinute); // trigger every 60 secs
break;
default: break;
}
}
var thedot="dot"
if (onOverride != -1) { thedot="ring"; startTime = onOverride; }
if (offOverride != -1) { thedot="ring"; endTime = offOverride; }
if (onOffsetOverride != -1) { thedot="ring"; actualStartOffset = onOffsetOverride; } //DJL
if (offOffsetOverride != -1) { thedot="ring"; actualEndOffset = offOffsetOverride; } //DJL
if (startTime == 5000) startTime = dawn;
if (startTime == 5001) startTime = dusk;
if (startTime == 5002) startTime = solarNoon;
if (startTime == 5003) startTime = sunrise;
if (startTime == 5004) startTime = sunset;
if (startTime == 5005) startTime = night;
if (startTime == 5006) startTime = nightEnd;
if (startTime == 5007) startTime = moonrise;
if (startTime == 5008) startTime = moonset;
if (endTime == 5000) endTime = dawn;
if (endTime == 5001) endTime = dusk;
if (endTime == 5002) endTime = solarNoon;
if (endTime == 5003) endTime = sunrise;
if (endTime == 5004) endTime = sunset;
if (endTime == 5005) endTime = night;
if (endTime == 5006) endTime = nightEnd;
if (endTime == 5007) endTime = moonrise;
if (endTime == 5008) endTime = moonset;
if (endTime == 10001) endTime = (startTime + 1) % 1440;
if (endTime == 10002) endTime = (startTime + 2) % 1440;
if (endTime == 10005) endTime = (startTime + 5) % 1440;
if (endTime == 10010) endTime = (startTime + 10) % 1440;
if (endTime == 10015) endTime = (startTime + 15) % 1440;
if (endTime == 10030) endTime = (startTime + 30) % 1440;
if (endTime == 10060) endTime = (startTime + 60) % 1440;
if (endTime == 10090) endTime = (startTime + 90) % 1440;
if (endTime == 10120) endTime = (startTime + 120) % 1440;
actualStartTime = (startTime + Number(actualStartOffset)) % 1440;
actualEndTime = (endTime + Number(actualEndOffset)) % 1440;
if (startTime2 == 5000) startTime2 = dawn;
if (startTime2 == 5001) startTime2 = dusk;
if (startTime2 == 5002) startTime2 = solarNoon;
if (startTime2 == 5003) startTime2 = sunrise;
if (startTime2 == 5004) startTime2 = sunset;
if (startTime2 == 5005) startTime2 = night;
if (startTime2 == 5006) startTime2 = nightEnd;
if (startTime2 == 5007) startTime2 = moonrise;
if (startTime2 == 5008) startTime2 = moonset;
if (endTime2 == 5000) endTime2 = dawn;
if (endTime2 == 5001) endTime2 = dusk;
if (endTime2 == 5002) endTime2 = solarNoon;
if (endTime2 == 5003) endTime2 = sunrise;
if (endTime2 == 5004) endTime2 = sunset;
if (endTime2 == 5005) endTime2 = night;
if (endTime2 == 5006) endTime2 = nightEnd;
if (endTime2 == 5007) endTime2 = moonrise;
if (endTime2 == 5008) endTime2 = moonset;
if (endTime2 == 10001) endTime2 = (startTime2 + 1) % 1440;
if (endTime2 == 10002) endTime2 = (startTime2 + 2) % 1440;
if (endTime2 == 10005) endTime2 = (startTime2 + 5) % 1440;
if (endTime2 == 10010) endTime2 = (startTime2 + 10) % 1440;
if (endTime2 == 10015) endTime2 = (startTime2 + 15) % 1440;
if (endTime2 == 10030) endTime2 = (startTime2 + 30) % 1440;
if (endTime2 == 10060) endTime2 = (startTime2 + 60) % 1440;
if (endTime2 == 10090) endTime2 = (startTime2 + 90) % 1440;
if (endTime2 == 10120) endTime2 = (startTime2 + 120) % 1440;
actualStartTime2 = (startTime2 + Number(actualStartOffset2)) % 1440;
actualEndTime2 = (endTime2 + Number(actualEndOffset2)) % 1440;
autoState = 0; goodDay = 0;
switch (now.getDay()) {
case 0:
if (node.sun)
autoState = 1;
break;
case 1:
if (node.mon)
autoState = 1;;
break;
case 2:
if (node.tue)
autoState = 1;
break;
case 3:
if (node.wed)
autoState = 1;
break;
case 4:
if (node.thu)
autoState = 1;
break;
case 5:
if (node.fri)
autoState = 1;
break;
case 6:
if (node.sat)
autoState = 1;
break;
}
if (autoState) {
autoState = 0;
switch (now.getMonth()) {
case 0:
if (node.jan)
autoState = 1;
break;
case 1:
if (node.feb)
autoState = 1;
break;
case 2:
if (node.mar)
autoState = 1;
break;
case 3:
if (node.apr)
autoState = 1;
break;
case 4:
if (node.may)
autoState = 1;
break;
case 5:
if (node.jun)
autoState = 1;
break;
case 6:
if (node.jul)
autoState = 1;
break;
case 7:
if (node.aug)
autoState = 1;
break;
case 8:
if (node.sep)
autoState = 1;
break;
case 9:
if (node.oct)
autoState = 1;
break;
case 10:
if (node.nov)
autoState = 1;
break;
case 11:
if (node.dec)
autoState = 1;
break;
}
}
if ((node.day1 == now.getDate()) && (node.month1 == (now.getMonth() + 1))) autoState = 1;
if ((node.day2 == now.getDate()) && (node.month2 == (now.getMonth() + 1))) autoState = 1;
if ((node.day3 == now.getDate()) && (node.month3 == (now.getMonth() + 1))) autoState = 1;
if ((node.day4 == now.getDate()) && (node.month4 == (now.getMonth() + 1))) autoState = 1;
if ((node.day5 == now.getDate()) && (node.month5 == (now.getMonth() + 1))) autoState = 1;
if ((node.day6 == now.getDate()) && (node.month6 == (now.getMonth() + 1))) autoState = 1;
if ((node.day7 == now.getDate()) && (node.month7 == (now.getMonth() + 1))) autoState = 1;
if ((node.day8 == now.getDate()) && (node.month8== (now.getMonth() + 1))) autoState = 1;
if ((node.day9 == now.getDate()) && (node.month9 == (now.getMonth() + 1))) autoState = 1;
if ((node.day10 == now.getDate()) && (node.month10 == (now.getMonth() + 1))) autoState = 1;
if ((node.day11 == now.getDate()) && (node.month11 == (now.getMonth() + 1))) autoState = 1;
if ((node.day12 == now.getDate()) && (node.month12 == (now.getMonth() + 1))) autoState = 1;
if (dayinmonth(now, node.d1, node.w1) == true) autoState = 1;
if (dayinmonth(now, node.d2, node.w2) == true) autoState = 1;
if (dayinmonth(now, node.d3, node.w3) == true) autoState = 1;
if (dayinmonth(now, node.d4, node.w4) == true) autoState = 1;
if (dayinmonth(now, node.d5, node.w5) == true) autoState = 1;
if ((node.xday1 == now.getDate()) && (node.xmonth1 == (now.getMonth() + 1))) autoState = 0;
if ((node.xday2 == now.getDate()) && (node.xmonth2 == (now.getMonth() + 1))) autoState = 0;
if ((node.xday3 == now.getDate()) && (node.xmonth3 == (now.getMonth() + 1))) autoState = 0;
if ((node.xday4 == now.getDate()) && (node.xmonth4 == (now.getMonth() + 1))) autoState = 0;
if ((node.xday5 == now.getDate()) && (node.xmonth5 == (now.getMonth() + 1))) autoState = 0;
if ((node.xday6 == now.getDate()) && (node.xmonth6 == (now.getMonth() + 1))) autoState = 0;
if ((node.xday7 == now.getDate()) && (node.xmonth7 == (now.getMonth() + 1))) autoState = 0; // feb 2023 fixed the last 6 which were inverted
if ((node.xday8 == now.getDate()) && (node.xmonth8 == (now.getMonth() + 1))) autoState = 0;
if ((node.xday9 == now.getDate()) && (node.xmonth9 == (now.getMonth() + 1))) autoState = 0;
if ((node.xday10 == now.getDate()) && (node.xmonth10 == (now.getMonth() + 1))) autoState = 0;
if ((node.xday11 == now.getDate()) && (node.xmonth11 == (now.getMonth() + 1))) autoState = 0;
if ((node.xday12 == now.getDate()) && (node.xmonth12 == (now.getMonth() + 1))) autoState = 0;
if (dayinmonth(now, node.xd1, node.xw1) == true) autoState = 0;
if (dayinmonth(now, node.xd2, node.xw2) == true) autoState = 0;
if (dayinmonth(now, node.xd3, node.xw3) == true) autoState = 0;
if (dayinmonth(now, node.xd4, node.xw4) == true) autoState = 0;
if (dayinmonth(now, node.xd5, node.xw5) == true) autoState = 0;
if (dayinmonth(now, node.xd6, node.xw6) == true) autoState = 0;
if (autoState) // have to handle midnight wrap
{
var wday;
wday=now.getDate()&1;
if ((node.odd)&&wday) autoState=0;
if ((node.even)&&!wday) autoState=0;
if (autoState == 1) goodDay = 1;
}
// if autoState==1 at this point - we are in the right day and right month or in a special day
// now we check the time
if (autoState) // have to handle midnight wrap
{
autoState = 0;
if (actualStartTime <= actualEndTime) {
if ((today >= actualStartTime) && (today < actualEndTime))
autoState = 1;
} else // right we are in an overlap situation
{
if (((today >= actualStartTime) || (today < actualEndTime)))
autoState = 1;
}
// added next line 17/02/2019 - suggestion from Mark McCans to overcome offset issue
if (node.startT2!=node.endT2)
{
if (actualStartTime2 <= actualEndTime2) {
if ((today >= actualStartTime2) && (today < actualEndTime2))
autoState = 2;
} else // right we are in an overlap situation
{
if (((today >= actualStartTime2) || (today < actualEndTime2)))
autoState = 2;
}
}
}
if ((node.atStart == 0) && (startDone == 0)) lastState = autoState; // that is - no output at the start if node.atStart is not ticked
if (autoState != lastState) // there's a change of auto
{
lastState = autoState; change = 1; // make a change happen and kill temporary manual
if (autoState) { actualEndOffset = 0; actualEndOffset2 = 0; } else { actualStartOffset = 0; actualStartOffset2 = 0; } // if turning on - reset offset for next OFF time else reset offset for next ON time
temporaryManual = 0; // kill temporaryManual (but not permanentManual) as we've changed to next auto state
}
if (precision) {
if (oneMinute==1000) precision--; else { if (precision>=60) precision-=60; }
if (precision==0) { clearInterval(tick); oneMinute=60000;
temporaryManual = 0; permanentManual = 0; change = 1; stopped = 0; goodDay = 1;
tick = setInterval(function () {
node.emit("input", {});
}, oneMinute); // trigger every 60 secs
}
}
if (temporaryManual || permanentManual) // auto does not time out.
{
if (timeout && (permanentManual==0)) {
if ((--timeout) == 0) {
manualState = autoState; // turn the output to auto state after X minutes of any kind of manual operation
temporaryManual = 0; // along with temporary manual setting
//permanentManual = 0; // april 16 2018
change = 1;
}
}
}
if (temporaryManual || permanentManual) actualState = manualState; else actualState = autoState;
var duration = 0;
var manov = "";
if (!goodDay==1) temporaryManual=0; // dec 16 2018
if (permanentManual == 1) manov = " Man. override. ";
else if (temporaryManual == 1)
{
if (precision)
{
if (precision>=60)
manov=" 'Timer' " + parseInt(precision/60) + " mins left. ";
else
manov=" 'Timer' " + precision + " secs left. ";
}
else manov = " Temp. override. ";
}
if (node.suspend) manov += " - SUSPENDED";
outmsg2.name = node.name;
outmsg2.time = 0;
if (actualState) outmsg2.state = "ON"; else outmsg2.state = "OFF";
if (stopped == 0) {
if (temporaryManual) outmsg2.state += " Override";
else if (permanentManual) outmsg2.state += " Manual";
else { if (goodDay == 1) outmsg2.state += " Auto"; }
}
else outmsg2.state += " Stopped";
if ((permanentManual == 1) || (temporaryManual == 1) || (node.suspend)) { // so manual then
if (actualState) {
if (stopped == 0)
{ if (awayMinutes>1) statusText = "Away " + ((awayMinutes)-1) + awayMod; else statusText = "ON" + manov;
node.status({
fill: "green",
shape: thedot,
text: statusText
}); }
else
{
if (awayMinutes>1) statusText = "Away " + ((awayMinutes)-1) + awayMod; else statusText = "STOPPED" + manov;
node.status({ // stopped completely
fill: "black",
shape: thedot,
text: statusText
});
}
}
else {
if (stopped == 0)
{
if (awayMinutes>1) statusText = "Away " + ((awayMinutes)-1) + awayMod; else statusText = "OFF" + manov;
node.status({
fill: "red",
shape: thedot,
text: statusText
});
}
else
{
if (awayMinutes>1) statusText = "Away " + ((awayMinutes)-1) + awayMod; else statusText = "STOPPED" + manov;
node.status({ // stopped completely
fill: "black",
shape: thedot,
text: statusText
});
}
}
}
else // so not manual but auto....
{
if (goodDay == 1) // auto and today's the day
{
if (actualState) { // i.e. if turning on automatically
if (actualState==1)
{
if (today <= actualEndTime)
duration = actualEndTime - today;
else
duration = actualEndTime + (1440 - today);
}
if (actualState==2)
{
if (today <= actualEndTime2)
duration = actualEndTime2 - today;
else
duration = actualEndTime2 + (1440 - today);
}
outmsg2.time = pad(parseInt(duration / 60), 2) + "hrs " + pad(duration % 60, 2) + "mins";
if (stopped == 0)
{
if (awayMinutes>1) statusText = "Away " + ((awayMinutes)-1) + awayMod; else statusText = "ON for " + pad(parseInt(duration / 60), 2) + "hrs " + pad(duration % 60, 2) + "mins";
node.status({
fill: "green",
shape: thedot,
text: statusText
});
}
else
{
if (awayMinutes>1) statusText = "Away " + ((awayMinutes)-1) + "mins"; else statusText = "STOPPED" + manov;
node.status({ // stopped completely
fill: "black",
shape: thedot,
text: statusText
});
}
}
else {
if ((node.startT2!=node.endT2)&&(today>actualEndTime) && (today<actualEndTime2)) // valid start and end 2 and we're past period 1
{
if ((today <= actualStartTime2))
duration = actualStartTime2 - today;
else
duration = actualStartTime2 + (1440 - today);
}
else
{
if (today <= actualStartTime)
duration = actualStartTime - today;
else
duration = actualStartTime + (1440 - today);
}
outmsg2.time = pad(parseInt(duration / 60), 2) + "hrs " + pad(duration % 60, 2) + "mins" + manov;
if (stopped == 0)
{
if (awayMinutes>1) statusText = "Away " + ((awayMinutes)-1) + awayMod; else statusText = "OFF for " + pad(parseInt(duration / 60), 2) + "hrs " + pad(duration % 60, 2) + "mins" + manov;
node.status({
fill: "blue",
shape: thedot,
text: statusText
});
}
else
{
if (awayMinutes>1) statusText = "Away " + ((awayMinutes)-1) + awayMod; else statusText = "STOPPED" + manov;
node.status({ // stopped completely
fill: "black",
shape: thedot,
text: statusText
});
}
}
}
else {
outmsg2.time = "";
if (stopped == 0)
{
if (awayMinutes>1) statusText = "Away " + ((awayMinutes)-1) + awayMod; else statusText = "No action today" + manov;
node.status({ // auto and nothing today thanks
fill: "black",
shape: thedot,
text: statusText
});
}
else
{
if (awayMinutes>1) statusText = "Away " + ((awayMinutes)-1) + awayMod; else statusText = "STOPPED" + manov;
node.status({ // stopped completely
fill: "black",
shape: thedot,
text: statusText
});
}
}
}
outmsg2.lon=node.lon;
outmsg2.lat=node.lat;
outmsg1.topic = node.outtopic;
outmsg3.payload = node.outText1;
outmsg3.topic = node.outtopic;
if (temporaryManual || permanentManual) outmsg1.state = (actualState) ? "on" : "off"; else outmsg1.state = "auto";
outmsg1.value = actualState;
if ((actualState) && (awayMinutes<2)) {
outmsg1.payload = node.outPayload1;
outmsg3.payload = node.outText1;
}
else {
outmsg1.payload = node.outPayload2;
outmsg3.payload = node.outText2;
}
// take into account CHANGE variable - if true a manual or auto change is due
outmsg1.autoState = autoState;
outmsg1.manualState = manualState;
outmsg1.timeout = timeout;
outmsg1.temporaryManual = temporaryManual;
outmsg1.permanentManual = permanentManual;
outmsg1.now = today;
outmsg1.timer = precision;
outmsg1.duration = duration;
outmsg1.stamp = Date.now();
outmsg1.extState=statusText;
if (awayMinutes) outmsg2.state="AWAY";
outmsg2.start = actualStartTime;
outmsg2.end = actualEndTime;
outmsg2.start2 = actualStartTime2; // added these 2 lines Sept 2022
outmsg2.end2 = actualEndTime2;
outmsg2.dusk = dusk;
outmsg2.dawn = dawn;
outmsg2.solarNoon = solarNoon;
outmsg2.sunrise = sunrise;
outmsg2.sunset = sunset;
outmsg2.night = night;
outmsg2.nightEnd = nightEnd;
outmsg2.moonrise = moonrise;
outmsg2.moonset = moonset;
outmsg2.now = today;
outmsg2.timer = precision;
outmsg2.duration = duration;
outmsg2.onOverride = onOverride;
outmsg2.offOverride = offOverride;
outmsg2.onOffsetOverride = onOffsetOverride;
outmsg2.offOffsetOverride = offOffsetOverride;
outmsg2.stamp = Date.now();
outmsg2.extState=statusText;
if (outmsg2.state.substr(0,2)=="ON") outmsg2.payload=1; else outmsg2.payload=0; // jan 9, 2022
if ((!node.suspend) && ((goodDay) || (permanentManual))) {
if ((change) || ((node.atStart) && (startDone == 0))) {
if (outmsg1.payload > "") {
if (stopped == 0)
{ if (change) node.send([outmsg1, outmsg2, outmsg3]);
else node.send([null, outmsg2, outmsg3]);
} else {
if (change)
// node.send([outmsg1, outmsg2, null]); // Before February 26 2024 this line and next were active
// else // Before February 26 2024 this line and previous were active
node.send([null, outmsg2, null]);
}
}
else {
if (stopped == 0) node.send([null, outmsg2, outmsg3]); else node.send([null, outmsg2, null]);
}
}
else {
if (outmsg1.payload > "") {
if (node.repeat)
{ if (stopped == 0) node.send([outmsg1, outmsg2, null]); else node.send([null, outmsg2, null]); }
else
{ if (stopped == 0) node.send([null, outmsg2, null]); else node.send([null, outmsg2, null]); }
}
else {
if (node.repeat) node.send([null, outmsg2, null]);
}
}
}
startDone = 1;
}); // end of the internal function
var tock = setTimeout(function () {
node.emit("input", {});
}, 2000); // wait 2 secs before starting to let things settle down -
// e.g. UI connect
var tick = setInterval(function () {
node.emit("input", {});
}, oneMinute); // trigger every 60 secs
node.on("close", function () {
if (tock) {
clearTimeout(tock);
}
if (tick) {
clearInterval(tick);
}
});
}
RED.nodes.registerType("bigtimer", bigTimerNode);
}