forked from niklaswall/node-red-contrib-light-scheduler
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlight-scheduler.js
154 lines (121 loc) · 4.87 KB
/
light-scheduler.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
module.exports = function(RED) {
"use strict";
var path = require('path');
var req = require('request');
var util = require('util');
var scheduler = require('./lib/scheduler.js');
var isItDark = require('./lib/isitdark.js');
var LightScheduler = function(n) {
RED.nodes.createNode(this, n);
this.settings = RED.nodes.getNode(n.settings); // Get global settings
this.events = JSON.parse(n.events);
this.runningEvents = JSON.parse(n.events); // With possible randomness.
this.topic = n.topic;
this.onPayload = n.onPayload;
this.onPayloadType = n.onPayloadType;
this.offPayload = n.offPayload;
this.offPayloadType = n.offPayloadType;
this.onlyWhenDark = n.onlyWhenDark;
this.sunElevationThreshold = n.sunElevationThreshold ? n.sunElevationThreshold : 6;
this.sunShowElevationInStatus = n.sunShowElevationInStatus | false;
this.outputfreq = n.outputfreq ? n.outputfreq : 'output.statechange.startup';
this.scheduleRndMax = !isNaN(parseInt(n.scheduleRndMax)) ? parseInt(n.scheduleRndMax) : 0;
this.scheduleRndMax = Math.max(Math.min(this.scheduleRndMax, 60), 0); // 0 -> 60 allowed
this.override = 'auto';
this.prevPayload = null;
var node = this;
function randomizeSchedule() {
function offsetMOD() {
var min = 0 - node.scheduleRndMax;
var max = node.scheduleRndMax;
return Math.floor(Math.random() * (max - min) + min);
}
node.runningEvents = node.events.map((event) => {
var minutes = event.end.mod - event.start.mod;
event.start.mod = event.start.mod + offsetMOD();
event.end.mod = event.end.mod + offsetMOD();
if(event.end.mod <= event.start.mod) // Handle "too short events"
event.end.mod = event.start.mod + minutes; // Events can overlap, but we don't need to care about that
return event;
});
}
function setState(out) {
var msg = {
topic: node.topic,
};
if(out)
msg.payload = RED.util.evaluateNodeProperty(node.onPayload, node.onPayloadType, node, msg);
else
msg.payload = RED.util.evaluateNodeProperty(node.offPayload, node.offPayloadType, node, msg);
var sunElevation = '';
if(node.sunShowElevationInStatus) {
sunElevation = ' Sun: ' + isItDark.getElevation(node).toFixed(1) + '°';
}
var overrideTxt = node.override == 'auto'?'':' Override: ' + node.override;
node.status({fill: out?"green":"red", shape: "dot", text: (out ? 'ON' : 'OFF') + sunElevation + overrideTxt});
// Only send anything if the state have changed.
if(node.outputfreq == 'output.minutely' || msg.payload !== node.prevPayload)
{
node.send(msg);
node.prevPayload = msg.payload;
}
}
function evaluate() {
// Handle override state, if any.
if(node.override == 'stop') {
node.status({fill: "gray", shape: "dot", text: 'Override: Stopped!'});
return;
}
if(node.override == 'on')
return setState(true);
if(node.override == 'off')
return setState(false);
var matchEvent = scheduler.matchSchedule(node);
if(node.override == 'schedule-only')
return setState(matchEvent);
if(node.override == 'light-only')
return setState(isItDark.isItDark(node));
// node.override == auto
if(!matchEvent)
return setState(false);
if(node.onlyWhenDark)
return setState(isItDark.isItDark(node));
return setState(true);
}
node.on('input', function(msg) {
msg.payload = msg.payload.toString(); // Make sure we have a string.
if(msg.payload.match(/^(1|on|0|off|auto|stop|schedule-only|light-only)$/i))
{
if(msg.payload == '0') msg.payload = 'off';
if(msg.payload == '1') msg.payload = 'on';
node.override = msg.payload.toLowerCase();
//console.log("Override: " + node.override);
}
else
node.warn('Failed to interpret incomming msg.payload. Ignoring it!');
evaluate();
});
// re-evaluate every minute
node.evalInterval = setInterval(evaluate, 60000);
// Run initially directly after start / deploy.
if(node.outputfreq != 'output.statechange')
setTimeout(evaluate, 1000);
node.on('close', function() {
clearInterval(node.evalInterval);
clearInterval(node.rndInterval);
});
if(node.scheduleRndMax > 0) {
randomizeSchedule();
// Randomize schedule every hour for now, could cause problems with large scheduleRndMax.
node.rndInterval = setInterval(randomizeSchedule, 1*60*60*1000);
}
};
RED.nodes.registerType("light-scheduler", LightScheduler);
RED.httpAdmin.get('/light-scheduler/js/*', function(req,res) {
var options = {
root: __dirname + '/static/',
dotfiles: 'deny'
};
res.sendFile(req.params[0], options);
});
};