@bitpoolos/edge-bacnet 1.5.1 → 1.5.3
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 +211 -77
- package/bacnet_device.js +3 -1
- package/bacnet_gateway.html +82 -0
- package/bacnet_gateway.js +38 -0
- package/bacnet_read.html +21 -24
- package/package.json +1 -1
- package/resources/node-bacstack-ts/dist/lib/client.js +7 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.5.3] - 23-01-2025
|
|
4
|
+
|
|
5
|
+
New feature:
|
|
6
|
+
- import / export buttons added to new tab in gateway node, used to manage the complete data model for backup or restore
|
|
7
|
+
- associated API end points for programatic backing up or restoring - /bitpool-bacnet-data/getDataModel; /bitpool-bacnet-data/updateDataModel;
|
|
8
|
+
|
|
9
|
+
Further async / await refactoring
|
|
10
|
+
|
|
11
|
+
Bug fixes:
|
|
12
|
+
- incorrect device name in read list export
|
|
13
|
+
- read command indexing unhandled scenario
|
|
14
|
+
- duplicating points in read list after pressing refresh tree button
|
|
15
|
+
- Multi State Values and other state text based points not being handled correctly in large volume scenarios
|
|
16
|
+
|
|
17
|
+
NOTE:
|
|
18
|
+
New importing and exporting feature handles a .json file instead of the .cfg file used in the back end. This is due to browsers flagging .cfg files as malicious. The contents of the file are unchanged.
|
|
19
|
+
|
|
20
|
+
## [1.5.2] - 10-01-2025
|
|
21
|
+
|
|
22
|
+
Mismatched network request hot fix
|
|
23
|
+
|
|
24
|
+
|
|
3
25
|
## [1.5.1] - 13-11-2024
|
|
4
26
|
|
|
5
27
|
### Summary
|
package/bacnet_client.js
CHANGED
|
@@ -365,6 +365,20 @@ class BacnetClient extends EventEmitter {
|
|
|
365
365
|
});
|
|
366
366
|
}
|
|
367
367
|
|
|
368
|
+
forceUpdateDevices(deviceArray) {
|
|
369
|
+
let that = this;
|
|
370
|
+
try {
|
|
371
|
+
deviceArray.forEach(async function (deviceId) {
|
|
372
|
+
let device = that.deviceList.find((ele) => ele.getDeviceId() === deviceId);
|
|
373
|
+
if (device) {
|
|
374
|
+
await that.buildJsonObject(device);
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
} catch (e) {
|
|
378
|
+
that.logOut("forceUpdateDevices error: ", e);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
368
382
|
async updatePointsForDevice(deviceObject) {
|
|
369
383
|
try {
|
|
370
384
|
let device = this.deviceList.find((ele) => ele.getDeviceId() === deviceObject.deviceId);
|
|
@@ -396,6 +410,7 @@ class BacnetClient extends EventEmitter {
|
|
|
396
410
|
await this.getDevicePointListWithoutObjectList(device);
|
|
397
411
|
await this.buildJsonObject(device);
|
|
398
412
|
} catch (e) {
|
|
413
|
+
await this.buildJsonObject(device);
|
|
399
414
|
this.logOut(`Update points list error 4: ${this.getDeviceAddress(device)}`, e);
|
|
400
415
|
}
|
|
401
416
|
}
|
|
@@ -495,11 +510,11 @@ class BacnetClient extends EventEmitter {
|
|
|
495
510
|
});
|
|
496
511
|
}
|
|
497
512
|
|
|
498
|
-
//test re write
|
|
499
513
|
async queryDevices() {
|
|
500
514
|
let that = this;
|
|
501
515
|
try {
|
|
502
516
|
that.pollInProgress = true;
|
|
517
|
+
|
|
503
518
|
let index = 0;
|
|
504
519
|
await query(index);
|
|
505
520
|
|
|
@@ -563,23 +578,35 @@ class BacnetClient extends EventEmitter {
|
|
|
563
578
|
}
|
|
564
579
|
}
|
|
565
580
|
|
|
566
|
-
updateDeviceName(device) {
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
581
|
+
// updateDeviceName(device) {
|
|
582
|
+
// let that = this;
|
|
583
|
+
// return new Promise((resolve, reject) => {
|
|
584
|
+
// that
|
|
585
|
+
// ._getDeviceName(device)
|
|
586
|
+
// .then(function (deviceObject) {
|
|
587
|
+
// if (typeof deviceObject.name == "string") {
|
|
588
|
+
// device.setDeviceName(deviceObject.name + " " + device.getDeviceId());
|
|
589
|
+
// device.setPointsList(deviceObject.devicePointEntry);
|
|
590
|
+
// }
|
|
591
|
+
// resolve();
|
|
592
|
+
// })
|
|
593
|
+
// .catch(function (e) {
|
|
594
|
+
// that.logOut("updateDeviceName error: ", e);
|
|
595
|
+
// resolve();
|
|
596
|
+
// });
|
|
597
|
+
// });
|
|
598
|
+
// }
|
|
599
|
+
|
|
600
|
+
async updateDeviceName(device) {
|
|
601
|
+
try {
|
|
602
|
+
const deviceObject = await this._getDeviceName(device);
|
|
603
|
+
if (typeof deviceObject?.name === "string") {
|
|
604
|
+
device.setDeviceName(deviceObject.name + " " + device.getDeviceId());
|
|
605
|
+
device.setPointsList(deviceObject.devicePointEntry);
|
|
606
|
+
}
|
|
607
|
+
} catch (e) {
|
|
608
|
+
this.logOut("updateDeviceName error: ", e);
|
|
609
|
+
}
|
|
583
610
|
}
|
|
584
611
|
|
|
585
612
|
reinitializeClient(config) {
|
|
@@ -713,15 +740,15 @@ class BacnetClient extends EventEmitter {
|
|
|
713
740
|
// Process points for the current device
|
|
714
741
|
const pointsToRead = readConfig.pointsToRead[key];
|
|
715
742
|
const pointNames = Object.keys(pointsToRead);
|
|
716
|
-
let totalPoints = pointNames.length;
|
|
743
|
+
let totalPoints = pointNames.length - 1;
|
|
717
744
|
let requestArray = [];
|
|
718
745
|
let processedPoints = 0; // Counter for processed points
|
|
719
746
|
|
|
720
747
|
// Process each point for the device in batches
|
|
721
748
|
for (let i = 0; i < pointNames.length; i++) {
|
|
749
|
+
|
|
722
750
|
const pointName = pointNames[i];
|
|
723
751
|
if (pointName === "deviceName") {
|
|
724
|
-
totalPoints = totalPoints - 1;
|
|
725
752
|
continue;
|
|
726
753
|
}
|
|
727
754
|
|
|
@@ -742,7 +769,7 @@ class BacnetClient extends EventEmitter {
|
|
|
742
769
|
}
|
|
743
770
|
|
|
744
771
|
// Process the batch when the request array is full or the last point is reached
|
|
745
|
-
if (requestArray.length === maxObjectCount
|
|
772
|
+
if (requestArray.length === maxObjectCount) {
|
|
746
773
|
if (device.getProtocolServiceSupport("ReadPropertyMultiple") == true) {
|
|
747
774
|
await that.processBatch(device, requestArray, deviceName, bacnetResults, that, roundDecimal);
|
|
748
775
|
} else {
|
|
@@ -752,11 +779,22 @@ class BacnetClient extends EventEmitter {
|
|
|
752
779
|
requestArray = [];
|
|
753
780
|
// Increment the processed points counter
|
|
754
781
|
processedPoints += maxObjectCount;
|
|
782
|
+
} else if (i === pointNames.length - 1) {
|
|
783
|
+
if (device.getProtocolServiceSupport("ReadPropertyMultiple") == true) {
|
|
784
|
+
await that.processBatch(device, requestArray, deviceName, bacnetResults, that, roundDecimal);
|
|
785
|
+
} else {
|
|
786
|
+
await that.processIndividualPoints(device, requestArray, deviceName, bacnetResults, that, roundDecimal);
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
requestArray = [];
|
|
790
|
+
// Increment the processed points counter
|
|
791
|
+
processedPoints += i;
|
|
755
792
|
}
|
|
756
793
|
|
|
757
794
|
// Check if all points for the device have been processed
|
|
758
795
|
if (processedPoints >= totalPoints) {
|
|
759
796
|
pendingRequests++;
|
|
797
|
+
|
|
760
798
|
// Emit the `values` event for the current device
|
|
761
799
|
that.emit(
|
|
762
800
|
"values",
|
|
@@ -851,22 +889,36 @@ class BacnetClient extends EventEmitter {
|
|
|
851
889
|
for (const request of requestArray) {
|
|
852
890
|
const { objectId, pointRef, pointName } = request;
|
|
853
891
|
try {
|
|
892
|
+
|
|
854
893
|
const result = await that.updatePoint(device, pointRef);
|
|
855
|
-
const val = result.values[0].value;
|
|
856
894
|
|
|
857
|
-
if (
|
|
858
|
-
|
|
859
|
-
} else {
|
|
860
|
-
pointRef.presentValue = val;
|
|
861
|
-
}
|
|
895
|
+
if (result.objectId.type == objectId.type && result.objectId.instance == objectId.instance) {
|
|
896
|
+
const val = result.values[0].value;
|
|
862
897
|
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
pointRef.status = "online";
|
|
866
|
-
pointRef.error = "none";
|
|
898
|
+
if (isNumber(val)) {
|
|
899
|
+
pointRef.presentValue = roundDecimalPlaces(val, roundDecimal);
|
|
867
900
|
|
|
868
|
-
|
|
869
|
-
|
|
901
|
+
if (pointRef.meta.objectId.type == 19 || pointRef.meta.objectId.type == 13 || pointRef.meta.objectId.type == 14) {
|
|
902
|
+
if (pointRef.stateTextArray && typeof pointRef.stateTextArray[0].value !== "object") {
|
|
903
|
+
if (val != 0) {
|
|
904
|
+
pointRef.presentValue = pointRef.stateTextArray[val - 1].value;
|
|
905
|
+
} else {
|
|
906
|
+
pointRef.presentValue = pointRef.stateTextArray[val].value;
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
} else {
|
|
911
|
+
pointRef.presentValue = val;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
pointRef.meta["device"] = deviceMetaInfo;
|
|
915
|
+
pointRef.timestamp = Date.now();
|
|
916
|
+
pointRef.status = "online";
|
|
917
|
+
pointRef.error = "none";
|
|
918
|
+
|
|
919
|
+
// Store the point data in results
|
|
920
|
+
bacnetResults[deviceName][pointName] = pointRef;
|
|
921
|
+
}
|
|
870
922
|
} catch (err) {
|
|
871
923
|
that.logOut(`Error updating point ${pointName}:`, err);
|
|
872
924
|
|
|
@@ -879,18 +931,53 @@ class BacnetClient extends EventEmitter {
|
|
|
879
931
|
}
|
|
880
932
|
}
|
|
881
933
|
|
|
882
|
-
updateManyPoints(device, points) {
|
|
934
|
+
// updateManyPoints(device, points) {
|
|
935
|
+
// let that = this;
|
|
936
|
+
// return new Promise((resolve, reject) => {
|
|
937
|
+
// // let readOptions = {
|
|
938
|
+
// // maxSegments: device.getMaxSe,
|
|
939
|
+
// // maxApdu: that.readPropertyMultipleOptions.maxApdu,
|
|
940
|
+
// // };
|
|
941
|
+
|
|
942
|
+
// that
|
|
943
|
+
// ._readObjectWithRequestArray(device, points, that.readPropertyMultipleOptions)
|
|
944
|
+
// .then(function (results) {
|
|
945
|
+
// resolve(results);
|
|
946
|
+
// })
|
|
947
|
+
// .catch(function (err) {
|
|
948
|
+
// reject(err);
|
|
949
|
+
// });
|
|
950
|
+
// });
|
|
951
|
+
// }
|
|
952
|
+
|
|
953
|
+
async updateManyPoints(device, points) {
|
|
954
|
+
try {
|
|
955
|
+
// let readOptions = {
|
|
956
|
+
// maxSegments: device.getMaxSe,
|
|
957
|
+
// maxApdu: that.readPropertyMultipleOptions.maxApdu,
|
|
958
|
+
// };
|
|
959
|
+
|
|
960
|
+
const results = await this._readObjectWithRequestArray(device, points, this.readPropertyMultipleOptions);
|
|
961
|
+
return results;
|
|
962
|
+
} catch (error) {
|
|
963
|
+
throw error;
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
updatePointWithRetry(device, point, retryCount = 1) {
|
|
883
968
|
let that = this;
|
|
884
|
-
|
|
885
|
-
that
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
}
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
}
|
|
969
|
+
const tryUpdate = (retriesLeft) => {
|
|
970
|
+
return that.updatePoint(device, point).catch((err) => {
|
|
971
|
+
if (retriesLeft > 0) {
|
|
972
|
+
that.logOut(`Retrying updatePoint... Attempts left: ${retriesLeft}`);
|
|
973
|
+
return tryUpdate(retriesLeft - 1);
|
|
974
|
+
}
|
|
975
|
+
// If no retries are left, reject with the original error
|
|
976
|
+
return Promise.reject(err);
|
|
977
|
+
});
|
|
978
|
+
};
|
|
979
|
+
|
|
980
|
+
return tryUpdate(retryCount);
|
|
894
981
|
}
|
|
895
982
|
|
|
896
983
|
updatePoint(device, point) {
|
|
@@ -1504,6 +1591,22 @@ class BacnetClient extends EventEmitter {
|
|
|
1504
1591
|
});
|
|
1505
1592
|
}
|
|
1506
1593
|
|
|
1594
|
+
getDataModel() {
|
|
1595
|
+
let that = this;
|
|
1596
|
+
return new Promise(async function (resolve, reject) {
|
|
1597
|
+
try {
|
|
1598
|
+
resolve({
|
|
1599
|
+
renderList: that.renderList,
|
|
1600
|
+
deviceList: that.deviceList,
|
|
1601
|
+
pointList: that.networkTree,
|
|
1602
|
+
renderListCount: that.renderListCount,
|
|
1603
|
+
});
|
|
1604
|
+
} catch (e) {
|
|
1605
|
+
reject(e);
|
|
1606
|
+
}
|
|
1607
|
+
});
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1507
1610
|
updatePointsList(json) {
|
|
1508
1611
|
let that = this;
|
|
1509
1612
|
json.deviceList.forEach(function (updatedDevice) {
|
|
@@ -1539,6 +1642,29 @@ class BacnetClient extends EventEmitter {
|
|
|
1539
1642
|
});
|
|
1540
1643
|
}
|
|
1541
1644
|
|
|
1645
|
+
updateDataModel(json) {
|
|
1646
|
+
let that = this;
|
|
1647
|
+
return new Promise(async function (resolve, reject) {
|
|
1648
|
+
try {
|
|
1649
|
+
if (json.body.renderList) {
|
|
1650
|
+
that.renderList = json.body.renderList;
|
|
1651
|
+
}
|
|
1652
|
+
if (json.body.deviceList) {
|
|
1653
|
+
await that.updateDeviceList(json);
|
|
1654
|
+
}
|
|
1655
|
+
if (json.body.pointList) {
|
|
1656
|
+
that.networkTree = json.body.pointList;
|
|
1657
|
+
}
|
|
1658
|
+
if (json.body.renderListCount) {
|
|
1659
|
+
that.renderListCount = json.body.renderListCount;
|
|
1660
|
+
}
|
|
1661
|
+
resolve();
|
|
1662
|
+
} catch (e) {
|
|
1663
|
+
reject(e);
|
|
1664
|
+
}
|
|
1665
|
+
});
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1542
1668
|
sortDevices(a, b) {
|
|
1543
1669
|
if (a.deviceId < b.deviceId) {
|
|
1544
1670
|
return -1;
|
|
@@ -1780,32 +1906,36 @@ class BacnetClient extends EventEmitter {
|
|
|
1780
1906
|
|
|
1781
1907
|
switch (object.id) {
|
|
1782
1908
|
case baEnum.PropertyIdentifier.PRESENT_VALUE:
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
if (
|
|
1787
|
-
values[objectId].presentValue = false;
|
|
1788
|
-
} else if (object.value[0].value == 1) {
|
|
1789
|
-
values[objectId].presentValue = true;
|
|
1790
|
-
}
|
|
1791
|
-
} else if (objectType == 40) {
|
|
1792
|
-
//character string
|
|
1793
|
-
values[objectId].presentValue = object.value[0].value;
|
|
1794
|
-
} else if (objectType == 13 || objectType == 14 || objectType == 19) {
|
|
1795
|
-
//check for MSV MSI MSO - for enum state text
|
|
1796
|
-
if (values[objectId].stateTextArray && values[objectId].stateTextArray.length > 0) {
|
|
1909
|
+
try {
|
|
1910
|
+
if (object.value[0] && object.value[0].value !== "undefined" && object.value[0].value !== null) {
|
|
1911
|
+
//check for binary object type
|
|
1912
|
+
if (objectType == 3 || objectType == 4 || objectType == 5) {
|
|
1797
1913
|
if (object.value[0].value == 0) {
|
|
1798
|
-
values[objectId].presentValue =
|
|
1799
|
-
} else if (object.value[0].value
|
|
1800
|
-
values[objectId].presentValue =
|
|
1801
|
-
values[objectId].stateTextArray[object.value[0].value - 1].value;
|
|
1914
|
+
values[objectId].presentValue = false;
|
|
1915
|
+
} else if (object.value[0].value == 1) {
|
|
1916
|
+
values[objectId].presentValue = true;
|
|
1802
1917
|
}
|
|
1918
|
+
} else if (objectType == 40) {
|
|
1919
|
+
//character string
|
|
1920
|
+
values[objectId].presentValue = object.value[0].value;
|
|
1921
|
+
} else if (objectType == 13 || objectType == 14 || objectType == 19) {
|
|
1922
|
+
//check for MSV MSI MSO - for enum state text
|
|
1923
|
+
if (values[objectId].stateTextArray && values[objectId].stateTextArray.length > 0) {
|
|
1924
|
+
if (object.value[0].value == 0) {
|
|
1925
|
+
values[objectId].presentValue = values[objectId].stateTextArray[object.value[0].value].value;
|
|
1926
|
+
} else if (object.value[0].value !== 0) {
|
|
1927
|
+
values[objectId].presentValue =
|
|
1928
|
+
values[objectId].stateTextArray[object.value[0].value - 1].value;
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
} else if (objectType !== 8) {
|
|
1932
|
+
values[objectId].presentValue = roundDecimalPlaces(object.value[0].value, 2);
|
|
1803
1933
|
}
|
|
1804
|
-
} else if (objectType !== 8) {
|
|
1805
|
-
values[objectId].presentValue = roundDecimalPlaces(object.value[0].value, 2);
|
|
1806
1934
|
}
|
|
1935
|
+
values[objectId].meta.arrayIndex = object.index;
|
|
1936
|
+
} catch (e) {
|
|
1937
|
+
that.logOut("buildResponse PRESENT_VALUE error: ", e);
|
|
1807
1938
|
}
|
|
1808
|
-
values[objectId].meta.arrayIndex = object.index;
|
|
1809
1939
|
break;
|
|
1810
1940
|
case baEnum.PropertyIdentifier.DESCRIPTION:
|
|
1811
1941
|
if (object.value[0]) values[objectId].description = object.value[0].value;
|
|
@@ -1853,20 +1983,24 @@ class BacnetClient extends EventEmitter {
|
|
|
1853
1983
|
}
|
|
1854
1984
|
break;
|
|
1855
1985
|
case baEnum.PropertyIdentifier.STATE_TEXT:
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1986
|
+
try {
|
|
1987
|
+
if (object.value) {
|
|
1988
|
+
values[objectId].stateTextArray = object.value;
|
|
1989
|
+
if (
|
|
1990
|
+
typeof values[objectId].presentValue == "number" &&
|
|
1991
|
+
values[objectId].presentValue !== null &&
|
|
1992
|
+
values[objectId].presentValue !== undefined
|
|
1993
|
+
) {
|
|
1994
|
+
const tempIndex = values[objectId].presentValue;
|
|
1995
|
+
if (tempIndex == 0) {
|
|
1996
|
+
values[objectId].presentValue = values[objectId].stateTextArray[tempIndex].value;
|
|
1997
|
+
} else if (tempIndex !== 0) {
|
|
1998
|
+
values[objectId].presentValue = values[objectId].stateTextArray[tempIndex - 1].value;
|
|
1999
|
+
}
|
|
1868
2000
|
}
|
|
1869
2001
|
}
|
|
2002
|
+
} catch (e) {
|
|
2003
|
+
that.logOut("buildResponse STATE_TEXT error: ", e);
|
|
1870
2004
|
}
|
|
1871
2005
|
break;
|
|
1872
2006
|
case baEnum.PropertyIdentifier.VENDOR_NAME:
|
package/bacnet_device.js
CHANGED
package/bacnet_gateway.html
CHANGED
|
@@ -203,6 +203,11 @@
|
|
|
203
203
|
label: "Server",
|
|
204
204
|
});
|
|
205
205
|
|
|
206
|
+
tabs.addTab({
|
|
207
|
+
id: "read-backup-tab",
|
|
208
|
+
label: "Backup & Restore",
|
|
209
|
+
});
|
|
210
|
+
|
|
206
211
|
if (node.networkInterfaces && node.networkInterfaces.length > 0) {
|
|
207
212
|
let nicSelector = document.getElementById("node-input-local_device_address");
|
|
208
213
|
node.networkInterfaces.forEach(function (option) {
|
|
@@ -333,6 +338,8 @@
|
|
|
333
338
|
vueapp.use(primevue.confirmationservice);
|
|
334
339
|
node.vm1 = vueapp.mount("#serverParent");
|
|
335
340
|
|
|
341
|
+
|
|
342
|
+
// Import Device List
|
|
336
343
|
$("#file-upload").on("change", function (event) {
|
|
337
344
|
const input = event.target.files[0];
|
|
338
345
|
const reader = new FileReader();
|
|
@@ -356,6 +363,7 @@
|
|
|
356
363
|
reader.readAsText(input);
|
|
357
364
|
});
|
|
358
365
|
|
|
366
|
+
//Export Device List
|
|
359
367
|
$("#file-export").click(function (params) {
|
|
360
368
|
$.ajax({
|
|
361
369
|
url: RED.settings.httpNodeRoot + "bitpool-bacnet-data/getDeviceList",
|
|
@@ -370,6 +378,45 @@
|
|
|
370
378
|
});
|
|
371
379
|
});
|
|
372
380
|
|
|
381
|
+
//Import complete Data model
|
|
382
|
+
$("#file-upload-database").on("change", function (event) {
|
|
383
|
+
const input = event.target.files[0];
|
|
384
|
+
const reader = new FileReader();
|
|
385
|
+
|
|
386
|
+
reader.onload = function (e) {
|
|
387
|
+
const text = e.target.result;
|
|
388
|
+
|
|
389
|
+
let jsonPayload = JSON.parse(text);
|
|
390
|
+
|
|
391
|
+
$.ajax({
|
|
392
|
+
type: "POST",
|
|
393
|
+
url: RED.settings.httpNodeRoot + "bitpool-bacnet-data/updateDataModel",
|
|
394
|
+
dataType: "json",
|
|
395
|
+
contentType: "application/json",
|
|
396
|
+
data: JSON.stringify(jsonPayload),
|
|
397
|
+
success: function (result) { },
|
|
398
|
+
timeout: 15000,
|
|
399
|
+
});
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
reader.readAsText(input);
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
// Export complete Data model
|
|
406
|
+
$("#file-export-database").click(function (params) {
|
|
407
|
+
$.ajax({
|
|
408
|
+
url: RED.settings.httpNodeRoot + "bitpool-bacnet-data/getDataModel",
|
|
409
|
+
success: function (deviceList) {
|
|
410
|
+
let data = "text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(deviceList));
|
|
411
|
+
let aEle = document.getElementById("exportJSON");
|
|
412
|
+
aEle.setAttribute("href", "data:" + data);
|
|
413
|
+
aEle.setAttribute("download", "edge-bacnet-datastore.json");
|
|
414
|
+
aEle.click();
|
|
415
|
+
},
|
|
416
|
+
timeout: 10000,
|
|
417
|
+
});
|
|
418
|
+
});
|
|
419
|
+
|
|
373
420
|
//start device scan range matrix
|
|
374
421
|
|
|
375
422
|
$("#node-input-deviceIdRangeMatrix-container")
|
|
@@ -739,6 +786,21 @@
|
|
|
739
786
|
.point_name {
|
|
740
787
|
font-weight: bold;
|
|
741
788
|
}
|
|
789
|
+
.database-backup {
|
|
790
|
+
border-top: 1px solid grey;
|
|
791
|
+
padding-top: 25px;
|
|
792
|
+
margin-top: 5px !important;
|
|
793
|
+
}
|
|
794
|
+
.database-file-label {
|
|
795
|
+
color: black;
|
|
796
|
+
font-weight: bold;
|
|
797
|
+
font-size: 16px;
|
|
798
|
+
width: auto !important;
|
|
799
|
+
|
|
800
|
+
}
|
|
801
|
+
.database-file-label-div {
|
|
802
|
+
padding-top: 15px;
|
|
803
|
+
}
|
|
742
804
|
</style>
|
|
743
805
|
|
|
744
806
|
<div class="form-row node-input-read-tabs-row">
|
|
@@ -967,6 +1029,26 @@
|
|
|
967
1029
|
</div>
|
|
968
1030
|
</div>
|
|
969
1031
|
</div>
|
|
1032
|
+
<div id="read-backup-tab" style="display:none">
|
|
1033
|
+
<div class="database-file-label-div">
|
|
1034
|
+
<span class="database-file-label">Database File</span>
|
|
1035
|
+
</div>
|
|
1036
|
+
|
|
1037
|
+
<div class="form-row bp-import-buttons database-backup" id="importDeviceList">
|
|
1038
|
+
<!-- <label> Device List: </label> -->
|
|
1039
|
+
<label for="file-upload-database" class="custom-file-upload">
|
|
1040
|
+
<i class="fa fa-arrow-circle-up" id="fileLabel"></i>
|
|
1041
|
+
<a id="fileLabelText" style="padding-left: 10px;">Import database</a>
|
|
1042
|
+
</label>
|
|
1043
|
+
<input type="file" id="file-upload-database" accept="application/JSON" class="inputStyle" style="width: 258px;" />
|
|
1044
|
+
<label for="file-export-database" class="custom-file-upload" style="margin-left: 5px;">
|
|
1045
|
+
<i class="fa fa-arrow-circle-down" id="fileLabel"></i>
|
|
1046
|
+
<a id="fileLabelText" style="padding-left: 10px;">Export database</a>
|
|
1047
|
+
</label>
|
|
1048
|
+
<input id="file-export-database" class="inputStyle" style="width: 258px; display: none;" />
|
|
1049
|
+
<a id="exportJSON" style="display: none"></a>
|
|
1050
|
+
</div>
|
|
1051
|
+
</div>
|
|
970
1052
|
</div>
|
|
971
1053
|
</script>
|
|
972
1054
|
<script type="text/html" data-help-name="Bacnet-Gateway">
|
package/bacnet_gateway.js
CHANGED
|
@@ -240,6 +240,8 @@ module.exports = function (RED) {
|
|
|
240
240
|
}).catch(function (error) {
|
|
241
241
|
logOut("Error in applyDisplayNames: ", error);
|
|
242
242
|
});
|
|
243
|
+
} else if (msg.forceUpdateDevices == true) {
|
|
244
|
+
node.bacnetClient.forceUpdateDevices(msg.deviceIdArray);
|
|
243
245
|
}
|
|
244
246
|
});
|
|
245
247
|
|
|
@@ -369,6 +371,42 @@ module.exports = function (RED) {
|
|
|
369
371
|
}
|
|
370
372
|
});
|
|
371
373
|
|
|
374
|
+
//route handler for getting data model
|
|
375
|
+
RED.httpAdmin.get("/bitpool-bacnet-data/getDataModel", function (req, res) {
|
|
376
|
+
if (!node.bacnetClient) {
|
|
377
|
+
logOut("Issue with the bacnetClient while getting data model: ", node.bacnetClient);
|
|
378
|
+
res.send(false);
|
|
379
|
+
} else {
|
|
380
|
+
node.bacnetClient
|
|
381
|
+
.getDataModel()
|
|
382
|
+
.then(function (result) {
|
|
383
|
+
res.send(result);
|
|
384
|
+
})
|
|
385
|
+
.catch(function (error) {
|
|
386
|
+
res.send(error);
|
|
387
|
+
logOut("Error getting data model: ", error);
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
//route handler for updating data model
|
|
393
|
+
RED.httpAdmin.post("/bitpool-bacnet-data/updateDataModel", function (req, res) {
|
|
394
|
+
if (!node.bacnetClient) {
|
|
395
|
+
logOut("Issue with the bacnetClient while getting data model: ", node.bacnetClient);
|
|
396
|
+
res.send(false);
|
|
397
|
+
} else {
|
|
398
|
+
node.bacnetClient
|
|
399
|
+
.updateDataModel(req)
|
|
400
|
+
.then(function (result) {
|
|
401
|
+
res.send(result);
|
|
402
|
+
})
|
|
403
|
+
.catch(function (error) {
|
|
404
|
+
res.send(error);
|
|
405
|
+
logOut("Error getting data model: ", error);
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
|
|
372
410
|
//route handler for purge device
|
|
373
411
|
RED.httpAdmin.post("/bitpool-bacnet-data/purgeDevice", function (req, res) {
|
|
374
412
|
if (!node.bacnetClient) {
|
package/bacnet_read.html
CHANGED
|
@@ -634,14 +634,7 @@
|
|
|
634
634
|
let app = this;
|
|
635
635
|
const slotProps = app.rightClickedDevice;
|
|
636
636
|
const displayName = app.deviceDisplayNameValue;
|
|
637
|
-
let device = app.
|
|
638
|
-
if (ele.address.address) {
|
|
639
|
-
return ele.address.address == slotProps.node.ipAddr && ele.deviceId == slotProps.node.deviceId;
|
|
640
|
-
} else {
|
|
641
|
-
return ele.address == slotProps.node.ipAddr && ele.deviceId == slotProps.node.deviceId;
|
|
642
|
-
}
|
|
643
|
-
});
|
|
644
|
-
|
|
637
|
+
let device = app.getDeviceFromDeviceList(slotProps.node.ipAddr, slotProps.node.deviceId);
|
|
645
638
|
if (device) {
|
|
646
639
|
app.nodeService.setDeviceDisplayName(device, displayName).then(function (result) {
|
|
647
640
|
if (result) {
|
|
@@ -660,7 +653,7 @@
|
|
|
660
653
|
const pointName = slotProps.node.pointName;
|
|
661
654
|
|
|
662
655
|
let device = app.deviceList.find((ele) => {
|
|
663
|
-
return ele.
|
|
656
|
+
return ele.displayName == slotProps.node.parentDevice;
|
|
664
657
|
});
|
|
665
658
|
|
|
666
659
|
if (device) {
|
|
@@ -676,6 +669,18 @@
|
|
|
676
669
|
|
|
677
670
|
app.showPointNameDialog = false;
|
|
678
671
|
},
|
|
672
|
+
getDeviceFromDeviceList(ip, id) {
|
|
673
|
+
let app = this;
|
|
674
|
+
let device = app.deviceList.find((ele) => {
|
|
675
|
+
if (ele.address.address) {
|
|
676
|
+
return ele.address.address == ip && ele.deviceId == id;
|
|
677
|
+
} else {
|
|
678
|
+
return ele.address == ip && ele.deviceId == id;
|
|
679
|
+
}
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
return device;
|
|
683
|
+
},
|
|
679
684
|
settingDeviceName(slotProps) {
|
|
680
685
|
let app = this;
|
|
681
686
|
if (slotProps.node.settingDisplayName) {
|
|
@@ -709,11 +714,9 @@
|
|
|
709
714
|
(ele) => ele.ipAddr == key.split("-")[0] && ele.deviceId == key.split("-")[1]
|
|
710
715
|
);
|
|
711
716
|
let deviceName;
|
|
712
|
-
|
|
713
717
|
if (readDevice) {
|
|
714
718
|
deviceName = readDevice.label;
|
|
715
719
|
}
|
|
716
|
-
|
|
717
720
|
exportJson[key] = {};
|
|
718
721
|
if (deviceName) {
|
|
719
722
|
exportJson[key]["deviceName"] = deviceName;
|
|
@@ -725,9 +728,8 @@
|
|
|
725
728
|
let pointName = devicePoints[pointIndex];
|
|
726
729
|
let pointObject = device[pointName];
|
|
727
730
|
|
|
728
|
-
//formatting json payload
|
|
729
|
-
|
|
730
|
-
if (pointObject) {
|
|
731
|
+
//formatting json payload
|
|
732
|
+
if (pointObject && pointName !== "deviceName") {
|
|
731
733
|
exportJson[key][pointName] = {
|
|
732
734
|
meta: pointObject.meta,
|
|
733
735
|
objectName: pointObject.objectName,
|
|
@@ -765,6 +767,7 @@
|
|
|
765
767
|
let ip = key.split("-")[0];
|
|
766
768
|
let id = key.split("-")[1];
|
|
767
769
|
const importedDevice = pointsToRead[key];
|
|
770
|
+
//match only by IP, to handle case of mstp device
|
|
768
771
|
let foundIndex = app.devices.findIndex((ele) => ele.ipAddr == ip);
|
|
769
772
|
|
|
770
773
|
if (foundIndex !== -1) {
|
|
@@ -772,17 +775,11 @@
|
|
|
772
775
|
if (app.devices[foundIndex].deviceId == id) {
|
|
773
776
|
//found device
|
|
774
777
|
let treeDevice = app.devices[foundIndex];
|
|
775
|
-
let device = app.
|
|
776
|
-
if (ele.address.address) {
|
|
777
|
-
return ele.address.address == ip && ele.deviceId == id;
|
|
778
|
-
} else {
|
|
779
|
-
return ele.address == ip && ele.deviceId == id;
|
|
780
|
-
}
|
|
781
|
-
});
|
|
778
|
+
let device = app.getDeviceFromDeviceList(ip, id);
|
|
782
779
|
|
|
783
780
|
for (let pointName in importedDevice) {
|
|
784
781
|
let point = importedDevice[pointName];
|
|
785
|
-
if (pointName == "deviceName") {
|
|
782
|
+
if (pointName == "deviceName" && typeof point == "string") {
|
|
786
783
|
app.nodeService.setDeviceDisplayName(device, point);
|
|
787
784
|
treeDevice.label = point;
|
|
788
785
|
} else {
|
|
@@ -849,7 +846,7 @@
|
|
|
849
846
|
|
|
850
847
|
for (let pointName in importedDevice) {
|
|
851
848
|
let point = importedDevice[pointName];
|
|
852
|
-
if (pointName == "deviceName") {
|
|
849
|
+
if (pointName == "deviceName" && typeof point == "string") {
|
|
853
850
|
app.nodeService.setDeviceDisplayName(device, point);
|
|
854
851
|
mstpDevice.label = point;
|
|
855
852
|
} else {
|
|
@@ -878,7 +875,7 @@
|
|
|
878
875
|
} else {
|
|
879
876
|
// read device found, add point to existing
|
|
880
877
|
let pointIndex = app.readDevices[isDeviceInReadList].children[0].children.findIndex(
|
|
881
|
-
(ele) => ele.
|
|
878
|
+
(ele) => parseInt(ele.bacnetInstance) == parseInt(point.meta.objectId.instance) && parseInt(ele.bacnetType) == parseInt(point.meta.objectId.type)
|
|
882
879
|
);
|
|
883
880
|
if (pointIndex == -1) {
|
|
884
881
|
app.readDevices[isDeviceInReadList].children[0].children.push(pointInTree);
|
package/package.json
CHANGED
|
@@ -237,9 +237,13 @@ class Client extends events_1.EventEmitter {
|
|
|
237
237
|
if (!result) return debug('Received invalid deleteObject message');
|
|
238
238
|
this.emit('deleteObject', { address: address, invokeId: invokeId, request: result, srcAddress: srcAddress });
|
|
239
239
|
} else if (service === baEnum.ConfirmedServiceChoice.ACKNOWLEDGE_ALARM) {
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
240
|
+
try {
|
|
241
|
+
result = baServices.alarmAcknowledge.decode(buffer, offset, length);
|
|
242
|
+
if (!result) return debug('Received invalid alarmAcknowledge message');
|
|
243
|
+
this.emit('alarmAcknowledge', { address: address, invokeId: invokeId, request: result, srcAddress: srcAddress });
|
|
244
|
+
} catch (e) {
|
|
245
|
+
//console.log("Error in alarmAcknowledge: ", e);
|
|
246
|
+
}
|
|
243
247
|
} else if (service === baEnum.ConfirmedServiceChoice.GET_ALARM_SUMMARY) {
|
|
244
248
|
this.emit('getAlarmSummary', { address: address, invokeId: invokeId });
|
|
245
249
|
} else if (service === baEnum.ConfirmedServiceChoice.GET_ENROLLMENT_SUMMARY) {
|