@bitpoolos/edge-bacnet 1.4.6 → 1.5.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.
@@ -161,6 +161,7 @@
161
161
  device_read_schedule_value: { value: "15", required: true },
162
162
  device_read_schedule_options: { value: "Minutes", required: true },
163
163
  deviceRangeRegisters: { value: [] },
164
+ portRangeRegisters: { value: [] },
164
165
  cacheFileEnabled: { value: false, required: true },
165
166
  sanitise_device_schedule: { value: "60", required: false },
166
167
  sanitise_device_schedule_value: { value: "1", required: false },
@@ -347,7 +348,7 @@
347
348
  contentType: "application/json",
348
349
  data: JSON.stringify(jsonPayload),
349
350
  success: function (result) { },
350
- timeout: 10000,
351
+ timeout: 15000,
351
352
  });
352
353
  };
353
354
 
@@ -368,7 +369,8 @@
368
369
  });
369
370
  });
370
371
 
371
- //device scan range matrix
372
+ //start device scan range matrix
373
+
372
374
  $("#node-input-deviceIdRangeMatrix-container")
373
375
  .css("min-width", "350px")
374
376
  .editableList({
@@ -402,12 +404,13 @@
402
404
 
403
405
  $(htmlRow).appendTo(fragment);
404
406
  row[0].appendChild(fragment);
405
- document.getElementById("node-input-reg-block-count").innerHTML = $(
407
+
408
+ document.getElementById("node-input-reg-block-count-deviceIdRangeMatrix").innerHTML = $(
406
409
  "#node-input-deviceIdRangeMatrix-container"
407
410
  ).editableList("length");
408
411
  },
409
412
  removeItem: function (data) {
410
- document.getElementById("node-input-reg-block-count").innerHTML = $(
413
+ document.getElementById("node-input-reg-block-count-deviceIdRangeMatrix").innerHTML = $(
411
414
  "#node-input-deviceIdRangeMatrix-container"
412
415
  ).editableList("length");
413
416
  },
@@ -415,7 +418,7 @@
415
418
  scrollOnAdd: false,
416
419
  header: $("<div style='display:flex; padding:10px 10px 0px 5px; column-gap: 10px'>").append(
417
420
  $.parseHTML(
418
- "<div><span class='bp-matrix-heading'>Device ID Range(s)</span> </div><div class='bp-matrix-count' style='color: gray'><label id='node-input-reg-block-count' >0</label> </div>"
421
+ "<div><span class='bp-matrix-heading'>Device ID Range(s)</span> </div><div class='bp-matrix-count' style='color: gray'><label id='node-input-reg-block-count-deviceIdRangeMatrix' >0</label> </div>"
419
422
  )
420
423
  ),
421
424
  buttons: [
@@ -454,7 +457,7 @@
454
457
  title: "Delete all registers",
455
458
  click: function (evt) {
456
459
  $("#node-input-deviceIdRangeMatrix-container").editableList("empty");
457
- document.getElementById("node-input-reg-block-count").innerHTML = 0;
460
+ document.getElementById("node-input-reg-block-count-deviceIdRangeMatrix").innerHTML = 0;
458
461
  },
459
462
  },
460
463
  ],
@@ -472,7 +475,119 @@
472
475
  });
473
476
  }
474
477
 
