@bitpoolos/edge-bacnet 1.4.5 → 1.4.6
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 +22 -0
- package/bacnet_client.js +341 -400
- package/bacnet_device.js +10 -0
- package/bacnet_read.html +29 -6
- package/package.json +1 -1
- package/resources/node-bacstack-ts/dist/lib/client.js +3 -0
- package/resources/node-bacstack-ts/dist/lib/transport.js +12 -7
- package/resources/style.css +17 -8
- package/treeBuilder.js +41 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.4.6] - 17-09-2024
|
|
4
|
+
### Summary
|
|
5
|
+
|
|
6
|
+
Minor fixes.
|
|
7
|
+
|
|
8
|
+
Wrapped updateDeviceName function with promise to avoid network conflict for clients.
|
|
9
|
+
|
|
10
|
+
Fixed UI styling bugs, primarily with long names and numbers wrapping.
|
|
11
|
+
|
|
12
|
+
Added ability to set device name of dummy MSTP routers.
|
|
13
|
+
|
|
14
|
+
Improved error handling for querying devices and building point json structures.
|
|
15
|
+
|
|
16
|
+
Fixed read node point export bug.
|
|
17
|
+
|
|
18
|
+
Excluded commas via point name conditioning
|
|
19
|
+
|
|
20
|
+
Fixed gateway port assignment bug - ability to communicate on different ports working now.
|
|
21
|
+
|
|
22
|
+
More async / await refactoring.
|
|
23
|
+
|
|
24
|
+
|
|
3
25
|
## [1.4.5] - 16-08-2024
|
|
4
26
|
### Summary
|
|
5
27
|
|
package/bacnet_client.js
CHANGED
|
@@ -27,6 +27,7 @@ class BacnetClient extends EventEmitter {
|
|
|
27
27
|
that.mutex = new Mutex();
|
|
28
28
|
that.manualMutex = new Mutex();
|
|
29
29
|
that.pollInProgress = false;
|
|
30
|
+
that.buildJsonInProgress = false;
|
|
30
31
|
that.scanMatrix = [];
|
|
31
32
|
that.renderListCount = 0;
|
|
32
33
|
|
|
@@ -63,6 +64,7 @@ class BacnetClient extends EventEmitter {
|
|
|
63
64
|
};
|
|
64
65
|
|
|
65
66
|
try {
|
|
67
|
+
|
|
66
68
|
that.client = new bacnet.Client({
|
|
67
69
|
apduTimeout: config.apduTimeout,
|
|
68
70
|
interface: config.localIpAdrress,
|
|
@@ -81,7 +83,13 @@ class BacnetClient extends EventEmitter {
|
|
|
81
83
|
|
|
82
84
|
//query device task
|
|
83
85
|
const queryDevices = new Task("simple task", () => {
|
|
84
|
-
if (!that.pollInProgress)
|
|
86
|
+
if (!that.pollInProgress) {
|
|
87
|
+
that.queryDevices();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (!that.buildJsonInProgress) {
|
|
91
|
+
that.buildJsonTree();
|
|
92
|
+
}
|
|
85
93
|
});
|
|
86
94
|
|
|
87
95
|
const queryJob = new SimpleIntervalJob({ seconds: parseInt(that.device_read_schedule) }, queryDevices);
|
|
@@ -102,6 +110,7 @@ class BacnetClient extends EventEmitter {
|
|
|
102
110
|
|
|
103
111
|
setTimeout(() => {
|
|
104
112
|
that.queryDevices();
|
|
113
|
+
that.buildJsonTree();
|
|
105
114
|
}, "5000");
|
|
106
115
|
} catch (e) {
|
|
107
116
|
that.logOut("Issue initializing client: ", e);
|
|
@@ -288,6 +297,7 @@ class BacnetClient extends EventEmitter {
|
|
|
288
297
|
that.renderList = [];
|
|
289
298
|
that.networkTree = {};
|
|
290
299
|
that.pollInProgress = false;
|
|
300
|
+
that.buildJsonInProgress = false;
|
|
291
301
|
that.renderListCount = 0;
|
|
292
302
|
resolve(true);
|
|
293
303
|
} catch (e) {
|
|
@@ -301,9 +311,7 @@ class BacnetClient extends EventEmitter {
|
|
|
301
311
|
let that = this;
|
|
302
312
|
return new Promise((resolve, reject) => {
|
|
303
313
|
try {
|
|
304
|
-
let renderListIndex = that.renderList.findIndex(
|
|
305
|
-
(ele) => ele.deviceId == device.deviceId && ele.ipAddr == device.address
|
|
306
|
-
);
|
|
314
|
+
let renderListIndex = that.renderList.findIndex((ele) => ele.deviceId == device.deviceId && ele.ipAddr == device.address);
|
|
307
315
|
let deviceListIndex = that.deviceList.findIndex((ele) => ele.getDeviceId() == device.deviceId);
|
|
308
316
|
let deviceKey = device.address + "-" + device.deviceId;
|
|
309
317
|
delete that.networkTree[deviceKey];
|
|
@@ -319,62 +327,46 @@ class BacnetClient extends EventEmitter {
|
|
|
319
327
|
});
|
|
320
328
|
}
|
|
321
329
|
|
|
322
|
-
updatePointsForDevice(deviceObject) {
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
330
|
+
async updatePointsForDevice(deviceObject) {
|
|
331
|
+
try {
|
|
332
|
+
let device = this.deviceList.find((ele) => ele.getDeviceId() === deviceObject.deviceId);
|
|
333
|
+
|
|
334
|
+
if (!device) {
|
|
335
|
+
throw new Error(`Device with ID ${deviceObject.deviceId} not found`);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
await this.updateDeviceName(device);
|
|
339
|
+
|
|
340
|
+
if (!device.getIsProtocolServicesSet()) {
|
|
341
|
+
try {
|
|
342
|
+
const result = await this.getProtocolSupported(device);
|
|
343
|
+
const decodedValues = decodeBitArray(8, result.values[0].originalBitString.value);
|
|
344
|
+
device.setProtocolServicesSupported(decodedValues);
|
|
345
|
+
} catch (error) {
|
|
346
|
+
this.logOut("getProtocolSupported error: ", error);
|
|
339
347
|
}
|
|
348
|
+
}
|
|
340
349
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
that
|
|
345
|
-
.buildJsonObject(device)
|
|
346
|
-
.then(function () {
|
|
347
|
-
// do nothing for now
|
|
348
|
-
resolve(true);
|
|
349
|
-
})
|
|
350
|
-
.catch(function (e) {
|
|
351
|
-
that.logOut(`Update points list error 1: ${that.getDeviceAddress(device)}`, e);
|
|
352
|
-
});
|
|
353
|
-
})
|
|
354
|
-
.catch(function (e) {
|
|
355
|
-
that.logOut(`Update points list error 2: ${that.getDeviceAddress(device)}`, e);
|
|
356
|
-
device.setManualDiscoveryMode(true);
|
|
357
|
-
that
|
|
358
|
-
.getDevicePointListWithoutObjectList(device)
|
|
359
|
-
.then(function () {
|
|
360
|
-
that
|
|
361
|
-
.buildJsonObject(device)
|
|
362
|
-
.then(function () {
|
|
363
|
-
// do nothing for now
|
|
364
|
-
resolve(true);
|
|
365
|
-
})
|
|
366
|
-
.catch(function (e) {
|
|
367
|
-
that.logOut(`Update points list error 3: ${that.getDeviceAddress(device)}`, e);
|
|
368
|
-
});
|
|
369
|
-
})
|
|
370
|
-
.catch(function (e) {
|
|
371
|
-
that.logOut(`Update points list error 4: ${that.getDeviceAddress(device)}`, e);
|
|
372
|
-
});
|
|
373
|
-
});
|
|
350
|
+
try {
|
|
351
|
+
await this.getDevicePointList(device);
|
|
352
|
+
await this.buildJsonObject(device);
|
|
374
353
|
} catch (e) {
|
|
375
|
-
|
|
354
|
+
this.logOut(`Update points list error 2: ${this.getDeviceAddress(device)}`, e);
|
|
355
|
+
device.setManualDiscoveryMode(true);
|
|
356
|
+
|
|
357
|
+
try {
|
|
358
|
+
await this.getDevicePointListWithoutObjectList(device);
|
|
359
|
+
await this.buildJsonObject(device);
|
|
360
|
+
} catch (e) {
|
|
361
|
+
this.logOut(`Update points list error 4: ${this.getDeviceAddress(device)}`, e);
|
|
362
|
+
}
|
|
376
363
|
}
|
|
377
|
-
|
|
364
|
+
|
|
365
|
+
return true;
|
|
366
|
+
} catch (e) {
|
|
367
|
+
this.logOut(`Error in updatePointsForDevice: ${e.message}`, e);
|
|
368
|
+
throw e; // Re-throw the error to be handled by the caller
|
|
369
|
+
}
|
|
378
370
|
}
|
|
379
371
|
|
|
380
372
|
applyDisplayNames(pointsToRead) {
|
|
@@ -407,13 +399,21 @@ class BacnetClient extends EventEmitter {
|
|
|
407
399
|
let that = this;
|
|
408
400
|
return new Promise((resolve, reject) => {
|
|
409
401
|
try {
|
|
410
|
-
let
|
|
402
|
+
let address = "";
|
|
403
|
+
if (typeof deviceObject.address == "string") {
|
|
404
|
+
address = deviceObject.address;
|
|
405
|
+
} else if (typeof deviceObject.address == "object") {
|
|
406
|
+
address = deviceObject.address.address;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
let device = that.deviceList.find(
|
|
410
|
+
(ele) => that.getDeviceAddress(ele) == address && ele.getDeviceId() == deviceObject.deviceId
|
|
411
|
+
);
|
|
411
412
|
device.setDisplayName(displayName);
|
|
412
413
|
that.buildTreeException = true;
|
|
413
414
|
resolve(true);
|
|
414
415
|
} catch (e) {
|
|
415
416
|
that.logOut("setDeviceDisplayName error: ", e);
|
|
416
|
-
|
|
417
417
|
reject(e);
|
|
418
418
|
}
|
|
419
419
|
});
|
|
@@ -457,108 +457,89 @@ class BacnetClient extends EventEmitter {
|
|
|
457
457
|
});
|
|
458
458
|
}
|
|
459
459
|
|
|
460
|
-
|
|
460
|
+
//test re write
|
|
461
|
+
async queryDevices() {
|
|
461
462
|
let that = this;
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
if (typeof device == "object") {
|
|
476
|
-
if (device.getIsProtocolServicesSet() == false) {
|
|
477
|
-
that
|
|
478
|
-
.getProtocolSupported(device)
|
|
479
|
-
.then(function (result) {
|
|
463
|
+
try {
|
|
464
|
+
that.pollInProgress = true;
|
|
465
|
+
let index = 0;
|
|
466
|
+
await query(index);
|
|
467
|
+
|
|
468
|
+
async function query(index) {
|
|
469
|
+
if (index < that.deviceList.length) {
|
|
470
|
+
let device = that.deviceList[index];
|
|
471
|
+
if (typeof device == "object" && (device.getIsDumbMstpRouter() == false || device.getIsDumbMstpRouter() == undefined)) {
|
|
472
|
+
if (device.getIsProtocolServicesSet() == false) {
|
|
473
|
+
try {
|
|
474
|
+
let result = await that.getProtocolSupported(device);
|
|
480
475
|
let decodedValues = decodeBitArray(8, result.values[0].originalBitString.value);
|
|
481
476
|
device.setProtocolServicesSupported(decodedValues);
|
|
482
|
-
})
|
|
483
|
-
.catch(function (error) {
|
|
477
|
+
} catch (error) {
|
|
484
478
|
that.logOut("getProtocolSupported error: ", error);
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
that
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
that
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
.catch(function (e) {
|
|
499
|
-
that.logOut(`getDevicePointList error: ${device.getAddress()}`, e);
|
|
500
|
-
query(index);
|
|
501
|
-
});
|
|
502
|
-
})
|
|
503
|
-
.catch(function (e) {
|
|
479
|
+
index++;
|
|
480
|
+
await query(index);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
try {
|
|
484
|
+
await that.updateDeviceName(device);
|
|
485
|
+
if (device.getSegmentation() !== 3) {
|
|
486
|
+
try {
|
|
487
|
+
await that.getDevicePointList(device);
|
|
488
|
+
|
|
489
|
+
index++;
|
|
490
|
+
await query(index);
|
|
491
|
+
} catch (e) {
|
|
504
492
|
that.logOut(`getDevicePointList error: ${device.getAddress()}`, e);
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
.then(function () {
|
|
527
|
-
that
|
|
528
|
-
.buildJsonObject(device)
|
|
529
|
-
.then(function () {
|
|
530
|
-
query(index);
|
|
531
|
-
})
|
|
532
|
-
.catch(function (e) {
|
|
533
|
-
that.logOut(`getDevicePointList error: ${device.getAddress()}`, e);
|
|
534
|
-
query(index);
|
|
535
|
-
});
|
|
536
|
-
})
|
|
537
|
-
.catch(function (e) {
|
|
538
|
-
query(index);
|
|
539
|
-
});
|
|
493
|
+
|
|
494
|
+
index++;
|
|
495
|
+
await query(index);
|
|
496
|
+
}
|
|
497
|
+
} else if (device.getSegmentation() == 3) {
|
|
498
|
+
try {
|
|
499
|
+
await that.getDevicePointListWithoutObjectList(device);
|
|
500
|
+
index++;
|
|
501
|
+
await query(index);
|
|
502
|
+
} catch (e) {
|
|
503
|
+
that.logOut(`getDevicePointList error: ${device.getAddress()}`, e);
|
|
504
|
+
|
|
505
|
+
index++;
|
|
506
|
+
await query(index);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
} catch (e) {
|
|
510
|
+
that.logOut("Error while querying devices: ", e);
|
|
511
|
+
|
|
512
|
+
index++;
|
|
513
|
+
await query(index);
|
|
540
514
|
}
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
query(index);
|
|
515
|
+
} else {
|
|
516
|
+
index++;
|
|
517
|
+
await query(index);
|
|
544
518
|
}
|
|
545
|
-
} else {
|
|
546
|
-
that.
|
|
547
|
-
query(index);
|
|
519
|
+
} else if (index == that.deviceList.length) {
|
|
520
|
+
that.pollInProgress = false;
|
|
548
521
|
}
|
|
549
|
-
} else if (index == that.deviceList.length) {
|
|
550
|
-
that.pollInProgress = false;
|
|
551
522
|
}
|
|
523
|
+
} catch (e) {
|
|
524
|
+
that.logOut("Error while querying devices: ", e);
|
|
552
525
|
}
|
|
553
526
|
}
|
|
554
527
|
|
|
555
528
|
updateDeviceName(device) {
|
|
556
529
|
let that = this;
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
device.
|
|
560
|
-
|
|
561
|
-
|
|
530
|
+
return new Promise((resolve, reject) => {
|
|
531
|
+
that
|
|
532
|
+
._getDeviceName(device.getAddress(), device.getDeviceId())
|
|
533
|
+
.then(function (deviceObject) {
|
|
534
|
+
if (typeof deviceObject.name == "string") {
|
|
535
|
+
device.setDeviceName(deviceObject.name + " " + device.getDeviceId());
|
|
536
|
+
device.setPointsList(deviceObject.devicePointEntry);
|
|
537
|
+
}
|
|
538
|
+
resolve();
|
|
539
|
+
})
|
|
540
|
+
.catch(function (e) {
|
|
541
|
+
reject(e);
|
|
542
|
+
});
|
|
562
543
|
});
|
|
563
544
|
}
|
|
564
545
|
|
|
@@ -598,7 +579,13 @@ class BacnetClient extends EventEmitter {
|
|
|
598
579
|
|
|
599
580
|
// //query device task
|
|
600
581
|
const queryDevices = new Task("simple task", () => {
|
|
601
|
-
if (!that.pollInProgress)
|
|
582
|
+
if (!that.pollInProgress) {
|
|
583
|
+
that.queryDevices();
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
if (!that.buildJsonInProgress) {
|
|
587
|
+
that.buildJsonTree();
|
|
588
|
+
}
|
|
602
589
|
});
|
|
603
590
|
|
|
604
591
|
const queryJob = new SimpleIntervalJob({ seconds: parseInt(config.device_read_schedule) }, queryDevices);
|
|
@@ -780,11 +767,7 @@ class BacnetClient extends EventEmitter {
|
|
|
780
767
|
|
|
781
768
|
if (isNumber(val)) {
|
|
782
769
|
pointRef.presentValue = roundDecimalPlaces(val, roundDecimal);
|
|
783
|
-
if (
|
|
784
|
-
pointRef.meta.objectId.type == 19 ||
|
|
785
|
-
pointRef.meta.objectId.type == 13 ||
|
|
786
|
-
pointRef.meta.objectId.type == 14
|
|
787
|
-
) {
|
|
770
|
+
if (pointRef.meta.objectId.type == 19 || pointRef.meta.objectId.type == 13 || pointRef.meta.objectId.type == 14) {
|
|
788
771
|
if (pointRef.stateTextArray && typeof pointRef.stateTextArray[0].value !== "object") {
|
|
789
772
|
if (val != 0) {
|
|
790
773
|
pointRef.presentValue = pointRef.stateTextArray[val - 1].value;
|
|
@@ -941,8 +924,12 @@ class BacnetClient extends EventEmitter {
|
|
|
941
924
|
}
|
|
942
925
|
} catch (e) {
|
|
943
926
|
that.logOut("Unable to get device name: ", e);
|
|
927
|
+
reject(e);
|
|
944
928
|
}
|
|
945
929
|
}
|
|
930
|
+
if (err) {
|
|
931
|
+
reject(err);
|
|
932
|
+
}
|
|
946
933
|
});
|
|
947
934
|
});
|
|
948
935
|
}
|
|
@@ -995,6 +982,7 @@ class BacnetClient extends EventEmitter {
|
|
|
995
982
|
device.setManualDiscoveryMode(false);
|
|
996
983
|
let result = await that.scanDevice(device);
|
|
997
984
|
device.setPointsList(result);
|
|
985
|
+
device.setLastSeen(Date.now());
|
|
998
986
|
resolve(result);
|
|
999
987
|
} catch (e) {
|
|
1000
988
|
that.logOut(`Error getting point list for ${device.getAddress().toString()} - ${device.getDeviceId()}: `, e);
|
|
@@ -1011,6 +999,7 @@ class BacnetClient extends EventEmitter {
|
|
|
1011
999
|
.scanDeviceManually(device)
|
|
1012
1000
|
.then(function (result) {
|
|
1013
1001
|
device.setPointsList(result);
|
|
1002
|
+
device.setLastSeen(Date.now());
|
|
1014
1003
|
resolve(result);
|
|
1015
1004
|
})
|
|
1016
1005
|
.catch(function (error) {
|
|
@@ -1615,280 +1604,232 @@ class BacnetClient extends EventEmitter {
|
|
|
1615
1604
|
}
|
|
1616
1605
|
}
|
|
1617
1606
|
|
|
1618
|
-
|
|
1607
|
+
async buildJsonTree() {
|
|
1619
1608
|
let that = this;
|
|
1620
|
-
|
|
1621
|
-
let
|
|
1622
|
-
|
|
1609
|
+
that.buildJsonInProgress = true;
|
|
1610
|
+
for (let i = 0; i < that.deviceList.length; i++) {
|
|
1611
|
+
try {
|
|
1612
|
+
let device = that.deviceList[i];
|
|
1613
|
+
await that.buildJsonObject(device);
|
|
1614
|
+
} catch (e) {
|
|
1615
|
+
that.logOut("buildJsonTree error: ", e);
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1623
1618
|
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
if (typeof pointList !== "undefined" && pointList.length > 0) {
|
|
1627
|
-
pointList.forEach(function (point, pointListIndex) {
|
|
1628
|
-
requestMutex.acquire().then(function (release) {
|
|
1629
|
-
if (device.getIsInitialQuery()) {
|
|
1630
|
-
that
|
|
1631
|
-
._readObjectLite(device, address, point.value.type, point.value.instance)
|
|
1632
|
-
.then(function (result) {
|
|
1633
|
-
if (!result.error) {
|
|
1634
|
-
if (result.length > 0 && Array.isArray(result)) {
|
|
1635
|
-
promiseArray = result;
|
|
1636
|
-
} else {
|
|
1637
|
-
promiseArray.push(result);
|
|
1638
|
-
}
|
|
1639
|
-
}
|
|
1619
|
+
that.buildJsonInProgress = false;
|
|
1620
|
+
}
|
|
1640
1621
|
|
|
1641
|
-
release();
|
|
1642
|
-
|
|
1643
|
-
if (pointListIndex == pointList.length - 1) {
|
|
1644
|
-
device.setIsInitialQuery(false);
|
|
1645
|
-
that
|
|
1646
|
-
.buildResponse(promiseArray, device)
|
|
1647
|
-
.then(function () {
|
|
1648
|
-
that.lastNetworkPoll = Date.now();
|
|
1649
|
-
resolve({ deviceList: that.deviceList, pointList: that.networkTree });
|
|
1650
|
-
})
|
|
1651
|
-
.catch(function (e) {
|
|
1652
|
-
that.logOut("Error while building json object: ", e);
|
|
1653
|
-
reject(e);
|
|
1654
|
-
});
|
|
1655
|
-
}
|
|
1656
|
-
})
|
|
1657
|
-
.catch(function (e) {
|
|
1658
|
-
release();
|
|
1659
|
-
that.logOut("_readObjectLite error: ", e);
|
|
1660
|
-
|
|
1661
|
-
if (pointListIndex == pointList.length - 1) {
|
|
1662
|
-
device.setIsInitialQuery(false);
|
|
1663
|
-
that
|
|
1664
|
-
.buildResponse(promiseArray, device)
|
|
1665
|
-
.then(function () {
|
|
1666
|
-
that.lastNetworkPoll = Date.now();
|
|
1667
|
-
resolve({ deviceList: that.deviceList, pointList: that.networkTree });
|
|
1668
|
-
})
|
|
1669
|
-
.catch(function (e) {
|
|
1670
|
-
that.logOut("Error while building json object: ", e);
|
|
1671
|
-
reject(e);
|
|
1672
|
-
});
|
|
1673
|
-
}
|
|
1674
|
-
});
|
|
1675
|
-
} else {
|
|
1676
|
-
that
|
|
1677
|
-
._readObjectFull(device, address, point.value.type, point.value.instance)
|
|
1678
|
-
.then(function (result) {
|
|
1679
|
-
if (!result.error) {
|
|
1680
|
-
if (result.length > 0 && Array.isArray(result)) {
|
|
1681
|
-
promiseArray = result;
|
|
1682
|
-
} else {
|
|
1683
|
-
promiseArray.push(result);
|
|
1684
|
-
}
|
|
1685
|
-
}
|
|
1686
1622
|
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
.catch(function (e) {
|
|
1697
|
-
that.logOut("Error while building json object: ", e);
|
|
1698
|
-
reject(e);
|
|
1699
|
-
});
|
|
1700
|
-
}
|
|
1701
|
-
})
|
|
1702
|
-
.catch(function (e) {
|
|
1703
|
-
release();
|
|
1704
|
-
that.logOut("_readObjectFull error: ", e);
|
|
1705
|
-
|
|
1706
|
-
if (pointListIndex == pointList.length - 1) {
|
|
1707
|
-
that
|
|
1708
|
-
.buildResponse(promiseArray, device)
|
|
1709
|
-
.then(function () {
|
|
1710
|
-
that.lastNetworkPoll = Date.now();
|
|
1711
|
-
resolve({ deviceList: that.deviceList, pointList: that.networkTree });
|
|
1712
|
-
})
|
|
1713
|
-
.catch(function (e) {
|
|
1714
|
-
that.logOut("Error while building json object: ", e);
|
|
1715
|
-
reject(e);
|
|
1716
|
-
});
|
|
1717
|
-
}
|
|
1718
|
-
});
|
|
1719
|
-
}
|
|
1720
|
-
});
|
|
1721
|
-
});
|
|
1722
|
-
} else {
|
|
1723
|
-
reject("Unable to build network tree, empty point list");
|
|
1623
|
+
async buildJsonObject(device) {
|
|
1624
|
+
try {
|
|
1625
|
+
const address = device.address;
|
|
1626
|
+
const pointList = device.getPointsList();
|
|
1627
|
+
const requestMutex = new Mutex();
|
|
1628
|
+
const promiseArray = [];
|
|
1629
|
+
|
|
1630
|
+
if (typeof pointList === "undefined" || pointList.length === 0) {
|
|
1631
|
+
throw new Error("Unable to build network tree, empty point list");
|
|
1724
1632
|
}
|
|
1725
|
-
|
|
1633
|
+
|
|
1634
|
+
for (const point of pointList) {
|
|
1635
|
+
await requestMutex.acquire();
|
|
1636
|
+
|
|
1637
|
+
let result;
|
|
1638
|
+
if (device.getIsInitialQuery()) {
|
|
1639
|
+
result = await this._readObjectLite(device, address, point.value.type, point.value.instance);
|
|
1640
|
+
} else {
|
|
1641
|
+
result = await this._readObjectFull(device, address, point.value.type, point.value.instance);
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
if (!result.error) {
|
|
1645
|
+
device.setLastSeen(Date.now());
|
|
1646
|
+
if (result.length > 0 && Array.isArray(result)) {
|
|
1647
|
+
promiseArray.push(...result);
|
|
1648
|
+
} else {
|
|
1649
|
+
promiseArray.push(result);
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
requestMutex.release();
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
await this.buildResponse(promiseArray, device);
|
|
1657
|
+
this.lastNetworkPoll = Date.now();
|
|
1658
|
+
|
|
1659
|
+
return { deviceList: this.deviceList, pointList: this.networkTree };
|
|
1660
|
+
} catch (error) {
|
|
1661
|
+
this.logOut("Error while building json object: ", error);
|
|
1662
|
+
throw error;
|
|
1663
|
+
}
|
|
1726
1664
|
}
|
|
1727
1665
|
|
|
1728
1666
|
// Builds response object for a fully qualified
|
|
1729
1667
|
buildResponse(fullObjects, device) {
|
|
1730
1668
|
let that = this;
|
|
1731
|
-
const reg = /[
|
|
1669
|
+
const reg = /[$#\/\\+,]/gi;
|
|
1732
1670
|
return new Promise(function (resolve, reject) {
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
let
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
pointProperty
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
let currobjectId = pointProperty.objectId.type;
|
|
1748
|
-
let bac_obj = that.getObjectType(currobjectId);
|
|
1749
|
-
let objectName = that._findValueById(pointProperty.values, baEnum.PropertyIdentifier.OBJECT_NAME);
|
|
1750
|
-
let objectType = pointProperty.objectId.type;
|
|
1751
|
-
|
|
1752
|
-
let objectId;
|
|
1753
|
-
if (objectName !== null && typeof objectName == "string") {
|
|
1754
|
-
objectName = objectName.replace(reg, "");
|
|
1755
|
-
objectId = objectName + "_" + bac_obj + "_" + pointProperty.objectId.instance;
|
|
1671
|
+
try {
|
|
1672
|
+
let deviceKey =
|
|
1673
|
+
typeof device.getAddress() == "object"
|
|
1674
|
+
? device.getAddress().address + "-" + device.getDeviceId()
|
|
1675
|
+
: device.getAddress() + "-" + device.getDeviceId();
|
|
1676
|
+
let values = that.networkTree[deviceKey] ? that.networkTree[deviceKey] : {};
|
|
1677
|
+
for (let i = 0; i < fullObjects.length; i++) {
|
|
1678
|
+
let obj = fullObjects[i];
|
|
1679
|
+
let successfulResult = !obj.error ? obj.value : null;
|
|
1680
|
+
if (successfulResult) {
|
|
1681
|
+
successfulResult.values.forEach(function (pointProperty, pointPropertyIndex) {
|
|
1682
|
+
if (!pointProperty.objectId && successfulResult.objectId && !pointProperty.values && successfulResult.values) {
|
|
1683
|
+
pointProperty = successfulResult;
|
|
1684
|
+
}
|
|
1756
1685
|
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
if (values[objectId].stateTextArray && values[objectId].stateTextArray.length > 0) {
|
|
1686
|
+
let currobjectId = pointProperty.objectId.type;
|
|
1687
|
+
let bac_obj = that.getObjectType(currobjectId);
|
|
1688
|
+
let objectName = that._findValueById(pointProperty.values, baEnum.PropertyIdentifier.OBJECT_NAME);
|
|
1689
|
+
let objectType = pointProperty.objectId.type;
|
|
1690
|
+
|
|
1691
|
+
let objectId;
|
|
1692
|
+
if (objectName !== null && typeof objectName == "string") {
|
|
1693
|
+
objectName = objectName.replace(reg, "");
|
|
1694
|
+
objectId = objectName + "_" + bac_obj + "_" + pointProperty.objectId.instance;
|
|
1695
|
+
|
|
1696
|
+
try {
|
|
1697
|
+
pointProperty.values.forEach(function (object, objectIndex) {
|
|
1698
|
+
//checks for error code json structure, returned for invalid bacnet requests
|
|
1699
|
+
if (object && object.value && !object.value.errorClass) {
|
|
1700
|
+
if (!values[objectId]) values[objectId] = {};
|
|
1701
|
+
values[objectId].meta = {
|
|
1702
|
+
objectId: pointProperty.objectId,
|
|
1703
|
+
};
|
|
1704
|
+
|
|
1705
|
+
switch (object.id) {
|
|
1706
|
+
case baEnum.PropertyIdentifier.PRESENT_VALUE:
|
|
1707
|
+
if (object.value[0] && object.value[0].value !== "undefined" && object.value[0].value !== null) {
|
|
1708
|
+
//check for binary object type
|
|
1709
|
+
if (objectType == 3 || objectType == 4 || objectType == 5) {
|
|
1782
1710
|
if (object.value[0].value == 0) {
|
|
1783
|
-
values[objectId].presentValue =
|
|
1784
|
-
} else if (object.value[0].value
|
|
1785
|
-
values[objectId].presentValue =
|
|
1786
|
-
|
|
1711
|
+
values[objectId].presentValue = false;
|
|
1712
|
+
} else if (object.value[0].value == 1) {
|
|
1713
|
+
values[objectId].presentValue = true;
|
|
1714
|
+
}
|
|
1715
|
+
} else if (objectType == 40) {
|
|
1716
|
+
//character string
|
|
1717
|
+
values[objectId].presentValue = object.value[0].value;
|
|
1718
|
+
} else if (objectType == 13 || objectType == 14 || objectType == 19) {
|
|
1719
|
+
//check for MSV MSI MSO - for enum state text
|
|
1720
|
+
if (values[objectId].stateTextArray && values[objectId].stateTextArray.length > 0) {
|
|
1721
|
+
if (object.value[0].value == 0) {
|
|
1722
|
+
values[objectId].presentValue = values[objectId].stateTextArray[object.value[0].value].value;
|
|
1723
|
+
} else if (object.value[0].value !== 0) {
|
|
1724
|
+
values[objectId].presentValue =
|
|
1725
|
+
values[objectId].stateTextArray[object.value[0].value - 1].value;
|
|
1726
|
+
}
|
|
1787
1727
|
}
|
|
1728
|
+
} else if (objectType !== 8) {
|
|
1729
|
+
values[objectId].presentValue = roundDecimalPlaces(object.value[0].value, 2);
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1732
|
+
values[objectId].meta.arrayIndex = object.index;
|
|
1733
|
+
break;
|
|
1734
|
+
case baEnum.PropertyIdentifier.DESCRIPTION:
|
|
1735
|
+
if (object.value[0]) values[objectId].description = object.value[0].value;
|
|
1736
|
+
break;
|
|
1737
|
+
case baEnum.PropertyIdentifier.UNITS:
|
|
1738
|
+
if (object.value[0] && object.value[0].value) values[objectId].units = getUnit(object.value[0].value);
|
|
1739
|
+
break;
|
|
1740
|
+
case baEnum.PropertyIdentifier.OBJECT_NAME:
|
|
1741
|
+
if (object.value[0] && object.value[0].value) {
|
|
1742
|
+
values[objectId].objectName = object.value[0].value.replace(reg, "");
|
|
1743
|
+
if (!values[objectId].displayName) {
|
|
1744
|
+
values[objectId].displayName = object.value[0].value.replace(reg, "");
|
|
1788
1745
|
}
|
|
1789
|
-
} else if (objectType !== 8) {
|
|
1790
|
-
values[objectId].presentValue = roundDecimalPlaces(object.value[0].value, 2);
|
|
1791
1746
|
}
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
case baEnum.PropertyIdentifier.OBJECT_NAME:
|
|
1803
|
-
if (object.value[0] && object.value[0].value) {
|
|
1804
|
-
values[objectId].objectName = object.value[0].value.replace(reg, "");
|
|
1805
|
-
if (!values[objectId].displayName) {
|
|
1806
|
-
values[objectId].displayName = object.value[0].value.replace(reg, "");
|
|
1747
|
+
break;
|
|
1748
|
+
case baEnum.PropertyIdentifier.OBJECT_TYPE:
|
|
1749
|
+
if (object.value[0] && object.value[0].value) values[objectId].objectType = object.value[0].value;
|
|
1750
|
+
break;
|
|
1751
|
+
case baEnum.PropertyIdentifier.PROPERTY_LIST:
|
|
1752
|
+
if (object.value) values[objectId].propertyList = that.mapPropsToArray(object.value);
|
|
1753
|
+
break;
|
|
1754
|
+
case baEnum.PropertyIdentifier.SYSTEM_STATUS:
|
|
1755
|
+
if (object.value[0]) {
|
|
1756
|
+
values[objectId].systemStatus = that.getPROP_SYSTEM_STATUS(object.value[0].value);
|
|
1807
1757
|
}
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
values[objectId].stateTextArray = object.value;
|
|
1844
|
-
if (
|
|
1845
|
-
typeof values[objectId].presentValue == "number" &&
|
|
1846
|
-
values[objectId].presentValue !== null &&
|
|
1847
|
-
values[objectId].presentValue !== undefined
|
|
1848
|
-
) {
|
|
1849
|
-
const tempIndex = values[objectId].presentValue;
|
|
1850
|
-
if (tempIndex == 0) {
|
|
1851
|
-
values[objectId].presentValue = values[objectId].stateTextArray[tempIndex].value;
|
|
1852
|
-
} else if (tempIndex !== 0) {
|
|
1853
|
-
values[objectId].presentValue = values[objectId].stateTextArray[tempIndex - 1].value;
|
|
1758
|
+
break;
|
|
1759
|
+
case baEnum.PropertyIdentifier.MODIFICATION_DATE:
|
|
1760
|
+
if (object.value[0]) {
|
|
1761
|
+
values[objectId].modificationDate = object.value[0].value;
|
|
1762
|
+
}
|
|
1763
|
+
break;
|
|
1764
|
+
case baEnum.PropertyIdentifier.PROGRAM_STATE:
|
|
1765
|
+
if (object.value[0]) {
|
|
1766
|
+
values[objectId].programState = that.getPROP_PROGRAM_STATE(object.value[0].value);
|
|
1767
|
+
}
|
|
1768
|
+
break;
|
|
1769
|
+
case baEnum.PropertyIdentifier.RECORD_COUNT:
|
|
1770
|
+
if (object.value[0]) {
|
|
1771
|
+
values[objectId].recordCount = object.value[0].value;
|
|
1772
|
+
}
|
|
1773
|
+
break;
|
|
1774
|
+
case baEnum.PropertyIdentifier.PRIORITY_ARRAY:
|
|
1775
|
+
if (object.value.length > 0) {
|
|
1776
|
+
values[objectId].hasPriorityArray = true;
|
|
1777
|
+
}
|
|
1778
|
+
break;
|
|
1779
|
+
case baEnum.PropertyIdentifier.STATE_TEXT:
|
|
1780
|
+
if (object.value) {
|
|
1781
|
+
values[objectId].stateTextArray = object.value;
|
|
1782
|
+
if (
|
|
1783
|
+
typeof values[objectId].presentValue == "number" &&
|
|
1784
|
+
values[objectId].presentValue !== null &&
|
|
1785
|
+
values[objectId].presentValue !== undefined
|
|
1786
|
+
) {
|
|
1787
|
+
const tempIndex = values[objectId].presentValue;
|
|
1788
|
+
if (tempIndex == 0) {
|
|
1789
|
+
values[objectId].presentValue = values[objectId].stateTextArray[tempIndex].value;
|
|
1790
|
+
} else if (tempIndex !== 0) {
|
|
1791
|
+
values[objectId].presentValue = values[objectId].stateTextArray[tempIndex - 1].value;
|
|
1792
|
+
}
|
|
1854
1793
|
}
|
|
1855
1794
|
}
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1795
|
+
break;
|
|
1796
|
+
case baEnum.PropertyIdentifier.VENDOR_NAME:
|
|
1797
|
+
if (object.value) {
|
|
1798
|
+
if (object.value[0].value && typeof object.value[0].value == "string") {
|
|
1799
|
+
values[objectId].vendorName = object.value[0].value;
|
|
1800
|
+
}
|
|
1862
1801
|
}
|
|
1863
|
-
|
|
1864
|
-
|
|
1802
|
+
break;
|
|
1803
|
+
}
|
|
1865
1804
|
}
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
}
|
|
1875
|
-
})
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1805
|
+
if (
|
|
1806
|
+
pointPropertyIndex == successfulResult.values.length - 1 &&
|
|
1807
|
+
objectIndex == pointProperty.values.length - 1 &&
|
|
1808
|
+
i == fullObjects.length - 1
|
|
1809
|
+
) {
|
|
1810
|
+
that.networkTree[deviceKey] = values;
|
|
1811
|
+
resolve(that.networkTree);
|
|
1812
|
+
}
|
|
1813
|
+
});
|
|
1814
|
+
} catch (e) {
|
|
1815
|
+
that.logOut("issue resolving bacnet payload, see error: ", e);
|
|
1816
|
+
reject(e);
|
|
1817
|
+
}
|
|
1879
1818
|
}
|
|
1819
|
+
});
|
|
1820
|
+
} else {
|
|
1821
|
+
//error found in point property
|
|
1822
|
+
if (i == fullObjects.length - 1) {
|
|
1823
|
+
that.networkTree[deviceKey] = values;
|
|
1824
|
+
resolve(that.networkTree);
|
|
1880
1825
|
}
|
|
1881
|
-
});
|
|
1882
|
-
} else {
|
|
1883
|
-
//error found in point property
|
|
1884
|
-
if (i == fullObjects.length - 1) {
|
|
1885
|
-
that.networkTree[deviceKey] = values;
|
|
1886
|
-
resolve(that.networkTree);
|
|
1887
1826
|
}
|
|
1888
1827
|
}
|
|
1828
|
+
that.networkTree[deviceKey] = values;
|
|
1829
|
+
resolve(that.networkTree);
|
|
1830
|
+
} catch (e) {
|
|
1831
|
+
reject(e);
|
|
1889
1832
|
}
|
|
1890
|
-
that.networkTree[deviceKey] = values;
|
|
1891
|
-
resolve(that.networkTree);
|
|
1892
1833
|
});
|
|
1893
1834
|
}
|
|
1894
1835
|
|
package/bacnet_device.js
CHANGED
|
@@ -35,6 +35,7 @@ class BacnetDevice {
|
|
|
35
35
|
that.protocolServicesSupported = config.protocolServicesSupported;
|
|
36
36
|
that.isProtocolServicesSet = config.isProtocolServicesSet;
|
|
37
37
|
that.isInitialQuery = config.isInitialQuery;
|
|
38
|
+
that.isDumbMstpRouter = config.isDumbMstpRouter;
|
|
38
39
|
|
|
39
40
|
} else if (fromImport == false) {
|
|
40
41
|
if (config.net && config.adr) {
|
|
@@ -64,9 +65,18 @@ class BacnetDevice {
|
|
|
64
65
|
that.protocolServicesSupported = [];
|
|
65
66
|
that.isProtocolServicesSet = false;
|
|
66
67
|
that.isInitialQuery = true;
|
|
68
|
+
that.isDumbMstpRouter = false;
|
|
67
69
|
}
|
|
68
70
|
}
|
|
69
71
|
|
|
72
|
+
setIsDumbMstpRouter(isDumbMstp) {
|
|
73
|
+
this.isDumbMstpRouter = isDumbMstp;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
getIsDumbMstpRouter() {
|
|
77
|
+
return this.isDumbMstpRouter;
|
|
78
|
+
}
|
|
79
|
+
|
|
70
80
|
setDisplayName(displayName) {
|
|
71
81
|
this.displayName = displayName;
|
|
72
82
|
}
|
package/bacnet_read.html
CHANGED
|
@@ -59,6 +59,7 @@
|
|
|
59
59
|
showPointNameDialog: ref(false),
|
|
60
60
|
deviceDisplayNameValue: ref(),
|
|
61
61
|
pointDisplayNameValue: ref(),
|
|
62
|
+
calculateProgressBarWidth: ref(),
|
|
62
63
|
};
|
|
63
64
|
},
|
|
64
65
|
setup() {
|
|
@@ -130,6 +131,7 @@
|
|
|
130
131
|
app.pointList = result.pointList;
|
|
131
132
|
app.pollFrequency = parseInt(result.pollFrequency);
|
|
132
133
|
app.deviceCount = result.deviceList.length;
|
|
134
|
+
|
|
133
135
|
//progress bar percentage
|
|
134
136
|
let progressVal = parseInt((result.renderListCount / result.deviceList.length) * 100);
|
|
135
137
|
if (typeof progressVal == "number" && !isNaN(progressVal)) {
|
|
@@ -138,6 +140,15 @@
|
|
|
138
140
|
app.progressBarValue = 0;
|
|
139
141
|
}
|
|
140
142
|
|
|
143
|
+
//set progress bar width based on device count integer digit length
|
|
144
|
+
if (app.deviceCount.toString().length >= 5) {
|
|
145
|
+
app.calculateProgressBarWidth = "width: 200px;";
|
|
146
|
+
} else if (app.deviceCount.toString().length == 4) {
|
|
147
|
+
app.calculateProgressBarWidth = "width: 210px;";
|
|
148
|
+
} else if (app.deviceCount.toString().length < 4) {
|
|
149
|
+
app.calculateProgressBarWidth = "width: 220px;";
|
|
150
|
+
}
|
|
151
|
+
|
|
141
152
|
app.$forceUpdate();
|
|
142
153
|
});
|
|
143
154
|
|
|
@@ -434,6 +445,16 @@
|
|
|
434
445
|
link.click();
|
|
435
446
|
}
|
|
436
447
|
}
|
|
448
|
+
} else {
|
|
449
|
+
if (parseInt(key) == keys.length - 1) {
|
|
450
|
+
// last iteration
|
|
451
|
+
var encodedUri = encodeURI(csvContent);
|
|
452
|
+
var link = document.createElement("a");
|
|
453
|
+
link.setAttribute("href", encodedUri);
|
|
454
|
+
link.setAttribute("download", "pointslist.csv");
|
|
455
|
+
document.body.appendChild(link);
|
|
456
|
+
link.click();
|
|
457
|
+
}
|
|
437
458
|
}
|
|
438
459
|
}
|
|
439
460
|
},
|
|
@@ -451,6 +472,8 @@
|
|
|
451
472
|
let app = this;
|
|
452
473
|
if ((Date.now() - slotProps.node.lastSeen) / 1000 < app.pollFrequency + 5) {
|
|
453
474
|
return true;
|
|
475
|
+
} else if (slotProps.node.isDumbMstpRouter == true || slotProps.node.isDumbMstpRouter == "true") {
|
|
476
|
+
return true;
|
|
454
477
|
}
|
|
455
478
|
return false;
|
|
456
479
|
},
|
|
@@ -530,7 +553,7 @@
|
|
|
530
553
|
app.pointDisplayNameValue = app.rightClickedPoint.node.label;
|
|
531
554
|
break;
|
|
532
555
|
case "updatePoint":
|
|
533
|
-
app.updatePoint(app.
|
|
556
|
+
app.updatePoint(app.rightClickedPoint);
|
|
534
557
|
break;
|
|
535
558
|
default:
|
|
536
559
|
break;
|
|
@@ -1061,7 +1084,7 @@
|
|
|
1061
1084
|
*
|
|
1062
1085
|
*
|
|
1063
1086
|
-->
|
|
1064
|
-
<div id="read-networkTree-tab" style="display:none">
|
|
1087
|
+
<div id="read-networkTree-tab" style="display:none" class="bp-networktree-tab">
|
|
1065
1088
|
<div id="read-networkTree-tab-content" class="networkTreeContent" style="display:none">
|
|
1066
1089
|
<p-dialog
|
|
1067
1090
|
v-model:visible="showDeviceNameDialog"
|
|
@@ -1107,8 +1130,8 @@
|
|
|
1107
1130
|
|
|
1108
1131
|
<div class="headerDiv">
|
|
1109
1132
|
<a class="countStatus" style="margin-left: 15px;"> <span class="bp-deviceCount">{{deviceCount}}</span> Device(s)</a>
|
|
1110
|
-
<p-progressbar :value="progressBarValue" :show-value="true" style="
|
|
1111
|
-
<div class="buttonGroup"
|
|
1133
|
+
<p-progressbar class="bp-readNode-progressbar" :value="progressBarValue" :show-value="true" :style="calculateProgressBarWidth"></p-progressbar>
|
|
1134
|
+
<div class="buttonGroup">
|
|
1112
1135
|
<button @click="rebuildDataModel()" class="rebuildDataButton" title="Rebuild Data Model">
|
|
1113
1136
|
<i class="pi pi-wrench" style="color: #969696;"></i>
|
|
1114
1137
|
</button>
|
|
@@ -1136,7 +1159,7 @@
|
|
|
1136
1159
|
<div v-if="isDeviceActive(slotProps)" class="deviceLabelParent">
|
|
1137
1160
|
<b class="deviceLabel">
|
|
1138
1161
|
<span class="statusOnline deviceStatus dotOnline dot"></span>
|
|
1139
|
-
{{slotProps.node.label}}
|
|
1162
|
+
<span class="deviceLabelSpan">{{slotProps.node.label}}</span>
|
|
1140
1163
|
<span v-if="hasMstpDevices(slotProps)" class="mstpDeviceCount"
|
|
1141
1164
|
> {{calculateMstpCount(slotProps)}} </span
|
|
1142
1165
|
>
|
|
@@ -1146,7 +1169,7 @@
|
|
|
1146
1169
|
<div v-else>
|
|
1147
1170
|
<b class="deviceLabel">
|
|
1148
1171
|
<span class="statusOffline deviceStatus dotOffline dot"></span>
|
|
1149
|
-
{{slotProps.node.label}}
|
|
1172
|
+
<span class="deviceLabelSpan">{{slotProps.node.label}}</span>
|
|
1150
1173
|
<span v-if="hasMstpDevices(slotProps)" class="mstpDeviceCount"
|
|
1151
1174
|
> {{calculateMstpCount(slotProps)}} </span
|
|
1152
1175
|
>
|
package/package.json
CHANGED
|
@@ -41,6 +41,7 @@ class Client extends events_1.EventEmitter {
|
|
|
41
41
|
this._lastSequenceNumber = 0;
|
|
42
42
|
this._segmentStore = [];
|
|
43
43
|
options = options || {};
|
|
44
|
+
|
|
44
45
|
this._settings = {
|
|
45
46
|
port: options.port || 47808,
|
|
46
47
|
interface: options.interface,
|
|
@@ -48,11 +49,13 @@ class Client extends events_1.EventEmitter {
|
|
|
48
49
|
broadcastAddress: options.broadcastAddress || '255.255.255.255',
|
|
49
50
|
apduTimeout: options.apduTimeout || 3000
|
|
50
51
|
};
|
|
52
|
+
|
|
51
53
|
this._transport = this._settings.transport || new transport_1.Transport({
|
|
52
54
|
port: this._settings.port,
|
|
53
55
|
interface: this._settings.interface,
|
|
54
56
|
broadcastAddress: this._settings.broadcastAddress
|
|
55
57
|
});
|
|
58
|
+
|
|
56
59
|
// Setup code
|
|
57
60
|
this._transport.on('message', this._receiveData.bind(this));
|
|
58
61
|
this._transport.on('error', this._receiveError.bind(this));
|
|
@@ -7,11 +7,16 @@ const DEFAULT_BACNET_PORT = 47808;
|
|
|
7
7
|
class Transport extends events_1.EventEmitter {
|
|
8
8
|
constructor(settings) {
|
|
9
9
|
super();
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
try {
|
|
11
|
+
this._lastSendMessages = {};
|
|
12
|
+
this._settings = settings;
|
|
13
|
+
this._server = (0, dgram_1.createSocket)({ type: 'udp4', reuseAddr: true });
|
|
14
|
+
this._server.on('message', (msg, rinfo) => this.emit('message', msg, rinfo.address));
|
|
15
|
+
this._server.on('error', (err) => this.emit('message', err));
|
|
16
|
+
|
|
17
|
+
} catch (e) {
|
|
18
|
+
//console.log("Transport constructor error: ", e);
|
|
19
|
+
}
|
|
15
20
|
}
|
|
16
21
|
getBroadcastAddress() {
|
|
17
22
|
return this._settings.broadcastAddress;
|
|
@@ -33,9 +38,9 @@ class Transport extends events_1.EventEmitter {
|
|
|
33
38
|
delete this._lastSendMessages[messageKey];
|
|
34
39
|
}, 10000); // delete after 10s, hopefully all cases are handled by that
|
|
35
40
|
}
|
|
36
|
-
const [address, port] = receiver.split(':');
|
|
37
|
-
this._server.send(buffer, 0, offset, port || DEFAULT_BACNET_PORT, address);
|
|
38
41
|
|
|
42
|
+
const [address, port] = receiver.split(':');
|
|
43
|
+
this._server.send(buffer, 0, offset, this._settings.port || DEFAULT_BACNET_PORT, address);
|
|
39
44
|
}
|
|
40
45
|
open() {
|
|
41
46
|
this._server.bind(this._settings.port, this._settings.interface, () => {
|
package/resources/style.css
CHANGED
|
@@ -46,10 +46,17 @@
|
|
|
46
46
|
}
|
|
47
47
|
.pointLabel {
|
|
48
48
|
font-weight: 400;
|
|
49
|
+
text-wrap: pretty;
|
|
50
|
+
display: inline-block;
|
|
51
|
+
max-width: 85%;
|
|
49
52
|
}
|
|
50
53
|
.deviceLabel {
|
|
51
54
|
color: #1F2731;
|
|
52
55
|
font-weight: 400;
|
|
56
|
+
text-wrap: pretty;
|
|
57
|
+
}
|
|
58
|
+
.deviceLabelSpan {
|
|
59
|
+
vertical-align: bottom;
|
|
53
60
|
}
|
|
54
61
|
.removeAllButton {
|
|
55
62
|
float: right;
|
|
@@ -248,6 +255,7 @@
|
|
|
248
255
|
flex-wrap: nowrap;
|
|
249
256
|
justify-content: space-between;
|
|
250
257
|
height: 20px;
|
|
258
|
+
min-width: max-content;
|
|
251
259
|
}
|
|
252
260
|
.msgTypeDiv {
|
|
253
261
|
display: flex;
|
|
@@ -262,8 +270,11 @@
|
|
|
262
270
|
white-space: nowrap;
|
|
263
271
|
text-overflow: ellipsis;
|
|
264
272
|
}
|
|
265
|
-
.
|
|
266
|
-
|
|
273
|
+
.bp-readNode-progressbar {
|
|
274
|
+
height: 20px !important;
|
|
275
|
+
}
|
|
276
|
+
.bp-networktree-tab {
|
|
277
|
+
/* min-width: 590px; */
|
|
267
278
|
}
|
|
268
279
|
#read-readList-tab {
|
|
269
280
|
display: flex;
|
|
@@ -296,6 +307,7 @@
|
|
|
296
307
|
border-radius: 50%;
|
|
297
308
|
display: inline-block;
|
|
298
309
|
margin-right: 5px;
|
|
310
|
+
vertical-align: middle;
|
|
299
311
|
}
|
|
300
312
|
.dotOnline {
|
|
301
313
|
background-color: #63B686;
|
|
@@ -405,7 +417,7 @@
|
|
|
405
417
|
font-size: 1rem !important;
|
|
406
418
|
}
|
|
407
419
|
.p-dialog-content {
|
|
408
|
-
padding: 0.5em 0.75rem 0.5rem 0.75rem;
|
|
420
|
+
padding: 0.5em 0.75rem 0.5rem 0.75rem !important;
|
|
409
421
|
|
|
410
422
|
& label {
|
|
411
423
|
font-size: 0.9rem !important;
|
|
@@ -500,14 +512,11 @@
|
|
|
500
512
|
input[type=checkbox]:focus {
|
|
501
513
|
outline: none;
|
|
502
514
|
}
|
|
503
|
-
|
|
504
515
|
}
|
|
505
|
-
|
|
506
516
|
.bp-checkbox {
|
|
507
517
|
accent-color: white;
|
|
508
518
|
bottom: -3px;
|
|
509
519
|
}
|
|
510
|
-
|
|
511
520
|
.bp-import-buttons {
|
|
512
521
|
margin-top: 25px;
|
|
513
522
|
label {
|
|
@@ -527,10 +536,10 @@
|
|
|
527
536
|
}
|
|
528
537
|
}
|
|
529
538
|
|
|
530
|
-
.red-ui-tab {
|
|
539
|
+
/* .red-ui-tab {
|
|
531
540
|
border-top-left-radius: 5px;
|
|
532
541
|
border-top-right-radius: 5px;
|
|
533
|
-
}
|
|
542
|
+
} */
|
|
534
543
|
|
|
535
544
|
.node-input-deviceIdRangeMatrix-container-row {
|
|
536
545
|
border-radius: 10px;
|
package/treeBuilder.js
CHANGED
|
@@ -63,11 +63,45 @@ class treeBuilder {
|
|
|
63
63
|
// Check if the device object exists and the device name is valid
|
|
64
64
|
if (deviceObject) {
|
|
65
65
|
await this.processDevicePoints(device, deviceObject, deviceName, ipAddress, deviceId, index);
|
|
66
|
+
|
|
67
|
+
//delete dummy object if all conditions satisfied
|
|
68
|
+
if (deviceName !== null) {
|
|
69
|
+
if (deviceId !== null) {
|
|
70
|
+
let lastIndex = deviceName.lastIndexOf(deviceId);
|
|
71
|
+
if (lastIndex) {
|
|
72
|
+
let formattedName = deviceName.substring(0, lastIndex);
|
|
73
|
+
formattedName = `${formattedName.trim()}_Device_${deviceId}`;
|
|
74
|
+
if (
|
|
75
|
+
this.networkTree[deviceKey][formattedName] &&
|
|
76
|
+
Object.keys(this.networkTree[deviceKey][formattedName]).length > 0 &&
|
|
77
|
+
this.networkTree[deviceKey]["device"]
|
|
78
|
+
) {
|
|
79
|
+
delete this.networkTree[deviceKey]["device"];
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
66
85
|
} else {
|
|
67
|
-
//
|
|
86
|
+
//invalid ip object, likely dumb mstp router
|
|
87
|
+
if (device.getIsDumbMstpRouter()) {
|
|
88
|
+
//update dumb mstp router name
|
|
89
|
+
await this.updateDumbMstpRouterName(deviceName, ipAddress, deviceId);
|
|
90
|
+
}
|
|
68
91
|
}
|
|
69
92
|
}
|
|
70
93
|
|
|
94
|
+
async updateDumbMstpRouterName(deviceName, ipAddress, deviceId) {
|
|
95
|
+
return new Promise((resolve, reject) => {
|
|
96
|
+
let listDeviceIndex = this.renderList.findIndex(item => item.deviceId == deviceId && item.ipAddr == ipAddress && item.isDumbMstpRouter == true);
|
|
97
|
+
if (listDeviceIndex !== -1) {
|
|
98
|
+
this.renderList[listDeviceIndex].label = deviceName;
|
|
99
|
+
this.renderList[listDeviceIndex].data = deviceName;
|
|
100
|
+
}
|
|
101
|
+
resolve();
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
71
105
|
/**
|
|
72
106
|
* Add the root device folder to the render list.
|
|
73
107
|
*
|
|
@@ -88,8 +122,9 @@ class treeBuilder {
|
|
|
88
122
|
|
|
89
123
|
// Check if the device already exists in the renderList
|
|
90
124
|
const existingDeviceIndex = this.renderList.findIndex(item => item.deviceId === deviceId && item.ipAddr === ipAddress);
|
|
91
|
-
|
|
92
125
|
if (existingDeviceIndex === -1) { // Device not found, add new entry
|
|
126
|
+
let isDumbMstpRouter = false;
|
|
127
|
+
if (device.getIsDumbMstpRouter() && deviceId == null) isDumbMstpRouter = true;
|
|
93
128
|
const rootFolder = {
|
|
94
129
|
key: index,
|
|
95
130
|
label: displayName,
|
|
@@ -103,6 +138,7 @@ class treeBuilder {
|
|
|
103
138
|
deviceId,
|
|
104
139
|
isMstpDevice: device.getIsMstpDevice(),
|
|
105
140
|
initialName: device.getDeviceName(),
|
|
141
|
+
isDumbMstpRouter: isDumbMstpRouter,
|
|
106
142
|
};
|
|
107
143
|
|
|
108
144
|
// Add the root folder to the render list
|
|
@@ -112,10 +148,10 @@ class treeBuilder {
|
|
|
112
148
|
}
|
|
113
149
|
|
|
114
150
|
addEmptyIpRootDevice(childDevice) {
|
|
115
|
-
|
|
116
151
|
const ipAddress = this.getDeviceIpAddress(childDevice);
|
|
117
152
|
|
|
118
|
-
let deviceIndex = this.deviceList.findIndex(ele => ele.address === ipAddress && ele.deviceId === null && ele.deviceName === ipAddress && ele.displayName === ipAddress);
|
|
153
|
+
//let deviceIndex = this.deviceList.findIndex(ele => ele.address === ipAddress && ele.deviceId === null && ele.deviceName === ipAddress && ele.displayName === ipAddress);
|
|
154
|
+
let deviceIndex = this.deviceList.findIndex(ele => ele.address === ipAddress && ele.deviceId === null);
|
|
119
155
|
|
|
120
156
|
if (deviceIndex === -1) {
|
|
121
157
|
let newDevice = {
|
|
@@ -140,6 +176,7 @@ class treeBuilder {
|
|
|
140
176
|
protocolServicesSupported: [],
|
|
141
177
|
isProtocolServicesSet: false,
|
|
142
178
|
isInitialQuery: true,
|
|
179
|
+
isDumbMstpRouter: true,
|
|
143
180
|
};
|
|
144
181
|
|
|
145
182
|
let newBacnetDevice = new BacnetDevice(true, newDevice);
|
|
@@ -287,7 +324,6 @@ class treeBuilder {
|
|
|
287
324
|
updateRenderList(children, device, deviceName, index, ipAddress, deviceId) {
|
|
288
325
|
// Create the folder structure for the device
|
|
289
326
|
const folderJson = this.createFolderJson(children, device.hasChildDevices(), deviceId);
|
|
290
|
-
|
|
291
327
|
if (!this.renderList) {
|
|
292
328
|
this.renderList = [];
|
|
293
329
|
}
|