-
Notifications
You must be signed in to change notification settings - Fork 11
/
node_helper.js
290 lines (284 loc) · 12 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
/* node_helper.js
*
* Magic Mirror
* Module: MMM-DCMetroTrainTimes
*
* Magic Mirror By Michael Teeuw http://michaelteeuw.nl
* MIT Licensed.
*
* Module MMM-DCMetroTrainTimes By Adam Moses http://adammoses.com
*/
// call in the required classes
var NodeHelper = require("node_helper");
const https = require('https');
const querystring = require('querystring');
const fs = require('fs');
const errorFailLimit = 5;
// the main module helper create
module.exports = NodeHelper.create({
// subclass start method, clears the initial config array
start: function() {
this.stationInformationList = null;
this.errorCount = 0;
this.stopUpdates = false;
},
// subclass socketNotificationReceived, received notification from module
socketNotificationReceived: function(notification, theConfig) {
if (notification === "REGISTER_CONFIG") {
// create self reference for interval calls
var self = this;
// load in the station information list
this.loadStationInformationList(theConfig.path);
// if config-ed to show incidients, start that up
if (theConfig.showIncidents)
{
this.updateIncidents(theConfig);
setInterval(function() { self.updateIncidents(theConfig); },
theConfig.refreshRateIncidents);
}
// if config-ed to show station train times, start that up
if (theConfig.showStationTrainTimes)
{
// delay the first train times check to not collide with first incident update
setTimeout(function() { self.updateTrainTimes(theConfig); },
1000);
setInterval(function() { self.updateTrainTimes(theConfig); },
theConfig.refreshRateStationTrainTimes);
}
return;
}
},
// increment error count, if passed limit send notice to module
processError: function() {
this.errorCount += 1;
if (this.errorCount >= errorFailLimit)
{
this.sendSocketNotification('DCMETRO_TOO_MANY_ERRORS', {} );
this.stopUpdates = true;
}
},
// --- STATION INFORMATION STUFF ---
// loads the station information list from file
loadStationInformationList: function(path) {
// station list is loaded from file as it is rarely updated
// to force update this please run ./stationcodes/getStationCodes.js
// if it's already been loaded then skip this
if (this.stationInformationList === null) {
var fileData = fs.readFileSync(path + '/stationcodes/stationcodes.json');
this.stationInformationList = JSON.parse(fileData);
}
},
// get the station name for a given station code
getStationName: function(theStationCode) {
// iterate through station info list and if you find a match return it
for (var cIndex = 0; cIndex < this.stationInformationList.length; cIndex++){
var stationCode = this.stationInformationList[cIndex].Code;
var stationName = this.stationInformationList[cIndex].Name;
if (stationCode === theStationCode)
return stationName;
}
// otherwise return null
return null;
},
// --- INCIDENT STUFF ---
// builds a full-text named list from the given line colors codes
parseLinesAffectedForColors: function(theLinesAffected, theLinesList) {
var cLinesAffected = theLinesAffected.toUpperCase();
var cAllCodes = [ 'BL', 'GR', 'OR', 'RD', 'SV', 'YL' ];
var cReturnLines = theLinesList;
// check for each color code in the string, if found then
// add code to the complete code string
for (var cIndex = 0; cIndex < cAllCodes.length; cIndex++)
{
var cCode = cAllCodes[cIndex];
if (cLinesAffected.includes(cCode) && (cReturnLines.indexOf(cCode) === -1))
cReturnLines[cReturnLines.length] = cCode;
}
return cReturnLines;
},
// main function to parse incididents
parseIncidents: function(theConfig, theIncidentList) {
var descriptionList = [];
var linesList = [];
// iterate through incident list
// add each description to the description list, TODO: use this list
// parse the lines affect for colors and track all color lines
// for any incidents
for (var cIndex = 0; cIndex < theIncidentList.length; cIndex++){
var incident = theIncidentList[cIndex];
descriptionList[descriptionList.length] = incident.Description;
linesList = this.parseLinesAffectedForColors(incident.LinesAffected, linesList);
}
// return the module ID, description list, and list of color line incidents
var returnPayload = {
identifier: theConfig.identifier,
descriptionList: descriptionList,
linesList: linesList
};
// send back to module
this.sendSocketNotification('DCMETRO_INCIDENT_UPDATE', returnPayload);
},
// makes the call to get the incidents
updateIncidents: function(theConfig){
// create the REST API call URL
var wmataIncidentURL =
'https://api.wmata.com/Incidents.svc/json/Incidents?api_key='
+ theConfig.wmata_api_key;
// create a self to use in the async call
var self = this;
if (!this.stopUpdates) {
https.get(wmataIncidentURL, (res) => {
let rawData = '';
res.on('data', (chunk) => rawData += chunk);
res.on('end', () => {
// once you have all the data send it to be parsed
self.parseIncidents(theConfig, JSON.parse(rawData).Incidents);
});
})
// if an error, handle it
.on('error', (e) => {
self.processError();
}); }
},
// --- STATION TRAIN TIME STUFF ---
// checks if the destination code is in not the list destinations to exclue
// returns true if not found
// return false if found
doesNotContainExcludedDestination: function(theConfig, theStationCode, theDestinationCode) {
// iterate through destinations to exclude, if one matches return false
for (var cIndex = 0; cIndex < theConfig.destinationsToExcludeList.length; cIndex++) {
var destToExclude = theConfig.destinationsToExcludeList[cIndex];
if (theDestinationCode === destToExclude)
return false;
}
// otherwise return true
return true;
},
// checks that train time string is not less than the configured time
// to show it
isNotLessThanConfigThreshold: function(theConfig, theMin) {
if (theConfig.hideTrainTimesLessThan === 0)
return true;
var cMin = theMin;
if ((cMin === "BRD") || (cMin === "ARR"))
cMin = 0;
cMin = parseInt(cMin);
if (cMin < theConfig.hideTrainTimesLessThan)
return false
return true;
},
// builds part of the REST API URL query to call based on station codes
getTrainQuery: function(theConfig) {
var returnQuery = "";
// list is comma delimited station codes, build accordingly and return it
for (var cIndex = 0; cIndex < theConfig.stationsToShowList.length; cIndex++){
var stationCode = theConfig.stationsToShowList[cIndex];
returnQuery += stationCode;
if (cIndex !== (theConfig.stationsToShowList.length - 1))
returnQuery += ",";
}
return returnQuery;
},
// build an empty station train times list to return in the payload
// return is a JSON object with keys of the station codes
// contains the station name and the list of train times
getEmptyStationTrainTimesList: function(theConfig) {
var returnList = {};
for (var cIndex = 0; cIndex < theConfig.stationsToShowList.length; cIndex++) {
var stationCode = theConfig.stationsToShowList[cIndex];
var stationName = this.getStationName(stationCode);
if (returnList[stationCode] === undefined)
{
var initStationPart = { StationName: stationName,
StationCode: stationCode,
TrainList: []
};
returnList[stationCode] = initStationPart;
}
}
return returnList;
},
// does the work of parsing the train times from the REST call
parseTrainTimes: function(theConfig, theTrains) {
// build an empty list in case some stations have no trains times
var stationTrainList = this.getEmptyStationTrainTimesList(theConfig);
// iterate through the train times list
for (var cIndex = 0; cIndex < theTrains.length; cIndex++){
var train = theTrains[cIndex];
// make sure there is a destination code
if (train.DestinationCode !== null)
{
// get all the parts of the train time
var tLocationCode = train.LocationCode;
var tLocationName = train.LocationName;
var tDestinationName = train.DestinationName;
var tDestinationCode = train.DestinationCode;
var tLine = train.Line;
var tMin = train.Min;
var trainListPart = stationTrainList[tLocationCode].TrainList;
// if value is set in the config for showDestinationFullName, use that value
if (theConfig.showDestinationFullName == "true")
{
var tDestination = this.getStationName(tDestinationCode);
}
else
{
var tDestination = train.Destination;
}
// build the train part
var trainPart = { Destination: tDestination,
DestinationName: tDestinationName,
DestinationCode: tDestinationCode,
Line: tLine,
Min: tMin
};
// if destination code isn't on the list of exclusions and
// it is not missing any of the required fields, then add
// it to the list
if ( (this.doesNotContainExcludedDestination(theConfig, tLocationCode, tDestinationCode))
&& this.isNotLessThanConfigThreshold(theConfig, tMin)
&& (tDestinationCode !== '')
&& (tDestinationName !== "Train")
&& (tLine !== "--")
&& (tMin !== '') )
trainListPart[trainListPart.length] = trainPart;
// set the main station train list object to the train list part
stationTrainList[tLocationCode].TrainList = trainListPart;
}
}
// return payload is the module id and the station train list
var returnPayload = {
identifier: theConfig.identifier,
stationTrainList: stationTrainList
};
// send the payload back to the module
this.sendSocketNotification('DCMETRO_STATIONTRAINTIMES_UPDATE', returnPayload);
},
// makes the call to get the train times list
updateTrainTimes: function(theConfig){
var self = this;
// get query part of the REST API URL
var trainQuery = self.getTrainQuery(theConfig);
// build the full URL call
var wmataTrainTimesURL =
'https://api.wmata.com/StationPrediction.svc/json/GetPrediction/'
+ trainQuery
+ '?api_key='
+ theConfig.wmata_api_key;
if (!this.stopUpdates) {
// make the async call
https.get(wmataTrainTimesURL, (res) => {
let rawData = '';
res.on('data', (chunk) => rawData += chunk);
res.on('end', () => {
// once you have all the data send it to be parsed
self.parseTrainTimes(theConfig, JSON.parse(rawData).Trains);
});
})
// if an error handle it
.on('error', (e) => {
self.processError();
}); }
}
});
//------------ END -------------