node-red-contrib-3dm-space 0.0.8 → 1.0.2
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/3dm-cloud-config.html +80 -16
- package/3dm-cloud-config.js +197 -192
- package/3dm-cloud-in.html +27 -14
- package/3dm-cloud-in.js +115 -115
- package/3dm-cloud-out.html +23 -10
- package/3dm-cloud-out.js +736 -669
- package/3dm-config-store.html +236 -0
- package/3dm-config-store.js +883 -0
- package/README.md +193 -58
- package/node-red-contrib-3dm-space-1.0.0.tgz +0 -0
- package/package.json +12 -5
package/3dm-cloud-out.js
CHANGED
|
@@ -1,669 +1,736 @@
|
|
|
1
|
-
module.exports = function (RED) {
|
|
2
|
-
"use strict";
|
|
3
|
-
|
|
4
|
-
var fs = require("fs");
|
|
5
|
-
var path = require("path");
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
*
|
|
10
|
-
*/
|
|
11
|
-
var
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
/*
|
|
20
|
-
*
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
var
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
var
|
|
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
|
-
node
|
|
84
|
-
|
|
85
|
-
node.
|
|
86
|
-
node.
|
|
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
|
-
function
|
|
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
|
-
function
|
|
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
|
-
msg
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
msg
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
updateStatus();
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
}
|
|
1
|
+
module.exports = function (RED) {
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
var fs = require("fs");
|
|
5
|
+
var path = require("path");
|
|
6
|
+
|
|
7
|
+
/*
|
|
8
|
+
* node-persistent-queue is optional so Windows can still install/load the node
|
|
9
|
+
* even if the old sqlite dependency fails.
|
|
10
|
+
*/
|
|
11
|
+
var Queue = null;
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
Queue = require("node-persistent-queue");
|
|
15
|
+
} catch (err) {
|
|
16
|
+
Queue = null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/*
|
|
20
|
+
* Hidden 3DM.space cloud settings.
|
|
21
|
+
*/
|
|
22
|
+
var TELEMETRY_TOPIC = "v1/devices/me/telemetry";
|
|
23
|
+
var MQTT_QOS = 1;
|
|
24
|
+
|
|
25
|
+
/*
|
|
26
|
+
* Store and forward.
|
|
27
|
+
*/
|
|
28
|
+
var STORE_AND_FORWARD_ENABLED = true;
|
|
29
|
+
|
|
30
|
+
/*
|
|
31
|
+
* Queue path.
|
|
32
|
+
* Uses the Node-RED user directory so it works on PassPort and Windows.
|
|
33
|
+
*/
|
|
34
|
+
var USER_DIR = RED.settings && RED.settings.userDir
|
|
35
|
+
? RED.settings.userDir
|
|
36
|
+
: process.cwd();
|
|
37
|
+
|
|
38
|
+
var QUEUE_DIR = path.join(USER_DIR, "queues");
|
|
39
|
+
var QUEUE_FILE_PREFIX = "3dm-space-telemetry";
|
|
40
|
+
|
|
41
|
+
/*
|
|
42
|
+
* Queue limits.
|
|
43
|
+
*/
|
|
44
|
+
var MAX_QUEUE_MESSAGES = 10000;
|
|
45
|
+
var QUEUE_HIGH_WATERMARK = 8000;
|
|
46
|
+
|
|
47
|
+
/*
|
|
48
|
+
* Backhaul speed.
|
|
49
|
+
* 250ms = 4 packets per second.
|
|
50
|
+
* This ignores the live publish rate limit.
|
|
51
|
+
*/
|
|
52
|
+
var BACKHAUL_REPLAY_GAP_MS = 250;
|
|
53
|
+
|
|
54
|
+
/*
|
|
55
|
+
* Live publish limit.
|
|
56
|
+
* 60000 = 1 update per minute.
|
|
57
|
+
* This only applies to live online data when the queue is empty.
|
|
58
|
+
*/
|
|
59
|
+
var LIVE_PUBLISH_INTERVAL_MS = 60000;
|
|
60
|
+
|
|
61
|
+
var RATE_LIMIT_TEXT = "rate limited: slow your payloads down!";
|
|
62
|
+
var QUEUE_FULL_TEXT = "queue full: message rejected";
|
|
63
|
+
var BAD_PAYLOAD_TEXT = "bad payload";
|
|
64
|
+
var STORE_FORWARD_UNAVAILABLE_TEXT = "store/forward unavailable";
|
|
65
|
+
|
|
66
|
+
function makeDir(dir) {
|
|
67
|
+
try {
|
|
68
|
+
if (!fs.existsSync(dir)) {
|
|
69
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
70
|
+
}
|
|
71
|
+
} catch (err) {
|
|
72
|
+
try {
|
|
73
|
+
fs.mkdirSync(dir);
|
|
74
|
+
} catch (err2) {
|
|
75
|
+
throw err;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function ThreeDMCloudOutNode(config) {
|
|
81
|
+
RED.nodes.createNode(this, config);
|
|
82
|
+
|
|
83
|
+
var node = this;
|
|
84
|
+
|
|
85
|
+
node.name = config.name;
|
|
86
|
+
node.server = RED.nodes.getNode(config.server);
|
|
87
|
+
|
|
88
|
+
var queueFile = path.join(
|
|
89
|
+
QUEUE_DIR,
|
|
90
|
+
QUEUE_FILE_PREFIX + "-" + node.id + ".sqlite"
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
node.lastLivePublishTime = 0;
|
|
94
|
+
node.queueOpen = false;
|
|
95
|
+
node.backhauling = false;
|
|
96
|
+
node.storeForwardUnavailable = false;
|
|
97
|
+
node.lastStatusText = "";
|
|
98
|
+
node.lastWarnText = "";
|
|
99
|
+
node.lastWarnTime = 0;
|
|
100
|
+
|
|
101
|
+
var queue = null;
|
|
102
|
+
var initJobs = [];
|
|
103
|
+
var replayTimer = null;
|
|
104
|
+
|
|
105
|
+
if (!node.server) {
|
|
106
|
+
node.status({ fill: "red", shape: "ring", text: "missing config" });
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function warnOncePerMinute(text) {
|
|
111
|
+
var now = Date.now();
|
|
112
|
+
|
|
113
|
+
if (text !== node.lastWarnText || (now - node.lastWarnTime) > 60000) {
|
|
114
|
+
node.lastWarnText = text;
|
|
115
|
+
node.lastWarnTime = now;
|
|
116
|
+
node.warn(text);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function setStatus(fill, shape, text) {
|
|
121
|
+
var key = fill + "|" + shape + "|" + text;
|
|
122
|
+
|
|
123
|
+
if (node.lastStatusText !== key) {
|
|
124
|
+
node.lastStatusText = key;
|
|
125
|
+
node.status({
|
|
126
|
+
fill: fill,
|
|
127
|
+
shape: shape,
|
|
128
|
+
text: text
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function queueCount() {
|
|
134
|
+
if (node.storeForwardUnavailable) {
|
|
135
|
+
return 0;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (!queue || !node.queueOpen) {
|
|
139
|
+
return initJobs.length;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
return queue.getLength();
|
|
144
|
+
} catch (err) {
|
|
145
|
+
return 0;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function queueIsEmpty() {
|
|
150
|
+
if (node.storeForwardUnavailable) {
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (!queue || !node.queueOpen) {
|
|
155
|
+
return initJobs.length === 0;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
return queue.isEmpty();
|
|
160
|
+
} catch (err) {
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function updateStatus(reason) {
|
|
166
|
+
var count = queueCount();
|
|
167
|
+
|
|
168
|
+
if (node.storeForwardUnavailable) {
|
|
169
|
+
if (node.server.connected) {
|
|
170
|
+
setStatus("yellow", "ring", STORE_FORWARD_UNAVAILABLE_TEXT);
|
|
171
|
+
} else {
|
|
172
|
+
setStatus("red", "ring", "offline: no storage");
|
|
173
|
+
}
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (!node.server.connected) {
|
|
178
|
+
setStatus("yellow", "ring", "storing offline (" + count + ")");
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (node.backhauling && count > 0) {
|
|
183
|
+
setStatus("green", "ring", "backhauling (" + count + ")");
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (count >= QUEUE_HIGH_WATERMARK) {
|
|
188
|
+
setStatus("yellow", "ring", "queue high (" + count + ")");
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (count > 0) {
|
|
193
|
+
setStatus("green", "ring", "queued (" + count + ")");
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
setStatus("green", "dot", reason || "connected");
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function buildTelemetryPacket(msg) {
|
|
201
|
+
var payload = msg.payload;
|
|
202
|
+
|
|
203
|
+
if (payload === null || payload === undefined) {
|
|
204
|
+
throw new Error("payload is empty");
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (typeof payload === "string") {
|
|
208
|
+
try {
|
|
209
|
+
payload = JSON.parse(payload);
|
|
210
|
+
} catch (err) {
|
|
211
|
+
throw new Error("payload must be a JSON object");
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (typeof payload !== "object" || Array.isArray(payload)) {
|
|
216
|
+
throw new Error("payload must be a JSON object");
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/*
|
|
220
|
+
* Timestamp is created when the message arrives here.
|
|
221
|
+
* This timestamp is kept during store and forward backhaul.
|
|
222
|
+
*/
|
|
223
|
+
var ts = Date.now();
|
|
224
|
+
|
|
225
|
+
if (msg.ts) {
|
|
226
|
+
ts = Number(msg.ts);
|
|
227
|
+
} else if (msg.timestamp) {
|
|
228
|
+
ts = Number(msg.timestamp);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (isNaN(ts)) {
|
|
232
|
+
ts = Date.now();
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return {
|
|
236
|
+
ts: ts,
|
|
237
|
+
values: payload
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function makeJob(packet) {
|
|
242
|
+
return {
|
|
243
|
+
topic: TELEMETRY_TOPIC,
|
|
244
|
+
qos: MQTT_QOS,
|
|
245
|
+
packet: packet,
|
|
246
|
+
createdAt: Date.now()
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function publishPacket(packet, callback) {
|
|
251
|
+
if (!node.server || !node.server.connected) {
|
|
252
|
+
callback(new Error("3DM Cloud is not connected"));
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
node.server.publish(
|
|
257
|
+
TELEMETRY_TOPIC,
|
|
258
|
+
packet,
|
|
259
|
+
{ qos: MQTT_QOS },
|
|
260
|
+
callback
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function isLiveRateLimited() {
|
|
265
|
+
var now = Date.now();
|
|
266
|
+
var elapsed = now - node.lastLivePublishTime;
|
|
267
|
+
|
|
268
|
+
if (elapsed < LIVE_PUBLISH_INTERVAL_MS) {
|
|
269
|
+
return {
|
|
270
|
+
limited: true,
|
|
271
|
+
remainingMs: LIVE_PUBLISH_INTERVAL_MS - elapsed
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return {
|
|
276
|
+
limited: false,
|
|
277
|
+
remainingMs: 0
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function canQueueNewMessage() {
|
|
282
|
+
return queueCount() < MAX_QUEUE_MESSAGES;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function sendResult(msg, send, done) {
|
|
286
|
+
send(msg);
|
|
287
|
+
|
|
288
|
+
if (done) {
|
|
289
|
+
done();
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function rejectQueueFull(msg, send, done) {
|
|
294
|
+
msg.sent = false;
|
|
295
|
+
msg.queued = false;
|
|
296
|
+
msg.queueFull = true;
|
|
297
|
+
msg.error = QUEUE_FULL_TEXT;
|
|
298
|
+
msg.topic = "3dm.telemetry";
|
|
299
|
+
|
|
300
|
+
setStatus("red", "ring", QUEUE_FULL_TEXT + " (" + queueCount() + ")");
|
|
301
|
+
warnOncePerMinute(QUEUE_FULL_TEXT + ". Limit is " + MAX_QUEUE_MESSAGES + " packets.");
|
|
302
|
+
|
|
303
|
+
sendResult(msg, send, done);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function rejectStoreForwardUnavailable(msg, send, done) {
|
|
307
|
+
msg.sent = false;
|
|
308
|
+
msg.queued = false;
|
|
309
|
+
msg.storeForwardUnavailable = true;
|
|
310
|
+
msg.error = STORE_FORWARD_UNAVAILABLE_TEXT;
|
|
311
|
+
msg.topic = "3dm.telemetry";
|
|
312
|
+
|
|
313
|
+
setStatus("red", "ring", STORE_FORWARD_UNAVAILABLE_TEXT);
|
|
314
|
+
warnOncePerMinute("3DM store and forward unavailable. node-persistent-queue is not installed.");
|
|
315
|
+
|
|
316
|
+
sendResult(msg, send, done);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function addJobToQueue(job, reason, msg, send, done) {
|
|
320
|
+
if (!STORE_AND_FORWARD_ENABLED) {
|
|
321
|
+
msg.sent = false;
|
|
322
|
+
msg.queued = false;
|
|
323
|
+
msg.error = "store and forward disabled";
|
|
324
|
+
msg.topic = "3dm.telemetry";
|
|
325
|
+
|
|
326
|
+
sendResult(msg, send, done);
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (node.storeForwardUnavailable || !Queue) {
|
|
331
|
+
rejectStoreForwardUnavailable(msg, send, done);
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (!canQueueNewMessage()) {
|
|
336
|
+
rejectQueueFull(msg, send, done);
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (!queue || !node.queueOpen) {
|
|
341
|
+
initJobs.push({
|
|
342
|
+
job: job,
|
|
343
|
+
reason: reason
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
msg.sent = false;
|
|
347
|
+
msg.queued = true;
|
|
348
|
+
msg.queueReason = reason;
|
|
349
|
+
msg.queueLength = queueCount();
|
|
350
|
+
msg.topic = "3dm.telemetry";
|
|
351
|
+
|
|
352
|
+
setStatus("yellow", "ring", "queue opening (" + queueCount() + ")");
|
|
353
|
+
|
|
354
|
+
sendResult(msg, send, done);
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
try {
|
|
359
|
+
queue.add(job);
|
|
360
|
+
|
|
361
|
+
msg.sent = false;
|
|
362
|
+
msg.queued = true;
|
|
363
|
+
msg.queueReason = reason;
|
|
364
|
+
msg.queueLength = queueCount();
|
|
365
|
+
msg.topic = "3dm.telemetry";
|
|
366
|
+
|
|
367
|
+
if (reason === "offline") {
|
|
368
|
+
setStatus("yellow", "ring", "storing offline (" + queueCount() + ")");
|
|
369
|
+
} else if (reason === "publish-failed") {
|
|
370
|
+
setStatus("yellow", "ring", "queued after send fail (" + queueCount() + ")");
|
|
371
|
+
} else {
|
|
372
|
+
setStatus("green", "ring", "queued (" + queueCount() + ")");
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (queueCount() >= QUEUE_HIGH_WATERMARK) {
|
|
376
|
+
warnOncePerMinute("3DM Cloud queue high: " + queueCount() + " packets.");
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
sendResult(msg, send, done);
|
|
380
|
+
|
|
381
|
+
maybeStartBackhaul();
|
|
382
|
+
} catch (err) {
|
|
383
|
+
msg.sent = false;
|
|
384
|
+
msg.queued = false;
|
|
385
|
+
msg.error = err.message || String(err);
|
|
386
|
+
msg.topic = "3dm.telemetry";
|
|
387
|
+
|
|
388
|
+
setStatus("red", "ring", "queue error");
|
|
389
|
+
node.error("3DM queue add failed: " + msg.error, msg);
|
|
390
|
+
|
|
391
|
+
sendResult(msg, send, done);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function maybeStartBackhaul() {
|
|
396
|
+
if (node.storeForwardUnavailable) {
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
if (!queue || !node.queueOpen) {
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
if (!node.server || !node.server.connected) {
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
if (queueIsEmpty()) {
|
|
409
|
+
node.backhauling = false;
|
|
410
|
+
updateStatus("connected");
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/*
|
|
415
|
+
* Backhaul ignores the live rate limit.
|
|
416
|
+
*/
|
|
417
|
+
try {
|
|
418
|
+
if (!queue.isStarted()) {
|
|
419
|
+
node.backhauling = true;
|
|
420
|
+
updateStatus();
|
|
421
|
+
queue.start();
|
|
422
|
+
}
|
|
423
|
+
} catch (err) {
|
|
424
|
+
warnOncePerMinute("3DM queue backhaul start failed: " + (err.message || err));
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function stopBackhaul() {
|
|
429
|
+
if (replayTimer) {
|
|
430
|
+
clearTimeout(replayTimer);
|
|
431
|
+
replayTimer = null;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
if (queue && node.queueOpen) {
|
|
435
|
+
try {
|
|
436
|
+
if (queue.isStarted()) {
|
|
437
|
+
queue.stop();
|
|
438
|
+
}
|
|
439
|
+
} catch (err) {
|
|
440
|
+
/*
|
|
441
|
+
* Ignore stop race.
|
|
442
|
+
*/
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
node.backhauling = false;
|
|
447
|
+
updateStatus();
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function flushInitJobsToQueue() {
|
|
451
|
+
if (!queue || !node.queueOpen || initJobs.length === 0) {
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
initJobs.forEach(function (item) {
|
|
456
|
+
if (canQueueNewMessage()) {
|
|
457
|
+
try {
|
|
458
|
+
queue.add(item.job);
|
|
459
|
+
} catch (err) {
|
|
460
|
+
warnOncePerMinute("3DM startup queue add failed: " + (err.message || err));
|
|
461
|
+
}
|
|
462
|
+
} else {
|
|
463
|
+
warnOncePerMinute(QUEUE_FULL_TEXT + " while loading startup messages.");
|
|
464
|
+
}
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
initJobs = [];
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function setupQueue() {
|
|
471
|
+
if (!STORE_AND_FORWARD_ENABLED) {
|
|
472
|
+
updateStatus();
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
if (!Queue) {
|
|
477
|
+
node.storeForwardUnavailable = true;
|
|
478
|
+
warnOncePerMinute("3DM store and forward unavailable. node-persistent-queue is not installed.");
|
|
479
|
+
updateStatus();
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
try {
|
|
484
|
+
makeDir(QUEUE_DIR);
|
|
485
|
+
} catch (err) {
|
|
486
|
+
node.storeForwardUnavailable = true;
|
|
487
|
+
setStatus("red", "ring", "queue dir error");
|
|
488
|
+
node.error("3DM queue directory error: " + (err.message || err));
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
queue = new Queue(queueFile);
|
|
493
|
+
|
|
494
|
+
function onQueueOpen() {
|
|
495
|
+
if (node.queueOpen) {
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
node.queueOpen = true;
|
|
500
|
+
node.storeForwardUnavailable = false;
|
|
501
|
+
|
|
502
|
+
node.log("3DM Cloud queue opened: " + queueFile);
|
|
503
|
+
|
|
504
|
+
flushInitJobsToQueue();
|
|
505
|
+
updateStatus();
|
|
506
|
+
maybeStartBackhaul();
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
queue.on("open", onQueueOpen);
|
|
510
|
+
|
|
511
|
+
queue.on("next", function (item) {
|
|
512
|
+
var job = item.job || item;
|
|
513
|
+
|
|
514
|
+
if (!job || !job.packet) {
|
|
515
|
+
warnOncePerMinute("3DM queue found invalid stored packet. Removing it.");
|
|
516
|
+
|
|
517
|
+
replayTimer = setTimeout(function () {
|
|
518
|
+
replayTimer = null;
|
|
519
|
+
|
|
520
|
+
try {
|
|
521
|
+
queue.done();
|
|
522
|
+
} catch (doneErr) {
|
|
523
|
+
node.error("3DM queue done failed: " + (doneErr.message || doneErr));
|
|
524
|
+
}
|
|
525
|
+
}, BACKHAUL_REPLAY_GAP_MS);
|
|
526
|
+
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
if (!node.server || !node.server.connected) {
|
|
531
|
+
stopBackhaul();
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
node.backhauling = true;
|
|
536
|
+
updateStatus();
|
|
537
|
+
|
|
538
|
+
publishPacket(job.packet, function (err) {
|
|
539
|
+
if (err) {
|
|
540
|
+
/*
|
|
541
|
+
* Do not call queue.done().
|
|
542
|
+
* This keeps the packet stored for the next retry.
|
|
543
|
+
*/
|
|
544
|
+
warnOncePerMinute("3DM Cloud backhaul stopped: " + (err.message || err));
|
|
545
|
+
stopBackhaul();
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
replayTimer = setTimeout(function () {
|
|
550
|
+
replayTimer = null;
|
|
551
|
+
|
|
552
|
+
try {
|
|
553
|
+
queue.done();
|
|
554
|
+
} catch (doneErr) {
|
|
555
|
+
node.error("3DM queue done failed: " + (doneErr.message || doneErr));
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
updateStatus("backhauled");
|
|
559
|
+
}, BACKHAUL_REPLAY_GAP_MS);
|
|
560
|
+
});
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
queue.on("start", function () {
|
|
564
|
+
node.backhauling = true;
|
|
565
|
+
updateStatus();
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
queue.on("stop", function () {
|
|
569
|
+
node.backhauling = false;
|
|
570
|
+
updateStatus();
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
queue.on("empty", function () {
|
|
574
|
+
node.backhauling = false;
|
|
575
|
+
updateStatus("connected");
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
queue.on("add", function () {
|
|
579
|
+
updateStatus();
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
queue
|
|
583
|
+
.open()
|
|
584
|
+
.then(function () {
|
|
585
|
+
onQueueOpen();
|
|
586
|
+
})
|
|
587
|
+
.catch(function (err) {
|
|
588
|
+
node.storeForwardUnavailable = true;
|
|
589
|
+
setStatus("red", "ring", "queue open failed");
|
|
590
|
+
node.error("3DM queue failed to open " + queueFile + ": " + (err.message || err));
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
var stateHandler = function (connected) {
|
|
595
|
+
if (connected) {
|
|
596
|
+
updateStatus("connected");
|
|
597
|
+
maybeStartBackhaul();
|
|
598
|
+
} else {
|
|
599
|
+
stopBackhaul();
|
|
600
|
+
updateStatus();
|
|
601
|
+
}
|
|
602
|
+
};
|
|
603
|
+
|
|
604
|
+
node.server.on("state", stateHandler);
|
|
605
|
+
|
|
606
|
+
setupQueue();
|
|
607
|
+
updateStatus();
|
|
608
|
+
|
|
609
|
+
node.on("input", function (msg, send, done) {
|
|
610
|
+
send = send || function () {
|
|
611
|
+
node.send.apply(node, arguments);
|
|
612
|
+
};
|
|
613
|
+
|
|
614
|
+
try {
|
|
615
|
+
var originalPayload = msg.payload;
|
|
616
|
+
var packet = buildTelemetryPacket(msg);
|
|
617
|
+
var job = makeJob(packet);
|
|
618
|
+
|
|
619
|
+
/*
|
|
620
|
+
* Offline:
|
|
621
|
+
* Store timestamped packet.
|
|
622
|
+
*/
|
|
623
|
+
if (!node.server || !node.server.connected) {
|
|
624
|
+
msg.payload = originalPayload;
|
|
625
|
+
addJobToQueue(job, "offline", msg, send, done);
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
/*
|
|
630
|
+
* If queue has data, queue new data too.
|
|
631
|
+
* Backhaul ignores live throttle and runs at 4Hz.
|
|
632
|
+
*/
|
|
633
|
+
if (!queueIsEmpty() || node.backhauling) {
|
|
634
|
+
msg.payload = originalPayload;
|
|
635
|
+
addJobToQueue(job, "queue-not-empty", msg, send, done);
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/*
|
|
640
|
+
* Live online publishing only.
|
|
641
|
+
* This is limited to 1 update per minute.
|
|
642
|
+
*/
|
|
643
|
+
var limit = isLiveRateLimited();
|
|
644
|
+
|
|
645
|
+
if (limit.limited) {
|
|
646
|
+
setStatus("yellow", "ring", RATE_LIMIT_TEXT);
|
|
647
|
+
|
|
648
|
+
msg.payload = originalPayload;
|
|
649
|
+
msg.sent = false;
|
|
650
|
+
msg.queued = false;
|
|
651
|
+
msg.rateLimited = true;
|
|
652
|
+
msg.rateLimitRemainingMs = limit.remainingMs;
|
|
653
|
+
msg.error = RATE_LIMIT_TEXT;
|
|
654
|
+
msg.topic = "3dm.telemetry";
|
|
655
|
+
|
|
656
|
+
warnOncePerMinute(RATE_LIMIT_TEXT);
|
|
657
|
+
|
|
658
|
+
sendResult(msg, send, done);
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
node.lastLivePublishTime = Date.now();
|
|
663
|
+
|
|
664
|
+
publishPacket(packet, function (err) {
|
|
665
|
+
if (err) {
|
|
666
|
+
msg.payload = originalPayload;
|
|
667
|
+
addJobToQueue(job, "publish-failed", msg, send, done);
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
setStatus("green", "dot", "sent");
|
|
672
|
+
|
|
673
|
+
msg.payload = originalPayload;
|
|
674
|
+
msg.sent = true;
|
|
675
|
+
msg.queued = false;
|
|
676
|
+
msg.rateLimited = false;
|
|
677
|
+
msg.topic = "3dm.telemetry";
|
|
678
|
+
|
|
679
|
+
sendResult(msg, send, done);
|
|
680
|
+
});
|
|
681
|
+
} catch (err) {
|
|
682
|
+
setStatus("red", "ring", BAD_PAYLOAD_TEXT);
|
|
683
|
+
|
|
684
|
+
msg.sent = false;
|
|
685
|
+
msg.queued = false;
|
|
686
|
+
msg.rateLimited = false;
|
|
687
|
+
msg.error = err.message || String(err);
|
|
688
|
+
msg.topic = "3dm.telemetry";
|
|
689
|
+
|
|
690
|
+
warnOncePerMinute("3DM Cloud telemetry bad payload: " + msg.error);
|
|
691
|
+
|
|
692
|
+
sendResult(msg, send, done);
|
|
693
|
+
}
|
|
694
|
+
});
|
|
695
|
+
|
|
696
|
+
node.on("close", function (removed, done) {
|
|
697
|
+
if (typeof removed === "function") {
|
|
698
|
+
done = removed;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
done = done || function () {};
|
|
702
|
+
|
|
703
|
+
if (node.server) {
|
|
704
|
+
node.server.removeListener("state", stateHandler);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
if (replayTimer) {
|
|
708
|
+
clearTimeout(replayTimer);
|
|
709
|
+
replayTimer = null;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
if (queue && node.queueOpen) {
|
|
713
|
+
try {
|
|
714
|
+
if (queue.isStarted()) {
|
|
715
|
+
queue.stop();
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
queue
|
|
719
|
+
.close()
|
|
720
|
+
.then(function () {
|
|
721
|
+
done();
|
|
722
|
+
})
|
|
723
|
+
.catch(function (err) {
|
|
724
|
+
done(err);
|
|
725
|
+
});
|
|
726
|
+
} catch (err) {
|
|
727
|
+
done(err);
|
|
728
|
+
}
|
|
729
|
+
} else {
|
|
730
|
+
done();
|
|
731
|
+
}
|
|
732
|
+
});
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
RED.nodes.registerType("3dm-cloud-out", ThreeDMCloudOutNode);
|
|
736
|
+
};
|