@bitpoolos/edge-bacnet 1.6.7 → 1.6.8
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 +16 -0
- package/bacnet_client.js +149 -24
- package/bacnet_gateway.html +82 -0
- package/bacnet_gateway.js +3 -1
- package/bacnet_write.html +2 -1
- package/common.js +8 -1
- package/package.json +1 -1
- package/resources/node-bacstack-ts/dist/lib/asn1.js +2 -2
- package/resources/node-bacstack-ts/dist/lib/client.js +198 -72
- package/resources/node-bacstack-ts/dist/lib/npdu.js +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.6.8] - 10-03-2026
|
|
4
|
+
|
|
5
|
+
Bug fix:
|
|
6
|
+
|
|
7
|
+
- Spiking values. Large polling sites were experiencing intermittent spiking values, due to a overloaded invokeId stack. This stack has been refactored to be per device, instead of global.
|
|
8
|
+
|
|
9
|
+
- Fixed byteLength errors
|
|
10
|
+
|
|
11
|
+
- Merged github PR's 36 and 37:
|
|
12
|
+
- Fix startup error spam: empty point list during initialization
|
|
13
|
+
- Fix boolean write failures with auto application tag detection
|
|
14
|
+
|
|
15
|
+
Minor update:
|
|
16
|
+
|
|
17
|
+
- Added concurrency management. Max Concurrent Requests option in the gateway node, which throttles how many concurrent requests can be made at any given point.
|
|
18
|
+
|
|
3
19
|
## [1.6.7] - 15-01-2026
|
|
4
20
|
|
|
5
21
|
Bug fix:
|
package/bacnet_client.js
CHANGED
|
@@ -36,9 +36,13 @@ class BacnetClient extends EventEmitter {
|
|
|
36
36
|
that.manualMutex = new Mutex();
|
|
37
37
|
that.pollInProgress = false;
|
|
38
38
|
that.buildJsonInProgress = false;
|
|
39
|
+
that.cacheLoaded = false;
|
|
39
40
|
that.scanMatrix = [];
|
|
40
41
|
that.renderListCount = 0;
|
|
41
42
|
that.portRangeMatrix = config.portRangeMatrix;
|
|
43
|
+
that._requestQueue = []; // Queue of waiting request resolvers
|
|
44
|
+
that._processingQueue = false; // Flag to prevent concurrent queue processing
|
|
45
|
+
that._maxQueueSize = 10000; // Maximum queued requests before rejecting (sized for large sites)
|
|
42
46
|
|
|
43
47
|
try {
|
|
44
48
|
that.roundDecimal = config.roundDecimal;
|
|
@@ -67,6 +71,7 @@ class BacnetClient extends EventEmitter {
|
|
|
67
71
|
port: config.port,
|
|
68
72
|
broadcastAddress: config.broadCastAddr,
|
|
69
73
|
portRangeMatrix: config.portRangeMatrix,
|
|
74
|
+
maxConcurrentRequests: config.maxConcurrentRequests,
|
|
70
75
|
});
|
|
71
76
|
that.setMaxListeners(1);
|
|
72
77
|
|
|
@@ -80,6 +85,8 @@ class BacnetClient extends EventEmitter {
|
|
|
80
85
|
|
|
81
86
|
//query device task
|
|
82
87
|
const queryDevices = new Task("simple task", () => {
|
|
88
|
+
if (!that.cacheLoaded) return;
|
|
89
|
+
|
|
83
90
|
if (!that.pollInProgress && that.enable_device_discovery) {
|
|
84
91
|
that.queryDevices();
|
|
85
92
|
}
|
|
@@ -192,24 +199,83 @@ class BacnetClient extends EventEmitter {
|
|
|
192
199
|
}
|
|
193
200
|
}
|
|
194
201
|
|
|
202
|
+
/**
|
|
203
|
+
* Waits until a request slot is available (throttling).
|
|
204
|
+
* Uses a queue to ensure only one waiter proceeds per available slot.
|
|
205
|
+
* Rejects if the queue is full (backpressure mechanism).
|
|
206
|
+
*
|
|
207
|
+
* The previous implementation used a `while` loop inside `processQueue` which
|
|
208
|
+
* called `nextResolve()` multiple times synchronously. Because Promise
|
|
209
|
+
* resolutions are scheduled as microtasks, none of those continuations ran
|
|
210
|
+
* before the next `canSendRequest()` check — so the while-loop could release
|
|
211
|
+
* dozens of waiters simultaneously even when only one slot was free. The fix
|
|
212
|
+
* is to release exactly ONE waiter per `requestComplete` event, letting the
|
|
213
|
+
* microtask queue drain before the next slot check.
|
|
214
|
+
*/
|
|
215
|
+
_waitForRequestSlot() {
|
|
216
|
+
let that = this;
|
|
217
|
+
return new Promise((resolve, reject) => {
|
|
218
|
+
// Fast path: a slot is available right now
|
|
219
|
+
if (that.client.canSendRequest()) {
|
|
220
|
+
resolve();
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Backpressure: refuse to queue more work than the limit allows
|
|
225
|
+
if (that._requestQueue.length >= that._maxQueueSize) {
|
|
226
|
+
reject(new Error('ERR_REQUEST_QUEUE_FULL: Too many pending requests. Reduce polling frequency or increase Max Concurrent Requests.'));
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Park this request until a slot opens
|
|
231
|
+
that._requestQueue.push(resolve);
|
|
232
|
+
|
|
233
|
+
if (!that._processingQueue) {
|
|
234
|
+
that._processingQueue = true;
|
|
235
|
+
|
|
236
|
+
const processQueue = () => {
|
|
237
|
+
// Release exactly ONE waiter per call. The 'requestComplete' event
|
|
238
|
+
// fires each time a slot is freed, so we will naturally be called
|
|
239
|
+
// again once the released request registers its own callback in the
|
|
240
|
+
// invoke store and eventually completes.
|
|
241
|
+
if (that._requestQueue.length > 0 && that.client.canSendRequest()) {
|
|
242
|
+
const nextResolve = that._requestQueue.shift();
|
|
243
|
+
nextResolve();
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (that._requestQueue.length === 0) {
|
|
247
|
+
that._processingQueue = false;
|
|
248
|
+
that.client.removeListener('requestComplete', processQueue);
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
that.client.on('requestComplete', processQueue);
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
195
257
|
async readCachedFile() {
|
|
196
258
|
let that = this;
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
parsedData.deviceList
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
259
|
+
try {
|
|
260
|
+
if (that.config.cacheFileEnabled) {
|
|
261
|
+
const cachedData = await Read_Config_Async();
|
|
262
|
+
const parsedData = JSON.parse(cachedData);
|
|
263
|
+
if (parsedData && typeof parsedData == "object") {
|
|
264
|
+
// renderList is no longer cached - will be rebuilt by tree builder
|
|
265
|
+
// if (parsedData.renderList) that.renderList = parsedData.renderList;
|
|
266
|
+
if (parsedData.deviceList) {
|
|
267
|
+
parsedData.deviceList.forEach(function (device) {
|
|
268
|
+
let newBacnetDevice = new BacnetDevice(true, device);
|
|
269
|
+
that.deviceList.push(newBacnetDevice);
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
if (parsedData.pointList) that.networkTree = parsedData.pointList;
|
|
273
|
+
// renderListCount is no longer cached - will be recalculated by tree builder
|
|
274
|
+
// if (parsedData.renderListCount) that.renderListCount = parsedData.renderListCount;
|
|
208
275
|
}
|
|
209
|
-
if (parsedData.pointList) that.networkTree = parsedData.pointList;
|
|
210
|
-
// renderListCount is no longer cached - will be recalculated by tree builder
|
|
211
|
-
// if (parsedData.renderListCount) that.renderListCount = parsedData.renderListCount;
|
|
212
276
|
}
|
|
277
|
+
} finally {
|
|
278
|
+
that.cacheLoaded = true;
|
|
213
279
|
}
|
|
214
280
|
}
|
|
215
281
|
|
|
@@ -331,7 +397,7 @@ class BacnetClient extends EventEmitter {
|
|
|
331
397
|
}
|
|
332
398
|
}
|
|
333
399
|
|
|
334
|
-
getProtocolSupported(device) {
|
|
400
|
+
async getProtocolSupported(device) {
|
|
335
401
|
//return protocols support for device
|
|
336
402
|
let that = this;
|
|
337
403
|
let addressObject = {
|
|
@@ -339,6 +405,10 @@ class BacnetClient extends EventEmitter {
|
|
|
339
405
|
port: device.getPort(),
|
|
340
406
|
};
|
|
341
407
|
const readOptions = that.getDeviceSpecificOptions(device);
|
|
408
|
+
|
|
409
|
+
// Wait for a request slot before proceeding
|
|
410
|
+
await that._waitForRequestSlot();
|
|
411
|
+
|
|
342
412
|
return new Promise((resolve, reject) => {
|
|
343
413
|
that.client.readProperty(
|
|
344
414
|
addressObject,
|
|
@@ -674,6 +744,8 @@ class BacnetClient extends EventEmitter {
|
|
|
674
744
|
|
|
675
745
|
// //query device task
|
|
676
746
|
const queryDevices = new Task("simple task", () => {
|
|
747
|
+
if (!that.cacheLoaded) return;
|
|
748
|
+
|
|
677
749
|
if (!that.pollInProgress && that.enable_device_discovery) {
|
|
678
750
|
that.queryDevices();
|
|
679
751
|
}
|
|
@@ -1046,7 +1118,7 @@ class BacnetClient extends EventEmitter {
|
|
|
1046
1118
|
}
|
|
1047
1119
|
|
|
1048
1120
|
//used in the doRead querying work flow
|
|
1049
|
-
updatePoint(device, point) {
|
|
1121
|
+
async updatePoint(device, point) {
|
|
1050
1122
|
let that = this;
|
|
1051
1123
|
let addressObject = {
|
|
1052
1124
|
address: device.getAddress(),
|
|
@@ -1056,6 +1128,9 @@ class BacnetClient extends EventEmitter {
|
|
|
1056
1128
|
// Use device-specific options
|
|
1057
1129
|
const settings = that.getDeviceSpecificOptions(device);
|
|
1058
1130
|
|
|
1131
|
+
// Wait for a request slot before proceeding
|
|
1132
|
+
await that._waitForRequestSlot();
|
|
1133
|
+
|
|
1059
1134
|
return new Promise((resolve, reject) => {
|
|
1060
1135
|
that.client.readProperty(
|
|
1061
1136
|
addressObject,
|
|
@@ -1226,13 +1301,16 @@ class BacnetClient extends EventEmitter {
|
|
|
1226
1301
|
|
|
1227
1302
|
send(index);
|
|
1228
1303
|
|
|
1229
|
-
function send(index) {
|
|
1304
|
+
async function send(index) {
|
|
1230
1305
|
let readOptions = {
|
|
1231
1306
|
maxSegments: that.readPropertyMultipleOptions.maxSegments,
|
|
1232
1307
|
maxApdu: that.readPropertyMultipleOptions.maxApdu,
|
|
1233
1308
|
arrayIndex: index,
|
|
1234
1309
|
};
|
|
1235
1310
|
|
|
1311
|
+
// Wait for a request slot before proceeding
|
|
1312
|
+
await that._waitForRequestSlot();
|
|
1313
|
+
|
|
1236
1314
|
that.client.readProperty(
|
|
1237
1315
|
addressObject,
|
|
1238
1316
|
{ type: baEnum.ObjectType.DEVICE, instance: deviceId },
|
|
@@ -1254,12 +1332,16 @@ class BacnetClient extends EventEmitter {
|
|
|
1254
1332
|
});
|
|
1255
1333
|
}
|
|
1256
1334
|
|
|
1257
|
-
_readObjectWithRequestArray(device, requestArray, readOptions) {
|
|
1335
|
+
async _readObjectWithRequestArray(device, requestArray, readOptions) {
|
|
1258
1336
|
let that = this;
|
|
1259
1337
|
let addressObject = {
|
|
1260
1338
|
address: device.getAddress(),
|
|
1261
1339
|
port: device.getPort(),
|
|
1262
1340
|
};
|
|
1341
|
+
|
|
1342
|
+
// Wait for a request slot before proceeding
|
|
1343
|
+
await that._waitForRequestSlot();
|
|
1344
|
+
|
|
1263
1345
|
return new Promise((resolve, reject) => {
|
|
1264
1346
|
that.client.readPropertyMultiple(addressObject, requestArray, readOptions, (error, value) => {
|
|
1265
1347
|
if (value && value.values) {
|
|
@@ -1335,8 +1417,12 @@ class BacnetClient extends EventEmitter {
|
|
|
1335
1417
|
}
|
|
1336
1418
|
}
|
|
1337
1419
|
|
|
1338
|
-
_readObject(addressObject, type, instance, properties, readOptions) {
|
|
1420
|
+
async _readObject(addressObject, type, instance, properties, readOptions) {
|
|
1339
1421
|
let that = this;
|
|
1422
|
+
|
|
1423
|
+
// Wait for a request slot before proceeding
|
|
1424
|
+
await that._waitForRequestSlot();
|
|
1425
|
+
|
|
1340
1426
|
return new Promise((resolve, reject) => {
|
|
1341
1427
|
const requestArray = [
|
|
1342
1428
|
{
|
|
@@ -1559,10 +1645,14 @@ class BacnetClient extends EventEmitter {
|
|
|
1559
1645
|
port: device.getPort(),
|
|
1560
1646
|
};
|
|
1561
1647
|
|
|
1648
|
+
let objectType = point.meta.objectId.type;
|
|
1649
|
+
let resolvedAppTag = that._resolveAppTag(objectType, options.appTag);
|
|
1650
|
+
let resolvedValue = that._coerceWriteValue(value, resolvedAppTag);
|
|
1651
|
+
|
|
1562
1652
|
let writeObject = {
|
|
1563
1653
|
address: addressObject,
|
|
1564
1654
|
objectId: {
|
|
1565
|
-
type:
|
|
1655
|
+
type: objectType,
|
|
1566
1656
|
instance: point.meta.objectId.instance,
|
|
1567
1657
|
},
|
|
1568
1658
|
values: {
|
|
@@ -1572,8 +1662,8 @@ class BacnetClient extends EventEmitter {
|
|
|
1572
1662
|
},
|
|
1573
1663
|
value: [
|
|
1574
1664
|
{
|
|
1575
|
-
type:
|
|
1576
|
-
value:
|
|
1665
|
+
type: resolvedAppTag,
|
|
1666
|
+
value: resolvedValue,
|
|
1577
1667
|
},
|
|
1578
1668
|
],
|
|
1579
1669
|
},
|
|
@@ -1606,7 +1696,8 @@ class BacnetClient extends EventEmitter {
|
|
|
1606
1696
|
point.options,
|
|
1607
1697
|
(err, value) => {
|
|
1608
1698
|
if (err) {
|
|
1609
|
-
that.
|
|
1699
|
+
let objType = that.getObjectType(point.objectId.type) || point.objectId.type;
|
|
1700
|
+
that.logOut("writeProperty error for " + objType + ":" + point.objectId.instance + " - ", err);
|
|
1610
1701
|
}
|
|
1611
1702
|
}
|
|
1612
1703
|
);
|
|
@@ -1938,7 +2029,7 @@ class BacnetClient extends EventEmitter {
|
|
|
1938
2029
|
const promiseArray = [];
|
|
1939
2030
|
|
|
1940
2031
|
if (typeof pointList === "undefined" || pointList.length === 0) {
|
|
1941
|
-
|
|
2032
|
+
return { deviceList: this.deviceList, pointList: this.networkTree };
|
|
1942
2033
|
}
|
|
1943
2034
|
|
|
1944
2035
|
for (const point of pointList) {
|
|
@@ -2276,6 +2367,40 @@ class BacnetClient extends EventEmitter {
|
|
|
2276
2367
|
}
|
|
2277
2368
|
}
|
|
2278
2369
|
|
|
2370
|
+
_resolveAppTag(objectType, userAppTag) {
|
|
2371
|
+
// If user explicitly set an app tag (not auto), use it
|
|
2372
|
+
if (userAppTag !== -1) return userAppTag;
|
|
2373
|
+
|
|
2374
|
+
// Auto-detect based on BACnet object type
|
|
2375
|
+
switch (objectType) {
|
|
2376
|
+
case 3: // BI
|
|
2377
|
+
case 4: // BO
|
|
2378
|
+
case 5: // BV
|
|
2379
|
+
return 9; // ENUMERATED
|
|
2380
|
+
case 13: // MI
|
|
2381
|
+
case 14: // MO
|
|
2382
|
+
case 19: // MV
|
|
2383
|
+
return 2; // UNSIGNED_INT
|
|
2384
|
+
default:
|
|
2385
|
+
return 4; // REAL
|
|
2386
|
+
}
|
|
2387
|
+
}
|
|
2388
|
+
|
|
2389
|
+
_coerceWriteValue(value, appTag) {
|
|
2390
|
+
switch (appTag) {
|
|
2391
|
+
case 9: // ENUMERATED - binary objects expect 0 (inactive) or 1 (active)
|
|
2392
|
+
if (value === true || value === 1 || value === "1" ||
|
|
2393
|
+
value === "true" || value === "active" || value === "on") {
|
|
2394
|
+
return 1;
|
|
2395
|
+
}
|
|
2396
|
+
return 0;
|
|
2397
|
+
case 2: // UNSIGNED_INT - multistate objects expect positive integers
|
|
2398
|
+
return parseInt(value) || 0;
|
|
2399
|
+
default:
|
|
2400
|
+
return value;
|
|
2401
|
+
}
|
|
2402
|
+
}
|
|
2403
|
+
|
|
2279
2404
|
getObjectType(objectId) {
|
|
2280
2405
|
switch (objectId) {
|
|
2281
2406
|
case 0:
|
package/bacnet_gateway.html
CHANGED
|
@@ -165,6 +165,7 @@
|
|
|
165
165
|
local_device_address: { value: "", required: true },
|
|
166
166
|
local_interface_name: { value: "", required: false },
|
|
167
167
|
apduTimeout: { value: 6000 },
|
|
168
|
+
maxConcurrentRequests: { value: 250 },
|
|
168
169
|
roundDecimal: { value: 2 },
|
|
169
170
|
local_device_port: { value: 47808, required: true },
|
|
170
171
|
apduSize: { value: "5", required: true },
|
|
@@ -837,6 +838,13 @@
|
|
|
837
838
|
oneditsave: function () {
|
|
838
839
|
let node = this;
|
|
839
840
|
|
|
841
|
+
// Validate and clamp maxConcurrentRequests (1-250)
|
|
842
|
+
// BACnet protocol limits invoke IDs to 256, so 250 is the safe max
|
|
843
|
+
let maxConcurrent = parseInt(document.getElementById("node-input-maxConcurrentRequests").value) || 250;
|
|
844
|
+
if (maxConcurrent < 1) maxConcurrent = 1;
|
|
845
|
+
if (maxConcurrent > 250) maxConcurrent = 250;
|
|
846
|
+
document.getElementById("node-input-maxConcurrentRequests").value = maxConcurrent;
|
|
847
|
+
|
|
840
848
|
document.getElementById("node-input-discover_polling_schedule").value = getTimePeriodInSeconds(
|
|
841
849
|
document.getElementById("node-input-discover_polling_schedule_value").value,
|
|
842
850
|
document.getElementById("node-input-discover_polling_schedule_options").value
|
|
@@ -1090,6 +1098,13 @@
|
|
|
1090
1098
|
<input type="text" id="node-input-apduTimeout" placeholder="10000" />
|
|
1091
1099
|
</div>
|
|
1092
1100
|
|
|
1101
|
+
<div class="form-row bp-row">
|
|
1102
|
+
<label for="node-input-maxConcurrentRequests"
|
|
1103
|
+
><i class="icon-tag"></i> <span data-i18n="bitpool-bacnet.label.maxConcurrentRequests"></span>Max Concurrent Requests</label
|
|
1104
|
+
>
|
|
1105
|
+
<input type="number" id="node-input-maxConcurrentRequests" placeholder="250" min="1" max="250" />
|
|
1106
|
+
</div>
|
|
1107
|
+
|
|
1093
1108
|
<div class="form-row bp-row" style="display: none;">
|
|
1094
1109
|
<label for="node-input-apduSize"
|
|
1095
1110
|
><i class="icon-tag"></i> <span data-i18n="bitpool-bacnet.label.apduSize"></span>Max Apdu Size</label
|
|
@@ -1310,6 +1325,7 @@
|
|
|
1310
1325
|
<h3><strong>Discovery Tab</strong></h3>
|
|
1311
1326
|
<ul class="node-ports">
|
|
1312
1327
|
<li>APDU Timeout - BACnet msg timeout option</li>
|
|
1328
|
+
<li>Max Concurrent Requests - Maximum number of simultaneous BACnet requests (1-250). Lower values reduce invoke ID exhaustion on large MSTP networks. Default: 250</li>
|
|
1313
1329
|
<li>Max APDU Size - BACnet max apdu size</li>
|
|
1314
1330
|
<li>Max Segments - BACnet max segments</li>
|
|
1315
1331
|
<li>
|
|
@@ -1341,6 +1357,72 @@
|
|
|
1341
1357
|
<li>Clear Server Points - a schedule for the locally generated BACnet points to get cleared from the node object store</li>
|
|
1342
1358
|
</ul>
|
|
1343
1359
|
|
|
1360
|
+
<h3><strong>Performance Tuning & Throttling</strong></h3>
|
|
1361
|
+
<p>
|
|
1362
|
+
For large BACnet networks, especially those with many MS/TP devices, proper tuning is essential to prevent
|
|
1363
|
+
communication issues like value spiking, timeouts, and data corruption.
|
|
1364
|
+
</p>
|
|
1365
|
+
|
|
1366
|
+
<h4>Understanding the BACnet Invoke ID Limit</h4>
|
|
1367
|
+
<p>
|
|
1368
|
+
The BACnet protocol uses an 8-bit "invoke ID" to match requests with responses. This limits the number of
|
|
1369
|
+
simultaneous outstanding requests to <strong>256</strong>. When polling many devices/points rapidly, this
|
|
1370
|
+
limit can be exhausted, causing responses to be matched with the wrong requests (value spiking).
|
|
1371
|
+
</p>
|
|
1372
|
+
|
|
1373
|
+
<h4>Key Settings for Large Networks</h4>
|
|
1374
|
+
<table style="width:100%; border-collapse: collapse; margin: 10px 0;">
|
|
1375
|
+
<tr style="background-color: #f0f0f0;">
|
|
1376
|
+
<th style="border: 1px solid #ddd; padding: 8px; text-align: left;">Setting</th>
|
|
1377
|
+
<th style="border: 1px solid #ddd; padding: 8px; text-align: left;">Recommended</th>
|
|
1378
|
+
<th style="border: 1px solid #ddd; padding: 8px; text-align: left;">Description</th>
|
|
1379
|
+
</tr>
|
|
1380
|
+
<tr>
|
|
1381
|
+
<td style="border: 1px solid #ddd; padding: 8px;"><strong>Max Concurrent Requests</strong></td>
|
|
1382
|
+
<td style="border: 1px solid #ddd; padding: 8px;">64-100 (MSTP)<br>150-200 (IP only)</td>
|
|
1383
|
+
<td style="border: 1px solid #ddd; padding: 8px;">Limits simultaneous in-flight requests. Lower for slow MSTP networks.</td>
|
|
1384
|
+
</tr>
|
|
1385
|
+
<tr>
|
|
1386
|
+
<td style="border: 1px solid #ddd; padding: 8px;"><strong>APDU Timeout</strong></td>
|
|
1387
|
+
<td style="border: 1px solid #ddd; padding: 8px;">8000-15000ms (MSTP)<br>3000-6000ms (IP only)</td>
|
|
1388
|
+
<td style="border: 1px solid #ddd; padding: 8px;">How long to wait for a response. MSTP token-passing adds latency.</td>
|
|
1389
|
+
</tr>
|
|
1390
|
+
</table>
|
|
1391
|
+
|
|
1392
|
+
<h4>Tuning Guidelines by Network Type</h4>
|
|
1393
|
+
<ul class="node-ports">
|
|
1394
|
+
<li>
|
|
1395
|
+
<strong>BACnet/IP Only (few devices):</strong> Default settings usually work. Max Concurrent: 200-250, Timeout: 3000-6000ms
|
|
1396
|
+
</li>
|
|
1397
|
+
<li>
|
|
1398
|
+
<strong>BACnet/IP with MS/TP routers (moderate):</strong> Max Concurrent: 100-150, Timeout: 6000-10000ms
|
|
1399
|
+
</li>
|
|
1400
|
+
<li>
|
|
1401
|
+
<strong>Large MS/TP Networks (100+ devices):</strong> Max Concurrent: 64-100, Timeout: 10000-15000ms
|
|
1402
|
+
</li>
|
|
1403
|
+
<li>
|
|
1404
|
+
<strong>Very Large Networks (500+ devices):</strong> Max Concurrent: 50-75, Timeout: 12000-20000ms.
|
|
1405
|
+
Consider segmenting polling across multiple read nodes with staggered schedules.
|
|
1406
|
+
</li>
|
|
1407
|
+
</ul>
|
|
1408
|
+
|
|
1409
|
+
<h4>Symptoms of Poor Tuning</h4>
|
|
1410
|
+
<ul class="node-ports">
|
|
1411
|
+
<li><strong>Value Spiking:</strong> Random incorrect values appearing briefly → Lower Max Concurrent Requests</li>
|
|
1412
|
+
<li><strong>Many Timeouts:</strong> Points frequently showing offline → Increase APDU Timeout</li>
|
|
1413
|
+
<li><strong>Slow Discovery:</strong> Devices/points taking too long to appear → May need higher Max Concurrent (if not MSTP-bound)</li>
|
|
1414
|
+
<li><strong>Network Congestion:</strong> Other BACnet traffic affected → Lower Max Concurrent Requests and increase polling intervals</li>
|
|
1415
|
+
</ul>
|
|
1416
|
+
|
|
1417
|
+
<h4>Best Practices</h4>
|
|
1418
|
+
<ul class="node-ports">
|
|
1419
|
+
<li>Start with conservative settings (lower Max Concurrent, higher Timeout) and tune up gradually</li>
|
|
1420
|
+
<li>For MS/TP networks, remember that token-passing adds 50-200ms latency per device hop</li>
|
|
1421
|
+
<li>Use longer polling intervals (5-15 minutes) for stable values; shorter (30s-1min) for critical points only</li>
|
|
1422
|
+
<li>Monitor for timeout errors in the console (enable "Log BACnet Errors to Console")</li>
|
|
1423
|
+
<li>If experiencing issues, reduce Max Concurrent Requests by 25% and test again</li>
|
|
1424
|
+
</ul>
|
|
1425
|
+
|
|
1344
1426
|
<h3><strong>Examples</strong></h3>
|
|
1345
1427
|
<p>
|
|
1346
1428
|
For example flows, please use the examples section for this node. These examples can be found at: Node-red hamburger menu on
|
package/bacnet_gateway.js
CHANGED
|
@@ -38,6 +38,7 @@ module.exports = function (RED) {
|
|
|
38
38
|
this.cacheFileEnabled = config.cacheFileEnabled;
|
|
39
39
|
this.sanitise_device_schedule = config.sanitise_device_schedule;
|
|
40
40
|
this.enable_device_discovery = config.enable_device_discovery;
|
|
41
|
+
this.maxConcurrentRequests = config.maxConcurrentRequests;
|
|
41
42
|
|
|
42
43
|
//client and config store
|
|
43
44
|
this.bacnetConfig = nodeContext.get("bacnetConfig");
|
|
@@ -67,7 +68,8 @@ module.exports = function (RED) {
|
|
|
67
68
|
node.cacheFileEnabled,
|
|
68
69
|
node.sanitise_device_schedule,
|
|
69
70
|
node.portRangeRegisters.filter((ele) => ele.enabled === true),
|
|
70
|
-
node.enable_device_discovery
|
|
71
|
+
node.enable_device_discovery,
|
|
72
|
+
node.maxConcurrentRequests
|
|
71
73
|
);
|
|
72
74
|
|
|
73
75
|
if (typeof node.bacnetClient !== "undefined") {
|
package/bacnet_write.html
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
color: "#00aeef",
|
|
9
9
|
defaults: {
|
|
10
10
|
name: { value: "" },
|
|
11
|
-
applicationTag: { value: "
|
|
11
|
+
applicationTag: { value: "-1" },
|
|
12
12
|
priority: { value: "16" },
|
|
13
13
|
pointsToWrite: { value: [] },
|
|
14
14
|
writeDevices: { value: [] },
|
|
@@ -943,6 +943,7 @@
|
|
|
943
943
|
><i class="icon-tag"></i><span data-i18n="bitpool-bacnet.label.applicationTag"></span> Application Tag</label
|
|
944
944
|
>
|
|
945
945
|
<select id="node-input-applicationTag">
|
|
946
|
+
<option value="-1">AUTO</option>
|
|
946
947
|
<option value="0">NULL</option>
|
|
947
948
|
<option value="1">BOOLEAN</option>
|
|
948
949
|
<option value="2">UNSIGNED_INT</option>
|
package/common.js
CHANGED
|
@@ -67,7 +67,8 @@ class BacnetClientConfig {
|
|
|
67
67
|
cacheFileEnabled,
|
|
68
68
|
sanitise_device_schedule,
|
|
69
69
|
portRangeMatrix,
|
|
70
|
-
enable_device_discovery
|
|
70
|
+
enable_device_discovery,
|
|
71
|
+
maxConcurrentRequests
|
|
71
72
|
) {
|
|
72
73
|
this.apduTimeout = apduTimeout;
|
|
73
74
|
this.localIpAdrress = localIpAdrress;
|
|
@@ -87,6 +88,12 @@ class BacnetClientConfig {
|
|
|
87
88
|
this.sanitise_device_schedule = sanitise_device_schedule;
|
|
88
89
|
this.portRangeMatrix = this.generatePortRangeArray(portRangeMatrix);
|
|
89
90
|
this.enable_device_discovery = enable_device_discovery;
|
|
91
|
+
// Clamp maxConcurrentRequests between 1 and 250
|
|
92
|
+
// BACnet protocol limits invoke IDs to 256, so 250 is the safe maximum
|
|
93
|
+
let clampedMaxConcurrent = parseInt(maxConcurrentRequests) || 250;
|
|
94
|
+
if (clampedMaxConcurrent < 1) clampedMaxConcurrent = 1;
|
|
95
|
+
if (clampedMaxConcurrent > 250) clampedMaxConcurrent = 250;
|
|
96
|
+
this.maxConcurrentRequests = clampedMaxConcurrent;
|
|
90
97
|
}
|
|
91
98
|
|
|
92
99
|
generatePortRangeArray(rangeMatrix) {
|
package/package.json
CHANGED
|
@@ -73,7 +73,7 @@ const encodeBacnetDouble = (buffer, value) => {
|
|
|
73
73
|
};
|
|
74
74
|
const decodeUnsigned = (buffer, offset, length) => ({
|
|
75
75
|
len: length,
|
|
76
|
-
value: buffer.readUIntBE(offset, length)
|
|
76
|
+
value: length === 0 ? 0 : buffer.readUIntBE(offset, length)
|
|
77
77
|
});
|
|
78
78
|
exports.decodeUnsigned = decodeUnsigned;
|
|
79
79
|
const decodeEnumerated = (buffer, offset, lenValue) => {
|
|
@@ -815,7 +815,7 @@ const decodeReadAccessResult = (buffer, offset, apduLen) => {
|
|
|
815
815
|
exports.decodeReadAccessResult = decodeReadAccessResult;
|
|
816
816
|
const decodeSigned = (buffer, offset, length) => ({
|
|
817
817
|
len: length,
|
|
818
|
-
value: buffer.readIntBE(offset, length)
|
|
818
|
+
value: length === 0 ? 0 : buffer.readIntBE(offset, length)
|
|
819
819
|
});
|
|
820
820
|
exports.decodeSigned = decodeSigned;
|
|
821
821
|
const decodeReal = (buffer, offset) => ({
|
|
@@ -36,8 +36,13 @@ const BVLC_HEADER_LENGTH = 4;
|
|
|
36
36
|
class Client extends events_1.EventEmitter {
|
|
37
37
|
constructor(options) {
|
|
38
38
|
super();
|
|
39
|
-
this._invokeCounter = 1;
|
|
40
39
|
this._invokeStore = {};
|
|
40
|
+
// Per-device invoke ID counters: keyed by device address string.
|
|
41
|
+
// BACnet (ASHRAE 135 §5.4) scopes invoke IDs per originator-destination pair,
|
|
42
|
+
// so each device gets its own independent 0-255 counter rather than sharing one
|
|
43
|
+
// global counter across all devices, which would exhaust after only 256 total
|
|
44
|
+
// in-flight requests regardless of how many devices are being polled.
|
|
45
|
+
this._deviceInvokeCounters = {};
|
|
41
46
|
this._lastSequenceNumber = 0;
|
|
42
47
|
this._segmentStore = [];
|
|
43
48
|
options = options || {};
|
|
@@ -50,7 +55,8 @@ class Client extends events_1.EventEmitter {
|
|
|
50
55
|
interface: options.interface,
|
|
51
56
|
transport: options.transport,
|
|
52
57
|
broadcastAddress: options.broadcastAddress || '255.255.255.255',
|
|
53
|
-
apduTimeout: options.apduTimeout || 3000
|
|
58
|
+
apduTimeout: options.apduTimeout || 3000,
|
|
59
|
+
maxConcurrentRequests: options.maxConcurrentRequests || 250
|
|
54
60
|
};
|
|
55
61
|
|
|
56
62
|
this._transport = this._settings.transport || new transport_1.Transport({
|
|
@@ -66,41 +72,136 @@ class Client extends events_1.EventEmitter {
|
|
|
66
72
|
this._transport.open();
|
|
67
73
|
}
|
|
68
74
|
// Helper utils
|
|
69
|
-
|
|
70
|
-
|
|
75
|
+
/**
|
|
76
|
+
* Returns the number of requests currently awaiting responses
|
|
77
|
+
* @returns {number} Active request count
|
|
78
|
+
*/
|
|
79
|
+
getActiveRequestCount() {
|
|
80
|
+
return Object.keys(this._invokeStore).length;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Check if a new request can be sent without risking invoke ID exhaustion
|
|
84
|
+
* @returns {boolean} True if safe to send a new request
|
|
85
|
+
*/
|
|
86
|
+
canSendRequest() {
|
|
87
|
+
return this.getActiveRequestCount() < this._settings.maxConcurrentRequests;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Returns the max concurrent requests setting
|
|
91
|
+
* @returns {number}
|
|
92
|
+
*/
|
|
93
|
+
getMaxConcurrentRequests() {
|
|
94
|
+
return this._settings.maxConcurrentRequests;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Derives a stable, unique string key for a BACnet device from its address.
|
|
98
|
+
*
|
|
99
|
+
* BACnet (ASHRAE 135 §5.4) scopes invoke IDs per originator-destination pair.
|
|
100
|
+
* For IP devices the IP address is the discriminator.
|
|
101
|
+
* For MSTP devices routed through a BBMD/router, multiple devices share the
|
|
102
|
+
* same router IP but have distinct network+MAC addresses — those are used as
|
|
103
|
+
* the key so their invoke ID spaces remain independent.
|
|
104
|
+
*
|
|
105
|
+
* @param {string|object} addressOrObject - The address passed to a service method,
|
|
106
|
+
* or the addressObject reconstructed from an incoming NPDU.
|
|
107
|
+
* @returns {string} A stable device key suitable for indexing _deviceInvokeCounters.
|
|
108
|
+
*/
|
|
109
|
+
_makeDeviceKey(addressOrObject) {
|
|
110
|
+
if (addressOrObject === null || addressOrObject === undefined) return 'default';
|
|
111
|
+
// Plain string IP address
|
|
112
|
+
if (typeof addressOrObject === 'string') return addressOrObject;
|
|
113
|
+
// Guard against port numbers accidentally being passed as the address
|
|
114
|
+
if (typeof addressOrObject === 'number') return 'default';
|
|
115
|
+
if (typeof addressOrObject === 'object') {
|
|
116
|
+
// MSTP routed device — has net (network number) and adr (MAC address array).
|
|
117
|
+
// These fields appear both in outgoing address objects and in the sourceAddress
|
|
118
|
+
// decoded by _handleNpdu from the NPDU header of incoming MSTP responses.
|
|
119
|
+
if (addressOrObject.net !== undefined && addressOrObject.adr !== undefined) {
|
|
120
|
+
let adrHex;
|
|
121
|
+
if (Buffer.isBuffer(addressOrObject.adr)) {
|
|
122
|
+
adrHex = addressOrObject.adr.toString('hex');
|
|
123
|
+
} else if (Array.isArray(addressOrObject.adr)) {
|
|
124
|
+
adrHex = Buffer.from(addressOrObject.adr).toString('hex');
|
|
125
|
+
} else {
|
|
126
|
+
adrHex = String(addressOrObject.adr);
|
|
127
|
+
}
|
|
128
|
+
return `mstp:${addressOrObject.net}:${adrHex}`;
|
|
129
|
+
}
|
|
130
|
+
// Wrapper object like {address: <ip-or-mstp-object>, port: N}
|
|
131
|
+
if (addressOrObject.address !== undefined) {
|
|
132
|
+
return this._makeDeviceKey(addressOrObject.address);
|
|
133
|
+
}
|
|
134
|
+
// Object from _handleNpdu with an ip field but no net/adr (pure IP)
|
|
135
|
+
if (addressOrObject.ip !== undefined) {
|
|
136
|
+
return addressOrObject.ip;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return String(addressOrObject);
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Returns a free invoke ID scoped to the given device key.
|
|
143
|
+
*
|
|
144
|
+
* Each device now gets its own independent 0-255 counter, matching the
|
|
145
|
+
* BACnet spec (ASHRAE 135 §5.4). Previously a single shared counter meant
|
|
146
|
+
* exhaustion after only 256 total in-flight requests across ALL devices,
|
|
147
|
+
* causing the fallback branch to return an already-in-use ID and silently
|
|
148
|
+
* overwrite the existing callback — the direct source of value mismatches.
|
|
149
|
+
*
|
|
150
|
+
* @param {string} deviceKey - Result of _makeDeviceKey() for the target device.
|
|
151
|
+
* @returns {number} An invoke ID (0-255) not currently in use for that device.
|
|
152
|
+
*/
|
|
153
|
+
_getInvokeId(deviceKey) {
|
|
154
|
+
if (!this._deviceInvokeCounters[deviceKey]) {
|
|
155
|
+
this._deviceInvokeCounters[deviceKey] = 1;
|
|
156
|
+
}
|
|
71
157
|
for (let attempts = 0; attempts < 256; attempts++) {
|
|
72
|
-
const id = this.
|
|
73
|
-
if (id >= 256) this.
|
|
74
|
-
|
|
158
|
+
const id = this._deviceInvokeCounters[deviceKey]++;
|
|
159
|
+
if (id >= 256) this._deviceInvokeCounters[deviceKey] = 1;
|
|
75
160
|
const invokeId = id - 1;
|
|
76
|
-
|
|
77
|
-
// If this invoke ID is not currently in use, return it
|
|
78
|
-
if (!this._invokeStore[invokeId]) {
|
|
161
|
+
if (!this._invokeStore[`${deviceKey}:${invokeId}`]) {
|
|
79
162
|
return invokeId;
|
|
80
163
|
}
|
|
81
164
|
}
|
|
82
|
-
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
|
|
86
|
-
|
|
165
|
+
// All 256 IDs in use for this device — caller should throttle before this point.
|
|
166
|
+
// Return the next counter value so at least we don't spin forever; the
|
|
167
|
+
// _waitForRequestSlot mechanism in bacnet_client.js prevents reaching here
|
|
168
|
+
// under normal operation.
|
|
169
|
+
const id = this._deviceInvokeCounters[deviceKey]++;
|
|
170
|
+
if (id >= 256) this._deviceInvokeCounters[deviceKey] = 1;
|
|
171
|
+
debug('All 256 invoke IDs exhausted for device', deviceKey, '- reusing ID which may cause value mismatch');
|
|
87
172
|
return id - 1;
|
|
88
173
|
}
|
|
89
|
-
|
|
90
|
-
|
|
174
|
+
/**
|
|
175
|
+
* Fires the stored callback for the given device+invokeId pair.
|
|
176
|
+
* Using a compound key prevents a response from device A triggering the
|
|
177
|
+
* callback registered for device B when both happen to share the same
|
|
178
|
+
* numeric invoke ID value.
|
|
179
|
+
*/
|
|
180
|
+
_invokeCallback(deviceKey, id, err, result) {
|
|
181
|
+
const storeKey = `${deviceKey}:${id}`;
|
|
182
|
+
const callback = this._invokeStore[storeKey];
|
|
91
183
|
if (callback)
|
|
92
184
|
return callback(err, result);
|
|
93
|
-
debug('InvokeId
|
|
185
|
+
debug('InvokeId', id, 'for device', deviceKey, 'not found -> drop package');
|
|
94
186
|
}
|
|
95
|
-
|
|
187
|
+
/**
|
|
188
|
+
* Registers a callback for the given device+invokeId, with a timeout guard.
|
|
189
|
+
* The compound store key means callbacks are isolated per device, so a late
|
|
190
|
+
* response from a slow MSTP device cannot accidentally resolve a callback
|
|
191
|
+
* that belongs to a different device.
|
|
192
|
+
*/
|
|
193
|
+
_addCallback(deviceKey, id, callback) {
|
|
194
|
+
const storeKey = `${deviceKey}:${id}`;
|
|
96
195
|
const timeout = setTimeout(() => {
|
|
97
|
-
delete this._invokeStore[
|
|
196
|
+
delete this._invokeStore[storeKey];
|
|
98
197
|
callback(new Error('ERR_TIMEOUT'));
|
|
198
|
+
this.emit('requestComplete', { activeCount: this.getActiveRequestCount(), timedOut: true });
|
|
99
199
|
}, this._settings.apduTimeout);
|
|
100
|
-
this._invokeStore[
|
|
200
|
+
this._invokeStore[storeKey] = (err, data) => {
|
|
101
201
|
clearTimeout(timeout);
|
|
102
|
-
delete this._invokeStore[
|
|
202
|
+
delete this._invokeStore[storeKey];
|
|
103
203
|
callback(err, data);
|
|
204
|
+
this.emit('requestComplete', { activeCount: this.getActiveRequestCount(), timedOut: false });
|
|
104
205
|
};
|
|
105
206
|
}
|
|
106
207
|
_getBuffer() {
|
|
@@ -110,17 +211,17 @@ class Client extends events_1.EventEmitter {
|
|
|
110
211
|
};
|
|
111
212
|
}
|
|
112
213
|
// Service Handlers
|
|
113
|
-
_processError(invokeId, buffer, offset, length) {
|
|
214
|
+
_processError(deviceKey, invokeId, buffer, offset, length) {
|
|
114
215
|
try {
|
|
115
216
|
const result = baServices.error.decode(buffer, offset);
|
|
116
217
|
if (!result)
|
|
117
218
|
return debug('Couldn`t decode Error');
|
|
118
|
-
this._invokeCallback(invokeId, new Error('BacnetError - Class:' + result.class + ' - Code:' + result.code));
|
|
219
|
+
this._invokeCallback(deviceKey, invokeId, new Error('BacnetError - Class:' + result.class + ' - Code:' + result.code));
|
|
119
220
|
} catch (e) {
|
|
120
221
|
}
|
|
121
222
|
}
|
|
122
|
-
_processAbort(invokeId, reason) {
|
|
123
|
-
this._invokeCallback(invokeId, new Error('BacnetAbort - Reason:' + reason));
|
|
223
|
+
_processAbort(deviceKey, invokeId, reason) {
|
|
224
|
+
this._invokeCallback(deviceKey, invokeId, new Error('BacnetAbort - Reason:' + reason));
|
|
124
225
|
}
|
|
125
226
|
_segmentAckResponse(receiver, port, negative, server, originalInvokeId, sequencenumber, actualWindowSize) {
|
|
126
227
|
const buffer = this._getBuffer();
|
|
@@ -402,6 +503,10 @@ class Client extends events_1.EventEmitter {
|
|
|
402
503
|
|
|
403
504
|
_handlePdu(address, type, buffer, offset, length, addressObject, destAddressObject, port) {
|
|
404
505
|
let result;
|
|
506
|
+
// Derive the device key from the source address. For MSTP devices routed
|
|
507
|
+
// through a BBMD the addressObject carries the NPDU source (net + adr) which
|
|
508
|
+
// uniquely identifies the MSTP node. For plain IP the IP string is used.
|
|
509
|
+
const deviceKey = this._makeDeviceKey(addressObject);
|
|
405
510
|
// Handle different PDU types
|
|
406
511
|
switch (type & baEnum.PDU_TYPE_MASK) {
|
|
407
512
|
case baEnum.PduTypes.UNCONFIRMED_REQUEST:
|
|
@@ -412,12 +517,12 @@ class Client extends events_1.EventEmitter {
|
|
|
412
517
|
result = baApdu.decodeSimpleAck(buffer, offset);
|
|
413
518
|
offset += result.len;
|
|
414
519
|
length -= result.len;
|
|
415
|
-
this._invokeCallback(result.invokeId, null, { result: result, buffer: buffer, offset: offset + result.len, length: length - result.len });
|
|
520
|
+
this._invokeCallback(deviceKey, result.invokeId, null, { result: result, buffer: buffer, offset: offset + result.len, length: length - result.len });
|
|
416
521
|
break;
|
|
417
522
|
case baEnum.PduTypes.COMPLEX_ACK:
|
|
418
523
|
result = baApdu.decodeComplexAck(buffer, offset);
|
|
419
524
|
if ((type & baEnum.PduConReqBits.SEGMENTED_MESSAGE) === 0) {
|
|
420
|
-
this._invokeCallback(result.invokeId, null, { result: result, buffer: buffer, offset: offset + result.len, length: length - result.len });
|
|
525
|
+
this._invokeCallback(deviceKey, result.invokeId, null, { result: result, buffer: buffer, offset: offset + result.len, length: length - result.len });
|
|
421
526
|
} else {
|
|
422
527
|
this._processSegment(addressObject, result.type, result.service, result.invokeId, baEnum.MaxSegmentsAccepted.SEGMENTS_0, baEnum.MaxApduLengthAccepted.OCTETS_50, false, result.sequencenumber, result.proposedWindowNumber, buffer, offset + result.len, length - result.len, port);
|
|
423
528
|
}
|
|
@@ -429,12 +534,12 @@ class Client extends events_1.EventEmitter {
|
|
|
429
534
|
break;
|
|
430
535
|
case baEnum.PduTypes.ERROR:
|
|
431
536
|
result = baApdu.decodeError(buffer, offset);
|
|
432
|
-
this._processError(result.invokeId, buffer, offset + result.len, length - result.len);
|
|
537
|
+
this._processError(deviceKey, result.invokeId, buffer, offset + result.len, length - result.len);
|
|
433
538
|
break;
|
|
434
539
|
case baEnum.PduTypes.REJECT:
|
|
435
540
|
case baEnum.PduTypes.ABORT:
|
|
436
541
|
result = baApdu.decodeAbort(buffer, offset);
|
|
437
|
-
this._processAbort(result.invokeId, result.reason);
|
|
542
|
+
this._processAbort(deviceKey, result.invokeId, result.reason);
|
|
438
543
|
break;
|
|
439
544
|
case baEnum.PduTypes.CONFIRMED_REQUEST:
|
|
440
545
|
result = baApdu.decodeConfirmedServiceRequest(buffer, offset);
|
|
@@ -639,11 +744,12 @@ class Client extends events_1.EventEmitter {
|
|
|
639
744
|
|
|
640
745
|
let address = addressObject.address ? addressObject.address : addressObject;
|
|
641
746
|
let port = addressObject.port ? addressObject.port : this._settings.port;
|
|
747
|
+
const deviceKey = this._makeDeviceKey(addressObject);
|
|
642
748
|
|
|
643
749
|
const settings = {
|
|
644
750
|
maxSegments: options.maxSegments || baEnum.MaxSegmentsAccepted.SEGMENTS_65,
|
|
645
751
|
maxApdu: options.maxApdu || baEnum.MaxApduLengthAccepted.OCTETS_1476,
|
|
646
|
-
invokeId: options.invokeId
|
|
752
|
+
invokeId: (options.invokeId !== undefined && options.invokeId !== null) ? options.invokeId : this._getInvokeId(deviceKey),
|
|
647
753
|
arrayIndex: options.arrayIndex || baEnum.ASN1_ARRAY_ALL
|
|
648
754
|
};
|
|
649
755
|
const buffer = this._getBuffer();
|
|
@@ -653,7 +759,7 @@ class Client extends events_1.EventEmitter {
|
|
|
653
759
|
baServices.readProperty.encode(buffer, objectId.type, objectId.instance, propertyId, settings.arrayIndex);
|
|
654
760
|
baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU, buffer.offset);
|
|
655
761
|
this.sendBvlc(address, port, buffer);
|
|
656
|
-
this._addCallback(settings.invokeId, (err, data) => {
|
|
762
|
+
this._addCallback(deviceKey, settings.invokeId, (err, data) => {
|
|
657
763
|
try {
|
|
658
764
|
if (err)
|
|
659
765
|
return next(err);
|
|
@@ -699,11 +805,12 @@ class Client extends events_1.EventEmitter {
|
|
|
699
805
|
|
|
700
806
|
let address = addressObject.address ? addressObject.address : addressObject;
|
|
701
807
|
let port = addressObject.port ? addressObject.port : this._settings.port;
|
|
808
|
+
const deviceKey = this._makeDeviceKey(addressObject);
|
|
702
809
|
|
|
703
810
|
const settings = {
|
|
704
811
|
maxSegments: options.maxSegments || baEnum.MaxSegmentsAccepted.SEGMENTS_65,
|
|
705
812
|
maxApdu: options.maxApdu || baEnum.MaxApduLengthAccepted.OCTETS_1476,
|
|
706
|
-
invokeId: options.invokeId
|
|
813
|
+
invokeId: (options.invokeId !== undefined && options.invokeId !== null) ? options.invokeId : this._getInvokeId(deviceKey),
|
|
707
814
|
arrayIndex: options.arrayIndex || baEnum.ASN1_ARRAY_ALL,
|
|
708
815
|
priority: options.priority
|
|
709
816
|
};
|
|
@@ -713,7 +820,7 @@ class Client extends events_1.EventEmitter {
|
|
|
713
820
|
baServices.writeProperty.encode(buffer, objectId.type, objectId.instance, propertyId, settings.arrayIndex, settings.priority, values);
|
|
714
821
|
baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU, buffer.offset);
|
|
715
822
|
this.sendBvlc(address, port, buffer);
|
|
716
|
-
this._addCallback(settings.invokeId, (err) => next(err));
|
|
823
|
+
this._addCallback(deviceKey, settings.invokeId, (err) => next(err));
|
|
717
824
|
}
|
|
718
825
|
/**
|
|
719
826
|
* The readPropertyMultiple command reads multiple properties in multiple objects from a device.
|
|
@@ -746,11 +853,12 @@ class Client extends events_1.EventEmitter {
|
|
|
746
853
|
|
|
747
854
|
let address = addressObject.address ? addressObject.address : addressObject;
|
|
748
855
|
let port = addressObject.port ? addressObject.port : this._settings.port;
|
|
856
|
+
const deviceKey = this._makeDeviceKey(addressObject);
|
|
749
857
|
|
|
750
858
|
const settings = {
|
|
751
859
|
maxSegments: options.maxSegments || baEnum.MaxSegmentsAccepted.SEGMENTS_65,
|
|
752
860
|
maxApdu: options.maxApdu || baEnum.MaxApduLengthAccepted.OCTETS_1476,
|
|
753
|
-
invokeId: options.invokeId
|
|
861
|
+
invokeId: (options.invokeId !== undefined && options.invokeId !== null) ? options.invokeId : this._getInvokeId(deviceKey)
|
|
754
862
|
};
|
|
755
863
|
const buffer = this._getBuffer();
|
|
756
864
|
baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE | baEnum.NpduControlBits.EXPECTING_REPLY, address, null, DEFAULT_HOP_COUNT, baEnum.NetworkLayerMessageType.WHO_IS_ROUTER_TO_NETWORK, 0);
|
|
@@ -759,7 +867,7 @@ class Client extends events_1.EventEmitter {
|
|
|
759
867
|
baServices.readPropertyMultiple.encode(buffer, propertiesArray);
|
|
760
868
|
baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU, buffer.offset);
|
|
761
869
|
this.sendBvlc(address, port, buffer);
|
|
762
|
-
this._addCallback(settings.invokeId, (err, data) => {
|
|
870
|
+
this._addCallback(deviceKey, settings.invokeId, (err, data) => {
|
|
763
871
|
try {
|
|
764
872
|
if (err)
|
|
765
873
|
return next(err);
|
|
@@ -811,11 +919,12 @@ class Client extends events_1.EventEmitter {
|
|
|
811
919
|
|
|
812
920
|
let address = addressObject.address ? addressObject.address : addressObject;
|
|
813
921
|
let port = addressObject.port ? addressObject.port : this._settings.port;
|
|
922
|
+
const deviceKey = this._makeDeviceKey(addressObject);
|
|
814
923
|
|
|
815
924
|
const settings = {
|
|
816
925
|
maxSegments: options.maxSegments || baEnum.MaxSegmentsAccepted.SEGMENTS_65,
|
|
817
926
|
maxApdu: options.maxApdu || baEnum.MaxApduLengthAccepted.OCTETS_1476,
|
|
818
|
-
invokeId: options.invokeId
|
|
927
|
+
invokeId: (options.invokeId !== undefined && options.invokeId !== null) ? options.invokeId : this._getInvokeId(deviceKey)
|
|
819
928
|
};
|
|
820
929
|
const buffer = this._getBuffer();
|
|
821
930
|
baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE | baEnum.NpduControlBits.EXPECTING_REPLY, address);
|
|
@@ -823,7 +932,7 @@ class Client extends events_1.EventEmitter {
|
|
|
823
932
|
baServices.writePropertyMultiple.encodeObject(buffer, values);
|
|
824
933
|
//baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU, buffer.offset);
|
|
825
934
|
this.sendBvlc(address, port, buffer);
|
|
826
|
-
this._addCallback(settings.invokeId, (err) => next(err));
|
|
935
|
+
this._addCallback(deviceKey, settings.invokeId, (err) => next(err));
|
|
827
936
|
}
|
|
828
937
|
/**
|
|
829
938
|
* The deviceCommunicationControl command enables or disables network communication of the target device.
|
|
@@ -849,10 +958,11 @@ class Client extends events_1.EventEmitter {
|
|
|
849
958
|
next = next || options;
|
|
850
959
|
let address = addressObject.address ? addressObject.address : addressObject;
|
|
851
960
|
let port = addressObject.port ? addressObject.port : this._settings.port;
|
|
961
|
+
const deviceKey = this._makeDeviceKey(addressObject);
|
|
852
962
|
const settings = {
|
|
853
963
|
maxSegments: options.maxSegments || baEnum.MaxSegmentsAccepted.SEGMENTS_65,
|
|
854
964
|
maxApdu: options.maxApdu || baEnum.MaxApduLengthAccepted.OCTETS_1476,
|
|
855
|
-
invokeId: options.invokeId
|
|
965
|
+
invokeId: (options.invokeId !== undefined && options.invokeId !== null) ? options.invokeId : this._getInvokeId(deviceKey),
|
|
856
966
|
password: options.password
|
|
857
967
|
};
|
|
858
968
|
const buffer = this._getBuffer();
|
|
@@ -861,7 +971,7 @@ class Client extends events_1.EventEmitter {
|
|
|
861
971
|
baServices.deviceCommunicationControl.encode(buffer, timeDuration, enableDisable, settings.password);
|
|
862
972
|
//baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU, buffer.offset);
|
|
863
973
|
this.sendBvlc(address, port, buffer);
|
|
864
|
-
this._addCallback(settings.invokeId, (err) => next(err));
|
|
974
|
+
this._addCallback(deviceKey, settings.invokeId, (err) => next(err));
|
|
865
975
|
}
|
|
866
976
|
/**
|
|
867
977
|
* The reinitializeDevice command initiates a restart of the target device.
|
|
@@ -886,10 +996,11 @@ class Client extends events_1.EventEmitter {
|
|
|
886
996
|
next = next || options;
|
|
887
997
|
let address = addressObject.address ? addressObject.address : addressObject;
|
|
888
998
|
let port = addressObject.port ? addressObject.port : this._settings.port;
|
|
999
|
+
const deviceKey = this._makeDeviceKey(addressObject);
|
|
889
1000
|
const settings = {
|
|
890
1001
|
maxSegments: options.maxSegments || baEnum.MaxSegmentsAccepted.SEGMENTS_65,
|
|
891
1002
|
maxApdu: options.maxApdu || baEnum.MaxApduLengthAccepted.OCTETS_1476,
|
|
892
|
-
invokeId: options.invokeId
|
|
1003
|
+
invokeId: (options.invokeId !== undefined && options.invokeId !== null) ? options.invokeId : this._getInvokeId(deviceKey),
|
|
893
1004
|
password: options.password
|
|
894
1005
|
};
|
|
895
1006
|
const buffer = this._getBuffer();
|
|
@@ -898,7 +1009,7 @@ class Client extends events_1.EventEmitter {
|
|
|
898
1009
|
baServices.reinitializeDevice.encode(buffer, state, settings.password);
|
|
899
1010
|
//baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU, buffer.offset);
|
|
900
1011
|
this.sendBvlc(address, port, buffer);
|
|
901
|
-
this._addCallback(settings.invokeId, (err) => next(err));
|
|
1012
|
+
this._addCallback(deviceKey, settings.invokeId, (err) => next(err));
|
|
902
1013
|
}
|
|
903
1014
|
/**
|
|
904
1015
|
* The writeFile command writes a file buffer to a specific position of a file object.
|
|
@@ -926,10 +1037,11 @@ class Client extends events_1.EventEmitter {
|
|
|
926
1037
|
next = next || options;
|
|
927
1038
|
let address = addressObject.address ? addressObject.address : addressObject;
|
|
928
1039
|
let port = addressObject.port ? addressObject.port : this._settings.port;
|
|
1040
|
+
const deviceKey = this._makeDeviceKey(addressObject);
|
|
929
1041
|
const settings = {
|
|
930
1042
|
maxSegments: options.maxSegments || baEnum.MaxSegmentsAccepted.SEGMENTS_65,
|
|
931
1043
|
maxApdu: options.maxApdu || baEnum.MaxApduLengthAccepted.OCTETS_1476,
|
|
932
|
-
invokeId: options.invokeId
|
|
1044
|
+
invokeId: (options.invokeId !== undefined && options.invokeId !== null) ? options.invokeId : this._getInvokeId(deviceKey)
|
|
933
1045
|
};
|
|
934
1046
|
const buffer = this._getBuffer();
|
|
935
1047
|
baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE | baEnum.NpduControlBits.EXPECTING_REPLY, address);
|
|
@@ -937,7 +1049,7 @@ class Client extends events_1.EventEmitter {
|
|
|
937
1049
|
baServices.atomicWriteFile.encode(buffer, false, objectId, position, fileBuffer);
|
|
938
1050
|
//baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU, buffer.offset);
|
|
939
1051
|
this.sendBvlc(address, port, buffer);
|
|
940
|
-
this._addCallback(settings.invokeId, (err, data) => {
|
|
1052
|
+
this._addCallback(deviceKey, settings.invokeId, (err, data) => {
|
|
941
1053
|
if (err)
|
|
942
1054
|
return next(err);
|
|
943
1055
|
const result = baServices.atomicWriteFile.decodeAcknowledge(data.buffer, data.offset);
|
|
@@ -972,10 +1084,11 @@ class Client extends events_1.EventEmitter {
|
|
|
972
1084
|
next = next || options;
|
|
973
1085
|
let address = addressObject.address ? addressObject.address : addressObject;
|
|
974
1086
|
let port = addressObject.port ? addressObject.port : this._settings.port;
|
|
1087
|
+
const deviceKey = this._makeDeviceKey(addressObject);
|
|
975
1088
|
const settings = {
|
|
976
1089
|
maxSegments: options.maxSegments || baEnum.MaxSegmentsAccepted.SEGMENTS_65,
|
|
977
1090
|
maxApdu: options.maxApdu || baEnum.MaxApduLengthAccepted.OCTETS_1476,
|
|
978
|
-
invokeId: options.invokeId
|
|
1091
|
+
invokeId: (options.invokeId !== undefined && options.invokeId !== null) ? options.invokeId : this._getInvokeId(deviceKey)
|
|
979
1092
|
};
|
|
980
1093
|
const buffer = this._getBuffer();
|
|
981
1094
|
baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE | baEnum.NpduControlBits.EXPECTING_REPLY, address);
|
|
@@ -983,7 +1096,7 @@ class Client extends events_1.EventEmitter {
|
|
|
983
1096
|
baServices.atomicReadFile.encode(buffer, true, objectId, position, count);
|
|
984
1097
|
//baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU, buffer.offset);
|
|
985
1098
|
this.sendBvlc(address, port, buffer);
|
|
986
|
-
this._addCallback(settings.invokeId, (err, data) => {
|
|
1099
|
+
this._addCallback(deviceKey, settings.invokeId, (err, data) => {
|
|
987
1100
|
if (err)
|
|
988
1101
|
return next(err);
|
|
989
1102
|
const result = baServices.atomicReadFile.decodeAcknowledge(data.buffer, data.offset);
|
|
@@ -1018,10 +1131,11 @@ class Client extends events_1.EventEmitter {
|
|
|
1018
1131
|
next = next || options;
|
|
1019
1132
|
let address = addressObject.address ? addressObject.address : addressObject;
|
|
1020
1133
|
let port = addressObject.port ? addressObject.port : this._settings.port;
|
|
1134
|
+
const deviceKey = this._makeDeviceKey(addressObject);
|
|
1021
1135
|
const settings = {
|
|
1022
1136
|
maxSegments: options.maxSegments || baEnum.MaxSegmentsAccepted.SEGMENTS_65,
|
|
1023
1137
|
maxApdu: options.maxApdu || baEnum.MaxApduLengthAccepted.OCTETS_1476,
|
|
1024
|
-
invokeId: options.invokeId
|
|
1138
|
+
invokeId: (options.invokeId !== undefined && options.invokeId !== null) ? options.invokeId : this._getInvokeId(deviceKey)
|
|
1025
1139
|
};
|
|
1026
1140
|
const buffer = this._getBuffer();
|
|
1027
1141
|
baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE | baEnum.NpduControlBits.EXPECTING_REPLY, address);
|
|
@@ -1029,7 +1143,7 @@ class Client extends events_1.EventEmitter {
|
|
|
1029
1143
|
baServices.readRange.encode(buffer, objectId, baEnum.PropertyIdentifier.LOG_BUFFER, baEnum.ASN1_ARRAY_ALL, baEnum.ReadRangeType.BY_POSITION, idxBegin, new Date(), quantity);
|
|
1030
1144
|
//baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU, buffer.offset);
|
|
1031
1145
|
this.sendBvlc(address, port, buffer);
|
|
1032
|
-
this._addCallback(settings.invokeId, (err, data) => {
|
|
1146
|
+
this._addCallback(deviceKey, settings.invokeId, (err, data) => {
|
|
1033
1147
|
if (err)
|
|
1034
1148
|
return next(err);
|
|
1035
1149
|
const result = baServices.readRange.decodeAcknowledge(data.buffer, data.offset, data.length);
|
|
@@ -1066,10 +1180,11 @@ class Client extends events_1.EventEmitter {
|
|
|
1066
1180
|
next = next || options;
|
|
1067
1181
|
let address = addressObject.address ? addressObject.address : addressObject;
|
|
1068
1182
|
let port = addressObject.port ? addressObject.port : this._settings.port;
|
|
1183
|
+
const deviceKey = this._makeDeviceKey(addressObject);
|
|
1069
1184
|
const settings = {
|
|
1070
1185
|
maxSegments: options.maxSegments || baEnum.MaxSegmentsAccepted.SEGMENTS_65,
|
|
1071
1186
|
maxApdu: options.maxApdu || baEnum.MaxApduLengthAccepted.OCTETS_1476,
|
|
1072
|
-
invokeId: options.invokeId
|
|
1187
|
+
invokeId: (options.invokeId !== undefined && options.invokeId !== null) ? options.invokeId : this._getInvokeId(deviceKey)
|
|
1073
1188
|
};
|
|
1074
1189
|
const buffer = this._getBuffer();
|
|
1075
1190
|
baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE | baEnum.NpduControlBits.EXPECTING_REPLY, address);
|
|
@@ -1077,7 +1192,7 @@ class Client extends events_1.EventEmitter {
|
|
|
1077
1192
|
baServices.subscribeCov.encode(buffer, subscribeId, objectId, cancel, issueConfirmedNotifications, lifetime);
|
|
1078
1193
|
//baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU, buffer.offset);
|
|
1079
1194
|
this.sendBvlc(address, port, buffer);
|
|
1080
|
-
this._addCallback(settings.invokeId, (err) => next(err));
|
|
1195
|
+
this._addCallback(deviceKey, settings.invokeId, (err) => next(err));
|
|
1081
1196
|
}
|
|
1082
1197
|
/**
|
|
1083
1198
|
* The subscribeProperty command subscribes to a specific property of an object for "Change of Value" notifications.
|
|
@@ -1109,10 +1224,11 @@ class Client extends events_1.EventEmitter {
|
|
|
1109
1224
|
next = next || options;
|
|
1110
1225
|
let address = addressObject.address ? addressObject.address : addressObject;
|
|
1111
1226
|
let port = addressObject.port ? addressObject.port : this._settings.port;
|
|
1227
|
+
const deviceKey = this._makeDeviceKey(addressObject);
|
|
1112
1228
|
const settings = {
|
|
1113
1229
|
maxSegments: options.maxSegments || baEnum.MaxSegmentsAccepted.SEGMENTS_65,
|
|
1114
1230
|
maxApdu: options.maxApdu || baEnum.MaxApduLengthAccepted.OCTETS_1476,
|
|
1115
|
-
invokeId: options.invokeId
|
|
1231
|
+
invokeId: (options.invokeId !== undefined && options.invokeId !== null) ? options.invokeId : this._getInvokeId(deviceKey)
|
|
1116
1232
|
};
|
|
1117
1233
|
const buffer = this._getBuffer();
|
|
1118
1234
|
baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE | baEnum.NpduControlBits.EXPECTING_REPLY, address);
|
|
@@ -1120,16 +1236,17 @@ class Client extends events_1.EventEmitter {
|
|
|
1120
1236
|
baServices.subscribeProperty.encode(buffer, subscribeId, objectId, cancel, issueConfirmedNotifications, 0, monitoredProperty, false, 0x0f);
|
|
1121
1237
|
//baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU, buffer.offset);
|
|
1122
1238
|
this.sendBvlc(address, port, buffer);
|
|
1123
|
-
this._addCallback(settings.invokeId, (err) => next(err));
|
|
1239
|
+
this._addCallback(deviceKey, settings.invokeId, (err) => next(err));
|
|
1124
1240
|
}
|
|
1125
1241
|
createObject(addressObject, objectId, values, options, next) {
|
|
1126
1242
|
next = next || options;
|
|
1127
1243
|
let address = addressObject.address ? addressObject.address : addressObject;
|
|
1128
1244
|
let port = addressObject.port ? addressObject.port : this._settings.port;
|
|
1245
|
+
const deviceKey = this._makeDeviceKey(addressObject);
|
|
1129
1246
|
const settings = {
|
|
1130
1247
|
maxSegments: options.maxSegments || baEnum.MaxSegmentsAccepted.SEGMENTS_65,
|
|
1131
1248
|
maxApdu: options.maxApdu || baEnum.MaxApduLengthAccepted.OCTETS_1476,
|
|
1132
|
-
invokeId: options.invokeId
|
|
1249
|
+
invokeId: (options.invokeId !== undefined && options.invokeId !== null) ? options.invokeId : this._getInvokeId(deviceKey)
|
|
1133
1250
|
};
|
|
1134
1251
|
const buffer = this._getBuffer();
|
|
1135
1252
|
baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE | baEnum.NpduControlBits.EXPECTING_REPLY, address);
|
|
@@ -1137,7 +1254,7 @@ class Client extends events_1.EventEmitter {
|
|
|
1137
1254
|
baServices.createObject.encode(buffer, objectId, values);
|
|
1138
1255
|
//baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU, buffer.offset);
|
|
1139
1256
|
this.sendBvlc(address, port, buffer);
|
|
1140
|
-
this._addCallback(settings.invokeId, (err) => next(err));
|
|
1257
|
+
this._addCallback(deviceKey, settings.invokeId, (err) => next(err));
|
|
1141
1258
|
}
|
|
1142
1259
|
/**
|
|
1143
1260
|
* The deleteObject command removes an object instance from a target device.
|
|
@@ -1163,10 +1280,11 @@ class Client extends events_1.EventEmitter {
|
|
|
1163
1280
|
next = next || options;
|
|
1164
1281
|
let address = addressObject.address ? addressObject.address : addressObject;
|
|
1165
1282
|
let port = addressObject.port ? addressObject.port : this._settings.port;
|
|
1283
|
+
const deviceKey = this._makeDeviceKey(addressObject);
|
|
1166
1284
|
const settings = {
|
|
1167
1285
|
maxSegments: options.maxSegments || baEnum.MaxSegmentsAccepted.SEGMENTS_65,
|
|
1168
1286
|
maxApdu: options.maxApdu || baEnum.MaxApduLengthAccepted.OCTETS_1476,
|
|
1169
|
-
invokeId: options.invokeId
|
|
1287
|
+
invokeId: (options.invokeId !== undefined && options.invokeId !== null) ? options.invokeId : this._getInvokeId(deviceKey)
|
|
1170
1288
|
};
|
|
1171
1289
|
const buffer = this._getBuffer();
|
|
1172
1290
|
baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE | baEnum.NpduControlBits.EXPECTING_REPLY, address);
|
|
@@ -1174,16 +1292,17 @@ class Client extends events_1.EventEmitter {
|
|
|
1174
1292
|
baServices.deleteObject.encode(buffer, objectId);
|
|
1175
1293
|
//baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU, buffer.offset);
|
|
1176
1294
|
this.sendBvlc(address, port, buffer);
|
|
1177
|
-
this._addCallback(settings.invokeId, (err) => next(err));
|
|
1295
|
+
this._addCallback(deviceKey, settings.invokeId, (err) => next(err));
|
|
1178
1296
|
}
|
|
1179
1297
|
removeListElement(addressObject, objectId, reference, values, options, next) {
|
|
1180
1298
|
next = next || options;
|
|
1181
1299
|
let address = addressObject.address ? addressObject.address : addressObject;
|
|
1182
1300
|
let port = addressObject.port ? addressObject.port : this._settings.port;
|
|
1301
|
+
const deviceKey = this._makeDeviceKey(addressObject);
|
|
1183
1302
|
const settings = {
|
|
1184
1303
|
maxSegments: options.maxSegments || baEnum.MaxSegmentsAccepted.SEGMENTS_65,
|
|
1185
1304
|
maxApdu: options.maxApdu || baEnum.MaxApduLengthAccepted.OCTETS_1476,
|
|
1186
|
-
invokeId: options.invokeId
|
|
1305
|
+
invokeId: (options.invokeId !== undefined && options.invokeId !== null) ? options.invokeId : this._getInvokeId(deviceKey)
|
|
1187
1306
|
};
|
|
1188
1307
|
const buffer = this._getBuffer();
|
|
1189
1308
|
baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE | baEnum.NpduControlBits.EXPECTING_REPLY, address);
|
|
@@ -1191,16 +1310,17 @@ class Client extends events_1.EventEmitter {
|
|
|
1191
1310
|
baServices.addListElement.encode(buffer, objectId, reference.id, reference.index, values);
|
|
1192
1311
|
//baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU, buffer.offset);
|
|
1193
1312
|
this.sendBvlc(address, port, buffer);
|
|
1194
|
-
this._addCallback(settings.invokeId, (err) => next(err));
|
|
1313
|
+
this._addCallback(deviceKey, settings.invokeId, (err) => next(err));
|
|
1195
1314
|
}
|
|
1196
1315
|
addListElement(addressObject, objectId, reference, values, options, next) {
|
|
1197
1316
|
next = next || options;
|
|
1198
1317
|
let address = addressObject.address ? addressObject.address : addressObject;
|
|
1199
1318
|
let port = addressObject.port ? addressObject.port : this._settings.port;
|
|
1319
|
+
const deviceKey = this._makeDeviceKey(addressObject);
|
|
1200
1320
|
const settings = {
|
|
1201
1321
|
maxSegments: options.maxSegments || baEnum.MaxSegmentsAccepted.SEGMENTS_65,
|
|
1202
1322
|
maxApdu: options.maxApdu || baEnum.MaxApduLengthAccepted.OCTETS_1476,
|
|
1203
|
-
invokeId: options.invokeId
|
|
1323
|
+
invokeId: (options.invokeId !== undefined && options.invokeId !== null) ? options.invokeId : this._getInvokeId(deviceKey)
|
|
1204
1324
|
};
|
|
1205
1325
|
const buffer = this._getBuffer();
|
|
1206
1326
|
baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE | baEnum.NpduControlBits.EXPECTING_REPLY, address);
|
|
@@ -1208,7 +1328,7 @@ class Client extends events_1.EventEmitter {
|
|
|
1208
1328
|
baServices.addListElement.encode(buffer, objectId, reference.id, reference.index, values);
|
|
1209
1329
|
//baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU, buffer.offset);
|
|
1210
1330
|
this.sendBvlc(address, port, buffer);
|
|
1211
|
-
this._addCallback(settings.invokeId, (err) => next(err));
|
|
1331
|
+
this._addCallback(deviceKey, settings.invokeId, (err) => next(err));
|
|
1212
1332
|
}
|
|
1213
1333
|
/**
|
|
1214
1334
|
* DEPRECATED The getAlarmSummary command returns a list of all active alarms on the target device.
|
|
@@ -1231,17 +1351,18 @@ class Client extends events_1.EventEmitter {
|
|
|
1231
1351
|
next = next || options;
|
|
1232
1352
|
let address = addressObject.address ? addressObject.address : addressObject;
|
|
1233
1353
|
let port = addressObject.port ? addressObject.port : this._settings.port;
|
|
1354
|
+
const deviceKey = this._makeDeviceKey(addressObject);
|
|
1234
1355
|
const settings = {
|
|
1235
1356
|
maxSegments: options.maxSegments || baEnum.MaxSegmentsAccepted.SEGMENTS_65,
|
|
1236
1357
|
maxApdu: options.maxApdu || baEnum.MaxApduLengthAccepted.OCTETS_1476,
|
|
1237
|
-
invokeId: options.invokeId
|
|
1358
|
+
invokeId: (options.invokeId !== undefined && options.invokeId !== null) ? options.invokeId : this._getInvokeId(deviceKey)
|
|
1238
1359
|
};
|
|
1239
1360
|
const buffer = this._getBuffer();
|
|
1240
1361
|
baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE | baEnum.NpduControlBits.EXPECTING_REPLY, address);
|
|
1241
1362
|
baApdu.encodeConfirmedServiceRequest(buffer, baEnum.PduTypes.CONFIRMED_REQUEST, baEnum.ConfirmedServiceChoice.GET_ALARM_SUMMARY, settings.maxSegments, settings.maxApdu, settings.invokeId, 0, 0);
|
|
1242
1363
|
//baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU, buffer.offset);
|
|
1243
1364
|
this.sendBvlc(address, port, buffer);
|
|
1244
|
-
this._addCallback(settings.invokeId, (err, data) => {
|
|
1365
|
+
this._addCallback(deviceKey, settings.invokeId, (err, data) => {
|
|
1245
1366
|
if (err)
|
|
1246
1367
|
return next(err);
|
|
1247
1368
|
const result = baServices.alarmSummary.decode(data.buffer, data.offset, data.length);
|
|
@@ -1274,10 +1395,11 @@ class Client extends events_1.EventEmitter {
|
|
|
1274
1395
|
next = next || options;
|
|
1275
1396
|
let address = addressObject.address ? addressObject.address : addressObject;
|
|
1276
1397
|
let port = addressObject.port ? addressObject.port : this._settings.port;
|
|
1398
|
+
const deviceKey = this._makeDeviceKey(addressObject);
|
|
1277
1399
|
const settings = {
|
|
1278
1400
|
maxSegments: options.maxSegments || baEnum.MaxSegmentsAccepted.SEGMENTS_65,
|
|
1279
1401
|
maxApdu: options.maxApdu || baEnum.MaxApduLengthAccepted.OCTETS_1476,
|
|
1280
|
-
invokeId: options.invokeId
|
|
1402
|
+
invokeId: (options.invokeId !== undefined && options.invokeId !== null) ? options.invokeId : this._getInvokeId(deviceKey)
|
|
1281
1403
|
};
|
|
1282
1404
|
const buffer = this._getBuffer();
|
|
1283
1405
|
baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE | baEnum.NpduControlBits.EXPECTING_REPLY, address);
|
|
@@ -1285,7 +1407,7 @@ class Client extends events_1.EventEmitter {
|
|
|
1285
1407
|
baAsn1.encodeContextObjectId(buffer, 0, objectId.type, objectId.instance);
|
|
1286
1408
|
//baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU, buffer.offset);
|
|
1287
1409
|
this.sendBvlc(address, port, buffer);
|
|
1288
|
-
this._addCallback(settings.invokeId, (err, data) => {
|
|
1410
|
+
this._addCallback(deviceKey, settings.invokeId, (err, data) => {
|
|
1289
1411
|
if (err)
|
|
1290
1412
|
return next(err);
|
|
1291
1413
|
const result = baServices.eventInformation.decode(data.buffer, data.offset, data.length);
|
|
@@ -1298,10 +1420,11 @@ class Client extends events_1.EventEmitter {
|
|
|
1298
1420
|
next = next || options;
|
|
1299
1421
|
let address = addressObject.address ? addressObject.address : addressObject;
|
|
1300
1422
|
let port = addressObject.port ? addressObject.port : this._settings.port;
|
|
1423
|
+
const deviceKey = this._makeDeviceKey(addressObject);
|
|
1301
1424
|
const settings = {
|
|
1302
1425
|
maxSegments: options.maxSegments || baEnum.MaxSegmentsAccepted.SEGMENTS_65,
|
|
1303
1426
|
maxApdu: options.maxApdu || baEnum.MaxApduLengthAccepted.OCTETS_1476,
|
|
1304
|
-
invokeId: options.invokeId
|
|
1427
|
+
invokeId: (options.invokeId !== undefined && options.invokeId !== null) ? options.invokeId : this._getInvokeId(deviceKey)
|
|
1305
1428
|
};
|
|
1306
1429
|
const buffer = this._getBuffer();
|
|
1307
1430
|
baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE | baEnum.NpduControlBits.EXPECTING_REPLY, address);
|
|
@@ -1309,7 +1432,7 @@ class Client extends events_1.EventEmitter {
|
|
|
1309
1432
|
baServices.alarmAcknowledge.encode(buffer, 57, objectId, eventState, ackText, evTimeStamp, ackTimeStamp);
|
|
1310
1433
|
//baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU, buffer.offset);
|
|
1311
1434
|
this.sendBvlc(address, port, buffer);
|
|
1312
|
-
this._addCallback(settings.invokeId, (err) => next(err));
|
|
1435
|
+
this._addCallback(deviceKey, settings.invokeId, (err) => next(err));
|
|
1313
1436
|
}
|
|
1314
1437
|
/**
|
|
1315
1438
|
* The confirmedPrivateTransfer command invokes a confirmed proprietary/non-standard service.
|
|
@@ -1335,10 +1458,11 @@ class Client extends events_1.EventEmitter {
|
|
|
1335
1458
|
next = next || options;
|
|
1336
1459
|
let address = addressObject.address ? addressObject.address : addressObject;
|
|
1337
1460
|
let port = addressObject.port ? addressObject.port : this._settings.port;
|
|
1461
|
+
const deviceKey = this._makeDeviceKey(addressObject);
|
|
1338
1462
|
const settings = {
|
|
1339
1463
|
maxSegments: options.maxSegments || baEnum.MaxSegmentsAccepted.SEGMENTS_65,
|
|
1340
1464
|
maxApdu: options.maxApdu || baEnum.MaxApduLengthAccepted.OCTETS_1476,
|
|
1341
|
-
invokeId: options.invokeId
|
|
1465
|
+
invokeId: (options.invokeId !== undefined && options.invokeId !== null) ? options.invokeId : this._getInvokeId(deviceKey)
|
|
1342
1466
|
};
|
|
1343
1467
|
const buffer = this._getBuffer();
|
|
1344
1468
|
baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE | baEnum.NpduControlBits.EXPECTING_REPLY, address);
|
|
@@ -1346,7 +1470,7 @@ class Client extends events_1.EventEmitter {
|
|
|
1346
1470
|
baServices.privateTransfer.encode(buffer, vendorId, serviceNumber, data);
|
|
1347
1471
|
//baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU, buffer.offset);
|
|
1348
1472
|
this.sendBvlc(address, port, buffer);
|
|
1349
|
-
this._addCallback(settings.invokeId, (err) => next(err));
|
|
1473
|
+
this._addCallback(deviceKey, settings.invokeId, (err) => next(err));
|
|
1350
1474
|
}
|
|
1351
1475
|
/**
|
|
1352
1476
|
* The unconfirmedPrivateTransfer command invokes an unconfirmed proprietary/non-standard service.
|
|
@@ -1400,10 +1524,11 @@ class Client extends events_1.EventEmitter {
|
|
|
1400
1524
|
next = next || options;
|
|
1401
1525
|
let address = addressObject.address ? addressObject.address : addressObject;
|
|
1402
1526
|
let port = addressObject.port ? addressObject.port : this._settings.port;
|
|
1527
|
+
const deviceKey = this._makeDeviceKey(addressObject);
|
|
1403
1528
|
const settings = {
|
|
1404
1529
|
maxSegments: options.maxSegments || baEnum.MaxSegmentsAccepted.SEGMENTS_65,
|
|
1405
1530
|
maxApdu: options.maxApdu || baEnum.MaxApduLengthAccepted.OCTETS_1476,
|
|
1406
|
-
invokeId: options.invokeId
|
|
1531
|
+
invokeId: (options.invokeId !== undefined && options.invokeId !== null) ? options.invokeId : this._getInvokeId(deviceKey)
|
|
1407
1532
|
};
|
|
1408
1533
|
const buffer = this._getBuffer();
|
|
1409
1534
|
baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE | baEnum.NpduControlBits.EXPECTING_REPLY, address);
|
|
@@ -1411,7 +1536,7 @@ class Client extends events_1.EventEmitter {
|
|
|
1411
1536
|
baServices.getEnrollmentSummary.encode(buffer, acknowledgmentFilter, options.enrollmentFilter, options.eventStateFilter, options.eventTypeFilter, options.priorityFilter, options.notificationClassFilter);
|
|
1412
1537
|
//baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU, buffer.offset);
|
|
1413
1538
|
this.sendBvlc(address, port, buffer);
|
|
1414
|
-
this._addCallback(settings.invokeId, (err, data) => {
|
|
1539
|
+
this._addCallback(deviceKey, settings.invokeId, (err, data) => {
|
|
1415
1540
|
if (err)
|
|
1416
1541
|
return next(err);
|
|
1417
1542
|
const result = baServices.getEnrollmentSummary.decodeAcknowledge(data.buffer, data.offset, data.length);
|
|
@@ -1434,10 +1559,11 @@ class Client extends events_1.EventEmitter {
|
|
|
1434
1559
|
next = next || options;
|
|
1435
1560
|
let address = addressObject.address ? addressObject.address : addressObject;
|
|
1436
1561
|
let port = addressObject.port ? addressObject.port : this._settings.port;
|
|
1562
|
+
const deviceKey = this._makeDeviceKey(addressObject);
|
|
1437
1563
|
const settings = {
|
|
1438
1564
|
maxSegments: options.maxSegments || baEnum.MaxSegmentsAccepted.SEGMENTS_65,
|
|
1439
1565
|
maxApdu: options.maxApdu || baEnum.MaxApduLengthAccepted.OCTETS_1476,
|
|
1440
|
-
invokeId: options.invokeId
|
|
1566
|
+
invokeId: (options.invokeId !== undefined && options.invokeId !== null) ? options.invokeId : this._getInvokeId(deviceKey)
|
|
1441
1567
|
};
|
|
1442
1568
|
const buffer = this._getBuffer();
|
|
1443
1569
|
baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE | baEnum.NpduControlBits.EXPECTING_REPLY, address);
|
|
@@ -1445,7 +1571,7 @@ class Client extends events_1.EventEmitter {
|
|
|
1445
1571
|
baServices.eventNotifyData.encode(buffer, eventNotification);
|
|
1446
1572
|
//baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU, buffer.offset);
|
|
1447
1573
|
this.sendBvlc(address, port, buffer);
|
|
1448
|
-
this._addCallback(settings.invokeId, (err) => next(err));
|
|
1574
|
+
this._addCallback(deviceKey, settings.invokeId, (err) => next(err));
|
|
1449
1575
|
}
|
|
1450
1576
|
// Public Device Functions
|
|
1451
1577
|
readPropertyResponse(receiver, invokeId, objectId, property, value) {
|
|
@@ -25,7 +25,7 @@ const decodeTarget = (buffer, offset) => {
|
|
|
25
25
|
const encodeTarget = (buffer, target) => {
|
|
26
26
|
buffer.buffer[buffer.offset++] = (target.net & 0xFF00) >> 8;
|
|
27
27
|
buffer.buffer[buffer.offset++] = (target.net & 0x00FF) >> 0;
|
|
28
|
-
if (target.net === 0xFFFF || !target.adr) {
|
|
28
|
+
if (target.net === 0xFFFF || !target.adr || target.adr.length === 0) {
|
|
29
29
|
buffer.buffer[buffer.offset++] = 0;
|
|
30
30
|
}
|
|
31
31
|
else {
|