node-red-zenbus 1.0.13 → 1.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/zenbus-next-bus.js +7 -110
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-red-zenbus",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.14",
|
|
4
4
|
"description": "Real-time next bus ETA from Zenbus networks via direct API (protobuf)",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "zenbus-next-bus.js",
|
|
@@ -46,6 +46,6 @@
|
|
|
46
46
|
"node": ">=18"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"
|
|
49
|
+
"zenbus": "^1.0.0"
|
|
50
50
|
}
|
|
51
51
|
}
|
package/zenbus-next-bus.js
CHANGED
|
@@ -1,99 +1,4 @@
|
|
|
1
|
-
var
|
|
2
|
-
|
|
3
|
-
var BASE = 'https://zenbus.net';
|
|
4
|
-
|
|
5
|
-
function fetchProto(url, Type) {
|
|
6
|
-
return fetch(url)
|
|
7
|
-
.then(function (r) { return r.arrayBuffer(); })
|
|
8
|
-
.then(function (ab) { return Type.decode(Buffer.from(ab)); });
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
function secsToHHMM(secs) {
|
|
12
|
-
var s = ((secs % 86400) + 86400) % 86400;
|
|
13
|
-
var h = Math.floor(s / 3600);
|
|
14
|
-
var m = Math.floor((s % 3600) / 60);
|
|
15
|
-
return h + 'h' + String(m).padStart(2, '0');
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function createClient(opts) {
|
|
19
|
-
var alias = opts.alias, itinerary = opts.itinerary, stop = opts.stop;
|
|
20
|
-
return fetch(BASE + '/poll/cdn/zenbus.proto')
|
|
21
|
-
.then(function (r) { return r.text(); })
|
|
22
|
-
.then(function (protoText) {
|
|
23
|
-
var root = protobuf.parse(protoText).root;
|
|
24
|
-
var StaticMessage = root.lookupType('zenbus_realtime.StaticMessage');
|
|
25
|
-
var LiveMessage = root.lookupType('zenbus_realtime.LiveMessage');
|
|
26
|
-
|
|
27
|
-
return fetchProto(BASE + '/publicapp/static-data?alias=' + alias, StaticMessage)
|
|
28
|
-
.then(function (staticData) {
|
|
29
|
-
var shape = (staticData.shape || []).find(function (s) { return s.itineraryId && s.itineraryId.toString() === itinerary; });
|
|
30
|
-
var stopAnchor = shape && (shape.anchor || []).find(function (a) { return a.stopId && a.stopId.toString() === stop; });
|
|
31
|
-
var stopIndex = stopAnchor ? (stopAnchor.stopIndexInItinerary != null ? stopAnchor.stopIndexInItinerary : -1) : -1;
|
|
32
|
-
var stopDistanceM = stopAnchor ? (stopAnchor.distanceTravelled || 0) : 0;
|
|
33
|
-
var stopEntry = (staticData.stop || []).find(function (s) { return s.stopId && s.stopId.toString() === stop; });
|
|
34
|
-
var stopName = stopEntry ? stopEntry.name : 'Unknown';
|
|
35
|
-
var itin = (staticData.itinerary || []).find(function (i) { return i.itineraryId && i.itineraryId.toString() === itinerary; });
|
|
36
|
-
var lineEntry = (staticData.line || []).find(function (l) { return itin && l.lineId && l.lineId.toString() === (itin.lineId && itin.lineId.toString()); });
|
|
37
|
-
var lineCode = lineEntry ? lineEntry.code : 'Unknown';
|
|
38
|
-
var pollUrl = BASE + '/publicapp/poll?alias=' + alias + '&itinerary=' + itinerary;
|
|
39
|
-
|
|
40
|
-
return {
|
|
41
|
-
stopName: stopName,
|
|
42
|
-
lineCode: lineCode,
|
|
43
|
-
poll: function () {
|
|
44
|
-
return fetchProto(pollUrl, LiveMessage).then(function (liveData) {
|
|
45
|
-
var now = new Date();
|
|
46
|
-
var midnightUtcSecs = (liveData.timetable && liveData.timetable[0] && liveData.timetable[0].midnight && liveData.timetable[0].midnight.toNumber)
|
|
47
|
-
? liveData.timetable[0].midnight.toNumber()
|
|
48
|
-
: Math.floor(new Date(now).setHours(0, 0, 0, 0) / 1000);
|
|
49
|
-
var nowSecs = Math.floor(now.getTime() / 1000) - midnightUtcSecs;
|
|
50
|
-
|
|
51
|
-
var allColumns = (liveData.tripColumn || []).slice();
|
|
52
|
-
(liveData.timetable || []).forEach(function (tt) {
|
|
53
|
-
(tt.column || []).forEach(function (col) { allColumns.push(col); });
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
var candidates = [];
|
|
57
|
-
allColumns.forEach(function (tc) {
|
|
58
|
-
var est = (tc.estimactual || []).find(function (s) { return s.stopIndexInItinerary === stopIndex; });
|
|
59
|
-
var etaSecs = est ? (est.arrival || est.departure || 0) : 0;
|
|
60
|
-
if (!etaSecs || etaSecs <= nowSecs) return;
|
|
61
|
-
|
|
62
|
-
var vehicleDist = tc.distanceTravelled || 0;
|
|
63
|
-
var hasStarted = tc.previousIndexInItinerary >= 0 && tc.pos && tc.pos.length > 0;
|
|
64
|
-
if (hasStarted && vehicleDist > stopDistanceM) return;
|
|
65
|
-
|
|
66
|
-
var remainingDist = hasStarted ? Math.max(0, stopDistanceM - vehicleDist) : stopDistanceM;
|
|
67
|
-
var aimed = (tc.aimed || []).find(function (s) { return s.stopIndexInItinerary === stopIndex; });
|
|
68
|
-
var schedSecs = aimed ? (aimed.arrival || aimed.departure || aimed.arriparture || 0) : 0;
|
|
69
|
-
|
|
70
|
-
candidates.push({
|
|
71
|
-
etaMinutes: Math.round((etaSecs - nowSecs) / 60),
|
|
72
|
-
distanceM: Math.round(remainingDist),
|
|
73
|
-
estimatedArrival: secsToHHMM(etaSecs),
|
|
74
|
-
scheduledTime: schedSecs ? secsToHHMM(schedSecs) : null,
|
|
75
|
-
isLive: hasStarted
|
|
76
|
-
});
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
candidates.sort(function (a, b) { return a.etaMinutes - b.etaMinutes; });
|
|
80
|
-
var best = candidates[0] || null;
|
|
81
|
-
var second = candidates.find(function (c, i) { return i > 0 && c.isLive; }) || null;
|
|
82
|
-
|
|
83
|
-
return {
|
|
84
|
-
stop: stopName,
|
|
85
|
-
line: lineCode,
|
|
86
|
-
timestamp: now.toISOString(),
|
|
87
|
-
now: secsToHHMM(nowSecs),
|
|
88
|
-
next: best,
|
|
89
|
-
secondBus: second
|
|
90
|
-
};
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
};
|
|
94
|
-
});
|
|
95
|
-
});
|
|
96
|
-
}
|
|
1
|
+
var zenbusReady = import('zenbus/core');
|
|
97
2
|
|
|
98
3
|
module.exports = function (RED) {
|
|
99
4
|
|
|
@@ -107,10 +12,12 @@ module.exports = function (RED) {
|
|
|
107
12
|
|
|
108
13
|
node.status({ fill: 'yellow', shape: 'ring', text: 'initializing...' });
|
|
109
14
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
15
|
+
zenbusReady.then(function (mod) {
|
|
16
|
+
return mod.createClient({
|
|
17
|
+
alias: config.alias,
|
|
18
|
+
itinerary: config.itinerary,
|
|
19
|
+
stop: config.stop
|
|
20
|
+
});
|
|
114
21
|
}).then(function (c) {
|
|
115
22
|
if (closing) return;
|
|
116
23
|
client = c;
|
|
@@ -148,16 +55,6 @@ module.exports = function (RED) {
|
|
|
148
55
|
});
|
|
149
56
|
}
|
|
150
57
|
|
|
151
|
-
node.on('input', function (msg, send, done) {
|
|
152
|
-
send = send || function () { node.send.apply(node, arguments); };
|
|
153
|
-
done = done || function (err) { if (err) node.error(err, msg); };
|
|
154
|
-
if (!client) { done(new Error('Client not initialized')); return; }
|
|
155
|
-
client.poll().then(function (data) {
|
|
156
|
-
send({ payload: data });
|
|
157
|
-
done();
|
|
158
|
-
}).catch(function (e) { done(e); });
|
|
159
|
-
});
|
|
160
|
-
|
|
161
58
|
node.on('close', function (removed, done) {
|
|
162
59
|
closing = true;
|
|
163
60
|
if (timer) { clearTimeout(timer); timer = null; }
|