@bitpoolos/edge-bacnet 1.4.5 → 1.4.7
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 +342 -400
- package/bacnet_device.js +10 -0
- package/bacnet_read.html +34 -9
- 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,233 @@ 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
|
+
device.setIsInitialQuery(false);
|
|
1641
|
+
} else {
|
|
1642
|
+
result = await this._readObjectFull(device, address, point.value.type, point.value.instance);
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
if (!result.error) {
|
|
1646
|
+
device.setLastSeen(Date.now());
|
|
1647
|
+
if (result.length > 0 && Array.isArray(result)) {
|
|
1648
|
+
promiseArray.push(...result);
|
|
1649
|
+
} else {
|
|
1650
|
+
promiseArray.push(result);
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
requestMutex.release();
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
await this.buildResponse(promiseArray, device);
|
|
1658
|
+
this.lastNetworkPoll = Date.now();
|
|
1659
|
+
|
|
1660
|
+
return { deviceList: this.deviceList, pointList: this.networkTree };
|
|
1661
|
+
} catch (error) {
|
|
1662
|
+
this.logOut("Error while building json object: ", error);
|
|
1663
|
+
throw error;
|
|
1664
|
+
}
|
|
1726
1665
|
}
|
|
1727
1666
|
|
|
1728
1667
|
// Builds response object for a fully qualified
|
|
1729
1668
|
buildResponse(fullObjects, device) {
|
|
1730
1669
|
let that = this;
|
|
1731
|
-
const reg = /[
|
|
1670
|
+
const reg = /[$#\/\\+,]/gi;
|
|
1732
1671
|
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;
|
|
1672
|
+
try {
|
|
1673
|
+
let deviceKey =
|
|
1674
|
+
typeof device.getAddress() == "object"
|
|
1675
|
+
? device.getAddress().address + "-" + device.getDeviceId()
|
|
1676
|
+
: device.getAddress() + "-" + device.getDeviceId();
|
|
1677
|
+
let values = that.networkTree[deviceKey] ? that.networkTree[deviceKey] : {};
|
|
1678
|
+
for (let i = 0; i < fullObjects.length; i++) {
|
|
1679
|
+
let obj = fullObjects[i];
|
|
1680
|
+
let successfulResult = !obj.error ? obj.value : null;
|
|
1681
|
+
if (successfulResult) {
|
|
1682
|
+
successfulResult.values.forEach(function (pointProperty, pointPropertyIndex) {
|
|
1683
|
+
if (!pointProperty.objectId && successfulResult.objectId && !pointProperty.values && successfulResult.values) {
|
|
1684
|
+
pointProperty = successfulResult;
|
|
1685
|
+
}
|
|
1756
1686
|
|
|
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) {
|
|
1687
|
+
let currobjectId = pointProperty.objectId.type;
|
|
1688
|
+
let bac_obj = that.getObjectType(currobjectId);
|
|
1689
|
+
let objectName = that._findValueById(pointProperty.values, baEnum.PropertyIdentifier.OBJECT_NAME);
|
|
1690
|
+
let objectType = pointProperty.objectId.type;
|
|
1691
|
+
|
|
1692
|
+
let objectId;
|
|
1693
|
+
if (objectName !== null && typeof objectName == "string") {
|
|
1694
|
+
objectName = objectName.replace(reg, "");
|
|
1695
|
+
objectId = objectName + "_" + bac_obj + "_" + pointProperty.objectId.instance;
|
|
1696
|
+
|
|
1697
|
+
try {
|
|
1698
|
+
pointProperty.values.forEach(function (object, objectIndex) {
|
|
1699
|
+
//checks for error code json structure, returned for invalid bacnet requests
|
|
1700
|
+
if (object && object.value && !object.value.errorClass) {
|
|
1701
|
+
if (!values[objectId]) values[objectId] = {};
|
|
1702
|
+
values[objectId].meta = {
|
|
1703
|
+
objectId: pointProperty.objectId,
|
|
1704
|
+
};
|
|
1705
|
+
|
|
1706
|
+
switch (object.id) {
|
|
1707
|
+
case baEnum.PropertyIdentifier.PRESENT_VALUE:
|
|
1708
|
+
if (object.value[0] && object.value[0].value !== "undefined" && object.value[0].value !== null) {
|
|
1709
|
+
//check for binary object type
|
|
1710
|
+
if (objectType == 3 || objectType == 4 || objectType == 5) {
|
|
1782
1711
|
if (object.value[0].value == 0) {
|
|
1783
|
-
values[objectId].presentValue =
|
|
1784
|
-
} else if (object.value[0].value
|
|
1785
|
-
values[objectId].presentValue =
|
|
1786
|
-
|
|
1712
|
+
values[objectId].presentValue = false;
|
|
1713
|
+
} else if (object.value[0].value == 1) {
|
|
1714
|
+
values[objectId].presentValue = true;
|
|
1715
|
+
}
|
|
1716
|
+
} else if (objectType == 40) {
|
|
1717
|
+
//character string
|
|
1718
|
+
values[objectId].presentValue = object.value[0].value;
|
|
1719
|
+
} else if (objectType == 13 || objectType == 14 || objectType == 19) {
|
|
1720
|
+
//check for MSV MSI MSO - for enum state text
|
|
1721
|
+
if (values[objectId].stateTextArray && values[objectId].stateTextArray.length > 0) {
|
|
1722
|
+
if (object.value[0].value == 0) {
|
|
1723
|
+
values[objectId].presentValue = values[objectId].stateTextArray[object.value[0].value].value;
|
|
1724
|
+
} else if (object.value[0].value !== 0) {
|
|
1725
|
+
values[objectId].presentValue =
|
|
1726
|
+
values[objectId].stateTextArray[object.value[0].value - 1].value;
|
|
1727
|
+
}
|
|
1787
1728
|
}
|
|
1729
|
+
} else if (objectType !== 8) {
|
|
1730
|
+
values[objectId].presentValue = roundDecimalPlaces(object.value[0].value, 2);
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1733
|
+
values[objectId].meta.arrayIndex = object.index;
|
|
1734
|
+
break;
|
|
1735
|
+
case baEnum.PropertyIdentifier.DESCRIPTION:
|
|
1736
|
+
if (object.value[0]) values[objectId].description = object.value[0].value;
|
|
1737
|
+
break;
|
|
1738
|
+
case baEnum.PropertyIdentifier.UNITS:
|
|
1739
|
+
if (object.value[0] && object.value[0].value) values[objectId].units = getUnit(object.value[0].value);
|
|
1740
|
+
break;
|
|
1741
|
+
case baEnum.PropertyIdentifier.OBJECT_NAME:
|
|
1742
|
+
if (object.value[0] && object.value[0].value) {
|
|
1743
|
+
values[objectId].objectName = object.value[0].value.replace(reg, "");
|
|
1744
|
+
if (!values[objectId].displayName) {
|
|
1745
|
+
values[objectId].displayName = object.value[0].value.replace(reg, "");
|
|
1788
1746
|
}
|
|
1789
|
-
} else if (objectType !== 8) {
|
|
1790
|
-
values[objectId].presentValue = roundDecimalPlaces(object.value[0].value, 2);
|
|
1791
1747
|
}
|
|
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, "");
|
|
1748
|
+
break;
|
|
1749
|
+
case baEnum.PropertyIdentifier.OBJECT_TYPE:
|
|
1750
|
+
if (object.value[0] && object.value[0].value) values[objectId].objectType = object.value[0].value;
|
|
1751
|
+
break;
|
|
1752
|
+
case baEnum.PropertyIdentifier.PROPERTY_LIST:
|
|
1753
|
+
if (object.value) values[objectId].propertyList = that.mapPropsToArray(object.value);
|
|
1754
|
+
break;
|
|
1755
|
+
case baEnum.PropertyIdentifier.SYSTEM_STATUS:
|
|
1756
|
+
if (object.value[0]) {
|
|
1757
|
+
values[objectId].systemStatus = that.getPROP_SYSTEM_STATUS(object.value[0].value);
|
|
1807
1758
|
}
|
|
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;
|
|
1759
|
+
break;
|
|
1760
|
+
case baEnum.PropertyIdentifier.MODIFICATION_DATE:
|
|
1761
|
+
if (object.value[0]) {
|
|
1762
|
+
values[objectId].modificationDate = object.value[0].value;
|
|
1763
|
+
}
|
|
1764
|
+
break;
|
|
1765
|
+
case baEnum.PropertyIdentifier.PROGRAM_STATE:
|
|
1766
|
+
if (object.value[0]) {
|
|
1767
|
+
values[objectId].programState = that.getPROP_PROGRAM_STATE(object.value[0].value);
|
|
1768
|
+
}
|
|
1769
|
+
break;
|
|
1770
|
+
case baEnum.PropertyIdentifier.RECORD_COUNT:
|
|
1771
|
+
if (object.value[0]) {
|
|
1772
|
+
values[objectId].recordCount = object.value[0].value;
|
|
1773
|
+
}
|
|
1774
|
+
break;
|
|
1775
|
+
case baEnum.PropertyIdentifier.PRIORITY_ARRAY:
|
|
1776
|
+
if (object.value.length > 0) {
|
|
1777
|
+
values[objectId].hasPriorityArray = true;
|
|
1778
|
+
}
|
|
1779
|
+
break;
|
|
1780
|
+
case baEnum.PropertyIdentifier.STATE_TEXT:
|
|
1781
|
+
if (object.value) {
|
|
1782
|
+
values[objectId].stateTextArray = object.value;
|
|
1783
|
+
if (
|
|
1784
|
+
typeof values[objectId].presentValue == "number" &&
|
|
1785
|
+
values[objectId].presentValue !== null &&
|
|
1786
|
+
values[objectId].presentValue !== undefined
|
|
1787
|
+
) {
|
|
1788
|
+
const tempIndex = values[objectId].presentValue;
|
|
1789
|
+
if (tempIndex == 0) {
|
|
1790
|
+
values[objectId].presentValue = values[objectId].stateTextArray[tempIndex].value;
|
|
1791
|
+
} else if (tempIndex !== 0) {
|
|
1792
|
+
values[objectId].presentValue = values[objectId].stateTextArray[tempIndex - 1].value;
|
|
1793
|
+
}
|
|
1854
1794
|
}
|
|
1855
1795
|
}
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1796
|
+
break;
|
|
1797
|
+
case baEnum.PropertyIdentifier.VENDOR_NAME:
|
|
1798
|
+
if (object.value) {
|
|
1799
|
+
if (object.value[0].value && typeof object.value[0].value == "string") {
|
|
1800
|
+
values[objectId].vendorName = object.value[0].value;
|
|
1801
|
+
}
|
|
1862
1802
|
}
|
|
1863
|
-
|
|
1864
|
-
|
|
1803
|
+
break;
|
|
1804
|
+
}
|
|
1865
1805
|
}
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
}
|
|
1875
|
-
})
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1806
|
+
if (
|
|
1807
|
+
pointPropertyIndex == successfulResult.values.length - 1 &&
|
|
1808
|
+
objectIndex == pointProperty.values.length - 1 &&
|
|
1809
|
+
i == fullObjects.length - 1
|
|
1810
|
+
) {
|
|
1811
|
+
that.networkTree[deviceKey] = values;
|
|
1812
|
+
resolve(that.networkTree);
|
|
1813
|
+
}
|
|
1814
|
+
});
|
|
1815
|
+
} catch (e) {
|
|
1816
|
+
that.logOut("issue resolving bacnet payload, see error: ", e);
|
|
1817
|
+
reject(e);
|
|
1818
|
+
}
|
|
1879
1819
|
}
|
|
1820
|
+
});
|
|
1821
|
+
} else {
|
|
1822
|
+
//error found in point property
|
|
1823
|
+
if (i == fullObjects.length - 1) {
|
|
1824
|
+
that.networkTree[deviceKey] = values;
|
|
1825
|
+
resolve(that.networkTree);
|
|
1880
1826
|
}
|
|
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
1827
|
}
|
|
1888
1828
|
}
|
|
1829
|
+
that.networkTree[deviceKey] = values;
|
|
1830
|
+
resolve(that.networkTree);
|
|
1831
|
+
} catch (e) {
|
|
1832
|
+
reject(e);
|
|
1889
1833
|
}
|
|
1890
|
-
that.networkTree[deviceKey] = values;
|
|
1891
|
-
resolve(that.networkTree);
|
|
1892
1834
|
});
|
|
1893
1835
|
}
|
|
1894
1836
|
|
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
|
|
|
@@ -394,7 +405,7 @@
|
|
|
394
405
|
},
|
|
395
406
|
exportPointListCsv() {
|
|
396
407
|
let app = this;
|
|
397
|
-
let csvContent = "
|
|
408
|
+
let csvContent = "ipAddress,deviceId,deviceName,pointName,objectType" + "\r\n";
|
|
398
409
|
const keys = Object.keys(app.pointList);
|
|
399
410
|
for (key in keys) {
|
|
400
411
|
const guid = keys[key];
|
|
@@ -426,14 +437,26 @@
|
|
|
426
437
|
|
|
427
438
|
if (parseInt(point) == points.length - 1 && parseInt(key) == keys.length - 1) {
|
|
428
439
|
// last iteration
|
|
429
|
-
var
|
|
440
|
+
var csvBlob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" });
|
|
430
441
|
var link = document.createElement("a");
|
|
431
|
-
|
|
442
|
+
var url = URL.createObjectURL(csvBlob);
|
|
443
|
+
link.setAttribute("href", url);
|
|
432
444
|
link.setAttribute("download", "pointslist.csv");
|
|
433
445
|
document.body.appendChild(link);
|
|
434
446
|
link.click();
|
|
435
447
|
}
|
|
436
448
|
}
|
|
449
|
+
} else {
|
|
450
|
+
if (parseInt(key) == keys.length - 1) {
|
|
451
|
+
// last iteration
|
|
452
|
+
var csvBlob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" });
|
|
453
|
+
var link = document.createElement("a");
|
|
454
|
+
var url = URL.createObjectURL(csvBlob);
|
|
455
|
+
link.setAttribute("href", url);
|
|
456
|
+
link.setAttribute("download", "pointslist.csv");
|
|
457
|
+
document.body.appendChild(link);
|
|
458
|
+
link.click();
|
|
459
|
+
}
|
|
437
460
|
}
|
|
438
461
|
}
|
|
439
462
|
},
|
|
@@ -451,6 +474,8 @@
|
|
|
451
474
|
let app = this;
|
|
452
475
|
if ((Date.now() - slotProps.node.lastSeen) / 1000 < app.pollFrequency + 5) {
|
|
453
476
|
return true;
|
|
477
|
+
} else if (slotProps.node.isDumbMstpRouter == true || slotProps.node.isDumbMstpRouter == "true") {
|
|
478
|
+
return true;
|
|
454
479
|
}
|
|
455
480
|
return false;
|
|
456
481
|
},
|
|
@@ -530,7 +555,7 @@
|
|
|
530
555
|
app.pointDisplayNameValue = app.rightClickedPoint.node.label;
|
|
531
556
|
break;
|
|
532
557
|
case "updatePoint":
|
|
533
|
-
app.updatePoint(app.
|
|
558
|
+
app.updatePoint(app.rightClickedPoint);
|
|
534
559
|
break;
|
|
535
560
|
default:
|
|
536
561
|
break;
|
|
@@ -1061,7 +1086,7 @@
|
|
|
1061
1086
|
*
|
|
1062
1087
|
*
|
|
1063
1088
|
-->
|
|
1064
|
-
<div id="read-networkTree-tab" style="display:none">
|
|
1089
|
+
<div id="read-networkTree-tab" style="display:none" class="bp-networktree-tab">
|
|
1065
1090
|
<div id="read-networkTree-tab-content" class="networkTreeContent" style="display:none">
|
|
1066
1091
|
<p-dialog
|
|
1067
1092
|
v-model:visible="showDeviceNameDialog"
|
|
@@ -1107,8 +1132,8 @@
|
|
|
1107
1132
|
|
|
1108
1133
|
<div class="headerDiv">
|
|
1109
1134
|
<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"
|
|
1135
|
+
<p-progressbar class="bp-readNode-progressbar" :value="progressBarValue" :show-value="true" :style="calculateProgressBarWidth"></p-progressbar>
|
|
1136
|
+
<div class="buttonGroup">
|
|
1112
1137
|
<button @click="rebuildDataModel()" class="rebuildDataButton" title="Rebuild Data Model">
|
|
1113
1138
|
<i class="pi pi-wrench" style="color: #969696;"></i>
|
|
1114
1139
|
</button>
|
|
@@ -1136,7 +1161,7 @@
|
|
|
1136
1161
|
<div v-if="isDeviceActive(slotProps)" class="deviceLabelParent">
|
|
1137
1162
|
<b class="deviceLabel">
|
|
1138
1163
|
<span class="statusOnline deviceStatus dotOnline dot"></span>
|
|
1139
|
-
{{slotProps.node.label}}
|
|
1164
|
+
<span class="deviceLabelSpan">{{slotProps.node.label}}</span>
|
|
1140
1165
|
<span v-if="hasMstpDevices(slotProps)" class="mstpDeviceCount"
|
|
1141
1166
|
> {{calculateMstpCount(slotProps)}} </span
|
|
1142
1167
|
>
|
|
@@ -1146,7 +1171,7 @@
|
|
|
1146
1171
|
<div v-else>
|
|
1147
1172
|
<b class="deviceLabel">
|
|
1148
1173
|
<span class="statusOffline deviceStatus dotOffline dot"></span>
|
|
1149
|
-
{{slotProps.node.label}}
|
|
1174
|
+
<span class="deviceLabelSpan">{{slotProps.node.label}}</span>
|
|
1150
1175
|
<span v-if="hasMstpDevices(slotProps)" class="mstpDeviceCount"
|
|
1151
1176
|
> {{calculateMstpCount(slotProps)}} </span
|
|
1152
1177
|
>
|
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
|
}
|