diff --git a/time/timeswitch/timeswitch.js b/time/timeswitch/timeswitch.js index 646f4476..35e55750 100644 --- a/time/timeswitch/timeswitch.js +++ b/time/timeswitch/timeswitch.js @@ -1,216 +1,223 @@ +module.exports = function (RED) { + "use strict"; + var SunCalc = require('suncalc'); + const spacetime = require("spacetime") + const SUNRISE_KEY = "sunrise"; + const SUNSET_KEY = "sunset"; -module.exports = function(RED) { - "use strict"; - var SunCalc = require('suncalc'); - const spacetime = require("spacetime") - const SUNRISE_KEY = "sunrise"; - const SUNSET_KEY = "sunset"; + function TimeswitchNode(n) { + RED.nodes.createNode(this, n); + this.lat = n.lat; + this.lon = n.lon; + this.startt = n.starttime; + this.endt = n.endtime; + this.sunriseOffset = n.dawnoff; + this.sunsetOffset = n.duskoff; + this.mytopic = n.mytopic; + this.timezone = n.timezone || "UTC"; - function TimeswitchNode(n) { - RED.nodes.createNode(this, n); - this.lat = n.lat; - this.lon = n.lon; - this.start = n.start || "sunrise"; - this.end = n.end || "sunset"; - this.startt = n.starttime; - this.endt = n.endtime; - this.duskoff = n.duskoff; - this.dawnoff = n.dawnoff; - this.mytopic = n.mytopic; - this.timezone = n.timezone || "UTC"; + this.sun = n.sun; + this.mon = n.mon; + this.tue = n.tue; + this.wed = n.wed; + this.thu = n.thu; + this.fri = n.fri; + this.sat = n.sat; + + this.jan = n.jan; + this.feb = n.feb; + this.mar = n.mar; + this.apr = n.apr; + this.may = n.may; + this.jun = n.jun; + this.jul = n.jul; + this.aug = n.aug; + this.sep = n.sep; + this.oct = n.oct; + this.nov = n.nov; + this.dec = n.dec; - this.sun = n.sun; - this.mon = n.mon; - this.tue = n.tue; - this.wed = n.wed; - this.thu = n.thu; - this.fri = n.fri; - this.sat = n.sat; - this.jan = n.jan; - this.feb = n.feb; - this.mar = n.mar; - this.apr = n.apr; - this.may = n.may; - this.jun = n.jun; - this.jul = n.jul; - this.aug = n.aug; - this.sep = n.sep; - this.oct = n.oct; - this.nov = n.nov; - this.dec = n.dec; + var node = this; - var node = this; - var ison = 0; - var newendtime = 0; + this.on("input", function () { + // current global time + const now = spacetime.now(); + const nowNative = now.toNativeDate(); - this.on("input", function(msg2) { - if (msg2.payload === "reset") { ison = 0; } - - // current global time - const now = spacetime.now(); - const nowNative = now.toNativeDate(); - - // all sun events for the given lat/long - const sunEvents = SunCalc.getTimes(nowNative, node.lat, node.lon); - let sunriseDateTime = spacetime(sunEvents[SUNRISE_KEY]).nearest("minute"); - let sunsetDateTime = spacetime(sunEvents[SUNSET_KEY]).nearest("minute"); + // all sun events for the given lat/long + const sunEvents = SunCalc.getTimes(nowNative, node.lat, node.lon); + let sunriseDateTime = spacetime(sunEvents[SUNRISE_KEY]).nearest("minute"); + let sunsetDateTime = spacetime(sunEvents[SUNSET_KEY]).nearest("minute"); - // check if sun event has already occurred today - if (now.isAfter(sunriseDateTime)) { - // get tomorrow's sunrise, since it'll be different - sunriseDateTime = spacetime(SunCalc.getTimes(now.add(1, "day").toNativeDate(), node.lat, node.lon)[SUNRISE_KEY]).nearest("minute"); - } - if (now.isAfter(sunsetDateTime)) { - // get tomorrow's sunset, since it'll be different - sunsetDateTime = spacetime(SunCalc.getTimes(now.add(1, "day").toNativeDate(), node.lat, node.lon)[SUNSET_KEY]).nearest("minute"); - } + // add optional sun event offset, if specified + sunriseDateTime = sunriseDateTime.add(Number(node.sunriseOffset), "minutes"); + sunsetDateTime = sunsetDateTime.add(Number(node.sunsetOffset), "minutes"); - // log - if (RED.settings.verbose) { - node.log(`Sunrise ${sunriseDateTime.format("time")} - Sunset ${sunsetDateTime.format("time")} `); - } + // check if sun event has already occurred today + if (now.isAfter(sunriseDateTime)) { + // get tomorrow's sunrise, since it'll be different + sunriseDateTime = spacetime(SunCalc.getTimes(now.add(1, "day").toNativeDate(), node.lat, node.lon)[SUNRISE_KEY]).nearest("minute"); + // add optional sun event offset, if specified (again) + sunriseDateTime = sunriseDateTime.add(Number(node.sunriseOffset), "minutes"); + } + if (now.isAfter(sunsetDateTime)) { + // get tomorrow's sunset, since it'll be different + sunsetDateTime = spacetime(SunCalc.getTimes(now.add(1, "day").toNativeDate(), node.lat, node.lon)[SUNSET_KEY]).nearest("minute"); + // add optional sun event offset, if specified (again) + sunsetDateTime = sunsetDateTime.add(Number(node.sunsetOffset), "minutes"); + } - // apply selected timezone to selected times (not to sunrise/sunset-- those are based on lat/long) - const currentTimeZone = now.timezone(); - const selectedTimeZone = spacetime(now.epoch, this.timezone.toLowerCase()).timezone(); + // log sun events + if (RED.settings.verbose) { + node.log(`Sunrise ${sunriseDateTime.format("time")} - Sunset ${sunsetDateTime.format("time")} `); + } - let getSelectedTimeFromMinuteString = minuteString => { - const selectedTimeInMinutesAfterMidnight = Number(minuteString); - let selectedTime = spacetime.now(); - // if less than 1440, what are the time values for the next start and stop time? - if (selectedTimeInMinutesAfterMidnight < 1440) { - // determine offset to get from selected time zone to current timezone - // e.g. current (EDT) is -4, selected (PDT) is -7 - // to get from PDT to EDT, you must add 3 - // (-4) - (-7) = +3 - const offset = currentTimeZone.current.offset - selectedTimeZone.current.offset; - const selectedHourValue = Math.floor(selectedTimeInMinutesAfterMidnight / 60); - const selectedMinuteValue = Math.floor(selectedTimeInMinutesAfterMidnight % 60); - selectedTime = selectedTime.hour(selectedHourValue).minute(selectedMinuteValue).second(0).millisecond(0); - selectedTime = selectedTime.add(offset, "hours"); - // select the next time if it's in the past - if(now.isAfter(selectedTime)) { - selectedTime = selectedTime.add(1, "day"); - } - } else if (selectedTimeInMinutesAfterMidnight == 5000) { // sunrise - selectedTime = sunriseDateTime; - } else if (selectedTimeInMinutesAfterMidnight == 6000) { // sunset - selectedTime = sunsetDateTime; - } - return selectedTime; - }; + // apply selected timezone to selected times (not to sunrise/sunset-- those are based on lat/long) + const currentTimeZone = now.timezone(); + const selectedTimeZone = spacetime(now.epoch, this.timezone.toLowerCase()).timezone(); - let selectedOnTime = getSelectedTimeFromMinuteString(node.startt); // 8/22 22:45 - let selectedOffTime = getSelectedTimeFromMinuteString(node.endt); // 8/23 06:31 + let getSelectedTimeFromMinuteString = minuteString => { + const selectedTimeInMinutesAfterMidnight = Number(minuteString); + let selectedTime = spacetime.now(); + // if less than 1440, what are the time values for the next start and stop time? + if (selectedTimeInMinutesAfterMidnight < 1440) { + // determine offset to get from selected time zone to current timezone + // e.g. current (EDT) is -4, selected (PDT) is -7 + // to get from PDT to EDT, you must add 3 + // (-4) - (-7) = +3 + const offset = currentTimeZone.current.offset - selectedTimeZone.current.offset; + const selectedHourValue = Math.floor(selectedTimeInMinutesAfterMidnight / 60); + const selectedMinuteValue = Math.floor(selectedTimeInMinutesAfterMidnight % 60); + selectedTime = selectedTime.hour(selectedHourValue).minute(selectedMinuteValue).second(0).millisecond(0); + selectedTime = selectedTime.add(offset, "hours"); + // select the next time if it's in the past + if (now.isAfter(selectedTime)) { + selectedTime = selectedTime.add(1, "day"); + } + } else if (selectedTimeInMinutesAfterMidnight == 5000) { // sunrise + selectedTime = sunriseDateTime; + } else if (selectedTimeInMinutesAfterMidnight == 6000) { // sunset + selectedTime = sunsetDateTime; + } + return selectedTime; + }; - // handle the "Start + X Minutes" cases - if (node.endt >= 10000) { - selectedOffTime = selectedOnTime.add(node.endt - 10000, "minutes"); - } + let selectedOnTime = getSelectedTimeFromMinuteString(node.startt); // 8/22 22:45 + let selectedOffTime = getSelectedTimeFromMinuteString(node.endt); // 8/23 06:31 - // handler function for the node payload - let sendPayload = (payload, nextTime) => { - if (payload == 1) { - node.status({ - fill: "yellow", - shape: "dot", - text: `on until ${nextTime.format("time")}` - }); - } - else { - node.status({ - fill: "blue", - shape: "dot", - text: `off until ${nextTime.format("time")}` - }); - } - var msg = {}; - if (node.mytopic) { msg.topic = node.mytopic; } - msg.payload = payload; - node.send(msg); - }; + // handle the "Start + X Minutes" cases + if (node.endt >= 10000) { + selectedOffTime = selectedOnTime.add(node.endt - 10000, "minutes"); + } - var proceed = true; - // if today is not among the selected days of the week, stop here - switch (nowNative.getDay()) { - case 0 : { if (!node.sun) { proceed &= false; } break; } - case 1 : { if (!node.mon) { proceed &= false; } break; } - case 2 : { if (!node.tue) { proceed &= false; } break; } - case 3 : { if (!node.wed) { proceed &= false; } break; } - case 4 : { if (!node.thu) { proceed &= false; } break; } - case 5 : { if (!node.fri) { proceed &= false; } break; } - case 6 : { if (!node.sat) { proceed &= false; } break; } - } + // handler function for the node payload + let sendPayload = (payload, nextTime) => { + if (payload == 1) { + node.status({ + fill: "yellow", + shape: "dot", + text: `on until ${nextTime.format("time")}` + }); + } else { + node.status({ + fill: "blue", + shape: "dot", + text: `off until ${nextTime.format("time")}` + }); + } + var msg = {}; + if (node.mytopic) { + msg.topic = node.mytopic; + } + msg.payload = payload; + node.send(msg); + }; - if (!proceed) { - sendPayload(0, selectedOnTime); - return; - } + var proceed = true; + // if today is not among the selected days of the week, stop here + switch (nowNative.getDay()) { + case 0 : { if (!node.sun) { proceed &= false; } break; } + case 1 : { if (!node.mon) { proceed &= false; } break; } + case 2 : { if (!node.tue) { proceed &= false; } break; } + case 3 : { if (!node.wed) { proceed &= false; } break; } + case 4 : { if (!node.thu) { proceed &= false; } break; } + case 5 : { if (!node.fri) { proceed &= false; } break; } + case 6 : { if (!node.sat) { proceed &= false; } break; } + } - // if this month is not among the selected months, stop here - switch (nowNative.getMonth()) { - case 0 : { if (!node.jan) { proceed &= false; } break; } - case 1 : { if (!node.feb) { proceed &= false; } break; } - case 2 : { if (!node.mar) { proceed &= false; } break; } - case 3 : { if (!node.apr) { proceed &= false; } break; } - case 4 : { if (!node.may) { proceed &= false; } break; } - case 5 : { if (!node.jun) { proceed &= false; } break; } - case 6 : { if (!node.jul) { proceed &= false; } break; } - case 7 : { if (!node.aug) { proceed &= false; } break; } - case 8 : { if (!node.sep) { proceed &= false; } break; } - case 9 : { if (!node.oct) { proceed &= false; } break; } - case 10: { if (!node.nov) { proceed &= false; } break; } - case 11: { if (!node.dec) { proceed &= false; } break; } - } + if (!proceed) { + sendPayload(0, selectedOnTime); + return; + } - if (!proceed) { - sendPayload(0, selectedOnTime); - return; - } + // if this month is not among the selected months, stop here + switch (nowNative.getMonth()) { + case 0 : { if (!node.jan) { proceed &= false; } break; } + case 1 : { if (!node.feb) { proceed &= false; } break; } + case 2 : { if (!node.mar) { proceed &= false; } break; } + case 3 : { if (!node.apr) { proceed &= false; } break; } + case 4 : { if (!node.may) { proceed &= false; } break; } + case 5 : { if (!node.jun) { proceed &= false; } break; } + case 6 : { if (!node.jul) { proceed &= false; } break; } + case 7 : { if (!node.aug) { proceed &= false; } break; } + case 8 : { if (!node.sep) { proceed &= false; } break; } + case 9 : { if (!node.oct) { proceed &= false; } break; } + case 10: { if (!node.nov) { proceed &= false; } break; } + case 11: { if (!node.dec) { proceed &= false; } break; } + } - // if the chronological order is NOW, ON, OFF, then now should be OFF - if (proceed && selectedOffTime.isAfter(selectedOnTime)) { - sendPayload(0, selectedOnTime); - return; - } + if (!proceed) { + sendPayload(0, selectedOnTime); + return; + } - // if the chronological order is NOW, OFF, ON, then now should be ON - if (proceed && selectedOffTime.isBefore(selectedOnTime)) { - sendPayload(1, selectedOnTime); - return; - } - }); + // if the chronological order is NOW, ON, OFF, then now should be OFF + if (proceed && selectedOffTime.isAfter(selectedOnTime)) { + sendPayload(0, selectedOnTime); + return; + } - var tock = setTimeout(function() { - node.emit("input", {}); - }, 2000); // wait 2 secs before starting to let things settle down – e.g. UI connect + // if the chronological order is NOW, OFF, ON, then now should be ON + if (proceed && selectedOffTime.isBefore(selectedOnTime)) { + sendPayload(1, selectedOffTime); + return; + } + }); - var tick = setInterval(function() { - node.emit("input", {}); - }, 60000); // trigger every 60 secs + var tock = setTimeout(function () { + node.emit("input", {}); + }, 2000); // wait 2 secs before starting to let things settle down – e.g. UI connect - this.on("close", function() { - if (tock) { clearTimeout(tock); } - if (tick) { clearInterval(tick); } - }); - } + var tick = setInterval(function () { + node.emit("input", {}); + }, 60000); // trigger every 60 secs - RED.httpAdmin.post("/timeswitch/:id", RED.auth.needsPermission("timeswitch.write"), function(req, res) { - var node = RED.nodes.getNode(req.params.id); - if (node != null) { - try { - node.emit("input", {payload:"reset"}); - res.sendStatus(200); - } - catch (err) { - res.sendStatus(500); - node.error("Inject failed:" + err); - } - } - else { - res.sendStatus(404); - } - }); + this.on("close", function () { + if (tock) { + clearTimeout(tock); + } + if (tick) { + clearInterval(tick); + } + }); + } - RED.nodes.registerType("timeswitch", TimeswitchNode); + RED.httpAdmin.post("/timeswitch/:id", RED.auth.needsPermission("timeswitch.write"), function (req, res) { + var node = RED.nodes.getNode(req.params.id); + if (node != null) { + try { + node.emit("input", { + payload: "reset" + }); + res.sendStatus(200); + } catch (err) { + res.sendStatus(500); + node.error("Inject failed:" + err); + } + } else { + res.sendStatus(404); + } + }); + + RED.nodes.registerType("timeswitch", TimeswitchNode); };