@bitpoolos/edge-bacnet 1.2.8 → 1.3.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/bacnet_read.html CHANGED
@@ -3,26 +3,24 @@
3
3
  -->
4
4
 
5
5
  <script type="text/javascript">
6
-
7
- RED.nodes.registerType('Bacnet-Discovery', {
8
- category: 'Bitpool BACnet',
9
- color: '#00aeef',
6
+ RED.nodes.registerType("Bacnet-Discovery", {
7
+ category: "Bitpool BACnet",
8
+ color: "#00aeef",
10
9
  defaults: {
11
- name: {value: ""},
12
- events: {value: true},
13
- json: {value: true},
14
- mqtt: {value: false},
15
- hiddenDeployToggle: {value: false},
16
- prevHiddenToggleState: {value: false},
17
- roundDecimal: {value: 2},
10
+ name: { value: "" },
11
+ events: { value: true },
12
+ json: { value: true },
13
+ mqtt: { value: false },
14
+ hiddenDeployToggle: { value: false },
15
+ prevHiddenToggleState: { value: false },
16
+ roundDecimal: { value: 2 },
18
17
  deviceList: [],
19
18
  pointList: [],
20
- pointsToRead: {value: {}},
21
- readDevices: {value: []},
19
+ pointsToRead: { value: {} },
20
+ readDevices: { value: [] },
22
21
  devicesToRead: [],
23
- object_property_simplePayload: {value: false},
24
- object_property_fullObject: {value: true}
25
-
22
+ object_property_simplePayload: { value: false },
23
+ object_property_fullObject: { value: true },
26
24
  },
27
25
  inputs: 1,
28
26
  outputs: 1,
@@ -38,7 +36,7 @@
38
36
 
39
37
  node.prevHiddenToggleState = node.hiddenDeployToggle;
40
38
 
41
- const {createApp, ref, onMounted} = Vue;
39
+ const { createApp, ref, onMounted } = Vue;
42
40
 
43
41
  //prime vue app
44
42
  const App = {
@@ -53,7 +51,13 @@
53
51
  pollFrequency: ref(),
54
52
  deviceCount: ref(),
55
53
  progressBarValue: ref(),
56
- }
54
+ rightClickedDevice: ref(),
55
+ rightClickedPoint: ref(),
56
+ showDeviceNameDialog: ref(false),
57
+ showPointNameDialog: ref(false),
58
+ deviceDisplayNameValue: ref(),
59
+ pointDisplayNameValue: ref(),
60
+ };
57
61
  },
58
62
  setup() {
59
63
  let pointList;
@@ -66,7 +70,7 @@
66
70
  expandNode(node);
67
71
  }
68
72
 
69
- expandedKeys.value = {...expandedKeys.value};
73
+ expandedKeys.value = { ...expandedKeys.value };
70
74
  };
71
75
  const collapseAll = () => {
72
76
  expandedKeys.value = {};
@@ -87,27 +91,49 @@
87
91
  expandAll,
88
92
  collapseAll,
89
93
  expandNode,
90
- }
94
+ };
91
95
  },
