@bitpoolos/edge-bacnet 1.6.0 → 1.6.2
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 +35 -0
- package/bacnet_client.js +14 -7
- package/bacnet_gateway.html +7 -0
- package/bacnet_gateway.js +5 -3
- package/bacnet_inspector.js +13 -157
- package/bacnet_inspector_worker.js +3 -3
- package/common.js +7 -5
- 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 +29 -21
- package/package.json +1 -1
- package/resources/downloadAsHtml.js +11 -9
- package/resources/inspectorStyles.css +58 -21
- package/ssrHtmlExporter.js +11 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,40 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.6.2] - 07-05-2025
|
|
4
|
+
|
|
5
|
+
Minor feature:
|
|
6
|
+
- Added "Enable device discovery" check box to gateway settings, discovery tab.
|
|
7
|
+
- This check box controls whether on not the auto point discovery and property discovery is enabled.
|
|
8
|
+
- This can be used to turn off unecessary network traffic once you have discovered all of the desired devices and points.
|
|
9
|
+
- IMPORTANT - if you are updating a existing deployment, the new property will be unticked, however new installs / dragging from the pallete will by default have the option checked. So if you want to keep this enabled on an existing deployment, please check the setting.
|
|
10
|
+
- Note - This does not turn off the whoIs task schedule.
|
|
11
|
+
- A user can use Right Click -> Update points on desired deviced in the read node tree if you would like to manually discover devices.
|
|
12
|
+
|
|
13
|
+
Minor update:
|
|
14
|
+
- Adjusted initial whoIs broadcast delay from 5seconds to 15seconds after node-red is started with a deployed gateway. This is to ensure large cached files are completely read and loaded.
|
|
15
|
+
|
|
16
|
+
Bug fixes:
|
|
17
|
+
- Inspector:
|
|
18
|
+
- Table resized to avoid scroll bars
|
|
19
|
+
- Percentage rounding to nearest 2 decimal places rather than whole integer for more detailed data.
|
|
20
|
+
- Loading animation added
|
|
21
|
+
- NAN years ago - removed as invalid time differential
|
|
22
|
+
- Last seen for device points adjusted to be more accurate
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
## [1.6.1] - 14-04-2025
|
|
27
|
+
|
|
28
|
+
Bug fixes:
|
|
29
|
+
- Inspector stats were calculating incorrectly in certain scenarios
|
|
30
|
+
- Inspector downloaded HTML files had incorrect stat percentages
|
|
31
|
+
- Inspector Last_Polled_Time stat was always "UNKNOWN", now shows correct date time.
|
|
32
|
+
|
|
33
|
+
Minor updates:
|
|
34
|
+
- Inspector BACnet main stats now output on msg.type = getBacnetStats inject. Can be set on a scheduled inject.
|
|
35
|
+
- Updated Examples with Inspector
|
|
36
|
+
|
|
37
|
+
|
|
3
38
|
## [1.6.0] - 09-04-2025
|
|
4
39
|
|
|
5
40
|
New features:
|
package/bacnet_client.js
CHANGED
|
@@ -52,6 +52,7 @@ class BacnetClient extends EventEmitter {
|
|
|
52
52
|
that.deviceRetryCount = parseInt(config.retries);
|
|
53
53
|
that.sanitise_device_schedule = config.sanitise_device_schedule;
|
|
54
54
|
that.buildTreeException = false;
|
|
55
|
+
that.enable_device_discovery = config.enable_device_discovery;
|
|
55
56
|
|
|
56
57
|
that.readPropertyMultipleOptions = {
|
|
57
58
|
maxSegments: 112,
|
|
@@ -80,11 +81,11 @@ class BacnetClient extends EventEmitter {
|
|
|
80
81
|
|
|
81
82
|
//query device task
|
|
82
83
|
const queryDevices = new Task("simple task", () => {
|
|
83
|
-
if (!that.pollInProgress) {
|
|
84
|
+
if (!that.pollInProgress && that.enable_device_discovery) {
|
|
84
85
|
that.queryDevices();
|
|
85
86
|
}
|
|
86
87
|
|
|
87
|
-
if (!that.buildJsonInProgress) {
|
|
88
|
+
if (!that.buildJsonInProgress && that.enable_device_discovery) {
|
|
88
89
|
that.buildJsonTree();
|
|
89
90
|
}
|
|
90
91
|
});
|
|
@@ -106,10 +107,15 @@ class BacnetClient extends EventEmitter {
|
|
|
106
107
|
setTimeout(() => {
|
|
107
108
|
that.globalWhoIs();
|
|
108
109
|
setTimeout(() => {
|
|
109
|
-
that.
|
|
110
|
-
|
|
110
|
+
if (!that.pollInProgress && that.enable_device_discovery) {
|
|
111
|
+
that.queryDevices();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (!that.buildJsonInProgress && that.enable_device_discovery) {
|
|
115
|
+
that.buildJsonTree();
|
|
116
|
+
}
|
|
111
117
|
}, "4000");
|
|
112
|
-
}, "
|
|
118
|
+
}, "15000");
|
|
113
119
|
|
|
114
120
|
} catch (e) {
|
|
115
121
|
that.logOut("Issue initializing client: ", e);
|
|
@@ -615,6 +621,7 @@ class BacnetClient extends EventEmitter {
|
|
|
615
621
|
that.deviceId = config.deviceId;
|
|
616
622
|
that.broadCastAddr = config.broadCastAddr;
|
|
617
623
|
that.device_read_schedule = config.device_read_schedule;
|
|
624
|
+
that.enable_device_discovery = config.enable_device_discovery;
|
|
618
625
|
|
|
619
626
|
if (that.scheduler !== null) {
|
|
620
627
|
that.scheduler.stop();
|
|
@@ -640,11 +647,11 @@ class BacnetClient extends EventEmitter {
|
|
|
640
647
|
|
|
641
648
|
// //query device task
|
|
642
649
|
const queryDevices = new Task("simple task", () => {
|
|
643
|
-
if (!that.pollInProgress) {
|
|
650
|
+
if (!that.pollInProgress && that.enable_device_discovery) {
|
|
644
651
|
that.queryDevices();
|
|
645
652
|
}
|
|
646
653
|
|
|
647
|
-
if (!that.buildJsonInProgress) {
|
|
654
|
+
if (!that.buildJsonInProgress && that.enable_device_discovery) {
|
|
648
655
|
that.buildJsonTree();
|
|
649
656
|
}
|
|
650
657
|
});
|
package/bacnet_gateway.html
CHANGED
|
@@ -177,6 +177,7 @@
|
|
|
177
177
|
sanitise_device_schedule: { value: "60", required: false },
|
|
178
178
|
sanitise_device_schedule_value: { value: "1", required: false },
|
|
179
179
|
sanitise_device_schedule_options: { value: "Hours", required: false },
|
|
180
|
+
enable_device_discovery: { value: true, required: true },
|
|
180
181
|
},
|
|
181
182
|
networkInterfaces: [],
|
|
182
183
|
inputs: 1,
|
|
@@ -985,6 +986,11 @@
|
|
|
985
986
|
</select>
|
|
986
987
|
</div>
|
|
987
988
|
|
|
989
|
+
<div class="form-row bp-checkbox-row">
|
|
990
|
+
<input type="checkbox" id="node-input-enable_device_discovery" class="bp-checkbox" />
|
|
991
|
+
<label for="node-input-enable_device_discovery"> Enable device discovery </label>
|
|
992
|
+
</div>
|
|
993
|
+
|
|
988
994
|
<div class="form-row bp-checkbox-row">
|
|
989
995
|
<input type="checkbox" id="node-input-cacheFileEnabled" class="bp-checkbox" />
|
|
990
996
|
<label for="node-input-cacheFileEnabled"> Enable cache file </label>
|
|
@@ -1100,6 +1106,7 @@
|
|
|
1100
1106
|
the this bacnet client will enter into manual discovery mode, where it iterates through types and instnace ranges. This
|
|
1101
1107
|
range can be used to limit this manual scanning
|
|
1102
1108
|
</li>
|
|
1109
|
+
<li>Enable device discovery - toggles whether automatic device discovery, object scanning, and tree building is enabled.</li>
|
|
1103
1110
|
<li>Log Device Found - toggles logging of found devices to the node-red debug tab.</li>
|
|
1104
1111
|
<li>Log BACnet Errors to Console - toggles logging of BACnet related errors to the node-red console</li>
|
|
1105
1112
|
</ul>
|
package/bacnet_gateway.js
CHANGED
|
@@ -37,6 +37,7 @@ module.exports = function (RED) {
|
|
|
37
37
|
this.portRangeRegisters = config.portRangeRegisters;
|
|
38
38
|
this.cacheFileEnabled = config.cacheFileEnabled;
|
|
39
39
|
this.sanitise_device_schedule = config.sanitise_device_schedule;
|
|
40
|
+
this.enable_device_discovery = config.enable_device_discovery;
|
|
40
41
|
|
|
41
42
|
//client and config store
|
|
42
43
|
this.bacnetConfig = nodeContext.get("bacnetConfig");
|
|
@@ -65,7 +66,8 @@ module.exports = function (RED) {
|
|
|
65
66
|
node.retries,
|
|
66
67
|
node.cacheFileEnabled,
|
|
67
68
|
node.sanitise_device_schedule,
|
|
68
|
-
node.portRangeRegisters.filter((ele) => ele.enabled === true)
|
|
69
|
+
node.portRangeRegisters.filter((ele) => ele.enabled === true),
|
|
70
|
+
node.enable_device_discovery
|
|
69
71
|
);
|
|
70
72
|
|
|
71
73
|
if (typeof node.bacnetClient !== "undefined") {
|
|
@@ -263,7 +265,7 @@ module.exports = function (RED) {
|
|
|
263
265
|
} else if (msg.doUpdatePriorityDevices == true && msg.priorityDevices !== null) {
|
|
264
266
|
node.bacnetClient
|
|
265
267
|
.updatePriorityQueue(msg.priorityDevices)
|
|
266
|
-
.then(function (result) {})
|
|
268
|
+
.then(function (result) { })
|
|
267
269
|
.catch(function (error) {
|
|
268
270
|
logOut("Error updating priorityQueue: ", error);
|
|
269
271
|
});
|
|
@@ -277,7 +279,7 @@ module.exports = function (RED) {
|
|
|
277
279
|
|
|
278
280
|
node.bacnetClient
|
|
279
281
|
.applyDisplayNames(msg.pointsToRead)
|
|
280
|
-
.then(function (result) {})
|
|
282
|
+
.then(function (result) { })
|
|
281
283
|
.catch(function (error) {
|
|
282
284
|
logOut("Error in applyDisplayNames: ", error);
|
|
283
285
|
});
|
package/bacnet_inspector.js
CHANGED
|
@@ -115,10 +115,6 @@ module.exports = function (RED) {
|
|
|
115
115
|
this.batchInterval = setInterval(processBatch, BATCH_PROCESS_INTERVAL);
|
|
116
116
|
this.syncInterval = setInterval(syncWithFlowContext, SYNC_INTERVAL);
|
|
117
117
|
|
|
118
|
-
const debouncedGetBacnetStats = debounce((node, context, msg) => {
|
|
119
|
-
getModelStats();
|
|
120
|
-
}, 3000);
|
|
121
|
-
|
|
122
118
|
// Function to sync cache to flow context
|
|
123
119
|
function syncWithFlowContext() {
|
|
124
120
|
const flow = context.flow;
|
|
@@ -187,7 +183,7 @@ module.exports = function (RED) {
|
|
|
187
183
|
|
|
188
184
|
node.on("input", function (msg, send, done) {
|
|
189
185
|
if (msg.type === "getBacnetStats") {
|
|
190
|
-
|
|
186
|
+
getPublishedPointsList();
|
|
191
187
|
} else if (msg.type === "Read") {
|
|
192
188
|
calculateCombinedReadList(node, msg);
|
|
193
189
|
if (done) done();
|
|
@@ -197,7 +193,6 @@ module.exports = function (RED) {
|
|
|
197
193
|
if (done) done();
|
|
198
194
|
} else if (msg.payload && msg.topic) {
|
|
199
195
|
//regular bacnet output
|
|
200
|
-
debouncedGetBacnetStats(node, context, msg);
|
|
201
196
|
// Queue the message for batch processing instead of immediate processing
|
|
202
197
|
messageQueue.push({ msg, send, done });
|
|
203
198
|
if (messageQueue.length >= MAX_BATCH_SIZE) {
|
|
@@ -218,6 +213,7 @@ module.exports = function (RED) {
|
|
|
218
213
|
}
|
|
219
214
|
if (done) done();
|
|
220
215
|
});
|
|
216
|
+
|
|
221
217
|
} else if (msg.reset === true) {
|
|
222
218
|
node.status({ text: "Resetting..." });
|
|
223
219
|
|
|
@@ -249,17 +245,6 @@ module.exports = function (RED) {
|
|
|
249
245
|
}
|
|
250
246
|
});
|
|
251
247
|
|
|
252
|
-
// Process individual messages that shouldn't be batched
|
|
253
|
-
function processMessage(msg, send, done) {
|
|
254
|
-
try {
|
|
255
|
-
getBacnetStats(node, context, msg);
|
|
256
|
-
if (done) done();
|
|
257
|
-
} catch (error) {
|
|
258
|
-
console.error("Error processing message:", error);
|
|
259
|
-
if (done) done(error);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
248
|
// Process a batch of messages efficiently
|
|
264
249
|
function processBatch() {
|
|
265
250
|
if (messageQueue.length === 0) return;
|
|
@@ -293,6 +278,10 @@ module.exports = function (RED) {
|
|
|
293
278
|
|
|
294
279
|
// Use cached data instead of flow context
|
|
295
280
|
const now = new Date();
|
|
281
|
+
const flow = context.flow;
|
|
282
|
+
|
|
283
|
+
// Set the last point pushed time
|
|
284
|
+
flow.set("Last_Point_Pushed_Time", now.toISOString());
|
|
296
285
|
|
|
297
286
|
// Process all messages in the batch
|
|
298
287
|
messages.forEach((msg) => {
|
|
@@ -449,6 +438,13 @@ module.exports = function (RED) {
|
|
|
449
438
|
|
|
450
439
|
// Update the node status
|
|
451
440
|
node.status({ text: "Points Online: " + cachedData.onlineCount + "/" + cachedData.totalUniquePolledCount });
|
|
441
|
+
|
|
442
|
+
// Periodically call getModelStats to keep model stats updated
|
|
443
|
+
// Use a debounce pattern to avoid calling it too frequently
|
|
444
|
+
if (!processBatchData.lastModelStatsUpdate || (now - processBatchData.lastModelStatsUpdate) > 3000) {
|
|
445
|
+
getModelStats();
|
|
446
|
+
processBatchData.lastModelStatsUpdate = now;
|
|
447
|
+
}
|
|
452
448
|
}
|
|
453
449
|
|
|
454
450
|
//API Request Handlers Start
|
|
@@ -925,146 +921,6 @@ module.exports = function (RED) {
|
|
|
925
921
|
return msg;
|
|
926
922
|
}
|
|
927
923
|
|
|
928
|
-
//calculates bacnet stats on correctly injected msg to inspector node
|
|
929
|
-
//fired on every valid bacnet output
|
|
930
|
-
function getBacnetStats(node, context, msg) {
|
|
931
|
-
let flow = context.flow;
|
|
932
|
-
let now = new Date(); // Create a new Date object
|
|
933
|
-
|
|
934
|
-
// Initialize or retrieve the current list of unique topics and the count from the context context
|
|
935
|
-
let uniqueTopics = flow.get("uniqueReadTopics") || [];
|
|
936
|
-
let topicStatusMap = flow.get("topicStatusMap") || {}; // Store status per topic
|
|
937
|
-
let topicDataMap = flow.get("topicDataMap") || {}; // Store presentValue, timestamp, and last value change time per topic
|
|
938
|
-
let totalUniqueCount = flow.get("totalUniquePolledCount") || 0;
|
|
939
|
-
let onlineCount = flow.get("onlineCount") || 0;
|
|
940
|
-
let offlineCount = flow.get("offlineCount") || 0;
|
|
941
|
-
|
|
942
|
-
flow.set("site_Name", node.siteName);
|
|
943
|
-
|
|
944
|
-
// Calculate the IP address of the edge device
|
|
945
|
-
var IP_Add = getIPAddresses(); // Get the IP address of the current device
|
|
946
|
-
var IP_Str = "NA";
|
|
947
|
-
if (IP_Add[0]) {
|
|
948
|
-
IP_Str = IP_Add[0].replace(/\./g, "");
|
|
949
|
-
} // If an IP address of the first network adapter exists remove the full stops
|
|
950
|
-
|
|
951
|
-
// Get the current message's topic, status, presentValue, and timestamp
|
|
952
|
-
let topic = msg.topic;
|
|
953
|
-
let status = msg.payload.status;
|
|
954
|
-
let error = msg.payload.error;
|
|
955
|
-
let presentValue = msg.payload.presentValue;
|
|
956
|
-
let timestamp = msg.payload.timestamp;
|
|
957
|
-
|
|
958
|
-
// Extract properties only if they exist
|
|
959
|
-
let deviceID = msg.payload.meta?.device?.deviceId;
|
|
960
|
-
let objectType = msg.payload.meta?.objectId?.type;
|
|
961
|
-
let objectInstance = msg.payload.meta?.objectId?.instance;
|
|
962
|
-
let pointName = msg.payload?.objectName;
|
|
963
|
-
|
|
964
|
-
if (pointName !== undefined) {
|
|
965
|
-
pointName = pointName + "_" + getObjectType(objectType) + "_" + objectInstance;
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
let displayName = msg.payload?.displayName;
|
|
969
|
-
let deviceName = msg.payload.meta?.device?.deviceName;
|
|
970
|
-
|
|
971
|
-
let ipAddress =
|
|
972
|
-
typeof msg.payload.meta?.device?.address === "object"
|
|
973
|
-
? msg.payload.meta.device.address.address
|
|
974
|
-
: msg.payload.meta?.device?.address;
|
|
975
|
-
|
|
976
|
-
// Only proceed if the status key exists
|
|
977
|
-
if (status !== undefined) {
|
|
978
|
-
// Check if the topic is already in the unique topics list
|
|
979
|
-
if (!uniqueTopics.includes(topic)) {
|
|
980
|
-
uniqueTopics.push(topic);
|
|
981
|
-
totalUniqueCount++;
|
|
982
|
-
}
|
|
983
|
-
|
|
984
|
-
// Update the status in the topicStatusMap
|
|
985
|
-
if (topicStatusMap[topic] !== status) {
|
|
986
|
-
// Adjust counts based on the previous status
|
|
987
|
-
if (topicStatusMap[topic] === "online") {
|
|
988
|
-
onlineCount--;
|
|
989
|
-
} else if (topicStatusMap[topic] === "offline") {
|
|
990
|
-
offlineCount--;
|
|
991
|
-
}
|
|
992
|
-
|
|
993
|
-
// Update with the new status
|
|
994
|
-
topicStatusMap[topic] = status;
|
|
995
|
-
|
|
996
|
-
// Adjust counts based on the new status
|
|
997
|
-
if (status === "online") {
|
|
998
|
-
onlineCount++;
|
|
999
|
-
} else if (status === "offline") {
|
|
1000
|
-
offlineCount++;
|
|
1001
|
-
}
|
|
1002
|
-
}
|
|
1003
|
-
|
|
1004
|
-
// Create an entry in the data map if it doesn't already exist
|
|
1005
|
-
if (!topicDataMap[topic]) {
|
|
1006
|
-
topicDataMap[topic] = {
|
|
1007
|
-
presentValue: presentValue,
|
|
1008
|
-
status: status,
|
|
1009
|
-
bacnetLastSeen: timestamp,
|
|
1010
|
-
lastCOVTime: now.getTime(), // Initialize last value change time
|
|
1011
|
-
};
|
|
1012
|
-
|
|
1013
|
-
// Add properties conditionally if they exist or are explicitly set to 0
|
|
1014
|
-
if (deviceID !== undefined) topicDataMap[topic].deviceID = deviceID;
|
|
1015
|
-
if (objectType !== undefined) topicDataMap[topic].objectType = objectType;
|
|
1016
|
-
if (objectInstance !== undefined) topicDataMap[topic].objectInstance = objectInstance;
|
|
1017
|
-
if (pointName !== undefined) topicDataMap[topic].pointName = pointName;
|
|
1018
|
-
if (displayName !== undefined) topicDataMap[topic].displayName = displayName;
|
|
1019
|
-
if (deviceName !== undefined) topicDataMap[topic].deviceName = deviceName;
|
|
1020
|
-
if (ipAddress !== undefined) topicDataMap[topic].ipAddress = ipAddress;
|
|
1021
|
-
if (error !== undefined) topicDataMap[topic].error = error;
|
|
1022
|
-
topicDataMap[topic].key =
|
|
1023
|
-
topicDataMap[topic].deviceID + ":" + topicDataMap[topic].objectType + ":" + topicDataMap[topic].objectInstance;
|
|
1024
|
-
} else {
|
|
1025
|
-
// If the entry already exists
|
|
1026
|
-
|
|
1027
|
-
if (presentValue != topicDataMap[topic].presentValue) {
|
|
1028
|
-
// If the present value has changed
|
|
1029
|
-
topicDataMap[topic].lastCOVTime = now.getTime(); // Update last value change time
|
|
1030
|
-
}
|
|
1031
|
-
|
|
1032
|
-
topicDataMap[topic].presentValue = presentValue; // Update the existing timestamp and present value
|
|
1033
|
-
topicDataMap[topic].bacnetLastSeen = timestamp; // Update timestamp
|
|
1034
|
-
|
|
1035
|
-
// Update properties conditionally if they exist or are explicitly set to 0
|
|
1036
|
-
if (deviceID !== undefined) topicDataMap[topic].deviceID = deviceID;
|
|
1037
|
-
if (objectType !== undefined) topicDataMap[topic].objectType = objectType;
|
|
1038
|
-
if (objectInstance !== undefined) topicDataMap[topic].objectInstance = objectInstance;
|
|
1039
|
-
if (pointName !== undefined) topicDataMap[topic].pointName = pointName;
|
|
1040
|
-
if (displayName !== undefined) topicDataMap[topic].displayName = displayName;
|
|
1041
|
-
if (deviceName !== undefined) topicDataMap[topic].deviceName = deviceName;
|
|
1042
|
-
if (ipAddress !== undefined) topicDataMap[topic].ipAddress = ipAddress;
|
|
1043
|
-
if (error !== undefined) topicDataMap[topic].error = error;
|
|
1044
|
-
topicDataMap[topic].key =
|
|
1045
|
-
topicDataMap[topic].deviceID + ":" + topicDataMap[topic].objectType + ":" + topicDataMap[topic].objectInstance;
|
|
1046
|
-
}
|
|
1047
|
-
} else {
|
|
1048
|
-
node.warn("Status key is missing for topic: " + topic);
|
|
1049
|
-
}
|
|
1050
|
-
|
|
1051
|
-
let offlinePercentage = (offlineCount / (onlineCount + offlineCount)) * 100;
|
|
1052
|
-
|
|
1053
|
-
node.statBlock.offlinePercentage = offlinePercentage;
|
|
1054
|
-
|
|
1055
|
-
// Store the updated unique topics, status map, topic data map, and counts back in the context context
|
|
1056
|
-
flow.set("uniqueTopics", uniqueTopics);
|
|
1057
|
-
flow.set("topicStatusMap", topicStatusMap);
|
|
1058
|
-
flow.set("topicDataMap", topicDataMap);
|
|
1059
|
-
flow.set("totalUniquePolledCount", totalUniqueCount);
|
|
1060
|
-
flow.set("onlineCount", onlineCount);
|
|
1061
|
-
flow.set("offlineCount", offlineCount);
|
|
1062
|
-
flow.set("offlinePercentage", offlinePercentage);
|
|
1063
|
-
|
|
1064
|
-
// Update the node status
|
|
1065
|
-
node.status({ text: "Points Online: " + onlineCount + "/" + totalUniqueCount });
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
924
|
function makeGetRequest(route) {
|
|
1069
925
|
return new Promise((resolve, reject) => {
|
|
1070
926
|
try {
|
|
@@ -3,10 +3,10 @@ const { Worker } = require("worker_threads");
|
|
|
3
3
|
const path = require("path");
|
|
4
4
|
|
|
5
5
|
function getRelativeTime(timestamp) {
|
|
6
|
-
if (!timestamp) return "N/A";
|
|
6
|
+
if (!timestamp || isNaN(timestamp) || typeof timestamp !== 'number') return "N/A";
|
|
7
7
|
let now = Date.now();
|
|
8
8
|
let timeDiff = now - timestamp;
|
|
9
|
-
if (timeDiff < 0) return "
|
|
9
|
+
if (isNaN(timeDiff) || timeDiff < 0) return "Invalid timestamp";
|
|
10
10
|
|
|
11
11
|
let seconds = Math.floor(timeDiff / 1000);
|
|
12
12
|
let minutes = Math.floor(seconds / 60);
|
|
@@ -367,7 +367,7 @@ function processModelStats(data) {
|
|
|
367
367
|
}
|
|
368
368
|
|
|
369
369
|
// Update statBlock
|
|
370
|
-
statBlock.missing =
|
|
370
|
+
statBlock.missing = pointNotInDiscoveryCount;
|
|
371
371
|
statBlock.warnings = pointMatchDiscoveryNotPublishingCount;
|
|
372
372
|
statBlock.unmapped = unmappedCount;
|
|
373
373
|
|
package/common.js
CHANGED
|
@@ -65,7 +65,8 @@ class BacnetClientConfig {
|
|
|
65
65
|
retries,
|
|
66
66
|
cacheFileEnabled,
|
|
67
67
|
sanitise_device_schedule,
|
|
68
|
-
portRangeMatrix
|
|
68
|
+
portRangeMatrix,
|
|
69
|
+
enable_device_discovery
|
|
69
70
|
) {
|
|
70
71
|
this.apduTimeout = apduTimeout;
|
|
71
72
|
this.localIpAdrress = localIpAdrress;
|
|
@@ -84,6 +85,7 @@ class BacnetClientConfig {
|
|
|
84
85
|
this.cacheFileEnabled = cacheFileEnabled;
|
|
85
86
|
this.sanitise_device_schedule = sanitise_device_schedule;
|
|
86
87
|
this.portRangeMatrix = this.generatePortRangeArray(portRangeMatrix);
|
|
88
|
+
this.enable_device_discovery = enable_device_discovery;
|
|
87
89
|
}
|
|
88
90
|
|
|
89
91
|
generatePortRangeArray(rangeMatrix) {
|
|
@@ -255,7 +257,7 @@ async function Store_Config(data) {
|
|
|
255
257
|
JSON.parse(tempContent);
|
|
256
258
|
} catch (verifyError) {
|
|
257
259
|
console.error("Temporary file validation failed:", verifyError);
|
|
258
|
-
await fs2.unlink(tempFile).catch(() => {});
|
|
260
|
+
await fs2.unlink(tempFile).catch(() => { });
|
|
259
261
|
return false;
|
|
260
262
|
}
|
|
261
263
|
|
|
@@ -275,8 +277,8 @@ async function Store_Config(data) {
|
|
|
275
277
|
|
|
276
278
|
// Cleanup temporary file if it exists
|
|
277
279
|
try {
|
|
278
|
-
await fs2.unlink(tempFile).catch(() => {});
|
|
279
|
-
} catch (cleanupError) {}
|
|
280
|
+
await fs2.unlink(tempFile).catch(() => { });
|
|
281
|
+
} catch (cleanupError) { }
|
|
280
282
|
|
|
281
283
|
// If main file is corrupted and backup exists, restore from backup
|
|
282
284
|
try {
|
|
@@ -396,7 +398,7 @@ async function Store_Config_Server(data) {
|
|
|
396
398
|
//console.log("Store_Config_Server writeFile error: ", err);
|
|
397
399
|
}
|
|
398
400
|
});
|
|
399
|
-
} catch (err) {}
|
|
401
|
+
} catch (err) { }
|
|
400
402
|
}
|
|
401
403
|
|
|
402
404
|
// READ CONFIG SYNC FUNCTION - BACNET SERVER ======================================
|
|
@@ -1 +1,170 @@
|
|
|
1
|
-
[
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "ba92a36a18ffdfac",
|
|
4
|
+
"type": "Bacnet-Gateway",
|
|
5
|
+
"z": "f1fc21f44027188c",
|
|
6
|
+
"name": "",
|
|
7
|
+
"local_device_address": "",
|
|
8
|
+
"local_interface_name": "",
|
|
9
|
+
"apduTimeout": 6000,
|
|
10
|
+
"roundDecimal": 2,
|
|
11
|
+
"local_device_port": 47808,
|
|
12
|
+
"apduSize": "5",
|
|
13
|
+
"maxSegments": "0x50",
|
|
14
|
+
"retries": "5",
|
|
15
|
+
"broadCastAddr": "255.255.255.255",
|
|
16
|
+
"toLogIam": false,
|
|
17
|
+
"discover_polling_schedule": "900",
|
|
18
|
+
"discover_polling_schedule_value": "15",
|
|
19
|
+
"discover_polling_schedule_options": "Minutes",
|
|
20
|
+
"deviceId": 817001,
|
|
21
|
+
"logErrorToConsole": false,
|
|
22
|
+
"serverEnabled": true,
|
|
23
|
+
"device_read_schedule": "900",
|
|
24
|
+
"device_read_schedule_value": "15",
|
|
25
|
+
"device_read_schedule_options": "Minutes",
|
|
26
|
+
"deviceRangeRegisters": [
|
|
27
|
+
{
|
|
28
|
+
"enabled": true,
|
|
29
|
+
"start": "0",
|
|
30
|
+
"end": "4194303"
|
|
31
|
+
}
|
|
32
|
+
],
|
|
33
|
+
"portRangeRegisters": [
|
|
34
|
+
{
|
|
35
|
+
"enabled": true,
|
|
36
|
+
"start": "47808",
|
|
37
|
+
"end": "47808"
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
"cacheFileEnabled": false,
|
|
41
|
+
"sanitise_device_schedule": "3600",
|
|
42
|
+
"sanitise_device_schedule_value": "1",
|
|
43
|
+
"sanitise_device_schedule_options": "Hours",
|
|
44
|
+
"x": 800,
|
|
45
|
+
"y": 460,
|
|
46
|
+
"wires": [
|
|
47
|
+
[
|
|
48
|
+
"0688901dadf6d984"
|
|
49
|
+
]
|
|
50
|
+
]
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"id": "c404d53530d50ce9",
|
|
54
|
+
"type": "Bacnet-Discovery",
|
|
55
|
+
"z": "f1fc21f44027188c",
|
|
56
|
+
"name": "",
|
|
57
|
+
"events": true,
|
|
58
|
+
"json": true,
|
|
59
|
+
"mqtt": false,
|
|
60
|
+
"pointJson": false,
|
|
61
|
+
"hiddenDeployToggle": false,
|
|
62
|
+
"prevHiddenToggleState": false,
|
|
63
|
+
"roundDecimal": 2,
|
|
64
|
+
"pointsToRead": {},
|
|
65
|
+
"readDevices": [],
|
|
66
|
+
"object_property_simplePayload": false,
|
|
67
|
+
"object_property_simpleWithStatus": false,
|
|
68
|
+
"object_property_fullObject": true,
|
|
69
|
+
"useDeviceName": true,
|
|
70
|
+
"x": 630,
|
|
71
|
+
"y": 400,
|
|
72
|
+
"wires": [
|
|
73
|
+
[
|
|
74
|
+
"ba92a36a18ffdfac"
|
|
75
|
+
]
|
|
76
|
+
]
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"id": "d89267e068e24882",
|
|
80
|
+
"type": "Bitpool-Inject",
|
|
81
|
+
"z": "f1fc21f44027188c",
|
|
82
|
+
"name": "Poll",
|
|
83
|
+
"props": [
|
|
84
|
+
{
|
|
85
|
+
"p": "payload"
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"p": "topic",
|
|
89
|
+
"vt": "str"
|
|
90
|
+
}
|
|
91
|
+
],
|
|
92
|
+
"repeat": "",
|
|
93
|
+
"crontab": "",
|
|
94
|
+
"once": false,
|
|
95
|
+
"onceDelay": 0.1,
|
|
96
|
+
"topic": "",
|
|
97
|
+
"payload": "",
|
|
98
|
+
"payloadType": "date",
|
|
99
|
+
"doPoll": true,
|
|
100
|
+
"doDiscover": false,
|
|
101
|
+
"json": false,
|
|
102
|
+
"mqtt": false,
|
|
103
|
+
"pointJson": true,
|
|
104
|
+
"object_property_simplePayload": false,
|
|
105
|
+
"object_property_simpleWithStatus": false,
|
|
106
|
+
"object_property_fullObject": true,
|
|
107
|
+
"useDeviceName": true,
|
|
108
|
+
"x": 470,
|
|
109
|
+
"y": 400,
|
|
110
|
+
"wires": [
|
|
111
|
+
[
|
|
112
|
+
"c404d53530d50ce9"
|
|
113
|
+
]
|
|
114
|
+
]
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"id": "5f5537b37018ec25",
|
|
118
|
+
"type": "Bitpool-Inject",
|
|
119
|
+
"z": "f1fc21f44027188c",
|
|
120
|
+
"name": "Discover",
|
|
121
|
+
"props": [
|
|
122
|
+
{
|
|
123
|
+
"p": "payload"
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
"p": "topic",
|
|
127
|
+
"vt": "str"
|
|
128
|
+
}
|
|
129
|
+
],
|
|
130
|
+
"repeat": "",
|
|
131
|
+
"crontab": "",
|
|
132
|
+
"once": false,
|
|
133
|
+
"onceDelay": 0.1,
|
|
134
|
+
"topic": "",
|
|
135
|
+
"payload": "",
|
|
136
|
+
"payloadType": "date",
|
|
137
|
+
"doPoll": false,
|
|
138
|
+
"doDiscover": true,
|
|
139
|
+
"json": false,
|
|
140
|
+
"mqtt": false,
|
|
141
|
+
"pointJson": true,
|
|
142
|
+
"object_property_simplePayload": false,
|
|
143
|
+
"object_property_simpleWithStatus": false,
|
|
144
|
+
"object_property_fullObject": true,
|
|
145
|
+
"useDeviceName": true,
|
|
146
|
+
"x": 620,
|
|
147
|
+
"y": 460,
|
|
148
|
+
"wires": [
|
|
149
|
+
[
|
|
150
|
+
"ba92a36a18ffdfac"
|
|
151
|
+
]
|
|
152
|
+
]
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
"id": "0688901dadf6d984",
|
|
156
|
+
"type": "debug",
|
|
157
|
+
"z": "f1fc21f44027188c",
|
|
158
|
+
"name": "debug 1",
|
|
159
|
+
"active": true,
|
|
160
|
+
"tosidebar": true,
|
|
161
|
+
"console": false,
|
|
162
|
+
"tostatus": false,
|
|
163
|
+
"complete": "false",
|
|
164
|
+
"statusVal": "",
|
|
165
|
+
"statusType": "auto",
|
|
166
|
+
"x": 1000,
|
|
167
|
+
"y": 460,
|
|
168
|
+
"wires": []
|
|
169
|
+
}
|
|
170
|
+
]
|