@bldgblocks/node-red-contrib-control 0.1.34 → 0.1.36

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.
Files changed (110) hide show
  1. package/nodes/accumulate-block.html +18 -8
  2. package/nodes/accumulate-block.js +39 -44
  3. package/nodes/add-block.html +1 -1
  4. package/nodes/add-block.js +18 -11
  5. package/nodes/alarm-collector.html +260 -0
  6. package/nodes/alarm-collector.js +292 -0
  7. package/nodes/alarm-config.html +129 -0
  8. package/nodes/alarm-config.js +126 -0
  9. package/nodes/alarm-service.html +96 -0
  10. package/nodes/alarm-service.js +142 -0
  11. package/nodes/analog-switch-block.js +25 -36
  12. package/nodes/and-block.js +44 -15
  13. package/nodes/average-block.js +46 -41
  14. package/nodes/boolean-switch-block.js +10 -28
  15. package/nodes/boolean-to-number-block.html +18 -5
  16. package/nodes/boolean-to-number-block.js +24 -16
  17. package/nodes/cache-block.js +24 -37
  18. package/nodes/call-status-block.html +91 -32
  19. package/nodes/call-status-block.js +398 -115
  20. package/nodes/changeover-block.html +5 -0
  21. package/nodes/changeover-block.js +167 -162
  22. package/nodes/comment-block.html +1 -1
  23. package/nodes/comment-block.js +14 -9
  24. package/nodes/compare-block.html +14 -4
  25. package/nodes/compare-block.js +23 -18
  26. package/nodes/contextual-label-block.html +5 -0
  27. package/nodes/contextual-label-block.js +6 -16
  28. package/nodes/convert-block.html +25 -39
  29. package/nodes/convert-block.js +31 -16
  30. package/nodes/count-block.html +11 -5
  31. package/nodes/count-block.js +34 -32
  32. package/nodes/delay-block.js +58 -53
  33. package/nodes/divide-block.js +43 -45
  34. package/nodes/edge-block.html +17 -10
  35. package/nodes/edge-block.js +43 -41
  36. package/nodes/enum-switch-block.js +6 -6
  37. package/nodes/frequency-block.html +6 -1
  38. package/nodes/frequency-block.js +64 -74
  39. package/nodes/global-getter.html +51 -15
  40. package/nodes/global-getter.js +43 -13
  41. package/nodes/global-setter.html +1 -1
  42. package/nodes/global-setter.js +40 -12
  43. package/nodes/history-buffer.html +96 -0
  44. package/nodes/history-buffer.js +461 -0
  45. package/nodes/history-collector.html +29 -1
  46. package/nodes/history-collector.js +37 -16
  47. package/nodes/history-config.html +13 -1
  48. package/nodes/history-service.html +84 -0
  49. package/nodes/history-service.js +52 -0
  50. package/nodes/hysteresis-block.html +5 -0
  51. package/nodes/hysteresis-block.js +13 -16
  52. package/nodes/interpolate-block.html +20 -2
  53. package/nodes/interpolate-block.js +39 -50
  54. package/nodes/join.html +78 -0
  55. package/nodes/join.js +78 -0
  56. package/nodes/latch-block.js +12 -14
  57. package/nodes/load-sequence-block.js +102 -110
  58. package/nodes/max-block.js +26 -26
  59. package/nodes/memory-block.js +57 -58
  60. package/nodes/min-block.js +26 -25
  61. package/nodes/minmax-block.js +35 -34
  62. package/nodes/modulo-block.js +45 -43
  63. package/nodes/multiply-block.js +43 -41
  64. package/nodes/negate-block.html +17 -7
  65. package/nodes/negate-block.js +25 -19
  66. package/nodes/network-point-read.html +128 -0
  67. package/nodes/network-point-read.js +230 -0
  68. package/nodes/{network-register.html → network-point-register.html} +94 -7
  69. package/nodes/{network-register.js → network-point-register.js} +18 -4
  70. package/nodes/network-point-write.html +149 -0
  71. package/nodes/network-point-write.js +222 -0
  72. package/nodes/network-service-bridge.html +131 -0
  73. package/nodes/network-service-bridge.js +376 -0
  74. package/nodes/network-service-read.html +81 -0
  75. package/nodes/{network-read.js → network-service-read.js} +4 -3
  76. package/nodes/{network-point-registry.html → network-service-registry.html} +19 -4
  77. package/nodes/{network-point-registry.js → network-service-registry.js} +7 -2
  78. package/nodes/network-service-write.html +89 -0
  79. package/nodes/{network-write.js → network-service-write.js} +3 -3
  80. package/nodes/nullify-block.js +13 -15
  81. package/nodes/on-change-block.html +17 -9
  82. package/nodes/on-change-block.js +49 -46
  83. package/nodes/oneshot-block.html +13 -10
  84. package/nodes/oneshot-block.js +57 -75
  85. package/nodes/or-block.js +44 -15
  86. package/nodes/pid-block.html +54 -4
  87. package/nodes/pid-block.js +459 -248
  88. package/nodes/priority-block.js +24 -35
  89. package/nodes/rate-limit-block.js +70 -72
  90. package/nodes/rate-of-change-block.html +33 -14
  91. package/nodes/rate-of-change-block.js +74 -62
  92. package/nodes/round-block.html +14 -9
  93. package/nodes/round-block.js +32 -25
  94. package/nodes/saw-tooth-wave-block.js +49 -76
  95. package/nodes/scale-range-block.html +12 -6
  96. package/nodes/scale-range-block.js +46 -39
  97. package/nodes/sine-wave-block.js +49 -57
  98. package/nodes/string-builder-block.js +6 -6
  99. package/nodes/subtract-block.js +38 -34
  100. package/nodes/thermistor-block.js +44 -44
  101. package/nodes/tick-tock-block.js +32 -32
  102. package/nodes/time-sequence-block.js +30 -42
  103. package/nodes/triangle-wave-block.js +49 -69
  104. package/nodes/tstat-block.js +34 -44
  105. package/nodes/units-block.html +90 -69
  106. package/nodes/units-block.js +22 -30
  107. package/nodes/utils.js +206 -3
  108. package/package.json +14 -6
  109. package/nodes/network-read.html +0 -56
  110. package/nodes/network-write.html +0 -65
