forked from boghammar/MMM-SL-PublicTransport
-
Notifications
You must be signed in to change notification settings - Fork 1
/
node_helper.js
378 lines (346 loc) · 16.2 KB
/
node_helper.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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
/* node_helper.js
*
* Magic Mirror module - Display public transport in Stockholm/Sweden.
* This module use the API's provided by Trafiklab.
*
* Magic Mirror
* Module: MMM-SL-PublicTransport
*
* Magic Mirror By Michael Teeuw http://michaelteeuw.nl
* MIT Licensed.
*
* Module MMM-SL-PublicTransport By Anders Boghammar
*/
const NodeHelper = require("node_helper");
const request = require("request-promise");
var HttpsProxyAgent = require('https-proxy-agent');
var Url = require('url');
var Departure = require('./departure.js');
var debugMe = false;
module.exports = NodeHelper.create({
// --------------------------------------- Start the helper
start: function () {
//Log.info('Starting helper: '+ this.name);
log('Starting helper: ' + this.name);
this.started = false;
},
// --------------------------------------- Schedule a departure update
scheduleUpdate: function () {
var self = this;
debug('scheduleUpdate=' + self.getNextUpdateInterval());
this.updatetimer = setInterval(function () { // This timer is saved in uitimer so that we can cancel it
self.getDepartures();
}, self.getNextUpdateInterval());
},
// --------------------------------------- Retrive departure info
getDepartures: function () {
var self = this;
clearInterval(this.updatetimer); // Clear the timer so that we can set it again
//debug("stationid is array="+Array.isArray(this.config.stationid));
var Proms = [];
// Loop over all stations
if (this.config.stations !== undefined) {
this.config.stations.forEach(station => {
var P = new Promise((resolve, reject) => {
self.getDeparture(station, resolve, reject);
});
debug('Pushing promise for station ' + station.stationId);
console.log(P);
Proms.push(P);
});
Promise.all(Proms).then(CurrentDeparturesArray => {
debug('all promises resolved '+CurrentDeparturesArray);
self.sendSocketNotification('DEPARTURES', CurrentDeparturesArray); // Send departures to module
}).catch(reason => {
debug('One or more promises rejected '+reason);
self.sendSocketNotification('SERVICE_FAILURE', reason);
});
} else {
debug('Stations not defined ');
self.sendSocketNotification('SERVICE_FAILURE', { resp: { Message: 'config.stations is not defined', StatusCode: 500}});
}
self.scheduleUpdate(); // reinitiate the timer
},
// --------------------------------------- Get departures for one station
// The CurrentDepartures object holds this data
// CurrentDepartures = {
// StationId: string
// LatestUpdate: date, // When the realtime data was updated
// DataAge: ??, // The age of the data in ??
// departures: [dir][deps] // An array of array of Departure objects
// }
getDeparture: function(station, resolve, reject) {
log('Getting departures for station id ' + station.stationId);
var self = this;
// http://api.sl.se/api2/realtimedeparturesV4.<FORMAT>?key=<DIN API NYCKEL>&siteid=<SITEID>&timewindow=<TIMEWINDOW>
var transport = (this.config.SSL ? 'https' : 'http');
var opt = {
uri: transport + '://api.sl.se/api2/realtimedeparturesV4.json',
qs: {
key: this.config.apikey,
siteid: station.stationId,
timewindow: 60
},
json: true
};
// Exclude those types of rides that you are not interested in
if (station.excludeTransportTypes !== undefined && Array.isArray(station.excludeTransportTypes)) {
for (var ix = 0; ix < station.excludeTransportTypes.length; ix++) {
opt.qs[station.excludeTransportTypes[ix]] = false
}
}
if (this.config.proxy !== undefined) {
opt.agent = new HttpsProxyAgent(Url.parse(this.config.proxy));
debug('SL-PublicTransport: Using proxy ' + this.config.proxy);
}
debug('SL-PublicTransport: station id ' + station.stationId + ' Calling ' + opt.uri);
console.log(opt);
request(opt)
.then(function (resp) {
if (resp.StatusCode == 0) {
//console.log(resp);
var CurrentDepartures = {};
var departures = [];
CurrentDepartures.StationId = station.stationId;
CurrentDepartures.StationName = (station.stationName === undefined ? 'NotSet' : station.stationName);
CurrentDepartures.LatestUpdate = resp.ResponseData.LatestUpdate; // Anger när realtidsinformationen (DPS) senast uppdaterades.
CurrentDepartures.DataAge = resp.ResponseData.DataAge; //Antal sekunder sedan tidsstämpeln LatestUpdate.
CurrentDepartures.obtained = new Date(); //When we got it.
self.addDepartures(station, departures, resp.ResponseData.Metros);
self.addDepartures(station, departures, resp.ResponseData.Buses);
self.addDepartures(station, departures, resp.ResponseData.Trains);
self.addDepartures(station, departures, resp.ResponseData.Trams);
self.addDepartures(station, departures, resp.ResponseData.Ships);
//console.log(self.departures);
// Sort on ExpectedDateTime
for (var ix = 0; ix < departures.length; ix++) {
if (departures[ix] !== undefined) {
departures[ix].sort(dynamicSort('ExpectedDateTime'))
}
}
//console.log(departures);
// Add the sorted arrays into one array
var temp = []
for (var ix = 0; ix < departures.length; ix++) {
if (departures[ix] !== undefined) {
for (var iy = 0; iy < departures[ix].length; iy++) {
temp.push(departures[ix][iy]);
}
}
}
//console.log(temp);
// TODO:Handle resp.ResponseData.StopPointDeviations
CurrentDepartures.departures = temp;
log('Found ' + CurrentDepartures.departures.length + ' DEPARTURES for station id=' + station.stationId);
resolve(CurrentDepartures);
} else {
log('Something went wrong: station id=' + station.stationId + ' StatusCode: ' + resp.StatusCode + ' Msg: ' + resp.Message);
reject(resp);
}
})
.catch(function (err) {
log('Problems: station id=' + station.stationId + ' ' + err);
reject( { resp: { StatusCode: 600, Message: err } });
});
},
// --------------------------------------- Add departures to our departures array
addDepartures: function (station, departures, depArray) {
for (var ix = 0; ix < depArray.length; ix++) {
var element = depArray[ix];
var dep = new Departure(element);
//debug("BLine: " + dep.LineNumber);
dep = this.fixJourneyDirection(station, dep);
if (this.isWantedLine(station, dep)) {
if (this.isWantedDirection(dep.JourneyDirection)) { // TODO not needed, remove
debug("Adding Line: " + dep.LineNumber + " Dir:" + dep.JourneyDirection + " Dst:" + dep.Destination);
if (departures[dep.JourneyDirection] === undefined) {
departures[dep.JourneyDirection] = [];
}
departures[dep.JourneyDirection].push(dep);
}
}
}
},
// --------------------------------------- Are we asking for this direction
isWantedDirection: function (dir) {
if (this.config.direction !== undefined && this.config.direction != '') {
return dir == this.config.direction;
}
return true;
},
// --------------------------------------- If we want to change direction number on a line
fixJourneyDirection: function (station, dep) {
var swapper = [0, 2, 1];
if (station.lines !== undefined && Array.isArray(station.lines)) {
for (var il=0; il < station.lines.length; il++) { // Check if this is a line we have defined
if (dep.LineNumber == station.lines[il].line) {
debug("Checking direction for line "+ dep.LineNumber + " Dir: " + dep.JourneyDirection);
if (station.lines[il].swapDir !== undefined && station.lines[il].swapDir) {
var newdir = swapper[dep.JourneyDirection];
debug("Swapping direction for line "+ dep.LineNumber + " From: " + dep.JourneyDirection + " To: " + newdir);
dep.JourneyDirection = newdir;
}
}
}
}
return dep;
},
// --------------------------------------- Are we asking for this line in this direction
isWantedLine: function (station, dep) {
//debug('0 ')
if (station.lines !== undefined) {
if (Array.isArray(station.lines)) {
//debug('1')
for (var il=0; il < station.lines.length; il++) { // Check if this is a line we want
//debug('2 '+ il)
//debug(typeof(dep.LineNumber) === 'string');
//debug(typeof(dep.LineNumber));
l1 = (typeof(dep.LineNumber) === 'string' ? dep.LineNumber.toUpperCase() : dep.LineNumber);
l2 = (typeof(station.lines[il].line) === 'string' ? station.lines[il].line.toUpperCase() : station.lines[il].line);
debug("Lines "+ dep.LineNumber + " checked against "+ station.lines[il].line);
//debug("Lines t "+ typeof(dep.LineNumber) + " checked against t "+ typeof(station.lines[il].line));
if (l1 == l2) {
debug("Checking line "+ dep.LineNumber + " Dir: " + dep.JourneyDirection)
if (station.lines[il].direction !== undefined) {
if (dep.JourneyDirection == station.lines[il].direction) {
return true;
} else {
return false;
}
} else {
return true; // We take all directions for this line
}
}
}
return false;
} else {
log('Problems: station id=' + station.stationId + ' lines is defined but not as array.');
throw new Error('station id=' + station.stationId + ' lines is defined but not as array.')
}
} else {
return true; // Take all lines on this station
}
},
// --------------------------------------- Are we asking for this direction
isWantedLineXXX: function (line) {
if (this.config.lines !== undefined) {
if (this.config.lines.length > 0) {
for (var ix = 0; ix < this.config.lines.length; ix++) {
// Handle objects in lines
if (line == this.getLineNumber(ix)) return true;
}
} else return true; // Its defined but does not contain anything = we want all lines
} else return true; // Its undefined = we want all lines
return false;
},
// --------------------------------------- Get the line number of a lines entry
getLineNumber: function (ix) {
var wasarray = false;
var ll = this.config.lines[ix];
if (Array.isArray(ll)) { //ll !== null && typeof ll === 'array') {
ll = ll[0];
wasarray = true;
}
//debug("IX: "+ ix + " LL:" + ll + " wasarray " + wasarray);
return ll;
},
// --------------------------------------- Figure out the next update time
getNextUpdateInterval: function() {
if (this.config.highUpdateInterval === undefined) return this.config.updateInterval;
// TODO: dont throw here use the normal update time but log the errors
if (this.config.highUpdateInterval.times === undefined) {
log("ERROR: highUpdateInterval.times is undefined in configuration.");
log("ERROR: Please remove the highUpdateInterval parameter if you do not use it.");
return this.config.updateInterval;
}
if (!Array.isArray(this.config.highUpdateInterval.times)) throw new Error("highUpdateInterval.times is not an array")
//Check which interval we are in and return the proper timer
for (let i = 0; i < this.config.highUpdateInterval.times.length; i++) {
const intervalDefinition = this.config.highUpdateInterval.times[i];
if (this.isBetween(intervalDefinition.days, intervalDefinition.start, intervalDefinition.stop)) {
if (intervalDefinition.updateInterval !== undefined) {
// If the interval has it's own update frequency defined, use that
return intervalDefinition.updateInterval
}
// If the interval doesn't have it own update frequency defined, use the high interval frequency.
return this.config.highUpdateInterval.updateInterval
}
}
return this.config.updateInterval;
},
// --------------------------------------- Check if now is in this time
isBetween: function (days, start, stop) {
var now = new Date();
var dow = now.getDay();
switch (days) {
case 'weekdays':
if (0 < dow && dow < 6) {
return this.isTimeBetween(start, stop);
}
break;
case 'weekends':
if (0 == dow || dow == 6) {
return this.isTimeBetween(start, stop);
}
break;
}
return false;
},
// --------------------------------------- Check if now is between these times
isTimeBetween: function (start, stop) {
var now = new Date();
var st = dateObj(start);
var en = dateObj(stop);
if (st > en) { // check if start comes before end
var temp = st; // if so, assume it's across midnight
st = en; // and swap the dates
en = temp;
}
return now < en && now >st
},
// --------------------------------------- Handle notifocations
socketNotificationReceived: function (notification, payload) {
const self = this;
if (notification === 'CONFIG' /*&& this.started == false*/) {
this.config = payload;
this.started = true;
debugMe = this.config.debug;
self.scheduleUpdate();
self.getDepartures(); // Get it first time
};
}
});
//
// Utilities
//
function dynamicSort(property) {
var sortOrder = 1;
if (property[0] === "-") {
sortOrder = -1;
property = property.substr(1);
}
return function (a, b) {
var result = (a[property] < b[property]) ? -1 : (a[property] > b[property]) ? 1 : 0;
return result * sortOrder;
}
}
// --------------------------------------- Create a date object with the time in timeStr (hh:mm)
function dateObj(timeStr) {
var parts = timeStr.split(':');
var date = new Date();
date.setHours(+parts.shift());
date.setMinutes(+parts.shift());
return date;
}
// --------------------------------------- At beginning of log entries
function logStart() {
return (new Date(Date.now())).toLocaleTimeString() + " MMM-SL-PublicTransport: ";
}
// --------------------------------------- Logging
function log(msg) {
console.log(logStart() + msg);
}
// --------------------------------------- Debugging
function debug(msg) {
if (debugMe) log(msg);
}