475
- document.getElementById("node-input-reg-block-count").innerHTML = node.deviceRangeRegisters.length;
478
+ document.getElementById("node-input-reg-block-count-deviceIdRangeMatrix").innerHTML = node.deviceRangeRegisters.length;
479
+
480
+ //end device id range matrix
481
+
482
+ //start port range matrix
483
+
484
+ $("#node-input-portRangeMatrix-container")
485
+ .css("min-width", "350px")
486
+ .editableList({
487
+ addItem: function (row, index, data) {
488
+ row.css({ overflow: "none", whiteSpace: "nowrap" });
489
+ let rowData = {};
490
+ if (data && typeof data.enabled == "boolean" && typeof data.start == "string" && typeof data.end == "string") {
491
+ if (data.enabled === true) {
492
+ rowData.enabled = "checked";
493
+ } else if (data.enabled === false) {
494
+ rowData.enabled = "";
495
+ }
496
+ rowData.start = data.start;
497
+ rowData.end = data.end;
498
+ } else {
499
+ rowData.enabled = "";
500
+ rowData.start = "";
501
+ rowData.end = "";
502
+ }
503
+
504
+ let itemNumber = index + 1;
505
+ let fragment = document.createDocumentFragment();
506
+ let htmlRow = `
507
+ <div class="form-row" id="node-input-port_range">
508
+ <span class='bp-matrix-itemIndex'> ${itemNumber} </span>
509
+ <a style="padding-left: 60px;">Start: </a><input type="number" id="node-input-port_range_entry_start" style="width: 125px;" min="0" max="64738" value="${rowData.start}" />
510
+ <a style="padding-left: 35px;">End: </a><input type="number" id="node-input-port_range_entry_end" style="width: 125px;" min="1" max="64738" value="${rowData.end}" />
511
+ <label class='bp-matrix-enable' for="node-input-port_range_entry"><i class="icon-tag"></i><span data-i18n="bitpool-bacnet.label.port_range_entry"></span>
512
+ <input type="checkbox" id="node-input-port_range_entry_enabled" style="width: auto;" ${rowData.enabled} /> </label>
513
+ </div>`;
514
+
515
+ $(htmlRow).appendTo(fragment);
516
+ row[0].appendChild(fragment);
517
+ document.getElementById("node-input-reg-block-count-portRangeMatrix").innerHTML = $(
518
+ "#node-input-portRangeMatrix-container"
519
+ ).editableList("length");
520
+
521
+ },
522
+ removeItem: function (data) {
523
+ document.getElementById("node-input-reg-block-count-portRangeMatrix").innerHTML = $(
524
+ "#node-input-portRangeMatrix-container"
525
+ ).editableList("length");
526
+ },
527
+ removable: true,
528
+ scrollOnAdd: false,
529
+ header: $("<div style='display:flex; padding:10px 10px 0px 5px; column-gap: 10px'>").append(
530
+ $.parseHTML(
531
+ "<div><span class='bp-matrix-heading'>Port Range(s)</span> </div><div class='bp-matrix-count' style='color: gray'><label id='node-input-reg-block-count-portRangeMatrix' >0</label> </div>"
532
+ )
533
+ ),
534
+ buttons: [
535
+ {
536
+ label: "json",
537
+ icon: "fa fa-share",
538
+ title: "Export tags as JSON to browser",
539
+ click: function (evt) {
540
+ var portRangeRegisters = $("#node-input-portRangeMatrix-container").editableList("items");
541
+ var mapItems = [];
542
+ portRangeRegisters.each(function (i) {
543
+ var registerMap = $(this);
544
+ var mapItem = {
545
+ enabled: registerMap.find("#node-input-port_range_entry_enabled").val(),
546
+ start: registerMap.find("#node-input-port_range_entry_start").val(),
547
+ end: registerMap.find("#node-input-port_range_entry_end").val(),
548
+ };
549
+ mapItems.push(mapItem);
550
+ });
551
+ var oMyBlob = new Blob(
552
+ [
553
+ JSON.stringify(mapItems, null, 0)
554
+ .replaceAll(/\[{/gi, "[\n{")
555
+ .replaceAll(/}\]/gi, "}\n]")
556
+ .replaceAll(/},/gi, "},\n")
557
+ .replaceAll(/{/gi, " {"),
558
+ ],
559
+ { type: "text/json" }
560
+ );
561
+ window.open(URL.createObjectURL(oMyBlob));
562
+ },
563
+ },
564
+ {
565
+ label: "delete",
566
+ icon: "fa-regular fa-trash-can",
567
+ title: "Delete all registers",
568
+ click: function (evt) {
569
+ $("#node-input-portRangeMatrix-container").editableList("empty");
570
+ document.getElementById("node-input-reg-block-count-portRangeMatrix").innerHTML = 0;
571
+ },
572
+ },
573
+ ],
574
+ });
575
+
576
+ if (node.portRangeRegisters.length > 0) {
577
+ for (var i = 0; i < node.portRangeRegisters.length; i++) {
578
+ $("#node-input-portRangeMatrix-container").editableList("addItem", node.portRangeRegisters[i]);
579
+ }
580
+ } else {
581
+ $("#node-input-portRangeMatrix-container").editableList("addItem", {
582
+ enabled: true,
583
+ start: "47808",
584
+ end: "47808",
585
+ });
586
+ }
587
+
588
+ document.getElementById("node-input-reg-block-count-portRangeMatrix").innerHTML = node.portRangeRegisters.length;
589
+ //end port range matrix
590
+
476
591
  },
477
592
  oneditsave: function () {
478
593
  let node = this;
@@ -503,6 +618,20 @@
503
618
  };
504
619
  node.deviceRangeRegisters.push(mapItem);
505
620
  });
621
+
622
+
623
+ node.portRangeRegisters = [];
624
+ let portRanges = $("#node-input-portRangeMatrix-container").editableList("items");
625
+ portRanges.each(function (i) {
626
+ let map = $(this);
627
+ let mapItem = {
628
+ enabled: map.find("#node-input-port_range_entry_enabled").is(":checked"),
629
+ start: map.find("#node-input-port_range_entry_start").val(),
630
+ end: map.find("#node-input-port_range_entry_end").val(),
631
+ };
632
+ node.portRangeRegisters.push(mapItem);
633
+ });
634
+
506
635
  },