@@ -1,34 +1,31 @@
1
1
  module.exports = function(RED) {
2
+ const utils = require('./utils')(RED);
2
3
  function InterpolateBlockNode(config) {
3
4
  RED.nodes.createNode(this, config);
4
5
 
5
6
  const node = this;
6
7
 
7
8
  // Initialize runtime state
8
- node.runtime = {
9
- name: config.name,
10
- points: null,
11
- lastOutput: null
12
- };
9
+ // Initialize state
10
+ node.name = config.name;
11
+ node.inputProperty = config.inputProperty || "payload";
12
+ node.points = null;
13
+ node.lastOutput = null;
13
14
 
14
15
  // Initialize points
15
16
  try {
16
- node.runtime.points = config.points ? JSON.parse(config.points) : [{ x: 0, y: 0 }, { x: 100, y: 100 }];
17
- if (!Array.isArray(node.runtime.points) || node.runtime.points.length < 2 ||
18
- !node.runtime.points.every(p => typeof p.x === "number" && !isNaN(p.x) &&
17
+ node.points = config.points ? JSON.parse(config.points) : [{ x: 0, y: 0 }, { x: 100, y: 100 }];
18
+ if (!Array.isArray(node.points) || node.points.length < 2 ||
19
+ !node.points.every(p => typeof p.x === "number" && !isNaN(p.x) &&
19
20
  typeof p.y === "number" && !isNaN(p.y))) {
20
- node.runtime.points = [{ x: 0, y: 0 }, { x: 100, y: 100 }];
21
- node.status({ fill: "red", shape: "ring", text: "invalid points, using default" });
21
+ node.points = [{ x: 0, y: 0 }, { x: 100, y: 100 }];
22
+ utils.setStatusError(node, "invalid points, using default");
22
23
  } else {
23
- node.status({
24
- fill: "green",
25
- shape: "dot",
26
- text: `name: ${node.runtime.name}, points: ${node.runtime.points.length}`
27
- });
24
+ utils.setStatusOK(node, `name: ${node.name}, points: ${node.points.length}`);
28
25
  }
29
26
  } catch (e) {
30
- node.runtime.points = [{ x: 0, y: 0 }, { x: 100, y: 100 }];
31
- node.status({ fill: "red", shape: "ring", text: "invalid points, using default" });
27
+ node.points = [{ x: 0, y: 0 }, { x: 100, y: 100 }];
28
+ utils.setStatusError(node, "invalid points, using default");
32
29
  }
33
30
 
34
31
  node.on("input", function(msg, send, done) {
@@ -36,7 +33,7 @@ module.exports = function(RED) {
36
33
 
37
34
  // Guard against invalid msg
38
35
  if (!msg) {
39
- node.status({ fill: "red", shape: "ring", text: "invalid message" });
36
+ utils.setStatusError(node, "invalid message");
40
37
  if (done) done();
41
38
  return;
42
39
  }
@@ -44,12 +41,12 @@ module.exports = function(RED) {
44
41
  // Handle configuration messages
45
42
  if (msg.context) {
46
43
  if (typeof msg.context !== "string" || !msg.context.trim()) {
47
- node.status({ fill: "yellow", shape: "ring", text: "unknown context" });
44
+ utils.setStatusWarn(node, "unknown context");
48
45
  if (done) done();
49
46
  return;
50
47
  }
51
48
  if (!msg.hasOwnProperty("payload")) {
52
- node.status({ fill: "red", shape: "ring", text: "missing payload" });
49
+ utils.setStatusError(node, "missing payload");
53
50
  if (done) done();
54
51
  return;
55
52
  }
@@ -59,49 +56,43 @@ module.exports = function(RED) {
59
56
  if (Array.isArray(newPoints) && newPoints.length >= 2 &&
60
57
  newPoints.every(p => typeof p.x === "number" && !isNaN(p.x) &&
61
58
  typeof p.y === "number" && !isNaN(p.y))) {
62
- node.runtime.points = newPoints;
63
- node.status({
64
- fill: "green",
65
- shape: "dot",
66
- text: `points: ${newPoints.length}`
67
- });
59
+ node.points = newPoints;
60
+ utils.setStatusOK(node, `points: ${newPoints.length}`);
68
61
  } else {
69
- node.status({ fill: "red", shape: "ring", text: "invalid points" });
62
+ utils.setStatusError(node, "invalid points");
70
63
  }
71
64
  } catch (e) {
72
- node.status({ fill: "red", shape: "ring", text: "invalid points" });
65
+ utils.setStatusError(node, "invalid points");
73
66
  }
74
67
  if (done) done();
75
68
  return;
76
69
  } else {
77
- node.status({ fill: "yellow", shape: "ring", text: "unknown context" });
70
+ utils.setStatusWarn(node, "unknown context");
78
71
  if (done) done();
79
72
  return;
80
73
  }
81
74
  }
82
75
 
83
- // Check for missing payload
84
- if (!msg.hasOwnProperty("payload")) {
85
- node.status({ fill: "red", shape: "ring", text: "missing payload" });
86
- if (done) done();
87
- return;
76
+ // Check for missing input property
77
+ let inputValue;
78
+ try {
79
+ inputValue = parseFloat(RED.util.getMessageProperty(msg, node.inputProperty));
80
+ } catch (err) {
81
+ inputValue = NaN;
88
82
  }
89
-
90
- // Process input
91
- const inputValue = parseFloat(msg.payload);
92
83
  if (isNaN(inputValue)) {
93
- node.status({ fill: "red", shape: "ring", text: "invalid input" });
84
+ utils.setStatusError(node, "missing or invalid input property");
94
85
  if (done) done();
95
86
  return;
96
87
  }
97
88
 
98
89
  // Linear interpolation
99
90
  let outputValue = NaN;
100
- const isPositiveSlope = node.runtime.points.length >= 2 && node.runtime.points[1].x > node.runtime.points[0].x;
91
+ const isPositiveSlope = node.points.length >= 2 && node.points[1].x > node.points[0].x;
101
92
 
102
- for (let i = 0; i < node.runtime.points.length - 1; i++) {
103
- let x1 = node.runtime.points[i].x, y1 = node.runtime.points[i].y;
104
- let x2 = node.runtime.points[i + 1].x, y2 = node.runtime.points[i + 1].y;
93
+ for (let i = 0; i < node.points.length - 1; i++) {
94
+ let x1 = node.points[i].x, y1 = node.points[i].y;
95
+ let x2 = node.points[i + 1].x, y2 = node.points[i + 1].y;
105
96
  if (isPositiveSlope ? (inputValue >= x1 && inputValue <= x2) : (inputValue <= x1 && inputValue >= x2)) {
106
97
  let m = (y2 - y1) / (x2 - x1);
107
98
  let b = y1 - (m * x1);
@@ -111,21 +102,19 @@ module.exports = function(RED) {
111
102
  }
112
103
 
113
104
  if (isNaN(outputValue)) {
114
- node.status({ fill: "red", shape: "ring", text: "input out of range" });
105
+ utils.setStatusError(node, "input out of range");
115
106
  if (done) done();
116
107
  return;
117
108
  }
118
109
 
119
110
  // Check if output value has changed
120
- const isUnchanged = outputValue === node.runtime.lastOutput;
121
- node.status({
122
- fill: "blue",
123
- shape: isUnchanged ? "ring" : "dot",
124
- text: `in: ${inputValue.toFixed(2)}, out: ${outputValue.toFixed(2)}`
125
- });
111
+ const isUnchanged = outputValue === node.lastOutput;
112
+ const statusShape = isUnchanged ? "ring" : "dot";
113
+ utils.setStatusOK(node, `in: ${inputValue.toFixed(2)}, out: ${outputValue.toFixed(2)}`);
114
+ if (statusShape === "ring") utils.setStatusUnchanged(node, `in: ${inputValue.toFixed(2)}, out: ${outputValue.toFixed(2)}`);
126
115
 
127
116
  if (!isUnchanged) {
128
- node.runtime.lastOutput = outputValue;
117
+ node.lastOutput = outputValue;
129
118
  send({ payload: outputValue });
130
119
  }
131
120
 
@@ -0,0 +1,78 @@
1
+ <!-- Data Template -->
2
+ <script type="text/html" data-template-name="bldgblocks-join">
3
+ <div class="form-row">
4
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
5
+ <input type="text" id="node-input-name" placeholder="Name">
6
+ </div>
7
+
8
+ <div class="form-row">
9
+ <label for="node-input-count"><i class="fa fa-sort-numeric-asc"></i> Key Count</label>
10
+ <input type="number" id="node-input-count" placeholder="4">
11
+ </div>
12
+
13
+ <div class="form-row">
14
+ <label for="node-input-excludedKeys"><i class="fa fa-ban"></i> Exclude</label>
15
+ <input type="text" id="node-input-excludedKeys" placeholder="status, req, res">
16
+ <div style="font-size: 0.8em; color: #888; margin-left: 105px;">Comma separated list of properties to ignore</div>
17
+ </div>
18
+
19
+ <div class="form-tips">
20
+ <b>Note:</b> Works with root properties only and must all be unique. Nested properties are not supported.
21
+ Instead of incoming properties like 'outdoor.temp' (a nested object), consider using outdoor/temp or outdoorTemp as property names.
22
+ </div>
23
+ </script>
24
+
25
+ <!-- Registration & logic -->
26
+ <script type="text/javascript">
27
+ RED.nodes.registerType('bldgblocks-join', {
28
+ category: "bldgblocks control",
29
+ color: '#301934',
30
+ defaults: {
31
+ name: { value: "" },
32
+ count: { value: 4, required: true, validate: RED.validators.number() },
33
+ excludedKeys: { value: "status" } // Default to 'status' to maintain previous behavior
34
+ },
35
+ inputs: 1,
36
+ outputs: 1,
37
+ icon: "font-awesome/fa-compress",
38
+ label: function() {
39
+ return this.name || "join (" + this.count + ")";
40
+ },
41
+ paletteLabel: "join",
42
+ oneditprepare: function() {
43
+ // Optional: visual tweaks when opening the edit dialog
44
+ }
45
+ });
46
+ </script>
47
+
48
+ <!-- Help Section -->
49
+ <script type="text/markdown" data-help-name="bldgblocks-join">
50
+ Joins multiple messages into a single message by accumulating unique properties.
51
+ This differs from the standard Node-RED Join node by focusing on unique keys rather than message counts or parts
52
+ and combines to root level using property names.
53
+
54
+ ### Inputs
55
+ : all (any) : All unique properties from incoming messages.
56
+ : Key Count (number) : The number of unique keys required before the node emits the accumulated message.
57
+ : Exclude (string) : A comma-separated list of properties to ignore (e.g., `status, topic`).
58
+
59
+ ### Outputs
60
+ : msg (any) : All unique properties combined at root level into a single message.
61
+
62
+ ### Details
63
+ 1. The node stores a running list of properties from every `msg` it receives.
64
+ 2. It ignores properties starting with `_` (like `_msgid`).
65
+ 3. It ignores any properties listed in the `Exclude` configuration.
66
+ 4. When the number of stored unique keys equals or exceeds the `Key Count`, it emits the combined object as a new message.
67
+
68
+ ### Status
69
+ - Green (dot): Configuration update
70
+ - Blue (dot): State changed
71
+ - Blue (ring): State unchanged
72
+ - Red (ring): Error
73
+ - Yellow (ring): Warning
74
+
75
+ ### References
76
+ - [Node-RED Documentation](https://nodered.org/docs/)
77
+ - [GitHub Repository](https://github.com/BldgBlocks/node-red-contrib-buildingblocks-control.git)
78
+ </script>
package/nodes/join.js ADDED
@@ -0,0 +1,78 @@
1
+ module.exports = function(RED) {
2
+ const utils = require('./utils')(RED);
3
+ function BldgBlocksJoinNode(config) {
4
+ RED.nodes.createNode(this, config);
5
+ const node = this;
6
+
7
+ // Get configuration from the UI
8
+ node.targetCount = parseInt(config.count) || 4;
9
+
10
+ // Parse excluded keys string into a Set for fast lookup
11
+ // Split by comma, trim whitespace, and remove empty entries
12
+ const exclusionString = config.excludedKeys || "";
13
+ const excludedSet = new Set(
14
+ exclusionString.split(',').map(s => s.trim()).filter(s => s.length > 0)
15
+ );
16
+
17
+ // --- INPUT HANDLER ---
18
+ node.on('input', function(msg, send, done) {
19
+ send = send || function() { node.send.apply(node, arguments); };
20
+
21
+ // Get current state from context
22
+ let valueMap = node.context().get("valueMap") || {};
23
+
24
+ // Add properties from incoming message to the state
25
+ Object.keys(msg).forEach(key => {
26
+ // Logic:
27
+ // 1. Value must exist (not undefined/null)
28
+ // 2. Key must NOT start with '_' (internal Node-RED props)
29
+ // 3. Key must NOT be in the user-defined excluded list
30
+ if (
31
+ msg[key] !== undefined &&
32
+ !key.startsWith('_') &&
33
+ !excludedSet.has(key)
34
+ ) {
35
+ valueMap[key] = msg[key];
36
+ }
37
+ });
38
+
39
+ // Calculate current unique key count
40
+ const currentCount = Object.keys(valueMap).length;
41
+
42
+ // Update status
43
+ if (currentCount >= node.targetCount) {
44
+ utils.setStatusOK(node, `${currentCount}/${node.targetCount} keys`);
45
+ } else {
46
+ utils.setStatusChanged(node, `${currentCount}/${node.targetCount} keys`);
47
+ }
48
+
49
+ // Save state back to context
50
+ node.context().set("valueMap", valueMap);
51
+
52
+ // Check if we hit the target
53
+ if (currentCount >= node.targetCount) {
54
+ // Clone the map to create the output message
55
+ const outputMsg = RED.util.cloneMessage(valueMap);
56
+
57
+ // Ensure we have a msgid
58
+ if (!outputMsg._msgid) {
59
+ outputMsg._msgid = RED.util.generateId();
60
+ }
61
+
62
+ send(outputMsg);
63
+ }
64
+
65
+ if (done) {
66
+ done();
67
+ }
68
+ });
69
+
70
+ node.on('close', function(removed, done) {
71
+ if (removed) {
72
+ node.context().set("valueMap", undefined);
73
+ }
74
+ done();
75
+ });
76
+ }
77
+ RED.nodes.registerType("bldgblocks-join", BldgBlocksJoinNode);
78
+ }
@@ -1,4 +1,6 @@
1
1
  module.exports = function(RED) {
2
+ const utils = require('./utils')(RED);
3
+
2
4
  function LatchBlockNode(config) {
3
5
  RED.nodes.createNode(this, config);
4
6
 
@@ -8,25 +10,21 @@ module.exports = function(RED) {
8
10
  node.state = config.state;
9
11
 
10
12
  // Set initial status
11
- node.status({
12
- fill: "green",
13
- shape: "dot",
14
- text: `state: ${node.state}`
15
- });
13
+ utils.setStatusOK(node, `state: ${node.state}`);
16
14
 
17
15
  node.on("input", function(msg, send, done) {
18
16
  send = send || function() { node.send.apply(node, arguments); };
19
17
 
20
18
  // Guard against invalid message
21
19
  if (!msg) {
22
- node.status({ fill: "red", shape: "ring", text: "invalid message" });
20
+ utils.setStatusError(node, "invalid message");
23
21
  if (done) done();
24
22
  return;
25
23
  }
26
24
 
27
25
  // Validate context
28
26
  if (!msg.hasOwnProperty("context") || typeof msg.context !== "string") {
29
- node.status({ fill: "red", shape: "ring", text: "missing or invalid context" });
27
+ utils.setStatusError(node, "missing or invalid context");
30
28
  if (done) done();
31
29
  return;
32
30
  }
@@ -35,13 +33,13 @@ module.exports = function(RED) {
35
33
  switch (msg.context) {
36
34
  case "set":
37
35
  if (node.state) {
38
- node.status({ fill: "blue", shape: "ring", text: `state: ${node.state}` });
36
+ utils.setStatusUnchanged(node, `state: ${node.state}`);
39
37
  } else {
40
38
  if (msg.payload) {
41
39
  node.state = true;
42
- node.status({ fill: "blue", shape: "dot", text: `state: ${node.state}` });
40
+ utils.setStatusChanged(node, `state: ${node.state}`);
43
41
  } else {
44
- node.status({ fill: "blue", shape: "ring", text: `state: ${node.state}` });
42
+ utils.setStatusUnchanged(node, `state: ${node.state}`);
45
43
  }
46
44
  }
47
45
  // Output latch value regardless
@@ -49,19 +47,19 @@ module.exports = function(RED) {
49
47
  break;
50
48
  case "reset":
51
49
  if (node.state === false) {
52
- node.status({ fill: "blue", shape: "ring", text: `state: ${node.state}` });
50
+ utils.setStatusUnchanged(node, `state: ${node.state}`);
53
51
  } else {
54
52
  if (msg.payload) {
55
53
  node.state = false;
56
- node.status({ fill: "blue", shape: "dot", text: `state: ${node.state}` });
54
+ utils.setStatusChanged(node, `state: ${node.state}`);
57
55
  } else {
58
- node.status({ fill: "blue", shape: "ring", text: `state: ${node.state}` });
56
+ utils.setStatusUnchanged(node, `state: ${node.state}`);
59
57
  }
60
58
  }
61
59
  send({ payload: node.state });
62
60
  break;
63
61
  default:
64
- node.status({ fill: "yellow", shape: "ring", text: "unknown context" });
62
+ utils.setStatusWarn(node, "unknown context");
65
63
  if (done) done("Unknown context");
66
64
  return;
67
65
  }