node-red-contrib-timerswitch
Version:
An easy, intuitive and precise node-red timer that allows you to turn something on/off multiple times a day and supports schedules as low as 1 second.
394 lines (330 loc) • 9.6 kB
JavaScript
module.exports = function() {
'use strict';
/* the scheduler object */
var scheduler = {
schedules: [],
events: {},
_state: false,
_paused: false,
_disabled: false,
_override: false
};
var self;
scheduler.create = function (config) {
var active;
self = this;
/* clear the current schedules */
self.clear();
self._disabled = false;
self._state = false;
/* set initial state to off */
scheduler.state('off');
for (var i = 0; i < config.length; i++) {
var c = config[i];
/* skip invalid schedules */
if (!c.valid) continue;
/* build date objects */
var on = self.date(c.on_h, c.on_m, c.on_s);
var off = self.date(c.off_h, c.off_m, c.off_s);
var now = new Date();
active = self.between(now, on, off);
/* if currently active, but the on time is in the future (meaning it started yesterday), move on
time to the past so our event handler starts immediately */
if (active && on.getTime() > now.getTime()) {
on.setHours(on.getHours() - 24);
}
/* if not currently active and on is below current time, then both are below current time so add 24 to both */
if (!active && on.getTime() < now.getTime()) {
on.setHours(on.getHours() + 24);
off.setHours(off.getHours() + 24);
}
/* if off is still below current time, then move off up 24 hours */
if (off.getTime() < now.getTime()) {
off.setHours(off.getHours() + 24);
}
/* push the schedule to the scheduler */
self.add({
id: i,
on: on,
off: off,
active: active,
events: {}
});
}
};
/*
* start up the scheduler
*/
scheduler.start = function () {
if (self.disabled()) return;
if (self.count() < 1) return;
/* foreach scheduler fire off events */
self.get().forEach(function (s, i) {
var start;
/* if the schedule is currently active, set the scheduler to on */
if (s.active) self.state('on');
/* start event */
self.registerEvent(i, 'start', setTimeout(self.turnon, self.until(s.on), s, i));
});
self.startMidnightEvent();
};
/**
* fire off event that alters the status every night at 00:00:00 to get rid of the +1
*/
scheduler.startMidnightEvent = function () {
var midnight = new Date();
midnight.setHours(24,0,1,0);
self.events.midnight = setTimeout(self.midnight, self.until(midnight));
};
/**
* at midnight ring the alarm so we update the status
*/
scheduler.midnight = function() {
self.alarm();
self.startMidnightEvent();
};
/**
* get one or all schedules
*
* @param i
* @returns {*}
*/
scheduler.get = function (i) {
if (typeof i === 'undefined') return self.schedules;
return self.schedules[i];
};
/* set or get the current state */
scheduler.state = function (state) {
if (typeof state === 'undefined') return self._state;
self._state = state;
};
/**
* add a schedule
*
* @param schedule
*/
scheduler.add = function (schedule) {
self.schedules.push(schedule);
};
/**
* clear the scheduler
*/
scheduler.clear = function () {
self.schedules = [];
};
/**
* disable the scheduler
*/
scheduler.disable = function () {
self._disabled = true;
};
/**
* check if scheduler is disabled
*
* @returns {*}
*/
scheduler.disabled = function () {
return self._disabled;
};
/**
* pause the scheduler
*/
scheduler.pause = function () {
self._paused = true;
};
/**
* unpause the scheduler
*/
scheduler.resume = function () {
self._paused = false;
self._override = false;
var s = self.current();
if (s.active) {
self.state('on');
} else {
self.state('off');
}
};
/**
* check if the scheduler is paused
*
* @returns {boolean|scheduler.paused|*}
*/
scheduler.paused = function () {
return self._paused;
};
/**
* set the state manually
* @param state
*/
scheduler.manual = function (state) {
if (typeof state === 'undefined') return self._override;
self._state = state;
self._override = true;
};
/**
* set an alarm handler function to tell the main app there is an on/off event
*
* @param alarm
*/
scheduler.register = function (alarm) {
self.alarm = alarm;
};
/**
* register an event with a scheduler so we can clear them later
*/
scheduler.registerEvent = function (i, type, event) {
self.schedules[i]['events'][type] = event;
};
/* time support methods */
/**
* create a date objects
* @param d_h
* @param d_m
* @param d_s
*/
scheduler.date = function (d_h, d_m, d_s) {
var d = new Date();
d.setHours(d_h);
d.setMinutes(d_m);
d.setSeconds(d_s);
return d;
};
/**
* how many milliseconds until the specified time
* @param d
* @returns {number}
*/
scheduler.until = function (d) {
var now = new Date();
return d.getTime() - now.getTime();
};
/**
* how many milliseconds between 2 times
* @returns {number}
* @param d1
* @param d2
*/
scheduler.duration = function (d1, d2) {
return d2.getTime() - d1.getTime();
};
/**
* check if a time is between 2 times
* if end is before start, swap the times and take the inverse
*
* @param time
* @param start
* @param end
* @returns {boolean}
*/
scheduler.between = function (time, start, end) {
if (start.getTime() <= end.getTime()) {
return time.getTime() >= start.getTime() && time.getTime() < end.getTime();
} else {
return !(time.getTime() >= end.getTime() && time.getTime() < start.getTime());
}
};
/**
* check if d1 is earlier than d2
*
* @param d1
* @param d2
* @returns {boolean}
*/
scheduler.earlier = function (d1, d2) {
if (d1.getHours() < d2.getHours()) return true;
if (d1.getHours() > d2.getHours()) return false;
if (d1.getMinutes() < d2.getMinutes()) return true;
if (d1.getMinutes() > d2.getMinutes()) return false;
if (d1.getSeconds() < d2.getSeconds()) return true;
return false;
};
/**
* set the scheduler to the next day
* @param i
*/
scheduler.addDay = function (i) {
var s = self.schedules[i];
s.on.setHours(s.on.getHours() + 24);
s.off.setHours(s.off.getHours() + 24);
};
/**
* get the current schedule. Could be running or the next one.
* @returns {*}
*/
scheduler.current = function () {
var schedule = false;
for (var i = 0; i < self.schedules.length; i++) {
var s = self.schedules[i];
if (s.active) return s;
if (!schedule || s.on.getTime() < schedule.on.getTime()) schedule = s;
}
return schedule;
}
/**
* find the next schedule to run
*
* @returns {boolean}
*/
scheduler.next = function () {
var next = false;
self.schedules.forEach(function (s) {
next = (!next || s.on.getTime() < next.on.getTime()) ? s : next;
});
return next;
};
/**
* return the number of schedules
*
* @returns {Number}
*/
scheduler.count = function () {
return self.schedules.length;
};
scheduler.setActive = function (i) {
for (var j = 0; j < self.schedules.length; j++) {
var s = self.schedules[j];
s.active = (i == j) ? true : false;
}
}
/**
*
* @param s
* @param i
*/
scheduler.turnon = function (s, i) {
if (!self.paused()) self.state('on');
/* fire OFF event */
self.registerEvent(i, 'end', setTimeout(self.turnoff, self.until(s.off), s, i));
/* set the schedule to the next day */
self.addDay(i);
/* get the new schedule date */
s = self.get(i);
/* fire ON event */
self.registerEvent(i, 'start', setTimeout(self.turnon, self.until(s.on), s, i));
/* set the active schedule */
self.setActive(i);
if (!self.paused()) {
/* timer has taken over again */
self._override = false;
/* send an alarm */
self.alarm();
}
};
/**
* this is called by the OFF setTimeout
* @param s
* @param i
*/
scheduler.turnoff = function (s, i) {
self.schedules[i].active = false;
if (!self.paused()) {
self.state('off');
/* timer has taken over again */
self._override = false;
/* send an alarm */
self.alarm();
}
};
return scheduler;
};