507
636
  });
508
637
 
@@ -652,6 +781,10 @@
652
781
  <input type="text" id="node-input-local_device_port" placeholder="47808" />
653
782
  </div>
654
783
 
784
+ <div class="form-row node-input-portRangeMatrix-container-row bp-row">
785
+ <ol id="node-input-portRangeMatrix-container"></ol>
786
+ </div>
787
+
655
788
  <div class="form-row bp-row">
656
789
  <label for="node-input-deviceId"
657
790
  ><i class="icon-tag"></i><span data-i18n="bitpool-bacnet.label.deviceId"></span> Device ID
@@ -662,6 +795,7 @@
662
795
  <div class="form-row node-input-deviceIdRangeMatrix-container-row bp-row">
663
796
  <ol id="node-input-deviceIdRangeMatrix-container"></ol>
664
797
  </div>
798
+
665
799
  </div>
666
800
 
667
801
  <div id="read-discover-tab" style="display:none">
package/bacnet_gateway.js CHANGED
@@ -33,6 +33,7 @@ module.exports = function (RED) {
33
33
  this.retries = config.retries;
34
34
  this.bacnetServer = nodeContext.get("bacnetServer") || null;
35
35
  this.deviceRangeRegisters = config.deviceRangeRegisters;
36
+ this.portRangeRegisters = config.portRangeRegisters;
36
37
  this.cacheFileEnabled = config.cacheFileEnabled;
37
38
  this.sanitise_device_schedule = config.sanitise_device_schedule;
38
39
 
@@ -61,7 +62,8 @@ module.exports = function (RED) {
61
62
  node.device_read_schedule,
62
63
  node.retries,
63
64
  node.cacheFileEnabled,
64
- node.sanitise_device_schedule
65
+ node.sanitise_device_schedule,
66
+ node.portRangeRegisters.filter((ele) => ele.enabled === true)
65
67
  );
66
68
 
67
69
  nodeContext.set("bacnetConfig", node.bacnetConfig);
@@ -225,11 +227,9 @@ module.exports = function (RED) {
225
227
  logOut("Error updating priorityQueue: ", error);
226
228
  });
227
229
  } else if (msg.testFunc == true) {
228
- node.bacnetClient.testFunction(msg.address, msg.type, msg.instance, msg.property);
230
+ node.bacnetClient.testFunction(msg.address, msg.port, msg.type, msg.instance, msg.property);
229
231
  } else if (msg.applyDisplayNames) {
230
-
231
232
  node.status({ fill: "blue", shape: "dot", text: "Updating display names" });
232
-
233
233
  setTimeout(() => {
234
234
  node.status({});
235
235
  }, 2000);
@@ -240,7 +240,6 @@ module.exports = function (RED) {
240
240
  }).catch(function (error) {
241
241
  logOut("Error in applyDisplayNames: ", error);
242
242
  });
243
-
244
243
  }
245
244
  });
246
245
 
package/bacnet_read.html CHANGED
@@ -405,7 +405,7 @@
405
405
  },
