@bitpoolos/edge-bacnet 1.2.8 → 1.3.0

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/bacnet_client.js CHANGED
@@ -6,10 +6,11 @@ const bacnet = require("./resources/node-bacstack-ts/dist/index.js");
6
6
  const baEnum = bacnet.enum;
7
7
  const bacnetIdMax = baEnum.ASN1_MAX_PROPERTY_ID;
8
8
  const { EventEmitter } = require("events");
9
- const { getUnit, roundDecimalPlaces, Store_Config, Read_Config_Sync, isNumber } = require("./common");
9
+ const { getUnit, roundDecimalPlaces, Store_Config, Read_Config_Sync, isNumber, decodeBitArray } = require("./common");
10
10
  const { ToadScheduler, SimpleIntervalJob, Task } = require("toad-scheduler");
11
11
  const { BacnetDevice } = require("./bacnet_device");
12
12
  const { Mutex } = require("async-mutex");
13
+ const { treeBuilder } = require("./treeBuilder.js");
13
14
 
14
15
  class BacnetClient extends EventEmitter {
15
16
  //client constructor
@@ -18,7 +19,6 @@ class BacnetClient extends EventEmitter {
18
19
  let that = this;
19
20
  that.config = config;
20
21
  that.deviceList = [];
21
- that.manualDiscoverQueue = [];
22
22
  that.networkTree = {};
23
23
  that.lastWhoIs = null;
24
24
  that.client = null;
@@ -52,12 +52,10 @@ class BacnetClient extends EventEmitter {
52
52
  that.discover_polling_schedule = config.discover_polling_schedule;
53
53
  that.deviceId = config.deviceId;
54
54
  that.broadCastAddr = config.broadCastAddr;
55
- that.manual_instance_range_enabled = config.manual_instance_range_enabled;
56
- that.manual_instance_range_start = config.manual_instance_range_start;
57
- that.manual_instance_range_end = config.manual_instance_range_end;
58
55
  that.device_read_schedule = config.device_read_schedule;
59
56
  that.deviceRetryCount = parseInt(config.retries);
60
57
  that.sanitise_device_schedule = config.sanitise_device_schedule;
58
+ that.buildTreeException = false;
61
59
 
62
60
  that.readPropertyMultipleOptions = {
63
61
  maxSegments: 112,
@@ -84,7 +82,6 @@ class BacnetClient extends EventEmitter {
84
82
  //query device task
85
83
  const queryDevices = new Task("simple task", () => {
86
84
  if (!that.pollInProgress) that.queryDevices();
87
- that.sanitizeDeviceList();
88
85
  });
89
86
 
90
87
  const queryJob = new SimpleIntervalJob({ seconds: parseInt(that.device_read_schedule) }, queryDevices);
@@ -93,12 +90,13 @@ class BacnetClient extends EventEmitter {
93
90
 
94
91
  //buildNetworkTreeData task
95
92
  const buildNetworkTree = new Task("simple task", () => {
96
- that.buildNetworkTreeData().then(function () {
97
- that.countDevices();
98
- });
93
+
94
+ that.doTreeBuilder();
95
+ that.countDevices();
96
+
99
97
  });
100
98
 
101
- const buildNetworkTreeJob = new SimpleIntervalJob({ seconds: 10 }, buildNetworkTree);
99
+ const buildNetworkTreeJob = new SimpleIntervalJob({ seconds: 5 }, buildNetworkTree);
102
100
 
103
101
  that.scheduler.addSimpleIntervalJob(buildNetworkTreeJob);
104
102
 
@@ -106,10 +104,6 @@ class BacnetClient extends EventEmitter {
106
104
 
107
105
  setTimeout(() => {
108
106
  that.queryDevices();
109
- that.sanitizeDeviceList();
110
- that.buildNetworkTreeData().then(function () {
111
- that.countDevices();
112
- });
113
107
  }, "5000");
114
108
  } catch (e) {
115
109
  that.logOut("Issue initializing client: ", e);
@@ -117,7 +111,6 @@ class BacnetClient extends EventEmitter {
117
111
 
118
112
  //who is callback
119
113
  that.client.on("iAm", (device) => {
120
- //console.log("found iAm device: ", device);
121
114
  if (device.address !== that.config.localIpAdrress) {
122
115
  if (that.scanMatrix.length > 0) {
123
116
  let matrixMap = that.scanMatrix.filter((ele) => device.deviceId >= ele.start && device.deviceId <= ele.end);
@@ -127,7 +120,6 @@ class BacnetClient extends EventEmitter {
127
120
  if (foundIndex == -1) {
128
121
  let newBacnetDevice = new BacnetDevice(false, device);
129
122
  newBacnetDevice.setLastSeen(Date.now());
130
- that.updateDeviceName(newBacnetDevice);
131
123
  if (newBacnetDevice.getIsMstpDevice()) {
132
124
  that.addToParentMstpNetwork(newBacnetDevice);
133
125
  }
@@ -135,8 +127,6 @@ class BacnetClient extends EventEmitter {
135
127
  } else if (foundIndex !== -1) {
136
128
  that.deviceList[foundIndex].updateDeviceConfig(device);
137
129
  that.deviceList[foundIndex].setLastSeen(Date.now());
138
- that.updateDeviceName(that.deviceList[foundIndex]);
139
-
140
130
  if (that.deviceList[foundIndex].getIsMstpDevice()) {
141
131
  that.addToParentMstpNetwork(that.deviceList[foundIndex]);
142
132
  }
@@ -151,7 +141,6 @@ class BacnetClient extends EventEmitter {
151
141
  if (foundIndex == -1) {
152
142
  let newBacnetDevice = new BacnetDevice(false, device);
153
143
  newBacnetDevice.setLastSeen(Date.now());
154
- that.updateDeviceName(newBacnetDevice);
155
144
  if (newBacnetDevice.getIsMstpDevice()) {
156
145
  that.addToParentMstpNetwork(newBacnetDevice);
157
146
  }
@@ -159,8 +148,6 @@ class BacnetClient extends EventEmitter {
159
148
  } else if (foundIndex !== -1) {
160
149
  that.deviceList[foundIndex].updateDeviceConfig(device);
161
150
  that.deviceList[foundIndex].setLastSeen(Date.now());
162
- that.updateDeviceName(that.deviceList[foundIndex]);
163
-
164
151
  if (that.deviceList[foundIndex].getIsMstpDevice()) {
165
152
  that.addToParentMstpNetwork(that.deviceList[foundIndex]);
166
153
  }
@@ -204,12 +191,36 @@ class BacnetClient extends EventEmitter {
204
191
  }
205
192
 
206
193
  if (value) {
207
- console.log("value: ", value);
194
+ console.log("value : ", value);
195
+ console.log("value1 : ", value.values[0].value);
208
196
  }
209
197
  }
210
198
  );
211
199
  }
212
200
 
201
+ getProtocolSupported(device) {
202
+ //return protocols support for device
203
+ let that = this;
204
+ return new Promise((resolve, reject) => {
205
+ that.client.readProperty(
206
+ device.getAddress(),
207
+ { type: baEnum.ObjectType.DEVICE, instance: device.getDeviceId() },
208
+ baEnum.PropertyIdentifier.PROTOCOL_SERVICES_SUPPORTED,
209
+ that.readPropertyMultipleOptions,
210
+ (err, value) => {
211
+ if (err) {
212
+ reject(err);
213
+ }
214
+
215
+ if (value) {
216
+ resolve(value);
217
+ }
218
+ }
219
+ );
220
+ });
221
+
222
+ }
223
+
213
224
  addToParentMstpNetwork(device) {
214
225
  let that = this;
215
226
  let address = device.getAddress().address;
@@ -243,223 +254,227 @@ class BacnetClient extends EventEmitter {
243
254
  });
244
255
  }
245
256
 
246
- queryDevices() {
257
+ purgeDevice(device) {
247
258
  let that = this;
259
+ return new Promise((resolve, reject) => {
260
+ try {
261
+ let renderListIndex = that.renderList.findIndex(
262
+ (ele) => ele.deviceId == device.deviceId && ele.ipAddr == device.address
263
+ );
264
+ let deviceListIndex = that.deviceList.findIndex((ele) => ele.getDeviceId() == device.deviceId);
265
+ let deviceKey = device.address + "-" + device.deviceId;
266
+ delete that.networkTree[deviceKey];
267
+ that.renderList.splice(renderListIndex, 1);
268
+ that.deviceList.splice(deviceListIndex, 1);
248
269
 
249
- that.pollInProgress = true;
250
-
251
- let index = 0;
252
-
253
- query(index);
254
-
255
- function query(index) {
256
- that.queryPriorityDevices().then(function () {
257
- let device = that.deviceList[index];
258
-
259
- if (index < that.deviceList.length) {
260
- index++;
270
+ that.countDevices();
261
271
 
262
- if (typeof device == "object") {
263
- if (!device.getManualDiscoveryMode()) {
264
- try {
265
- that
266
- .getDevicePointList(device)
267
- .then(function () {
268
- that.removeDeviceFromManualQueue(device);
269
- that
270
- .buildJsonObject(device, null)
271
- .then(function () {
272
- query(index);
273
- })
274
- .catch(function (e) {
275
- that.logOut(`getDevicePointList error: ${device.getAddress()}`, e);
276
- query(index);
277
- });
278
- })
279
- .catch(function (e) {
280
- that.logOut(`getDevicePointList error: ${device.getAddress()}`, e);
281
- device.setManualDiscoveryMode(true);
282
- that
283
- .getDevicePointListWithoutObjectList(device)
284
- .then(function () {
285
- that
286
- .buildJsonObject(device, null)
287
- .then(function () {
288
- query(index);
289
- })
290
- .catch(function (e) {
291
- that.logOut(`getDevicePointList error: ${device.getAddress()}`, e);
292
- query(index);
293
- });
294
- })
295
- .catch(function (e) {
296
- query(index);
297
- });
298
- });
299
- } catch (e) {
300
- that.logOut("Error while querying devices: ", e);
301
- query(index);
302
- }
303
- } else {
304
- query(index);
305
- }
306
- } else {
307
- that.logOut("queryDevices: invalid device found: ", device);
308
- query(index);
309
- }
310
- } else if (index == that.deviceList.length) {
311
- if (that.manualDiscoverQueue.length > 0) {
312
- that.queryDevicesManually();
313
- } else {
314
- that.pollInProgress = false;
315
- }
316
- }
317
- });
318
- }
272
+ resolve(true);
273
+ } catch (e) {
274
+ reject(e);
275
+ }
276
+ });
319
277
  }
320
278
 
321
- queryDevicesManually() {
279
+ updatePointsForDevice(deviceObject) {
322
280
  let that = this;
323
- let index = 0;
324
- query(index);
281
+ return new Promise((resolve, reject) => {
282
+ try {
283
+ let device = that.deviceList.find((ele) => ele.getDeviceId() == deviceObject.deviceId);
284
+ that.updateDeviceName(device);
285
+
286
+ if (device.getIsProtocolServicesSet() == false) {
287
+ that.getProtocolSupported(device).then(function (result) {
288
+ let decodedValues = decodeBitArray(8, result.values[0].originalBitString.value);
289
+ device.setProtocolServicesSupported(decodedValues);
290
+ }).catch(function (error) {
291
+ that.logOut("getProtocolSupported error: ", error);
292
+ });
293
+ }
325
294
 
326
- function query(index) {
327
- that.queryPriorityDevices().then(function () {
328
- let device = that.manualDiscoverQueue[index];
329
- if (index < that.manualDiscoverQueue.length) {
330
- index++;
331
- if (typeof device == "object") {
332
- try {
333
- if (device.shouldBeInManualMode()) {
295
+ that
296
+ .getDevicePointList(device)
297
+ .then(function () {
298
+ that
299
+ .buildJsonObject(device)
300
+ .then(function () {
301
+ // do nothing for now
302
+ resolve(true);
303
+ })
304
+ .catch(function (e) {
305
+ that.logOut(`Update points list error 1: ${device.getAddress()}`, e);
306
+ });
307
+ })
308
+ .catch(function (e) {
309
+ that.logOut(`Update points list error 2: ${device.getAddress()}`, e);
310
+ device.setManualDiscoveryMode(true);
311
+ that
312
+ .getDevicePointListWithoutObjectList(device)
313
+ .then(function () {
334
314
  that
335
- .getDevicePointListWithoutObjectList(device)
315
+ .buildJsonObject(device)
336
316
  .then(function () {
337
- that
338
- .buildJsonObject(device, null)
339
- .then(function () {
340
- query(index);
341
- })
342
- .catch(function (e) {
343
- that.logOut(`getDevicePointList error: ${device.getAddress()}`, e);
344
- query(index);
345
- });
317
+ // do nothing for now
318
+ resolve(true);
346
319
  })
347
320
  .catch(function (e) {
348
- query(index);
321
+ that.logOut(`Update points list error 3: ${device.getAddress()}`, e);
349
322
  });
350
- } else {
351
- that.removeDeviceFromManualQueue(device);
352
- query(index);
353
- }
354
- } catch (e) {
355
- query(index);
356
- }
357
- } else {
358
- query(index);
359
- }
360
- } else if (index == that.manualDiscoverQueue.length) {
361
- that.pollInProgress = false;
362
- }
363
- });
364
- }
323
+ })
324
+ .catch(function (e) {
325
+ that.logOut(`Update points list error 4: ${device.getAddress()}`, e);
326
+ });
327
+ });
328
+ } catch (e) {
329
+ reject(e);
330
+ }
331
+ });
365
332
  }
366
333
 
367
- queryPriorityDevices() {
334
+ setDeviceDisplayName(deviceObject, displayName) {
368
335
  let that = this;
369
336
  return new Promise((resolve, reject) => {
370
- let priorityDevices = that.getPriorityDevices();
371
-
372
- if (priorityDevices.length > 0) {
373
- let index = 0;
337
+ try {
338
+ let device = that.deviceList.find((ele) => ele.getDeviceId() == deviceObject.deviceId);
374
339
 
375
- query(index);
340
+ device.setDisplayName(displayName);
376
341
 
377
- function query(index) {
378
- let device = priorityDevices[index];
342
+ that.buildTreeException = true;
379
343
 
380
- if (index < priorityDevices.length) {
381
- index++;
344
+ resolve(true);
345
+ } catch (e) {
346
+ that.logOut("setDeviceDisplayName error: ", e);
382
347
 
383
- if (
384
- typeof device == "object" &&
385
- (Date.now() - device.getLastPriorityQueueTS()) / 1000 > parseInt(that.device_read_schedule)
386
- ) {
387
- try {
388
- let points = device.getPriorityQueue();
389
- that
390
- .buildJsonObject(device, points)
391
- .then(function () {
392
- device.setLastPriorityQueueTS();
393
- query(index);
394
- })
395
- .catch(function (e) {
396
- that.logOut(`queryPriorityDevices error: ${device.getAddress()}`, e);
397
- query(index);
398
- });
399
- } catch (e) {
400
- that.logOut("Error while querying priority devices: ", e);
401
- query(index);
402
- }
403
- } else {
404
- query(index);
405
- }
406
- } else if (index == priorityDevices.length) {
407
- resolve();
408
- }
409
- }
410
- } else if (priorityDevices.length == 0) {
411
- resolve();
348
+ reject(e);
412
349
  }
413
350
  });
414
351
  }
415
352
 
416
- addDeviceToManualQueue(device) {
353
+ setPointDisplayName(deviceKey, pointName, pointDisplayName) {
417
354
  let that = this;
355
+ return new Promise((resolve, reject) => {
356
+ try {
357
+ if (that.networkTree[deviceKey][pointName]) {
358
+ that.networkTree[deviceKey][pointName].displayName = pointDisplayName;
359
+ }
360
+
361
+ that.buildTreeException = true;
418
362
 
419
- if (device.getPointListRetryCount() > that.deviceRetryCount) {
420
- device.setManualDiscoveryMode(true);
421
- let index = that.manualDiscoverQueue.findIndex((ele) => ele.getDeviceId() == device.getDeviceId());
422
- if (index == -1) {
423
- that.manualDiscoverQueue.push(device);
363
+ resolve(true);
364
+ } catch (e) {
365
+ that.logOut("setPointDisplayName error: ", e);
366
+ reject(e);
424
367
  }
425
- } else {
426
- device.incrementPointListRetryCount();
427
- }
368
+ });
428
369
  }
429
370
 
430
- removeDeviceFromManualQueue(device) {
371
+ importReadList(payload) {
431
372
  let that = this;
432
- device.setManualDiscoveryMode(false);
433
- device.clearPointListRetryCount();
434
- let index = that.manualDiscoverQueue.findIndex((ele) => ele.getDeviceId() == device.getDeviceId());
435
- if (index !== -1) {
436
- that.manualDiscoverQueue.splice(index, 1);
437
- }
373
+
374
+ return new Promise((resolve, reject) => {
375
+ try {
376
+ that.buildTreeException = true;
377
+
378
+ for (let key in payload) {
379
+ let device = payload[key];
380
+ for (let pointName in device) {
381
+ let pointObject = device[pointName]
382
+ if (that.networkTree[key][pointName]) {
383
+ that.networkTree[key][pointName] = pointObject;
384
+ }
385
+ }
386
+ }
387
+ resolve(true);
388
+ } catch (e) {
389
+ that.logOut("importReadList error: ", e);
390
+ reject(e);
391
+ }
392
+ });
438
393
  }
439
394
 
440
- sanitizeDeviceList() {
395
+ queryDevices() {
441
396
  let that = this;
442
397
 
443
- //that.sanitise_device_schedule - from gateway node
444
- let timeoutThreshold = parseInt(that.sanitise_device_schedule);
445
- that.deviceList.forEach(function (device, index) {
446
- if ((Date.now() - device.getLastSeen()) / 1000 > timeoutThreshold && device.getPriorityQueueIsActive() == false) {
447
- //render device hasnt responded to whoIs for sanitise_device_schedule
448
- let renderListIndex = that.renderList.findIndex((ele) => ele.deviceId == device.getDeviceId());
449
- let ipAddr = typeof device.getAddress() == "object" ? device.getAddress().address : device.getAddress();
450
- let deviceKey = ipAddr + "-" + device.getDeviceId();
451
- delete that.networkTree[deviceKey];
452
- that.renderList.splice(renderListIndex, 1);
453
- that.deviceList.splice(index, 1);
398
+ that.pollInProgress = true;
399
+
400
+ let index = 0;
401
+
402
+ query(index);
403
+
404
+ function query(index) {
405
+ let device = that.deviceList[index];
406
+
407
+ if (index < that.deviceList.length) {
408
+ index++;
409
+
410
+ if (typeof device == "object") {
411
+ if (device.getIsProtocolServicesSet() == false) {
412
+ that.getProtocolSupported(device).then(function (result) {
413
+ let decodedValues = decodeBitArray(8, result.values[0].originalBitString.value);
414
+ device.setProtocolServicesSupported(decodedValues);
415
+ }).catch(function (error) {
416
+ that.logOut("getProtocolSupported error: ", error);
417
+ });
418
+ }
419
+
420
+ if (!device.getManualDiscoveryMode()) {
421
+ try {
422
+ that.updateDeviceName(device);
423
+ that
424
+ .getDevicePointList(device)
425
+ .then(function () {
426
+ that
427
+ .buildJsonObject(device)
428
+ .then(function () {
429
+ query(index);
430
+ })
431
+ .catch(function (e) {
432
+ that.logOut(`getDevicePointList error: ${device.getAddress()}`, e);
433
+ query(index);
434
+ });
435
+ })
436
+ .catch(function (e) {
437
+ that.logOut(`getDevicePointList error: ${device.getAddress()}`, e);
438
+ device.setManualDiscoveryMode(true);
439
+ that
440
+ .getDevicePointListWithoutObjectList(device)
441
+ .then(function () {
442
+ that
443
+ .buildJsonObject(device)
444
+ .then(function () {
445
+ query(index);
446
+ })
447
+ .catch(function (e) {
448
+ that.logOut(`getDevicePointList error: ${device.getAddress()}`, e);
449
+ query(index);
450
+ });
451
+ })
452
+ .catch(function (e) {
453
+ query(index);
454
+ });
455
+ });
456
+ } catch (e) {
457
+ that.logOut("Error while querying devices: ", e);
458
+ query(index);
459
+ }
460
+ } else {
461
+ query(index);
462
+ }
463
+ } else {
464
+ that.logOut("queryDevices: invalid device found: ", device);
465
+ query(index);
466
+ }
467
+ } else if (index == that.deviceList.length) {
468
+ that.pollInProgress = false;
454
469
  }
455
- });
470
+ }
456
471
  }
457
472
 
458
473
  updateDeviceName(device) {
459
474
  let that = this;
460
475
  that._getDeviceName(device.getAddress(), device.getDeviceId()).then(function (deviceObject) {
461
476
  if (typeof deviceObject.name == "string") {
462
- device.setDeviceName(deviceObject.name);
477
+ device.setDeviceName(deviceObject.name + " " + device.getDeviceId());
463
478
  device.setPointsList(deviceObject.devicePointEntry);
464
479
  }
465
480
  });
@@ -475,9 +490,6 @@ class BacnetClient extends EventEmitter {
475
490
  that.discover_polling_schedule = config.discover_polling_schedule;
476
491
  that.deviceId = config.deviceId;
477
492
  that.broadCastAddr = config.broadCastAddr;
478
- that.manual_instance_range_enabled = config.manual_instance_range_enabled;
479
- that.manual_instance_range_start = config.manual_instance_range_start;
480
- that.manual_instance_range_end = config.manual_instance_range_end;
481
493
  that.device_read_schedule = config.device_read_schedule;
482
494
 
483
495
  if (that.scheduler !== null) {
@@ -505,7 +517,6 @@ class BacnetClient extends EventEmitter {
505
517
  // //query device task
506
518
  const queryDevices = new Task("simple task", () => {
507
519
  if (!that.pollInProgress) that.queryDevices();
508
- that.sanitizeDeviceList();
509
520
  });
510
521
 
511
522
  const queryJob = new SimpleIntervalJob({ seconds: parseInt(config.device_read_schedule) }, queryDevices);
@@ -514,7 +525,7 @@ class BacnetClient extends EventEmitter {
514
525
 
515
526
  //buildNetworkTreeData task
516
527
  const buildNetworkTree = new Task("simple task", () => {
517
- that.buildNetworkTreeData();
528
+ that.doTreeBuilder();
518
529
  that.countDevices();
519
530
  });
520
531
 
@@ -545,70 +556,196 @@ class BacnetClient extends EventEmitter {
545
556
  return newProps;
546
557
  }
547
558
 
548
- doRead(readConfig, outputType, objectPropertyType, readNodeName) {
549
- let that = this;
550
- that.roundDecimal = readConfig.precision;
551
- let devicesToRead = Object.keys(readConfig.pointsToRead);
559
+ findDeviceByKey(key, deviceList, that) {
560
+ return deviceList.find(ele => `${that.getDeviceAddress(ele)}-${ele.getDeviceId()}` === key);
561
+ }
562
+
563
+ getObjectId(pointName, pointConfig, that) {
564
+ // Retrieve the object type based on the point configuration
565
+ const bacObjType = that.getObjectType(pointConfig.meta.objectId.type);
566
+ // Construct the object ID string
567
+ return `${pointName}_${bacObjType}_${pointConfig.meta.objectId.instance}`;
568
+ }
569
+
570
+ createDeviceKey(device) {
571
+ // Create a device key by combining the address and device ID
572
+ const address = device.getAddress();
573
+ const deviceId = device.getDeviceId();
574
+ if (typeof address === "object") {
575
+ return `${address.address}-${deviceId}`;
576
+ } else {
577
+ return `${address}-${deviceId}`;
578
+ }
579
+ }
580
+
581
+
582
+ async doRead(readConfig, outputType, objectPropertyType, readNodeName) {
583
+ const that = this;
584
+ const roundDecimal = readConfig.precision;
585
+ const devicesToRead = Object.keys(readConfig.pointsToRead);
586
+ const bacnetResults = {};
587
+ let pendingRequests = 0;
588
+
552
589
  try {
553
- let bacnetResults = {};
554
- devicesToRead.forEach(function (key, index) {
555
- let device = that.deviceList.find((ele) => `${that.getDeviceAddress(ele)}-${ele.getDeviceId()}` == key);
556
- if (device) {
557
- let deviceName = device.getDeviceName();
558
- let deviceKey =
559
- typeof device.getAddress() == "object"
560
- ? device.getAddress().address + "-" + device.getDeviceId()
561
- : device.getAddress() + "-" + device.getDeviceId();
562
- let deviceObject = that.networkTree[deviceKey];
563
- if (!bacnetResults[deviceName]) bacnetResults[deviceName] = {};
564
- if (deviceObject) {
565
- for (const pointName in readConfig.pointsToRead[key]) {
566
- let bac_obj = that.getObjectType(readConfig.pointsToRead[key][pointName].meta.objectId.type);
567
- let objectId =
568
- pointName + "_" + bac_obj + "_" + readConfig.pointsToRead[key][pointName].meta.objectId.instance;
569
- let point = deviceObject[objectId];
570
590
 
571
- that
572
- .updatePoint(device, point)
573
- .then(function (result) {
574
- if (isNumber(result.values[0].value)) {
575
- point.presentValue = roundDecimalPlaces(result.values[0].value, that.roundDecimal);
576
- } else {
577
- point.presentValue = result.values[0].value;
578
- }
579
- bacnetResults[deviceName][pointName] = point;
580
- })
581
- .catch(function (err) {
582
- //do nothing for now
583
- });
591
+ // Process all devices in sequence
592
+ for (let deviceIndex = 0; deviceIndex < devicesToRead.length; deviceIndex++) {
593
+ const key = devicesToRead[deviceIndex];
594
+ const device = that.findDeviceByKey(key, that.deviceList, that);
595
+ if (!device) continue;
596
+
597
+ const deviceName = that.computeDeviceName(device);
598
+ const deviceKey = that.createDeviceKey(device);
599
+ const deviceObject = that.networkTree[deviceKey];
600
+ const maxObjectCount = that.estimateMaxObjectSize(device.getMaxApdu());
601
+
602
+ if (!bacnetResults[deviceName]) {
603
+ bacnetResults[deviceName] = {};
604
+ }
605
+
606
+ // Process points for the current device
607
+ const pointsToRead = readConfig.pointsToRead[key];
608
+ const pointNames = Object.keys(pointsToRead);
609
+ const totalPoints = pointNames.length;
610
+ let requestArray = [];
611
+ let processedPoints = 0; // Counter for processed points
612
+
613
+ // Process each point for the device in batches
614
+ for (let i = 0; i < pointNames.length; i++) {
615
+ const pointName = pointNames[i];
616
+ if (pointName === "deviceName") continue;
617
+
618
+ const pointConfig = pointsToRead[pointName];
619
+ const objectId = that.getObjectId(pointName, pointConfig, that);
620
+ const point = deviceObject[objectId];
621
+
622
+ if (point) {
623
+ point.displayName = pointConfig.displayName;
624
+
625
+ // Prepare request array for batch processing
626
+ requestArray.push({
627
+ objectId: { type: point.meta.objectId.type, instance: point.meta.objectId.instance },
628
+ properties: [{ id: baEnum.PropertyIdentifier.PRESENT_VALUE }],
629
+ pointRef: point,
630
+ pointName: pointName
631
+ });
632
+ }
633
+
634
+ // Process the batch when the request array is full or the last point is reached
635
+ if (requestArray.length === maxObjectCount || i === pointNames.length - 1) {
636
+ if (device.getProtocolServiceSupport("ReadPropertyMultiple") == true) {
637
+ await that.processBatch(device, requestArray, deviceName, bacnetResults, that, roundDecimal);
638
+ } else {
639
+ await that.processIndividualPoints(device, requestArray, deviceName, bacnetResults, that, roundDecimal);
584
640
  }
641
+
642
+ requestArray = [];
643
+ // Increment the processed points counter
644
+ processedPoints += maxObjectCount;
645
+ }
646
+
647
+ // Check if all points for the device have been processed
648
+ if (processedPoints >= totalPoints) {
649
+ pendingRequests++;
650
+ // Emit the `values` event for the current device
651
+ that.emit("values", bacnetResults, outputType, objectPropertyType, readNodeName, pendingRequests, devicesToRead.length);
652
+ delete bacnetResults[deviceName];
653
+
585
654
  }
586
655
  }
656
+ }
657
+ } catch (error) {
658
+ that.logOut("doRead error: ", error);
659
+ }
660
+ }
587
661
 
588
- setTimeout(() => {
589
- if (index == devicesToRead.length - 1 && Object.keys(readConfig.pointsToRead).length > 0)
590
- that.emit("values", bacnetResults, outputType, objectPropertyType, readNodeName);
591
- }, 3000);
662
+ async processBatch(device, requestArray, deviceName, bacnetResults, that, roundDecimal) {
663
+ try {
664
+ const results = await that.updateManyPoints(device, requestArray);
665
+ if (results.error) {
666
+ throw results.error;
667
+ }
668
+
669
+ // Process the results of the batch
670
+ results.value.values.forEach(pointResult => {
671
+ const cacheRef = requestArray.find(ele =>
672
+ ele.pointRef.meta.objectId.type === pointResult.objectId.type &&
673
+ ele.pointRef.meta.objectId.instance === pointResult.objectId.instance
674
+ );
675
+
676
+ if (cacheRef) {
677
+ const pointRef = cacheRef.pointRef;
678
+ const pointNameRef = cacheRef.pointName;
679
+ const val = pointResult.values[0].value[0].value;
680
+
681
+ if (isNumber(val)) {
682
+ pointRef.presentValue = roundDecimalPlaces(val, roundDecimal);
683
+ if (pointRef.meta.objectId.type == 19 || pointRef.meta.objectId.type == 13 || pointRef.meta.objectId.type == 14) {
684
+ if (val != 0) {
685
+ pointRef.presentValue = pointRef.stateTextArray[val - 1].value;
686
+ } else {
687
+ pointRef.presentValue = pointRef.stateTextArray[val].value;
688
+ }
689
+ }
690
+ } else {
691
+ if (typeof val !== "object") {
692
+ pointRef.presentValue = val;
693
+ }
694
+ }
695
+
696
+ // Store the point data in results
697
+ bacnetResults[deviceName][pointNameRef] = pointRef;
698
+ }
592
699
  });
593
- } catch (e) {
594
- that.logOut("Issue doing read, see error: ", e);
700
+ } catch (err) {
701
+ that.logOut("Error processing batch:", err);
702
+ }
703
+ }
704
+
705
+ async processIndividualPoints(device, requestArray, deviceName, bacnetResults, that, roundDecimal) {
706
+ for (const request of requestArray) {
707
+ const { objectId, pointRef, pointName } = request;
708
+ try {
709
+ const result = await that.updatePoint(device, pointRef);
710
+ const val = result.values[0].value;
711
+
712
+ if (isNumber(val)) {
713
+ pointRef.presentValue = roundDecimalPlaces(val, roundDecimal);
714
+ } else {
715
+ pointRef.presentValue = val;
716
+ }
717
+
718
+ // Store the point data in results
719
+ bacnetResults[deviceName][pointName] = pointRef;
720
+ } catch (err) {
721
+ that.logOut(`Error updating point ${pointName}:`, err);
722
+ }
595
723
  }
596
724
  }
597
725
 
726
+ updateManyPoints(device, points) {
727
+ let that = this;
728
+ return new Promise((resolve, reject) => {
729
+ that._readObjectWithRequestArray(device.getAddress(), points, that.readPropertyMultipleOptions).then(function (results) {
730
+ resolve(results);
731
+ }).catch(function (err) {
732
+ reject(err);
733
+ });
734
+ });
735
+ }
736
+
598
737
  updatePoint(device, point) {
599
738
  let that = this;
600
739
  return new Promise((resolve, reject) => {
601
740
  that.client.readProperty(
602
741
  device.getAddress(),
603
- { type: point.objectID.type, instance: point.objectID.instance },
742
+ { type: point.meta.objectId.type, instance: point.meta.objectId.instance },
604
743
  baEnum.PropertyIdentifier.PRESENT_VALUE,
605
- {},
744
+ that.readPropertyMultipleOptions,
606
745
  (err, value) => {
607
746
  if (err) {
608
- //console.log("err ", err);
609
747
  reject(err);
610
748
  }
611
-
612
749
  if (value) {
613
750
  resolve(value);
614
751
  }
@@ -617,6 +754,18 @@ class BacnetClient extends EventEmitter {
617
754
  });
618
755
  }
619
756
 
757
+ estimateMaxObjectSize(apduSize) {
758
+ if (apduSize < 500) {
759
+ return 20;
760
+ } else if (apduSize > 500 && apduSize < 1000) {
761
+ //return Math.round(((apduSize - 30) / 7));
762
+ return 50;
763
+ } else if (apduSize > 1000) {
764
+ //return Math.round(((apduSize - 30) / 7));
765
+ return 100;
766
+ }
767
+ }
768
+
620
769
  getDeviceAddress(device) {
621
770
  switch (typeof device.getAddress()) {
622
771
  case "object":
@@ -825,12 +974,13 @@ class BacnetClient extends EventEmitter {
825
974
  }
826
975
 
827
976
  _readObjectFull(device, deviceAddress, type, instance) {
828
- let that = this;
977
+ const that = this;
829
978
  const readOptions = {
830
979
  maxSegments: that.readPropertyMultipleOptions.maxSegments,
831
980
  maxApdu: that.readPropertyMultipleOptions.maxApdu,
832
981
  };
833
982
 
983
+ // Define all properties to be read
834
984
  const allProperties = [
835
985
  { id: baEnum.PropertyIdentifier.PRESENT_VALUE },
836
986
  { id: baEnum.PropertyIdentifier.DESCRIPTION },
@@ -840,80 +990,77 @@ class BacnetClient extends EventEmitter {
840
990
  { id: baEnum.PropertyIdentifier.OBJECT_IDENTIFIER },
841
991
  { id: baEnum.PropertyIdentifier.SYSTEM_STATUS },
842
992
  { id: baEnum.PropertyIdentifier.MODIFICATION_DATE },
843
- { id: baEnum.PropertyIdentifier.PROGRAM_STATE },
993
+ { id: baEnum.PropertyIdentifier.STATE_TEXT },
844
994
  { id: baEnum.PropertyIdentifier.RECORD_COUNT },
845
995
  { id: baEnum.PropertyIdentifier.PRIORITY_ARRAY },
996
+ { id: baEnum.PropertyIdentifier.VENDOR_NAME },
846
997
  ];
847
998
 
848
999
  return new Promise((resolve, reject) => {
849
- that
850
- ._readObject(deviceAddress, type, instance, [{ id: baEnum.PropertyIdentifier.ALL }], readOptions)
851
- .then(function (result) {
1000
+ // Try to read all properties at once
1001
+ that._readObject(deviceAddress, type, instance, [{ id: baEnum.PropertyIdentifier.ALL }], readOptions)
1002
+ .then(result => {
852
1003
  if (result.value) {
1004
+ // If the result has value, resolve the promise
853
1005
  resolve(result);
854
- }
855
-
856
- if (result.error) {
857
- let i = 0;
858
- readIndividualProperties(i);
1006
+ } else {
1007
+ // If not, proceed to read individual properties
1008
+ readPropertiesIndividually();
859
1009
  }
860
1010
  })
861
- .catch(function (error) {
862
- let i = 0;
863
- readIndividualProperties(i);
1011
+ .catch(() => {
1012
+ // On error, proceed to read individual properties
1013
+ readPropertiesIndividually();
864
1014
  });
865
1015
 
866
- let resultArray = [];
867
- let errorArray = [];
868
-
869
- function readIndividualProperties(index) {
870
- const property = allProperties[index];
871
-
872
- that.client.readProperty(
873
- deviceAddress,
874
- { type: type, instance: instance },
875
- property.id,
876
- readOptions,
877
- (err, value) => {
878
- if (err) {
879
- errorArray.push(err);
880
- }
881
-
882
- if (value) {
883
- const structuredResult = {
884
- id: value.property.id,
885
- index: value.property.index,
886
- value: value.values,
887
- };
888
-
889
- resultArray.push(structuredResult);
1016
+ // Function to read properties individually
1017
+ const readPropertiesIndividually = () => {
1018
+ const promises = allProperties.map((property, index) => new Promise((propertyResolve) => {
1019
+ that.client.readProperty(
1020
+ deviceAddress,
1021
+ { type: type, instance: instance },
1022
+ property.id,
1023
+ readOptions,
1024
+ (err, value) => {
1025
+ if (err) {
1026
+ propertyResolve(null);
1027
+ } else {
1028
+ propertyResolve({
1029
+ id: property.id,
1030
+ index: value.property.index,
1031
+ value: value.values,
1032
+ });
1033
+ }
890
1034
  }
891
-
892
- if (index == allProperties.length - 1) {
893
- resolve({
894
- error: null,
895
- value: {
896
- values: [
897
- {
898
- objectId: {
899
- type: type,
900
- instance: instance,
901
- },
902
- values: resultArray,
1035
+ );
1036
+ }));
1037
+
1038
+ Promise.all(promises)
1039
+ .then(resultArray => {
1040
+ // Filter out null results
1041
+ const validResults = resultArray.filter(result => result !== null);
1042
+
1043
+ resolve({
1044
+ error: null,
1045
+ value: {
1046
+ values: [
1047
+ {
1048
+ objectId: {
1049
+ type: type,
1050
+ instance: instance,
903
1051
  },
904
- ],
905
- },
906
- });
907
- } else if (index < allProperties.length - 1) {
908
- index++;
909
- readIndividualProperties(index);
910
- }
911
- }
912
- );
913
- }
1052
+ values: validResults,
1053
+ },
1054
+ ],
1055
+ },
1056
+ });
1057
+ })
1058
+ .catch(reject);
1059
+ };
914
1060
  });
915
1061
  }
916
1062
 
1063
+
917
1064
  _readObjectPropList(deviceAddress, type, instance) {
918
1065
  return this._readObject(deviceAddress, type, instance, [{ id: baEnum.PropertyIdentifier.PROPERTY_LIST }]);
919
1066
  }
@@ -1048,6 +1195,7 @@ class BacnetClient extends EventEmitter {
1048
1195
  reducedDeviceList.forEach((device) => {
1049
1196
  delete device["pointsList"];
1050
1197
  });
1198
+
1051
1199
  resolve({
1052
1200
  renderList: that.renderList,
1053
1201
  deviceList: reducedDeviceList,
@@ -1096,42 +1244,6 @@ class BacnetClient extends EventEmitter {
1096
1244
  });
1097
1245
  }
1098
1246
 
1099
- updatePriorityQueue(priorityDevices) {
1100
- let that = this;
1101
- return new Promise(async function (resolve, reject) {
1102
- try {
1103
- let keys = Object.keys(priorityDevices);
1104
- if (keys.length > 0) {
1105
- keys.forEach(function (key) {
1106
- let device = that.deviceList.find((ele) => `${that.getDeviceAddress(ele)}-${ele.getDeviceId()}` == key);
1107
- let points = priorityDevices[key];
1108
- if (device) {
1109
- device.setPriorityQueue(points);
1110
- }
1111
- });
1112
- } else if (keys.length == 0) {
1113
- that.clearPriorityQueues();
1114
- }
1115
- resolve(true);
1116
- } catch (e) {
1117
- reject(e);
1118
- }
1119
- });
1120
- }
1121
-
1122
- clearPriorityQueues() {
1123
- let that = this;
1124
- that.deviceList.forEach(function (device) {
1125
- device.clearPriorityQueue();
1126
- });
1127
- }
1128
-
1129
- getPriorityDevices() {
1130
- let that = this;
1131
- let priorityDevices = that.deviceList.filter((device) => device.getPriorityQueueIsActive() == true);
1132
- return priorityDevices;
1133
- }
1134
-
1135
1247
  sortDevices(a, b) {
1136
1248
  if (a.deviceId < b.deviceId) {
1137
1249
  return -1;
@@ -1153,443 +1265,128 @@ class BacnetClient extends EventEmitter {
1153
1265
  return a.label.localeCompare(b.label);
1154
1266
  }
1155
1267
 
1156
- buildNetworkTreeData() {
1268
+ computeDeviceName(device) {
1269
+ if (device.getDisplayName() !== null && device.getDisplayName() !== "" && device.getDisplayName() !== undefined) {
1270
+ return device.getDisplayName();
1271
+ }
1272
+ return device.getDeviceName();
1273
+ }
1274
+
1275
+ checkInterruptFlag() {
1157
1276
  let that = this;
1158
- that.buildTreeMutex = new Mutex();
1159
- let displayNameCharThreshold = 40;
1277
+ let BreakException = {};
1160
1278
 
1161
- if (that.config.cacheFileEnabled) {
1162
- Store_Config(
1163
- JSON.stringify({
1164
- renderList: that.renderList,
1165
- deviceList: that.deviceList,
1166
- pointList: that.networkTree,
1167
- renderListCount: that.renderListCount,
1168
- })
1169
- );
1279
+ if (that.buildTreeException) {
1280
+ throw BreakException;
1170
1281
  }
1282
+ }
1171
1283
 
1172
- return new Promise(async function (resolve, reject) {
1173
- if (!that.renderList) that.renderList = [];
1174
- if (that.deviceList && that.networkTree) {
1175
- that.deviceList.forEach(function (deviceInfo, index) {
1176
- that.buildTreeMutex.acquire().then(function (release) {
1177
- let ipAddr =
1178
- typeof deviceInfo.getAddress() == "object" ? deviceInfo.getAddress().address : deviceInfo.getAddress();
1179
- let deviceId = deviceInfo.getDeviceId();
1180
- let deviceName = deviceInfo.getDeviceName() == null ? ipAddr : deviceInfo.getDeviceName();
1181
- let deviceKey = ipAddr + "-" + deviceInfo.getDeviceId();
1182
- let deviceObject = that.networkTree[deviceKey];
1183
- let isMstpDevice = deviceInfo.getIsMstpDevice();
1184
- let manualDiscoveryMode = deviceInfo.getManualDiscoveryMode();
1185
-
1186
- if (deviceObject && typeof deviceName !== "object") {
1187
- let children = [];
1188
- let pointIndex = 0;
1189
-
1190
- for (const pointName in deviceObject) {
1191
- let pointProperties = [];
1192
- let values = deviceObject[pointName];
1193
-
1194
- let displayName = pointName;
1195
- if (pointName.length > displayNameCharThreshold) {
1196
- displayName = "";
1197
- let charArray = pointName.split("");
1198
- for (let i = 0; i < charArray.length; i++) {
1199
- if (i < displayNameCharThreshold) {
1200
- displayName += charArray[i];
1201
- }
1202
- }
1203
- displayName += "...";
1204
- }
1284
+ getPointName(object, pointName) {
1285
+ if (object.displayName) {
1286
+ return object.displayName;
1287
+ }
1288
+ return pointName;
1289
+ }
1205
1290
 
1206
- if (values.objectName) {
1207
- pointProperties.push({
1208
- key: `${index}-0-${pointIndex}-0`,
1209
- label: `Name: ${values.objectName}`,
1210
- data: values.objectName,
1211
- icon: "pi pi-cog",
1212
- children: null,
1213
- });
1214
- }
1215
- if (
1216
- values.objectType &&
1217
- values.hasOwnProperty("objectID") &&
1218
- values.objectID.hasOwnProperty("type") &&
1219
- values.objectID.type !== 8
1220
- ) {
1221
- pointProperties.push({
1222
- key: `${index}-0-${pointIndex}-1`,
1223
- label: `Object Type: ${values.objectType}`,
1224
- data: values.objectType,
1225
- icon: "pi pi-cog",
1226
- children: null,
1227
- });
1228
- }
1229
- if (values.objectID && values.objectID.instance) {
1230
- pointProperties.push({
1231
- key: `${index}-0-${pointIndex}-2`,
1232
- label: `Object Instance: ${values.objectID.instance}`,
1233
- data: values.objectID.instance,
1234
- icon: "pi pi-cog",
1235
- children: null,
1236
- });
1237
- }
1238
- if (values.description) {
1239
- pointProperties.push({
1240
- key: `${index}-0-${pointIndex}-3`,
1241
- label: `Description: ${values.description}`,
1242
- data: `${values.description}`,
1243
- icon: "pi pi-cog",
1244
- children: null,
1245
- });
1246
- }
1247
- if (values.units) {
1248
- pointProperties.push({
1249
- key: `${index}-0-${pointIndex}-4`,
1250
- label: `Units: ${values.units}`,
1251
- data: `${values.units}`,
1252
- icon: "pi pi-cog",
1253
- children: null,
1254
- });
1255
- }
1256
- if (
1257
- values.presentValue !== "undefined" &&
1258
- values.presentValue !== null &&
1259
- typeof values.presentValue !== "undefined"
1260
- ) {
1261
- pointProperties.push({
1262
- key: `${index}-0-${pointIndex}-5`,
1263
- label: `Present Value: ${values.presentValue}`,
1264
- data: `${values.presentValue}`,
1265
- icon: "pi pi-cog",
1266
- children: null,
1267
- });
1268
- }
1269
- if (
1270
- values.systemStatus !== null &&
1271
- typeof values.systemStatus !== "undefined" &&
1272
- values.systemStatus !== ""
1273
- ) {
1274
- pointProperties.push({
1275
- key: `${index}-0-${pointIndex}-6`,
1276
- label: `System Status: ${values.systemStatus}`,
1277
- data: `${values.systemStatus}`,
1278
- icon: "pi pi-cog",
1279
- children: null,
1280
- });
1281
- }
1282
- if (values.modificationDate && !values.modificationDate.errorClass) {
1283
- pointProperties.push({
1284
- key: `${index}-0-${pointIndex}-7`,
1285
- label: `Modification Date: ${values.modificationDate}`,
1286
- data: `${values.modificationDate}`,
1287
- icon: "pi pi-cog",
1288
- children: null,
1289
- });
1290
- }
1291
- if (values.programState) {
1292
- pointProperties.push({
1293
- key: `${index}-0-${pointIndex}-8`,
1294
- label: `Program State: ${values.programState}`,
1295
- data: `${values.programState}`,
1296
- icon: "pi pi-cog",
1297
- children: null,
1298
- });
1299
- }
1300
- if (values.recordCount && !values.recordCount.errorClass) {
1301
- pointProperties.push({
1302
- key: `${index}-0-${pointIndex}-9`,
1303
- label: `Record Count: ${values.recordCount}`,
1304
- data: `${values.recordCount}`,
1305
- icon: "pi pi-cog",
1306
- children: null,
1307
- });
1308
- }
1309
- if (
1310
- values.hasOwnProperty("objectID") &&
1311
- values.objectID.hasOwnProperty("type") &&
1312
- values.objectID.type == 8
1313
- ) {
1314
- //device point, add segmentation supported, and apdu size
1315
- pointProperties.push({
1316
- key: `${index}-0-${pointIndex}-10`,
1317
- label: `Segmentation Supported: ${deviceInfo.getSegmentation()}`,
1318
- data: `${deviceInfo.getSegmentation()}`,
1319
- icon: "pi pi-cog",
1320
- children: null,
1321
- });
1322
- pointProperties.push({
1323
- key: `${index}-0-${pointIndex}-11`,
1324
- label: `APDU Size: ${deviceInfo.getMaxApdu()}`,
1325
- data: `${deviceInfo.getMaxApdu()}`,
1326
- icon: "pi pi-cog",
1327
- children: null,
1328
- });
1329
- }
1291
+ addUniqueToArray(device, array) {
1292
+ const foundIndex = array.findIndex(ele => ele.getDeviceId() === device.getDeviceId());
1293
+ if (foundIndex === -1) {
1294
+ array.push(device);
1295
+ }
1296
+ }
1330
1297
 
1331
- children.push({
1332
- key: `${index}-0-${pointIndex}`,
1333
- label: displayName,
1334
- data: displayName,
1335
- pointName: pointName,
1336
- icon: that.getPointIcon(values),
1337
- children: pointProperties,
1338
- type: "point",
1339
- parentDevice: deviceName,
1340
- showAdded: false,
1341
- bacnetType: values.meta.objectId.type,
1342
- });
1343
- pointIndex++;
1298
+ async getDevicesNotRenderedYet() {
1299
+ let that = this;
1300
+ let missingDevices = [];
1301
+ for (let i = 0; i < that.deviceList.length; i++) {
1302
+ const device = that.deviceList[i];
1303
+ if (!device.getIsMstpDevice()) {
1304
+ //ip device
1305
+ const foundIndex = that.renderList.findIndex(ele => ele.deviceId == device.getDeviceId());
1306
+ if (foundIndex == -1) {
1307
+ that.addUniqueToArray(device, missingDevices);
1308
+ }
1309
+ } else {
1310
+ //mstp device
1311
+ const foundParentIndex = that.renderList.findIndex(ele => ele.deviceId == device.getParentDeviceId());
1312
+ if (foundParentIndex == -1) {
1313
+ //parent not existent in tree
1314
+ const parentDeviceIndex = that.deviceList.findIndex(ele => ele.getDeviceId() === device.getParentDeviceId());
1315
+ if (parentDeviceIndex !== -1) {
1316
+ that.addUniqueToArray(that.deviceList[parentDeviceIndex], missingDevices);
1317
+ }
1318
+ that.addUniqueToArray(device, missingDevices);
1319
+ } else {
1320
+ const parentTreeDevice = that.renderList[foundParentIndex];
1321
+ let mstpIndex = -1;
1322
+ parentTreeDevice.children.forEach(child => {
1323
+ if (child.label.includes("MSTP")) {
1324
+ const tempIndex = child.children.findIndex(ele => ele.deviceId == device.getDeviceId());
1325
+ if (tempIndex !== -1) {
1326
+ mstpIndex = tempIndex;
1344
1327
  }
1345
- let foundIndex = that.renderList.findIndex((ele) => ele.deviceId == deviceId && ele.ipAddr == ipAddr);
1346
- if (foundIndex !== -1) {
1347
- let folderJson = [];
1348
- if (deviceInfo.hasChildDevices()) {
1349
- folderJson = [
1350
- {
1351
- key: `${index}-0`,
1352
- label: "Points",
1353
- data: "Points Folder",
1354
- icon: "pi pi-circle-fill",
1355
- type: "pointFolder",
1356
- children: children.sort(that.sortPoints),
1357
- },
1358
- {
1359
- key: `${index}-1`,
1360
- label: "MSTP Network",
1361
- data: "Devices Folder",
1362
- icon: "pi pi-database",
1363
- type: "deviceFolder",
1364
- children: [],
1365
- },
1366
- ];
1367
- } else {
1368
- folderJson = [
1369
- {
1370
- key: `${index}-0`,
1371
- label: "Points",
1372
- data: "Points Folder",
1373
- icon: "pi pi-circle-fill",
1374
- type: "pointFolder",
1375
- children: children.sort(that.sortPoints),
1376
- },
1377
- ];
1378
- }
1328
+ }
1329
+ });
1330
+ if (mstpIndex == -1) {
1331
+ that.addUniqueToArray(device, missingDevices);
1332
+ }
1333
+ }
1334
+ }
1335
+ }
1336
+ return missingDevices;
1337
+ }
1379
1338
 
1380
- if (!isMstpDevice) {
1381
- that.renderList[foundIndex] = {
1382
- key: index,
1383
- label: deviceName,
1384
- data: deviceName,
1385
- icon: that.getDeviceIcon(isMstpDevice, manualDiscoveryMode),
1386
- children: that.renderList[foundIndex].children,
1387
- type: "device",
1388
- lastSeen: deviceInfo.getLastSeen(),
1389
- showAdded: false,
1390
- ipAddr: ipAddr,
1391
- deviceId: deviceId,
1392
- isMstpDevice: isMstpDevice,
1393
- };
1394
- } else if (isMstpDevice) {
1395
- let parentDeviceId = deviceInfo.getParentDeviceId();
1396
- let parentDeviceIndex = that.renderList.findIndex(
1397
- (ele) => ele.deviceId == parentDeviceId && ele.ipAddr == ipAddr
1398
- );
1399
-
1400
- if (parentDeviceIndex !== -1 && that.renderList[parentDeviceIndex].children[1].children) {
1401
- let mstpDeviceIndex = that.renderList[parentDeviceIndex].children[1].children.findIndex(
1402
- (ele) => ele.deviceId == deviceId && ele.ipAddr == ipAddr
1403
- );
1404
- if (mstpDeviceIndex == -1) {
1405
- //that.renderListCount++;
1406
- that.renderList[parentDeviceIndex].children[1].children.push({
1407
- key: index,
1408
- label: deviceName,
1409
- data: deviceName,
1410
- icon: that.getDeviceIcon(isMstpDevice, manualDiscoveryMode),
1411
- children: folderJson,
1412
- type: "device",
1413
- lastSeen: deviceInfo.getLastSeen(),
1414
- showAdded: false,
1415
- ipAddr: ipAddr,
1416
- deviceId: deviceId,
1417
- isMstpDevice: isMstpDevice,
1418
- });
1419
- } else {
1420
- that.renderList[parentDeviceIndex].children[1].children[mstpDeviceIndex] = {
1421
- key: index,
1422
- label: deviceName,
1423
- data: deviceName,
1424
- icon: that.getDeviceIcon(isMstpDevice, manualDiscoveryMode),
1425
- children: folderJson,
1426
- type: "device",
1427
- lastSeen: deviceInfo.getLastSeen(),
1428
- showAdded: false,
1429
- ipAddr: ipAddr,
1430
- deviceId: deviceId,
1431
- isMstpDevice: isMstpDevice,
1432
- };
1433
- }
1434
- }
1435
- }
1436
- } else if (foundIndex == -1) {
1437
- let folderJson = [];
1438
- if (deviceInfo.hasChildDevices()) {
1439
- folderJson = [
1440
- {
1441
- key: `${index}-0`,
1442
- label: "Points",
1443
- data: "Points Folder",
1444
- icon: "pi pi-circle-fill",
1445
- type: "pointFolder",
1446
- children: children.sort(that.sortPoints),
1447
- },
1448
- {
1449
- key: `${index}-1`,
1450
- label: "MSTP Network",
1451
- data: "Devices Folder",
1452
- icon: "pi pi-database",
1453
- type: "deviceFolder",
1454
- children: [],
1455
- },
1456
- ];
1457
- } else {
1458
- folderJson = [
1459
- {
1460
- key: `${index}-0`,
1461
- label: "Points",
1462
- data: "Points Folder",
1463
- icon: "pi pi-circle-fill",
1464
- type: "pointFolder",
1465
- children: children.sort(that.sortPoints),
1466
- },
1467
- ];
1468
- }
1339
+ initialTreeBuild = true;
1469
1340
 
1470
- if (!isMstpDevice) {
1471
- //that.renderListCount++;
1472
- that.renderList.push({
1473
- key: index,
1474
- label: deviceName,
1475
- data: deviceName,
1476
- icon: that.getDeviceIcon(isMstpDevice, manualDiscoveryMode),
1477
- children: folderJson,
1478
- type: "device",
1479
- lastSeen: deviceInfo.getLastSeen(),
1480
- showAdded: false,
1481
- ipAddr: ipAddr,
1482
- deviceId: deviceId,
1483
- isMstpDevice: isMstpDevice,
1484
- });
1485
- } else if (isMstpDevice) {
1486
- let parentDeviceId = deviceInfo.getParentDeviceId();
1487
- let parentDeviceIndex = that.renderList.findIndex(
1488
- (ele) => ele.deviceId == parentDeviceId && ele.ipAddr == ipAddr
1489
- );
1341
+ async doTreeBuilder() {
1342
+ let that = this;
1490
1343
 
1491
- if (
1492
- parentDeviceIndex !== -1 &&
1493
- that.renderList[parentDeviceIndex].children &&
1494
- that.renderList[parentDeviceIndex].children[1].children
1495
- ) {
1496
- let mstpDeviceIndex = that.renderList[parentDeviceIndex].children[1].children.findIndex(
1497
- (ele) => ele.deviceId == deviceId && ele.ipAddr == ipAddr
1498
- );
1499
-
1500
- if (mstpDeviceIndex == -1) {
1501
- // that.renderListCount++;
1502
- that.renderList[parentDeviceIndex].children[1].children.push({
1503
- key: index,
1504
- label: deviceName,
1505
- data: deviceName,
1506
- icon: that.getDeviceIcon(isMstpDevice, manualDiscoveryMode),
1507
- children: folderJson,
1508
- type: "device",
1509
- lastSeen: deviceInfo.getLastSeen(),
1510
- showAdded: false,
1511
- ipAddr: ipAddr,
1512
- deviceId: deviceId,
1513
- isMstpDevice: isMstpDevice,
1514
- });
1515
- } else {
1516
- that.renderList[parentDeviceIndex].children[1].children[mstpDeviceIndex] = {
1517
- key: index,
1518
- label: deviceName,
1519
- data: deviceName,
1520
- icon: that.getDeviceIcon(isMstpDevice, manualDiscoveryMode),
1521
- children: folderJson,
1522
- type: "device",
1523
- lastSeen: deviceInfo.getLastSeen(),
1524
- showAdded: false,
1525
- ipAddr: ipAddr,
1526
- deviceId: deviceId,
1527
- isMstpDevice: isMstpDevice,
1528
- };
1529
- }
1530
- }
1531
- }
1532
- }
1344
+ const treeWorker = new treeBuilder(that.deviceList, that.networkTree, that.renderList, that.renderListCount, that.initialTreeBuild);
1533
1345
 
1534
- if (index == that.deviceList.length - 1) {
1535
- that.renderList.sort(that.sortDevices);
1536
- resolve({
1537
- renderList: that.renderList,
1538
- deviceList: that.deviceList,
1539
- pointList: that.networkTree,
1540
- pollFrequency: that.discover_polling_schedule,
1541
- });
1542
- }
1543
- } else {
1544
- if (index == that.deviceList.length - 1) {
1545
- that.renderList.sort(that.sortDevices);
1546
- resolve({
1547
- renderList: that.renderList,
1548
- deviceList: that.deviceList,
1549
- pointList: that.networkTree,
1550
- pollFrequency: that.discover_polling_schedule,
1551
- });
1552
- }
1553
- }
1346
+ treeWorker.cacheData();
1554
1347
 
1555
- release();
1556
- });
1557
- });
1558
- }
1559
- });
1348
+ //const missingDevices = await that.getDevicesNotRenderedYet();
1349
+
1350
+ for (let i = 0; i < that.deviceList.length; i++) {
1351
+ let device = that.deviceList[i];
1352
+ await treeWorker.processDevice(device, i);
1353
+ }
1354
+
1355
+ that.deviceList = treeWorker.deviceList;
1356
+ that.networkTree = treeWorker.networkTree;
1357
+ that.renderList = treeWorker.renderList;
1358
+
1359
+ that.initialTreeBuild = false;
1560
1360
  }
1561
1361
 
1562
1362
  countDevices() {
1563
1363
  let that = this;
1564
-
1565
1364
  let deviceCount = 0;
1566
1365
 
1567
- that.renderList.forEach(function (device, index) {
1568
- if (device) {
1569
- if (
1570
- device.children[1] &&
1571
- device.children[1].label == "MSTP Network" &&
1572
- device.children[1].children &&
1573
- device.children[1].children.length > 0
1574
- ) {
1575
- //increment for parent device / mstp router
1576
- deviceCount++;
1577
- //increment for mstp device list
1578
- deviceCount += device.children[1].children.length;
1579
- } else {
1580
- deviceCount++;
1366
+ if (that.renderList && that.renderList.length > 0) {
1367
+ that.renderList.forEach(function (device, index) {
1368
+ if (device && device.children.length > 0) {
1369
+ device.children.forEach(function (folder) {
1370
+ if (folder.label == "Points") {
1371
+ //increment for parent device / mstp router
1372
+ deviceCount++;
1373
+ } else if (folder.label.includes("MSTP")) {
1374
+ //increment for mstp device list
1375
+ deviceCount += folder.children.length;
1376
+ }
1377
+ });
1581
1378
  }
1582
- }
1583
- if (index == that.renderList.length - 1) {
1584
- that.renderListCount = deviceCount;
1585
- }
1586
- });
1379
+ if (index == that.renderList.length - 1) {
1380
+ that.renderListCount = deviceCount;
1381
+ }
1382
+ });
1383
+ }
1587
1384
  }
1588
1385
 
1589
- buildJsonObject(device, priorityQueue) {
1386
+ buildJsonObject(device) {
1590
1387
  let that = this;
1591
1388
  let address = device.address;
1592
- let pointList = priorityQueue !== null ? priorityQueue : device.getPointsList();
1389
+ let pointList = device.getPointsList();
1593
1390
  let requestMutex = new Mutex();
1594
1391
 
1595
1392
  return new Promise(function (resolve, reject) {
@@ -1651,7 +1448,7 @@ class BacnetClient extends EventEmitter {
1651
1448
  // Builds response object for a fully qualified
1652
1449
  buildResponse(fullObjects, device) {
1653
1450
  let that = this;
1654
-
1451
+ const reg = /[$#\/\\+]/gi;
1655
1452
  return new Promise(function (resolve, reject) {
1656
1453
  let deviceKey =
1657
1454
  typeof device.getAddress() == "object"
@@ -1673,6 +1470,7 @@ class BacnetClient extends EventEmitter {
1673
1470
  let objectType = that._findValueById(pointProperty.values, baEnum.PropertyIdentifier.OBJECT_TYPE);
1674
1471
  let objectId;
1675
1472
  if (objectName !== null && typeof objectName == "string") {
1473
+ objectName = objectName.replace(reg, '');
1676
1474
  objectId = objectName + "_" + bac_obj + "_" + pointProperty.objectId.instance;
1677
1475
 
1678
1476
  try {
@@ -1697,6 +1495,15 @@ class BacnetClient extends EventEmitter {
1697
1495
  } else if (objectType == 40) {
1698
1496
  //character string
1699
1497
  values[objectId].presentValue = object.value[0].value;
1498
+ } else if (objectType == 13 || objectType == 14 || objectType == 19) {
1499
+ //check for MSV MSI MSO - for enum state text
1500
+ if (values[objectId].stateTextArray && values[objectId].stateTextArray.length > 0) {
1501
+ if (object.value[0].value == 0) {
1502
+ values[objectId].presentValue = values[objectId].stateTextArray[object.value[0].value].value;
1503
+ } else if (object.value[0].value !== 0) {
1504
+ values[objectId].presentValue = values[objectId].stateTextArray[object.value[0].value - 1].value;
1505
+ }
1506
+ }
1700
1507
  } else {
1701
1508
  values[objectId].presentValue = roundDecimalPlaces(object.value[0].value, 2);
1702
1509
  }
@@ -1711,7 +1518,12 @@ class BacnetClient extends EventEmitter {
1711
1518
  values[objectId].units = getUnit(object.value[0].value);
1712
1519
  break;
1713
1520
  case baEnum.PropertyIdentifier.OBJECT_NAME:
1714
- if (object.value[0] && object.value[0].value) values[objectId].objectName = object.value[0].value;
1521
+ if (object.value[0] && object.value[0].value) {
1522
+ values[objectId].objectName = object.value[0].value.replace(reg, '');
1523
+ if (!values[objectId].displayName) {
1524
+ values[objectId].displayName = object.value[0].value.replace(reg, '');
1525
+ }
1526
+ }
1715
1527
  break;
1716
1528
  case baEnum.PropertyIdentifier.OBJECT_TYPE:
1717
1529
  if (object.value[0] && object.value[0].value) values[objectId].objectType = object.value[0].value;
@@ -1722,13 +1534,11 @@ class BacnetClient extends EventEmitter {
1722
1534
  case baEnum.PropertyIdentifier.PROPERTY_LIST:
1723
1535
  if (object.value) values[objectId].propertyList = that.mapPropsToArray(object.value);
1724
1536
  break;
1725
-
1726
1537
  case baEnum.PropertyIdentifier.SYSTEM_STATUS:
1727
1538
  if (object.value[0]) {
1728
1539
  values[objectId].systemStatus = that.getPROP_SYSTEM_STATUS(object.value[0].value);
1729
1540
  }
1730
1541
  break;
1731
-
1732
1542
  case baEnum.PropertyIdentifier.MODIFICATION_DATE:
1733
1543
  if (object.value[0]) {
1734
1544
  values[objectId].modificationDate = object.value[0].value;
@@ -1751,6 +1561,28 @@ class BacnetClient extends EventEmitter {
1751
1561
  values[objectId].hasPriorityArray = true;
1752
1562
  }
1753
1563
  break;
1564
+ case baEnum.PropertyIdentifier.STATE_TEXT:
1565
+ if (object.value) {
1566
+ values[objectId].stateTextArray = object.value;
1567
+ if (typeof values[objectId].presentValue == "number" &&
1568
+ values[objectId].presentValue !== null &&
1569
+ values[objectId].presentValue !== undefined) {
1570
+ const tempIndex = values[objectId].presentValue;
1571
+ if (tempIndex == 0) {
1572
+ values[objectId].presentValue = values[objectId].stateTextArray[tempIndex].value;
1573
+ } else if (tempIndex !== 0) {
1574
+ values[objectId].presentValue = values[objectId].stateTextArray[tempIndex - 1].value;
1575
+ }
1576
+ }
1577
+ }
1578
+ break;
1579
+ case baEnum.PropertyIdentifier.VENDOR_NAME:
1580
+ if (object.value) {
1581
+ if (object.value[0].value && typeof object.value[0].value == "string") {
1582
+ values[objectId].vendorName = object.value[0].value;
1583
+ }
1584
+ }
1585
+ break;
1754
1586
  }
1755
1587
  }
1756
1588
  if (