node-red-contrib-schedex
Advanced tools
Comparing version 0.8.1 to 1.0.0
124
index.js
@@ -36,6 +36,17 @@ /** | ||
var node = this, | ||
events = {on: setupEvent('on', 'dot'), off: setupEvent('off', 'ring')}; | ||
events = { on: setupEvent('on', 'dot'), off: setupEvent('off', 'ring') }; | ||
events.on.inverse = events.off; | ||
events.off.inverse = events.on; | ||
// migration code : if new values are undefined, set all to true | ||
if (config.sun === undefined && config.mon === undefined && config.tue === undefined && | ||
config.wed === undefined && config.thu === undefined && config.fri === undefined && | ||
config.sat === undefined) { | ||
const name = config.name || config.ontime + ' - ' + config.offtime; | ||
node.warn('Schedex [' + name + ']: New weekday configuration attributes are not defined, please edit the node. Defaulting to true.'); | ||
config.sun = config.mon = config.tue = config.wed = config.thu = config.fri = config.sat = true; | ||
} | ||
var weekdays = [config.mon, config.tue, config.wed, config.thu, config.fri, config.sat, config.sun]; | ||
node.on('input', function (msg) { | ||
@@ -59,10 +70,10 @@ var handled = false, requiresBootstrap = false; | ||
} | ||
eachProp(function (eventName, msgProperty, typeConstructor) { | ||
var prop = eventName + msgProperty; | ||
enumerateOnOffEvents(function (eventType, eventName, eventNameTypeConverter) { | ||
var prop = eventType + eventName; | ||
var match = new RegExp('.*' + prop + '\\s+(\\S+)').exec(msg.payload); | ||
if (match) { | ||
handled = true; | ||
var previous = events[eventName][msgProperty]; | ||
events[eventName][msgProperty] = typeConstructor(match[1]); | ||
requiresBootstrap = requiresBootstrap || previous !== events[eventName][msgProperty]; | ||
var previous = events[eventType][eventName]; | ||
events[eventType][eventName] = eventNameTypeConverter(match[1]); | ||
requiresBootstrap = requiresBootstrap || previous !== events[eventType][eventName]; | ||
} | ||
@@ -78,9 +89,9 @@ }); | ||
} | ||
eachProp(function (eventName, msgProperty, typeConstructor) { | ||
var prop = eventName + msgProperty; | ||
enumerateOnOffEvents(function (eventType, eventName, eventNameTypeConverter) { | ||
var prop = eventType + eventName; | ||
if (msg.payload.hasOwnProperty(prop)) { | ||
handled = true; | ||
var previous = events[eventName][msgProperty]; | ||
events[eventName][msgProperty] = typeConstructor(msg.payload[prop]); | ||
requiresBootstrap = requiresBootstrap || previous !== events[eventName][msgProperty]; | ||
var previous = events[eventType][eventName]; | ||
events[eventType][eventName] = eventNameTypeConverter(msg.payload[prop]); | ||
requiresBootstrap = requiresBootstrap || previous !== events[eventType][eventName]; | ||
} | ||
@@ -90,3 +101,3 @@ }); | ||
if (!handled) { | ||
node.status({fill: 'red', shape: 'dot', text: 'Unsupported input'}); | ||
node.status({ fill: 'red', shape: 'dot', text: 'Unsupported input' }); | ||
} else if (requiresBootstrap) { | ||
@@ -116,3 +127,3 @@ bootstrap(); | ||
function send(event, manual) { | ||
node.send({topic: event.topic, payload: event.payload}); | ||
node.send({ topic: event.topic, payload: event.payload }); | ||
node.status({ | ||
@@ -129,4 +140,4 @@ fill: manual ? 'blue' : 'green', | ||
if (matches && matches.length) { | ||
// Don't use 'now' here as hour and minute mutate the moment. | ||
event.moment = moment().hour(matches[1]).minute(matches[2]); | ||
// Don't use existing 'now' moment here as hour and minute mutate the moment. | ||
event.moment = moment().hour(+matches[1]).minute(+matches[2]); | ||
} else { | ||
@@ -139,24 +150,30 @@ var sunCalcTimes = SunCalc.getTimes(new Date(), config.lat, config.lon); | ||
} | ||
if (event.moment) { | ||
event.moment.seconds(0); | ||
if (event.offset) { | ||
var adjustment = event.offset; | ||
if (event.randomoffset) { | ||
adjustment = event.offset * Math.random(); | ||
} | ||
event.moment.add(adjustment, 'minutes'); | ||
} | ||
if (!event.moment) { | ||
node.status({ fill: 'red', shape: 'dot', text: 'Invalid time: ' + event.time }); | ||
return false; | ||
} | ||
event.moment.seconds(0); | ||
if (!isInitial || isInitial && now.isAfter(event.moment)) { | ||
event.moment.add(1, 'day'); | ||
} | ||
if (!isInitial || isInitial && now.isAfter(event.moment)) { | ||
event.moment.add(1, 'day'); | ||
if (event.offset) { | ||
var adjustment = event.offset; | ||
if (event.randomoffset) { | ||
adjustment = event.offset * Math.random(); | ||
} | ||
event.moment.add(adjustment, 'minutes'); | ||
} | ||
var delay = event.moment.diff(now); | ||
if (event.timeout) { | ||
clearTimeout(event.timeout); | ||
} | ||
event.timeout = setTimeout(event.callback, delay); | ||
} else { | ||
node.status({fill: 'red', shape: 'dot', text: 'Invalid time: ' + event.time}); | ||
// Adjust weekday if not selected | ||
while (!weekdays[event.moment.isoWeekday() - 1]) { | ||
event.moment.add(1, 'day'); | ||
} | ||
var delay = event.moment.diff(now); | ||
if (event.timeout) { | ||
clearTimeout(event.timeout); | ||
} | ||
event.timeout = setTimeout(event.callback, delay); | ||
return true; | ||
} | ||
@@ -167,16 +184,20 @@ | ||
clearTimeout(events.off.timeout); | ||
node.status({fill: 'grey', shape: 'dot', text: 'Scheduling suspended - manual mode only'}); | ||
node.status({ | ||
fill: 'grey', | ||
shape: 'dot', | ||
text: 'Scheduling suspended ' + (weekdays.indexOf(true) === -1 ? '(no weekdays selected) ' : '') + '- manual mode only' | ||
}); | ||
} | ||
function resume() { | ||
schedule(events.on, true); | ||
schedule(events.off, true); | ||
var firstEvent = events.on.moment.isBefore(events.off.moment) ? events.on : events.off; | ||
var message = firstEvent.name + ' ' + firstEvent.moment.format(fmt) + ', ' + | ||
firstEvent.inverse.name + ' ' + firstEvent.inverse.moment.format(fmt); | ||
node.status({fill: 'yellow', shape: 'dot', text: message}); | ||
if (schedule(events.on, true) && schedule(events.off, true)) { | ||
var firstEvent = events.on.moment.isBefore(events.off.moment) ? events.on : events.off; | ||
var message = firstEvent.name + ' ' + firstEvent.moment.format(fmt) + ', ' + | ||
firstEvent.inverse.name + ' ' + firstEvent.inverse.moment.format(fmt); | ||
node.status({ fill: 'yellow', shape: 'dot', text: message }); | ||
} | ||
} | ||
function bootstrap() { | ||
if (config.suspended) { | ||
if (config.suspended || weekdays.indexOf(true) === -1) { | ||
suspend(); | ||
@@ -188,9 +209,10 @@ } else { | ||
function eachProp(callback) { | ||
Object.keys(events).forEach(function (eventName) { | ||
callback(eventName, 'time', String); | ||
callback(eventName, 'topic', String); | ||
callback(eventName, 'payload', String); | ||
callback(eventName, 'offset', Number); | ||
callback(eventName, 'randomoffset', toBoolean); | ||
function enumerateOnOffEvents(callback) { | ||
// The keys here will be ['on', 'off'] | ||
Object.keys(events).forEach(function (eventType) { | ||
callback(eventType, 'time', String); | ||
callback(eventType, 'topic', String); | ||
callback(eventType, 'payload', String); | ||
callback(eventType, 'offset', Number); | ||
callback(eventType, 'randomoffset', toBoolean); | ||
}); | ||
@@ -203,4 +225,8 @@ } | ||
node.schedexEvents = function () { | ||
return events; | ||
}; | ||
bootstrap(); | ||
}); | ||
}; | ||
}; |
{ | ||
"name": "node-red-contrib-schedex", | ||
"version": "0.8.1", | ||
"version": "1.0.0", | ||
"description": "", | ||
@@ -14,3 +14,3 @@ "main": "index.js", | ||
"scripts": { | ||
"test": "node_modules/.bin/mocha -R spec ./tests/test.js", | ||
"test": "nyc --reporter=html node_modules/.bin/mocha -R spec ./tests/test.js", | ||
"readme2html": "node_modules/.bin/markdown README.md -f gfm" | ||
@@ -33,6 +33,9 @@ }, | ||
"devDependencies": { | ||
"chai": "^3.5.0", | ||
"chai": "^4.0.1", | ||
"eslint": "^3.19.0", | ||
"eslint-config-biddster": "^0.3.0", | ||
"markdown-to-html": "0.0.13", | ||
"mocha": "^3.1.2", | ||
"node-red-contrib-mock-node": "^0.2.0" | ||
"mocha": "^3.4.2", | ||
"node-red-contrib-mock-node": "^0.3.0", | ||
"nyc": "^11.0.2" | ||
}, | ||
@@ -43,3 +46,11 @@ "node-red": { | ||
} | ||
}, | ||
"eslintConfig": { | ||
"env": { | ||
"es6": true, | ||
"node": true, | ||
"mocha": true | ||
}, | ||
"extends": "eslint-config-biddster/es6-node" | ||
} | ||
} |
@@ -8,7 +8,8 @@ # Schedex | ||
__NOTE: When upgrading from versions prior to 1.0.0, you will see a message for each Schedex node in the Node-RED debug window. This message is to advise that the Schedex configuration changed slightly in version 1.0.0 | ||
in order to accomodate days of the week when scheduling. To remedy, simply edit each Schedex node, tick the days of the week you want Schedex enabled and re-deploy.__ | ||
# Installation | ||
Change directory to your node red installation: | ||
$ cd ~/.node-red | ||
$ npm install node-red-contrib-schedex | ||
@@ -18,2 +19,7 @@ | ||
## Schedule | ||
The scheduling days allow you to choose which days of the week to schedule events. Unticking all days | ||
will suspend scheduling. | ||
## Suspending scheduling | ||
@@ -20,0 +26,0 @@ |
@@ -26,2 +26,3 @@ /** | ||
"use strict"; | ||
var assert = require('chai').assert; | ||
@@ -36,18 +37,121 @@ var _ = require('lodash'); | ||
it('should schedule initially', function () { | ||
var node = mock(nodeRedModule, { | ||
onTime: '11:45', | ||
onTopic: 'on', | ||
onPayload: 'on payload', | ||
offTime: '11:48', | ||
offTopic: 'off', | ||
offPayload: 'off payload', | ||
lat: 51.33411, | ||
lon: -0.83716, | ||
unitTest: true | ||
}); | ||
var node = newNode(); | ||
assert.strictEqual(node.schedexEvents().on.time, '11:45'); | ||
assert.strictEqual(node.schedexEvents().off.time, 'dawn'); | ||
// TODO - actually do something here. | ||
node.emit('input', { payload: 'on' }); | ||
assert.strictEqual(node.sent(0).payload, 'on payload'); | ||
assert.strictEqual(node.sent(0).topic, 'on topic'); | ||
// assert.strictEqual(2881, node.messages().length); | ||
node.emit('input', { payload: 'off' }); | ||
assert.strictEqual(node.sent(1).payload, 'off payload'); | ||
assert.strictEqual(node.sent(1).topic, 'off topic'); | ||
}); | ||
it('should handle programmatic scheduling', function () { | ||
var node = newNode(); | ||
node.emit('input', { payload: 'ontime 11:12' }); | ||
assert.strictEqual(node.schedexEvents().on.time, '11:12'); | ||
node.emit('input', { payload: { ontime: '23:12' } }); | ||
assert.strictEqual(node.schedexEvents().on.time, '23:12'); | ||
node.emit('input', { payload: 'offtime 10:12' }); | ||
assert.strictEqual(node.schedexEvents().off.time, '10:12'); | ||
node.emit('input', { payload: { offtime: '22:12' } }); | ||
assert.strictEqual(node.schedexEvents().off.time, '22:12'); | ||
}); | ||
it('should indicate bad programmatic input', function () { | ||
var node = newNode(); | ||
node.emit('input', { payload: 'wibble' }); | ||
assert.strictEqual(node.status().text, 'Unsupported input'); | ||
node.status().text = ''; | ||
node.emit('input', { payload: '4412' }); | ||
assert.strictEqual(node.status().text, 'Unsupported input'); | ||
}); | ||
it('should indicate bad configuration', function () { | ||
var node = newNode({ ontime: '5555' }); | ||
assert.strictEqual(node.status().text, 'Invalid time: 5555'); | ||
}); | ||
it('should suspend initially', function () { | ||
var node = newNode({ suspended: true }); | ||
assert(node.status().text.indexOf('Scheduling suspended') === 0); | ||
}); | ||
it('should suspend if all weekdays are unticked and disabled', function () { | ||
var config = _.zipObject(['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'], _.times(7, () => false)); | ||
var node = newNode(config); | ||
assert(node.status().text.indexOf('Scheduling suspended') === 0); | ||
}); | ||
it('should suspend programtically', function () { | ||
var node = newNode(); | ||
node.emit('input', { payload: { suspended: true } }); | ||
assert(node.status().text.indexOf('Scheduling suspended') === 0); | ||
node = newNode(); | ||
node.emit('input', { payload: 'suspended true' }); | ||
assert(node.status().text.indexOf('Scheduling suspended') === 0); | ||
}); | ||
it('should handle day configuration', function () { | ||
var now = moment(); | ||
// Start by disabling today in the configuration. | ||
var config = _.zipObject(['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'], _.times(7, index => now.isoWeekday() !== (index + 1))); | ||
// Make sure we schedule 'on' for today by making the time after now. That way, disabling | ||
// today in the config will force the 'on' to be tomorrow and we can assert it. | ||
config.ontime = moment().add(1, 'minute').format('HH:mm'); | ||
var node = newNode(config); | ||
assert.strictEqual(node.schedexEvents().on.moment.isoWeekday(), now.add(1, 'day').isoWeekday()); | ||
}); | ||
it('should send something when tiggered', function (done) { | ||
this.timeout(60000 * 5); | ||
console.log('This test will take 3 minutes, please wait...'); | ||
var ontime = moment().add(1, 'minute').format('HH:mm'); | ||
var offtime = moment().add(2, 'minute').format('HH:mm'); | ||
var node = newNode({ ontime: ontime, offtime: offtime, offoffset: 0, offrandomoffset: '0' }); | ||
setTimeout(function () { | ||
assert.strictEqual(node.sent(0).payload, 'on payload'); | ||
assert.strictEqual(node.sent(0).topic, 'on topic'); | ||
assert.strictEqual(node.sent(1).payload, 'off payload'); | ||
assert.strictEqual(node.sent(1).topic, 'off topic'); | ||
done(); | ||
}, 60000 * 3); | ||
}); | ||
it('should send something after programmatic configuration when tiggered', function (done) { | ||
this.timeout(60000 * 5); | ||
console.log('This test will take 3 minutes, please wait...'); | ||
var ontime = moment().add(1, 'minute').format('HH:mm'); | ||
var offtime = moment().add(2, 'minute').format('HH:mm'); | ||
var node = newNode({ offoffset: 0, offrandomoffset: '0' }); | ||
node.emit('input', { payload: 'ontime ' + ontime }); | ||
node.emit('input', { payload: 'offtime ' + offtime }); | ||
setTimeout(function () { | ||
assert.strictEqual(node.sent(0).payload, 'on payload'); | ||
assert.strictEqual(node.sent(0).topic, 'on topic'); | ||
assert.strictEqual(node.sent(1).payload, 'off payload'); | ||
assert.strictEqual(node.sent(1).topic, 'off topic'); | ||
done(); | ||
}, 60000 * 3); | ||
}); | ||
}); | ||
function newNode(configOverrides) { | ||
var config = { | ||
suspended: false, | ||
ontime: '11:45', | ||
ontopic: 'on topic', | ||
onpayload: 'on payload', | ||
onoffset: '', | ||
onrandomoffset: 0, | ||
offtime: 'dawn', | ||
offtopic: 'off topic', | ||
offpayload: 'off payload', | ||
offoffset: '5', | ||
offrandomoffset: 1, | ||
lat: 51.33411, | ||
lon: -0.83716, | ||
unittest: true | ||
}; | ||
if (configOverrides) { | ||
_.assign(config, configOverrides); | ||
} | ||
return mock(nodeRedModule, config); | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
39380
337
1
113
7