UNPKG

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