@bitpoolos/edge-bacnet 1.5.2 → 1.6.0

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