@bitpoolos/edge-bacnet 1.4.1 → 1.4.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 CHANGED
@@ -1,4 +1,25 @@
1
1
  # Changelog
2
+
3
+ ## [1.4.2] - 23-07-2024
4
+ ### Summary
5
+
6
+ Improved UI tree generation, fixing some unique scenarios where devices were not being correctly added.
7
+
8
+ Added new payload type: Individual JSON. This publishes JSON payloads to a point level, rather than to a device or property level.
9
+
10
+ Added payload and output types to be configured via Inject node. Any checked options in the inject node will take priority over read node options. Note - to use read node output types only, please deselect all inject node output options.
11
+
12
+ Added dumb BACnet parent devices to UI tree, for unique situations where MSTP devices are on a network without a parent IP device.
13
+
14
+ Bug fixes:
15
+ - Importing read list was incorrectly generating UI
16
+ - Block per device JSON payloads were not using DisplayName and JSON key
17
+ - Added node-red context variable to monitor if writeProperty event has been subscribed to, avoiding a new subscription for every node-red deploy.
18
+ - Fixed issue requiring Bacnet server to be constantly enabled in order for the node to function.
19
+
20
+
21
+ No nodes need to be deleted and replaced for this update.
22
+
2
23
  ## [1.4.1] - 09-07-2024
3
24
 
4
25
  ### Summary
package/README.md CHANGED
@@ -39,6 +39,8 @@ $ npm install @bitpoolos/edge-bacnet
39
39
  The module can be updated via the Node-RED pallette manager, or via the npm cli.
40
40
 
