@bitpoolos/edge-bacnet 1.6.6 → 1.6.7
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 +11 -0
- package/bacnet_client.js +81 -58
- package/bacnet_device.js +27 -0
- package/package.json +4 -2
- package/resources/style.css +7 -2
- package/treeBuilder.js +6 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.6.7] - 15-01-2026
|
|
4
|
+
|
|
5
|
+
Bug fix:
|
|
6
|
+
|
|
7
|
+
- Fix UI tree rendering issue with MSTP folders
|
|
8
|
+
|
|
9
|
+
Minor update:
|
|
10
|
+
|
|
11
|
+
- Styling
|
|
12
|
+
- Added properties to device points and minor change to device point polling
|
|
13
|
+
|
|
3
14
|
## [1.6.6] - 20-11-2025
|
|
4
15
|
|
|
5
16
|
Minor update:
|
package/bacnet_client.js
CHANGED
|
@@ -362,7 +362,7 @@ class BacnetClient extends EventEmitter {
|
|
|
362
362
|
let that = this;
|
|
363
363
|
let address = device.getAddress().address;
|
|
364
364
|
let deviceId = device.getDeviceId();
|
|
365
|
-
let foundParentIndex = that.deviceList.findIndex((ele) => that.getDeviceAddress(ele) == address);
|
|
365
|
+
let foundParentIndex = that.deviceList.findIndex((ele) => that.getDeviceAddress(ele) == address && !ele.getIsMstpDevice());
|
|
366
366
|
if (foundParentIndex !== -1) {
|
|
367
367
|
that.deviceList[foundParentIndex].addChildDevice(deviceId);
|
|
368
368
|
device.setParentDeviceId(that.deviceList[foundParentIndex].getDeviceId());
|
|
@@ -1368,8 +1368,8 @@ class BacnetClient extends EventEmitter {
|
|
|
1368
1368
|
port: device.getPort(),
|
|
1369
1369
|
};
|
|
1370
1370
|
|
|
1371
|
-
// Define
|
|
1372
|
-
const
|
|
1371
|
+
// Define default properties for non-device objects
|
|
1372
|
+
const defaultProperties = [
|
|
1373
1373
|
{ id: baEnum.PropertyIdentifier.PRESENT_VALUE },
|
|
1374
1374
|
{ id: baEnum.PropertyIdentifier.DESCRIPTION },
|
|
1375
1375
|
{ id: baEnum.PropertyIdentifier.UNITS },
|
|
@@ -1384,8 +1384,66 @@ class BacnetClient extends EventEmitter {
|
|
|
1384
1384
|
{ id: baEnum.PropertyIdentifier.VENDOR_NAME },
|
|
1385
1385
|
];
|
|
1386
1386
|
|
|
1387
|
+
// Use device-specific properties for type 8, otherwise use default
|
|
1388
|
+
const propertiesToRead = type === 8 ? BacnetDevice.getDeviceObjectProperties() : defaultProperties;
|
|
1389
|
+
|
|
1390
|
+
// Function to read properties individually
|
|
1391
|
+
const readPropertiesIndividually = (resolve, reject) => {
|
|
1392
|
+
const promises = propertiesToRead.map(
|
|
1393
|
+
(property) =>
|
|
1394
|
+
new Promise((propertyResolve) => {
|
|
1395
|
+
that.client.readProperty(
|
|
1396
|
+
addressObject,
|
|
1397
|
+
{ type: type, instance: instance },
|
|
1398
|
+
property.id,
|
|
1399
|
+
readIndividualPropsOptions,
|
|
1400
|
+
(err, value) => {
|
|
1401
|
+
if (err) {
|
|
1402
|
+
propertyResolve(null);
|
|
1403
|
+
} else {
|
|
1404
|
+
propertyResolve({
|
|
1405
|
+
id: property.id,
|
|
1406
|
+
index: value.property.index,
|
|
1407
|
+
value: value.values,
|
|
1408
|
+
});
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
);
|
|
1412
|
+
})
|
|
1413
|
+
);
|
|
1414
|
+
|
|
1415
|
+
Promise.all(promises)
|
|
1416
|
+
.then((resultArray) => {
|
|
1417
|
+
// Filter out null results
|
|
1418
|
+
const validResults = resultArray.filter((result) => result !== null);
|
|
1419
|
+
|
|
1420
|
+
resolve({
|
|
1421
|
+
error: null,
|
|
1422
|
+
value: {
|
|
1423
|
+
values: [
|
|
1424
|
+
{
|
|
1425
|
+
objectId: {
|
|
1426
|
+
type: type,
|
|
1427
|
+
instance: instance,
|
|
1428
|
+
},
|
|
1429
|
+
values: validResults,
|
|
1430
|
+
},
|
|
1431
|
+
],
|
|
1432
|
+
},
|
|
1433
|
+
});
|
|
1434
|
+
})
|
|
1435
|
+
.catch(reject);
|
|
1436
|
+
};
|
|
1437
|
+
|
|
1387
1438
|
return new Promise((resolve, reject) => {
|
|
1388
|
-
//
|
|
1439
|
+
// For Device objects (type 8), skip ALL attempt - many MSTP devices don't support it
|
|
1440
|
+
// Go straight to reading individual properties for better reliability
|
|
1441
|
+
if (type === 8) {
|
|
1442
|
+
readPropertiesIndividually(resolve, reject);
|
|
1443
|
+
return;
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
// For other object types, try to read all properties at once first
|
|
1389
1447
|
that
|
|
1390
1448
|
._readObject(addressObject, type, instance, [{ id: baEnum.PropertyIdentifier.ALL }], readOptions)
|
|
1391
1449
|
.then((result) => {
|
|
@@ -1394,61 +1452,13 @@ class BacnetClient extends EventEmitter {
|
|
|
1394
1452
|
resolve(result);
|
|
1395
1453
|
} else {
|
|
1396
1454
|
// If not, proceed to read individual properties
|
|
1397
|
-
readPropertiesIndividually();
|
|
1455
|
+
readPropertiesIndividually(resolve, reject);
|
|
1398
1456
|
}
|
|
1399
1457
|
})
|
|
1400
1458
|
.catch(() => {
|
|
1401
1459
|
// On error, proceed to read individual properties
|
|
1402
|
-
readPropertiesIndividually();
|
|
1460
|
+
readPropertiesIndividually(resolve, reject);
|
|
1403
1461
|
});
|
|
1404
|
-
|
|
1405
|
-
// Function to read properties individually
|
|
1406
|
-
const readPropertiesIndividually = () => {
|
|
1407
|
-
const promises = allProperties.map(
|
|
1408
|
-
(property, index) =>
|
|
1409
|
-
new Promise((propertyResolve) => {
|
|
1410
|
-
that.client.readProperty(
|
|
1411
|
-
addressObject,
|
|
1412
|
-
{ type: type, instance: instance },
|
|
1413
|
-
property.id,
|
|
1414
|
-
readIndividualPropsOptions,
|
|
1415
|
-
(err, value) => {
|
|
1416
|
-
if (err) {
|
|
1417
|
-
propertyResolve(null);
|
|
1418
|
-
} else {
|
|
1419
|
-
propertyResolve({
|
|
1420
|
-
id: property.id,
|
|
1421
|
-
index: value.property.index,
|
|
1422
|
-
value: value.values,
|
|
1423
|
-
});
|
|
1424
|
-
}
|
|
1425
|
-
}
|
|
1426
|
-
);
|
|
1427
|
-
})
|
|
1428
|
-
);
|
|
1429
|
-
|
|
1430
|
-
Promise.all(promises)
|
|
1431
|
-
.then((resultArray) => {
|
|
1432
|
-
// Filter out null results
|
|
1433
|
-
const validResults = resultArray.filter((result) => result !== null);
|
|
1434
|
-
|
|
1435
|
-
resolve({
|
|
1436
|
-
error: null,
|
|
1437
|
-
value: {
|
|
1438
|
-
values: [
|
|
1439
|
-
{
|
|
1440
|
-
objectId: {
|
|
1441
|
-
type: type,
|
|
1442
|
-
instance: instance,
|
|
1443
|
-
},
|
|
1444
|
-
values: validResults,
|
|
1445
|
-
},
|
|
1446
|
-
],
|
|
1447
|
-
},
|
|
1448
|
-
});
|
|
1449
|
-
})
|
|
1450
|
-
.catch(reject);
|
|
1451
|
-
};
|
|
1452
1462
|
});
|
|
1453
1463
|
}
|
|
1454
1464
|
|
|
@@ -2102,10 +2112,23 @@ class BacnetClient extends EventEmitter {
|
|
|
2102
2112
|
}
|
|
2103
2113
|
break;
|
|
2104
2114
|
case baEnum.PropertyIdentifier.VENDOR_NAME:
|
|
2105
|
-
if (object.value) {
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2115
|
+
if (object.value && object.value[0] && object.value[0].value && typeof object.value[0].value == "string") {
|
|
2116
|
+
values[objectId].vendorName = object.value[0].value;
|
|
2117
|
+
}
|
|
2118
|
+
break;
|
|
2119
|
+
case baEnum.PropertyIdentifier.MODEL_NAME:
|
|
2120
|
+
if (object.value && object.value[0] && object.value[0].value && typeof object.value[0].value == "string") {
|
|
2121
|
+
values[objectId].modelName = object.value[0].value;
|
|
2122
|
+
}
|
|
2123
|
+
break;
|
|
2124
|
+
case baEnum.PropertyIdentifier.FIRMWARE_REVISION:
|
|
2125
|
+
if (object.value && object.value[0] && object.value[0].value && typeof object.value[0].value == "string") {
|
|
2126
|
+
values[objectId].firmwareRevision = object.value[0].value;
|
|
2127
|
+
}
|
|
2128
|
+
break;
|
|
2129
|
+
case baEnum.PropertyIdentifier.APPLICATION_SOFTWARE_VERSION:
|
|
2130
|
+
if (object.value && object.value[0] && object.value[0].value && typeof object.value[0].value == "string") {
|
|
2131
|
+
values[objectId].applicationSoftwareVersion = object.value[0].value;
|
|
2109
2132
|
}
|
|
2110
2133
|
break;
|
|
2111
2134
|
}
|
package/bacnet_device.js
CHANGED
|
@@ -1,4 +1,31 @@
|
|
|
1
|
+
const bacnet = require("./resources/node-bacstack-ts/dist/index.js");
|
|
2
|
+
const baEnum = bacnet.enum;
|
|
3
|
+
|
|
4
|
+
// Device Object (type 8) properties - critical properties guaranteed per ASHRAE 135
|
|
5
|
+
// These are the minimum required properties that all BACnet devices must support
|
|
6
|
+
const DEVICE_OBJECT_PROPERTIES = [
|
|
7
|
+
{ id: baEnum.PropertyIdentifier.OBJECT_IDENTIFIER },
|
|
8
|
+
{ id: baEnum.PropertyIdentifier.OBJECT_NAME },
|
|
9
|
+
{ id: baEnum.PropertyIdentifier.OBJECT_TYPE },
|
|
10
|
+
{ id: baEnum.PropertyIdentifier.SYSTEM_STATUS },
|
|
11
|
+
{ id: baEnum.PropertyIdentifier.VENDOR_NAME },
|
|
12
|
+
{ id: baEnum.PropertyIdentifier.VENDOR_IDENTIFIER },
|
|
13
|
+
{ id: baEnum.PropertyIdentifier.MODEL_NAME },
|
|
14
|
+
{ id: baEnum.PropertyIdentifier.FIRMWARE_REVISION },
|
|
15
|
+
{ id: baEnum.PropertyIdentifier.APPLICATION_SOFTWARE_VERSION },
|
|
16
|
+
{ id: baEnum.PropertyIdentifier.DESCRIPTION },
|
|
17
|
+
];
|
|
18
|
+
|
|
1
19
|
class BacnetDevice {
|
|
20
|
+
/**
|
|
21
|
+
* Returns the standardized list of properties to read for Device objects (type 8).
|
|
22
|
+
* These are critical properties guaranteed to exist on all BACnet devices per ASHRAE 135.
|
|
23
|
+
* @returns {Array} Array of property identifier objects
|
|
24
|
+
*/
|
|
25
|
+
static getDeviceObjectProperties() {
|
|
26
|
+
return DEVICE_OBJECT_PROPERTIES;
|
|
27
|
+
}
|
|
28
|
+
|
|
2
29
|
constructor(fromImport, config) {
|
|
3
30
|
let that = this;
|
|
4
31
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bitpoolos/edge-bacnet",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.7",
|
|
4
4
|
"description": "A bacnet gateway for node-red",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@plus4nodered/ts-node-bacnet": "^1.0.0-beta.2",
|
|
@@ -26,13 +26,15 @@
|
|
|
26
26
|
}
|
|
27
27
|
},
|
|
28
28
|
"keywords": [
|
|
29
|
+
"BACnet",
|
|
29
30
|
"node-red",
|
|
30
31
|
"bitpool",
|
|
31
32
|
"bitpoolos",
|
|
32
33
|
"edge",
|
|
33
34
|
"big data",
|
|
34
35
|
"iot",
|
|
35
|
-
"
|
|
36
|
+
"building automation",
|
|
37
|
+
"bms"
|
|
36
38
|
],
|
|
37
39
|
"engines": {
|
|
38
40
|
"node": ">=12.0.0"
|
package/resources/style.css
CHANGED
|
@@ -319,8 +319,13 @@
|
|
|
319
319
|
margin-left: 10px;
|
|
320
320
|
background-color: #00aeef;
|
|
321
321
|
color: #ffffff;
|
|
322
|
-
border-radius:
|
|
323
|
-
font-size:
|
|
322
|
+
border-radius: 4px;
|
|
323
|
+
font-size: 11px;
|
|
324
|
+
font-weight: 500;
|
|
325
|
+
padding: 2px 6px;
|
|
326
|
+
display: inline-block;
|
|
327
|
+
vertical-align: middle;
|
|
328
|
+
line-height: 1.2;
|
|
324
329
|
}
|
|
325
330
|
|
|
326
331
|
.pointAddedToRead {
|
package/treeBuilder.js
CHANGED
|
@@ -355,6 +355,12 @@ class treeBuilder {
|
|
|
355
355
|
this.addPointProperty(pointProperties, "Modification Date", point.modificationDate);
|
|
356
356
|
this.addPointProperty(pointProperties, "Program State", point.programState);
|
|
357
357
|
this.addPointProperty(pointProperties, "Record Count", point.recordCount);
|
|
358
|
+
|
|
359
|
+
// Add device-specific properties (type 8)
|
|
360
|
+
this.addPointProperty(pointProperties, "Vendor Name", point.vendorName);
|
|
361
|
+
this.addPointProperty(pointProperties, "Model Name", point.modelName);
|
|
362
|
+
this.addPointProperty(pointProperties, "Firmware Revision", point.firmwareRevision);
|
|
363
|
+
this.addPointProperty(pointProperties, "Application Software Version", point.applicationSoftwareVersion);
|
|
358
364
|
|
|
359
365
|
// Return the array of point properties
|
|
360
366
|
return pointProperties;
|