@bitpoolos/edge-bacnet 1.5.3 → 1.6.1
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 +155 -10
- package/bacnet_client.js +123 -107
- package/bacnet_gateway.html +22 -6
- package/bacnet_gateway.js +346 -250
- package/bacnet_inspector.html +43 -0
- package/bacnet_inspector.js +1420 -0
- package/bacnet_inspector_worker.js +535 -0
- package/bacnet_read.html +27 -27
- package/bacnet_read.js +0 -3
- package/common.js +201 -38
- package/examples/1-Discover-Read.json +170 -1
- package/examples/2-Discover-Write.json +164 -1
- package/examples/3-Discover-Read-Write.json +227 -1
- package/examples/4-Inspector.json +368 -0
- package/inspector.html +455 -0
- package/package.json +6 -2
- package/resources/Logo_Simplified_Positive.svg +32 -0
- package/resources/downloadAsHtml.js +656 -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 +503 -0
- package/resources/primevue.min.js +1 -0
- package/resources/style.css +17 -1
- package/resources/vue3513.global.prod.js +9 -0
- package/ssrHtmlExporter.js +537 -0
- package/treeBuilder.js +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,27 +1,172 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.6.1] - 14-04-2025
|
|
4
|
+
|
|
5
|
+
Bug fixes:
|
|
6
|
+
- Inspector stats were calculating incorrectly in certain scenarios
|
|
7
|
+
- Inspector downloaded HTML files had incorrect stat percentages
|
|
8
|
+
- Inspector Last_Polled_Time stat was always "UNKNOWN", now shows correct date time.
|
|
9
|
+
|
|
10
|
+
Minor updates:
|
|
11
|
+
- Inspector BACnet main stats now output on msg.type = getBacnetStats inject. Can be set on a scheduled inject.
|
|
12
|
+
- Updated Examples with Inspector
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
## [1.6.0] - 09-04-2025
|
|
16
|
+
|
|
17
|
+
New features:
|
|
18
|
+
|
|
19
|
+
- New node - Inspector version 1:
|
|
20
|
+
|
|
21
|
+
- 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.
|
|
22
|
+
- 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.
|
|
23
|
+
- 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.
|
|
24
|
+
- The custom UI shows a data table with rich meta data, that can be filtered.
|
|
25
|
+
- The inspector node comes with a variety of API routes that can be used for analysis and engineering
|
|
26
|
+
- API routes available:
|
|
27
|
+
|
|
28
|
+
- /inspector
|
|
29
|
+
- view UI in web browser
|
|
30
|
+
|
|
31
|
+
- /inspector-downloadhtml
|
|
32
|
+
- downloads the html of the inspector in its current state
|
|
33
|
+
|
|
34
|
+
- /inspector-downloadhtml?filter=tableKey&value=value1,value2
|
|
35
|
+
- downloads the html of the inspector, but with a applied filter to the table.
|
|
36
|
+
- tableKey in the above example can be any of the table columns, only 1 tableKey may be filtered on:
|
|
37
|
+
deviceID, objectType, objectInstance, presentValue, dataModelStatus, pointName, discoveredBACnetPointName, displayName, deviceName, ipAddress, area, key, topic, lastSeen, error
|
|
38
|
+
- value=value1,value2 etc can be any value that the tableKey can contain. This parameter can accept many comma separated values
|
|
39
|
+
- an example filter request may look like:
|
|
40
|
+
/inspector-downloadhtml?filter=dataModelStatus&value=error,missing
|
|
41
|
+
|
|
42
|
+
- /getModelStats
|
|
43
|
+
- returns JSON data with analysis of the BACnet model
|
|
44
|
+
- contains point status, metrics, and detailed information
|
|
45
|
+
|
|
46
|
+
- /pointstoread
|
|
47
|
+
- downloads CSV file with all points in the read list
|
|
48
|
+
- format: [siteName]_PointsToRead_[timestamp].csv
|
|
49
|
+
|
|
50
|
+
- /getpointerrors
|
|
51
|
+
- downloads CSV file with all points that have errors
|
|
52
|
+
- format: [siteName]_PointErrors_[timestamp].csv
|
|
53
|
+
|
|
54
|
+
- /getmodelstatscsv
|
|
55
|
+
- downloads CSV file with all model stats data in CSV format
|
|
56
|
+
- format: [siteName]_ModelStats_[timestamp].csv
|
|
57
|
+
|
|
58
|
+
- /publishedpointslist
|
|
59
|
+
- downloads CSV file with all published points and their current values
|
|
60
|
+
- format: [siteName]_PublishedPointsList_[timestamp].csv
|
|
61
|
+
- outputs mqtt topic and payloads with statistics about the current state of the bacnet network
|
|
62
|
+
- output topics:
|
|
63
|
+
EDGE_DEVICE_{IP_ID}/STATUS/LAST_POINT_PUSHED_TIME
|
|
64
|
+
payload: Timestamp of when the last point was pushed (ISO string)
|
|
65
|
+
EDGE_DEVICE_{IP_ID}/STATUS/LAST_STAT_CALC_TIME
|
|
66
|
+
payload: Current timestamp (ISO string)
|
|
67
|
+
EDGE_DEVICE_{IP_ID}/STATUS/UPTIME
|
|
68
|
+
payload: System uptime formatted as string (e.g., "Uptime: 3 days, 5 hours, 12 minutes, 45 seconds")
|
|
69
|
+
EDGE_DEVICE_{IP_ID}/STATUS/ONLINE_POINTS
|
|
70
|
+
payload: Number of online points
|
|
71
|
+
EDGE_DEVICE_{IP_ID}/STATUS/OFFLINE_POINTS
|
|
72
|
+
payload: Number of offline points
|
|
73
|
+
EDGE_DEVICE_{IP_ID}/STATUS/TOTAL_POLLED_POINTS
|
|
74
|
+
payload: Total number of polled points
|
|
75
|
+
EDGE_DEVICE_{IP_ID}/STATUS/AVERAGE_TIME_SINCE_COV_IN_SECONDS
|
|
76
|
+
payload: Average time since last change of value in seconds
|
|
77
|
+
EDGE_DEVICE_{IP_ID}/STATUS/TOTAL_POINTS_TO_READ
|
|
78
|
+
payload: Total number of points to read
|
|
79
|
+
EDGE_DEVICE_{IP_ID}/STATUS/DISCOVERED_POINT_COUNT
|
|
80
|
+
payload: Number of discovered points
|
|
81
|
+
EDGE_DEVICE_{IP_ID}/STATUS/DISCOVERED_DEVICE_COUNT
|
|
82
|
+
payload: Number of discovered devices
|
|
83
|
+
|
|
84
|
+
where {IP_ID} is the IP address of the device with periods removed (e.g., 192.168.1.100 becomes 192168110).
|
|
85
|
+
each of these topics includes the site name as a tag in the message metadata with format geoAddr={siteName}.
|
|
86
|
+
|
|
87
|
+
- Additional input options:
|
|
88
|
+
- reset - resets the complete data model used for all of the inspector analytics
|
|
89
|
+
- msg input format: msg.reset = true
|
|
90
|
+
|
|
91
|
+
- sendMqttStats - outputs additional mqtt statistics
|
|
92
|
+
- msg input format: msg.type = sendMqttStats
|
|
93
|
+
- output topics:
|
|
94
|
+
EDGE_DEVICE_{siteName}/BACNETSTATS/ok
|
|
95
|
+
payload: Number of points with OK status
|
|
96
|
+
EDGE_DEVICE_{siteName}/BACNETSTATS/error
|
|
97
|
+
payload: Number of points with error status
|
|
98
|
+
EDGE_DEVICE_{siteName}/BACNETSTATS/missing
|
|
99
|
+
payload: Number of missing points
|
|
100
|
+
EDGE_DEVICE_{siteName}/BACNETSTATS/warnings
|
|
101
|
+
payload: Number of points with warnings
|
|
102
|
+
EDGE_DEVICE_{siteName}/BACNETSTATS/moved
|
|
103
|
+
payload: Number of points that have moved (e.g changed object instance)
|
|
104
|
+
EDGE_DEVICE_{siteName}/BACNETSTATS/deviceIdChange
|
|
105
|
+
payload: Number of points with changed device IDs
|
|
106
|
+
EDGE_DEVICE_{siteName}/BACNETSTATS/deviceIdConflict
|
|
107
|
+
payload: Number of points with conflicting device IDs
|
|
108
|
+
EDGE_DEVICE_{siteName}/BACNETSTATS/unmapped
|
|
109
|
+
payload: Number of unmapped points
|
|
110
|
+
EDGE_DEVICE_{siteName}/BACNETSTATS/offlinePercentage
|
|
111
|
+
payload: Percentage of points that are offline
|
|
112
|
+
|
|
113
|
+
where {siteName} is the site name configured in the inspector node.
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
- Right click -> Update Point on a individual point in the device tree. (Read node UI)
|
|
117
|
+
|
|
118
|
+
- 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.
|
|
119
|
+
- Example workflow:
|
|
120
|
+
-inject into gateway node:
|
|
121
|
+
```javascript
|
|
122
|
+
msg = {
|
|
123
|
+
reinitializeBacnetServer: true,
|
|
124
|
+
responseTopic: "/mqtt/subscriber/topic",
|
|
125
|
+
};
|
|
126
|
+
```
|
|
127
|
+
-upon sucessfull reinitialize, gateway outputs:
|
|
128
|
+
```javascript
|
|
129
|
+
{
|
|
130
|
+
topic: "/mqtt/subscriber/topic";
|
|
131
|
+
payload: "Server successfully reinitialized";
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Refactor:
|
|
136
|
+
|
|
137
|
+
- Reading and Writing to the cache file of the datamodel.
|
|
138
|
+
- write operations are now locked to 1 operation at a time
|
|
139
|
+
- a rolling secondary backup file is now created, which can be used in case of corruption of the primary file
|
|
140
|
+
|
|
141
|
+
- Ported more of the project to async / await program flow vs Promise.then
|
|
142
|
+
|
|
143
|
+
Bug fixes:
|
|
144
|
+
|
|
145
|
+
- timeouts removed from import and export database file, as they can be very large.
|
|
146
|
+
|
|
3
147
|
## [1.5.3] - 23-01-2025
|
|
4
148
|
|
|
5
|
-
New feature:
|
|
6
|
-
|
|
7
|
-
|
|
149
|
+
New feature:
|
|
150
|
+
|
|
151
|
+
- import / export buttons added to new tab in gateway node, used to manage the complete data model for backup or restore
|
|
152
|
+
- associated API end points for programatic backing up or restoring - /bitpool-bacnet-data/getDataModel; /bitpool-bacnet-data/updateDataModel;
|
|
8
153
|
|
|
9
154
|
Further async / await refactoring
|
|
10
155
|
|
|
11
156
|
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
157
|
|
|
17
|
-
|
|
18
|
-
|
|
158
|
+
- incorrect device name in read list export
|
|
159
|
+
- read command indexing unhandled scenario
|
|
160
|
+
- duplicating points in read list after pressing refresh tree button
|
|
161
|
+
- Multi State Values and other state text based points not being handled correctly in large volume scenarios
|
|
162
|
+
|
|
163
|
+
NOTE:
|
|
164
|
+
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
165
|
|
|
20
166
|
## [1.5.2] - 10-01-2025
|
|
21
167
|
|
|
22
168
|
Mismatched network request hot fix
|
|
23
169
|
|
|
24
|
-
|
|
25
170
|
## [1.5.1] - 13-11-2024
|
|
26
171
|
|
|
27
172
|
### 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) {
|
|
@@ -578,25 +592,6 @@ class BacnetClient extends EventEmitter {
|
|
|
578
592
|
}
|
|
579
593
|
}
|
|
580
594
|
|
|
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
595
|
async updateDeviceName(device) {
|
|
601
596
|
try {
|
|
602
597
|
const deviceObject = await this._getDeviceName(device);
|
|
@@ -931,32 +926,8 @@ class BacnetClient extends EventEmitter {
|
|
|
931
926
|
}
|
|
932
927
|
}
|
|
933
928
|
|
|
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
929
|
async updateManyPoints(device, points) {
|
|
954
930
|
try {
|
|
955
|
-
// let readOptions = {
|
|
956
|
-
// maxSegments: device.getMaxSe,
|
|
957
|
-
// maxApdu: that.readPropertyMultipleOptions.maxApdu,
|
|
958
|
-
// };
|
|
959
|
-
|
|
960
931
|
const results = await this._readObjectWithRequestArray(device, points, this.readPropertyMultipleOptions);
|
|
961
932
|
return results;
|
|
962
933
|
} catch (error) {
|
|
@@ -980,18 +951,63 @@ class BacnetClient extends EventEmitter {
|
|
|
980
951
|
return tryUpdate(retryCount);
|
|
981
952
|
}
|
|
982
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
|
|
983
982
|
updatePoint(device, point) {
|
|
984
983
|
let that = this;
|
|
985
984
|
let addressObject = {
|
|
986
985
|
address: device.getAddress(),
|
|
987
986
|
port: device.getPort(),
|
|
988
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
|
+
|
|
989
1005
|
return new Promise((resolve, reject) => {
|
|
990
1006
|
that.client.readProperty(
|
|
991
1007
|
addressObject,
|
|
992
1008
|
{ type: point.meta.objectId.type, instance: point.meta.objectId.instance },
|
|
993
1009
|
baEnum.PropertyIdentifier.PRESENT_VALUE,
|
|
994
|
-
|
|
1010
|
+
settings,
|
|
995
1011
|
(err, value) => {
|
|
996
1012
|
if (err) {
|
|
997
1013
|
reject(err);
|
|
@@ -1855,7 +1871,7 @@ class BacnetClient extends EventEmitter {
|
|
|
1855
1871
|
requestMutex.release();
|
|
1856
1872
|
}
|
|
1857
1873
|
|
|
1858
|
-
await this.
|
|
1874
|
+
await this.buildNetworkModel(promiseArray, device);
|
|
1859
1875
|
this.lastNetworkPoll = Date.now();
|
|
1860
1876
|
|
|
1861
1877
|
return { deviceList: this.deviceList, pointList: this.networkTree };
|
|
@@ -1865,8 +1881,8 @@ class BacnetClient extends EventEmitter {
|
|
|
1865
1881
|
}
|
|
1866
1882
|
}
|
|
1867
1883
|
|
|
1868
|
-
// Builds
|
|
1869
|
-
|
|
1884
|
+
// Builds the data rich bacnet network model
|
|
1885
|
+
buildNetworkModel(fullObjects, device) {
|
|
1870
1886
|
let that = this;
|
|
1871
1887
|
const reg = /[$#\/\\+,]/gi;
|
|
1872
1888
|
return new Promise(function (resolve, reject) {
|
|
@@ -1934,7 +1950,7 @@ class BacnetClient extends EventEmitter {
|
|
|
1934
1950
|
}
|
|
1935
1951
|
values[objectId].meta.arrayIndex = object.index;
|
|
1936
1952
|
} catch (e) {
|
|
1937
|
-
that.logOut("
|
|
1953
|
+
that.logOut("buildNetworkModel PRESENT_VALUE error: ", e);
|
|
1938
1954
|
}
|
|
1939
1955
|
break;
|
|
1940
1956
|
case baEnum.PropertyIdentifier.DESCRIPTION:
|
|
@@ -2000,7 +2016,7 @@ class BacnetClient extends EventEmitter {
|
|
|
2000
2016
|
}
|
|
2001
2017
|
}
|
|
2002
2018
|
} catch (e) {
|
|
2003
|
-
that.logOut("
|
|
2019
|
+
that.logOut("buildNetworkModel STATE_TEXT error: ", e);
|
|
2004
2020
|
}
|
|
2005
2021
|
break;
|
|
2006
2022
|
case baEnum.PropertyIdentifier.VENDOR_NAME:
|
package/bacnet_gateway.html
CHANGED
|
@@ -106,6 +106,16 @@
|
|
|
106
106
|
body: JSON.stringify({ d: device }),
|
|
107
107
|
}).then((res) => res.json());
|
|
108
108
|
}
|
|
109
|
+
updatePoint(device, pointKey) {
|
|
110
|
+
return fetch(RED.settings.httpNodeRoot + "bitpool-bacnet-data/updatePoint", {
|
|
111
|
+
method: "POST",
|
|
112
|
+
headers: {
|
|
113
|
+
Accept: "application/json",
|
|
114
|
+
"Content-Type": "application/json",
|
|
115
|
+
},
|
|
116
|
+
body: JSON.stringify({ d: device, k: pointKey }),
|
|
117
|
+
}).then((res) => res.json());
|
|
118
|
+
}
|
|
109
119
|
setDeviceDisplayName(device, displayName) {
|
|
110
120
|
return fetch(RED.settings.httpNodeRoot + "bitpool-bacnet-data/setDeviceDisplayName", {
|
|
111
121
|
method: "POST",
|
|
@@ -143,6 +153,7 @@
|
|
|
143
153
|
defaults: {
|
|
144
154
|
name: { value: "" },
|
|
145
155
|
local_device_address: { value: "", required: true },
|
|
156
|
+
local_interface_name: { value: "", required: false },
|
|
146
157
|
apduTimeout: { value: 6000 },
|
|
147
158
|
roundDecimal: { value: 2 },
|
|
148
159
|
local_device_port: { value: 47808, required: true },
|
|
@@ -241,6 +252,13 @@
|
|
|
241
252
|
});
|
|
242
253
|
}
|
|
243
254
|
|
|
255
|
+
//sets name of network adapter
|
|
256
|
+
document.getElementById("node-input-local_device_address").addEventListener("change", function () {
|
|
257
|
+
let selectedText = this.options[this.selectedIndex].innerText;
|
|
258
|
+
let nicName = selectedText.split(":")[0];
|
|
259
|
+
node.local_interface_name = nicName;
|
|
260
|
+
});
|
|
261
|
+
|
|
244
262
|
queryAdapters();
|
|
245
263
|
|
|
246
264
|
window.confirmDialogExists = window.confirmDialogExists || false;
|
|
@@ -338,7 +356,6 @@
|
|
|
338
356
|
vueapp.use(primevue.confirmationservice);
|
|
339
357
|
node.vm1 = vueapp.mount("#serverParent");
|
|
340
358
|
|
|
341
|
-
|
|
342
359
|
// Import Device List
|
|
343
360
|
$("#file-upload").on("change", function (event) {
|
|
344
361
|
const input = event.target.files[0];
|
|
@@ -356,7 +373,7 @@
|
|
|
356
373
|
contentType: "application/json",
|
|
357
374
|
data: JSON.stringify(jsonPayload),
|
|
358
375
|
success: function (result) { },
|
|
359
|
-
timeout:
|
|
376
|
+
timeout: 0,
|
|
360
377
|
});
|
|
361
378
|
};
|
|
362
379
|
|
|
@@ -374,7 +391,7 @@
|
|
|
374
391
|
aEle.setAttribute("download", "bitpool-bacnet-devices.json");
|
|
375
392
|
aEle.click();
|
|
376
393
|
},
|
|
377
|
-
timeout:
|
|
394
|
+
timeout: 0,
|
|
378
395
|
});
|
|
379
396
|
});
|
|
380
397
|
|
|
@@ -395,7 +412,7 @@
|
|
|
395
412
|
contentType: "application/json",
|
|
396
413
|
data: JSON.stringify(jsonPayload),
|
|
397
414
|
success: function (result) { },
|
|
398
|
-
timeout:
|
|
415
|
+
timeout: 0,
|
|
399
416
|
});
|
|
400
417
|
};
|
|
401
418
|
|
|
@@ -413,7 +430,7 @@
|
|
|
413
430
|
aEle.setAttribute("download", "edge-bacnet-datastore.json");
|
|
414
431
|
aEle.click();
|
|
415
432
|
},
|
|
416
|
-
timeout:
|
|
433
|
+
timeout: 0,
|
|
417
434
|
});
|
|
418
435
|
});
|
|
419
436
|
|
|
@@ -796,7 +813,6 @@
|
|
|
796
813
|
font-weight: bold;
|
|
797
814
|
font-size: 16px;
|
|
798
815
|
width: auto !important;
|
|
799
|
-
|
|
800
816
|
}
|
|
801
817
|
.database-file-label-div {
|
|
802
818
|
padding-top: 15px;
|