@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 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
- if (that.config.cacheFileEnabled) {
198
- const cachedData = await Read_Config_Async();
199
- const parsedData = JSON.parse(cachedData);
200
- if (parsedData && typeof parsedData == "object") {
201
- // renderList is no longer cached - will be rebuilt by tree builder
202
- // if (parsedData.renderList) that.renderList = parsedData.renderList;
203
- if (parsedData.deviceList) {
204
- parsedData.deviceList.forEach(function (device) {
205
- let newBacnetDevice = new BacnetDevice(true, device);
206
- that.deviceList.push(newBacnetDevice);
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: point.meta.objectId.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: options.appTag,
1576
- value: 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.logOut(err);
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
- throw new Error("Unable to build network tree, empty point list");
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:
@@ -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 &amp; 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: "4" },
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bitpoolos/edge-bacnet",
3
- "version": "1.6.7",
3
+ "version": "1.6.8",
4
4
  "description": "A bacnet gateway for node-red",
5
5
  "dependencies": {
6
6
  "@plus4nodered/ts-node-bacnet": "^1.0.0-beta.2",
@@ -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
- _getInvokeId() {
70
- // Try up to 256 times to find an unused invoke ID
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._invokeCounter++;
73
- if (id >= 256) this._invokeCounter = 1;
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
- // Edge case: if all 256 invoke IDs are in use, fall back to original behavior
84
- // This prevents infinite loops while maintaining backwards compatibility
85
- const id = this._invokeCounter++;
86
- if (id >= 256) this._invokeCounter = 1;
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
- _invokeCallback(id, err, result) {
90
- const callback = this._invokeStore[id];
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 ', id, ' not found -> drop package');
185
+ debug('InvokeId', id, 'for device', deviceKey, 'not found -> drop package');
94
186
  }
95
- _addCallback(id, callback) {
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[id];
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[id] = (err, data) => {
200
+ this._invokeStore[storeKey] = (err, data) => {
101
201
  clearTimeout(timeout);
102
- delete this._invokeStore[id];
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 || this._getInvokeId(),
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 || this._getInvokeId(),
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 || this._getInvokeId()
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 || this._getInvokeId()
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 || this._getInvokeId(),
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 || this._getInvokeId(),
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 || this._getInvokeId()
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 || this._getInvokeId()
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 || this._getInvokeId()
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 || this._getInvokeId()
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 || this._getInvokeId()
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 || this._getInvokeId()
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 || this._getInvokeId()
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 || this._getInvokeId()
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 || this._getInvokeId()
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 || this._getInvokeId()
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 || this._getInvokeId()
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 || this._getInvokeId()
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 || this._getInvokeId()
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 || this._getInvokeId()
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 || this._getInvokeId()
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 {