406
406
  exportPointListCsv() {
407
407
  let app = this;
408
- let csvContent = "data:text/csv;charset=utf-8,ipAddress,deviceId,deviceName,pointName,objectType" + "\r\n";
408
+ let csvContent = "ipAddress,deviceId,deviceName,pointName,objectType" + "\r\n";
409
409
  const keys = Object.keys(app.pointList);
410
410
  for (key in keys) {
411
411
  const guid = keys[key];
@@ -422,24 +422,38 @@
422
422
  if (device) {
423
423
  const deviceName = device.deviceName;
424
424
  const points = Object.keys(deviceObject);
425
- for (point in points) {
426
- csvContent +=
427
- ipAddress +
428
- "," +
429
- deviceId +
430
- "," +
431
- deviceName +
432
- "," +
433
- points[point] +
434
- "," +
435
- deviceObject[points[point]].meta.objectId.type +
436
- "\r\n";
437
-
438
- if (parseInt(point) == points.length - 1 && parseInt(key) == keys.length - 1) {
425
+ if (points.length > 0) {
426
+ for (point in points) {
427
+ csvContent +=
428
+ ipAddress +
429
+ "," +
430
+ deviceId +
431
+ "," +
432
+ deviceName +
433
+ "," +
434
+ points[point] +
435
+ "," +
436
+ deviceObject[points[point]].meta.objectId.type +
437
+ "\r\n";
438
+
439
+ if (parseInt(point) == points.length - 1 && parseInt(key) == keys.length - 1) {
440
+ // last iteration
441
+ var csvBlob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" });
442
+ var link = document.createElement("a");
443
+ var url = URL.createObjectURL(csvBlob);
444
+ link.setAttribute("href", url);
445
+ link.setAttribute("download", "pointslist.csv");
446
+ document.body.appendChild(link);
447
+ link.click();
448
+ }
449
+ }
450
+ } else {
451
+ if (parseInt(key) == keys.length - 1) {
439
452
  // last iteration
440
- var encodedUri = encodeURI(csvContent);
453
+ var csvBlob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" });
441
454
  var link = document.createElement("a");
442
- link.setAttribute("href", encodedUri);
455
+ var url = URL.createObjectURL(csvBlob);
456
+ link.setAttribute("href", url);
443
457
  link.setAttribute("download", "pointslist.csv");
444
458
  document.body.appendChild(link);
445
459
  link.click();
@@ -448,9 +462,10 @@
448
462
  } else {
449
463
  if (parseInt(key) == keys.length - 1) {
450
464
  // last iteration
451
- var encodedUri = encodeURI(csvContent);
465
+ var csvBlob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" });
452
466
  var link = document.createElement("a");
453
- link.setAttribute("href", encodedUri);
467
+ var url = URL.createObjectURL(csvBlob);
468
+ link.setAttribute("href", url);
454
469
  link.setAttribute("download", "pointslist.csv");
455
470
  document.body.appendChild(link);
456
471
  link.click();
package/common.js CHANGED
@@ -65,7 +65,8 @@ class BacnetClientConfig {
65
65
  device_read_schedule,
66
66
  retries,
67
67
  cacheFileEnabled,
68
- sanitise_device_schedule
68
+ sanitise_device_schedule,
69
+ portRangeMatrix
69
70
  ) {
70
71
  this.apduTimeout = apduTimeout;
71
72
  this.localIpAdrress = localIpAdrress;
@@ -83,9 +84,26 @@ class BacnetClientConfig {
83
84
  this.retries = retries;
84
85
  this.cacheFileEnabled = cacheFileEnabled;
85
86
  this.sanitise_device_schedule = sanitise_device_schedule;
87
+ this.portRangeMatrix = this.generatePortRangeArray(portRangeMatrix);
88
+ }
89
+
90
+ generatePortRangeArray(rangeMatrix) {
91
+ let portArray = [];
92
+ for (let x = 0; x < rangeMatrix.length; x++) {
93
+ let rangeEntry = rangeMatrix[x];
94
+ let start = parseInt(rangeEntry.start);
95
+ let end = parseInt(rangeEntry.end);
96
+ for (let i = start; i <= end; i++) {
97
+ portArray.push(i);
98
+ }
99
+ }
100
+
101
+ return portArray;
86
102
  }
87
103
  }
88
104
 
105
+
106
+
89
107
  class ReadCommandConfig {
90
108
  constructor(pointsToRead, objectProperties, decimalPrecision) {
91
109
  this.pointsToRead = pointsToRead;
@@ -273,6 +291,26 @@ function decodeBitArray(size, bits) {
273
291
  };
274
292
  }
275
293
 
294
+ function getBacnetErrorString(classInt, codeInt) {
295
+ const classString = Object.keys(baEnum.ErrorClass).find(key => baEnum.ErrorClass[key] === classInt);
296
+ const codeString = Object.keys(baEnum.ErrorCode).find(key => baEnum.ErrorCode[key] === codeInt);
297
+ return `BacnetError - Class:${classString} - Code:${codeString}`;
298
+ }
299
+
300
+ function parseBacnetError(error) {
301
+ let err = error.message;
302
+ if (err.includes("Class") && err.includes("Code")) {
303
+ const match = err.match(/Class:(\d+) - Code:(\d+)/);
304
+ if (match) {
305
+ err = getBacnetErrorString(parseInt(match[1], 10), parseInt(match[2], 10));
306
+ }
307
+ } else if (err.includes("ERR_TIMEOUT")) {
308
+ err = "Request TIMEOUT";
309
+ }
310
+
311
+ return err;
312
+ };
313
+
276
314
  module.exports = {
277
315
  BacnetConfig,
278
316
  BacnetClientConfig,
@@ -289,4 +327,6 @@ module.exports = {
289
327
  Read_Config_Sync_Server,
290
328
  isNumber,
291
329
  decodeBitArray,
330
+ parseBacnetError,
331
+ getBacnetErrorString,
292
332
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bitpoolos/edge-bacnet",
3
- "version": "1.4.6",
3
+ "version": "1.5.0",
4
4
  "description": "A bacnet gateway for node-red",
5
5
  "dependencies": {
6
6
  "@plus4nodered/ts-node-bacnet": "^1.0.0-beta.2",