92
96
  mounted() {
93
97
  let app = this;
94
- this.getData();
95
- if(!node.reloadTimer) {
96
- node.reloadTimer = setInterval(() => app.getData(), 5000);
98
+ this.getData(true);
99
+ if (!node.reloadTimer) {
100
+ node.reloadTimer = setInterval(() => app.getData(false), 7000);
97
101
  }
102
+
103
+ $("#readlist-file-upload").on("change", function (event) {
104
+ const input = event.target.files[0];
105
+ const reader = new FileReader();
106
+
107
+ reader.readAsText(input);
108
+
109
+ reader.onload = function (e) {
110
+ const text = e.target.result;
111
+ let jsonPayload = JSON.parse(text);
112
+
113
+ app.nodeService.importReadList(jsonPayload).then(function (result) {
114
+ if (result) {
115
+ app.pointsToRead = jsonPayload;
116
+ app.addToReadDevices(jsonPayload);
117
+ }
118
+ });
119
+ };
120
+ });
98
121
  },
99
122
  methods: {
100
- getData() {
123
+ getData(initial) {
101
124
  let app = this;
102
125
  this.nodeService.getNetworkData().then(function (result) {
126
+ //remove after below fixed
103
127
  app.devices = result.renderList;
128
+ //end remove
129
+
104
130
  app.deviceList = result.deviceList;
105
131
  app.pointList = result.pointList;
106
132
  app.pollFrequency = parseInt(result.pollFrequency);
107
133
  app.deviceCount = result.deviceList.length;
108
134
  //progress bar percentage
109
135
  let progressVal = parseInt((result.renderListCount / result.deviceList.length) * 100);
110
- if(typeof progressVal == "number" && !isNaN(progressVal)) {
136
+ if (typeof progressVal == "number" && !isNaN(progressVal)) {
111
137
  app.progressBarValue = progressVal;
112
138
  } else {
113
139
  app.progressBarValue = 0;
@@ -121,7 +147,7 @@
121
147
  rebuildDataModel() {
122
148
  let app = this;
123
149
  app.nodeService.rebuildDataModel().then(function (result) {
124
- if(result == true){
150
+ if (result == true) {
125
151
  app.progressBarValue = 0;
126
152
  }
127
153
  });
@@ -130,37 +156,47 @@
130
156
  addAllDevices() {
131
157
  let app = this;
132
158
  app.devices.forEach(function (device) {
133
- app.addAllClicked({"node": device});
159
+ app.addAllClicked({ node: device });
134
160
  });
135
161
  app.$forceUpdate();
136
162
  },
137
163
  addAllClicked(slotProps) {
138
- //update UI
164
+ //update UI
139
165
  let app = this;
140
-
141
166
  let clone = JSON.parse(JSON.stringify(slotProps.node));
142
167
 
168
+ while (clone.children.length > 1) {
169
+ let child = clone.children[1];
170
+ if (child.label.includes('MSTP')) {
171
+ child.children.forEach(function (mstpDevice) {
172
+ app.addAllClicked({ node: mstpDevice });
173
+ });
174
+ clone.children.splice(1, 1);
175
+ }
176
+ }
177
+
143
178
  if (this.readDevices) {
144
- let foundIndex = this.readDevices.findIndex(ele => ele.key == slotProps.node.key && ele.label == slotProps.node.label);
145
- if (foundIndex == -1) this.readDevices.push(clone);
179
+ let foundIndex = this.readDevices.findIndex((ele) => ele.initialName == slotProps.node.initialName);
180
+ if (foundIndex == -1) {
181
+ this.readDevices.push(clone);
182
+ } else if (foundIndex !== -1) {
183
+ this.readDevices[foundIndex] = clone;
184
+ }
146
185
  } else {
147
186
  this.readDevices = [clone];
148
187
  }
149
-
150
- let deviceSlot = this.devices.find(ele => ele.label == slotProps.node.label);
151
- if(deviceSlot) {
188
+ let deviceSlot = this.devices.find((ele) => ele.initialName == slotProps.node.initialName);
189
+ if (deviceSlot) {
152
190
  deviceSlot.showAdded = true;
153
- deviceSlot.children.forEach(function(child) {
191
+ deviceSlot.children[0].children.forEach(function (child) {
154
192
  child.showAdded = true;
155
193
  });
156
194
  }
157
195
  slotProps.node.showAdded = true;
158
-
159
- this.$forceUpdate()
160
-
196
+ this.$forceUpdate();
161
197
  //update node-red data structure to forward to gateway
162
- let device = this.deviceList.find(ele => {
163
- if(ele.address.address) {
198
+ let device = this.deviceList.find((ele) => {
199
+ if (ele.address.address) {
164
200
  return ele.address.address == slotProps.node.ipAddr && ele.deviceId == slotProps.node.deviceId;
165
201
  } else {
166
202
  return ele.address == slotProps.node.ipAddr && ele.deviceId == slotProps.node.deviceId;
@@ -169,101 +205,47 @@
169
205
  let deviceAddress = app.getDeviceAddress(device.address);
170
206
  let key = `${deviceAddress}-${device.deviceId}`;
171
207
  let points = this.pointList[key];
172
-
173
208
  if (!this.pointsToRead[key]) {
174
209
  this.pointsToRead[key] = {};
175
210
  }
176
-
177
211
  for (let pointName in points) {
178
212
  let point = this.pointList[key][pointName];
179
213
  this.pointsToRead[key][point.objectName] = point;
180
214
  }
181
-
182
215
  //force a deploy state
183
216
  node.hiddenDeployToggle = !node.prevHiddenToggleState;
184
217
  },
185
- removeAllDevices() {
186
- let app = this;
187
-
188
- let clone = JSON.parse(JSON.stringify(app.readDevices));
189
-
190
- clone.forEach(function (device) {
191
- app.removeAllClicked({"node": device});
192
- });
193
-
194
- app.$forceUpdate();
195
-
196
- },
197
- removeAllClicked(slotProps) {
198
- let app = this;
199
- try {
200
- //update UI
201
- if (this.readDevices.length > 0) {
202
- let foundIndex = this.readDevices.findIndex(ele => ele.key == slotProps.node.key && ele.label == slotProps.node.label);
203
- if (foundIndex !== -1) this.readDevices.splice(foundIndex, 1);
204
- }
205
-
206
- let deviceSlot = this.devices.find(ele => ele.label == slotProps.node.label);
207
- if(deviceSlot) {
208
- deviceSlot.showAdded = false;
209
- deviceSlot.children.forEach(function(child) {
210
- child.showAdded = false;
211
- });
212
- }
213
- slotProps.node.showAdded = false;
214
- this.$forceUpdate();
215
-
216
- //update node-red data structure
217
- let device = this.deviceList.find(ele => {
218
- if(ele.address.address) {
219
- return ele.address.address == slotProps.node.ipAddr && ele.deviceId == slotProps.node.deviceId;
220
- } else{
221
- return ele.address == slotProps.node.ipAddr && ele.deviceId == slotProps.node.deviceId;
222
- }
223
- });
224
- let deviceAddress = app.getDeviceAddress(device.address);
225
- let key = `${deviceAddress}-${device.deviceId}`;
226
- if (this.pointsToRead[key]) {
227
- delete this.pointsToRead[key];
228
- }
229
-
230
- //force a deploy state
231
- node.hiddenDeployToggle = !node.prevHiddenToggleState;
232
-
233
- } catch(e) {
234
- //do nothing
235
- }
236
-
237
- app.$forceUpdate();
238
- },
239
218
  addPointClicked(slotProps) {
240
219
  let app = this;
241
220
  //update UI
242
221
  let parentDeviceName = slotProps.node.parentDevice;
243
- let foundDeviceIndex = this.readDevices ? this.readDevices.findIndex(ele => ele.label == parentDeviceName) : -1;
244
- let device = this.deviceList.find(ele => ele.deviceName == parentDeviceName);
222
+ let device = this.deviceList.find((ele) => ele.deviceName == parentDeviceName);
223
+ let foundDeviceIndex = this.readDevices
224
+ ? this.readDevices.findIndex((ele) => ele.deviceId == device.deviceId)
225
+ : -1;
245
226
  let deviceAddress = app.getDeviceAddress(device.address);
246
227
  let key = `${deviceAddress}-${device.deviceId}`;
247
- let parentDevice = this.devices.find(ele => ele.ipAddr == deviceAddress);
228
+ let parentDevice = this.devices.find((ele) => ele.ipAddr == deviceAddress);
248
229
  let childDevice;
249
- if(device.isMstp){
250
- let foundChildIndex = parentDevice.children[1].children.findIndex(ele => ele.label == parentDeviceName);
251
- if(foundChildIndex !== -1) {
230
+ if (device.isMstp) {
231
+ let foundChildIndex = parentDevice.children[1].children.findIndex(
232
+ (ele) => ele.initialName == parentDeviceName
233
+ );
234
+ if (foundChildIndex !== -1) {
252
235
  childDevice = parentDevice.children[1].children[foundChildIndex];
253
236
  }
254
- }
237
+ }
255
238
 
256
239
  if (foundDeviceIndex == -1) {
257
240
  //no read devices present, add new
258
241
  let newReadParent;
259
- if(childDevice) {
260
- newReadParent = {...childDevice};
261
- } else{
262
- newReadParent = {...parentDevice};
263
- }
264
- newReadParent.children = [];
265
- newReadParent.children.push(slotProps.node);
266
-
242
+ if (childDevice) {
243
+ newReadParent = JSON.parse(JSON.stringify(childDevice));
244
+ } else {
245
+ newReadParent = JSON.parse(JSON.stringify(parentDevice));
246
+ }
247
+ newReadParent.children[0].children = [];
248
+ newReadParent.children[0].children.push(slotProps.node);
267
249
  if (this.readDevices) {
268
250
  this.readDevices.push(newReadParent);
269
251
  } else {
@@ -271,10 +253,15 @@
271
253
  }
272
254
  } else {
273
255
  // read device found, add point to existing
274
- this.readDevices[foundDeviceIndex].children.push(slotProps.node);
256
+ let pointIndex = this.readDevices[foundDeviceIndex].children[0].children.findIndex(
257
+ (ele) => ele.pointName == slotProps.node.pointName
258
+ );
259
+ if (pointIndex == -1) {
260
+ this.readDevices[foundDeviceIndex].children[0].children.push(slotProps.node);
261
+ }
275
262
  }
276
263
 
277
- //set show added to true
264
+ //set show added to true
278
265
  slotProps.node.showAdded = true;
279
266
  this.$forceUpdate();
280
267
 
@@ -291,25 +278,85 @@
291
278
 
292
279
  app.$forceUpdate();
293
280
  },
281
+ removeAllDevices() {
282
+ let app = this;
283
+ let clone = JSON.parse(JSON.stringify(app.readDevices));
284
+ clone.forEach(function (device) {
285
+ app.removeAllClicked({ node: device });
286
+ });
287
+
288
+ app.pointsToRead = {};
289
+
290
+ app.$forceUpdate();
291
+ },
292
+ removeAllClicked(slotProps) {
293
+ let app = this;
294
+
295
+ try {
296
+ //update UI
297
+ if (app.readDevices.length > 0) {
298
+ let foundIndex = app.readDevices.findIndex(
299
+ (ele) => ele.key == slotProps.node.key && ele.label == slotProps.node.label
300
+ );
301
+ if (foundIndex !== -1) app.readDevices.splice(foundIndex, 1);
302
+ }
303
+
304
+ let deviceSlot = app.devices.find((ele) => ele.label == slotProps.node.label);
305
+ if (deviceSlot) {
306
+ deviceSlot.showAdded = false;
307
+ deviceSlot.children.forEach(function (child) {
308
+ child.showAdded = false;
309
+ });
310
+ }
311
+ slotProps.node.showAdded = false;
312
+ app.$forceUpdate();
313
+
314
+ //update node-red data structure
315
+ let device = app.deviceList.find((ele) => {
316
+ if (ele.address.address) {
317
+ return ele.address.address == slotProps.node.ipAddr && ele.deviceId == slotProps.node.deviceId;
318
+ } else {
319
+ return ele.address == slotProps.node.ipAddr && ele.deviceId == slotProps.node.deviceId;
320
+ }
321
+ });
322
+ let deviceAddress = app.getDeviceAddress(device.address);
323
+ let key = `${deviceAddress}-${device.deviceId}`;
324
+ if (app.pointsToRead[key]) {
325
+ delete app.pointsToRead[key];
326
+ }
327
+
328
+ //force a deploy state
329
+ node.hiddenDeployToggle = !node.prevHiddenToggleState;
330
+ } catch (e) {
331
+ //do nothing
332
+ }
333
+
334
+ app.$forceUpdate();
335
+ },
294
336
  removePointClicked(slotProps) {
295
337
  let app = this;
338
+
296
339
  //update UI
297
340
  let parentDeviceName = slotProps.node.parentDevice;
298
- let foundDeviceIndex = this.readDevices ? this.readDevices.findIndex(ele => ele.label == parentDeviceName) : -1;
299
- let parentDevice = this.devices.find(ele => ele.label == parentDeviceName);
300
- let device = this.deviceList.find(ele => ele.deviceName == parentDeviceName);
341
+ let parentDevice = this.devices.find((ele) => ele.label == parentDeviceName);
342
+ let device = this.deviceList.find((ele) => ele.deviceName == parentDeviceName);
343
+ let foundDeviceIndex = this.readDevices
344
+ ? this.readDevices.findIndex((ele) => ele.deviceId == device.deviceId)
345
+ : -1;
301
346
  let deviceAddress = app.getDeviceAddress(device.address);
302
347
  let key = `${deviceAddress}-${device.deviceId}`;
303
348
 
304
349
  if (foundDeviceIndex !== -1) {
305
- let foundIndex = this.readDevices[foundDeviceIndex].children.findIndex(ele => ele.key == slotProps.node.key && ele.pointName == slotProps.node.pointName);
306
- if (foundIndex !== -1) this.readDevices[foundDeviceIndex].children.splice(foundIndex, 1);
307
- if (this.readDevices[foundDeviceIndex].children.length == 0) {
350
+ let foundIndex = this.readDevices[foundDeviceIndex].children[0].children.findIndex(
351
+ (ele) => ele.pointName == slotProps.node.pointName
352
+ );
353
+ if (foundIndex !== -1) this.readDevices[foundDeviceIndex].children[0].children.splice(foundIndex, 1);
354
+ if (this.readDevices[foundDeviceIndex].children[0].children.length == 0) {
308
355
  this.readDevices.splice(foundDeviceIndex, 1);
309
356
  }
310
357
  }
311
358
 
312
- //set show added to true
359
+ //set show added to true
313
360
  slotProps.node.showAdded = false;
314
361
  this.$forceUpdate();
315
362
 
@@ -317,26 +364,69 @@
317
364
  let point = this.pointList[key][slotProps.node.pointName];
318
365
  if (this.pointsToRead[key][point.objectName]) delete this.pointsToRead[key][point.objectName];
319
366
  //if last point is removed, deleted whole entry
320
- if(Object.keys(this.pointsToRead[key]).length == 0) delete this.pointsToRead[key];
367
+ if (Object.keys(this.pointsToRead[key]).length == 0) delete this.pointsToRead[key];
321
368
 
322
369
  //force a deploy state
323
370
  node.hiddenDeployToggle = !node.prevHiddenToggleState;
324
371
 
325
372
  app.$forceUpdate();
326
373
  },
374
+ exportPointListCsv() {
375
+ let app = this;
376
+ let csvContent = "data:text/csv;charset=utf-8,ipAddress,deviceId,deviceName,pointName,objectType" + "\r\n";
377
+ const keys = Object.keys(app.pointList);
378
+ for (key in keys) {
379
+ const guid = keys[key];
380
+ const ipAddress = guid.split("-")[0];
381
+ const deviceId = guid.split("-")[1];
382
+ const deviceObject = app.pointList[keys[key]];
383
+ const device = app.deviceList.find((ele) => {
384
+ if (ele.address.address) {
385
+ return ele.address.address == ipAddress && ele.deviceId == deviceId;
386
+ } else {
387
+ return ele.address == ipAddress && ele.deviceId == deviceId;
388
+ }
389
+ });
390
+ const deviceName = device.deviceName;
391
+ const points = Object.keys(deviceObject);
392
+ for (point in points) {
393
+ csvContent +=
394
+ ipAddress +
395
+ "," +
396
+ deviceId +
397
+ "," +
398
+ deviceName +
399
+ "," +
400
+ points[point] +
401
+ "," +
402
+ deviceObject[points[point]].meta.objectId.type +
403
+ "\r\n";
404
+
405
+ if (parseInt(point) == points.length - 1 && parseInt(key) == keys.length - 1) {
406
+ // last iteration
407
+ var encodedUri = encodeURI(csvContent);
408
+ var link = document.createElement("a");
409
+ link.setAttribute("href", encodedUri);
410
+ link.setAttribute("download", "pointslist.csv");
411
+ document.body.appendChild(link);
412
+ link.click();
413
+ }
414
+ }
415
+ }
416
+ },
327
417
  getDeviceAddress(addr) {
328
- switch(typeof addr) {
418
+ switch (typeof addr) {
329
419
  case "object":
330
420
  return addr.address;
331
421
  case "string":
332
422
  return addr;
333
- default:
423
+ default:
334
424
  return addr;
335
425
  }
336
426
  },
337
427
  isDeviceActive(slotProps) {
338
428
  let app = this;
339
- if (((Date.now() - slotProps.node.lastSeen) / 1000) < (app.pollFrequency + 5)) {
429
+ if ((Date.now() - slotProps.node.lastSeen) / 1000 < app.pollFrequency + 5) {
340
430
  return true;
341
431
  }
342
432
  return false;
@@ -348,23 +438,400 @@
348
438
  return false;
349
439
  }
350
440
  },
441
+ hasMstpDevices(slotProps) {
442
+ if (slotProps.node.children[1] && slotProps.node.children[1].children.length > 0) {
443
+ return true;
444
+ }
445
+ return false;
446
+ },
351
447
  isSlotAdded(slotProps) {
352
448
  return slotProps.node.showAdded;
353
449
  },
354
450
  hasData() {
355
451
  if (this.devices && this.devices.length > 0) {
356
- return true
452
+ return true;
357
453
  } else {
358
454
  return false;
359
455
  }
360
- }
456
+ },
457
+ emptyTree() {
458
+ if (this.devices.length > 0) {
459
+ return true;
460
+ }
461
+ return false;
462
+ },
463
+ onDeviceRightClick(slotProps, event) {
464
+ let app = this;
465
+ app.rightClickedDevice = slotProps;
466
+ event.preventDefault();
467
+ menu.style.setProperty("--mouse-x", event.clientX + "px");
468
+ menu.style.setProperty("--mouse-y", event.clientY + "px");
469
+ menu.style.display = "block";
470
+ },
471
+ onPointRightClick(slotProps, event) {
472
+ let app = this;
473
+ app.rightClickedPoint = slotProps;
474
+ event.preventDefault();
475
+ pointMenu.style.setProperty("--mouse-x", event.clientX + "px");
476
+ pointMenu.style.setProperty("--mouse-y", event.clientY + "px");
477
+ pointMenu.style.display = "block";
478
+ },
479
+ handleContextMenuClick(type) {
480
+ let app = this;
481
+ switch (type) {
482
+ case "purgeDevice":
483
+ app.purgeDevice(app.rightClickedDevice);
484
+ break;
485
+ case "updatePoints":
486
+ app.updatePointsForDevice(app.rightClickedDevice);
487
+ break;
488
+ case "addAllPoints":
489
+ app.addAllClicked(app.rightClickedDevice);
490
+ break;
491
+ case "removeAllPoints":
492
+ app.removeAllClicked(app.rightClickedDevice);
493
+ break;
494
+ case "setDeviceName":
495
+ app.showDeviceNameDialog = true;
496
+ app.deviceDisplayNameValue = app.rightClickedDevice.node.label;
497
+ break;
498
+ default:
499
+ break;
500
+ }
501
+ },
502
+ handlePointContextMenuClick(type) {
503
+ let app = this;
504
+ switch (type) {
505
+ case "setPointName":
506
+ app.showPointNameDialog = true;
507
+ app.pointDisplayNameValue = app.rightClickedPoint.node.label;
508
+ break;
509
+ case "updatePoint":
510
+ app.updatePoint(app.rightClickedDevice);
511
+ break;
512
+ default:
513
+ break;
514
+ }
515
+ },
516
+ purgeDevice(slotProps) {
517
+ let app = this;
518
+ let device = app.deviceList.find((ele) => {
519
+ if (ele.address.address) {
520
+ return ele.address.address == slotProps.node.ipAddr && ele.deviceId == slotProps.node.deviceId;
521
+ } else {
522
+ return ele.address == slotProps.node.ipAddr && ele.deviceId == slotProps.node.deviceId;
523
+ }
524
+ });
525
+ if (device) {
526
+ app.nodeService.purgeDevice(device).then(function (result) { });
527
+ }
528
+ },
529
+ updatePointsForDevice(slotProps) {
530
+ let app = this;
531
+ let device = app.deviceList.find((ele) => {
532
+ if (ele.address.address) {
533
+ return ele.address.address == slotProps.node.ipAddr && ele.deviceId == slotProps.node.deviceId;
534
+ } else {
535
+ return ele.address == slotProps.node.ipAddr && ele.deviceId == slotProps.node.deviceId;
536
+ }
537
+ });
538
+ if (device) {
539
+ app.nodeService.updatePointsForDevice(device).then(function (result) { });
540
+ }
541
+ },
542
+ updatePoint(slotProps) {
543
+ let app = this;
544
+ let device = app.deviceList.find((ele) => {
545
+ if (ele.address.address) {
546
+ return ele.address.address == slotProps.node.ipAddr && ele.deviceId == slotProps.node.deviceId;
547
+ } else {
548
+ return ele.address == slotProps.node.ipAddr && ele.deviceId == slotProps.node.deviceId;
549
+ }
550
+ });
551
+ if (device) {
552
+ app.nodeService.updatePoint(device).then(function (result) { });
553
+ }
554
+ },
555
+ setDeviceName() {
556
+ let app = this;
557
+ const slotProps = app.rightClickedDevice;
558
+ const displayName = app.deviceDisplayNameValue;
559
+ let device = app.deviceList.find((ele) => {
560
+ if (ele.address.address) {
561
+ return ele.address.address == slotProps.node.ipAddr && ele.deviceId == slotProps.node.deviceId;
562
+ } else {
563
+ return ele.address == slotProps.node.ipAddr && ele.deviceId == slotProps.node.deviceId;
564
+ }
565
+ });
566
+
567
+ if (device) {
568
+ app.nodeService.setDeviceDisplayName(device, displayName).then(function (result) {
569
+ if (result) {
570
+ slotProps.node.label = displayName;
571
+ }
572
+ });
573
+ }
574
+
575
+ app.showDeviceNameDialog = false;
576
+ },
577
+ setPointName() {
578
+ let app = this;
579
+
580
+ const slotProps = app.rightClickedPoint;
581
+ const pointDisplayName = app.pointDisplayNameValue;
582
+ const pointName = slotProps.node.pointName;
583
+
584
+ let device = app.deviceList.find((ele) => {
585
+ return ele.deviceName == slotProps.node.parentDevice;
586
+ });
587
+
588
+ if (device) {
589
+ let deviceAddress = app.getDeviceAddress(device.address);
590
+ let deviceKey = `${deviceAddress}-${device.deviceId}`;
591
+
592
+ app.nodeService.setPointDisplayName(deviceKey, pointName, pointDisplayName).then(function (result) {
593
+ if (result) {
594
+ slotProps.node.label = pointDisplayName;
595
+ }
596
+ });
597
+ }
598
+
599
+ app.showPointNameDialog = false;
600
+ },
601
+ settingDeviceName(slotProps) {
602
+ let app = this;
603
+ if (slotProps.node.settingDisplayName) {
604
+ return true;
605
+ }
606
+ return false;
607
+ },
608
+ cancelDisplayNameDialog() {
609
+ let app = this;
610
+ app.deviceDisplayNameValue = "";
611
+ app.showDeviceNameDialog = false;
612
+ },
613
+ cancelPointNameDialog() {
614
+ let app = this;
615
+ app.pointDisplayNameValue = "";
616
+ app.showPointNameDialog = false;
617
+ },
618
+ exportReadList() {
619
+ let app = this;
620
+ let exportJson = {};
621
+ let deviceIndex = 0;
622
+ const devicesToExport = Object.keys(app.pointsToRead);
623
+
624
+ doDevice(deviceIndex);
625
+
626
+ function doDevice(deviceIndex) {
627
+ const key = devicesToExport[deviceIndex];
628
+ const device = app.pointsToRead[key];
629
+
630
+ let readDevice = app.readDevices.find(ele => ele.ipAddr == key.split("-")[0] && ele.deviceId == key.split("-")[1]);
631
+ let deviceName;
632
+
633
+ if (readDevice) {
634
+ deviceName = readDevice.label
635
+ }
636
+
637
+ exportJson[key] = {};
638
+ if (deviceName) {
639
+ exportJson[key]["deviceName"] = deviceName;
640
+ }
641
+
642
+ const devicePoints = Object.keys(device);
643
+ let pointIndex = 0;
644
+ doPoints(pointIndex);
645
+
646
+ function doPoints(pointIndex) {
647
+ let pointName = devicePoints[pointIndex];
648
+ let pointObject = device[pointName];
649
+
650
+ //formatting json payload, still in progress
651
+
652
+ exportJson[key][pointName] = {
653
+ meta: pointObject.meta,
654
+ objectName: pointObject.objectName,
655
+ displayName: pointObject.displayName,
656
+ };
657
+
658
+ // exportJson[key][pointName] = pointObject;
659
+
660
+ if (pointIndex < devicePoints.length - 1) {
661
+ //more points to read for device
662
+ pointIndex++;
663
+ doPoints(pointIndex);
664
+ } else if (pointIndex == devicePoints.length - 1) {
665
+ //all points have been iterated
666
+
667
+ if (deviceIndex < devicesToExport.length - 1) {
668
+ //more work to do
669
+ deviceIndex++;
670
+ doDevice(deviceIndex);
671
+ } else if (deviceIndex == devicesToExport.length - 1) {
672
+ //all read devices complete
673
+ let data = "text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(exportJson));
674
+ let aEle = document.getElementById("exportJSON");
675
+ aEle.setAttribute("href", "data:" + data);
676
+ aEle.setAttribute("download", "bacnet-read-list.json");
677
+ aEle.click();
678
+ }
679
+ }
680
+ }
681
+ }
682
+ },
683
+ importReadList() {
684
+ let app = this;
685
+ $("#readlist-file-upload").click();
686
+ },
687
+ addToReadDevices(pointsToRead) {
688
+ let app = this;
689
+
690
+ for (let key in pointsToRead) {
691
+ let ip = key.split("-")[0];
692
+ let id = key.split("-")[1];
693
+ const importedDevice = pointsToRead[key];
694
+ let foundIndex = app.devices.findIndex((ele) => ele.ipAddr == ip);
695
+
696
+ if (foundIndex !== -1) {
697
+ // match ip, check id
698
+ if (app.devices[foundIndex].deviceId == id) {
699
+ //found device
700
+ let treeDevice = app.devices[foundIndex];
701
+ let device = app.deviceList.find((ele) => {
702
+ if (ele.address.address) {
703
+ return ele.address.address == ip && ele.deviceId == id;
704
+ } else {
705
+ return ele.address == ip && ele.deviceId == id;
706
+ }
707
+ });
708
+
709
+ for (let pointName in importedDevice) {
710
+ let point = importedDevice[pointName];
711
+ if (pointName == "deviceName") {
712
+ app.nodeService.setDeviceDisplayName(device, point);
713
+ treeDevice.label = point;
714
+ }
715
+ let pointInTree = treeDevice.children[0].children.find(
716
+ (ele) => ele.data == pointName && ele.bacnetType == point.meta.objectId.type
717
+ );
718
+
719
+ if (pointInTree) {
720
+ pointInTree.label = point.displayName;
721
+
722
+ const isDeviceInReadList = app.readDevices
723
+ ? app.readDevices.findIndex((ele) => ele.deviceId == treeDevice.deviceId)
724
+ : -1;
725
+
726
+ if (isDeviceInReadList == -1) {
727
+ //no read devices present, add new
728
+ let newReadParent = JSON.parse(JSON.stringify(treeDevice));
729
+ newReadParent.children[0].children = [];
730
+ newReadParent.children[0].children.push(pointInTree);
731
+ if (app.readDevices) {
732
+ app.readDevices.push(newReadParent);
733
+ } else {
734
+ app.readDevices = [newReadParent];
735
+ }
736
+ } else {
737
+ // read device found, add point to existing
738
+ let pointIndex = app.readDevices[isDeviceInReadList].children[0].children.findIndex(
739
+ (ele) => ele.data == point.objectName
740
+ );
741
+ if (pointIndex == -1) {
742
+ app.readDevices[isDeviceInReadList].children[0].children.push(pointInTree);
743
+ } else {
744
+ app.readDevices[isDeviceInReadList].children[0].children[pointIndex] = pointInTree;
745
+ }
746
+ }
747
+ }
748
+ }
749
+ } else {
750
+ //search mstp devices
751
+ let mstpIndex = app.devices[foundIndex].children[1].children.findIndex((ele) => ele.deviceId == id);
752
+
753
+ if (mstpIndex !== -1) {
754
+ let mstpDevice = app.devices[foundIndex].children[1].children[mstpIndex];
755
+ let device = app.deviceList.find((ele) => {
756
+ if (ele.address.address) {
757
+ return ele.address.address == ip && ele.deviceId == id;
758
+ } else {
759
+ return ele.address == ip && ele.deviceId == id;
760
+ }
761
+ });
762
+
763
+ for (let pointName in importedDevice) {
764
+ let point = importedDevice[pointName];
765
+ if (pointName == "deviceName") {
766
+ app.nodeService.setDeviceDisplayName(device, point);
767
+ mstpDevice.label = point;
768
+ }
769
+ let pointInTree = mstpDevice.children[0].children.find(
770
+ (ele) => ele.data == pointName && ele.bacnetType == point.meta.objectId.type
771
+ );
772
+
773
+ if (pointInTree) {
774
+ pointInTree.label = point.displayName;
775
+
776
+ const isDeviceInReadList = app.readDevices
777
+ ? app.readDevices.findIndex((ele) => ele.deviceId == mstpDevice.deviceId)
778
+ : -1;
779
+
780
+ if (isDeviceInReadList == -1) {
781
+ //no read devices present, add new
782
+ let newReadParent = JSON.parse(JSON.stringify(mstpDevice));
783
+ newReadParent.children[0].children = [];
784
+ newReadParent.children[0].children.push(pointInTree);
785
+ if (app.readDevices) {
786
+ app.readDevices.push(newReadParent);
787
+ } else {
788
+ app.readDevices = [newReadParent];
789
+ }
790
+ } else {
791
+ // read device found, add point to existing
792
+ let pointIndex = app.readDevices[isDeviceInReadList].children[0].children.findIndex(
793
+ (ele) => ele.data == point.objectName
794
+ );
795
+ if (pointIndex == -1) {
796
+ app.readDevices[isDeviceInReadList].children[0].children.push(pointInTree);
797
+ } else {
798
+ app.readDevices[isDeviceInReadList].children[0].children[pointIndex] = pointInTree;
799
+ }
800
+ }
801
+ }
802
+ }
803
+ } else {
804
+ // not part of device list at all, notify user
805
+ }
806
+ }
807
+ }
808
+ }
809
+
810
+ app.$forceUpdate();
811
+ },
812
+ calculateMstpCount(slotProps) {
813
+ let count = 0;
814
+ slotProps.node.children.forEach(function (child) {
815
+ if (child.label.includes("MSTP")) {
816
+ count += child.children.length;
817
+ }
818
+ });
819
+
820
+ return count;
821
+ },
822
+ calculatePointCount(slotProps) {
823
+ let count = 0;
824
+ return count;
825
+ },
361
826
  },
362
827
  components: {
363
828
  "p-tree": primevue.tree,
364
829
  "p-button": primevue.button,
365
830
  "p-timeline": primevue.timeline,
366
- "p-progressbar": primevue.progressbar
367
- }
831
+ "p-progressbar": primevue.progressbar,
832
+ "p-dialog": primevue.dialog,
833
+ "p-input-text": primevue.inputtext,
834
+ },
368
835
  };
369
836
 
370
837
  let vueapp = createApp(App);
@@ -394,13 +861,26 @@
394
861
  document.getElementById("node-input-mqtt").checked = node.mqtt;
395
862
  document.getElementById("node-input-mqtt").onclick = handleMsgTypeClick;
396
863
 
397
- function handleCheckboxClick() {
864
+ var menu = document.querySelector(".context-menu");
865
+ window.addEventListener("click", (event) => {
866
+ menu.style.display = "none";
867
+ });
868
+
869
+ var pointMenu = document.querySelector(".point-context-menu");
870
+ window.addEventListener("click", (event) => {
871
+ pointMenu.style.display = "none";
872
+ });
398
873
 
399
- if(this.id == "node-input-object_property_simplePayload") {
400
- document.getElementById("node-input-object_property_fullObject").checked = !document.getElementById("node-input-object_property_fullObject").checked;
874
+ function handleCheckboxClick() {
875
+ if (this.id == "node-input-object_property_simplePayload") {
876
+ document.getElementById("node-input-object_property_fullObject").checked = !document.getElementById(
877
+ "node-input-object_property_fullObject"
878
+ ).checked;
401
879
  }
402
- if(this.id == "node-input-object_property_fullObject") {
403
- document.getElementById("node-input-object_property_simplePayload").checked = !document.getElementById("node-input-object_property_simplePayload").checked;
880
+ if (this.id == "node-input-object_property_fullObject") {
881
+ document.getElementById("node-input-object_property_simplePayload").checked = !document.getElementById(
882
+ "node-input-object_property_simplePayload"
883
+ ).checked;
404
884
  }
405
885
  }
406
886
 
@@ -413,33 +893,29 @@
413
893
  }
414
894
  }
415
895
 
416
- //create tabs
417
- let tabs = RED.tabs.create(
418
- {
419
- id: "node-input-read-tabs",
420
- onchange: function (tab) {
421
- $("#node-input-tabs-content").children().hide()
422
- $("#" + tab.id).show()
423
- }
424
- });
425
-
426
- tabs.addTab(
427
- {
428
- id: "read-networkTree-tab",
429
- label: "Device List"
430
- });
431
-
432
- tabs.addTab(
433
- {
434
- id: "read-readList-tab",
435
- label: "Read List"
436
- });
437
-
438
- tabs.addTab(
439
- {
440
- id: "read-properties-tab",
441
- label: "Properties"
442
- });
896
+ //create tabs
897
+ let tabs = RED.tabs.create({
898
+ id: "node-input-read-tabs",
899
+ onchange: function (tab) {
900
+ $("#node-input-tabs-content").children().hide();
901
+ $("#" + tab.id).show();
902
+ },
903
+ });
904
+
905
+ tabs.addTab({
906
+ id: "read-networkTree-tab",
907
+ label: "Device List",
908
+ });
909
+
910
+ tabs.addTab({
911
+ id: "read-readList-tab",
912
+ label: "Read List",
913
+ });
914
+
915
+ tabs.addTab({
916
+ id: "read-properties-tab",
917
+ label: "Properties",
918
+ });
443
919
 
444
920
  //remove loading animation
445
921
  let loadingGif = document.getElementById("loadingGif");
@@ -448,7 +924,6 @@
448
924
  loadingGif.style.display = "none";
449
925
  loadingText.style.display = "none";
450
926
  table.style.display = "inherit";
451
-
452
927
  },
453
928
  oneditsave: function () {
454
929
  let node = this;
@@ -456,7 +931,7 @@
456
931
  if (node.vm.$data.readDevices) node.readDevices = node.vm.$data.readDevices;
457
932
  if (node.vm.$data.pointsToRead) node.pointsToRead = node.vm.$data.pointsToRead;
458
933
  //clear reload data function
459
- if(node.reloadTimer) {
934
+ if (node.reloadTimer) {
460
935
  clearInterval(node.reloadTimer);
461
936
  node.reloadTimer = null;
462
937
  }
@@ -467,470 +942,424 @@
467
942
  if (node.vm.$data.readDevices) node.readDevices = node.vm.$data.readDevices;
468
943
  if (node.vm.$data.pointsToRead) node.pointsToRead = node.vm.$data.pointsToRead;
469
944
  //clear reload data function
470
- if(node.reloadTimer) {
945
+ if (node.reloadTimer) {
471
946
  clearInterval(node.reloadTimer);
472
947
  node.reloadTimer = null;
473
948
  }
474
- }
949
+ },
475
950
  });
476
-
477
951
  </script>
478
952
 
479
953
  <script type="text/html" data-template-name="Bacnet-Discovery">
480
- <style>
481
-
482
- .p-treenode-label {
483
- color: black;
484
- width: 100%;
485
- }
486
- .p-tree {
487
- background: inherit !important;
488
- border: inherit !important;
489
- padding-right: 0px !important;
490
- }
491
- .p-button {
492
- margin-right: .5rem;
493
- }
494
- .addPointButton {
495
- float: right;
496
- }
497
- .minusPointButton {
498
- float: right;
499
- }
500
- .addPointButton:hover, .minusPointButton:hover {
501
- background: #d5d5d5 !important;
502
- }
503
- .pointLabel {
504
- font-weight: 400;
505
- }
506
- .deviceLabel {
507
- font-weight: 100;
508
- }
509
- .removeAllButton {
510
- float: right;
511
- }
512
- .addAllButton {
513
- float: right;
514
- }
515
- .bacnetbutton {
516
- background: none;
517
- border: none;
518
- color: black;
519
- font-weight: 400;
520
- }
521
- .bacnetbutton:hover {
522
- background: #f0f0f0;
523
- border-radius: 10px;
524
- }
525
- .allFunctionsText {
526
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol" !important;
527
- padding-left: 5px;
528
- }
529
- .p-treenode-children > li > .p-treenode-children > li {
530
- font-size: 14px;
531
- /* height: 35px; */
532
- color: #b5b5b5 !important;
533
- }
534
- .p-treenode-children > li > .p-treenode-children > .p-treenode-label {
535
- color: #b5b5b5 !important;
536
- }
537
- /*
538
- .p-treenode-children > li > .p-treenode-children > li:last-child {
539
- padding-bottom: 30px;
540
- }
541
- */
542
- .p-tree-toggler:enabled:hover {
543
- background: #d5d5d5 !important;
544
- }
545
- .p-tree-toggler:focus {
546
- border: none !important;
547
- box-shadow: none !important;
548
- }
549
- .deviceStatus {
550
- font-size: 10px;
551
- }
552
- .statusOnline {
553
- color: #11c511;
554
- }
555
- .statusOffline {
556
- color: red;
557
- }
558
-
559
- .deviceLabel {
560
- position: absolute;
561
- }
562
- .objectPropertiesLabel {
563
- display: flex !important;
564
- flex-direction: row;
565
- align-items: center;
566
- }
567
- .p-tree .p-tree-container .p-treenode .p-treenode-content:focus {
568
- outline: 0 none !important;
569
- outline-offset: 0 !important;
570
- box-shadow: inset 0 0 0 1px #bdbdbd !important;
571
- }
572
- .checkbox-round {
573
- width: 13px !important;
574
- height: 13px !important;
575
- background-color: white;
576
- border-radius: 50%;
577
- vertical-align: middle;
578
- border: 1px solid #ddd;
579
- appearance: none;
580
- -webkit-appearance: none;
581
- outline: none;
582
- cursor: pointer;
583
- }
584
- .checkbox-round:checked {
585
- background-color: #00AEEF;
586
- }
587
- .checkbox-round:focus {
588
- outline: none !important;
589
- }
590
- .p-tree-filter:focus, .p-tree-filter:focus-visible, .p-inputtext:enabled:hover {
591
- box-shadow: inset 0 0 0 0.15rem #dfdcdc !important;
592
- border-color: transparent !important;
593
- }
594
- .bacnetbutton > .pi {
595
- display: flex;
596
- }
597
- .p-tree-container {
598
- margin: 0 !important;
599
- }
600
- .reloadButtonIcon {
601
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol" !important;
602
- }
603
- .removeAllDevicesButton {
604
- border: none;
605
- background: none;
606
- font-size: 14px !important;
607
- float: right;
608
- display: flex;
609
- align-items: center;
610
- }
611
- .removeAllDevicesButton:hover {
612
- background-color: #d5d5d5;
613
- border-radius: 10px;
614
- }
615
- .reloadButton {
616
- border: none;
617
- background: none;
618
- font-size: 14px !important;
619
- float: right;
620
- }
621
- .rebuildDataButton {
622
- border: none;
623
- background: none;
624
- font-size: 14px !important;
625
- float: right;
626
- }
627
- .reloadButton:hover {
628
- background-color: #d5d5d5;
629
- border-radius: 10px;
630
- }
631
- .rebuildDataButton:hover {
632
- background-color: #d5d5d5;
633
- border-radius: 10px;
634
- }
635
- .headerDiv {
636
- display: flex;
637
- flex-direction: row;
638
- flex-wrap: nowrap;
639
- justify-content: space-around;
640
- height: 20px;
641
- }
642
- .msgTypeDiv {
643
- display: flex;
644
- align-items: flex-start;
645
- flex-direction: column;
646
- }
647
- .p-progressbar .p-progressbar-value {
648
- background: #00AEEF !important;
649
- }
650
- .p-treenode-label {
651
- overflow: hidden;
652
- white-space: nowrap;
653
- text-overflow: ellipsis;
654
- }
655
- .buttonGroup {
656
- padding-left: 7px;
657
- }
658
- #read-readList-tab {
659
- display: flex;
660
- flex-direction: column;
661
- }
662
- .removeAllDevicesDiv {
663
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
664
- padding-right: 20px;
665
- }
666
-
667
- .p-tree .p-treenode-children {
668
- padding: 0 !important;
669
- }
670
-
671
- .p-tree-toggler .p-link {
672
- width: 25px !important;
673
- }
674
-
675
-
676
- /*
677
- .p-treenode-content {
678
- height: 15px;
679
- }
680
- */
681
-
682
-
683
- </style>
684
-
685
- <div class="form-row">
686
- <label for="node-input-name"><i class="icon-tag"></i><span data-i18n="bitpool-bacnet.label.name"></span> Name</label>
687
- <input type="text" id="node-input-name" placeholder="Name">
688
- </div>
689
-
690
- <div class='form-row node-input-read-tabs-row'>
691
- <ul style='min-width:600px;margin-bottom:20px' id='node-input-read-tabs'></ul>
692
- </div>
693
-
694
- <div id='node-input-tabs-content'>
695
-
696
- <!--
954
+ <div class="form-row">
955
+ <label for="node-input-name"><i class="icon-tag"></i><span data-i18n="bitpool-bacnet.label.name"></span> Name</label>
956
+ <input type="text" id="node-input-name" placeholder="Name" />
957
+ </div>
958
+
959
+ <div class="form-row node-input-read-tabs-row">
960
+ <ul style="min-width:600px;margin-bottom:20px" id="node-input-read-tabs"></ul>
961
+ </div>
962
+
963
+ <div id="node-input-tabs-content">
964
+ <!-- Start Device Context Menu -->
965
+ <ul
966
+ class="red-ui-menu red-ui-menu-dropdown red-ui-menu-dropdown-direction-right red-ui-menu-dropdown-noicons red-ui-menu-dropdown-submenus context-menu">
967
+ <li class="context-menu-item" @click="handleContextMenuClick('purgeDevice')">
968
+ <a class="red-ui-menu-label">
969
+ <i class="pi pi-wrench context-menu-icon"></i>
970
+ <span class="context-menu-item-text">Purge Device</span>
971
+ </a>
972
+ </li>
973
+ <li class="context-menu-item" @click="handleContextMenuClick('updatePoints')">
974
+ <a class="red-ui-menu-label">
975
+ <i class="pi pi-refresh context-menu-icon"></i>
976
+ <span class="context-menu-item-text">Update Points</span>
977
+ </a>
978
+ </li>
979
+ <li class="red-ui-menu-divider"></li>
980
+ <li class="context-menu-item" @click="handleContextMenuClick('addAllPoints')">
981
+ <a class="red-ui-menu-label">
982
+ <i class="pi pi-plus-circle context-menu-icon"></i>
983
+ <span class="context-menu-item-text">Add All Points </span>
984
+ </a>
985
+ </li>
986
+ <li class="context-menu-item" @click="handleContextMenuClick('removeAllPoints')">
987
+ <a class="red-ui-menu-label">
988
+ <i class="pi pi-minus-circle context-menu-icon"></i>
989
+ <span class="context-menu-item-text">Remove All Points</span>
990
+ </a>
991
+ </li>
992
+ <li class="red-ui-menu-divider"></li>
993
+ <li class="context-menu-item" @click="handleContextMenuClick('setDeviceName')">
994
+ <a class="red-ui-menu-label">
995
+ <i class="pi pi-pencil context-menu-icon"></i>
996
+ <span class="context-menu-item-text">Set Device Name</span>
997
+ </a>
998
+ </li>
999
+ </ul>
1000
+ <!-- End Device Context Menu -->
1001
+
1002
+ <!-- Start Point Context Menu -->
1003
+ <ul
1004
+ class="red-ui-menu red-ui-menu-dropdown red-ui-menu-dropdown-direction-right red-ui-menu-dropdown-noicons red-ui-menu-dropdown-submenus point-context-menu">
1005
+ <li class="context-menu-item" @click="handlePointContextMenuClick('setPointName')">
1006
+ <a class="red-ui-menu-label">
1007
+ <i class="pi pi-pencil context-menu-icon"></i>
1008
+ <span class="context-menu-item-text">Set Point Name</span>
1009
+ </a>
1010
+ </li>
1011
+ <li class="red-ui-menu-divider"></li>
1012
+ <li class="context-menu-item" @click="handlePointContextMenuClick('updatePoint')">
1013
+ <a class="red-ui-menu-label">
1014
+ <i class="pi pi-refresh context-menu-icon"></i>
1015
+ <span class="context-menu-item-text">Update Point</span>
1016
+ </a>
1017
+ </li>
1018
+ </ul>
1019
+ <!-- End Point Context Menu -->
1020
+
1021
+ <!--
697
1022
  *
698
1023
  *
699
1024
  * Network Tree tab
700
1025
  *
701
1026
  *
702
1027
  -->
703
- <div id='read-networkTree-tab' style='display:none'>
704
-
705
- <div id='read-networkTree-tab-content' class="networkTreeContent" style='display:none'>
706
-
707
- <div class="headerDiv">
708
- <a class="countStatus" style="margin-left: 15px;">Count: {{deviceCount}} device(s)</a>
709
- <p-progressbar :value="progressBarValue" :show-value="true" style="width: 300px; height: 20px;"></p-progressbar>
710
- <div class="buttonGroup" style="padding-left: 15px;">
711
- <button @click="rebuildDataModel()" class="rebuildDataButton" title="Rebuild Data Model" >
712
- <i class="pi pi-wrench" style="color: #ff0000;"></i>
713
- </button>
714
- <button @click="getData()" class="reloadButton" title="Reload Data">
715
- <i class="pi pi-refresh" style="color: #00AEEF;"></i>
716
- </button>
717
- <button @click="addAllDevices()" class="reloadButton" title="Add all devices">
718
- <i class="pi pi-plus" style="color: #00AEEF;"></i>
719
- </button>
720
- </div>
721
- </div>
722
-
723
-
724
- <div id="deviceListApp">
725
- <p-tree :value="devices" selectable="false" :filter="true" filterMode="lenient" filterPlaceholder="No results found." v-if="hasData()">
726
- <template #device="slotProps">
727
- <div v-if="isDeviceActive(slotProps)" class="deviceLabelParent">
728
- <b class="deviceLabel">{{slotProps.node.label}} <b class="statusOnline deviceStatus">Online</b></b>
729
- </div>
730
- <div v-else>
731
- <b class="deviceLabel">{{slotProps.node.label}} <b class="statusOffline deviceStatus">Offline</b></b>
732
- </div>
733
-
734
- <template v-if="isDeviceAdded(slotProps)">
735
- <button class="addAllButton bacnetbutton">
736
- <i class="pi pi-check-circle " style="color: #00AEEF;"></i>
737
- </button>
738
- </template>
739
- <template v-else>
740
- <button @click="addAllClicked(slotProps)" class="addAllButton bacnetbutton">
741
- <i class="pi pi-plus-circle "><a class="allFunctionsText">Add all</a></i>
742
- </button>
743
- </template>
744
-
745
- <button @click="removeAllClicked(slotProps, this)" class="removeAllButton bacnetbutton">
746
- <i class="pi pi-minus-circle "><a class="allFunctionsText">Remove all</a></i>
747
- </button>
748
- </template>
749
-
750
- <!-- <template #pointFolder="slotProps">
751
-
752
- </template>
753
-
754
-
755
- <template #deviceFolder="slotProps">
756
-
757
- </template> -->
758
-
759
- <template #point="slotProps" v-model:class="pointContent">
760
- <b class="pointLabel">{{slotProps.node.label}}</b>
761
-
762
- <template v-if="isSlotAdded(slotProps)">
763
- <button class="addPointButton bacnetbutton">
764
- <i class="pi pi-check-circle " style="color: #00AEEF;"></i>
765
- </button>
766
- </template>
767
- <template v-else>
768
- <button @click="addPointClicked(slotProps, this)" class="addPointButton bacnetbutton">
769
- <i class="pi pi-plus-circle "></i>
770
- </button>
771
- </template>
772
-
773
- <button @click="removePointClicked(slotProps, this)" class="minusPointButton bacnetbutton">
774
- <i class="pi pi-minus-circle "></i>
775
- </button>
776
- </template>
777
- </p-tree>
778
- <div v-else style="text-align: center; padding-top: 20px;">
779
- <a>No devices found.</a>
780
- </div>
781
- </div>
1028
+ <div id="read-networkTree-tab" style="display:none">
1029
+ <div id="read-networkTree-tab-content" class="networkTreeContent" style="display:none">
1030
+ <p-dialog
1031
+ v-model:visible="showDeviceNameDialog"
1032
+ modal
1033
+ header="Set Display Name"
1034
+ :style="{ width: '25rem' }"
1035
+ class="deviceNameDialog">
1036
+ <div class="flex align-items-center gap-3 mb-3">
1037
+ <label for="displayName" class="font-semibold">Name</label>
1038
+ <p-input-text id="displayName" class="flex-auto" autocomplete="off" v-model="deviceDisplayNameValue" />
1039
+ </div>
1040
+ <div class="flex justify-content-end gap-2">
1041
+ <p-button
1042
+ class="diplayNameDialogCancel"
1043
+ type="button"
1044
+ label="Cancel"
1045
+ severity="secondary"
1046
+ @click="cancelDisplayNameDialog"></p-button>
1047
+ <p-button class="diplayNameDialogSave" type="button" label="Save" @click="setDeviceName"></p-button>
1048
+ </div>
1049
+ </p-dialog>
1050
+
1051
+ <p-dialog
1052
+ v-model:visible="showPointNameDialog"
1053
+ modal
1054
+ header="Set Point Name"
1055
+ :style="{ width: '25rem' }"
1056
+ class="deviceNameDialog">
1057
+ <div class="flex align-items-center gap-3 mb-3">
1058
+ <label for="displayName" class="font-semibold">Name</label>
1059
+ <p-input-text id="displayName" class="flex-auto" autocomplete="off" v-model="pointDisplayNameValue" />
1060
+ </div>
1061
+ <div class="flex justify-content-end gap-2">
1062
+ <p-button
1063
+ class="diplayNameDialogCancel"
1064
+ type="button"
1065
+ label="Cancel"
1066
+ severity="secondary"
1067
+ @click="cancelPointNameDialog"></p-button>
1068
+ <p-button class="diplayNameDialogSave" type="button" label="Save" @click="setPointName"></p-button>
1069
+ </div>
1070
+ </p-dialog>
1071
+
1072
+ <div class="headerDiv">
1073
+ <a class="countStatus" style="margin-left: 15px;">Count: {{deviceCount}} device(s)</a>
1074
+ <p-progressbar :value="progressBarValue" :show-value="true" style="width: 300px; height: 20px;"></p-progressbar>
1075
+ <div class="buttonGroup" style="padding-left: 15px;">
1076
+ <button @click="rebuildDataModel()" class="rebuildDataButton" title="Rebuild Data Model">
1077
+ <i class="pi pi-wrench" style="color: #ff0000;"></i>
1078
+ </button>
1079
+ <button @click="getData()" class="reloadButton" title="Reload Data">
1080
+ <i class="pi pi-refresh" style="color: #00AEEF;"></i>
1081
+ </button>
1082
+ <button @click="addAllDevices()" class="reloadButton" title="Add all devices">
1083
+ <i class="pi pi-plus" style="color: #00AEEF;"></i>
1084
+ </button>
1085
+ </div>
1086
+ </div>
1087
+
1088
+ <div id="deviceListApp" class="readDeviceList">
1089
+ <p-tree
1090
+ :value="devices"
1091
+ selectable="false"
1092
+ :filter="true"
1093
+ filterMode="lenient"
1094
+ filterPlaceholder="No results found."
1095
+ v-if="hasData()">
1096
+ <template #device="slotProps">
1097
+ <div @contextmenu="onDeviceRightClick(slotProps, $event)" class="p-treenode-label">
1098
+ <div v-if="isDeviceActive(slotProps)" class="deviceLabelParent">
1099
+ <b class="deviceLabel">
1100
+ <span class="statusOnline deviceStatus dotOnline dot"></span>
1101
+ {{slotProps.node.label}}
1102
+ <span v-if="hasMstpDevices(slotProps)" class="mstpDeviceCount"
1103
+ >&nbsp; {{calculateMstpCount(slotProps)}} &nbsp;</span
1104
+ >
1105
+ </b>
782
1106
  </div>
783
1107
 
784
- <img id="loadingGif" src="resources/@bitpoolos/edge-bacnet/BitpoolCube_Blue_350.gif" style="width: 70px; display: block; margin-left: auto; margin-right: auto;" />
785
- <p id="loadingText" style="display: block; margin-left: auto; margin-right: auto; text-align: center; color: #505050;">Loading current network info</p>
786
- </div>
1108
+ <div v-else>
1109
+ <b class="deviceLabel">
1110
+ <span class="statusOffline deviceStatus dotOffline dot"></span>
1111
+ {{slotProps.node.label}}
1112
+ <span v-if="hasMstpDevices(slotProps)" class="mstpDeviceCount"
1113
+ >&nbsp; {{calculateMstpCount(slotProps)}} &nbsp;</span
1114
+ >
1115
+ </b>
1116
+ </div>
1117
+
1118
+ <template v-if="isDeviceAdded(slotProps)">
1119
+ <button class="addAllButton bacnetbutton">
1120
+ <i class="pi pi-check-circle " style="color: #00AEEF;"></i>
1121
+ </button>
1122
+ </template>
1123
+ <template v-else>
1124
+ <button @click="addAllClicked(slotProps)" class="addAllButton bacnetbutton">
1125
+ <i class="pi pi-plus-circle "><a class="allFunctionsText">Add all</a></i>
1126
+ </button>
1127
+ </template>
1128
+
1129
+ <button @click="removeAllClicked(slotProps, this)" class="removeAllButton bacnetbutton">
1130
+ <i class="pi pi-minus-circle "><a class="allFunctionsText">Remove all</a></i>
1131
+ </button>
1132
+ </div>
1133
+ </template>
1134
+
1135
+ <template #point="slotProps" v-model:class="pointContent">
1136
+ <div @contextmenu="onPointRightClick(slotProps, $event)">
1137
+ <b class="pointLabel">{{slotProps.node.label}}</b>
1138
+
1139
+ <template v-if="isSlotAdded(slotProps)">
1140
+ <button class="addPointButton bacnetbutton">
1141
+ <i class="pi pi-check-circle " style="color: #00AEEF;"></i>
1142
+ </button>
1143
+ </template>
1144
+ <template v-else>
1145
+ <button @click="addPointClicked(slotProps, this)" class="addPointButton bacnetbutton">
1146
+ <i class="pi pi-plus-circle "></i>
1147
+ </button>
1148
+ </template>
1149
+
1150
+ <button @click="removePointClicked(slotProps, this)" class="minusPointButton bacnetbutton">
1151
+ <i class="pi pi-minus-circle "></i>
1152
+ </button>
1153
+ </div>
1154
+ </template>
1155
+ </p-tree>
1156
+ <div v-else style="text-align: center; padding-top: 20px;">
1157
+ <a>Building Tree...</a>
1158
+ </div>
1159
+ </div>
1160
+ </div>
1161
+
1162
+ <img
1163
+ id="loadingGif"
1164
+ src="resources/@bitpoolos/edge-bacnet/BitpoolCube_Blue_350.gif"
1165
+ style="width: 70px; display: block; margin-left: auto; margin-right: auto;" />
1166
+ <p id="loadingText" style="display: block; margin-left: auto; margin-right: auto; text-align: center; color: #505050;">
1167
+ Loading current network info
1168
+ </p>
1169
+ </div>
787
1170
 
788
- <!--
1171
+ <!--
789
1172
  *
790
1173
  *
791
1174
  * Read list tab
792
1175
  *
793
1176
  *
794
1177
  -->
795
- <div id='read-readList-tab' style='display:none'>
796
- <div class="removeAllDevicesDiv" >
797
- <button @click="removeAllDevices()" class="removeAllDevicesButton" title="Remove all devices">
798
- <i class="pi pi-minus-circle" style="color: #ff0000; padding-right: 5px;"> </i><a style="color: #ff0000;">Remove All Devices</a>
799
- </button>
800
- </div>
801
-
802
- <p-tree :value="readDevices">
803
- <template #device="slotProps">
804
- <b class="deviceLabel">{{slotProps.node.label}} </b>
805
- <button @click="removeAllClicked(slotProps, this)" class="removeAllButton bacnetbutton">
806
- <i class="pi pi-minus-circle "><a class="allFunctionsText">Remove all</a></i>
807
- </button>
808
- </template>
809
-
810
- <template #point="slotProps" v-model:class="pointContent">
811
- <b class="pointLabel">{{slotProps.node.label}}</b>
812
- <button @click="removePointClicked(slotProps, this)" class="minusPointButton bacnetbutton">
813
- <i class="pi pi-minus-circle "></i>
814
- </button>
815
- </template>
816
- </p-tree>
817
-
818
- </div>
819
- <!--
1178
+ <div id="read-readList-tab" style="display:none">
1179
+ <div class="removeAllDevicesDiv">
1180
+ <button @click="exportReadList()" class="export-points-button" title="Export Read List">
1181
+ <i class="fa fa-cloud-download"> </i><a id="fileLabelText" style="padding-left: 10px;">Export</a>
1182
+ </button>
1183
+ <a id="exportJSON" style="display: none"></a>
1184
+
1185
+ <button @click="importReadList()" class="export-points-button" title="Import Read List">
1186
+ <i class="fa fa-cloud-upload" id="importReadListButton"></i
1187
+ ><a id="fileLabelText" style="padding-left: 10px;">Import</a>
1188
+ </button>
1189
+ <input type="file" id="readlist-file-upload" accept="application/JSON" class="inputStyle" style="display: none;" />
1190
+
1191
+ <button @click="removeAllDevices()" class="removeAllDevicesButton" title="Remove all devices">
1192
+ <i class="pi pi-minus-circle" style="color: #ff0000; padding-right: 5px;"> </i
1193
+ ><a style="color: #ff0000;">Remove All Devices</a>
1194
+ </button>
1195
+ </div>
1196
+
1197
+ <p-tree :value="readDevices">
1198
+ <template #device="slotProps">
1199
+ <b class="deviceLabel">{{slotProps.node.label}} </b>
1200
+ <button @click="removeAllClicked(slotProps, this)" class="removeAllButton bacnetbutton">
1201
+ <i class="pi pi-minus-circle "><a class="allFunctionsText">Remove all</a></i>
1202
+ </button>
1203
+ </template>
1204
+
1205
+ <template #point="slotProps" v-model:class="pointContent">
1206
+ <b class="pointLabel">{{slotProps.node.label}}</b>
1207
+ <button @click="removePointClicked(slotProps, this)" class="minusPointButton bacnetbutton">
1208
+ <i class="pi pi-minus-circle "></i>
1209
+ </button>
1210
+ </template>
1211
+ </p-tree>
1212
+ </div>
1213
+ <!--
820
1214
  *
821
1215
  *
822
1216
  * Read Properties tab
823
1217
  *
824
1218
  *
825
1219
  -->
826
- <div id='read-properties-tab' style='display:none'>
827
- <div class="form-row" style="display: flex; width: fit-content; flex-wrap: nowrap; flex-direction: column;">
828
-
829
- <label for="node-input-object_properties_group" style="display: flex; align-items: center; white-space: nowrap; text-decoration: underline;">Object Properties:</label>
830
-
831
-
832
- <div id="node-input-object_properties_group" style="display: flex; align-items: flex-start; flex-direction: column; padding-left: 50px;">
833
- <div class="objectPropertiesLabel">
834
- <label for="node-input-object_property_simplePayload" style="padding-left: 4px; width: auto; align-items: start;" class="objectPropertiesLabel">
835
- <i class="icon-tag"></i> <span data-i18n="bitpool-bacnet.label.object_property_simplePayload"></span>
836
- <input style="margin-left: 5px;" class=" objectProp" type="checkbox" id="node-input-object_property_simplePayload">
837
- <a style="white-space: nowrap; padding-left: 20px;">Simple Payload</a>
838
- </label>
839
- </div>
840
-
841
- <div class="objectPropertiesLabel">
842
- <label for="node-input-object_property_fullObject" style="padding-left: 4px; width: auto; align-items: start;" class="objectPropertiesLabel">
843
- <i class="icon-tag"></i> <span data-i18n="bitpool-bacnet.label.object_property_fullObject"></span>
844
- <input style="margin-left: 5px;" class=" objectProp" type="checkbox" id="node-input-object_property_fullObject">
845
- <a style="white-space: nowrap; padding-left: 20px;">Full Object</a>
846
- </label>
847
- </div>
848
-
849
- </div>
850
- </div>
851
-
852
- <hr>
853
-
854
- <div class="form-row msgTypeDiv" style="display: flex;">
855
- <label for="node-input-msgType" style="text-decoration: underline;">
856
- Message type:
857
- </label>
858
- <div id="node-input-msgType" style="display: flex; align-items: flex-start; flex-direction: column; padding-left: 50px;">
859
-
860
- <!-- class= checkbox-round -->
861
- <div style="display: flex; flex-direction: row; align-items: flex-start;"><input class="checkbox-round" type="checkbox" id="node-input-json" style="width: 13px; margin-left: 5px;"/><label for="node-input-json" style="padding-left: 20px; width: fit-content;">Block per device</label> </div>
862
- <div style="display: flex; flex-direction: row; align-items: flex-start;"><input class="checkbox-round" type="checkbox" id="node-input-mqtt" style="width: 13px; margin-left: 5px;"/><label for="node-input-mqtt" style="padding-left: 20px; width: fit-content;">Individual msgs</label> </div>
863
-
864
- </div>
865
- </div>
866
-
867
- <div class="form-row">
868
- <label for="node-input-roundDecimal">
869
- Decimal place precision
870
- </label>
871
- <input type="number" id="node-input-roundDecimal" placeholder="None">
872
- </div>
873
-
874
- <label for="node-input-hiddenDeployToggle" style="display: none !important;" >
875
- <i class="icon-tag" style="display: none !important;"></i> <span data-i18n="bitpool-bacnet.label.hiddenDeployToggle" style="display: none !important;"></span>
876
- <input style="display: none !important;" type="checkbox" id="node-input-hiddenDeployToggle">
1220
+ <div id="read-properties-tab" style="display:none">
1221
+ <div class="form-row" style="display: flex; width: fit-content; flex-wrap: nowrap; flex-direction: column;">
1222
+ <label
1223
+ for="node-input-object_properties_group"
1224
+ style="display: flex; align-items: center; white-space: nowrap; text-decoration: underline;"
1225
+ >Object Properties:</label
1226
+ >
1227
+
1228
+ <div
1229
+ id="node-input-object_properties_group"
1230
+ style="display: flex; align-items: flex-start; flex-direction: column; padding-left: 50px;">
1231
+ <div class="objectPropertiesLabel">
1232
+ <label
1233
+ for="node-input-object_property_simplePayload"
1234
+ style="padding-left: 4px; width: auto; align-items: start;"
1235
+ class="objectPropertiesLabel">
1236
+ <i class="icon-tag"></i> <span data-i18n="bitpool-bacnet.label.object_property_simplePayload"></span>
1237
+ <input
1238
+ style="margin-left: 5px;"
1239
+ class=" objectProp"
1240
+ type="checkbox"
1241
+ id="node-input-object_property_simplePayload" />
1242
+ <a style="white-space: nowrap; padding-left: 20px;">Simple Payload</a>
877
1243
  </label>
1244
+ </div>
1245
+
1246
+ <div class="objectPropertiesLabel">
1247
+ <label
1248
+ for="node-input-object_property_fullObject"
1249
+ style="padding-left: 4px; width: auto; align-items: start;"
1250
+ class="objectPropertiesLabel">
1251
+ <i class="icon-tag"></i> <span data-i18n="bitpool-bacnet.label.object_property_fullObject"></span>
1252
+ <input
1253
+ style="margin-left: 5px;"
1254
+ class=" objectProp"
1255
+ type="checkbox"
1256
+ id="node-input-object_property_fullObject" />
1257
+ <a style="white-space: nowrap; padding-left: 20px;">Full Object</a>
1258
+ </label>
1259
+ </div>
1260
+ </div>
1261
+ </div>
1262
+
1263
+ <hr />
1264
+
1265
+ <div class="form-row msgTypeDiv" style="display: flex;">
1266
+ <label for="node-input-msgType" style="text-decoration: underline;"> Message type: </label>
1267
+ <div
1268
+ id="node-input-msgType"
1269
+ style="display: flex; align-items: flex-start; flex-direction: column; padding-left: 50px;">
1270
+ <!-- class= checkbox-round -->
1271
+ <div style="display: flex; flex-direction: row; align-items: flex-start;">
1272
+ <input
1273
+ class="checkbox-round"
1274
+ type="checkbox"
1275
+ id="node-input-json"
1276
+ style="width: 13px; margin-left: 5px;" /><label
1277
+ for="node-input-json"
1278
+ style="padding-left: 20px; width: fit-content;"
1279
+ >Block per device</label
1280
+ >
1281
+ </div>
1282
+ <div style="display: flex; flex-direction: row; align-items: flex-start;">
1283
+ <input
1284
+ class="checkbox-round"
1285
+ type="checkbox"
1286
+ id="node-input-mqtt"
1287
+ style="width: 13px; margin-left: 5px;" /><label
1288
+ for="node-input-mqtt"
1289
+ style="padding-left: 20px; width: fit-content;"
1290
+ >Individual msgs</label
1291
+ >
1292
+ </div>
878
1293
  </div>
879
-
1294
+ </div>
1295
+
1296
+ <div class="form-row">
1297
+ <label for="node-input-roundDecimal"> Decimal place precision </label>
1298
+ <input type="number" id="node-input-roundDecimal" placeholder="None" />
1299
+ </div>
1300
+
1301
+ <label for="node-input-hiddenDeployToggle" style="display: none !important;">
1302
+ <i class="icon-tag" style="display: none !important;"></i>
1303
+ <span data-i18n="bitpool-bacnet.label.hiddenDeployToggle" style="display: none !important;"></span>
1304
+ <input style="display: none !important;" type="checkbox" id="node-input-hiddenDeployToggle" />
1305
+ </label>
1306
+
1307
+ <div class="form-row">
1308
+ <label for="node-input-exportCsv"> Points List </label>
1309
+ <label for="points-export" class="export-points-button" @click="exportPointListCsv()">
1310
+ <i class="fa fa-cloud-download" id="fileLabel"></i> <a id="points-export" style="padding-left: 10px;">Export</a>
1311
+ </label>
1312
+ </div>
880
1313
  </div>
881
-
1314
+ </div>
882
1315
  </script>
883
1316
  <script type="text/html" data-help-name="Bacnet-Discovery">
884
- <p> A node used to view, select devices, device points, and point properties to add to the Read polling list. </p>
885
-
886
- <h3><strong>Device List</strong></h3>
887
- <ol class="node-ports">
888
- <p>
889
- This tab displays the devices and device points that are a result of a network Discover. The data is broken down and listed as Devices, Points for the Device,
890
- and Point properties for the Points.
891
-
892
- On this tab the user may choose specific points to read, or all of the points present in a device.
893
-
894
- The reload button may be used to show any new data that may have been recieved by the bitpool BACnet node.
895
-
896
- Please note: Data can only be shown here once a Discover sucessfully recieves a response from online devices on the network
897
- </p>
898
-
899
- </ol>
900
-
901
- <h3><strong>Read List</strong></h3>
902
- <ol class="node-ports">
903
- <p>
904
- This tab shows all of the devices and points that have been chosen to read.
905
- </p>
906
-
907
- </ol>
908
-
909
- <h3><strong>Properties</strong></h3>
910
- <ol class="node-ports">
911
- <p>
912
- This tab shows all of the point properties the user may choose to read from the points detailed in the Read List tab.
913
- Here the user may also choose the output format of the Read data, as MQTT compatible Individual msgs, or a JSON block per device.
914
- </p>
915
-
916
- </ol>
917
-
918
- <h3><strong>Examples</strong></h3>
919
- <p>For example flows, please use the examples section for this node. These examples can be found at: Node-red hamburger menu on top right -> Import -> Examples -> @bitpoolos/edge-bacnet</p>
920
- <p>To find captured examples of settings and flows, please go to our wiki <a href="https://wiki.bitpool.com/en/edge/apps/bitpool-edge/nr-bacnet">here</a></p>
921
-
922
-
923
-
924
-
925
- <h3>Resources:</h3>
926
-
927
- <p><a href="https://youtu.be/4K7mVxfvfbc">Video Walk-through </a></p>
928
-
929
- <h4><strong>Online Docs:</strong></h4>
930
- <ul type="1">
931
- <li><a href="https://www.bitpool.com/">bitpool.com</a> - check us out here.</li>
932
- <li><a href="https://app.bitpool.com/">app.bitpool.com</a> - set up your account.</li>
933
- <li><a href="https://wiki.bitpool.com/">wiki.bitpool.com</a> - find more documentation.</li>
934
- <li><a href="https://bacnet.org/">BACnet</a> - find more about the protocol.</li>
935
- </ul>
1317
+ <p>A node used to view, select devices, device points, and point properties to add to the Read polling list.</p>
1318
+
1319
+ <h3><strong>Device List</strong></h3>
1320
+ <ol class="node-ports">
1321
+ <p>
1322
+ This tab displays the devices and device points that are a result of a network Discover. The data is broken down and
1323
+ listed as Devices, Points for the Device, and Point properties for the Points. On this tab the user may choose specific
1324
+ points to read, or all of the points present in a device. The reload button may be used to show any new data that may
1325
+ have been recieved by the bitpool BACnet node. Please note: Data can only be shown here once a Discover sucessfully
1326
+ recieves a response from online devices on the network
1327
+ </p>
1328
+ </ol>
1329
+
1330
+ <h3><strong>Read List</strong></h3>
1331
+ <ol class="node-ports">
1332
+ <p>This tab shows all of the devices and points that have been chosen to read.</p>
1333
+ </ol>
1334
+
1335
+ <h3><strong>Properties</strong></h3>
1336
+ <ol class="node-ports">
1337
+ <p>
1338
+ This tab shows all of the point properties the user may choose to read from the points detailed in the Read List tab.
1339
+ Here the user may also choose the output format of the Read data, as MQTT compatible Individual msgs, or a JSON block
1340
+ per device.
1341
+ </p>
1342
+ </ol>
1343
+
1344
+ <h3><strong>Examples</strong></h3>
1345
+ <p>
1346
+ For example flows, please use the examples section for this node. These examples can be found at: Node-red hamburger menu
1347
+ on top right -> Import -> Examples -> @bitpoolos/edge-bacnet
1348
+ </p>
1349
+ <p>
1350
+ To find captured examples of settings and flows, please go to our wiki
1351
+ <a href="https://wiki.bitpool.com/en/edge/apps/bitpool-edge/nr-bacnet">here</a>
1352
+ </p>
1353
+
1354
+ <h3>Resources:</h3>
1355
+
1356
+ <p><a href="https://youtu.be/4K7mVxfvfbc">Video Walk-through </a></p>
1357
+
1358
+ <h4><strong>Online Docs:</strong></h4>
1359
+ <ul type="1">
1360
+ <li><a href="https://www.bitpool.com/">bitpool.com</a> - check us out here.</li>
1361
+ <li><a href="https://app.bitpool.com/">app.bitpool.com</a> - set up your account.</li>
1362
+ <li><a href="https://wiki.bitpool.com/">wiki.bitpool.com</a> - find more documentation.</li>
1363
+ <li><a href="https://bacnet.org/">BACnet</a> - find more about the protocol.</li>
1364
+ </ul>
936
1365
  </script>