41
41
  ```javascript
42
+ Note: the following is our reccommendation based on error encountered in our experience. The below steps are mostly critical for updates that modify or add new properties to the UI of a node. Refer to comments in changelog.
43
+
42
44
  Upon updating to the latest version, we highly recommend:
43
45
  - Check out the changelog for latest feature notes and updates
44
46
  - Remove all @bitpoolos/edge-bacnet nodes from all flows
package/bacnet_gateway.js CHANGED
@@ -45,8 +45,6 @@ module.exports = function (RED) {
45
45
  //determines whether or not to log a found device on whoIs response
46
46
  this.toLogIam = config.toLogIam;
47
47
 
48
- this.websocketListener = null;
49
-
50
48
  node.bacnetConfig = new BacnetClientConfig(
51
49
  node.apduTimeout,
52
50
  node.localDeviceAddress,
@@ -75,6 +73,8 @@ module.exports = function (RED) {
75
73
  } else {
76
74
  node.bacnetClient = new BacnetClient(node.bacnetConfig);
77
75
  nodeContext.set("bacnetClient", node.bacnetClient);
76
+
77
+ nodeContext.set("serverWritePropEvent", false);
78
78
  }
79
79
 
80
80
  node.bacnetClient.scanMatrix = node.deviceRangeRegisters.filter((ele) => ele.enabled === true);
@@ -107,7 +107,7 @@ module.exports = function (RED) {
107
107
  node.status({});
108
108
  }, 3000);
109
109
  }
110
- if (outputType.json && !outputType.mqtt) {
110
+ if (outputType.json && !outputType.mqtt && !outputType.pointJson) {
111
111
  //json
112
112
  if (objectPropertyType.simpleWithStatus && !objectPropertyType.fullObject && !objectPropertyType.simplePayload) {
113
113
  //simpleWithStatus
@@ -119,7 +119,7 @@ module.exports = function (RED) {
119
119
  //simplePayload
120
120
  sendSimpleJson(values, readNodeName);
121
121
  }
122
- } else if (!outputType.json && outputType.mqtt) {
122
+ } else if (outputType.mqtt && !outputType.json && !outputType.pointJson) {
123
123
  //mqtt
124
124
  if (objectPropertyType.simpleWithStatus && !objectPropertyType.fullObject && !objectPropertyType.simplePayload) {
125
125
  //simpleWithStatus
@@ -131,6 +131,18 @@ module.exports = function (RED) {
131
131
  //simplePayload
132
132
  sendSimpleMqtt(values, readNodeName);
133
133
  }
134
+ } else if (outputType.pointJson && !outputType.json && !outputType.mqtt) {
135
+ //pointJson
136
+ if (objectPropertyType.simpleWithStatus && !objectPropertyType.fullObject && !objectPropertyType.simplePayload) {
137
+ //simpleWithStatus
138
+ sendSimpleWithStatus(values, readNodeName, false);
139
+ } else if (objectPropertyType.fullObject && !objectPropertyType.simplePayload && !objectPropertyType.simpleWithStatus) {
140
+ //fullObject
141
+ sendIndividualMsgJson(values, readNodeName);
142
+ } else if (objectPropertyType.simplePayload && !objectPropertyType.fullObject && !objectPropertyType.simpleWithStatus) {
143
+ //simplePayload
144
+ sendSimpleJsonPerPoint(values, readNodeName);
145
+ }
134
146
  }
135
147
  }
136
148
  });
@@ -164,22 +176,25 @@ module.exports = function (RED) {
164
176
  }
165
177
  }
166
178
 
167
- node.bacnetServer.on("writeProperty", (topic, newValue) => {
168
- let formattedTopic = topic;
169
- if (
170
- node.nodeName !== "gateway" &&
171
- node.nodeName !== "" &&
172
- node.nodeName !== "null" &&
173
- node.nodeName !== "undefined" &&
174
- typeof node.nodeName == "string"
175
- ) {
176
- formattedTopic = `${node.nodeName}/BITPOOL_EDGE_BACNET_GATEWAY/BACNET_SERVER/${topic}`;
177
- } else {
178
- formattedTopic = `BITPOOL_EDGE_BACNET_GATEWAY/BACNET_SERVER/${topic}`;
179
- }
179
+ if (node.bacnetServerEnabled == true && node.bacnetClient && node.bacnetServer && nodeContext.get("serverWritePropEvent") == false) {
180
+ node.bacnetServer.on("writeProperty", (topic, newValue) => {
181
+ let formattedTopic = topic;
182
+ if (
183
+ node.nodeName !== "gateway" &&
184
+ node.nodeName !== "" &&
185
+ node.nodeName !== "null" &&
186
+ node.nodeName !== "undefined" &&
187
+ typeof node.nodeName == "string"
188
+ ) {
189
+ formattedTopic = `${node.nodeName}/BITPOOL_EDGE_BACNET_GATEWAY/BACNET_SERVER/${topic}`;
190
+ } else {
191
+ formattedTopic = `BITPOOL_EDGE_BACNET_GATEWAY/BACNET_SERVER/${topic}`;
192
+ }
180
193
 
181
- node.send({ payload: newValue, topic: formattedTopic });
182
- });
194
+ node.send({ payload: newValue, topic: formattedTopic });
195
+ });
196
+ nodeContext.set("serverWritePropEvent", true);
197
+ }
183
198
 
184
199
  node.on("input", function (msg) {
185
200
  if (msg.topic && msg.payload !== null) {
@@ -441,7 +456,7 @@ module.exports = function (RED) {
441
456
  node.status({});
442
457
  }, 3000);
443
458
  }
444
- if (outputType.json && !outputType.mqtt) {
459
+ if (outputType.json && !outputType.mqtt && !outputType.pointJson) {
445
460
  //json
446
461
  if (objectPropertyType.simpleWithStatus && !objectPropertyType.fullObject && !objectPropertyType.simplePayload) {
447
462
  //simpleWithStatus
@@ -453,7 +468,7 @@ module.exports = function (RED) {
453
468
  //simplePayload
454
469
  sendSimpleJson(values, readNodeName);
455
470
  }
456
- } else if (!outputType.json && outputType.mqtt) {
471
+ } else if (outputType.mqtt && !outputType.json && !outputType.pointJson) {
457
472
  //mqtt
458
473
  if (objectPropertyType.simpleWithStatus && !objectPropertyType.fullObject && !objectPropertyType.simplePayload) {
459
474
  //simpleWithStatus
@@ -465,6 +480,18 @@ module.exports = function (RED) {
465
480
  //simplePayload
466
481
  sendSimpleMqtt(values, readNodeName);
467
482
  }
483
+ } else if (outputType.pointJson && !outputType.json && !outputType.mqtt) {
484
+ //pointJson
485
+ if (objectPropertyType.simpleWithStatus && !objectPropertyType.fullObject && !objectPropertyType.simplePayload) {
486
+ //simpleWithStatus
487
+ sendSimpleWithStatus(values, readNodeName, false);
488
+ } else if (objectPropertyType.fullObject && !objectPropertyType.simplePayload && !objectPropertyType.simpleWithStatus) {
489
+ //fullObject
490
+ sendIndividualMsgJson(values, readNodeName);
491
+ } else if (objectPropertyType.simplePayload && !objectPropertyType.fullObject && !objectPropertyType.simpleWithStatus) {
492
+ //simplePayload
493
+ sendSimpleJsonPerPoint(values, readNodeName);
494
+ }
468
495
  }
469
496
  }
470
497
  });
@@ -487,13 +514,6 @@ module.exports = function (RED) {
487
514
  }
488
515
  }
489
516
 
490
- function getPointName(object, pointName) {
491
- if (object.displayName) {
492
- return object.displayName;
493
- }
494
- return pointName;
495
- }
496
-
497
517
  sendSimpleWithStatus = function (values, readNodeName, isJson) {
498
518
  let devices = Object.keys(values);
499
519
  devices.forEach(function (device) {
@@ -739,6 +759,7 @@ module.exports = function (RED) {
739
759
  keys.forEach(function (key) {
740
760
  let points = values[key];
741
761
  let msgg = {};
762
+ let structuredObject = {};
742
763
  if (
743
764
  node.nodeName !== "gateway" &&
744
765
  node.nodeName !== "" &&
@@ -764,12 +785,112 @@ module.exports = function (RED) {
764
785
  msgg.topic = `BITPOOL_BACNET_GATEWAY/${key}`;
765
786
  }
766
787
  }
767
- msgg.payload = points;
788
+ for (let point in points) {
789
+ let pointName = getPointName(points[point], point);
790
+ structuredObject[pointName] = points[point];
791
+ }
792
+
793
+ msgg.payload = structuredObject;
768
794
  node.send(msgg);
769
795
  });
770
796
  }
771
797
  };
772
- }
773
798
 
799
+ sendIndividualMsgJson = function (values, readNodeName) {
800
+ if (typeof values == "object") {
801
+ let keys = Object.keys(values);
802
+ keys.forEach(function (key) {
803
+ let points = values[key];
804
+ let msgg = {};
805
+ for (let point in points) {
806
+ let pointName = getPointName(points[point], point);
807
+ if (
808
+ node.nodeName !== "gateway" &&
809
+ node.nodeName !== "" &&
810
+ node.nodeName !== "null" &&
811
+ node.nodeName !== "undefined" &&
812
+ typeof node.nodeName == "string"
813
+ ) {
814
+ if (readNodeName !== '' &&
815
+ readNodeName !== null &&
816
+ readNodeName !== undefined
817
+ ) {
818
+ msgg.topic = `${node.nodeName}/${readNodeName}/${key}/${pointName}`;
819
+ } else {
820
+ msgg.topic = `${node.nodeName}/${key}/${pointName}`;
821
+ }
822
+ } else {
823
+ if (readNodeName !== '' &&
824
+ readNodeName !== null &&
825
+ readNodeName !== undefined
826
+ ) {
827
+ msgg.topic = `BITPOOL_BACNET_GATEWAY/${readNodeName}/${key}/${pointName}`;
828
+ } else {
829
+ msgg.topic = `BITPOOL_BACNET_GATEWAY/${key}/${pointName}`;
830
+ }
831
+ }
832
+
833
+ msgg.payload = points[point];
834
+ node.send(msgg);
835
+ msgg = {};
836
+ }
837
+ });
838
+ }
839
+ }
840
+
841
+ sendSimpleJsonPerPoint = function (values, readNodeName) {
842
+ let devices = Object.keys(values);
843
+ devices.forEach(function (device) {
844
+ if (device !== "_msgid") {
845
+ let points = values[device];
846
+ let msgg = {};
847
+ for (let point in points) {
848
+ if (points[point] && "presentValue" in points[point]) {
849
+ let pointName = getPointName(points[point], point);
850
+ if (
851
+ node.nodeName !== "gateway" &&
852
+ node.nodeName !== "" &&
853
+ node.nodeName !== "null" &&
854
+ node.nodeName !== "undefined" &&
855
+ typeof node.nodeName == "string"
856
+ ) {
857
+ if (readNodeName !== '' &&
858
+ readNodeName !== null &&
859
+ readNodeName !== undefined
860
+ ) {
861
+ msgg.topic = `${node.nodeName}/${readNodeName}/${device}/${pointName}`;
862
+ } else {
863
+ msgg.topic = `${node.nodeName}/${device}/${pointName}`;
864
+ }
865
+ } else {
866
+ if (readNodeName !== '' &&
867
+ readNodeName !== null &&
868
+ readNodeName !== undefined
869
+ ) {
870
+ msgg.topic = `BITPOOL_BACNET_GATEWAY/${readNodeName}/${device}/${pointName}`;
871
+ } else {
872
+ msgg.topic = `BITPOOL_BACNET_GATEWAY/${device}/${pointName}`;
873
+ }
874
+ }
875
+ let payload = {
876
+ presentValue: points[point]["presentValue"]
877
+ };
878
+ msgg.payload = payload;
879
+ node.send(msgg);
880
+ msgg = {};
881
+ }
882
+ }
883
+ }
884
+ });
885
+ }
886
+
887
+ function getPointName(object, pointName) {
888
+ if (object.displayName) {
889
+ return object.displayName;
890
+ }
891
+ return pointName;
892
+ }
893
+
894
+ }
774
895
  RED.nodes.registerType("Bacnet-Gateway", BitpoolBacnetGatewayDevice);
775
896
  };
package/bacnet_read.html CHANGED
@@ -11,6 +11,7 @@
11
11
  events: { value: true },
12
12
  json: { value: true },
13
13
  mqtt: { value: false },
14
+ pointJson: { value: false },
14
15
  hiddenDeployToggle: { value: false },
15
16
  prevHiddenToggleState: { value: false },
16
17
  roundDecimal: { value: 2 },
@@ -725,75 +726,21 @@
725
726
  if (pointName == "deviceName") {
726
727
  app.nodeService.setDeviceDisplayName(device, point);
727
728
  treeDevice.label = point;
728
- }
729
- let pointInTree = treeDevice.children[0].children.find(
730
- (ele) => ele.data == pointName && ele.bacnetType == point.meta.objectId.type
731
- );
732
-
733
- if (pointInTree) {
734
- pointInTree.label = point.displayName;
735
-
736
- const isDeviceInReadList = app.readDevices
737
- ? app.readDevices.findIndex((ele) => ele.deviceId == treeDevice.deviceId)
738
- : -1;
739
-
740
- if (isDeviceInReadList == -1) {
741
- //no read devices present, add new
742
- let newReadParent = JSON.parse(JSON.stringify(treeDevice));
743
- newReadParent.children[0].children = [];
744
- newReadParent.children[0].children.push(pointInTree);
745
- if (app.readDevices) {
746
- app.readDevices.push(newReadParent);
747
- } else {
748
- app.readDevices = [newReadParent];
749
- }
750
- } else {
751
- // read device found, add point to existing
752
- let pointIndex = app.readDevices[isDeviceInReadList].children[0].children.findIndex(
753
- (ele) => ele.data == point.objectName
754
- );
755
- if (pointIndex == -1) {
756
- app.readDevices[isDeviceInReadList].children[0].children.push(pointInTree);
757
- } else {
758
- app.readDevices[isDeviceInReadList].children[0].children[pointIndex] = pointInTree;
759
- }
760
- }
761
- }
762
- }
763
- } else {
764
- //search mstp devices
765
- let mstpIndex = app.devices[foundIndex].children[1].children.findIndex((ele) => ele.deviceId == id);
766
-
767
- if (mstpIndex !== -1) {
768
- let mstpDevice = app.devices[foundIndex].children[1].children[mstpIndex];
769
- let device = app.deviceList.find((ele) => {
770
- if (ele.address.address) {
771
- return ele.address.address == ip && ele.deviceId == id;
772
- } else {
773
- return ele.address == ip && ele.deviceId == id;
774
- }
775
- });
776
-
777
- for (let pointName in importedDevice) {
778
- let point = importedDevice[pointName];
779
- if (pointName == "deviceName") {
780
- app.nodeService.setDeviceDisplayName(device, point);
781
- mstpDevice.label = point;
782
- }
783
- let pointInTree = mstpDevice.children[0].children.find(
784
- (ele) => ele.data == pointName && ele.bacnetType == point.meta.objectId.type
729
+ } else {
730
+ let pointInTree = treeDevice.children[0].children.find(
731
+ (ele) => ele.bacnetInstance == point.meta.objectId.instance && ele.bacnetType == point.meta.objectId.type
785
732
  );
786
733
 
787
734
  if (pointInTree) {
788
735
  pointInTree.label = point.displayName;
789
736
 
790
737
  const isDeviceInReadList = app.readDevices
791
- ? app.readDevices.findIndex((ele) => ele.deviceId == mstpDevice.deviceId)
738
+ ? app.readDevices.findIndex((ele) => ele.deviceId == treeDevice.deviceId)
792
739
  : -1;
793
740
 
794
741
  if (isDeviceInReadList == -1) {
795
742
  //no read devices present, add new
796
- let newReadParent = JSON.parse(JSON.stringify(mstpDevice));
743
+ let newReadParent = JSON.parse(JSON.stringify(treeDevice));
797
744
  newReadParent.children[0].children = [];
798
745
  newReadParent.children[0].children.push(pointInTree);
799
746
  if (app.readDevices) {
@@ -814,6 +761,74 @@
814
761
  }
815
762
  }
816
763
  }
764
+ }
765
+ } else {
766
+ //search mstp devices
767
+ let mstpIndex = -1;
768
+ let folderIndex = -1;
769
+
770
+ app.devices[foundIndex].children.forEach(function (child, index) {
771
+ let temporaryIndex = -1;
772
+ if (child.label.includes("MSTP")) {
773
+ temporaryIndex = child.children.findIndex((ele) => ele.deviceId == id);
774
+ }
775
+ if (temporaryIndex !== -1) {
776
+ mstpIndex = temporaryIndex;
777
+ folderIndex = index;
778
+ }
779
+ });
780
+
781
+ if (mstpIndex !== -1 && folderIndex !== -1) {
782
+ let mstpDevice = app.devices[foundIndex].children[folderIndex].children[mstpIndex];
783
+ let device = app.deviceList.find((ele) => {
784
+ if (ele.address.address) {
785
+ return ele.address.address == ip && ele.deviceId == id;
786
+ } else {
787
+ return ele.address == ip && ele.deviceId == id;
788
+ }
789
+ });
790
+
791
+ for (let pointName in importedDevice) {
792
+ let point = importedDevice[pointName];
793
+ if (pointName == "deviceName") {
794
+ app.nodeService.setDeviceDisplayName(device, point);
795
+ mstpDevice.label = point;
796
+ } else {
797
+ let pointInTree = mstpDevice.children[0].children.find(
798
+ (ele) => ele.bacnetInstance == point.meta.objectId.instance && ele.bacnetType == point.meta.objectId.type
799
+ );
800
+
801
+ if (pointInTree) {
802
+ pointInTree.label = point.displayName;
803
+
804
+ const isDeviceInReadList = app.readDevices
805
+ ? app.readDevices.findIndex((ele) => ele.deviceId == mstpDevice.deviceId)
806
+ : -1;
807
+
808
+ if (isDeviceInReadList == -1) {
809
+ //no read devices present, add new
810
+ let newReadParent = JSON.parse(JSON.stringify(mstpDevice));
811
+ newReadParent.children[0].children = [];
812
+ newReadParent.children[0].children.push(pointInTree);
813
+ if (app.readDevices) {
814
+ app.readDevices.push(newReadParent);
815
+ } else {
816
+ app.readDevices = [newReadParent];
817
+ }
818
+ } else {
819
+ // read device found, add point to existing
820
+ let pointIndex = app.readDevices[isDeviceInReadList].children[0].children.findIndex(
821
+ (ele) => ele.data == point.objectName
822
+ );
823
+ if (pointIndex == -1) {
824
+ app.readDevices[isDeviceInReadList].children[0].children.push(pointInTree);
825
+ } else {
826
+ app.readDevices[isDeviceInReadList].children[0].children[pointIndex] = pointInTree;
827
+ }
828
+ }
829
+ }
830
+ }
831
+ }
817
832
  } else {
818
833
  // not part of device list at all, notify user
819
834
  }
@@ -876,6 +891,8 @@
876
891
  document.getElementById("node-input-json").onclick = handleMsgTypeClick;
877
892
  document.getElementById("node-input-mqtt").checked = node.mqtt;
878
893
  document.getElementById("node-input-mqtt").onclick = handleMsgTypeClick;
894
+ document.getElementById("node-input-pointJson").checked = node.pointJson;
895
+ document.getElementById("node-input-pointJson").onclick = handleMsgTypeClick;
879
896
 
880
897
  var menu = document.querySelector(".context-menu");
881
898
  window.addEventListener("click", (event) => {
@@ -904,10 +921,16 @@
904
921
 
905
922
  function handleMsgTypeClick() {
906
923
  if (this.id == "node-input-json") {
907
- document.getElementById("node-input-mqtt").checked = !document.getElementById("node-input-mqtt").checked;
924
+ document.getElementById("node-input-mqtt").checked = false;
925
+ document.getElementById("node-input-pointJson").checked = false;
908
926
  }
909
927
  if (this.id == "node-input-mqtt") {
910
- document.getElementById("node-input-json").checked = !document.getElementById("node-input-json").checked;
928
+ document.getElementById("node-input-json").checked = false;
929
+ document.getElementById("node-input-pointJson").checked = false;
930
+ }
931
+ if (this.id == "node-input-pointJson") {
932
+ document.getElementById("node-input-json").checked = false;
933
+ document.getElementById("node-input-mqtt").checked = false;
911
934
  }
912
935
  }
913
936
 
@@ -1312,6 +1335,17 @@
1312
1335
  >Individual msgs</label
1313
1336
  >
1314
1337
  </div>
1338
+ <div style="display: flex; flex-direction: row; align-items: flex-start;">
1339
+ <input
1340
+ class="checkbox-round"
1341
+ type="checkbox"
1342
+ id="node-input-pointJson"
1343
+ style="width: 13px; margin-left: 5px;" /><label
1344
+ for="node-input-pointJson"
1345
+ style="padding-left: 20px; width: fit-content;"
1346
+ >Individual JSON</label
1347
+ >
1348
+ </div>
1315
1349
  </div>
1316
1350
  </div>
1317
1351
 
package/bacnet_read.js CHANGED
@@ -15,6 +15,7 @@ module.exports = function (RED) {
15
15
 
16
16
  this.json = config.json;
17
17
  this.mqtt = config.mqtt;
18
+ this.pointJson = config.pointJson;
18
19
  this.roundDecimal = config.roundDecimal;
19
20
  this.pointsToRead = config.pointsToRead;
20
21
  this.readDevices = config.readDevices;
@@ -55,20 +56,54 @@ module.exports = function (RED) {
55
56
  node.on('input', function (msg) {
56
57
  node.status({ fill: "blue", shape: "dot", text: "Reading values" });
57
58
 
59
+ let object_property_simplePayload = false;
60
+ let object_property_simpleWithStatus = false;
61
+ let object_property_fullObject = false;
62
+
63
+ let jsonType = false;
64
+ let mqttType = false;
65
+ let pointJsonType = false;
66
+
67
+ if (msg.simplePayload) {
68
+ object_property_simplePayload = msg.simplePayload;
69
+ } else if (msg.simpleWithStatus) {
70
+ object_property_simpleWithStatus = msg.simpleWithStatus;
71
+ } else if (msg.fullObject) {
72
+ object_property_fullObject = msg.fullObject;
73
+ } else {
74
+ object_property_simplePayload = node.object_property_simplePayload;
75
+ object_property_simpleWithStatus = node.object_property_simpleWithStatus;
76
+ object_property_fullObject = node.object_property_fullObject;
77
+ }
78
+
79
+ if (msg.json) {
80
+ jsonType = msg.json;
81
+ } else if (msg.mqtt) {
82
+ mqttType = msg.mqtt;
83
+ } else if (msg.pointJson) {
84
+ pointJsonType = msg.pointJson;
85
+ } else {
86
+ jsonType = node.json;
87
+ mqttType = node.mqtt;
88
+ pointJsonType = node.pointJson
89
+ }
90
+
58
91
  let readConfig = new ReadCommandConfig(node.pointsToRead, node.object_props, node.roundDecimal);
92
+
59
93
  let output = {
60
94
  type: "Read",
61
95
  id: node.id,
62
96
  readNodeName: node.nodeName,
63
97
  options: readConfig,
64
98
  objectPropertyType: {
65
- simplePayload: node.object_property_simplePayload,
66
- simpleWithStatus: node.object_property_simpleWithStatus,
67
- fullObject: node.object_property_fullObject
99
+ simplePayload: object_property_simplePayload,
100
+ simpleWithStatus: object_property_simpleWithStatus,
101
+ fullObject: object_property_fullObject
68
102
  },
69
103
  outputType: {
70
- json: node.json,
71
- mqtt: node.mqtt
104
+ json: jsonType,
105
+ mqtt: mqttType,
106
+ pointJson: pointJsonType
72
107
  }
73
108
  };
74
109
 
@@ -42,7 +42,104 @@
42
42
  </div>
43
43
  </div>
44
44
 
45
-
45
+ <div class="form-row" style="display: flex; width: fit-content; flex-wrap: nowrap; flex-direction: row; padding-top: 10px; margin-left: auto; margin-right: auto;">
46
+
47
+ <div
48
+ id="node-input-object_properties_group"
49
+ style="display: flex; align-items: flex-start; flex-direction: column;">
50
+ <label
51
+ for="node-input-object_properties_group"
52
+ style="display: flex; align-items: center; white-space: nowrap; text-decoration: underline;"
53
+ >
54
+ Object Properties:
55
+ </label>
56
+
57
+ <div class="objectPropertiesLabel">
58
+ <label
59
+ for="node-input-object_property_simplePayload"
60
+ style="padding-left: 4px; width: auto; align-items: start;"
61
+ class="objectPropertiesLabel">
62
+ <i class="icon-tag"></i> <span data-i18n="bitpool-bacnet.label.object_property_simplePayload"></span>
63
+ <input
64
+ style="margin-left: 5px;"
65
+ class=" objectProp"
66
+ type="checkbox"
67
+ id="node-input-object_property_simplePayload" />
68
+ <a style="white-space: nowrap; padding-left: 20px;">Simple Payload</a>
69
+ </label>
70
+ </div>
71
+
72
+ <div class="objectPropertiesLabel">
73
+ <label
74
+ for="node-input-object_property_simpleWithStatus"
75
+ style="padding-left: 4px; width: auto; align-items: start;"
76
+ class="objectPropertiesLabel">
77
+ <i class="icon-tag"></i> <span data-i18n="bitpool-bacnet.label.object_property_simpleWithStatus"></span>
78
+ <input
79
+ style="margin-left: 5px;"
80
+ class=" objectProp"
81
+ type="checkbox"
82
+ id="node-input-object_property_simpleWithStatus" />
83
+ <a style="white-space: nowrap; padding-left: 20px;">Simple With Status</a>
84
+ </label>
85
+ </div>
86
+
87
+ <div class="objectPropertiesLabel">
88
+ <label
89
+ for="node-input-object_property_fullObject"
90
+ style="padding-left: 4px; width: auto; align-items: start;"
91
+ class="objectPropertiesLabel">
92
+ <i class="icon-tag"></i> <span data-i18n="bitpool-bacnet.label.object_property_fullObject"></span>
93
+ <input
94
+ style="margin-left: 5px;"
95
+ class=" objectProp"
96
+ type="checkbox"
97
+ id="node-input-object_property_fullObject" />
98
+ <a style="white-space: nowrap; padding-left: 20px;">Full Object</a>
99
+ </label>
100
+ </div>
101
+
102
+ </div>
103
+ <div
104
+ id="node-input-msgType"
105
+ style="display: flex; align-items: flex-start; flex-direction: column; padding-left: 50px;">
106
+ <label for="node-input-msgType" style="text-decoration: underline;"> Message type: </label>
107
+ <!-- class= checkbox-round -->
108
+ <div style="display: flex; flex-direction: row; align-items: flex-start;">
109
+ <input
110
+ class="checkbox-round"
111
+ type="checkbox"
112
+ id="node-input-json"
113
+ style="width: 13px; margin-left: 5px;" /><label
114
+ for="node-input-json"
115
+ style="padding-left: 20px; width: fit-content;"
116
+ >Block per device</label
117
+ >
118
+ </div>
119
+ <div style="display: flex; flex-direction: row; align-items: flex-start;">
120
+ <input
121
+ class="checkbox-round"
122
+ type="checkbox"
123
+ id="node-input-mqtt"
124
+ style="width: 13px; margin-left: 5px;" /><label
125
+ for="node-input-mqtt"
126
+ style="padding-left: 20px; width: fit-content;"
127
+ >Individual msgs</label
128
+ >
129
+ </div>
130
+ <div style="display: flex; flex-direction: row; align-items: flex-start;">
131
+ <input
132
+ class="checkbox-round"
133
+ type="checkbox"
134
+ id="node-input-pointJson"
135
+ style="width: 13px; margin-left: 5px;" /><label
136
+ for="node-input-pointJson"
137
+ style="padding-left: 20px; width: fit-content;"
138
+ >Individual JSON</label
139
+ >
140
+ </div>
141
+ </div>
142
+ </div>
46
143
  </div>
47
144
 
48
145
  <div class="injectHeading" style="margin-top: 20px;">
@@ -373,6 +470,12 @@
373
470
  payloadType: { value: "date" },
374
471
  doPoll: { value: true },
375
472
  doDiscover: { value: false },
473
+ json: { value: false },
474
+ mqtt: { value: false },
475
+ pointJson: { value: true },
476
+ object_property_simplePayload: { value: false },
477
+ object_property_simpleWithStatus: { value: false },
478
+ object_property_fullObject: { value: true },
376
479
  },
377
480
  icon: "bitpool.svg",
378
481
  inputs: 0,
@@ -669,10 +772,26 @@
669
772
  $("#inject-time-type-select").trigger("change");
670
773
  $("#inject-time-interval-time-start").trigger("change");
671
774
 
775
+ document.getElementById("node-input-object_property_simplePayload").checked = node.object_property_simplePayload;
776
+ document.getElementById("node-input-object_property_simplePayload").onclick = handlePayloadCheckboxClick;
777
+ document.getElementById("node-input-object_property_simpleWithStatus").checked = node.object_property_simpleWithStatus;
778
+ document.getElementById("node-input-object_property_simpleWithStatus").onclick = handlePayloadCheckboxClick;
779
+ document.getElementById("node-input-object_property_fullObject").checked = node.object_property_fullObject;
780
+ document.getElementById("node-input-object_property_fullObject").onclick = handlePayloadCheckboxClick;
781
+
672
782
 
673
783
  document.getElementById("node-input-doDiscover").onclick = handleCheckboxClick;
674
784
  document.getElementById("node-input-doPoll").onclick = handleCheckboxClick;
675
785
 
786
+ document.getElementById("node-input-json").checked = node.json;
787
+ document.getElementById("node-input-json").onclick = handleMsgTypeClick;
788
+ document.getElementById("node-input-mqtt").checked = node.mqtt;
789
+ document.getElementById("node-input-mqtt").onclick = handleMsgTypeClick;
790
+ document.getElementById("node-input-pointJson").checked = node.pointJson;
791
+ document.getElementById("node-input-pointJson").onclick = handleMsgTypeClick;
792
+
793
+
794
+
676
795
  function handleCheckboxClick() {
677
796
  if (this.id == "node-input-doDiscover") {
678
797
  document.getElementById("node-input-doPoll").checked = false;
@@ -682,6 +801,36 @@
682
801
  }
683
802
  }
684
803
 
804
+ function handlePayloadCheckboxClick() {
805
+ if (this.id == "node-input-object_property_simplePayload") {
806
+ document.getElementById("node-input-object_property_fullObject").checked = false;
807
+ document.getElementById("node-input-object_property_simpleWithStatus").checked = false;
808
+ }
809
+ if (this.id == "node-input-object_property_simpleWithStatus") {
810
+ document.getElementById("node-input-object_property_fullObject").checked = false;
811
+ document.getElementById("node-input-object_property_simplePayload").checked = false;
812
+ }
813
+ if (this.id == "node-input-object_property_fullObject") {
814
+ document.getElementById("node-input-object_property_simplePayload").checked = false;
815
+ document.getElementById("node-input-object_property_simpleWithStatus").checked = false;
816
+ }
817
+ }
818
+
819
+ function handleMsgTypeClick() {
820
+ if (this.id == "node-input-json") {
821
+ document.getElementById("node-input-mqtt").checked = false;
822
+ document.getElementById("node-input-pointJson").checked = false;
823
+ }
824
+ if (this.id == "node-input-mqtt") {
825
+ document.getElementById("node-input-json").checked = false;
826
+ document.getElementById("node-input-pointJson").checked = false;
827
+ }
828
+ if (this.id == "node-input-pointJson") {
829
+ document.getElementById("node-input-json").checked = false;
830
+ document.getElementById("node-input-mqtt").checked = false;
831
+ }
832
+ }
833
+
685
834
  },
686
835
  oneditsave: function () {
687
836
  var repeat = "";
package/bitpool_inject.js CHANGED
@@ -19,28 +19,28 @@
19
19
  */
20
20
 
21
21
 
22
- module.exports = function(RED) {
22
+ module.exports = function (RED) {
23
23
  "use strict";
24
- const {scheduleTask} = require("cronosjs");
24
+ const { scheduleTask } = require("cronosjs");
25
25
 
26
26
  function BitpoolInjectNode(n) {
27
- RED.nodes.createNode(this,n);
27
+ RED.nodes.createNode(this, n);
28
28
 
29
29
  /* Handle legacy */
30
- if(!Array.isArray(n.props)){
30
+ if (!Array.isArray(n.props)) {
31
31
  n.props = [];
32
32
  n.props.push({
33
- p:'payload',
34
- v:n.payload,
35
- vt:n.payloadType
33
+ p: 'payload',
34
+ v: n.payload,
35
+ vt: n.payloadType
36
36
  });
37
37
  n.props.push({
38
- p:'topic',
39
- v:n.topic,
40
- vt:'str'
38
+ p: 'topic',
39
+ v: n.topic,
40
+ vt: 'str'
41
41
  });
42
42
  } else {
43
- for (var i=0,l=n.props.length; i<l; i++) {
43
+ for (var i = 0, l = n.props.length; i < l; i++) {
44
44
  if (n.props[i].p === 'payload' && !n.props[i].hasOwnProperty('v')) {
45
45
  n.props[i].v = n.payload;
46
46
  n.props[i].vt = n.payloadType;
@@ -56,6 +56,14 @@
56
56
  this.once = n.once;
57
57
  this.doPoll = n.doPoll;
58
58
  this.doDiscover = n.doDiscover;
59
+
60
+ this.object_property_simplePayload = n.object_property_simplePayload;
61
+ this.object_property_simpleWithStatus = n.object_property_simpleWithStatus;
62
+ this.object_property_fullObject = n.object_property_fullObject;
63
+ this.json = n.json;
64
+ this.mqtt = n.mqtt;
65
+ this.pointJson = n.pointJson;
66
+
59
67
  this.onceDelay = (n.onceDelay || 0.1) * 1000;
60
68
  this.interval_id = null;
61
69
  this.cronjob = null;
@@ -68,7 +76,7 @@
68
76
  prop.exp = RED.util.prepareJSONataExpression(val, node);
69
77
  }
70
78
  catch (err) {
71
- node.error(RED._("inject.errors.invalid-expr", {error:err.message}));
79
+ node.error(RED._("inject.errors.invalid-expr", { error: err.message }));
72
80
  prop.exp = null;
73
81
  }
74
82
  }
@@ -83,27 +91,37 @@
83
91
  if (this.repeat && !isNaN(this.repeat) && this.repeat > 0) {
84
92
  this.repeat = this.repeat * 1000;
85
93
  this.debug(RED._("inject.repeat", this));
86
- this.interval_id = setInterval(function() {
94
+ this.interval_id = setInterval(function () {
87
95
  node.emit("input", {});
88
96
  }, this.repeat);
89
97
  } else if (this.crontab) {
90
98
  this.debug(RED._("inject.crontab", this));
91
- this.cronjob = scheduleTask(this.crontab,() => { node.emit("input", {})});
99
+ this.cronjob = scheduleTask(this.crontab, () => { node.emit("input", {}) });
92
100
  }
93
101
  };
94
102
 
95
103
  if (this.once) {
96
- this.onceTimeout = setTimeout( function() {
97
- node.emit("input",{});
104
+ this.onceTimeout = setTimeout(function () {
105
+ node.emit("input", {});
98
106
  node.repeaterSetup();
99
107
  }, this.onceDelay);
100
108
  } else {
101
109
  node.repeaterSetup();
102
110
  }
103
111
 
104
- this.on("input", function(msg, send, done) {
112
+ this.on("input", function (msg, send, done) {
105
113
  msg.doDiscover = node.doDiscover;
106
114
  msg.doPoll = node.doPoll;
115
+
116
+ msg.simplePayload = node.object_property_simplePayload;
117
+ msg.simpleWithStatus = node.object_property_simpleWithStatus;
118
+ msg.fullObject = node.object_property_fullObject;
119
+
120
+ msg.json = node.json;
121
+ msg.mqtt = node.mqtt;
122
+ msg.pointJson = node.pointJson;
123
+
124
+
107
125
  var errors = [];
108
126
  var props = this.props;
109
127
  if (msg.__user_inject_props__ && Array.isArray(msg.__user_inject_props__)) {
@@ -124,14 +142,14 @@
124
142
  var val = RED.util.evaluateJSONataExpression(exp, msg);
125
143
  RED.util.setMessageProperty(msg, property, val, true);
126
144
  }
127
- catch (err) {
145
+ catch (err) {
128
146
  errors.push(err.message);
129
147
  }
130
148
  }
131
149
  return;
132
150
  }
133
151
  try {
134
- RED.util.setMessageProperty(msg,property,RED.util.evaluateNodeProperty(value, valueType, this, msg),true);
152
+ RED.util.setMessageProperty(msg, property, RED.util.evaluateNodeProperty(value, valueType, this, msg), true);
135
153
  } catch (err) {
136
154
  errors.push(err.toString());
137
155
  }
@@ -146,9 +164,9 @@
146
164
  });
147
165
  }
148
166
 
149
- RED.nodes.registerType("Bitpool-Inject",BitpoolInjectNode);
167
+ RED.nodes.registerType("Bitpool-Inject", BitpoolInjectNode);
150
168
 
151
- BitpoolInjectNode.prototype.close = function() {
169
+ BitpoolInjectNode.prototype.close = function () {
152
170
  if (this.onceTimeout) {
153
171
  clearTimeout(this.onceTimeout);
154
172
  }
@@ -160,7 +178,7 @@
160
178
  }
161
179
  };
162
180
 
163
- RED.httpAdmin.post("/inject/:id", RED.auth.needsPermission("inject.write"), function(req,res) {
181
+ RED.httpAdmin.post("/inject/:id", RED.auth.needsPermission("inject.write"), function (req, res) {
164
182
  var node = RED.nodes.getNode(req.params.id);
165
183
  if (node != null) {
166
184
  try {
@@ -170,9 +188,9 @@
170
188
  node.receive();
171
189
  }
172
190
  res.sendStatus(200);
173
- } catch(err) {
191
+ } catch (err) {
174
192
  res.sendStatus(500);
175
- node.error(RED._("inject.failed",{error:err.toString()}));
193
+ node.error(RED._("inject.failed", { error: err.toString() }));
176
194
  }
177
195
  } else {
178
196
  res.sendStatus(404);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bitpoolos/edge-bacnet",
3
- "version": "1.4.1",
3
+ "version": "1.4.2",
4
4
  "description": "A bacnet gateway for node-red",
5
5
  "dependencies": {
6
6
  "@plus4nodered/ts-node-bacnet": "^1.0.0-beta.2",
package/treeBuilder.js CHANGED
@@ -1,4 +1,5 @@
1
1
  const { Store_Config } = require("./common");
2
+ const { BacnetDevice } = require("./bacnet_device");
2
3
 
3
4
  /**
4
5
  * The `treeBuilder` class is responsible for building and processing the network tree structure.
@@ -111,6 +112,42 @@ class treeBuilder {
111
112
  }
112
113
  }
113
114
 
115
+ addEmptyIpRootDevice(childDevice) {
116
+
117
+ const ipAddress = this.getDeviceIpAddress(childDevice);
118
+
119
+ let deviceIndex = this.deviceList.findIndex(ele => ele.address === ipAddress && ele.deviceId === null && ele.deviceName === ipAddress && ele.displayName === ipAddress);
120
+
121
+ if (deviceIndex === -1) {
122
+ let newDevice = {
123
+ address: ipAddress,
124
+ isMstp: false,
125
+ deviceId: null,
126
+ maxApdu: childDevice.getMaxApdu(),
127
+ segmentation: childDevice.getSegmentation(),
128
+ vendorId: childDevice.getVendorId(),
129
+ lastSeen: null,
130
+ deviceName: ipAddress,
131
+ pointsList: [],
132
+ pointListUpdateTs: null,
133
+ manualDiscoveryMode: false,
134
+ pointListRetryCount: 0,
135
+ priorityQueueIsActive: false,
136
+ priorityQueue: [],
137
+ lastPriorityQueueTS: null,
138
+ childDevices: [childDevice.getDeviceId()],
139
+ parentDeviceId: null,
140
+ displayName: ipAddress,
141
+ protocolServicesSupported: [],
142
+ isProtocolServicesSet: false,
143
+ isInitialQuery: true,
144
+ };
145
+
146
+ let newBacnetDevice = new BacnetDevice(true, newDevice);
147
+ this.deviceList.push(newBacnetDevice);
148
+ }
149
+ }
150
+
114
151
  /**
115
152
  * Process the points of a device and add them to the render list.
116
153
  *
@@ -124,7 +161,7 @@ class treeBuilder {
124
161
  */
125
162
  async processDevicePoints(device, deviceObject, deviceName, ipAddress, deviceId, index) {
126
163
  // Initialize the list of children points
127
- const children = [];
164
+ let children = [];
128
165
 
129
166
  // Process each point in the device object
130
167
  for (const pointName in deviceObject) {
@@ -171,6 +208,7 @@ class treeBuilder {
171
208
  parentDeviceId: device.getDeviceId(),
172
209
  showAdded: false,
173
210
  bacnetType: point.meta.objectId.type,
211
+ bacnetInstance: point.meta.objectId.instance,
174
212
  };
175
213
  }
176
214
 
@@ -305,7 +343,21 @@ class treeBuilder {
305
343
  } else {
306
344
  mstpNetworkFolder.children[mstpDeviceIndex] = newDeviceEntry;
307
345
  }
346
+ } else {
347
+ //no parent found in render list
348
+
349
+ if (parentDeviceId !== null) {
350
+ let parentDeviceListIndex = this.deviceList.findIndex(ele => ele.getDeviceId == parentDeviceId);
351
+
352
+ if (parentDeviceListIndex !== -1) {
353
+ let parentDevice = this.deviceList[parentDeviceListIndex];
354
+ this.addRootDeviceFolder(parentDevice, parentDevice.getDeviceName(), parentDeviceListIndex, parentDevice.getAddress(), parentDeviceId);
355
+ }
356
+ } else {
357
+ this.addEmptyIpRootDevice(device);
358
+ }
308
359
  }
360
+
309
361
  } else {
310
362
  // Add the new device entry to the root of the render list
311
363
  if (foundIndex === -1) {