node-red-contrib-eztimer
Advanced tools
Comparing version 1.1.6 to 1.2.1
311
index.js
@@ -29,3 +29,2 @@ const util = require('util'); | ||
module.exports = function(RED) { | ||
const debug = true; | ||
const moment = require('moment'); | ||
@@ -94,10 +93,12 @@ const SunCalc = require('suncalc'); | ||
send(events.on, true); | ||
if (events.off.type == '3') schedule(events.off, null, true); // If 'off' is of type duration, schedule 'off' event. | ||
status(events.on, true); | ||
} else if (msg.payload === 'off') { | ||
if (events.off && events.off.type == '3') schedule(events.off, null, true); // If 'off' is of type duration, schedule 'off' event. | ||
updateStatus(); | ||
} else if (msg.payload === 'off' && config.timerType == '1') { | ||
// Sends the off event, then re-schedules it | ||
handled = true; | ||
clearTimeout(events.off.timeout); | ||
events.off.moment = null; | ||
send(events.off, true); | ||
if (!isSuspended()) schedule(events.off); | ||
status(events.off, true); | ||
if (!isSuspended() && events.off.type != '3') schedule(events.off); | ||
updateStatus(); | ||
} else if (msg.payload === 'trigger') { | ||
@@ -107,2 +108,3 @@ // Sends the trigger/on event without impact the scheduled event | ||
send(events.on); | ||
updateStatus(); | ||
} else if (msg.payload === 'cancel' && config.timerType == '1') { | ||
@@ -113,5 +115,7 @@ // Cancels the current timer without sending the off event | ||
schedule(events.on); | ||
schedule(events.off); | ||
clearTimeout(events.off.timeout); | ||
events.off.moment = null; | ||
if (!isSuspended() && events.off.type != '3') schedule(events.off); | ||
} | ||
status(events.off); | ||
updateStatus(); | ||
} else if (msg.payload === 'info') { | ||
@@ -130,4 +134,4 @@ handled = true; | ||
ret.on = { | ||
property: 'msg.' + events.on.property, | ||
value: events.on.value || "<none>", | ||
property: (events.on.propertytype || 'msg') + '.' + events.on.property, | ||
value: getValue(events.on) || "<none>", | ||
nextEvent: function() { | ||
@@ -141,4 +145,4 @@ if (isSuspended()) return 'suspended'; | ||
ret.off = { | ||
property: 'msg.' + events.off.property, | ||
value: events.off.value || "<none>", | ||
property: (events.off.propertytype || 'msg') + '.' + events.off.property, | ||
value: getValue(events.off) || "<none>", | ||
nextEvent: function() { | ||
@@ -154,4 +158,4 @@ if (config.timerType == '2') return undefined; // Trigger | ||
ret.trigger = { | ||
property: 'msg.' + events.on.property, | ||
value: events.on.value || "<none>", | ||
property: (events.on.propertytype || 'msg') + '.' + events.on.property, | ||
value: getValue(events.on) || "<none>", | ||
nextEvent: function() { | ||
@@ -217,30 +221,50 @@ if (isSuspended()) return 'suspended'; | ||
function log(level, message) { | ||
if (config.debug) level = Math.max(3, level); //Outputs everything in node warn or error. | ||
switch (level) { | ||
case 1: //verbose, ignore | ||
break; | ||
case 2: | ||
node.log(message); // log to node console only | ||
break; | ||
case 3: | ||
node.warn(message); // log to node debug window | ||
break; | ||
default: | ||
node.error(message); //anything above 3. | ||
} | ||
} | ||
function dynamicDuration(property, duration) { | ||
// Return false if not a duration request | ||
if (property != "duration") return true; | ||
if (state) { | ||
// Timer currently 'on' - parse time | ||
var secs = getSeconds(duration); | ||
var offTime = moment(events.on.last.moment).add(secs, 'seconds'); | ||
if (offTime.isBefore(node.now())) { | ||
// New time is before now - need to turn off and schedule | ||
node.log("Live duration change (" + duration + " => " + secs + "s) causes an off-time in the past, sending 'off' event."); | ||
send(events.off); | ||
schedule(events.off); | ||
status(events.off); | ||
if (property == "offtime" && state) { | ||
schedule(events.off, null, true); | ||
} else if (property == "duration") { | ||
if (state) { | ||
// Timer currently 'on' - parse time | ||
var secs = getSeconds(duration); | ||
var offTime = moment(events.on.last.moment).add(secs, 'seconds'); | ||
if (offTime.isBefore(node.now())) { | ||
// New time is before now - need to turn off and schedule | ||
log(2, "Live duration change (" + duration + " => " + secs + "s) causes an off-time in the past, sending 'off' event."); | ||
send(events.off); | ||
schedule(events.off); | ||
} else { | ||
// New time is after now - just update the scheduled off event (and status) | ||
log(2, "Live duration change (" + duration + " => " + secs + "s), rescheduling 'off' time to " + offTime.toString()); | ||
schedule(events.off, null, true); | ||
} | ||
} else { | ||
// New time is after now - just update the scheduled off event (and status) | ||
node.log("Live duration change (" + duration + " => " + secs + "s), rescheduling 'off' time to " + offTime.toString()); | ||
schedule(events.off, null, true); | ||
status(events.on); | ||
// Timer currently 'off', just re-schedule of off event (if an on event is scheduled) | ||
if (!isSuspended()) { | ||
schedule(events.off); | ||
} | ||
} | ||
} else { | ||
// Timer currently 'off', just re-schedule of off event (if an on event is scheduled) | ||
if (!isSuspended()) { | ||
schedule(events.off); | ||
status(events.off); | ||
} | ||
return true; | ||
} | ||
updateStatus(); | ||
// Return false to indicate no bootstrap required | ||
@@ -288,3 +312,3 @@ return false; | ||
send(event); | ||
if (events.on.type != '9' && !isSuspended()) { | ||
if (!isSuspended()) { | ||
// Schedule the next event, if it's not a 'manual' timer | ||
@@ -297,3 +321,3 @@ schedule(event, null, null); | ||
// Update the status/icon | ||
status(event); | ||
updateStatus(); | ||
}; | ||
@@ -303,28 +327,57 @@ return event; | ||
function getValue(event) { | ||
// Parse value to selected format | ||
var tgtValue = event.value; | ||
switch (event.valuetype) { | ||
case 'flow': | ||
tgtValue = node.context().flow.get(tgtValue); | ||
break; | ||
case 'global': | ||
tgtValue = node.context().global.get(tgtValue); | ||
break; | ||
case 'json': | ||
tgtValue = JSON.parse(tgtValue); | ||
break; | ||
case 'bool': | ||
tgtValue = (tgtValue == "true"); | ||
break; | ||
case 'date': | ||
tgtValue = (new Date()).getTime(); | ||
break; | ||
} | ||
return tgtValue; | ||
} | ||
function send(event, manual) { | ||
//node.warn('sending \'' + event.name + '\''); | ||
var msg = {}; | ||
msg.tag = config.tag || 'eztimer'; | ||
if (event.topic) msg.topic = event.topic; | ||
//msg.topic = event.topic || (msg.tag + '.' + event.name.toLowerCase()); | ||
var currPart = msg; | ||
var spl = event.property.split('.'); | ||
for (var i in spl) { | ||
if (i < (spl.length - 1)) { | ||
if (!currPart[spl[i]]) currPart[spl[i]] = {}; | ||
currPart = currPart[spl[i]]; | ||
} else { | ||
if (event.valuetype == 'json') { | ||
currPart[spl[i]] = JSON.parse(event.value); | ||
} else if (event.valuetype == 'bool') { | ||
currPart[spl[i]] = (event.value == "true"); | ||
} else if (event.valuetype == 'date') { | ||
currPart[spl[i]] = (new Date()).getTime(); | ||
} else { | ||
currPart[spl[i]] = event.value; | ||
log(1, 'emitting \'' + event.name + '\' event'); | ||
event.last.moment = node.now(); | ||
if (!event.suppressrepeats || state != event.state) { | ||
// Output value | ||
switch (event.propertytype || 'msg') { | ||
case "flow": | ||
node.context().flow.set(event.property, getValue(event)); | ||
break; | ||
case "global": | ||
node.context().global.set(event.property, getValue(event)); | ||
break; | ||
case "msg": | ||
var msg = {}; | ||
msg.tag = config.tag || 'eztimer'; | ||
if (event.topic) msg.topic = event.topic; | ||
var currPart = msg; | ||
var spl = event.property.split('.'); | ||
for (var i in spl) { | ||
if (i < (spl.length - 1)) { | ||
if (!currPart[spl[i]]) currPart[spl[i]] = {}; | ||
currPart = currPart[spl[i]]; | ||
} else { | ||
currPart[spl[i]] = getValue(event); | ||
} | ||
} | ||
node.send(msg); | ||
break; | ||
} | ||
} | ||
} | ||
event.last.moment = node.now(); | ||
if (!event.suppressrepeats || state != event.state) node.send(msg); | ||
state = event.state; | ||
@@ -336,5 +389,6 @@ } | ||
var now = node.now(); | ||
switch (event.type) { | ||
case '1': //Sun | ||
event.typeName = 'sun'; | ||
var nextDate = new Date(); | ||
@@ -349,3 +403,3 @@ // Get tomorrow's sun data | ||
if (event.timesun != sunTimes[t]) { | ||
node.warn({ "message": 'Sun event (' + event.timesun + ') invalid for chosen lat/long (due to polar proximity). Sun event \'' + sunTimes[t] + '\' has been chosen as the closest valid candidate.', "events": sunCalcTimes}); | ||
log(4, { "message": 'Sun event (' + event.timesun + ') invalid for chosen lat/long (due to polar proximity). Sun event \'' + sunTimes[t] + '\' has been chosen as the closest valid candidate.', "events": sunCalcTimes}); | ||
} | ||
@@ -359,4 +413,10 @@ // Use determined event time | ||
} | ||
break; | ||
case '2': //Time of Day | ||
if (event.timetod == '') { | ||
event.moment = null; | ||
return true; | ||
} | ||
event.typeName = 'time of day'; | ||
var m = node.now().millisecond(0); | ||
@@ -386,10 +446,14 @@ var re = new RegExp(/\d+/g); | ||
case '3': //Duration | ||
event.typeName = 'duration'; | ||
var secs = getSeconds(event.duration); | ||
if (manual) { | ||
if (manual && event.inverse.last.moment) { | ||
//event is manual - schedule based on last 'on' event | ||
event.moment = moment(event.inverse.last.moment).add(secs, 'seconds'); | ||
} else { | ||
} else if (event.inverse.moment) { | ||
// event is auto - schedule based on current 'on' event | ||
event.moment = moment(event.inverse.moment).add(secs, 'seconds'); | ||
} else { | ||
event.moment = null; | ||
return true; | ||
} | ||
@@ -401,3 +465,8 @@ | ||
} | ||
break; | ||
case '9': //Manual | ||
event.typeName = 'manual'; | ||
event.moment = null; | ||
return true; | ||
} | ||
@@ -432,2 +501,5 @@ | ||
// Log event | ||
log(1, "Scheduled '" + event.name + "' (" + event.typeName + ") for " + event.moment.toString()); | ||
// Clear any pending event | ||
@@ -442,21 +514,43 @@ if (event.timeout) clearTimeout(event.timeout); | ||
function status(event, manual) { | ||
manual = manual || (events.on.type == 9) | ||
var data = { | ||
fill: manual ? 'blue' : 'green', | ||
shape: event.shape, | ||
text: {} | ||
function updateStatus() { | ||
var message = null; | ||
// Determine the next event | ||
var nextEvent = null; | ||
switch (config.timerType) { | ||
case "1": // on/off | ||
if (state) { | ||
if (events.off && events.off.moment) nextEvent = events.off; | ||
} else { | ||
if (events.on.moment) nextEvent = events.on; | ||
} | ||
message = { | ||
fill: 'green', | ||
shape: state ? 'dot' : 'ring', | ||
text: state ? events.on.name : events.off.name | ||
}; | ||
if (nextEvent) message.text += ` until ${nextEvent.moment.format(fmt)}`; | ||
break; | ||
case "2": // trigger | ||
if (events.on.moment) nextEvent = events.on; | ||
message = { | ||
fill: 'green', | ||
shape: 'ring', | ||
text: '' | ||
}; | ||
if (nextEvent) message.text = `trigger @ ${nextEvent.moment.format(fmt)}`; | ||
break; | ||
} | ||
if (event.inverse) { | ||
if (event.inverse.moment && event.inverse.moment.isAfter(node.now())) { | ||
//data.text = event.name + (manual ? ' manual' : ' auto') + (isSuspended() ? ' - scheduling suspended' : ` until ${event.inverse.moment.format(fmt)}`); | ||
data.text = event.name + (manual ? ' manual' : ' auto') + ` until ${event.inverse.moment.format(fmt)}`; | ||
if (!nextEvent) { | ||
if (isSuspended()) { | ||
if (!state) message.fill = 'grey'; | ||
message.shape = 'dot'; | ||
message.text += 'scheduling suspended'; | ||
} else { | ||
data.text = event.name + (manual ? ' manual' : ' auto') + (isSuspended() ? ' - scheduling suspended' : ``); | ||
message.text += ', no scheduled event'; | ||
} | ||
} else { | ||
data.text = `trigger @ ${event.moment.format(fmt)}`; | ||
message.text += weekdays().indexOf(true) === -1 ? ' (no weekdays selected) ' : ''; | ||
} | ||
node.status(data); | ||
node.status(message); | ||
} | ||
@@ -473,32 +567,9 @@ | ||
events.on.moment = null; | ||
node.status({ | ||
fill: 'grey', | ||
shape: 'dot', | ||
text: `Scheduling suspended ${ | ||
weekdays().indexOf(true) === -1 ? '(no weekdays selected) ' : '' | ||
}` | ||
}); | ||
updateStatus(); | ||
} | ||
function resume() { | ||
if (events.on.type == '9') return; // Don't do anything when resuming a manual timer | ||
if (schedule(events.on, true) && (!events.off || (events.off && schedule(events.off, true)))) { | ||
const firstEvent = events.off && events.off.moment.isBefore(events.on.moment) ? events.off : events.on; | ||
var message; | ||
if (events.off && events.off.moment) { | ||
message = { | ||
fill: 'yellow', | ||
shape: 'dot', | ||
text: `${firstEvent.name} @ ${firstEvent.moment.format(fmt)}, ${firstEvent.inverse.name} @ ${firstEvent.inverse.moment.format(fmt)}` | ||
} | ||
} else { | ||
message = { | ||
fill: 'green', | ||
shape: 'ring', | ||
text: `trigger @ ${firstEvent.moment.format(fmt)}` | ||
} | ||
} | ||
node.status(message); | ||
} | ||
var on = events.on.type != '9' && schedule(events.on, true); | ||
var off = (!events.off || (events.off && events.off.type != '9' && schedule(events.off, true))); | ||
} | ||
@@ -511,17 +582,29 @@ | ||
resume(); | ||
// Wait 2.5 for startup, then fire PREVIOUS event to ensure we're in the right state. | ||
// Wait 1000ms for startup, then fire PREVIOUS event to ensure we're in the right state. | ||
setTimeout(function() { | ||
if (config.startupMessage && config.startupMessage == true && events.on && events.on.moment && events.off && events.off.moment) { | ||
if (events.on && events.on.moment && events.off && events.off.moment) { | ||
if (events.off.moment.isAfter(events.on.moment)) { | ||
//Next event is ON, send OFF | ||
send(events.off); | ||
if (config.startupMessage && config.startupMessage == true) { | ||
send(events.off); | ||
} else { | ||
state = false; | ||
} | ||
} else { | ||
//Next event is OFF, send ON | ||
if (config.startupMessage && config.startupMessage == true) { | ||
send(events.on); | ||
} else { | ||
state = true; | ||
} | ||
} | ||
} else if (events.on && (!events.off || events.off.type == '9')) { | ||
//Trigger | ||
if (config.startupMessage && config.startupMessage == true) { | ||
send(events.on); | ||
} | ||
} else if (config.startupMessage && config.startupMessage == true && events.on && !events.off) { | ||
//Trigger | ||
send(events.on); | ||
} | ||
}, 2500); | ||
updateStatus(); | ||
}, 1000); | ||
} | ||
@@ -528,0 +611,0 @@ } |
{ | ||
"name": "node-red-contrib-eztimer", | ||
"version": "1.1.6", | ||
"version": "1.2.1", | ||
"description": "A simple-yet-flexible timer/scheduler for node-red", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -119,2 +119,14 @@ # eztimer | ||
# Change Log | ||
## 1.2.0 | ||
* Fixed emitting of `flow` and `global` context values (node wouldn't pick them up as values previously) | ||
* Fixed assignment of `flow` and `global` context values to store correct type. [credit @LorenzKahl](https://github.com/mrgadget/node-red-contrib-eztimer/issues/24). | ||
* Renamed `Input Trigger` off-type to `Manual` to align with the on-type of the same name. | ||
* Fixed status reports for `Manual` off time. [credit @moryoav](https://github.com/mrgadget/node-red-contrib-eztimer/issues/25). | ||
* Permit blank on time - allows for full programmatic usage without errors being displayed. [credit @moryoav](https://github.com/mrgadget/node-red-contrib-eztimer/issues/25). | ||
* Simplified node status, removed auto/manual concept (inherited from parent, didn't really make any sense with the way the node works now). | ||
## 1.1.7 | ||
* Fixed `cancel` to actually work - the node no longer emits the `off` event after a `cancel` call. [credit @svwhisper](https://github.com/mrgadget/node-red-contrib-eztimer/issues/23). | ||
* Added code to support `flow` and `global` contexts as assignment properties. When selected, these do not emit flow message. [credit @LorenzKahl](https://github.com/mrgadget/node-red-contrib-eztimer/issues/22). | ||
## 1.1.6 | ||
@@ -121,0 +133,0 @@ Fixes driven by issue #21 [credit @jazzgil](https://github.com/mrgadget/node-red-contrib-eztimer/issues/21). |
Sorry, the diff of this file is not supported yet
88410
946
202