@halsystems/red-bacnet 2.2.0-alpha.0 → 2.2.0-alpha.1
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 +8 -1
- package/common/bacnet.js +69 -21
- package/common/core/concurrent.js +43 -2
- package/common/job/discover_device.js +10 -5
- package/common/job/discover_point.js +48 -41
- package/common/job/read_point.js +11 -7
- package/common/job/write_point.js +41 -6
- package/package.json +1 -1
- package/red-bacnet/discover_device.js +4 -5
- package/red-bacnet/discover_point.html +0 -8
- package/red-bacnet/discover_point.js +5 -9
- package/red-bacnet/read_point.js +11 -15
- package/red-bacnet/write_point.html +1 -1
- package/red-bacnet/write_point.js +10 -14
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## [2.2.0]
|
|
3
|
+
## [2.2.0-alpha.1]
|
|
4
|
+
### Changed
|
|
5
|
+
- `ConcorrentTasksWithNetworkAwareness` added `invoke-id` generator and tracking
|
|
6
|
+
- `writePoint` output `msg.payload` about failed write points after completion, and `msg.result` about written points statistics
|
|
7
|
+
- `discoverPoint` send output on every device discovery completed ([] if failed), removed `groupExportDeviceCount` feature and add `msg.device` to output
|
|
8
|
+
- refactored all nodes to retain original `msg` properties
|
|
9
|
+
|
|
10
|
+
## [2.2.0-alpha.0]
|
|
4
11
|
### Changed
|
|
5
12
|
- `ConcurrentTask` changed to `ConcorrentTasksWithNetworkAwareness` round robin distributing task according to IP address to reduce congestion on a particular network
|
|
6
13
|
|
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,7 +398,8 @@ 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
405
|
eventEmitter.emit(EVENT_ERROR, { id: id, error: getErrMsg(err) });
|
|
@@ -416,12 +442,14 @@ module.exports = {
|
|
|
416
442
|
* eg:{ type: 2, instance: 1 }
|
|
417
443
|
* @param {number} propertyId
|
|
418
444
|
* eg: 85
|
|
445
|
+
* @param {Function} [getNextInvokeId]
|
|
446
|
+
* optional function to get the next invoke ID
|
|
419
447
|
* @returns object
|
|
420
448
|
* eg: { len: 11, objectId: { type: 19, instance: 1 },
|
|
421
449
|
* property: { id: 85, index: 4294967295 }, values: [ { type: 2, value: 3 }]}
|
|
422
450
|
* @async
|
|
423
451
|
*/
|
|
424
|
-
readProperty: async function (client, device, objectId, propertyId) {
|
|
452
|
+
readProperty: async function (client, device, objectId, propertyId, getNextInvokeId) {
|
|
425
453
|
let addressSet = device.ipAddress
|
|
426
454
|
if (device.macAddress != null && device.network != null) {
|
|
427
455
|
addressSet = {
|
|
@@ -432,17 +460,21 @@ module.exports = {
|
|
|
432
460
|
}
|
|
433
461
|
|
|
434
462
|
return new Promise((resolve, reject) => {
|
|
463
|
+
const options = { maxApdu: device.maxApdu };
|
|
464
|
+
applyInvokeId(options, getNextInvokeId);
|
|
465
|
+
|
|
435
466
|
client.readProperty(
|
|
436
467
|
addressSet,
|
|
437
468
|
objectId,
|
|
438
469
|
propertyId,
|
|
439
|
-
|
|
470
|
+
options,
|
|
440
471
|
(err, value) => {
|
|
441
472
|
if (err)
|
|
442
473
|
reject(err);
|
|
443
474
|
else
|
|
444
475
|
resolve(value);
|
|
445
|
-
}
|
|
476
|
+
}
|
|
477
|
+
);
|
|
446
478
|
});
|
|
447
479
|
},
|
|
448
480
|
|
|
@@ -456,11 +488,13 @@ module.exports = {
|
|
|
456
488
|
* eg:{ type: 2, instance: 1 }
|
|
457
489
|
* @param {number} propertyId
|
|
458
490
|
* eg: 85
|
|
491
|
+
* @param {Function} [getNextInvokeId]
|
|
492
|
+
* optional function to get the next invoke ID
|
|
459
493
|
* @returns array of objects
|
|
460
494
|
* eg: [{type: 12, value: {type: 8, instance: 123}, ...],
|
|
461
495
|
* @async
|
|
462
496
|
*/
|
|
463
|
-
readPropertyReturnArr: async function (client, device, objectId, propertyId) {
|
|
497
|
+
readPropertyReturnArr: async function (client, device, objectId, propertyId, getNextInvokeId) {
|
|
464
498
|
const result = [];
|
|
465
499
|
|
|
466
500
|
let addressSet = device.ipAddress
|
|
@@ -474,10 +508,14 @@ module.exports = {
|
|
|
474
508
|
|
|
475
509
|
async function readPropertyPart(index) {
|
|
476
510
|
return new Promise((resolve, reject) => {
|
|
477
|
-
|
|
511
|
+
const options = { maxApdu: device.maxApdu, arrayIndex: index };
|
|
512
|
+
applyInvokeId(options, getNextInvokeId);
|
|
513
|
+
|
|
514
|
+
client.readProperty(
|
|
515
|
+
addressSet,
|
|
478
516
|
objectId,
|
|
479
517
|
propertyId,
|
|
480
|
-
|
|
518
|
+
options,
|
|
481
519
|
(err, value) => {
|
|
482
520
|
if (err) {
|
|
483
521
|
if (index === 1)
|
|
@@ -493,7 +531,8 @@ module.exports = {
|
|
|
493
531
|
readPropertyPart(index + 1).then(resolve).catch(reject);
|
|
494
532
|
}
|
|
495
533
|
}
|
|
496
|
-
}
|
|
534
|
+
}
|
|
535
|
+
);
|
|
497
536
|
});
|
|
498
537
|
}
|
|
499
538
|
|
|
@@ -512,6 +551,8 @@ module.exports = {
|
|
|
512
551
|
* properties: [ { id: 85 }, ... ]
|
|
513
552
|
* },
|
|
514
553
|
* ...]
|
|
554
|
+
* @param {Function} [getNextInvokeId]
|
|
555
|
+
* optional function to get the next invoke ID
|
|
515
556
|
* @returns object
|
|
516
557
|
* eg: {"len":1937,"values":[{
|
|
517
558
|
* "objectId":{"type":3,"instance":0},
|
|
@@ -519,7 +560,7 @@ module.exports = {
|
|
|
519
560
|
* ...}]}
|
|
520
561
|
* @async
|
|
521
562
|
*/
|
|
522
|
-
readPropertyMultple: async function (client, device, reqArr) {
|
|
563
|
+
readPropertyMultple: async function (client, device, reqArr, getNextInvokeId) {
|
|
523
564
|
let addressSet = device.ipAddress
|
|
524
565
|
if (device.macAddress != null && device.network != null) {
|
|
525
566
|
addressSet = {
|
|
@@ -530,16 +571,20 @@ module.exports = {
|
|
|
530
571
|
}
|
|
531
572
|
|
|
532
573
|
return new Promise((resolve, reject) => {
|
|
574
|
+
const options = { maxApdu: device.maxApdu };
|
|
575
|
+
applyInvokeId(options, getNextInvokeId);
|
|
576
|
+
|
|
533
577
|
client.readPropertyMultiple(
|
|
534
578
|
addressSet,
|
|
535
579
|
reqArr,
|
|
536
|
-
|
|
580
|
+
options,
|
|
537
581
|
(err, value) => {
|
|
538
582
|
if (err)
|
|
539
583
|
reject(err);
|
|
540
584
|
else
|
|
541
585
|
resolve(value);
|
|
542
|
-
}
|
|
586
|
+
}
|
|
587
|
+
);
|
|
543
588
|
});
|
|
544
589
|
},
|
|
545
590
|
|
|
@@ -557,10 +602,12 @@ module.exports = {
|
|
|
557
602
|
* eg:[{type: 2, value: 3}]
|
|
558
603
|
* @param {number} priority
|
|
559
604
|
* eg: 8
|
|
605
|
+
* @param {Function} [getNextInvokeId]
|
|
606
|
+
* optional function to get the next invoke ID
|
|
560
607
|
* @returns true / undefined: true if success, else undefined
|
|
561
608
|
* @async
|
|
562
609
|
*/
|
|
563
|
-
writeProperty: async function (client, device, objectId, propertyId, writeValue, priority) {
|
|
610
|
+
writeProperty: async function (client, device, objectId, propertyId, writeValue, priority, getNextInvokeId) {
|
|
564
611
|
let addressSet = device.ipAddress
|
|
565
612
|
if (device.macAddress != null && device.network != null) {
|
|
566
613
|
addressSet = {
|
|
@@ -576,6 +623,7 @@ module.exports = {
|
|
|
576
623
|
if (priority !== null && priority !== undefined) {
|
|
577
624
|
options.priority = priority;
|
|
578
625
|
}
|
|
626
|
+
applyInvokeId(options, getNextInvokeId);
|
|
579
627
|
|
|
580
628
|
client.writeProperty(
|
|
581
629
|
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,11 @@ module.exports = {
|
|
|
86
88
|
validatedDevices.push(result);
|
|
87
89
|
}
|
|
88
90
|
|
|
89
|
-
this.
|
|
91
|
+
this.msg.payload = validatedDevices;
|
|
92
|
+
delete this.msg.network;
|
|
93
|
+
delete this.msg.lowLimit;
|
|
94
|
+
delete this.msg.highLimit;
|
|
95
|
+
this.eventEmitter.emit(EVENT_OUTPUT, this.msg);
|
|
90
96
|
this.#updateProgress(100)
|
|
91
97
|
resolve();
|
|
92
98
|
}).catch(error => {
|
|
@@ -146,8 +152,7 @@ module.exports = {
|
|
|
146
152
|
const deviceInfo = await readDeviceName(d, addressSet);
|
|
147
153
|
this.discoveredDevices.push(deviceInfo);
|
|
148
154
|
|
|
149
|
-
|
|
150
|
-
await delay(75); // tweak between 50–150ms
|
|
155
|
+
await delay(50);
|
|
151
156
|
this.#updateProgress(
|
|
152
157
|
Math.min(90, Math.round((80 / this.discoverList.length) * (i + 1) + 10))
|
|
153
158
|
);
|
|
@@ -188,4 +193,4 @@ module.exports = {
|
|
|
188
193
|
this.eventEmitter.emit(EVENT_UPDATE_STATUS, progress);
|
|
189
194
|
}
|
|
190
195
|
},
|
|
191
|
-
}
|
|
196
|
+
}
|
|
@@ -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,10 @@ 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
|
+
delete this.msg.devices;
|
|
253
|
+
this.eventEmitter.emit(EVENT_OUTPUT, this.msg);
|
|
246
254
|
}
|
|
247
255
|
}
|
|
248
256
|
}
|
|
@@ -250,7 +258,7 @@ module.exports = {
|
|
|
250
258
|
// ---------------------------------- functions ----------------------------------
|
|
251
259
|
const readPoints = async (
|
|
252
260
|
device, objects, eventEmitter, name, client, readMethod, maxConcurrentSinglePointRead,
|
|
253
|
-
concurrentTaskDelay
|
|
261
|
+
concurrentTaskDelay, getNextInvokeId
|
|
254
262
|
) => {
|
|
255
263
|
const points = [];
|
|
256
264
|
|
|
@@ -271,7 +279,7 @@ const readPoints = async (
|
|
|
271
279
|
try {
|
|
272
280
|
// First request - get basic properties
|
|
273
281
|
const result = await smartReadProperty(
|
|
274
|
-
client, device, reqArr, readMethod, maxConcurrentSinglePointRead, 50, concurrentTaskDelay
|
|
282
|
+
client, device, reqArr, readMethod, maxConcurrentSinglePointRead, 50, concurrentTaskDelay, getNextInvokeId
|
|
275
283
|
);
|
|
276
284
|
|
|
277
285
|
// Process basic properties first
|
|
@@ -292,7 +300,7 @@ const readPoints = async (
|
|
|
292
300
|
try {
|
|
293
301
|
// Force use single read on state text to reduce loss especially long text
|
|
294
302
|
const stateTextResult = await smartReadProperty(
|
|
295
|
-
client, device, stateTextReqArr, 0, 1, 50, concurrentTaskDelay
|
|
303
|
+
client, device, stateTextReqArr, 0, 1, 50, concurrentTaskDelay, getNextInvokeId
|
|
296
304
|
);
|
|
297
305
|
|
|
298
306
|
// Update points with STATE_TEXT
|
|
@@ -315,11 +323,10 @@ const readPoints = async (
|
|
|
315
323
|
));
|
|
316
324
|
}
|
|
317
325
|
}
|
|
318
|
-
|
|
319
|
-
return points;
|
|
320
326
|
} catch (error) {
|
|
321
327
|
eventEmitter.emit(EVENT_ERROR, errMsg(name, ERR_READING_POINTS, error));
|
|
322
328
|
}
|
|
329
|
+
return { device: device, points: points }
|
|
323
330
|
};
|
|
324
331
|
|
|
325
332
|
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,10 @@ module.exports = {
|
|
|
311
312
|
exportPoints[k] = result;
|
|
312
313
|
}
|
|
313
314
|
|
|
314
|
-
this.
|
|
315
|
+
this.msg.payload = exportPoints;
|
|
316
|
+
delete this.msg.devices;
|
|
317
|
+
delete this.msg.points;
|
|
318
|
+
this.eventEmitter.emit(EVENT_OUTPUT, this.msg);
|
|
315
319
|
}
|
|
316
320
|
}
|
|
317
|
-
}
|
|
321
|
+
}
|
|
@@ -23,7 +23,7 @@ module.exports = {
|
|
|
23
23
|
devicePointSeparator = '.';
|
|
24
24
|
|
|
25
25
|
constructor(
|
|
26
|
-
client, eventEmitter,
|
|
26
|
+
client, eventEmitter, msg,
|
|
27
27
|
maxConcurrentDeviceWrite = 2, maxConcurrentPointWrite = 1,
|
|
28
28
|
concurrentTaskDelay = 50,
|
|
29
29
|
name = 'write point'
|
|
@@ -31,9 +31,10 @@ module.exports = {
|
|
|
31
31
|
super();
|
|
32
32
|
this.client = client
|
|
33
33
|
this.eventEmitter = eventEmitter
|
|
34
|
-
this.
|
|
35
|
-
this.
|
|
36
|
-
this.
|
|
34
|
+
this.msg = msg
|
|
35
|
+
this.devices = msg?.devices
|
|
36
|
+
this.points = msg?.points
|
|
37
|
+
this.writePoints = msg?.writePoints
|
|
37
38
|
this.maxConcurrentDeviceWrite = maxConcurrentDeviceWrite
|
|
38
39
|
this.maxConcurrentPointWrite = maxConcurrentPointWrite
|
|
39
40
|
this.concurrentTaskDelay = concurrentTaskDelay
|
|
@@ -287,10 +288,10 @@ module.exports = {
|
|
|
287
288
|
.map(([k, v]) => ({
|
|
288
289
|
id: k,
|
|
289
290
|
device: v.device,
|
|
290
|
-
task: async () => {
|
|
291
|
+
task: async (getNextInvokeId) => {
|
|
291
292
|
return await smartWriteProperty(
|
|
292
293
|
this.client, v.device, v.writePoints, smartWriteEvent,
|
|
293
|
-
this.maxConcurrentPointWrite, this.concurrentTaskDelay
|
|
294
|
+
this.maxConcurrentPointWrite, this.concurrentTaskDelay, getNextInvokeId
|
|
294
295
|
);
|
|
295
296
|
}
|
|
296
297
|
}));
|
|
@@ -312,7 +313,41 @@ module.exports = {
|
|
|
312
313
|
});
|
|
313
314
|
|
|
314
315
|
await concurrentTasksWithNetworkAwareness(smartWriteEvent, tasks, this.maxConcurrentDeviceWrite);
|
|
316
|
+
|
|
317
|
+
// emit output with msg containing failed points and results
|
|
318
|
+
this.#exportWriteResults();
|
|
315
319
|
}
|
|
316
320
|
|
|
321
|
+
#exportWriteResults() {
|
|
322
|
+
// Separate successful and failed points
|
|
323
|
+
const failedPoints = {};
|
|
324
|
+
let totalPoints = 0;
|
|
325
|
+
let successfulPoints = 0;
|
|
326
|
+
|
|
327
|
+
Object.entries(this.writePointDetails).forEach(([key, value]) => {
|
|
328
|
+
totalPoints++;
|
|
329
|
+
// Check if point has an error property (would be set if write failed)
|
|
330
|
+
if (value.error) {
|
|
331
|
+
failedPoints[key] = value;
|
|
332
|
+
} else {
|
|
333
|
+
successfulPoints++;
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
// Update msg with results
|
|
338
|
+
this.msg.payload = failedPoints;
|
|
339
|
+
this.msg.result = {
|
|
340
|
+
written: successfulPoints,
|
|
341
|
+
failed: Object.keys(failedPoints).length,
|
|
342
|
+
total: totalPoints
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
// Remove input properties from msg
|
|
346
|
+
delete this.msg.devices;
|
|
347
|
+
delete this.msg.points;
|
|
348
|
+
delete this.msg.writePoints;
|
|
349
|
+
|
|
350
|
+
this.eventEmitter.emit(EVENT_OUTPUT, this.msg);
|
|
351
|
+
}
|
|
317
352
|
}
|
|
318
353
|
}
|
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>
|
|
@@ -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) => {
|
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>
|
|
@@ -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
|
});
|