@bitpoolos/edge-bacnet 1.3.0 → 1.3.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 +29 -0
- package/README.md +3 -0
- package/bacnet_client.js +269 -61
- package/bacnet_device.js +10 -0
- package/bacnet_gateway.html +14 -14
- package/bacnet_read.html +28 -7
- package/bacnet_server.js +91 -27
- package/bacnet_write.html +62 -1
- package/common.js +1 -1
- package/package.json +1 -1
- package/resources/node-bacstack-ts/dist/lib/client.js +1 -1
- package/treeBuilder.js +15 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.3.1] - 06-06-2024
|
|
4
|
+
|
|
5
|
+
### Summary
|
|
6
|
+
|
|
7
|
+
Primarily bug fixes and performance improvements
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
- Adding individual points for MSTP devices would add incorrect device name to read list
|
|
12
|
+
|
|
13
|
+
- Adding individual points for IP devices would add all MSTP network folders to read list
|
|
14
|
+
|
|
15
|
+
- Intermittent incorrect device naming issues
|
|
16
|
+
|
|
17
|
+
- Fixed BACnet server incompatibility with YABE and other BACnet browsers.
|
|
18
|
+
|
|
19
|
+
- Added undefined check to bacstack client
|
|
20
|
+
|
|
21
|
+
### Improvements
|
|
22
|
+
|
|
23
|
+
- First poll cycle only queries Object Name and Present Value properties for all applicable Object Types for smaller initial network load. Objects are then back populated with the remaining Object properties on the subsequent poll cycles.
|
|
24
|
+
|
|
25
|
+
- Devices are immediately added to the UI tree with a device placeholder on a whoIs/iAm response. The devices are then back populated with Names and BACnet Objects. This gives the user a fast understanding of the size and relationships of the BACnet network.
|
|
26
|
+
|
|
27
|
+
- Added docstrings to bacnet_server.js and code clean up.
|
|
28
|
+
|
|
29
|
+
- Set Server enable to default on True
|
|
30
|
+
|
|
31
|
+
|
|
3
32
|
## [1.3.0] - 16-05-2024
|
|
4
33
|
|
|
5
34
|
### Summary
|
package/README.md
CHANGED
|
@@ -45,6 +45,7 @@ Upon updating to the latest version, we highly recommend:
|
|
|
45
45
|
- Deploy all flows
|
|
46
46
|
- Restart Node-RED
|
|
47
47
|
- Insert and reconfigure new @bitpoolos/edge-bacnet nodes.
|
|
48
|
+
- Restart Node-RED again if no devices are discovered.
|
|
48
49
|
```
|
|
49
50
|
Main reason being, the behaviour of the bacnet client binding to network interfaces can remain stagnent if the Node-RED service is not restarted. This also ensures that all of the nodes are correctly configured as there are often properties added and removed from nodes.
|
|
50
51
|
|
|
@@ -61,6 +62,8 @@ Main reason being, the behaviour of the bacnet client binding to network interfa
|
|
|
61
62
|
|
|
62
63
|
- If you are using this node in a linux environment, using the 'All interfaces : 0.0.0.0' can be more reliable with a greater range of BACnet devices.
|
|
63
64
|
|
|
65
|
+
- Note your broadcast address, compatibility can vary from 255.255.255.255 (all subnets) and 192.x.x.255 (locked down to your current subnet).
|
|
66
|
+
|
|
64
67
|
## Resources
|
|
65
68
|
- [bitpool.com](https://www.bitpool.com/) - who are we.
|
|
66
69
|
- [wiki.bitpool.com](https://wiki.bitpool.com/) - helpful docs.
|
package/bacnet_client.js
CHANGED
|
@@ -124,14 +124,15 @@ class BacnetClient extends EventEmitter {
|
|
|
124
124
|
that.addToParentMstpNetwork(newBacnetDevice);
|
|
125
125
|
}
|
|
126
126
|
that.deviceList.push(newBacnetDevice);
|
|
127
|
+
that.addToNetworkTree(newBacnetDevice);
|
|
127
128
|
} else if (foundIndex !== -1) {
|
|
128
129
|
that.deviceList[foundIndex].updateDeviceConfig(device);
|
|
129
130
|
that.deviceList[foundIndex].setLastSeen(Date.now());
|
|
130
131
|
if (that.deviceList[foundIndex].getIsMstpDevice()) {
|
|
131
132
|
that.addToParentMstpNetwork(that.deviceList[foundIndex]);
|
|
132
133
|
}
|
|
134
|
+
that.addToNetworkTree(that.deviceList[foundIndex]);
|
|
133
135
|
}
|
|
134
|
-
|
|
135
136
|
//emit event for node-red to log
|
|
136
137
|
that.emit("deviceFound", device);
|
|
137
138
|
}
|
|
@@ -145,12 +146,14 @@ class BacnetClient extends EventEmitter {
|
|
|
145
146
|
that.addToParentMstpNetwork(newBacnetDevice);
|
|
146
147
|
}
|
|
147
148
|
that.deviceList.push(newBacnetDevice);
|
|
149
|
+
that.addToNetworkTree(newBacnetDevice);
|
|
148
150
|
} else if (foundIndex !== -1) {
|
|
149
151
|
that.deviceList[foundIndex].updateDeviceConfig(device);
|
|
150
152
|
that.deviceList[foundIndex].setLastSeen(Date.now());
|
|
151
153
|
if (that.deviceList[foundIndex].getIsMstpDevice()) {
|
|
152
154
|
that.addToParentMstpNetwork(that.deviceList[foundIndex]);
|
|
153
155
|
}
|
|
156
|
+
that.addToNetworkTree(that.deviceList[foundIndex]);
|
|
154
157
|
}
|
|
155
158
|
|
|
156
159
|
//emit event for node-red to log
|
|
@@ -177,27 +180,68 @@ class BacnetClient extends EventEmitter {
|
|
|
177
180
|
|
|
178
181
|
testFunction(address, type, instance, property) {
|
|
179
182
|
let that = this;
|
|
180
|
-
|
|
181
183
|
console.log("test function ");
|
|
182
|
-
|
|
183
184
|
that.client.readProperty(
|
|
184
185
|
address,
|
|
185
186
|
{ type: type, instance: instance },
|
|
186
187
|
property,
|
|
187
188
|
that.readPropertyMultipleOptions,
|
|
188
189
|
(err, value) => {
|
|
189
|
-
|
|
190
|
-
console.log("err: ", err);
|
|
191
|
-
}
|
|
192
|
-
|
|
190
|
+
console.log(value);
|
|
193
191
|
if (value) {
|
|
194
|
-
|
|
195
|
-
console.log(
|
|
192
|
+
// If the result has value, resolve the promise
|
|
193
|
+
console.log(value.values[0]);
|
|
194
|
+
value.values[0].values.forEach(function (value) {
|
|
195
|
+
console.log("value: ", value.value);
|
|
196
|
+
});
|
|
197
|
+
} else {
|
|
198
|
+
console.log(err);
|
|
196
199
|
}
|
|
197
200
|
}
|
|
198
201
|
);
|
|
199
202
|
}
|
|
200
203
|
|
|
204
|
+
addToNetworkTree(device) {
|
|
205
|
+
let that = this;
|
|
206
|
+
try {
|
|
207
|
+
const deviceKey = that.createDeviceKey(device);
|
|
208
|
+
let deviceName = device.getDeviceName();
|
|
209
|
+
if (deviceName !== null) {
|
|
210
|
+
const deviceId = device.getDeviceId();
|
|
211
|
+
if (deviceId !== null) {
|
|
212
|
+
let lastIndex = deviceName.lastIndexOf(deviceId);
|
|
213
|
+
if (lastIndex) {
|
|
214
|
+
let formattedName = deviceName.substring(0, lastIndex);
|
|
215
|
+
formattedName = `${formattedName.trim()}_Device_${deviceId}`;
|
|
216
|
+
if (that.networkTree[deviceKey][formattedName] &&
|
|
217
|
+
Object.keys(that.networkTree[deviceKey][formattedName]).length > 0) {
|
|
218
|
+
delete that.networkTree[deviceKey]["device"];
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
} else {
|
|
223
|
+
const json = {
|
|
224
|
+
"objectId": {
|
|
225
|
+
"type": 8,
|
|
226
|
+
"instance": device.getDeviceId()
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
if (that.networkTree[deviceKey] && that.networkTree[deviceKey]["device"]) {
|
|
231
|
+
that.networkTree[deviceKey]["device"]["meta"] = json;
|
|
232
|
+
} else {
|
|
233
|
+
that.networkTree[deviceKey] = {
|
|
234
|
+
"device": {
|
|
235
|
+
"meta": json
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
} catch (e) {
|
|
241
|
+
that.logOut("addToNetworkTree error: ", e);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
201
245
|
getProtocolSupported(device) {
|
|
202
246
|
//return protocols support for device
|
|
203
247
|
let that = this;
|
|
@@ -283,8 +327,26 @@ class BacnetClient extends EventEmitter {
|
|
|
283
327
|
let device = that.deviceList.find((ele) => ele.getDeviceId() == deviceObject.deviceId);
|
|
284
328
|
that.updateDeviceName(device);
|
|
285
329
|
|
|
330
|
+
|
|
331
|
+
//test
|
|
332
|
+
|
|
333
|
+
that.getProtocolSupported(device).then(function (result) {
|
|
334
|
+
console.log("updatePointsForDevice getProtocolSupported ", result.values[0].originalBitString);
|
|
335
|
+
console.log(result.values[0]);
|
|
336
|
+
console.log(result);
|
|
337
|
+
console.log(result.values[0]);
|
|
338
|
+
let decodedValues = decodeBitArray(8, result.values[0].originalBitString.value);
|
|
339
|
+
device.setProtocolServicesSupported(decodedValues);
|
|
340
|
+
}).catch(function (error) {
|
|
341
|
+
that.logOut("getProtocolSupported error: ", error);
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
//test
|
|
345
|
+
|
|
346
|
+
|
|
286
347
|
if (device.getIsProtocolServicesSet() == false) {
|
|
287
348
|
that.getProtocolSupported(device).then(function (result) {
|
|
349
|
+
console.log("updatePointsForDevice getProtocolSupported ", result.values[0].originalBitString);
|
|
288
350
|
let decodedValues = decodeBitArray(8, result.values[0].originalBitString.value);
|
|
289
351
|
device.setProtocolServicesSupported(decodedValues);
|
|
290
352
|
}).catch(function (error) {
|
|
@@ -302,11 +364,11 @@ class BacnetClient extends EventEmitter {
|
|
|
302
364
|
resolve(true);
|
|
303
365
|
})
|
|
304
366
|
.catch(function (e) {
|
|
305
|
-
that.logOut(`Update points list error 1: ${
|
|
367
|
+
that.logOut(`Update points list error 1: ${that.getDeviceAddress(device)}`, e);
|
|
306
368
|
});
|
|
307
369
|
})
|
|
308
370
|
.catch(function (e) {
|
|
309
|
-
that.logOut(`Update points list error 2: ${
|
|
371
|
+
that.logOut(`Update points list error 2: ${that.getDeviceAddress(device)}`, e);
|
|
310
372
|
device.setManualDiscoveryMode(true);
|
|
311
373
|
that
|
|
312
374
|
.getDevicePointListWithoutObjectList(device)
|
|
@@ -318,11 +380,11 @@ class BacnetClient extends EventEmitter {
|
|
|
318
380
|
resolve(true);
|
|
319
381
|
})
|
|
320
382
|
.catch(function (e) {
|
|
321
|
-
that.logOut(`Update points list error 3: ${
|
|
383
|
+
that.logOut(`Update points list error 3: ${that.getDeviceAddress(device)}`, e);
|
|
322
384
|
});
|
|
323
385
|
})
|
|
324
386
|
.catch(function (e) {
|
|
325
|
-
that.logOut(`Update points list error 4: ${
|
|
387
|
+
that.logOut(`Update points list error 4: ${that.getDeviceAddress(device)}`, e);
|
|
326
388
|
});
|
|
327
389
|
});
|
|
328
390
|
} catch (e) {
|
|
@@ -416,9 +478,9 @@ class BacnetClient extends EventEmitter {
|
|
|
416
478
|
that.logOut("getProtocolSupported error: ", error);
|
|
417
479
|
});
|
|
418
480
|
}
|
|
481
|
+
try {
|
|
419
482
|
|
|
420
|
-
|
|
421
|
-
try {
|
|
483
|
+
if (device.getSegmentation() !== 3) {
|
|
422
484
|
that.updateDeviceName(device);
|
|
423
485
|
that
|
|
424
486
|
.getDevicePointList(device)
|
|
@@ -435,7 +497,6 @@ class BacnetClient extends EventEmitter {
|
|
|
435
497
|
})
|
|
436
498
|
.catch(function (e) {
|
|
437
499
|
that.logOut(`getDevicePointList error: ${device.getAddress()}`, e);
|
|
438
|
-
device.setManualDiscoveryMode(true);
|
|
439
500
|
that
|
|
440
501
|
.getDevicePointListWithoutObjectList(device)
|
|
441
502
|
.then(function () {
|
|
@@ -453,11 +514,29 @@ class BacnetClient extends EventEmitter {
|
|
|
453
514
|
query(index);
|
|
454
515
|
});
|
|
455
516
|
});
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
517
|
+
|
|
518
|
+
} else if (device.getSegmentation() == 3) {
|
|
519
|
+
|
|
520
|
+
that.updateDeviceName(device);
|
|
521
|
+
that
|
|
522
|
+
.getDevicePointListWithoutObjectList(device)
|
|
523
|
+
.then(function () {
|
|
524
|
+
that
|
|
525
|
+
.buildJsonObject(device)
|
|
526
|
+
.then(function () {
|
|
527
|
+
query(index);
|
|
528
|
+
})
|
|
529
|
+
.catch(function (e) {
|
|
530
|
+
that.logOut(`getDevicePointList error: ${device.getAddress()}`, e);
|
|
531
|
+
query(index);
|
|
532
|
+
});
|
|
533
|
+
})
|
|
534
|
+
.catch(function (e) {
|
|
535
|
+
query(index);
|
|
536
|
+
});
|
|
459
537
|
}
|
|
460
|
-
}
|
|
538
|
+
} catch (e) {
|
|
539
|
+
that.logOut("Error while querying devices: ", e);
|
|
461
540
|
query(index);
|
|
462
541
|
}
|
|
463
542
|
} else {
|
|
@@ -1061,6 +1140,83 @@ class BacnetClient extends EventEmitter {
|
|
|
1061
1140
|
}
|
|
1062
1141
|
|
|
1063
1142
|
|
|
1143
|
+
_readObjectLite(device, deviceAddress, type, instance) {
|
|
1144
|
+
const that = this;
|
|
1145
|
+
const readOptions = {
|
|
1146
|
+
maxSegments: that.readPropertyMultipleOptions.maxSegments,
|
|
1147
|
+
maxApdu: that.readPropertyMultipleOptions.maxApdu,
|
|
1148
|
+
};
|
|
1149
|
+
|
|
1150
|
+
// Define all properties to be read
|
|
1151
|
+
const allProperties = [
|
|
1152
|
+
{ id: baEnum.PropertyIdentifier.PRESENT_VALUE },
|
|
1153
|
+
{ id: baEnum.PropertyIdentifier.OBJECT_NAME },
|
|
1154
|
+
];
|
|
1155
|
+
|
|
1156
|
+
return new Promise((resolve, reject) => {
|
|
1157
|
+
// Try to read all properties at once
|
|
1158
|
+
that._readObject(deviceAddress, type, instance, allProperties, readOptions)
|
|
1159
|
+
.then(result => {
|
|
1160
|
+
if (result.value) {
|
|
1161
|
+
// If the result has value, resolve the promise
|
|
1162
|
+
resolve(result);
|
|
1163
|
+
} else {
|
|
1164
|
+
// If not, proceed to read individual properties
|
|
1165
|
+
readPropertiesIndividually();
|
|
1166
|
+
}
|
|
1167
|
+
})
|
|
1168
|
+
.catch(() => {
|
|
1169
|
+
// On error, proceed to read individual properties
|
|
1170
|
+
readPropertiesIndividually();
|
|
1171
|
+
});
|
|
1172
|
+
|
|
1173
|
+
// Function to read properties individually
|
|
1174
|
+
const readPropertiesIndividually = () => {
|
|
1175
|
+
const promises = allProperties.map((property, index) => new Promise((propertyResolve) => {
|
|
1176
|
+
that.client.readProperty(
|
|
1177
|
+
deviceAddress,
|
|
1178
|
+
{ type: type, instance: instance },
|
|
1179
|
+
property.id,
|
|
1180
|
+
readOptions,
|
|
1181
|
+
(err, value) => {
|
|
1182
|
+
if (err) {
|
|
1183
|
+
propertyResolve(null);
|
|
1184
|
+
} else {
|
|
1185
|
+
propertyResolve({
|
|
1186
|
+
id: property.id,
|
|
1187
|
+
index: value.property.index,
|
|
1188
|
+
value: value.values,
|
|
1189
|
+
});
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
);
|
|
1193
|
+
}));
|
|
1194
|
+
|
|
1195
|
+
Promise.all(promises)
|
|
1196
|
+
.then(resultArray => {
|
|
1197
|
+
// Filter out null results
|
|
1198
|
+
const validResults = resultArray.filter(result => result !== null);
|
|
1199
|
+
|
|
1200
|
+
resolve({
|
|
1201
|
+
error: null,
|
|
1202
|
+
value: {
|
|
1203
|
+
values: [
|
|
1204
|
+
{
|
|
1205
|
+
objectId: {
|
|
1206
|
+
type: type,
|
|
1207
|
+
instance: instance,
|
|
1208
|
+
},
|
|
1209
|
+
values: validResults,
|
|
1210
|
+
},
|
|
1211
|
+
],
|
|
1212
|
+
},
|
|
1213
|
+
});
|
|
1214
|
+
})
|
|
1215
|
+
.catch(reject);
|
|
1216
|
+
};
|
|
1217
|
+
});
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1064
1220
|
_readObjectPropList(deviceAddress, type, instance) {
|
|
1065
1221
|
return this._readObject(deviceAddress, type, instance, [{ id: baEnum.PropertyIdentifier.PROPERTY_LIST }]);
|
|
1066
1222
|
}
|
|
@@ -1394,49 +1550,100 @@ class BacnetClient extends EventEmitter {
|
|
|
1394
1550
|
if (typeof pointList !== "undefined" && pointList.length > 0) {
|
|
1395
1551
|
pointList.forEach(function (point, pointListIndex) {
|
|
1396
1552
|
requestMutex.acquire().then(function (release) {
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
if (result.
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1553
|
+
if (device.getIsInitialQuery()) {
|
|
1554
|
+
that
|
|
1555
|
+
._readObjectLite(device, address, point.value.type, point.value.instance)
|
|
1556
|
+
.then(function (result) {
|
|
1557
|
+
if (!result.error) {
|
|
1558
|
+
if (result.length > 0 && Array.isArray(result)) {
|
|
1559
|
+
promiseArray = result;
|
|
1560
|
+
} else {
|
|
1561
|
+
promiseArray.push(result);
|
|
1562
|
+
}
|
|
1405
1563
|
}
|
|
1406
|
-
}
|
|
1407
1564
|
|
|
1408
|
-
|
|
1565
|
+
release();
|
|
1566
|
+
|
|
1567
|
+
if (pointListIndex == pointList.length - 1) {
|
|
1568
|
+
device.setIsInitialQuery(false);
|
|
1569
|
+
that
|
|
1570
|
+
.buildResponse(promiseArray, device)
|
|
1571
|
+
.then(function () {
|
|
1572
|
+
that.lastNetworkPoll = Date.now();
|
|
1573
|
+
resolve({ deviceList: that.deviceList, pointList: that.networkTree });
|
|
1574
|
+
})
|
|
1575
|
+
.catch(function (e) {
|
|
1576
|
+
that.logOut("Error while building json object: ", e);
|
|
1577
|
+
reject(e);
|
|
1578
|
+
});
|
|
1579
|
+
}
|
|
1580
|
+
})
|
|
1581
|
+
.catch(function (e) {
|
|
1582
|
+
release();
|
|
1583
|
+
that.logOut("_readObjectLite error: ", e);
|
|
1584
|
+
|
|
1585
|
+
if (pointListIndex == pointList.length - 1) {
|
|
1586
|
+
device.setIsInitialQuery(false);
|
|
1587
|
+
that
|
|
1588
|
+
.buildResponse(promiseArray, device)
|
|
1589
|
+
.then(function () {
|
|
1590
|
+
that.lastNetworkPoll = Date.now();
|
|
1591
|
+
resolve({ deviceList: that.deviceList, pointList: that.networkTree });
|
|
1592
|
+
})
|
|
1593
|
+
.catch(function (e) {
|
|
1594
|
+
that.logOut("Error while building json object: ", e);
|
|
1595
|
+
reject(e);
|
|
1596
|
+
});
|
|
1597
|
+
}
|
|
1598
|
+
});
|
|
1409
1599
|
|
|
1410
|
-
if (pointListIndex == pointList.length - 1) {
|
|
1411
|
-
that
|
|
1412
|
-
.buildResponse(promiseArray, device)
|
|
1413
|
-
.then(function () {
|
|
1414
|
-
that.lastNetworkPoll = Date.now();
|
|
1415
|
-
resolve({ deviceList: that.deviceList, pointList: that.networkTree });
|
|
1416
|
-
})
|
|
1417
|
-
.catch(function (e) {
|
|
1418
|
-
that.logOut("Error while building json object: ", e);
|
|
1419
|
-
reject(e);
|
|
1420
|
-
});
|
|
1421
|
-
}
|
|
1422
|
-
})
|
|
1423
|
-
.catch(function (e) {
|
|
1424
|
-
release();
|
|
1425
|
-
that.logOut("_readObjectFull error: ", e);
|
|
1426
1600
|
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
}
|
|
1438
|
-
|
|
1439
|
-
|
|
1601
|
+
|
|
1602
|
+
} else {
|
|
1603
|
+
that
|
|
1604
|
+
._readObjectFull(device, address, point.value.type, point.value.instance)
|
|
1605
|
+
.then(function (result) {
|
|
1606
|
+
if (!result.error) {
|
|
1607
|
+
if (result.length > 0 && Array.isArray(result)) {
|
|
1608
|
+
promiseArray = result;
|
|
1609
|
+
} else {
|
|
1610
|
+
promiseArray.push(result);
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
release();
|
|
1615
|
+
|
|
1616
|
+
if (pointListIndex == pointList.length - 1) {
|
|
1617
|
+
that
|
|
1618
|
+
.buildResponse(promiseArray, device)
|
|
1619
|
+
.then(function () {
|
|
1620
|
+
that.lastNetworkPoll = Date.now();
|
|
1621
|
+
resolve({ deviceList: that.deviceList, pointList: that.networkTree });
|
|
1622
|
+
})
|
|
1623
|
+
.catch(function (e) {
|
|
1624
|
+
that.logOut("Error while building json object: ", e);
|
|
1625
|
+
reject(e);
|
|
1626
|
+
});
|
|
1627
|
+
}
|
|
1628
|
+
})
|
|
1629
|
+
.catch(function (e) {
|
|
1630
|
+
release();
|
|
1631
|
+
that.logOut("_readObjectFull error: ", e);
|
|
1632
|
+
|
|
1633
|
+
if (pointListIndex == pointList.length - 1) {
|
|
1634
|
+
that
|
|
1635
|
+
.buildResponse(promiseArray, device)
|
|
1636
|
+
.then(function () {
|
|
1637
|
+
that.lastNetworkPoll = Date.now();
|
|
1638
|
+
resolve({ deviceList: that.deviceList, pointList: that.networkTree });
|
|
1639
|
+
})
|
|
1640
|
+
.catch(function (e) {
|
|
1641
|
+
that.logOut("Error while building json object: ", e);
|
|
1642
|
+
reject(e);
|
|
1643
|
+
});
|
|
1644
|
+
}
|
|
1645
|
+
});
|
|
1646
|
+
}
|
|
1440
1647
|
});
|
|
1441
1648
|
});
|
|
1442
1649
|
} else {
|
|
@@ -1467,7 +1674,8 @@ class BacnetClient extends EventEmitter {
|
|
|
1467
1674
|
let currobjectId = pointProperty.objectId.type;
|
|
1468
1675
|
let bac_obj = that.getObjectType(currobjectId);
|
|
1469
1676
|
let objectName = that._findValueById(pointProperty.values, baEnum.PropertyIdentifier.OBJECT_NAME);
|
|
1470
|
-
let objectType =
|
|
1677
|
+
let objectType = pointProperty.objectId.type;
|
|
1678
|
+
|
|
1471
1679
|
let objectId;
|
|
1472
1680
|
if (objectName !== null && typeof objectName == "string") {
|
|
1473
1681
|
objectName = objectName.replace(reg, '');
|
|
@@ -1504,7 +1712,7 @@ class BacnetClient extends EventEmitter {
|
|
|
1504
1712
|
values[objectId].presentValue = values[objectId].stateTextArray[object.value[0].value - 1].value;
|
|
1505
1713
|
}
|
|
1506
1714
|
}
|
|
1507
|
-
} else {
|
|
1715
|
+
} else if (objectType !== 8) {
|
|
1508
1716
|
values[objectId].presentValue = roundDecimalPlaces(object.value[0].value, 2);
|
|
1509
1717
|
}
|
|
1510
1718
|
}
|
package/bacnet_device.js
CHANGED
|
@@ -34,6 +34,7 @@ class BacnetDevice {
|
|
|
34
34
|
that.displayName = config.displayName;
|
|
35
35
|
that.protocolServicesSupported = config.protocolServicesSupported;
|
|
36
36
|
that.isProtocolServicesSet = config.isProtocolServicesSet;
|
|
37
|
+
that.isInitialQuery = config.isInitialQuery;
|
|
37
38
|
|
|
38
39
|
} else if (fromImport == false) {
|
|
39
40
|
if (config.net && config.adr) {
|
|
@@ -62,6 +63,7 @@ class BacnetDevice {
|
|
|
62
63
|
that.protocolServicesSupported = [];
|
|
63
64
|
that.protocolServicesSupported = [];
|
|
64
65
|
that.isProtocolServicesSet = false;
|
|
66
|
+
that.isInitialQuery = true;
|
|
65
67
|
}
|
|
66
68
|
}
|
|
67
69
|
|
|
@@ -354,6 +356,14 @@ class BacnetDevice {
|
|
|
354
356
|
}
|
|
355
357
|
}
|
|
356
358
|
|
|
359
|
+
getIsInitialQuery() {
|
|
360
|
+
return this.isInitialQuery;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
setIsInitialQuery(bool) {
|
|
364
|
+
this.isInitialQuery = bool;
|
|
365
|
+
}
|
|
366
|
+
|
|
357
367
|
}
|
|
358
368
|
|
|
359
369
|
module.exports = { BacnetDevice };
|
package/bacnet_gateway.html
CHANGED
|
@@ -75,19 +75,19 @@
|
|
|
75
75
|
<script type="text/javascript">
|
|
76
76
|
class NodeService {
|
|
77
77
|
getNetworkData() {
|
|
78
|
-
return fetch("
|
|
78
|
+
return fetch(RED.settings.httpNodeRoot + "bitpool-bacnet-data/getNetworkTree").then((res) => res.json());
|
|
79
79
|
}
|
|
80
80
|
rebuildDataModel() {
|
|
81
|
-
return fetch("
|
|
81
|
+
return fetch(RED.settings.httpNodeRoot + "bitpool-bacnet-data/rebuildDataModel").then((res) => res.json());
|
|
82
82
|
}
|
|
83
83
|
clearBacnetServerPoints() {
|
|
84
|
-
return fetch("
|
|
84
|
+
return fetch(RED.settings.httpNodeRoot + "bitpool-bacnet-data/clearBacnetServerPoints").then((res) => res.json());
|
|
85
85
|
}
|
|
86
86
|
getBacnetServerPoints() {
|
|
87
|
-
return fetch('
|
|
87
|
+
return fetch(RED.settings.httpNodeRoot + 'bitpool-bacnet-data/getBacnetServerPoints').then(res => res.json());
|
|
88
88
|
}
|
|
89
89
|
purgeDevice(device) {
|
|
90
|
-
return fetch('
|
|
90
|
+
return fetch(RED.settings.httpNodeRoot + 'bitpool-bacnet-data/purgeDevice', {
|
|
91
91
|
method: 'POST',
|
|
92
92
|
headers: {
|
|
93
93
|
'Accept': 'application/json',
|
|
@@ -97,7 +97,7 @@
|
|
|
97
97
|
}).then(res => res.json());
|
|
98
98
|
}
|
|
99
99
|
updatePointsForDevice(device) {
|
|
100
|
-
return fetch('
|
|
100
|
+
return fetch(RED.settings.httpNodeRoot + 'bitpool-bacnet-data/updatePointsForDevice', {
|
|
101
101
|
method: 'POST',
|
|
102
102
|
headers: {
|
|
103
103
|
'Accept': 'application/json',
|
|
@@ -107,7 +107,7 @@
|
|
|
107
107
|
}).then(res => res.json());
|
|
108
108
|
}
|
|
109
109
|
setDeviceDisplayName(device, displayName) {
|
|
110
|
-
return fetch('
|
|
110
|
+
return fetch(RED.settings.httpNodeRoot + 'bitpool-bacnet-data/setDeviceDisplayName', {
|
|
111
111
|
method: 'POST',
|
|
112
112
|
headers: {
|
|
113
113
|
'Accept': 'application/json',
|
|
@@ -117,7 +117,7 @@
|
|
|
117
117
|
}).then(res => res.json());
|
|
118
118
|
}
|
|
119
119
|
setPointDisplayName(deviceKey, pointName, pointDisplayName) {
|
|
120
|
-
return fetch('
|
|
120
|
+
return fetch(RED.settings.httpNodeRoot + 'bitpool-bacnet-data/setPointDisplayName', {
|
|
121
121
|
method: 'POST',
|
|
122
122
|
headers: {
|
|
123
123
|
'Accept': 'application/json',
|
|
@@ -127,7 +127,7 @@
|
|
|
127
127
|
}).then(res => res.json());
|
|
128
128
|
}
|
|
129
129
|
importReadList(payload) {
|
|
130
|
-
return fetch('
|
|
130
|
+
return fetch(RED.settings.httpNodeRoot + 'bitpool-bacnet-data/importReadList', {
|
|
131
131
|
method: 'POST',
|
|
132
132
|
headers: {
|
|
133
133
|
'Accept': 'application/json',
|
|
@@ -156,7 +156,7 @@
|
|
|
156
156
|
discover_polling_schedule_options: { value: "Minutes", required: true },
|
|
157
157
|
deviceId: { value: 817001, required: true },
|
|
158
158
|
logErrorToConsole: { value: false },
|
|
159
|
-
serverEnabled: { value:
|
|
159
|
+
serverEnabled: { value: true },
|
|
160
160
|
device_read_schedule: { value: "900" },
|
|
161
161
|
device_read_schedule_value: { value: "15", required: true },
|
|
162
162
|
device_read_schedule_options: { value: "Minutes", required: true },
|
|
@@ -213,7 +213,7 @@
|
|
|
213
213
|
function queryAdapters() {
|
|
214
214
|
let nicSelector = document.getElementById("node-input-local_device_address");
|
|
215
215
|
$.ajax({
|
|
216
|
-
url: "
|
|
216
|
+
url: RED.settings.httpNodeRoot + "bitpool-bacnet-data/getNetworkInterfaces",
|
|
217
217
|
success: function (data) {
|
|
218
218
|
let keys = Object.keys(data);
|
|
219
219
|
|
|
@@ -289,7 +289,7 @@
|
|
|
289
289
|
let app = this;
|
|
290
290
|
$.ajax({
|
|
291
291
|
type: "POST",
|
|
292
|
-
url: '
|
|
292
|
+
url: RED.settings.httpNodeRoot + 'bitpool-bacnet-data/clearBacnetServerPoint',
|
|
293
293
|
dataType: 'json',
|
|
294
294
|
contentType: 'application/json',
|
|
295
295
|
data: JSON.stringify(json),
|
|
@@ -342,7 +342,7 @@
|
|
|
342
342
|
|
|
343
343
|
$.ajax({
|
|
344
344
|
type: "POST",
|
|
345
|
-
url: "
|
|
345
|
+
url: RED.settings.httpNodeRoot + "bitpool-bacnet-data/updateDeviceList",
|
|
346
346
|
dataType: "json",
|
|
347
347
|
contentType: "application/json",
|
|
348
348
|
data: JSON.stringify(jsonPayload),
|
|
@@ -356,7 +356,7 @@
|
|
|
356
356
|
|
|
357
357
|
$("#file-export").click(function (params) {
|
|
358
358
|
$.ajax({
|
|
359
|
-
url: "
|
|
359
|
+
url: RED.settings.httpNodeRoot + "bitpool-bacnet-data/getDeviceList",
|
|
360
360
|
success: function (deviceList) {
|
|
361
361
|
let data = "text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(deviceList));
|
|
362
362
|
let aEle = document.getElementById("exportJSON");
|
package/bacnet_read.html
CHANGED
|
@@ -218,8 +218,9 @@
|
|
|
218
218
|
addPointClicked(slotProps) {
|
|
219
219
|
let app = this;
|
|
220
220
|
//update UI
|
|
221
|
-
|
|
222
|
-
|
|
221
|
+
const parentDeviceName = slotProps.node.parentDevice;
|
|
222
|
+
const slotDeviceId = slotProps.node.parentDeviceId;
|
|
223
|
+
let device = this.deviceList.find((ele) => ele.deviceId == slotDeviceId);
|
|
223
224
|
let foundDeviceIndex = this.readDevices
|
|
224
225
|
? this.readDevices.findIndex((ele) => ele.deviceId == device.deviceId)
|
|
225
226
|
: -1;
|
|
@@ -228,11 +229,24 @@
|
|
|
228
229
|
let parentDevice = this.devices.find((ele) => ele.ipAddr == deviceAddress);
|
|
229
230
|
let childDevice;
|
|
230
231
|
if (device.isMstp) {
|
|
231
|
-
let
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
232
|
+
let indexMap = {
|
|
233
|
+
deviceIndex: -1,
|
|
234
|
+
mstpNetorkIndex: -1
|
|
235
|
+
};
|
|
236
|
+
parentDevice.children.forEach(function (child, index) {
|
|
237
|
+
if (child.label.includes("MSTP")) {
|
|
238
|
+
const tempIndex = child.children.findIndex(
|
|
239
|
+
(ele) => ele.deviceId == slotDeviceId
|
|
240
|
+
);
|
|
241
|
+
if (tempIndex !== -1) {
|
|
242
|
+
indexMap.deviceIndex = tempIndex;
|
|
243
|
+
indexMap.mstpNetorkIndex = index;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
if (indexMap.deviceIndex !== -1 && indexMap.mstpNetorkIndex !== -1) {
|
|
249
|
+
childDevice = parentDevice.children[indexMap.mstpNetorkIndex].children[indexMap.deviceIndex];
|
|
236
250
|
}
|
|
237
251
|
}
|
|
238
252
|
|
|
@@ -246,6 +260,13 @@
|
|
|
246
260
|
}
|
|
247
261
|
newReadParent.children[0].children = [];
|
|
248
262
|
newReadParent.children[0].children.push(slotProps.node);
|
|
263
|
+
while (newReadParent.children.length > 1) {
|
|
264
|
+
newReadParent.children.forEach(function (child, index) {
|
|
265
|
+
if (child.label.includes("MSTP")) {
|
|
266
|
+
newReadParent.children.splice(index, 1);
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
}
|
|
249
270
|
if (this.readDevices) {
|
|
250
271
|
this.readDevices.push(newReadParent);
|
|
251
272
|
} else {
|
package/bacnet_server.js
CHANGED
|
@@ -3,6 +3,19 @@ const pjson = require('./package.json');
|
|
|
3
3
|
const baEnum = bacnet.enum;
|
|
4
4
|
const { Store_Config_Server, Read_Config_Sync_Server } = require('./common');
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Class representing a BACnet Server.
|
|
8
|
+
*
|
|
9
|
+
* This class initializes a BACnet server with specified client, device ID, and Node-Red version.
|
|
10
|
+
* It provides methods to set device name, add objects, retrieve objects, clear server points, clear server point, and get server points.
|
|
11
|
+
*
|
|
12
|
+
* Simulates a BACnet IP device on a regular IP network
|
|
13
|
+
*
|
|
14
|
+
* @constructor
|
|
15
|
+
* @param {Object} client - The BACnet client object.
|
|
16
|
+
* @param {number} deviceId - The ID of the device.
|
|
17
|
+
* @param {string} nodeRedVersion - The version of Node-Red.
|
|
18
|
+
*/
|
|
6
19
|
class BacnetServer {
|
|
7
20
|
|
|
8
21
|
constructor(client, deviceId, nodeRedVersion) {
|
|
@@ -14,13 +27,13 @@ class BacnetServer {
|
|
|
14
27
|
that.objectIdNumber[baEnum.ObjectType.ANALOG_VALUE] = 0;
|
|
15
28
|
that.objectIdNumber[baEnum.ObjectType.CHARACTERSTRING_VALUE] = 0;
|
|
16
29
|
that.objectIdNumber[baEnum.ObjectType.BINARY_VALUE] = 0;
|
|
17
|
-
|
|
18
30
|
that.nodeRedVersion = nodeRedVersion;
|
|
19
31
|
that.deviceId = deviceId;
|
|
20
32
|
that.vendorId = 1401;
|
|
21
33
|
that.objectList = [
|
|
22
34
|
{ value: { type: baEnum.ObjectType.DEVICE, instance: that.deviceId }, type: 12 }
|
|
23
35
|
];
|
|
36
|
+
|
|
24
37
|
that.objectStore = {
|
|
25
38
|
[baEnum.ObjectType.DEVICE]: {
|
|
26
39
|
[baEnum.PropertyIdentifier.OBJECT_IDENTIFIER]: [{ value: { type: baEnum.ObjectType.DEVICE, instance: that.deviceId }, type: 12 }],
|
|
@@ -36,8 +49,7 @@ class BacnetServer {
|
|
|
36
49
|
[baEnum.PropertyIdentifier.PROTOCOL_REVISION]: [{ value: 19, type: 2 }],
|
|
37
50
|
[baEnum.PropertyIdentifier.PROTOCOL_VERSION]: [{ value: 0, type: 2 }],
|
|
38
51
|
[baEnum.PropertyIdentifier.APPLICATION_SOFTWARE_VERSION]: [{ value: pjson.version, type: 7 }],
|
|
39
|
-
[baEnum.PropertyIdentifier.PROTOCOL_SERVICES_SUPPORTED]: [{ value: { value: [0,
|
|
40
|
-
[baEnum.PropertyIdentifier.PROTOCOL_OBJECT_TYPES_SUPPORTED]: [{ value: { value: [0, 80, 0, 4, 4], bitsUsed: 40 }, type: 8 }],
|
|
52
|
+
[baEnum.PropertyIdentifier.PROTOCOL_SERVICES_SUPPORTED]: [{ value: { value: [0, 10, 0, 32, 32], bitsUsed: 40 }, type: 8 }],
|
|
41
53
|
[baEnum.PropertyIdentifier.MAX_APDU_LENGTH_ACCEPTED]: [{ value: 1476, type: 2 }],
|
|
42
54
|
[baEnum.PropertyIdentifier.SEGMENTATION_SUPPORTED]: [{ value: 0, type: 9 }],
|
|
43
55
|
[baEnum.PropertyIdentifier.APDU_TIMEOUT]: [{ value: that.bacnetClient.config.apduTimeout, type: 2 }],
|
|
@@ -76,8 +88,10 @@ class BacnetServer {
|
|
|
76
88
|
try {
|
|
77
89
|
let cachedData = JSON.parse(Read_Config_Sync_Server());
|
|
78
90
|
if (typeof cachedData == "object") {
|
|
79
|
-
|
|
80
|
-
|
|
91
|
+
if (cachedData.objectList) {
|
|
92
|
+
that.objectList = cachedData.objectList;
|
|
93
|
+
that.objectStore[baEnum.ObjectType.DEVICE][baEnum.PropertyIdentifier.OBJECT_LIST] = that.objectList;
|
|
94
|
+
}
|
|
81
95
|
if (cachedData.objectStore) {
|
|
82
96
|
that.objectStore[baEnum.ObjectType.ANALOG_VALUE] = cachedData.objectStore[baEnum.ObjectType.ANALOG_VALUE];
|
|
83
97
|
that.objectStore[baEnum.ObjectType.CHARACTERSTRING_VALUE] = cachedData.objectStore[baEnum.ObjectType.CHARACTERSTRING_VALUE];
|
|
@@ -94,24 +108,20 @@ class BacnetServer {
|
|
|
94
108
|
});
|
|
95
109
|
|
|
96
110
|
that.bacnetClient.client.on('readPropertyMultiple', (data) => {
|
|
97
|
-
|
|
98
111
|
let senderAddress = data.address;
|
|
99
112
|
let requestProps = data.request.properties;
|
|
100
113
|
let responseObject = [];
|
|
101
114
|
|
|
102
115
|
try {
|
|
103
116
|
if (requestProps) {
|
|
104
|
-
|
|
105
117
|
for (let i = 0; i < requestProps.length; i++) {
|
|
106
118
|
let prop = requestProps[i].properties[0].id;
|
|
107
119
|
let type = requestProps[i].objectId.type;
|
|
108
120
|
let instance = requestProps[i].objectId.instance;
|
|
109
121
|
let foundObject = that.getObjectMultiple(type, prop, instance, requestProps[i].properties);
|
|
110
|
-
|
|
111
122
|
if (foundObject !== null && foundObject !== undefined && foundObject !== "undefined") {
|
|
112
123
|
responseObject.push({ objectId: { type: type, instance: instance }, values: foundObject });
|
|
113
124
|
}
|
|
114
|
-
|
|
115
125
|
if (i == requestProps.length - 1) {
|
|
116
126
|
if (responseObject.length > 0) {
|
|
117
127
|
that.bacnetClient.client.readPropertyMultipleResponse(senderAddress, data.invokeId, responseObject);
|
|
@@ -127,7 +137,6 @@ class BacnetServer {
|
|
|
127
137
|
}
|
|
128
138
|
}
|
|
129
139
|
}
|
|
130
|
-
|
|
131
140
|
} catch (e) {
|
|
132
141
|
that.bacnetClient.client.errorResponse(
|
|
133
142
|
data.address,
|
|
@@ -137,24 +146,21 @@ class BacnetServer {
|
|
|
137
146
|
baEnum.ErrorCode.UNKNOWN_PROPERTY
|
|
138
147
|
);
|
|
139
148
|
}
|
|
140
|
-
|
|
141
149
|
});
|
|
142
150
|
|
|
143
151
|
that.bacnetClient.client.on('readProperty', (data) => {
|
|
144
|
-
|
|
145
152
|
try {
|
|
146
|
-
|
|
147
153
|
let objectId = data.request.objectId.type;
|
|
148
154
|
let objectInstance = data.request.objectId.instance;
|
|
149
155
|
let propId = data.request.property.id.toString();
|
|
150
|
-
|
|
151
156
|
let responseObj = that.getObject(objectId, propId, objectInstance);
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
157
|
+
if (propId == baEnum.PropertyIdentifier.OBJECT_LIST) {
|
|
158
|
+
if (data.request.property.index !== 0xFFFFFFFF) {
|
|
159
|
+
responseObj = responseObj[data.request.property.index];
|
|
160
|
+
}
|
|
155
161
|
}
|
|
156
162
|
if (responseObj !== null && responseObj !== undefined && typeof responseObj !== "undefined") {
|
|
157
|
-
that.bacnetClient.client.readPropertyResponse(data.address, data.invokeId, objectId, data.request.property, responseObj);
|
|
163
|
+
that.bacnetClient.client.readPropertyResponse(data.address, data.invokeId, data.request.objectId, data.request.property, responseObj);
|
|
158
164
|
} else {
|
|
159
165
|
that.bacnetClient.client.errorResponse(
|
|
160
166
|
data.address,
|
|
@@ -167,13 +173,17 @@ class BacnetServer {
|
|
|
167
173
|
} catch (e) {
|
|
168
174
|
console.log("Local BACnet device readProperty error: ", e);
|
|
169
175
|
}
|
|
170
|
-
|
|
171
176
|
});
|
|
172
177
|
|
|
173
178
|
//do initial iAm broadcast when BACnet server starts
|
|
174
179
|
that.bacnetClient.client.iAmResponse(that.deviceId, baEnum.Segmentation.SEGMENTED_BOTH, that.vendorId);
|
|
175
180
|
}
|
|
176
181
|
|
|
182
|
+
/**
|
|
183
|
+
* Set the name of the device.
|
|
184
|
+
*
|
|
185
|
+
* @param {string} nodeName - The new name for the device.
|
|
186
|
+
*/
|
|
177
187
|
setDeviceName(nodeName) {
|
|
178
188
|
let that = this;
|
|
179
189
|
if (typeof nodeName == "string" && nodeName !== "") {
|
|
@@ -181,6 +191,13 @@ class BacnetServer {
|
|
|
181
191
|
}
|
|
182
192
|
}
|
|
183
193
|
|
|
194
|
+
/**
|
|
195
|
+
* Adds a new object to the BacnetServer's object store based on the provided name and value.
|
|
196
|
+
*
|
|
197
|
+
* @param {string} name - The name of the object to be added.
|
|
198
|
+
* @param {number|boolean|string} value - The value of the object to be added.
|
|
199
|
+
* @returns {void}
|
|
200
|
+
*/
|
|
184
201
|
addObject(name, value) {
|
|
185
202
|
let that = this;
|
|
186
203
|
let objectType = that.getBacnetObjectType(value);
|
|
@@ -290,11 +307,18 @@ class BacnetServer {
|
|
|
290
307
|
Store_Config_Server(JSON.stringify({ objectList: that.objectList, objectStore: that.objectStore }));
|
|
291
308
|
}
|
|
292
309
|
|
|
310
|
+
/**
|
|
311
|
+
* Retrieves a specific property of an object based on the object ID, property ID, and instance number.
|
|
312
|
+
*
|
|
313
|
+
* @param {number} objectId - The ID of the object type.
|
|
314
|
+
* @param {number} propId - The ID of the property to retrieve.
|
|
315
|
+
* @param {number} instance - The instance number of the object.
|
|
316
|
+
* @returns {any} The requested property value if found, otherwise null.
|
|
317
|
+
*/
|
|
293
318
|
getObject(objectId, propId, instance) {
|
|
294
319
|
let that = this;
|
|
295
320
|
let objectGroup = that.objectStore[objectId];
|
|
296
321
|
|
|
297
|
-
|
|
298
322
|
if (Array.isArray(objectGroup)) {
|
|
299
323
|
for (let i = 0; i < objectGroup.length; i++) {
|
|
300
324
|
let object = objectGroup[i];
|
|
@@ -306,19 +330,26 @@ class BacnetServer {
|
|
|
306
330
|
}
|
|
307
331
|
}
|
|
308
332
|
} else {
|
|
309
|
-
|
|
310
333
|
return objectGroup[propId];
|
|
311
334
|
}
|
|
312
335
|
|
|
313
336
|
return null;
|
|
314
337
|
}
|
|
315
338
|
|
|
339
|
+
/**
|
|
340
|
+
* Retrieves the properties of a specific object instance from the objectStore based on the provided parameters.
|
|
341
|
+
*
|
|
342
|
+
* @param {number} objectId - The type of the object to retrieve.
|
|
343
|
+
* @param {number} propId - The property identifier to retrieve.
|
|
344
|
+
* @param {number} instance - The instance number of the object to retrieve.
|
|
345
|
+
* @param {Array} properties - An array of additional properties to retrieve along with the main property.
|
|
346
|
+
* @returns {Array|null} - An array of properties with values for the specified object instance, or null if not found.
|
|
347
|
+
*/
|
|
316
348
|
getObjectMultiple(objectId, propId, instance, properties) {
|
|
317
349
|
let that = this;
|
|
318
350
|
let objectGroup = that.objectStore[objectId];
|
|
319
351
|
|
|
320
352
|
try {
|
|
321
|
-
|
|
322
353
|
if (Array.isArray(objectGroup)) {
|
|
323
354
|
for (let i = 0; i < objectGroup.length; i++) {
|
|
324
355
|
let object = objectGroup[i];
|
|
@@ -379,6 +410,12 @@ class BacnetServer {
|
|
|
379
410
|
return null;
|
|
380
411
|
}
|
|
381
412
|
|
|
413
|
+
/**
|
|
414
|
+
* Clear all server points by resetting the object lists and object store.
|
|
415
|
+
* This method resets the object list for the device, clears the character string values,
|
|
416
|
+
* and clears the analog values. It also resets the object id numbers for analog, character string, and binary values.
|
|
417
|
+
* Finally, it stores the updated object list and object store in the server configuration.
|
|
418
|
+
*/
|
|
382
419
|
clearServerPoints() {
|
|
383
420
|
let that = this;
|
|
384
421
|
|
|
@@ -397,6 +434,14 @@ class BacnetServer {
|
|
|
397
434
|
Store_Config_Server(JSON.stringify({ objectList: that.objectList, objectStore: that.objectStore }));
|
|
398
435
|
}
|
|
399
436
|
|
|
437
|
+
/**
|
|
438
|
+
* Removes a server point from the objectStore and objectList based on the provided JSON data.
|
|
439
|
+
*
|
|
440
|
+
* @param {Object} json - The JSON data containing information about the server point to be removed.
|
|
441
|
+
* @param {string} json.body.type - The type of the server point ('SV' for CharacterString, 'BV' for BinaryValue, default is AnalogValue).
|
|
442
|
+
* @param {number} json.body.instance - The instance number of the server point to be removed.
|
|
443
|
+
* @returns {Promise<boolean>} A Promise that resolves to true if the server point is successfully removed, otherwise rejects with an error.
|
|
444
|
+
*/
|
|
400
445
|
clearServerPoint(json) {
|
|
401
446
|
let that = this;
|
|
402
447
|
return new Promise(async function (resolve, reject) {
|
|
@@ -447,6 +492,17 @@ class BacnetServer {
|
|
|
447
492
|
});
|
|
448
493
|
}
|
|
449
494
|
|
|
495
|
+
/**
|
|
496
|
+
* Retrieves all server points from the BacnetServer objectStore.
|
|
497
|
+
* Points include Analog Value, Character String Value, and Binary Value objects.
|
|
498
|
+
* Each point is represented as an object with properties:
|
|
499
|
+
* - name: The name of the object.
|
|
500
|
+
* - type: The type of the object (AV for Analog Value, SV for Character String Value, BV for Binary Value).
|
|
501
|
+
* - instance: The instance number of the object.
|
|
502
|
+
*
|
|
503
|
+
* @returns {Promise<Array>} A promise that resolves with an array of points sorted by instance number.
|
|
504
|
+
* @throws {Error} If an error occurs during the retrieval process.
|
|
505
|
+
*/
|
|
450
506
|
getServerPoints() {
|
|
451
507
|
let that = this;
|
|
452
508
|
let points = [];
|
|
@@ -502,10 +558,12 @@ class BacnetServer {
|
|
|
502
558
|
});
|
|
503
559
|
}
|
|
504
560
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
561
|
+
/**
|
|
562
|
+
* Determines the BACnet object type based on the provided value.
|
|
563
|
+
*
|
|
564
|
+
* @param {any} value - The value to determine the BACnet object type for.
|
|
565
|
+
* @returns {string|null} The BACnet object type as a string ('string', 'number', 'boolean') or null if the type is not recognized.
|
|
566
|
+
*/
|
|
509
567
|
getBacnetObjectType(value) {
|
|
510
568
|
let type = typeof value;
|
|
511
569
|
|
|
@@ -521,6 +579,13 @@ class BacnetServer {
|
|
|
521
579
|
}
|
|
522
580
|
}
|
|
523
581
|
|
|
582
|
+
/**
|
|
583
|
+
* Returns the object identifier for the given type and instance number.
|
|
584
|
+
*
|
|
585
|
+
* @param {string} type - The type of the object.
|
|
586
|
+
* @param {number} instanceNumber - The instance number of the object.
|
|
587
|
+
* @returns {number} The object identifier.
|
|
588
|
+
*/
|
|
524
589
|
getObjectIdentifier(type, instanceNumber) {
|
|
525
590
|
let that = this;
|
|
526
591
|
// manual instance numbering
|
|
@@ -533,7 +598,6 @@ class BacnetServer {
|
|
|
533
598
|
that.objectIdNumber[type]++;
|
|
534
599
|
return objectId;
|
|
535
600
|
}
|
|
536
|
-
|
|
537
601
|
}
|
|
538
602
|
|
|
539
603
|
module.exports = { BacnetServer };
|
package/bacnet_write.html
CHANGED
|
@@ -42,6 +42,10 @@
|
|
|
42
42
|
pointsToWrite: ref([]),
|
|
43
43
|
nodeService: ref(new NodeService()),
|
|
44
44
|
deviceCount: ref(),
|
|
45
|
+
showDeviceNameDialog: ref(false),
|
|
46
|
+
showPointNameDialog: ref(false),
|
|
47
|
+
deviceDisplayNameValue: ref(),
|
|
48
|
+
pointDisplayNameValue: ref(),
|
|
45
49
|
};
|
|
46
50
|
},
|
|
47
51
|
setup() {
|
|
@@ -301,6 +305,12 @@
|
|
|
301
305
|
|
|
302
306
|
return count;
|
|
303
307
|
},
|
|
308
|
+
hasMstpDevices(slotProps) {
|
|
309
|
+
if (slotProps.node.children[1] && slotProps.node.children[1].children.length > 0) {
|
|
310
|
+
return true;
|
|
311
|
+
}
|
|
312
|
+
return false;
|
|
313
|
+
},
|
|
304
314
|
settingDeviceName(slotProps) {
|
|
305
315
|
let app = this;
|
|
306
316
|
if (slotProps.node.settingDisplayName) {
|
|
@@ -461,6 +471,8 @@
|
|
|
461
471
|
"p-tree": primevue.tree,
|
|
462
472
|
"p-button": primevue.button,
|
|
463
473
|
"p-timeline": primevue.timeline,
|
|
474
|
+
"p-dialog": primevue.dialog,
|
|
475
|
+
"p-input-text": primevue.inputtext,
|
|
464
476
|
},
|
|
465
477
|
};
|
|
466
478
|
|
|
@@ -794,6 +806,49 @@
|
|
|
794
806
|
|
|
795
807
|
<div id="read-networkTree-tab" style="display:none">
|
|
796
808
|
<div id="read-networkTree-tab-content" class="networkTreeContent" style="display:none">
|
|
809
|
+
<p-dialog
|
|
810
|
+
v-model:visible="showDeviceNameDialog"
|
|
811
|
+
modal
|
|
812
|
+
header="Set Display Name"
|
|
813
|
+
:style="{ width: '25rem' }"
|
|
814
|
+
class="deviceNameDialog">
|
|
815
|
+
<div class="flex align-items-center gap-3 mb-3">
|
|
816
|
+
<label for="displayName" class="font-semibold">Name</label>
|
|
817
|
+
<p-input-text id="displayName" class="flex-auto" autocomplete="off" v-model="deviceDisplayNameValue" />
|
|
818
|
+
</div>
|
|
819
|
+
<div class="flex justify-content-end gap-2">
|
|
820
|
+
<p-button
|
|
821
|
+
class="diplayNameDialogCancel"
|
|
822
|
+
type="button"
|
|
823
|
+
label="Cancel"
|
|
824
|
+
severity="secondary"
|
|
825
|
+
@click="cancelDisplayNameDialog"></p-button>
|
|
826
|
+
<p-button class="diplayNameDialogSave" type="button" label="Save" @click="setDeviceName"></p-button>
|
|
827
|
+
</div>
|
|
828
|
+
</p-dialog>
|
|
829
|
+
|
|
830
|
+
<p-dialog
|
|
831
|
+
v-model:visible="showPointNameDialog"
|
|
832
|
+
modal
|
|
833
|
+
header="Set Point Name"
|
|
834
|
+
:style="{ width: '25rem' }"
|
|
835
|
+
class="deviceNameDialog">
|
|
836
|
+
<div class="flex align-items-center gap-3 mb-3">
|
|
837
|
+
<label for="displayName" class="font-semibold">Name</label>
|
|
838
|
+
<p-input-text id="displayName" class="flex-auto" autocomplete="off" v-model="pointDisplayNameValue" />
|
|
839
|
+
</div>
|
|
840
|
+
<div class="flex justify-content-end gap-2">
|
|
841
|
+
<p-button
|
|
842
|
+
class="diplayNameDialogCancel"
|
|
843
|
+
type="button"
|
|
844
|
+
label="Cancel"
|
|
845
|
+
severity="secondary"
|
|
846
|
+
@click="cancelPointNameDialog"></p-button>
|
|
847
|
+
<p-button class="diplayNameDialogSave" type="button" label="Save" @click="setPointName"></p-button>
|
|
848
|
+
</div>
|
|
849
|
+
</p-dialog>
|
|
850
|
+
|
|
851
|
+
|
|
797
852
|
<div>
|
|
798
853
|
<a class="countStatus" style="margin-left: 15px;">Count: {{deviceCount}} device(s)</a>
|
|
799
854
|
<button @click="getData()" class="reloadButton">
|
|
@@ -809,13 +864,19 @@
|
|
|
809
864
|
<div v-if="isDeviceActive(slotProps)" class="deviceLabelParent">
|
|
810
865
|
<b class="deviceLabel">
|
|
811
866
|
<span class="statusOnline deviceStatus dotOnline dot"></span>
|
|
812
|
-
{{slotProps.node.label}}
|
|
867
|
+
{{slotProps.node.label}}
|
|
868
|
+
<span v-if="hasMstpDevices(slotProps)" class="mstpDeviceCount"
|
|
869
|
+
> {{calculateMstpCount(slotProps)}} </span
|
|
870
|
+
>
|
|
813
871
|
</b>
|
|
814
872
|
</div>
|
|
815
873
|
<div v-else>
|
|
816
874
|
<b class="deviceLabel">
|
|
817
875
|
<span class="statusOffline deviceStatus dotOffline dot"></span>
|
|
818
876
|
{{slotProps.node.label}}
|
|
877
|
+
<span v-if="hasMstpDevices(slotProps)" class="mstpDeviceCount"
|
|
878
|
+
> {{calculateMstpCount(slotProps)}} </span
|
|
879
|
+
>
|
|
819
880
|
</b>
|
|
820
881
|
</div>
|
|
821
882
|
|
package/common.js
CHANGED
package/package.json
CHANGED
|
@@ -430,7 +430,7 @@ class Client extends events_1.EventEmitter {
|
|
|
430
430
|
const result = baNpdu.decode(buffer, offset);
|
|
431
431
|
var addressObject, destAddress;
|
|
432
432
|
// @todo a MSTP Dest & Source could occur. this case is not handled.
|
|
433
|
-
if (typeof (result.source) != 'undefined') {
|
|
433
|
+
if (typeof (result.source) != 'undefined' && result && result.source) {
|
|
434
434
|
addressObject = result.source;
|
|
435
435
|
addressObject.ip = remoteAddress;
|
|
436
436
|
} else {
|
package/treeBuilder.js
CHANGED
|
@@ -60,8 +60,18 @@ class treeBuilder {
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
// Check if the device object exists and the device name is valid
|
|
63
|
-
if (deviceObject && typeof deviceName !== 'object') {
|
|
63
|
+
//if (deviceObject && typeof deviceName !== 'object') {
|
|
64
|
+
if (deviceObject) {
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
//console.log("processDevice found deviceObject ");
|
|
68
|
+
|
|
69
|
+
//await this.processDevicePoints(device, deviceObject, "testingDeviceName", ipAddress, deviceId, index);
|
|
70
|
+
|
|
64
71
|
await this.processDevicePoints(device, deviceObject, deviceName, ipAddress, deviceId, index);
|
|
72
|
+
|
|
73
|
+
} else {
|
|
74
|
+
//console.log("Unable to find device object");
|
|
65
75
|
}
|
|
66
76
|
}
|
|
67
77
|
|
|
@@ -165,6 +175,7 @@ class treeBuilder {
|
|
|
165
175
|
children: pointProperties,
|
|
166
176
|
type: 'point',
|
|
167
177
|
parentDevice: deviceName,
|
|
178
|
+
parentDeviceId: device.getDeviceId(),
|
|
168
179
|
showAdded: false,
|
|
169
180
|
bacnetType: point.meta.objectId.type,
|
|
170
181
|
};
|
|
@@ -190,7 +201,6 @@ class treeBuilder {
|
|
|
190
201
|
this.addPointProperty(pointProperties, 'Modification Date', point.modificationDate);
|
|
191
202
|
this.addPointProperty(pointProperties, 'Program State', point.programState);
|
|
192
203
|
this.addPointProperty(pointProperties, 'Record Count', point.recordCount);
|
|
193
|
-
this.addPointProperty(pointProperties, 'Vendor Name', point.vendorName);
|
|
194
204
|
|
|
195
205
|
// Return the array of point properties
|
|
196
206
|
return pointProperties;
|
|
@@ -394,7 +404,9 @@ class treeBuilder {
|
|
|
394
404
|
* @returns {string} - The computed device name.
|
|
395
405
|
*/
|
|
396
406
|
computeDeviceName(device) {
|
|
397
|
-
if (device.
|
|
407
|
+
if (device.getDeviceName() == null && device.getDisplayName() == null) {
|
|
408
|
+
return `${this.getDeviceIpAddress(device)}-${device.getDeviceId()}`;
|
|
409
|
+
} else if (device.getDisplayName() !== null && device.getDisplayName() !== "" && device.getDisplayName() !== undefined) {
|
|
398
410
|
return device.getDisplayName();
|
|
399
411
|
}
|
|
400
412
|
return device.getDeviceName();
|