@halsystems/red-bacnet 2.2.0-alpha.0 → 2.2.0-alpha2
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/CHANGELOG.md +13 -1
- package/common/bacnet.js +75 -22
- package/common/core/concurrent.js +43 -2
- package/common/job/discover_device.js +7 -5
- package/common/job/discover_point.js +47 -41
- package/common/job/read_point.js +9 -7
- package/common/job/write_point.js +18 -14
- package/package.json +1 -1
- package/red-bacnet/discover_device.js +4 -5
- package/red-bacnet/discover_point.html +1 -9
- package/red-bacnet/discover_point.js +5 -9
- package/red-bacnet/read_point.html +1 -1
- package/red-bacnet/read_point.js +11 -15
- package/red-bacnet/write_point.html +7 -1
- package/red-bacnet/write_point.js +10 -14
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## [2.2.0]
|
|
3
|
+
## [2.2.0-alpha.2]
|
|
4
|
+
### Changed
|
|
5
|
+
- all `nodes` retained original `msg` properties
|
|
6
|
+
- `writePoint` emit `payload` with failed points after completion
|
|
7
|
+
|
|
8
|
+
## [2.2.0-alpha.1]
|
|
9
|
+
### Changed
|
|
10
|
+
- `ConcorrentTasksWithNetworkAwareness` added `invoke-id` generator and tracking
|
|
11
|
+
- `writePoint` output `msg.payload` about failed write points after completion, and `msg.result` about written points statistics
|
|
12
|
+
- `discoverPoint` send output on every device discovery completed ([] if failed), removed `groupExportDeviceCount` feature and add `msg.device` to output
|
|
13
|
+
- refactored all nodes to retain original `msg` properties
|
|
14
|
+
|
|
15
|
+
## [2.2.0-alpha.0]
|
|
4
16
|
### Changed
|
|
5
17
|
- `ConcurrentTask` changed to `ConcorrentTasksWithNetworkAwareness` round robin distributing task according to IP address to reduce congestion on a particular network
|
|
6
18
|
|
package/common/bacnet.js
CHANGED
|
@@ -10,7 +10,20 @@ const { EVENT_ERROR, EVENT_OUTPUT } = require('@root/common/core/constant.js')
|
|
|
10
10
|
const { concurrentTasks } = require('@root/common/core/concurrent.js')
|
|
11
11
|
const { getErrMsg } = require('@root/common/func.js')
|
|
12
12
|
|
|
13
|
+
// ---------------------------------- helper functions ----------------------------------
|
|
14
|
+
/**
|
|
15
|
+
* Apply invoke ID to options if getNextInvokeId function is provided
|
|
16
|
+
* @param {object} options - The options object to modify
|
|
17
|
+
* @param {Function} [getNextInvokeId] - Optional function to get the next invoke ID
|
|
18
|
+
*/
|
|
19
|
+
function applyInvokeId(options, getNextInvokeId) {
|
|
20
|
+
if (getNextInvokeId && typeof getNextInvokeId === 'function') {
|
|
21
|
+
options.invokeId = getNextInvokeId();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
13
25
|
// ---------------------------------- type def ----------------------------------
|
|
26
|
+
|
|
14
27
|
/**
|
|
15
28
|
* @typedef {import('@root/ext/node-bacstack/dist/index.js').Client} BacnetClient
|
|
16
29
|
*/
|
|
@@ -32,17 +45,19 @@ module.exports = {
|
|
|
32
45
|
* vendorId: 36,
|
|
33
46
|
* deviceName: 'BMS'
|
|
34
47
|
* }
|
|
48
|
+
* @param {Function} [getNextInvokeId]
|
|
49
|
+
* optional function to get the next invoke ID
|
|
35
50
|
* @returns array of objects
|
|
36
51
|
* eg: [{type: 12, value: {type: 8, instance: 123}, ...],
|
|
37
52
|
* @async
|
|
38
53
|
*/
|
|
39
|
-
readObjectList: async function (client, device) {
|
|
54
|
+
readObjectList: async function (client, device, getNextInvokeId) {
|
|
40
55
|
const objectId = { type: baEnum.ObjectType.DEVICE, instance: device.deviceId };
|
|
41
56
|
const propertyId = baEnum.PropertyIdentifier.OBJECT_LIST;
|
|
42
57
|
|
|
43
|
-
let result = await module.exports.readPropertyMultipleReturnArr(client, device, objectId, propertyId);
|
|
58
|
+
let result = await module.exports.readPropertyMultipleReturnArr(client, device, objectId, propertyId, getNextInvokeId);
|
|
44
59
|
if (result.length == 0) {
|
|
45
|
-
result = await module.exports.readPropertyReturnArr(client, device, objectId, propertyId);
|
|
60
|
+
result = await module.exports.readPropertyReturnArr(client, device, objectId, propertyId, getNextInvokeId);
|
|
46
61
|
}
|
|
47
62
|
|
|
48
63
|
return result;
|
|
@@ -55,10 +70,12 @@ module.exports = {
|
|
|
55
70
|
* @param {object} device
|
|
56
71
|
* @param {object} objectId
|
|
57
72
|
* @param {number} propertyId
|
|
73
|
+
* @param {Function} [getNextInvokeId]
|
|
74
|
+
* optional function to get the next invoke ID
|
|
58
75
|
* @returns array of objects
|
|
59
76
|
* @async
|
|
60
77
|
*/
|
|
61
|
-
readPropertyMultipleReturnArr: async function (client, device, objectId, propertyId) {
|
|
78
|
+
readPropertyMultipleReturnArr: async function (client, device, objectId, propertyId, getNextInvokeId) {
|
|
62
79
|
let addressSet = device.ipAddress;
|
|
63
80
|
if (device.macAddress != null && device.network != null) {
|
|
64
81
|
addressSet = {
|
|
@@ -82,10 +99,14 @@ module.exports = {
|
|
|
82
99
|
}];
|
|
83
100
|
|
|
84
101
|
return new Promise((resolve, reject) => {
|
|
102
|
+
const options = { maxApdu: device.maxApdu };
|
|
103
|
+
applyInvokeId(options, getNextInvokeId);
|
|
104
|
+
|
|
85
105
|
client.readPropertyMultiple(
|
|
106
|
+
|
|
86
107
|
addressSet,
|
|
87
108
|
reqArr,
|
|
88
|
-
|
|
109
|
+
options,
|
|
89
110
|
(err, value) => {
|
|
90
111
|
if (err) return reject(err);
|
|
91
112
|
resolve(value);
|
|
@@ -156,13 +177,15 @@ module.exports = {
|
|
|
156
177
|
* retry times for single read failed
|
|
157
178
|
* @param {number} concurrentTaskDelay
|
|
158
179
|
* delay between concurrent tasks
|
|
180
|
+
* @param {Function} [getNextInvokeId]
|
|
181
|
+
* optional function to get the next invoke ID
|
|
159
182
|
* @returns array of objects
|
|
160
183
|
* eg: [{type: 12, value: {type: 8, instance: 123}, ...],
|
|
161
184
|
* @async
|
|
162
185
|
*/
|
|
163
186
|
smartReadProperty: async function (
|
|
164
187
|
client, device, reqArr, readMethod = 1, maxConcurrentSinglePointRead = 5,
|
|
165
|
-
singleReadFailedRetry = 5, concurrentTaskDelay = 50
|
|
188
|
+
singleReadFailedRetry = 5, concurrentTaskDelay = 50, getNextInvokeId
|
|
166
189
|
) {
|
|
167
190
|
/* reqArr example
|
|
168
191
|
[{
|
|
@@ -226,7 +249,7 @@ module.exports = {
|
|
|
226
249
|
} while (!batchFull)
|
|
227
250
|
|
|
228
251
|
// read batch block
|
|
229
|
-
const value = await module.exports.readPropertyMultple(client, device, reqArrBatch)
|
|
252
|
+
const value = await module.exports.readPropertyMultple(client, device, reqArrBatch, getNextInvokeId)
|
|
230
253
|
.catch(() => {
|
|
231
254
|
// Reduce batch size by half (minimum 1)
|
|
232
255
|
currBatchSize = Math.max(1, Math.floor(currBatchSize / 2))
|
|
@@ -274,7 +297,7 @@ module.exports = {
|
|
|
274
297
|
id: `${x}-${y}`,
|
|
275
298
|
task: async () => {
|
|
276
299
|
try {
|
|
277
|
-
let value = await module.exports.readProperty(client, device, req.objectId, prop.id);
|
|
300
|
+
let value = await module.exports.readProperty(client, device, req.objectId, prop.id, getNextInvokeId);
|
|
278
301
|
|
|
279
302
|
if (value == null) {
|
|
280
303
|
value = {
|
|
@@ -351,11 +374,13 @@ module.exports = {
|
|
|
351
374
|
* maximum concurrent point to write
|
|
352
375
|
* @param {number} concurrentTaskDelay
|
|
353
376
|
* delay between concurrent tasks
|
|
377
|
+
* @param {Function} [getNextInvokeId]
|
|
378
|
+
* optional function to get the next invoke ID
|
|
354
379
|
* @async
|
|
355
380
|
*/
|
|
356
381
|
smartWriteProperty: async function (
|
|
357
382
|
client, device, writePoints, eventEmitter, maxConcurrentWrite,
|
|
358
|
-
concurrentTaskDelay = 50
|
|
383
|
+
concurrentTaskDelay = 50, getNextInvokeId
|
|
359
384
|
) {
|
|
360
385
|
const entries = Object.entries(writePoints);
|
|
361
386
|
|
|
@@ -373,10 +398,16 @@ module.exports = {
|
|
|
373
398
|
{ type: point.bacType, instance: point.bacInstance },
|
|
374
399
|
point.bacProp,
|
|
375
400
|
[{ type: point.valueType, value: point.value }],
|
|
376
|
-
point.priority
|
|
401
|
+
point.priority,
|
|
402
|
+
getNextInvokeId
|
|
377
403
|
);
|
|
378
404
|
} catch (err) {
|
|
379
|
-
eventEmitter.emit(EVENT_ERROR, {
|
|
405
|
+
eventEmitter.emit(EVENT_ERROR, {
|
|
406
|
+
point: {
|
|
407
|
+
[point.id]: point.value
|
|
408
|
+
},
|
|
409
|
+
error: getErrMsg(err)
|
|
410
|
+
});
|
|
380
411
|
}
|
|
381
412
|
return
|
|
382
413
|
}
|
|
@@ -416,12 +447,14 @@ module.exports = {
|
|
|
416
447
|
* eg:{ type: 2, instance: 1 }
|
|
417
448
|
* @param {number} propertyId
|
|
418
449
|
* eg: 85
|
|
450
|
+
* @param {Function} [getNextInvokeId]
|
|
451
|
+
* optional function to get the next invoke ID
|
|
419
452
|
* @returns object
|
|
420
453
|
* eg: { len: 11, objectId: { type: 19, instance: 1 },
|
|
421
454
|
* property: { id: 85, index: 4294967295 }, values: [ { type: 2, value: 3 }]}
|
|
422
455
|
* @async
|
|
423
456
|
*/
|
|
424
|
-
readProperty: async function (client, device, objectId, propertyId) {
|
|
457
|
+
readProperty: async function (client, device, objectId, propertyId, getNextInvokeId) {
|
|
425
458
|
let addressSet = device.ipAddress
|
|
426
459
|
if (device.macAddress != null && device.network != null) {
|
|
427
460
|
addressSet = {
|
|
@@ -432,17 +465,21 @@ module.exports = {
|
|
|
432
465
|
}
|
|
433
466
|
|
|
434
467
|
return new Promise((resolve, reject) => {
|
|
468
|
+
const options = { maxApdu: device.maxApdu };
|
|
469
|
+
applyInvokeId(options, getNextInvokeId);
|
|
470
|
+
|
|
435
471
|
client.readProperty(
|
|
436
472
|
addressSet,
|
|
437
473
|
objectId,
|
|
438
474
|
propertyId,
|
|
439
|
-
|
|
475
|
+
options,
|
|
440
476
|
(err, value) => {
|
|
441
477
|
if (err)
|
|
442
478
|
reject(err);
|
|
443
479
|
else
|
|
444
480
|
resolve(value);
|
|
445
|
-
}
|
|
481
|
+
}
|
|
482
|
+
);
|
|
446
483
|
});
|
|
447
484
|
},
|
|
448
485
|
|
|
@@ -456,11 +493,13 @@ module.exports = {
|
|
|
456
493
|
* eg:{ type: 2, instance: 1 }
|
|
457
494
|
* @param {number} propertyId
|
|
458
495
|
* eg: 85
|
|
496
|
+
* @param {Function} [getNextInvokeId]
|
|
497
|
+
* optional function to get the next invoke ID
|
|
459
498
|
* @returns array of objects
|
|
460
499
|
* eg: [{type: 12, value: {type: 8, instance: 123}, ...],
|
|
461
500
|
* @async
|
|
462
501
|
*/
|
|
463
|
-
readPropertyReturnArr: async function (client, device, objectId, propertyId) {
|
|
502
|
+
readPropertyReturnArr: async function (client, device, objectId, propertyId, getNextInvokeId) {
|
|
464
503
|
const result = [];
|
|
465
504
|
|
|
466
505
|
let addressSet = device.ipAddress
|
|
@@ -474,10 +513,14 @@ module.exports = {
|
|
|
474
513
|
|
|
475
514
|
async function readPropertyPart(index) {
|
|
476
515
|
return new Promise((resolve, reject) => {
|
|
477
|
-
|
|
516
|
+
const options = { maxApdu: device.maxApdu, arrayIndex: index };
|
|
517
|
+
applyInvokeId(options, getNextInvokeId);
|
|
518
|
+
|
|
519
|
+
client.readProperty(
|
|
520
|
+
addressSet,
|
|
478
521
|
objectId,
|
|
479
522
|
propertyId,
|
|
480
|
-
|
|
523
|
+
options,
|
|
481
524
|
(err, value) => {
|
|
482
525
|
if (err) {
|
|
483
526
|
if (index === 1)
|
|
@@ -493,7 +536,8 @@ module.exports = {
|
|
|
493
536
|
readPropertyPart(index + 1).then(resolve).catch(reject);
|
|
494
537
|
}
|
|
495
538
|
}
|
|
496
|
-
}
|
|
539
|
+
}
|
|
540
|
+
);
|
|
497
541
|
});
|
|
498
542
|
}
|
|
499
543
|
|
|
@@ -512,6 +556,8 @@ module.exports = {
|
|
|
512
556
|
* properties: [ { id: 85 }, ... ]
|
|
513
557
|
* },
|
|
514
558
|
* ...]
|
|
559
|
+
* @param {Function} [getNextInvokeId]
|
|
560
|
+
* optional function to get the next invoke ID
|
|
515
561
|
* @returns object
|
|
516
562
|
* eg: {"len":1937,"values":[{
|
|
517
563
|
* "objectId":{"type":3,"instance":0},
|
|
@@ -519,7 +565,7 @@ module.exports = {
|
|
|
519
565
|
* ...}]}
|
|
520
566
|
* @async
|
|
521
567
|
*/
|
|
522
|
-
readPropertyMultple: async function (client, device, reqArr) {
|
|
568
|
+
readPropertyMultple: async function (client, device, reqArr, getNextInvokeId) {
|
|
523
569
|
let addressSet = device.ipAddress
|
|
524
570
|
if (device.macAddress != null && device.network != null) {
|
|
525
571
|
addressSet = {
|
|
@@ -530,16 +576,20 @@ module.exports = {
|
|
|
530
576
|
}
|
|
531
577
|
|
|
532
578
|
return new Promise((resolve, reject) => {
|
|
579
|
+
const options = { maxApdu: device.maxApdu };
|
|
580
|
+
applyInvokeId(options, getNextInvokeId);
|
|
581
|
+
|
|
533
582
|
client.readPropertyMultiple(
|
|
534
583
|
addressSet,
|
|
535
584
|
reqArr,
|
|
536
|
-
|
|
585
|
+
options,
|
|
537
586
|
(err, value) => {
|
|
538
587
|
if (err)
|
|
539
588
|
reject(err);
|
|
540
589
|
else
|
|
541
590
|
resolve(value);
|
|
542
|
-
}
|
|
591
|
+
}
|
|
592
|
+
);
|
|
543
593
|
});
|
|
544
594
|
},
|
|
545
595
|
|
|
@@ -557,10 +607,12 @@ module.exports = {
|
|
|
557
607
|
* eg:[{type: 2, value: 3}]
|
|
558
608
|
* @param {number} priority
|
|
559
609
|
* eg: 8
|
|
610
|
+
* @param {Function} [getNextInvokeId]
|
|
611
|
+
* optional function to get the next invoke ID
|
|
560
612
|
* @returns true / undefined: true if success, else undefined
|
|
561
613
|
* @async
|
|
562
614
|
*/
|
|
563
|
-
writeProperty: async function (client, device, objectId, propertyId, writeValue, priority) {
|
|
615
|
+
writeProperty: async function (client, device, objectId, propertyId, writeValue, priority, getNextInvokeId) {
|
|
564
616
|
let addressSet = device.ipAddress
|
|
565
617
|
if (device.macAddress != null && device.network != null) {
|
|
566
618
|
addressSet = {
|
|
@@ -576,6 +628,7 @@ module.exports = {
|
|
|
576
628
|
if (priority !== null && priority !== undefined) {
|
|
577
629
|
options.priority = priority;
|
|
578
630
|
}
|
|
631
|
+
applyInvokeId(options, getNextInvokeId);
|
|
579
632
|
|
|
580
633
|
client.writeProperty(
|
|
581
634
|
addressSet,
|
|
@@ -59,10 +59,11 @@ module.exports = {
|
|
|
59
59
|
* Executes a list of tasks concurrently with IP address awareness.
|
|
60
60
|
* Tasks targeting the same IP address will execute sequentially.
|
|
61
61
|
* Tasks targeting different IP addresses can execute in parallel.
|
|
62
|
+
* Invoke IDs are automatically managed per-task to avoid collisions.
|
|
62
63
|
*
|
|
63
64
|
* @param {EventEmitter} eventEmitter - The event emitter to emit events to.
|
|
64
65
|
* @param {Array} tasks - The list of tasks to execute. Each task should have:
|
|
65
|
-
* { id, device: { ipAddress, ... }, task: async () => {...} }
|
|
66
|
+
* { id, device: { ipAddress, ... }, task: async (getNextInvokeId) => {...} }
|
|
66
67
|
* @param {number} maxConcurrent - The maximum number of concurrent tasks.
|
|
67
68
|
* @param {number} concurrentTaskDelay - The delay between concurrent tasks.
|
|
68
69
|
* @returns {Promise<Array>} - A promise that resolves with an array of results.
|
|
@@ -75,6 +76,10 @@ module.exports = {
|
|
|
75
76
|
const results = [];
|
|
76
77
|
const ipAddressInUse = new Set(); // Track which IPs are currently executing
|
|
77
78
|
|
|
79
|
+
// Create a sequential invoke ID generator with tracking
|
|
80
|
+
let invokeIdCounter = 1;
|
|
81
|
+
const invokeIdsInUse = new Set(); // Track which invoke IDs are currently in use
|
|
82
|
+
|
|
78
83
|
// Pending tasks that have not been started yet.
|
|
79
84
|
// We keep them in a list and remove them once scheduled so that a
|
|
80
85
|
// task blocked by a busy IP never blocks later tasks with free IPs.
|
|
@@ -88,6 +93,7 @@ module.exports = {
|
|
|
88
93
|
/**
|
|
89
94
|
* Start a single task and wire up its lifecycle. When the task settles
|
|
90
95
|
* it releases its IP so waiting tasks targeting the same IP can run.
|
|
96
|
+
* Creates a per-task invoke ID generator that tracks and releases IDs.
|
|
91
97
|
*
|
|
92
98
|
* @param {{id: any, task: Function, ipAddress: string|null}} item
|
|
93
99
|
*/
|
|
@@ -95,7 +101,38 @@ module.exports = {
|
|
|
95
101
|
const { id, task, ipAddress } = item;
|
|
96
102
|
if (ipAddress) ipAddressInUse.add(ipAddress);
|
|
97
103
|
|
|
98
|
-
|
|
104
|
+
// Track invoke IDs issued to this specific task
|
|
105
|
+
const taskInvokeIds = new Set();
|
|
106
|
+
|
|
107
|
+
// Create a task-scoped getNextInvokeId function that:
|
|
108
|
+
// 1. Finds the next available invoke ID (1-255)
|
|
109
|
+
// 2. Skips IDs already in use
|
|
110
|
+
// 3. Tracks the ID for later release
|
|
111
|
+
const taskGetNextInvokeId = () => {
|
|
112
|
+
let attempts = 0;
|
|
113
|
+
while (attempts < 255) {
|
|
114
|
+
const candidateId = invokeIdCounter++;
|
|
115
|
+
if (invokeIdCounter > 255) invokeIdCounter = 1;
|
|
116
|
+
|
|
117
|
+
// If this ID is not in use, claim it
|
|
118
|
+
if (!invokeIdsInUse.has(candidateId)) {
|
|
119
|
+
invokeIdsInUse.add(candidateId);
|
|
120
|
+
taskInvokeIds.add(candidateId);
|
|
121
|
+
return candidateId;
|
|
122
|
+
}
|
|
123
|
+
attempts++;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Fallback: if all 255 IDs are exhausted (shouldn't happen with proper concurrency limits),
|
|
127
|
+
// just return the next ID anyway
|
|
128
|
+
const fallbackId = invokeIdCounter++;
|
|
129
|
+
if (invokeIdCounter > 255) invokeIdCounter = 1;
|
|
130
|
+
invokeIdsInUse.add(fallbackId);
|
|
131
|
+
taskInvokeIds.add(fallbackId);
|
|
132
|
+
return fallbackId;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const promise = task(taskGetNextInvokeId)
|
|
99
136
|
.then(result => {
|
|
100
137
|
eventEmitter.emit(EVENT_OUTPUT, { id, result });
|
|
101
138
|
results.push({ id, result });
|
|
@@ -108,6 +145,10 @@ module.exports = {
|
|
|
108
145
|
executing.delete(promise);
|
|
109
146
|
// Release the IP address so waiting tasks can run
|
|
110
147
|
if (ipAddress) ipAddressInUse.delete(ipAddress);
|
|
148
|
+
// Release all invoke IDs that were issued to this task
|
|
149
|
+
for (const invokeId of taskInvokeIds) {
|
|
150
|
+
invokeIdsInUse.delete(invokeId);
|
|
151
|
+
}
|
|
111
152
|
});
|
|
112
153
|
|
|
113
154
|
executing.add(promise);
|
|
@@ -27,18 +27,20 @@ module.exports = {
|
|
|
27
27
|
* Construct a DiscoverDeviceJob.
|
|
28
28
|
* @param {BacnetClient} client - Bacnet client to use for the discovery.
|
|
29
29
|
* @param {EventEmitter} eventEmitter - Status and error event emitter.
|
|
30
|
+
* @param {Object} msg - Original incoming message.
|
|
30
31
|
* @param {number} network - Network to discover devices on, eg: 65535 / 1 / 1001
|
|
31
32
|
* @param {number} lowLimit - Low limit of the network range.
|
|
32
33
|
* @param {number} highLimit - High limit of the network range.
|
|
33
34
|
* @param {number} whoIsTimeout - Who is request timeout in milliseconds.
|
|
34
35
|
*/
|
|
35
36
|
constructor(
|
|
36
|
-
client, eventEmitter, network, lowLimit, highLimit, whoIsTimeout,
|
|
37
|
+
client, eventEmitter, msg, network, lowLimit, highLimit, whoIsTimeout,
|
|
37
38
|
name = 'discover device'
|
|
38
39
|
) {
|
|
39
40
|
super();
|
|
40
41
|
this.client = client
|
|
41
42
|
this.eventEmitter = eventEmitter
|
|
43
|
+
this.msg = msg
|
|
42
44
|
this.network = network
|
|
43
45
|
this.lowLimit = lowLimit
|
|
44
46
|
this.highLimit = highLimit
|
|
@@ -86,7 +88,8 @@ module.exports = {
|
|
|
86
88
|
validatedDevices.push(result);
|
|
87
89
|
}
|
|
88
90
|
|
|
89
|
-
this.
|
|
91
|
+
this.msg.payload = validatedDevices;
|
|
92
|
+
this.eventEmitter.emit(EVENT_OUTPUT, this.msg);
|
|
90
93
|
this.#updateProgress(100)
|
|
91
94
|
resolve();
|
|
92
95
|
}).catch(error => {
|
|
@@ -146,8 +149,7 @@ module.exports = {
|
|
|
146
149
|
const deviceInfo = await readDeviceName(d, addressSet);
|
|
147
150
|
this.discoveredDevices.push(deviceInfo);
|
|
148
151
|
|
|
149
|
-
|
|
150
|
-
await delay(75); // tweak between 50–150ms
|
|
152
|
+
await delay(50);
|
|
151
153
|
this.#updateProgress(
|
|
152
154
|
Math.min(90, Math.round((80 / this.discoverList.length) * (i + 1) + 10))
|
|
153
155
|
);
|
|
@@ -188,4 +190,4 @@ module.exports = {
|
|
|
188
190
|
this.eventEmitter.emit(EVENT_UPDATE_STATUS, progress);
|
|
189
191
|
}
|
|
190
192
|
},
|
|
191
|
-
}
|
|
193
|
+
}
|
|
@@ -53,20 +53,19 @@ const supportedObjectTypes = [
|
|
|
53
53
|
module.exports = {
|
|
54
54
|
DiscoverPointJob: class DiscoverPointJob extends BaseJob {
|
|
55
55
|
devices = [];
|
|
56
|
-
pointsList = [];
|
|
57
56
|
|
|
58
57
|
constructor(
|
|
59
|
-
client, eventEmitter,
|
|
58
|
+
client, eventEmitter, msg, discoverMode, readMethod,
|
|
60
59
|
maxConcurrentDeviceRead, maxConcurrentSinglePointRead, concurrentTaskDelay = 50,
|
|
61
60
|
name = 'discover point'
|
|
62
61
|
) {
|
|
63
62
|
super();
|
|
64
63
|
this.client = client
|
|
65
64
|
this.eventEmitter = eventEmitter
|
|
66
|
-
this.
|
|
65
|
+
this.msg = msg
|
|
66
|
+
this.inputDevices = msg?.devices
|
|
67
67
|
this.discoverMode = discoverMode
|
|
68
68
|
this.readMethod = readMethod
|
|
69
|
-
this.groupExportDeviceCount = groupExportDeviceCount
|
|
70
69
|
this.maxConcurrentDeviceRead = maxConcurrentDeviceRead
|
|
71
70
|
this.maxConcurrentSinglePointRead = maxConcurrentSinglePointRead
|
|
72
71
|
this.concurrentTaskDelay = concurrentTaskDelay
|
|
@@ -74,7 +73,7 @@ module.exports = {
|
|
|
74
73
|
}
|
|
75
74
|
|
|
76
75
|
async execute() {
|
|
77
|
-
this.#updateProgress(
|
|
76
|
+
this.#updateProgress('discovering')
|
|
78
77
|
|
|
79
78
|
return new Promise((resolve) => {
|
|
80
79
|
try {
|
|
@@ -88,7 +87,7 @@ module.exports = {
|
|
|
88
87
|
example: EG_BACNET_DEVICES
|
|
89
88
|
})
|
|
90
89
|
)
|
|
91
|
-
this.#updateProgress(
|
|
90
|
+
this.#updateProgress('completed')
|
|
92
91
|
resolve();
|
|
93
92
|
return
|
|
94
93
|
}
|
|
@@ -124,14 +123,16 @@ module.exports = {
|
|
|
124
123
|
this.devices.push(result);
|
|
125
124
|
}
|
|
126
125
|
|
|
127
|
-
|
|
126
|
+
// If no valid devices, complete immediately
|
|
127
|
+
if (this.devices.length === 0) {
|
|
128
|
+
this.#updateProgress('completed')
|
|
129
|
+
resolve();
|
|
130
|
+
return
|
|
131
|
+
}
|
|
128
132
|
|
|
129
133
|
// read all device points
|
|
130
134
|
this.#readAllDevicePoints().then(() => {
|
|
131
|
-
|
|
132
|
-
this.#exportPoints();
|
|
133
|
-
|
|
134
|
-
this.#updateProgress(100)
|
|
135
|
+
this.#updateProgress('completed')
|
|
135
136
|
resolve();
|
|
136
137
|
});
|
|
137
138
|
} catch (err) {
|
|
@@ -145,23 +146,17 @@ module.exports = {
|
|
|
145
146
|
const discoverPointEvent = new EventEmitter();
|
|
146
147
|
const size = this.devices.length;
|
|
147
148
|
let count = 0;
|
|
148
|
-
let discoveredDevices = 0
|
|
149
149
|
|
|
150
150
|
discoverPointEvent.on(EVENT_OUTPUT, (data) => {
|
|
151
|
-
this.#updateProgress(
|
|
152
|
-
Math.round((85 / size) * (count + 1) + 10)
|
|
153
|
-
)
|
|
154
|
-
|
|
155
|
-
if (Array.isArray(data.result))
|
|
156
|
-
this.pointsList.push(...data.result);
|
|
157
|
-
|
|
158
151
|
count++;
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
152
|
+
// Export points immediately after each device is discovered
|
|
153
|
+
if (Array.isArray(data.result?.points)) {
|
|
154
|
+
this.#exportPoints(data.result?.points, data.result?.device);
|
|
155
|
+
} else {
|
|
156
|
+
// Export empty array if no points discovered
|
|
157
|
+
this.#exportPoints([], data.result?.device);
|
|
164
158
|
}
|
|
159
|
+
this.#updateProgress(count, size);
|
|
165
160
|
});
|
|
166
161
|
|
|
167
162
|
discoverPointEvent.on(EVENT_ERROR, (data) => {
|
|
@@ -171,13 +166,15 @@ module.exports = {
|
|
|
171
166
|
const tasks = this.devices.map((d, i) => ({
|
|
172
167
|
id: d.deviceName,
|
|
173
168
|
device: d,
|
|
174
|
-
task: async () => {
|
|
169
|
+
task: async (getNextInvokeId) => {
|
|
175
170
|
void i
|
|
176
171
|
try {
|
|
177
|
-
const objectList = await readObjectList(this.client, d);
|
|
172
|
+
const objectList = await readObjectList(this.client, d, getNextInvokeId);
|
|
178
173
|
|
|
179
174
|
// istanbul ignore next
|
|
180
|
-
if (objectList == null)
|
|
175
|
+
if (objectList == null) {
|
|
176
|
+
return { device: d, points: [] }
|
|
177
|
+
}
|
|
181
178
|
|
|
182
179
|
let objectListFinal = objectList
|
|
183
180
|
.map(obj => {
|
|
@@ -206,10 +203,11 @@ module.exports = {
|
|
|
206
203
|
|
|
207
204
|
return await readPoints(
|
|
208
205
|
d, objectListFinal, discoverPointEvent, this.name, this.client, this.readMethod,
|
|
209
|
-
this.maxConcurrentSinglePointRead, this.concurrentTaskDelay
|
|
206
|
+
this.maxConcurrentSinglePointRead, this.concurrentTaskDelay, getNextInvokeId
|
|
210
207
|
);
|
|
211
208
|
} catch (error) {
|
|
212
209
|
this.eventEmitter.emit(EVENT_ERROR, errMsg(this.name, `Error reading ${d.deviceName} points`, error));
|
|
210
|
+
return { device: d, points: [] }
|
|
213
211
|
}
|
|
214
212
|
}
|
|
215
213
|
}));
|
|
@@ -217,22 +215,30 @@ module.exports = {
|
|
|
217
215
|
await concurrentTasksWithNetworkAwareness(discoverPointEvent, tasks, this.maxConcurrentDeviceRead);
|
|
218
216
|
}
|
|
219
217
|
|
|
220
|
-
#updateProgress(
|
|
221
|
-
|
|
218
|
+
#updateProgress(discovered, total) {
|
|
219
|
+
let message;
|
|
220
|
+
if (typeof discovered === 'string') {
|
|
221
|
+
// discovered is a status message like 'initializing' or 'completed'
|
|
222
|
+
message = discovered;
|
|
223
|
+
} else {
|
|
224
|
+
// discovered and total are numbers
|
|
225
|
+
message = `discovered: ${discovered}/${total}`;
|
|
226
|
+
}
|
|
227
|
+
this.eventEmitter.emit(EVENT_UPDATE_STATUS, message);
|
|
222
228
|
}
|
|
223
229
|
|
|
224
|
-
#exportPoints() {
|
|
230
|
+
#exportPoints(points, device) {
|
|
225
231
|
let exportPointsList = [];
|
|
226
|
-
for (let i = 0; i <
|
|
232
|
+
for (let i = 0; i < points.length; i++) {
|
|
227
233
|
const { error, value: result } = bacnetPointSchema.validate(
|
|
228
|
-
|
|
234
|
+
points[i], { stripUnknown: true }
|
|
229
235
|
);
|
|
230
236
|
// istanbul ignore next
|
|
231
237
|
{
|
|
232
238
|
if (error) {
|
|
233
239
|
this.eventEmitter.emit(EVENT_ERROR, errMsg(
|
|
234
240
|
this.name, ERR_SCHEMA_VALIDATION, {
|
|
235
|
-
bacnetPoint:
|
|
241
|
+
bacnetPoint: points[i],
|
|
236
242
|
error: error,
|
|
237
243
|
}));
|
|
238
244
|
continue
|
|
@@ -241,8 +247,9 @@ module.exports = {
|
|
|
241
247
|
exportPointsList.push(result);
|
|
242
248
|
}
|
|
243
249
|
|
|
244
|
-
this.
|
|
245
|
-
this.
|
|
250
|
+
this.msg.payload = exportPointsList;
|
|
251
|
+
this.msg.device = device;
|
|
252
|
+
this.eventEmitter.emit(EVENT_OUTPUT, this.msg);
|
|
246
253
|
}
|
|
247
254
|
}
|
|
248
255
|
}
|
|
@@ -250,7 +257,7 @@ module.exports = {
|
|
|
250
257
|
// ---------------------------------- functions ----------------------------------
|
|
251
258
|
const readPoints = async (
|
|
252
259
|
device, objects, eventEmitter, name, client, readMethod, maxConcurrentSinglePointRead,
|
|
253
|
-
concurrentTaskDelay
|
|
260
|
+
concurrentTaskDelay, getNextInvokeId
|
|
254
261
|
) => {
|
|
255
262
|
const points = [];
|
|
256
263
|
|
|
@@ -271,7 +278,7 @@ const readPoints = async (
|
|
|
271
278
|
try {
|
|
272
279
|
// First request - get basic properties
|
|
273
280
|
const result = await smartReadProperty(
|
|
274
|
-
client, device, reqArr, readMethod, maxConcurrentSinglePointRead, 50, concurrentTaskDelay
|
|
281
|
+
client, device, reqArr, readMethod, maxConcurrentSinglePointRead, 50, concurrentTaskDelay, getNextInvokeId
|
|
275
282
|
);
|
|
276
283
|
|
|
277
284
|
// Process basic properties first
|
|
@@ -292,7 +299,7 @@ const readPoints = async (
|
|
|
292
299
|
try {
|
|
293
300
|
// Force use single read on state text to reduce loss especially long text
|
|
294
301
|
const stateTextResult = await smartReadProperty(
|
|
295
|
-
client, device, stateTextReqArr, 0, 1, 50, concurrentTaskDelay
|
|
302
|
+
client, device, stateTextReqArr, 0, 1, 50, concurrentTaskDelay, getNextInvokeId
|
|
296
303
|
);
|
|
297
304
|
|
|
298
305
|
// Update points with STATE_TEXT
|
|
@@ -315,11 +322,10 @@ const readPoints = async (
|
|
|
315
322
|
));
|
|
316
323
|
}
|
|
317
324
|
}
|
|
318
|
-
|
|
319
|
-
return points;
|
|
320
325
|
} catch (error) {
|
|
321
326
|
eventEmitter.emit(EVENT_ERROR, errMsg(name, ERR_READING_POINTS, error));
|
|
322
327
|
}
|
|
328
|
+
return { device: device, points: points }
|
|
323
329
|
};
|
|
324
330
|
|
|
325
331
|
const updatePointWithStateText = (point, stateTextData) => {
|
package/common/job/read_point.js
CHANGED
|
@@ -22,14 +22,15 @@ module.exports = {
|
|
|
22
22
|
devicePointSeparator = '.';
|
|
23
23
|
|
|
24
24
|
constructor(
|
|
25
|
-
client, eventEmitter,
|
|
25
|
+
client, eventEmitter, msg, readMethod, maxConcurrentDeviceRead = 2,
|
|
26
26
|
maxConcurrentSinglePointRead = 5, concurrentTaskDelay = 50, name = 'read point'
|
|
27
27
|
) {
|
|
28
28
|
super();
|
|
29
29
|
this.client = client
|
|
30
30
|
this.eventEmitter = eventEmitter
|
|
31
|
-
this.
|
|
32
|
-
this.
|
|
31
|
+
this.msg = msg
|
|
32
|
+
this.devices = msg?.devices
|
|
33
|
+
this.points = msg?.points
|
|
33
34
|
this.readMethod = readMethod
|
|
34
35
|
this.maxConcurrentDeviceRead = maxConcurrentDeviceRead
|
|
35
36
|
this.maxConcurrentSinglePointRead = maxConcurrentSinglePointRead
|
|
@@ -232,10 +233,10 @@ module.exports = {
|
|
|
232
233
|
.map(([k, v]) => ({
|
|
233
234
|
id: k,
|
|
234
235
|
device: v.device,
|
|
235
|
-
task: async () => {
|
|
236
|
+
task: async (getNextInvokeId) => {
|
|
236
237
|
return await smartReadProperty(
|
|
237
238
|
this.client, v.device, v.points, this.readMethod,
|
|
238
|
-
this.maxConcurrentSinglePointRead, 10, this.concurrentTaskDelay
|
|
239
|
+
this.maxConcurrentSinglePointRead, 10, this.concurrentTaskDelay, getNextInvokeId
|
|
239
240
|
);
|
|
240
241
|
}
|
|
241
242
|
}));
|
|
@@ -311,7 +312,8 @@ module.exports = {
|
|
|
311
312
|
exportPoints[k] = result;
|
|
312
313
|
}
|
|
313
314
|
|
|
314
|
-
this.
|
|
315
|
+
this.msg.payload = exportPoints;
|
|
316
|
+
this.eventEmitter.emit(EVENT_OUTPUT, this.msg);
|
|
315
317
|
}
|
|
316
318
|
}
|
|
317
|
-
}
|
|
319
|
+
}
|
|
@@ -20,10 +20,11 @@ const {
|
|
|
20
20
|
module.exports = {
|
|
21
21
|
WritePointJob: class WritePointJob extends BaseJob {
|
|
22
22
|
writePointDetails = {}
|
|
23
|
+
failedPoints = {}
|
|
23
24
|
devicePointSeparator = '.';
|
|
24
25
|
|
|
25
26
|
constructor(
|
|
26
|
-
client, eventEmitter,
|
|
27
|
+
client, eventEmitter, msg,
|
|
27
28
|
maxConcurrentDeviceWrite = 2, maxConcurrentPointWrite = 1,
|
|
28
29
|
concurrentTaskDelay = 50,
|
|
29
30
|
name = 'write point'
|
|
@@ -31,9 +32,10 @@ module.exports = {
|
|
|
31
32
|
super();
|
|
32
33
|
this.client = client
|
|
33
34
|
this.eventEmitter = eventEmitter
|
|
34
|
-
this.
|
|
35
|
-
this.
|
|
36
|
-
this.
|
|
35
|
+
this.msg = msg
|
|
36
|
+
this.devices = msg?.devices
|
|
37
|
+
this.points = msg?.points
|
|
38
|
+
this.writePoints = msg?.writePoints
|
|
37
39
|
this.maxConcurrentDeviceWrite = maxConcurrentDeviceWrite
|
|
38
40
|
this.maxConcurrentPointWrite = maxConcurrentPointWrite
|
|
39
41
|
this.concurrentTaskDelay = concurrentTaskDelay
|
|
@@ -175,14 +177,7 @@ module.exports = {
|
|
|
175
177
|
}
|
|
176
178
|
|
|
177
179
|
// add point details
|
|
178
|
-
pointDetails[id] = {
|
|
179
|
-
deviceName: p.deviceName,
|
|
180
|
-
bacType: p.bacType,
|
|
181
|
-
bacInstance: p.bacInstance,
|
|
182
|
-
bacProp: p.bacProp,
|
|
183
|
-
valueType: p.valueType,
|
|
184
|
-
priority: p.priority
|
|
185
|
-
}
|
|
180
|
+
pointDetails[id] = { ...p };
|
|
186
181
|
});
|
|
187
182
|
|
|
188
183
|
if (pointSchemaErrList.length > 0) {
|
|
@@ -287,10 +282,10 @@ module.exports = {
|
|
|
287
282
|
.map(([k, v]) => ({
|
|
288
283
|
id: k,
|
|
289
284
|
device: v.device,
|
|
290
|
-
task: async () => {
|
|
285
|
+
task: async (getNextInvokeId) => {
|
|
291
286
|
return await smartWriteProperty(
|
|
292
287
|
this.client, v.device, v.writePoints, smartWriteEvent,
|
|
293
|
-
this.maxConcurrentPointWrite, this.concurrentTaskDelay
|
|
288
|
+
this.maxConcurrentPointWrite, this.concurrentTaskDelay, getNextInvokeId
|
|
294
289
|
);
|
|
295
290
|
}
|
|
296
291
|
}));
|
|
@@ -306,13 +301,22 @@ module.exports = {
|
|
|
306
301
|
});
|
|
307
302
|
|
|
308
303
|
smartWriteEvent.on(EVENT_ERROR, (data) => {
|
|
304
|
+
Object.assign(this.failedPoints, data.point);
|
|
309
305
|
this.eventEmitter.emit(EVENT_ERROR, errMsg(
|
|
310
306
|
this.name, ERR_WRITING_POINT, data
|
|
311
307
|
));
|
|
312
308
|
});
|
|
313
309
|
|
|
314
310
|
await concurrentTasksWithNetworkAwareness(smartWriteEvent, tasks, this.maxConcurrentDeviceWrite);
|
|
311
|
+
|
|
312
|
+
// emit output with msg containing failed points and results
|
|
313
|
+
this.#exportWriteResults();
|
|
315
314
|
}
|
|
316
315
|
|
|
316
|
+
#exportWriteResults() {
|
|
317
|
+
// Update msg with results
|
|
318
|
+
this.msg.payload = this.failedPoints;
|
|
319
|
+
this.eventEmitter.emit(EVENT_OUTPUT, this.msg);
|
|
320
|
+
}
|
|
317
321
|
}
|
|
318
322
|
}
|
package/package.json
CHANGED
|
@@ -42,6 +42,7 @@ module.exports = function (RED) {
|
|
|
42
42
|
this.task = new DiscoverDeviceJob(
|
|
43
43
|
this.client,
|
|
44
44
|
this.#eventEmitter,
|
|
45
|
+
null,
|
|
45
46
|
this.network,
|
|
46
47
|
this.lowLimit,
|
|
47
48
|
this.highLimit,
|
|
@@ -72,12 +73,14 @@ module.exports = function (RED) {
|
|
|
72
73
|
this.task.network = (msg.network === undefined) ? this.network : msg.network;
|
|
73
74
|
this.task.lowLimit = (msg.lowLimit === undefined) ? this.lowLimit : msg.lowLimit;
|
|
74
75
|
this.task.highLimit = (msg.highLimit === undefined) ? this.highLimit : msg.highLimit;
|
|
76
|
+
this.task.msg = msg;
|
|
75
77
|
|
|
76
78
|
this.job.addJob({
|
|
77
79
|
id: jobId,
|
|
78
80
|
task: this.task,
|
|
79
81
|
priority: Number.isFinite(msg.priority) ? msg.priority : 5,
|
|
80
82
|
});
|
|
83
|
+
|
|
81
84
|
// @ts-ignore
|
|
82
85
|
this.status({ fill: 'yellow', shape: 'dot', text: `in queue` });
|
|
83
86
|
});
|
|
@@ -87,10 +90,7 @@ module.exports = function (RED) {
|
|
|
87
90
|
this.task.onStop();
|
|
88
91
|
})
|
|
89
92
|
|
|
90
|
-
this.#eventEmitter.on(EVENT_OUTPUT, (
|
|
91
|
-
const msg = {
|
|
92
|
-
payload: data
|
|
93
|
-
};
|
|
93
|
+
this.#eventEmitter.on(EVENT_OUTPUT, (msg) => {
|
|
94
94
|
// @ts-ignore
|
|
95
95
|
this.send(msg);
|
|
96
96
|
});
|
|
@@ -114,4 +114,3 @@ module.exports = function (RED) {
|
|
|
114
114
|
// ----- register node -----
|
|
115
115
|
RED.nodes.registerType('discover device', DiscoverDevice);
|
|
116
116
|
}
|
|
117
|
-
|
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
client: { type: "bacnet client", required: true },
|
|
8
8
|
discoverMode: { value: 0, required: true, validate: RED.validators.number() },
|
|
9
9
|
readMethod: { value: 2, required: true, validate: RED.validators.number() },
|
|
10
|
-
groupExportDeviceCount: { value: 30000, required: true, validate: RED.validators.number() },
|
|
11
10
|
maxConcurrentDeviceRead: { value: 1, required: true, validate: RED.validators.number() },
|
|
12
11
|
maxConcurrentSinglePointRead: { value: 10, required: true, validate: RED.validators.number() },
|
|
13
12
|
concurrentTaskDelay: { value: 0, required: true, validate: RED.validators.number() },
|
|
@@ -46,10 +45,6 @@
|
|
|
46
45
|
<option value="1">Comprehensive Read</option>
|
|
47
46
|
</select>
|
|
48
47
|
</div>
|
|
49
|
-
<div class="form-row">
|
|
50
|
-
<label for="node-input-groupExportDeviceCount"><i class="fa fa-tag"></i> Group Export Device Count</label>
|
|
51
|
-
<input type="number" id="node-input-groupExportDeviceCount" placeholder="50", min="10", max="1000">
|
|
52
|
-
</div>
|
|
53
48
|
<div class="form-row">
|
|
54
49
|
<label for="node-input-maxConcurrentDeviceRead"><i class="fa fa-tag"></i> Max Concurrent Device Read</label>
|
|
55
50
|
<input type="number" id="node-input-maxConcurrentDeviceRead" placeholder="1" , min="1" , max="1000">
|
|
@@ -81,9 +76,6 @@
|
|
|
81
76
|
<dd> <b>Read Single Only</b>: use <code>readProperty</code></dd>
|
|
82
77
|
<dd> <b>Comprehensive Read</b>: try <code>readPropertyMultiple</code>, reduce query size if failed, and fallback to <code>readProperty</code> if query size reduced to 1</dd>
|
|
83
78
|
|
|
84
|
-
<dt>groupExportDeviceCount<span class="property-type">number</span></dt>
|
|
85
|
-
<dd> Emit <code>msg.payload</code> once discovered devices count reaches this limit</dd>
|
|
86
|
-
|
|
87
79
|
<dt>maxConcurrentDeviceRead<span class="property-type">number</span></dt>
|
|
88
80
|
<dd> Devices count to discover simultaneously.</dd>
|
|
89
81
|
<dd> <b> USE WITH CAUTION!</b> Reduce to low value on a slow network</dd>
|
|
@@ -103,7 +95,7 @@
|
|
|
103
95
|
<dt class="optional">msg.id <span class="property-type">string | number</span></dt>
|
|
104
96
|
<dd> Job ID. Default to <code>discoverPoints</code> if not provided.</dd>
|
|
105
97
|
<dt class="optional">msg.priority <span class="property-type">number</span></dt>
|
|
106
|
-
|
|
98
|
+
<dd> Job priority in queue. Default to <code>5</code> if not provided.</dd>
|
|
107
99
|
</dl>
|
|
108
100
|
|
|
109
101
|
<h3>Outputs</h3>
|
|
@@ -24,7 +24,6 @@ module.exports = function (RED) {
|
|
|
24
24
|
this.client = RED.nodes.getNode(config.client).instance;
|
|
25
25
|
this.discoverMode = +config.discoverMode
|
|
26
26
|
this.readMethod = +config.readMethod
|
|
27
|
-
this.groupExportDeviceCount = +config.groupExportDeviceCount
|
|
28
27
|
this.maxConcurrentDeviceRead = +config.maxConcurrentDeviceRead
|
|
29
28
|
this.maxConcurrentSinglePointRead = +config.maxConcurrentSinglePointRead
|
|
30
29
|
this.concurrentTaskDelay = +config.concurrentTaskDelay
|
|
@@ -54,8 +53,8 @@ module.exports = function (RED) {
|
|
|
54
53
|
}
|
|
55
54
|
|
|
56
55
|
const task = new DiscoverPointJob(
|
|
57
|
-
this.client, this.#eventEmitter, msg
|
|
58
|
-
this.
|
|
56
|
+
this.client, this.#eventEmitter, msg, this.discoverMode, this.readMethod,
|
|
57
|
+
this.maxConcurrentDeviceRead,
|
|
59
58
|
this.maxConcurrentSinglePointRead, this.concurrentTaskDelay
|
|
60
59
|
);
|
|
61
60
|
|
|
@@ -68,21 +67,18 @@ module.exports = function (RED) {
|
|
|
68
67
|
this.status({ fill: 'yellow', shape: 'dot', text: `in queue` });
|
|
69
68
|
});
|
|
70
69
|
|
|
71
|
-
this.#eventEmitter.on(EVENT_OUTPUT, (
|
|
72
|
-
const msg = {
|
|
73
|
-
payload: data
|
|
74
|
-
};
|
|
70
|
+
this.#eventEmitter.on(EVENT_OUTPUT, (msg) => {
|
|
75
71
|
// @ts-ignore
|
|
76
72
|
this.send(msg);
|
|
77
73
|
});
|
|
78
74
|
|
|
79
75
|
this.#eventEmitter.on(EVENT_UPDATE_STATUS, (msg) => {
|
|
80
|
-
if (msg ===
|
|
76
|
+
if (msg === 'completed')
|
|
81
77
|
// @ts-ignore
|
|
82
78
|
this.status({ fill: 'green', shape: 'dot', text: `completed: ${nowFormatted()}` });
|
|
83
79
|
else
|
|
84
80
|
// @ts-ignore
|
|
85
|
-
this.status({ fill: 'yellow', shape: 'dot', text:
|
|
81
|
+
this.status({ fill: 'yellow', shape: 'dot', text: `${msg}` });
|
|
86
82
|
});
|
|
87
83
|
|
|
88
84
|
this.#eventEmitter.on(EVENT_ERROR, (err) => {
|
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
<dd> Job ID. Default to <code>readPoints</code> if not provided.</dd>
|
|
92
92
|
|
|
93
93
|
<dt class="optional">msg.priority <span class="property-type">number</span></dt>
|
|
94
|
-
<dd> Job priority in queue. Default to <code>
|
|
94
|
+
<dd> Job priority in queue. Default to <code>3</code> if not provided.</dd>
|
|
95
95
|
</dl>
|
|
96
96
|
|
|
97
97
|
<h3>Outputs</h3>
|
package/red-bacnet/read_point.js
CHANGED
|
@@ -44,38 +44,34 @@ module.exports = function (RED) {
|
|
|
44
44
|
*/
|
|
45
45
|
// @ts-ignore
|
|
46
46
|
this.on(EVENT_INPUT, async function (msg) {
|
|
47
|
+
const jobId = (typeof msg.id === 'string' || typeof msg.id === 'number') ? msg.id : this.jobId;
|
|
48
|
+
|
|
49
|
+
if (this.job.queue.map(item => item.id).includes(jobId)) {
|
|
50
|
+
// @ts-ignore
|
|
51
|
+
print(`Coalesced job: ${jobId}`, true)
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
47
55
|
const task = new ReadPointJob(
|
|
48
56
|
this.client,
|
|
49
57
|
this.#eventEmitter,
|
|
50
|
-
msg
|
|
51
|
-
msg.points,
|
|
58
|
+
msg,
|
|
52
59
|
this.readMethod,
|
|
53
60
|
this.maxConcurrentDeviceRead,
|
|
54
61
|
this.maxConcurrentSinglePointRead,
|
|
55
62
|
this.concurrentTaskDelay
|
|
56
63
|
);
|
|
57
64
|
|
|
58
|
-
const jobId = (typeof msg.id === 'string' || typeof msg.id === 'number') ? msg.id : this.jobId;
|
|
59
|
-
|
|
60
|
-
if (this.job.queue.map(item => item.id).includes(jobId)) {
|
|
61
|
-
// @ts-ignore
|
|
62
|
-
print(`Coalesced job: ${jobId}`, true)
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
65
|
this.job.addJob({
|
|
67
66
|
id: jobId,
|
|
68
67
|
task: task,
|
|
69
|
-
priority: Number.isFinite(msg.priority) ? msg.priority :
|
|
68
|
+
priority: Number.isFinite(msg.priority) ? msg.priority : 3,
|
|
70
69
|
});
|
|
71
70
|
// @ts-ignore
|
|
72
71
|
this.status({ fill: 'yellow', shape: 'dot', text: `in queue` });
|
|
73
72
|
});
|
|
74
73
|
|
|
75
|
-
this.#eventEmitter.on(EVENT_OUTPUT, (
|
|
76
|
-
const msg = {
|
|
77
|
-
payload: data
|
|
78
|
-
};
|
|
74
|
+
this.#eventEmitter.on(EVENT_OUTPUT, (msg) => {
|
|
79
75
|
// @ts-ignore
|
|
80
76
|
this.send(msg);
|
|
81
77
|
});
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
<input type="number" id="node-input-maxConcurrentPointWrite" placeholder="5" , min="1" , max="1000">
|
|
38
38
|
</div>
|
|
39
39
|
<div class="form-row">
|
|
40
|
-
<label for="node-input-concurrentTaskDelay"><i class="fa fa-tag"></i> Concurrent Task Delay</label>
|
|
40
|
+
<label for="node-input-concurrentTaskDelay"><i class="fa fa-tag"></i> Concurrent Task Delay (ms)</label>
|
|
41
41
|
<input type="number" id="node-input-concurrentTaskDelay" placeholder="0" , min="0" , max="10000">
|
|
42
42
|
</div>
|
|
43
43
|
</script>
|
|
@@ -82,6 +82,12 @@
|
|
|
82
82
|
<dd> Job priority in queue. Default to <code>2</code> if not provided.</dd>
|
|
83
83
|
</dl>
|
|
84
84
|
|
|
85
|
+
<h3>Outputs</h3>
|
|
86
|
+
<dl class="message-properties">
|
|
87
|
+
<dt>msg.payload <span class="property-type">object</span></dt>
|
|
88
|
+
<dd>Failed to write points.</dd>
|
|
89
|
+
</dl>
|
|
90
|
+
|
|
85
91
|
<h3>Details</h3>
|
|
86
92
|
<p>If node is triggered while existing job is running, new job will be coalesced if <code>msg.id</code> are identical, else new job will be put in queue until current job is completed.</p>
|
|
87
93
|
</script>
|
|
@@ -43,16 +43,6 @@ module.exports = function (RED) {
|
|
|
43
43
|
*/
|
|
44
44
|
// @ts-ignore
|
|
45
45
|
this.on(EVENT_INPUT, async function (msg) {
|
|
46
|
-
const task = new WritePointJob(
|
|
47
|
-
this.client,
|
|
48
|
-
this.#eventEmitter,
|
|
49
|
-
msg.devices,
|
|
50
|
-
msg.points,
|
|
51
|
-
msg.writePoints,
|
|
52
|
-
this.maxConcurrentDeviceWrite,
|
|
53
|
-
this.maxConcurrentPointWrite,
|
|
54
|
-
this.concurrentTaskDelay
|
|
55
|
-
);
|
|
56
46
|
const jobId = (typeof msg.id === 'string' || typeof msg.id === 'number') ? msg.id : this.jobId;
|
|
57
47
|
|
|
58
48
|
if (this.job.queue.map(item => item.id).includes(jobId)) {
|
|
@@ -61,6 +51,15 @@ module.exports = function (RED) {
|
|
|
61
51
|
return;
|
|
62
52
|
}
|
|
63
53
|
|
|
54
|
+
const task = new WritePointJob(
|
|
55
|
+
this.client,
|
|
56
|
+
this.#eventEmitter,
|
|
57
|
+
msg,
|
|
58
|
+
this.maxConcurrentDeviceWrite,
|
|
59
|
+
this.maxConcurrentPointWrite,
|
|
60
|
+
this.concurrentTaskDelay
|
|
61
|
+
);
|
|
62
|
+
|
|
64
63
|
this.job.addJob({
|
|
65
64
|
id: jobId,
|
|
66
65
|
task: task,
|
|
@@ -70,10 +69,7 @@ module.exports = function (RED) {
|
|
|
70
69
|
this.status({ fill: 'yellow', shape: 'dot', text: `in queue` });
|
|
71
70
|
});
|
|
72
71
|
|
|
73
|
-
this.#eventEmitter.on(EVENT_OUTPUT, (
|
|
74
|
-
const msg = {
|
|
75
|
-
payload: data
|
|
76
|
-
};
|
|
72
|
+
this.#eventEmitter.on(EVENT_OUTPUT, (msg) => {
|
|
77
73
|
// @ts-ignore
|
|
78
74
|
this.send(msg);
|
|
79
75
|
});
|