@bitpoolos/edge-bacnet 1.5.2 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +151 -1
- package/bacnet_client.js +202 -108
- package/bacnet_device.js +3 -1
- package/bacnet_gateway.html +100 -2
- package/bacnet_gateway.js +382 -250
- package/bacnet_inspector.html +43 -0
- package/bacnet_inspector.js +1564 -0
- package/bacnet_inspector_worker.js +535 -0
- package/bacnet_read.html +47 -50
- package/bacnet_read.js +0 -3
- package/common.js +201 -38
- package/inspector.html +460 -0
- package/package.json +6 -2
- package/resources/Logo_Simplified_Positive.svg +32 -0
- package/resources/downloadAsHtml.js +654 -0
- package/resources/icons/device-id-change-icon.svg +4 -0
- package/resources/icons/device-id-conflict-icon.svg +4 -0
- package/resources/icons/favicon.ico +0 -0
- package/resources/icons/points-error-icon.svg +4 -0
- package/resources/icons/points-missing-icon.svg +4 -0
- package/resources/icons/points-ok-icon.svg +4 -0
- package/resources/icons/points-unmapped-icon.svg +5 -0
- package/resources/icons/points-warning-icon.svg +4 -0
- package/resources/inspector.css +25312 -0
- package/resources/inspectorStyle.css +254 -0
- package/resources/inspectorStyles.css +478 -0
- package/resources/node-bacstack-ts/dist/lib/client.js +7 -3
- package/resources/primevue.min.js +1 -0
- package/resources/style.css +17 -1
- package/resources/vue3513.global.prod.js +9 -0
- package/ssrHtmlExporter.js +535 -0
- package/treeBuilder.js +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,10 +1,160 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.6.0] - 09-04-2025
|
|
4
|
+
|
|
5
|
+
New features:
|
|
6
|
+
|
|
7
|
+
- New node - Inspector version 1:
|
|
8
|
+
|
|
9
|
+
- Link the outputs of all of your Read nodes and Gateway node into the inspector node for a more detailed analysis of that current state of your site.
|
|
10
|
+
- There is a custom UI which can be viewed once the node is placed and deployed, via the open webpage link in the node properties view.
|
|
11
|
+
- Basic point status statistics can be output in MQTT format via an injection of msg.type = "sendMqttStats" directly into the inspector node. This can be done with a node-red inject node or a bitpool-inject node.
|
|
12
|
+
- The custom UI shows a data table with rich meta data, that can be filtered.
|
|
13
|
+
- The inspector node comes with a variety of API routes that can be used for analysis and engineering
|
|
14
|
+
- API routes available:
|
|
15
|
+
|
|
16
|
+
- /inspector
|
|
17
|
+
- view UI in web browser
|
|
18
|
+
|
|
19
|
+
- /inspector-downloadhtml
|
|
20
|
+
- downloads the html of the inspector in its current state
|
|
21
|
+
|
|
22
|
+
- /inspector-downloadhtml?filter=tableKey&value=value1,value2
|
|
23
|
+
- downloads the html of the inspector, but with a applied filter to the table.
|
|
24
|
+
- tableKey in the above example can be any of the table columns, only 1 tableKey may be filtered on:
|
|
25
|
+
deviceID, objectType, objectInstance, presentValue, dataModelStatus, pointName, discoveredBACnetPointName, displayName, deviceName, ipAddress, area, key, topic, lastSeen, error
|
|
26
|
+
- value=value1,value2 etc can be any value that the tableKey can contain. This parameter can accept many comma separated values
|
|
27
|
+
- an example filter request may look like:
|
|
28
|
+
/inspector-downloadhtml?filter=dataModelStatus&value=error,missing
|
|
29
|
+
|
|
30
|
+
- /getModelStats
|
|
31
|
+
- returns JSON data with analysis of the BACnet model
|
|
32
|
+
- contains point status, metrics, and detailed information
|
|
33
|
+
|
|
34
|
+
- /pointstoread
|
|
35
|
+
- downloads CSV file with all points in the read list
|
|
36
|
+
- format: [siteName]_PointsToRead_[timestamp].csv
|
|
37
|
+
|
|
38
|
+
- /getpointerrors
|
|
39
|
+
- downloads CSV file with all points that have errors
|
|
40
|
+
- format: [siteName]_PointErrors_[timestamp].csv
|
|
41
|
+
|
|
42
|
+
- /getmodelstatscsv
|
|
43
|
+
- downloads CSV file with all model stats data in CSV format
|
|
44
|
+
- format: [siteName]_ModelStats_[timestamp].csv
|
|
45
|
+
|
|
46
|
+
- /publishedpointslist
|
|
47
|
+
- downloads CSV file with all published points and their current values
|
|
48
|
+
- format: [siteName]_PublishedPointsList_[timestamp].csv
|
|
49
|
+
- outputs mqtt topic and payloads with statistics about the current state of the bacnet network
|
|
50
|
+
- output topics:
|
|
51
|
+
EDGE_DEVICE_{IP_ID}/STATUS/LAST_POINT_PUSHED_TIME
|
|
52
|
+
payload: Timestamp of when the last point was pushed (ISO string)
|
|
53
|
+
EDGE_DEVICE_{IP_ID}/STATUS/LAST_STAT_CALC_TIME
|
|
54
|
+
payload: Current timestamp (ISO string)
|
|
55
|
+
EDGE_DEVICE_{IP_ID}/STATUS/UPTIME
|
|
56
|
+
payload: System uptime formatted as string (e.g., "Uptime: 3 days, 5 hours, 12 minutes, 45 seconds")
|
|
57
|
+
EDGE_DEVICE_{IP_ID}/STATUS/ONLINE_POINTS
|
|
58
|
+
payload: Number of online points
|
|
59
|
+
EDGE_DEVICE_{IP_ID}/STATUS/OFFLINE_POINTS
|
|
60
|
+
payload: Number of offline points
|
|
61
|
+
EDGE_DEVICE_{IP_ID}/STATUS/TOTAL_POLLED_POINTS
|
|
62
|
+
payload: Total number of polled points
|
|
63
|
+
EDGE_DEVICE_{IP_ID}/STATUS/AVERAGE_TIME_SINCE_COV_IN_SECONDS
|
|
64
|
+
payload: Average time since last change of value in seconds
|
|
65
|
+
EDGE_DEVICE_{IP_ID}/STATUS/TOTAL_POINTS_TO_READ
|
|
66
|
+
payload: Total number of points to read
|
|
67
|
+
EDGE_DEVICE_{IP_ID}/STATUS/DISCOVERED_POINT_COUNT
|
|
68
|
+
payload: Number of discovered points
|
|
69
|
+
EDGE_DEVICE_{IP_ID}/STATUS/DISCOVERED_DEVICE_COUNT
|
|
70
|
+
payload: Number of discovered devices
|
|
71
|
+
|
|
72
|
+
where {IP_ID} is the IP address of the device with periods removed (e.g., 192.168.1.100 becomes 192168110).
|
|
73
|
+
each of these topics includes the site name as a tag in the message metadata with format geoAddr={siteName}.
|
|
74
|
+
|
|
75
|
+
- Additional input options:
|
|
76
|
+
- reset - resets the complete data model used for all of the inspector analytics
|
|
77
|
+
- msg input format: msg.reset = true
|
|
78
|
+
|
|
79
|
+
- sendMqttStats - outputs additional mqtt statistics
|
|
80
|
+
- msg input format: msg.type = sendMqttStats
|
|
81
|
+
- output topics:
|
|
82
|
+
EDGE_DEVICE_{siteName}/BACNETSTATS/ok
|
|
83
|
+
payload: Number of points with OK status
|
|
84
|
+
EDGE_DEVICE_{siteName}/BACNETSTATS/error
|
|
85
|
+
payload: Number of points with error status
|
|
86
|
+
EDGE_DEVICE_{siteName}/BACNETSTATS/missing
|
|
87
|
+
payload: Number of missing points
|
|
88
|
+
EDGE_DEVICE_{siteName}/BACNETSTATS/warnings
|
|
89
|
+
payload: Number of points with warnings
|
|
90
|
+
EDGE_DEVICE_{siteName}/BACNETSTATS/moved
|
|
91
|
+
payload: Number of points that have moved (e.g changed object instance)
|
|
92
|
+
EDGE_DEVICE_{siteName}/BACNETSTATS/deviceIdChange
|
|
93
|
+
payload: Number of points with changed device IDs
|
|
94
|
+
EDGE_DEVICE_{siteName}/BACNETSTATS/deviceIdConflict
|
|
95
|
+
payload: Number of points with conflicting device IDs
|
|
96
|
+
EDGE_DEVICE_{siteName}/BACNETSTATS/unmapped
|
|
97
|
+
payload: Number of unmapped points
|
|
98
|
+
EDGE_DEVICE_{siteName}/BACNETSTATS/offlinePercentage
|
|
99
|
+
payload: Percentage of points that are offline
|
|
100
|
+
|
|
101
|
+
where {siteName} is the site name configured in the inspector node.
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
- Right click -> Update Point on a individual point in the device tree. (Read node UI)
|
|
105
|
+
|
|
106
|
+
- Added programmatic reinitialize/clear points on BACnet server via injecting { msg.reinitializeBacnetServer: true } into the gateway node. Optionally can include a msg.responseTopic string to get a confrimation published to that topic output from the gateway node on sucessfull reinitialize.
|
|
107
|
+
- Example workflow:
|
|
108
|
+
-inject into gateway node:
|
|
109
|
+
```javascript
|
|
110
|
+
msg = {
|
|
111
|
+
reinitializeBacnetServer: true,
|
|
112
|
+
responseTopic: "/mqtt/subscriber/topic",
|
|
113
|
+
};
|
|
114
|
+
```
|
|
115
|
+
-upon sucessfull reinitialize, gateway outputs:
|
|
116
|
+
```javascript
|
|
117
|
+
{
|
|
118
|
+
topic: "/mqtt/subscriber/topic";
|
|
119
|
+
payload: "Server successfully reinitialized";
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Refactor:
|
|
124
|
+
|
|
125
|
+
- Reading and Writing to the cache file of the datamodel.
|
|
126
|
+
- write operations are now locked to 1 operation at a time
|
|
127
|
+
- a rolling secondary backup file is now created, which can be used in case of corruption of the primary file
|
|
128
|
+
|
|
129
|
+
- Ported more of the project to async / await program flow vs Promise.then
|
|
130
|
+
|
|
131
|
+
Bug fixes:
|
|
132
|
+
|
|
133
|
+
- timeouts removed from import and export database file, as they can be very large.
|
|
134
|
+
|
|
135
|
+
## [1.5.3] - 23-01-2025
|
|
136
|
+
|
|
137
|
+
New feature:
|
|
138
|
+
|
|
139
|
+
- import / export buttons added to new tab in gateway node, used to manage the complete data model for backup or restore
|
|
140
|
+
- associated API end points for programatic backing up or restoring - /bitpool-bacnet-data/getDataModel; /bitpool-bacnet-data/updateDataModel;
|
|
141
|
+
|
|
142
|
+
Further async / await refactoring
|
|
143
|
+
|
|
144
|
+
Bug fixes:
|
|
145
|
+
|
|
146
|
+
- incorrect device name in read list export
|
|
147
|
+
- read command indexing unhandled scenario
|
|
148
|
+
- duplicating points in read list after pressing refresh tree button
|
|
149
|
+
- Multi State Values and other state text based points not being handled correctly in large volume scenarios
|
|
150
|
+
|
|
151
|
+
NOTE:
|
|
152
|
+
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.
|
|
153
|
+
|
|
3
154
|
## [1.5.2] - 10-01-2025
|
|
4
155
|
|
|
5
156
|
Mismatched network request hot fix
|
|
6
157
|
|
|
7
|
-
|
|
8
158
|
## [1.5.1] - 13-11-2024
|
|
9
159
|
|
|
10
160
|
### Summary
|
package/bacnet_client.js
CHANGED
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
|
|
5
5
|
const bacnet = require("./resources/node-bacstack-ts/dist/index.js");
|
|
6
6
|
const baEnum = bacnet.enum;
|
|
7
|
-
const bacnetIdMax = baEnum.ASN1_MAX_PROPERTY_ID;
|
|
8
7
|
const { EventEmitter } = require("events");
|
|
9
8
|
const {
|
|
10
9
|
getUnit,
|
|
11
10
|
roundDecimalPlaces,
|
|
12
11
|
parseBacnetError,
|
|
13
12
|
getBacnetErrorString,
|
|
13
|
+
Read_Config_Async,
|
|
14
14
|
Read_Config_Sync,
|
|
15
15
|
isNumber,
|
|
16
16
|
decodeBitArray,
|
|
@@ -41,20 +41,6 @@ class BacnetClient extends EventEmitter {
|
|
|
41
41
|
that.portRangeMatrix = config.portRangeMatrix;
|
|
42
42
|
|
|
43
43
|
try {
|
|
44
|
-
if (that.config.cacheFileEnabled) {
|
|
45
|
-
let cachedData = JSON.parse(Read_Config_Sync());
|
|
46
|
-
if (cachedData && typeof cachedData == "object") {
|
|
47
|
-
if (cachedData.renderList) that.renderList = cachedData.renderList;
|
|
48
|
-
if (cachedData.deviceList) {
|
|
49
|
-
cachedData.deviceList.forEach(function (device) {
|
|
50
|
-
let newBacnetDevice = new BacnetDevice(true, device);
|
|
51
|
-
that.deviceList.push(newBacnetDevice);
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
if (cachedData.pointList) that.networkTree = cachedData.pointList;
|
|
55
|
-
if (cachedData.renderListCount) that.renderListCount = cachedData.renderListCount;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
44
|
|
|
59
45
|
that.roundDecimal = config.roundDecimal;
|
|
60
46
|
that.apduSize = config.apduSize;
|
|
@@ -73,6 +59,8 @@ class BacnetClient extends EventEmitter {
|
|
|
73
59
|
};
|
|
74
60
|
|
|
75
61
|
try {
|
|
62
|
+
that.readCachedFile();
|
|
63
|
+
|
|
76
64
|
that.client = new bacnet.Client({
|
|
77
65
|
apduTimeout: config.apduTimeout,
|
|
78
66
|
interface: config.localIpAdrress,
|
|
@@ -115,22 +103,48 @@ class BacnetClient extends EventEmitter {
|
|
|
115
103
|
|
|
116
104
|
that.scheduler.addSimpleIntervalJob(buildNetworkTreeJob);
|
|
117
105
|
|
|
118
|
-
that.globalWhoIs();
|
|
119
|
-
|
|
120
106
|
setTimeout(() => {
|
|
121
|
-
that.
|
|
122
|
-
|
|
107
|
+
that.globalWhoIs();
|
|
108
|
+
setTimeout(() => {
|
|
109
|
+
that.queryDevices();
|
|
110
|
+
that.buildJsonTree();
|
|
111
|
+
}, "4000");
|
|
123
112
|
}, "5000");
|
|
113
|
+
|
|
124
114
|
} catch (e) {
|
|
125
115
|
that.logOut("Issue initializing client: ", e);
|
|
126
116
|
}
|
|
127
117
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
if (
|
|
118
|
+
try {
|
|
119
|
+
|
|
120
|
+
//who is callback
|
|
121
|
+
that.client.on("iAm", (device) => {
|
|
122
|
+
if (device.address !== that.config.localIpAdrress) {
|
|
123
|
+
if (that.scanMatrix.length > 0) {
|
|
124
|
+
let matrixMap = that.scanMatrix.filter((ele) => device.deviceId >= ele.start && device.deviceId <= ele.end);
|
|
125
|
+
if (matrixMap.length > 0) {
|
|
126
|
+
//only add unique device to array
|
|
127
|
+
let foundIndex = that.deviceList.findIndex((ele) => ele.getDeviceId() == device.deviceId);
|
|
128
|
+
if (foundIndex == -1) {
|
|
129
|
+
let newBacnetDevice = new BacnetDevice(false, device);
|
|
130
|
+
newBacnetDevice.setLastSeen(Date.now());
|
|
131
|
+
if (newBacnetDevice.getIsMstpDevice()) {
|
|
132
|
+
that.addToParentMstpNetwork(newBacnetDevice);
|
|
133
|
+
}
|
|
134
|
+
that.deviceList.push(newBacnetDevice);
|
|
135
|
+
that.addToNetworkTree(newBacnetDevice);
|
|
136
|
+
} else if (foundIndex !== -1) {
|
|
137
|
+
that.deviceList[foundIndex].updateDeviceConfig(device);
|
|
138
|
+
that.deviceList[foundIndex].setLastSeen(Date.now());
|
|
139
|
+
if (that.deviceList[foundIndex].getIsMstpDevice()) {
|
|
140
|
+
that.addToParentMstpNetwork(that.deviceList[foundIndex]);
|
|
141
|
+
}
|
|
142
|
+
that.addToNetworkTree(that.deviceList[foundIndex]);
|
|
143
|
+
}
|
|
144
|
+
//emit event for node-red to log
|
|
145
|
+
that.emit("deviceFound", device);
|
|
146
|
+
}
|
|
147
|
+
} else {
|
|
134
148
|
//only add unique device to array
|
|
135
149
|
let foundIndex = that.deviceList.findIndex((ele) => ele.getDeviceId() == device.deviceId);
|
|
136
150
|
if (foundIndex == -1) {
|
|
@@ -149,49 +163,49 @@ class BacnetClient extends EventEmitter {
|
|
|
149
163
|
}
|
|
150
164
|
that.addToNetworkTree(that.deviceList[foundIndex]);
|
|
151
165
|
}
|
|
166
|
+
|
|
152
167
|
//emit event for node-red to log
|
|
153
168
|
that.emit("deviceFound", device);
|
|
154
169
|
}
|
|
155
|
-
} else {
|
|
156
|
-
//only add unique device to array
|
|
157
|
-
let foundIndex = that.deviceList.findIndex((ele) => ele.getDeviceId() == device.deviceId);
|
|
158
|
-
if (foundIndex == -1) {
|
|
159
|
-
let newBacnetDevice = new BacnetDevice(false, device);
|
|
160
|
-
newBacnetDevice.setLastSeen(Date.now());
|
|
161
|
-
if (newBacnetDevice.getIsMstpDevice()) {
|
|
162
|
-
that.addToParentMstpNetwork(newBacnetDevice);
|
|
163
|
-
}
|
|
164
|
-
that.deviceList.push(newBacnetDevice);
|
|
165
|
-
that.addToNetworkTree(newBacnetDevice);
|
|
166
|
-
} else if (foundIndex !== -1) {
|
|
167
|
-
that.deviceList[foundIndex].updateDeviceConfig(device);
|
|
168
|
-
that.deviceList[foundIndex].setLastSeen(Date.now());
|
|
169
|
-
if (that.deviceList[foundIndex].getIsMstpDevice()) {
|
|
170
|
-
that.addToParentMstpNetwork(that.deviceList[foundIndex]);
|
|
171
|
-
}
|
|
172
|
-
that.addToNetworkTree(that.deviceList[foundIndex]);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
//emit event for node-red to log
|
|
176
|
-
that.emit("deviceFound", device);
|
|
177
170
|
}
|
|
171
|
+
});
|
|
172
|
+
} catch (e) {
|
|
173
|
+
that.logOut("Issue with creating bacnet client, see error: ", e);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
that.client.on("error", (err) => {
|
|
177
|
+
that.logOut("Error occurred: ", err);
|
|
178
|
+
|
|
179
|
+
if (err.errno == -4090) {
|
|
180
|
+
that.logOut("Invalid Client information or incorrect IP address provided");
|
|
181
|
+
} else if (err.errno == -49) {
|
|
182
|
+
that.logOut("Invalid IP address provided");
|
|
183
|
+
} else {
|
|
184
|
+
that.reinitializeClient(that.config);
|
|
178
185
|
}
|
|
179
186
|
});
|
|
180
187
|
} catch (e) {
|
|
181
|
-
|
|
188
|
+
console.log("BACnet Client client binder error: ", e);
|
|
182
189
|
}
|
|
190
|
+
}
|
|
183
191
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
that.
|
|
191
|
-
|
|
192
|
-
|
|
192
|
+
async readCachedFile() {
|
|
193
|
+
let that = this;
|
|
194
|
+
if (that.config.cacheFileEnabled) {
|
|
195
|
+
const cachedData = await Read_Config_Async();
|
|
196
|
+
const parsedData = JSON.parse(cachedData);
|
|
197
|
+
if (parsedData && typeof parsedData == "object") {
|
|
198
|
+
if (parsedData.renderList) that.renderList = parsedData.renderList;
|
|
199
|
+
if (parsedData.deviceList) {
|
|
200
|
+
parsedData.deviceList.forEach(function (device) {
|
|
201
|
+
let newBacnetDevice = new BacnetDevice(true, device);
|
|
202
|
+
that.deviceList.push(newBacnetDevice);
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
if (parsedData.pointList) that.networkTree = parsedData.pointList;
|
|
206
|
+
if (parsedData.renderListCount) that.renderListCount = parsedData.renderListCount;
|
|
193
207
|
}
|
|
194
|
-
}
|
|
208
|
+
}
|
|
195
209
|
}
|
|
196
210
|
|
|
197
211
|
testFunction(address, port, type, instance, property) {
|
|
@@ -534,7 +548,6 @@ class BacnetClient extends EventEmitter {
|
|
|
534
548
|
}
|
|
535
549
|
}
|
|
536
550
|
try {
|
|
537
|
-
|
|
538
551
|
await that.updateDeviceName(device);
|
|
539
552
|
|
|
540
553
|
if (device.getSegmentation() !== 3) {
|
|
@@ -579,23 +592,16 @@ class BacnetClient extends EventEmitter {
|
|
|
579
592
|
}
|
|
580
593
|
}
|
|
581
594
|
|
|
582
|
-
updateDeviceName(device) {
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
.
|
|
587
|
-
.
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
resolve();
|
|
593
|
-
})
|
|
594
|
-
.catch(function (e) {
|
|
595
|
-
that.logOut("updateDeviceName error: ", e);
|
|
596
|
-
resolve();
|
|
597
|
-
});
|
|
598
|
-
});
|
|
595
|
+
async updateDeviceName(device) {
|
|
596
|
+
try {
|
|
597
|
+
const deviceObject = await this._getDeviceName(device);
|
|
598
|
+
if (typeof deviceObject?.name === "string") {
|
|
599
|
+
device.setDeviceName(deviceObject.name + " " + device.getDeviceId());
|
|
600
|
+
device.setPointsList(deviceObject.devicePointEntry);
|
|
601
|
+
}
|
|
602
|
+
} catch (e) {
|
|
603
|
+
this.logOut("updateDeviceName error: ", e);
|
|
604
|
+
}
|
|
599
605
|
}
|
|
600
606
|
|
|
601
607
|
reinitializeClient(config) {
|
|
@@ -729,15 +735,15 @@ class BacnetClient extends EventEmitter {
|
|
|
729
735
|
// Process points for the current device
|
|
730
736
|
const pointsToRead = readConfig.pointsToRead[key];
|
|
731
737
|
const pointNames = Object.keys(pointsToRead);
|
|
732
|
-
let totalPoints = pointNames.length;
|
|
738
|
+
let totalPoints = pointNames.length - 1;
|
|
733
739
|
let requestArray = [];
|
|
734
740
|
let processedPoints = 0; // Counter for processed points
|
|
735
741
|
|
|
736
742
|
// Process each point for the device in batches
|
|
737
743
|
for (let i = 0; i < pointNames.length; i++) {
|
|
744
|
+
|
|
738
745
|
const pointName = pointNames[i];
|
|
739
746
|
if (pointName === "deviceName") {
|
|
740
|
-
totalPoints = totalPoints - 1;
|
|
741
747
|
continue;
|
|
742
748
|
}
|
|
743
749
|
|
|
@@ -758,8 +764,7 @@ class BacnetClient extends EventEmitter {
|
|
|
758
764
|
}
|
|
759
765
|
|
|
760
766
|
// Process the batch when the request array is full or the last point is reached
|
|
761
|
-
if (requestArray.length === maxObjectCount
|
|
762
|
-
|
|
767
|
+
if (requestArray.length === maxObjectCount) {
|
|
763
768
|
if (device.getProtocolServiceSupport("ReadPropertyMultiple") == true) {
|
|
764
769
|
await that.processBatch(device, requestArray, deviceName, bacnetResults, that, roundDecimal);
|
|
765
770
|
} else {
|
|
@@ -769,6 +774,16 @@ class BacnetClient extends EventEmitter {
|
|
|
769
774
|
requestArray = [];
|
|
770
775
|
// Increment the processed points counter
|
|
771
776
|
processedPoints += maxObjectCount;
|
|
777
|
+
} else if (i === pointNames.length - 1) {
|
|
778
|
+
if (device.getProtocolServiceSupport("ReadPropertyMultiple") == true) {
|
|
779
|
+
await that.processBatch(device, requestArray, deviceName, bacnetResults, that, roundDecimal);
|
|
780
|
+
} else {
|
|
781
|
+
await that.processIndividualPoints(device, requestArray, deviceName, bacnetResults, that, roundDecimal);
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
requestArray = [];
|
|
785
|
+
// Increment the processed points counter
|
|
786
|
+
processedPoints += i;
|
|
772
787
|
}
|
|
773
788
|
|
|
774
789
|
// Check if all points for the device have been processed
|
|
@@ -870,17 +885,23 @@ class BacnetClient extends EventEmitter {
|
|
|
870
885
|
const { objectId, pointRef, pointName } = request;
|
|
871
886
|
try {
|
|
872
887
|
|
|
873
|
-
|
|
874
888
|
const result = await that.updatePoint(device, pointRef);
|
|
875
889
|
|
|
876
|
-
//const result = await that.updatePointWithRetry(device, pointRef, 1);
|
|
877
|
-
|
|
878
|
-
|
|
879
890
|
if (result.objectId.type == objectId.type && result.objectId.instance == objectId.instance) {
|
|
880
891
|
const val = result.values[0].value;
|
|
881
892
|
|
|
882
893
|
if (isNumber(val)) {
|
|
883
894
|
pointRef.presentValue = roundDecimalPlaces(val, roundDecimal);
|
|
895
|
+
|
|
896
|
+
if (pointRef.meta.objectId.type == 19 || pointRef.meta.objectId.type == 13 || pointRef.meta.objectId.type == 14) {
|
|
897
|
+
if (pointRef.stateTextArray && typeof pointRef.stateTextArray[0].value !== "object") {
|
|
898
|
+
if (val != 0) {
|
|
899
|
+
pointRef.presentValue = pointRef.stateTextArray[val - 1].value;
|
|
900
|
+
} else {
|
|
901
|
+
pointRef.presentValue = pointRef.stateTextArray[val].value;
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
}
|
|
884
905
|
} else {
|
|
885
906
|
pointRef.presentValue = val;
|
|
886
907
|
}
|
|
@@ -905,24 +926,13 @@ class BacnetClient extends EventEmitter {
|
|
|
905
926
|
}
|
|
906
927
|
}
|
|
907
928
|
|
|
908
|
-
updateManyPoints(device, points) {
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
// };
|
|
916
|
-
|
|
917
|
-
that
|
|
918
|
-
._readObjectWithRequestArray(device, points, that.readPropertyMultipleOptions)
|
|
919
|
-
.then(function (results) {
|
|
920
|
-
resolve(results);
|
|
921
|
-
})
|
|
922
|
-
.catch(function (err) {
|
|
923
|
-
reject(err);
|
|
924
|
-
});
|
|
925
|
-
});
|
|
929
|
+
async updateManyPoints(device, points) {
|
|
930
|
+
try {
|
|
931
|
+
const results = await this._readObjectWithRequestArray(device, points, this.readPropertyMultipleOptions);
|
|
932
|
+
return results;
|
|
933
|
+
} catch (error) {
|
|
934
|
+
throw error;
|
|
935
|
+
}
|
|
926
936
|
}
|
|
927
937
|
|
|
928
938
|
updatePointWithRetry(device, point, retryCount = 1) {
|
|
@@ -941,18 +951,63 @@ class BacnetClient extends EventEmitter {
|
|
|
941
951
|
return tryUpdate(retryCount);
|
|
942
952
|
}
|
|
943
953
|
|
|
954
|
+
//used for manual point updates in the UI tree
|
|
955
|
+
async updateIndividualPoint(deviceKey, pointKey) {
|
|
956
|
+
let that = this;
|
|
957
|
+
try {
|
|
958
|
+
let device = that.deviceList.find((ele) => ele.getDeviceId() == deviceKey.split("-")[1]);
|
|
959
|
+
const pointType = parseInt(pointKey.split(":")[0]);
|
|
960
|
+
const pointInstance = parseInt(pointKey.split(":")[1]);
|
|
961
|
+
const promiseArray = [];
|
|
962
|
+
const result = await that._readObjectFull(device, pointType, pointInstance);
|
|
963
|
+
|
|
964
|
+
if (!result.error) {
|
|
965
|
+
device.setLastSeen(Date.now());
|
|
966
|
+
if (result.length > 0 && Array.isArray(result)) {
|
|
967
|
+
promiseArray.push(...result);
|
|
968
|
+
} else {
|
|
969
|
+
promiseArray.push(result);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
await that.buildNetworkModel(promiseArray, device);
|
|
974
|
+
|
|
975
|
+
return true;
|
|
976
|
+
} catch (e) {
|
|
977
|
+
throw e
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
//used in the doRead querying work flow
|
|
944
982
|
updatePoint(device, point) {
|
|
945
983
|
let that = this;
|
|
946
984
|
let addressObject = {
|
|
947
985
|
address: device.getAddress(),
|
|
948
986
|
port: device.getPort(),
|
|
949
987
|
};
|
|
988
|
+
|
|
989
|
+
let maxSegments = that.readPropertyMultipleOptions.maxSegments;
|
|
990
|
+
let maxApdu = that.readPropertyMultipleOptions.maxApdu;
|
|
991
|
+
|
|
992
|
+
if (device.getSegmentation() == 3) {
|
|
993
|
+
maxSegments = 0;
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
if (device.getMaxApdu() == 480) {
|
|
997
|
+
maxApdu = 3;
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
let settings = {
|
|
1001
|
+
maxSegments: maxSegments,
|
|
1002
|
+
maxApdu: maxApdu,
|
|
1003
|
+
}
|
|
1004
|
+
|
|
950
1005
|
return new Promise((resolve, reject) => {
|
|
951
1006
|
that.client.readProperty(
|
|
952
1007
|
addressObject,
|
|
953
1008
|
{ type: point.meta.objectId.type, instance: point.meta.objectId.instance },
|
|
954
1009
|
baEnum.PropertyIdentifier.PRESENT_VALUE,
|
|
955
|
-
|
|
1010
|
+
settings,
|
|
956
1011
|
(err, value) => {
|
|
957
1012
|
if (err) {
|
|
958
1013
|
reject(err);
|
|
@@ -1552,6 +1607,22 @@ class BacnetClient extends EventEmitter {
|
|
|
1552
1607
|
});
|
|
1553
1608
|
}
|
|
1554
1609
|
|
|
1610
|
+
getDataModel() {
|
|
1611
|
+
let that = this;
|
|
1612
|
+
return new Promise(async function (resolve, reject) {
|
|
1613
|
+
try {
|
|
1614
|
+
resolve({
|
|
1615
|
+
renderList: that.renderList,
|
|
1616
|
+
deviceList: that.deviceList,
|
|
1617
|
+
pointList: that.networkTree,
|
|
1618
|
+
renderListCount: that.renderListCount,
|
|
1619
|
+
});
|
|
1620
|
+
} catch (e) {
|
|
1621
|
+
reject(e);
|
|
1622
|
+
}
|
|
1623
|
+
});
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1555
1626
|
updatePointsList(json) {
|
|
1556
1627
|
let that = this;
|
|
1557
1628
|
json.deviceList.forEach(function (updatedDevice) {
|
|
@@ -1587,6 +1658,29 @@ class BacnetClient extends EventEmitter {
|
|
|
1587
1658
|
});
|
|
1588
1659
|
}
|
|
1589
1660
|
|
|
1661
|
+
updateDataModel(json) {
|
|
1662
|
+
let that = this;
|
|
1663
|
+
return new Promise(async function (resolve, reject) {
|
|
1664
|
+
try {
|
|
1665
|
+
if (json.body.renderList) {
|
|
1666
|
+
that.renderList = json.body.renderList;
|
|
1667
|
+
}
|
|
1668
|
+
if (json.body.deviceList) {
|
|
1669
|
+
await that.updateDeviceList(json);
|
|
1670
|
+
}
|
|
1671
|
+
if (json.body.pointList) {
|
|
1672
|
+
that.networkTree = json.body.pointList;
|
|
1673
|
+
}
|
|
1674
|
+
if (json.body.renderListCount) {
|
|
1675
|
+
that.renderListCount = json.body.renderListCount;
|
|
1676
|
+
}
|
|
1677
|
+
resolve();
|
|
1678
|
+
} catch (e) {
|
|
1679
|
+
reject(e);
|
|
1680
|
+
}
|
|
1681
|
+
});
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1590
1684
|
sortDevices(a, b) {
|
|
1591
1685
|
if (a.deviceId < b.deviceId) {
|
|
1592
1686
|
return -1;
|
|
@@ -1777,7 +1871,7 @@ class BacnetClient extends EventEmitter {
|
|
|
1777
1871
|
requestMutex.release();
|
|
1778
1872
|
}
|
|
1779
1873
|
|
|
1780
|
-
await this.
|
|
1874
|
+
await this.buildNetworkModel(promiseArray, device);
|
|
1781
1875
|
this.lastNetworkPoll = Date.now();
|
|
1782
1876
|
|
|
1783
1877
|
return { deviceList: this.deviceList, pointList: this.networkTree };
|
|
@@ -1787,8 +1881,8 @@ class BacnetClient extends EventEmitter {
|
|
|
1787
1881
|
}
|
|
1788
1882
|
}
|
|
1789
1883
|
|
|
1790
|
-
// Builds
|
|
1791
|
-
|
|
1884
|
+
// Builds the data rich bacnet network model
|
|
1885
|
+
buildNetworkModel(fullObjects, device) {
|
|
1792
1886
|
let that = this;
|
|
1793
1887
|
const reg = /[$#\/\\+,]/gi;
|
|
1794
1888
|
return new Promise(function (resolve, reject) {
|
|
@@ -1856,7 +1950,7 @@ class BacnetClient extends EventEmitter {
|
|
|
1856
1950
|
}
|
|
1857
1951
|
values[objectId].meta.arrayIndex = object.index;
|
|
1858
1952
|
} catch (e) {
|
|
1859
|
-
that.logOut("
|
|
1953
|
+
that.logOut("buildNetworkModel PRESENT_VALUE error: ", e);
|
|
1860
1954
|
}
|
|
1861
1955
|
break;
|
|
1862
1956
|
case baEnum.PropertyIdentifier.DESCRIPTION:
|
|
@@ -1922,7 +2016,7 @@ class BacnetClient extends EventEmitter {
|
|
|
1922
2016
|
}
|
|
1923
2017
|
}
|
|
1924
2018
|
} catch (e) {
|
|
1925
|
-
that.logOut("
|
|
2019
|
+
that.logOut("buildNetworkModel STATE_TEXT error: ", e);
|
|
1926
2020
|
}
|
|
1927
2021
|
break;
|
|
1928
2022
|
case baEnum.PropertyIdentifier.VENDOR_NAME:
|