@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,44 +1,49 @@
1
1
  module.exports = function(RED) {
2
+ const utils = require('./utils')(RED);
3
+
2
4
  function CommentBlockNode(config) {
3
5
  RED.nodes.createNode(this, config);
4
6
  const node = this;
5
7
 
6
8
  // Initialize properties
7
9
  node.name = config.name;
8
- node.comment = config.comment;
10
+ node.comment = config.comment || "";
9
11
  node.statusDisplay = config.statusDisplay;
10
12
 
11
13
  // Ensure comment is within 100 characters
12
- if (node.comment.length > 100) {
14
+ if (node.comment && node.comment.length > 100) {
13
15
  node.comment = node.comment.substring(0, 100);
14
16
  }
15
17
 
16
- // Status helper function
18
+ // Update status based on configuration
17
19
  const updateStatus = function() {
18
20
  switch (node.statusDisplay) {
19
21
  case "default":
20
- return { fill: "green", shape: "dot", text: node.comment || "No comment set" };
22
+ utils.setStatusOK(node, node.comment || "No comment set");
23
+ break;
21
24
  case "name":
22
- return { fill: "green", shape: "dot", text: node.name || "comment" };
25
+ utils.setStatusOK(node, node.name || "comment");
26
+ break;
23
27
  case "none":
24
28
  default:
25
- return {};
29
+ // No status for "none"
30
+ break;
26
31
  }
27
32
  };
28
33
 
29
- node.status(updateStatus());
34
+ updateStatus();
30
35
 
31
36
  node.on("input", function(msg, send, done) {
32
37
  send = send || function() { node.send.apply(node, arguments); };
33
38
 
34
39
  // Guard against invalid msg
35
40
  if (!msg) {
36
- node.status({ fill: "red", shape: "ring", text: "invalid message" });
41
+ utils.setStatusError(node, "invalid message");
37
42
  if (done) done();
38
43
  return;
39
44
  }
40
45
 
41
- node.status(updateStatus());
46
+ updateStatus();
42
47
 
43
48
  send(msg);
44
49
  if (done) done();
@@ -4,7 +4,11 @@
4
4
  <label for="node-input-name" title="Display name shown on the canvas"><i class="fa fa-tag"></i> Name</label>
5
5
  <input type="text" id="node-input-name" placeholder="Name">
6
6
  </div>
7
- <div class="form-row">
7
+ <div class="form-row">
8
+ <label for="node-input-inputProperty" title="Message property to read input from"><i class="fa fa-folder-open"></i> Input Property</label>
9
+ <input type="text" id="node-input-inputProperty" placeholder="payload">
10
+ </div>
11
+ <div class="form-row">
8
12
  <label for="node-input-setpoint" title="Default setpoint value for comparison (number)"><i class="fa fa-crosshairs"></i> Setpoint</label>
9
13
  <input type="number" id="node-input-setpoint" placeholder="50" step="any">
10
14
  </div>
@@ -17,6 +21,7 @@
17
21
  color: "#301934",
18
22
  defaults: {
19
23
  name: { value: "" },
24
+ inputProperty: { value: "payload" },
20
25
  setpoint: { value: 50, required: true, validate: function(v) { return !isNaN(+v) && isFinite(+v); } }
21
26
  },
22
27
  inputs: 1,
@@ -36,21 +41,26 @@
36
41
  Compares a numeric input to a setpoint, outputting three boolean signals.
37
42
 
38
43
  ### Inputs
44
+ : input-property (number) : Input number to compare, read from the configured Input Property.
39
45
  : context (string) : Configuration commands - setpoint (`"setpoint"`). Unmatched values trigger error.
40
- : payload (number) : Input number to compare, or setpoint value for config.
41
-
42
46
  ### Outputs
43
47
  : greater than (boolean) : `true` if input > setpoint, else `false`.
44
48
  : equal to (boolean) : `true` if input = setpoint, else `false`.
45
49
  : less than (boolean) : `true` if input < setpoint, else `false`.
46
50
 
47
51
  ### Properties
52
+ : name (string) : Display name in editor.
53
+ : inputProperty (string) : Message property to read input from (default: `payload`). Supports nested properties (e.g., `data.value`).
48
54
  : setpoint (number) : Value for comparison.
49
55
 
50
56
  ### Details
51
- Compares `msg.payload` (number) to a `setpoint` (default: 50), outputting three messages to separate ports:
57
+ Compares a numeric input (read from the configured **Input Property**, default: `msg.payload`) to a `setpoint` (default: 50), outputting three messages to separate ports:
52
58
  `true` for greater than, equal to, or less than the setpoint, respectively; `false` otherwise.
53
59
 
60
+ Output is always written to `msg.payload` in each output message.
61
+
62
+ Setpoint configurable via editor or `msg.context = "setpoint"` with numeric `msg.payload`.
63
+
54
64
  ### Status
55
65
  - Green (dot): Configuration update
56
66
  - Blue (dot): State changed
@@ -1,8 +1,11 @@
1
1
  module.exports = function(RED) {
2
+ const utils = require('./utils')(RED);
3
+
2
4
  function CompareBlockNode(config) {
3
5
  RED.nodes.createNode(this, config);
4
6
  const node = this;
5
7
 
8
+ node.inputProperty = config.inputProperty || "payload";
6
9
  node.setpoint = Number(config.setpoint);
7
10
 
8
11
  node.on("input", function(msg, send, done) {
@@ -10,7 +13,7 @@ module.exports = function(RED) {
10
13
 
11
14
  // Guard against invalid message
12
15
  if (!msg) {
13
- node.status({ fill: "red", shape: "ring", text: "invalid message" });
16
+ utils.setStatusError(node, "invalid message");
14
17
  if (done) done();
15
18
  return;
16
19
  }
@@ -18,38 +21,44 @@ module.exports = function(RED) {
18
21
  // Handle context updates
19
22
  if (msg.hasOwnProperty("context")) {
20
23
  if (!msg.hasOwnProperty("payload")) {
21
- node.status({ fill: "red", shape: "ring", text: "missing payload for setpoint" });
24
+ utils.setStatusError(node, "missing payload for setpoint");
22
25
  if (done) done();
23
26
  return;
24
27
  }
25
28
 
26
29
  if (msg.context === "setpoint") {
27
- const setpointValue = parseFloat(msg.payload);
28
- if (!isNaN(setpointValue) && isFinite(setpointValue)) {
29
- node.setpoint = setpointValue;
30
- node.status({ fill: "green", shape: "dot", text: `setpoint: ${setpointValue.toFixed(2)}` });
30
+ const numVal = utils.validateNumericPayload(msg.payload);
31
+ if (numVal.valid) {
32
+ node.setpoint = numVal.value;
33
+ utils.setStatusOK(node, `setpoint: ${numVal.value.toFixed(2)}`);
31
34
  } else {
32
- node.status({ fill: "red", shape: "ring", text: "invalid setpoint" });
35
+ utils.setStatusError(node, "invalid setpoint");
33
36
  }
34
37
  if (done) done();
35
38
  return;
36
39
  } else {
37
- node.status({ fill: "yellow", shape: "ring", text: "unknown context" });
40
+ utils.setStatusWarn(node, "unknown context");
38
41
  if (done) done("Unknown context");
39
42
  return;
40
43
  }
41
44
  }
42
45
 
43
- // Validate input
44
- if (!msg.hasOwnProperty("payload")) {
45
- node.status({ fill: "red", shape: "ring", text: "missing input" });
46
+ // Get input from configured property
47
+ let input;
48
+ try {
49
+ input = RED.util.getMessageProperty(msg, node.inputProperty);
50
+ } catch (err) {
51
+ input = undefined;
52
+ }
53
+ if (input === undefined) {
54
+ utils.setStatusError(node, "missing or invalid input property");
46
55
  if (done) done();
47
56
  return;
48
57
  }
49
58
 
50
- const inputValue = parseFloat(msg.payload);
59
+ const inputValue = parseFloat(input);
51
60
  if (isNaN(inputValue) || !isFinite(inputValue)) {
52
- node.status({ fill: "red", shape: "ring", text: "invalid input" });
61
+ utils.setStatusError(node, "invalid input");
53
62
  if (done) done();
54
63
  return;
55
64
  }
@@ -64,11 +73,7 @@ module.exports = function(RED) {
64
73
  { payload: less }
65
74
  ];
66
75
 
67
- node.status({
68
- fill: "blue",
69
- shape: "dot",
70
- text: `in: ${inputValue.toFixed(2)}, sp: ${node.setpoint.toFixed(2)}, out: [${greater}, ${equal}, ${less}]`
71
- });
76
+ utils.setStatusOK(node, `in: ${inputValue.toFixed(2)}, sp: ${node.setpoint.toFixed(2)}, out: [${greater}, ${equal}, ${less}]`);
72
77
 
73
78
  send(outputs);
74
79
 
@@ -3,6 +3,10 @@
3
3
  <label for="node-input-name" title="Display name shown on the canvas"><i class="fa fa-tag"></i> Name</label>
4
4
  <input type="text" id="node-input-name" placeholder="Name">
5
5
  </div>
6
+ <div class="form-row">
7
+ <label for="node-input-inputProperty" title="Message property to read label from"><i class="fa fa-folder-open"></i> Input Property</label>
8
+ <input type="text" id="node-input-inputProperty" placeholder="payload">
9
+ </div>
6
10
  <div class="form-row">
7
11
  <label for="node-input-contextPropertyName" title="Context property to set (e.g., priority, clear, in1)"><i class="fa fa-key"></i> Context Property</label>
8
12
  <input type="text" id="node-input-contextPropertyName" placeholder="e.g., priority">
@@ -19,6 +23,7 @@ RED.nodes.registerType("contextual-label-block", {
19
23
  color: "#301934",
20
24
  defaults: {
21
25
  name: { value: "" },
26
+ inputProperty: { value: "payload" },
22
27
  contextPropertyName: {
23
28
  value: "in1",
24
29
  required: true,
@@ -1,4 +1,6 @@
1
1
  module.exports = function(RED) {
2
+ const utils = require('./utils')(RED);
3
+
2
4
  function ContextualLabelBlockNode(config) {
3
5
  RED.nodes.createNode(this, config);
4
6
  const node = this;
@@ -6,17 +8,13 @@ module.exports = function(RED) {
6
8
  node.contextPropertyName = config.contextPropertyName || "in1";
7
9
  node.removeLabel = config.removeLabel || false;
8
10
 
9
- node.status({
10
- fill: "green",
11
- shape: "dot",
12
- text: `mode: ${node.removeLabel ? "remove" : "set"}, property: ${node.contextPropertyName}`
13
- });
11
+ utils.setStatusOK(node, `mode: ${node.removeLabel ? "remove" : "set"}, property: ${node.contextPropertyName}`);
14
12
 
15
13
  node.on("input", function(msg, send, done) {
16
14
  send = send || function() { node.send.apply(node, arguments); };
17
15
 
18
16
  if (!msg) {
19
- node.status({ fill: "red", shape: "ring", text: "missing message" });
17
+ utils.setStatusError(node, "missing message");
20
18
  node.warn("Missing message");
21
19
  if (done) done();
22
20
  return;
@@ -25,18 +23,10 @@ module.exports = function(RED) {
25
23
  // Set or remove context property
26
24
  if (node.removeLabel) {
27
25
  delete msg.context;
28
- node.status({
29
- fill: "blue",
30
- shape: "dot",
31
- text: `in: ${msg.payload}, out: removed context`
32
- });
26
+ utils.setStatusChanged(node, `in: ${msg.payload}, out: removed context`);
33
27
  } else {
34
28
  msg.context = node.contextPropertyName;
35
- node.status({
36
- fill: "blue",
37
- shape: "dot",
38
- text: `in: ${msg.payload}, out: ${node.contextPropertyName}`
39
- });
29
+ utils.setStatusChanged(node, `in: ${msg.payload}, out: ${node.contextPropertyName}`);
40
30
  }
41
31
 
42
32
  send(msg);
@@ -3,6 +3,10 @@
3
3
  <label for="node-input-name" title="Display name shown on the canvas"><i class="fa fa-tag"></i> Name</label>
4
4
  <input type="text" id="node-input-name" placeholder="Name">
5
5
  </div>
6
+ <div class="form-row">
7
+ <label for="node-input-inputProperty" title="Message property to read input from"><i class="fa fa-folder-open"></i> Input Property</label>
8
+ <input type="text" id="node-input-inputProperty" placeholder="payload">
9
+ </div>
6
10
  <div class="form-row">
7
11
  <label for="node-input-conversion" title="Select the unit conversion to perform"><i class="fa fa-exchange"></i> Conversion</label>
8
12
  <select id="node-input-conversion">
@@ -62,6 +66,7 @@
62
66
  color: "#301934",
63
67
  defaults: {
64
68
  name: { value: "" },
69
+ inputProperty: { value: "payload" },
65
70
  conversion: { value: "C to F", required: true }
66
71
  },
67
72
  inputs: 1,
@@ -117,54 +122,35 @@
117
122
  </script>
118
123
 
119
124
  <script type="text/markdown" data-help-name="convert-block">
120
- Converts numeric input payload between temperature or pressure units.
125
+ Converts numeric input from a configured property between various units.
121
126
 
122
127
  ### Inputs
128
+ : input-property (number) : Numeric value to convert, read from the configured Input Property.
123
129
  : context (string) : Configures the conversion (`"conversion"`). Unmatched values ignored.
124
- : payload (number) : Numeric value to convert
125
130
 
126
131
  ### Outputs
127
132
  : payload (number) : Converted value (e.g., °C to °F, Pa to inH₂O).
128
133
 
134
+ ### Properties
135
+ : name (string) : Display name in editor.
136
+ : inputProperty (string) : Message property to read input from (default: `payload`). Supports nested properties (e.g., `data.value`).
137
+ : conversion (string) : Conversion type to apply (e.g., "F to C", "Pa to inH₂O").
138
+
129
139
  ### Details
130
- Converts `msg.payload` between units based on the selected conversion.
140
+ Converts numeric input (read from the configured **Input Property**, default: `msg.payload`) between units based on the selected conversion.
131
141
 
132
- ### validConversions = [
133
- "F to C",
134
- "C to F",
135
- "K to C",
136
- "C to K",
137
- "K to F",
138
- "F to K",
139
- "R to F",
140
- "F to R",
141
- "decimal to %",
142
- "% to decimal",
143
- "Pa to inH₂O",
144
- "inH₂O to Pa",
145
- "Pa to inHg",
146
- "inHg to Pa",
147
- "Pa to bar",
148
- "bar to Pa",
149
- "Pa to psi",
150
- "psi to Pa",
151
- "m to ft",
152
- "ft to m",
153
- "m to in",
154
- "in to m",
155
- "mm to in",
156
- "in to mm",
157
- "kg to lb",
158
- "lb to kg",
159
- "L to gal",
160
- "gal to L",
161
- "kW to hp",
162
- "hp to kW",
163
- "rad to deg",
164
- "deg to rad",
165
- "s to min",
166
- "min to s"
167
- ];
142
+ Supported conversions include:
143
+ - Temperature: F to C, C to F, K to C, C to K, K to F, F to K, R to F, F to R
144
+ - Percentage: decimal to %, % to decimal
145
+ - Pressure: Pa to inH₂O, inH₂O to Pa, Pa to inHg, inHg to Pa, Pa to bar, bar to Pa, Pa to psi, psi to Pa
146
+ - Distance: m to ft, ft to m, m to in, in to m, mm to in, in to mm
147
+ - Weight: kg to lb, lb to kg
148
+ - Volume: L to gal, gal to L
149
+ - Power: kW to hp, hp to kW
150
+ - Angle: rad to deg, deg to rad
151
+ - Time: s to min, min to s
152
+
153
+ Output is always written to `msg.payload`.
168
154
 
169
155
  ### Status
170
156
  - Green (dot): Configuration update
@@ -1,4 +1,6 @@
1
1
  module.exports = function(RED) {
2
+ const utils = require('./utils')(RED);
3
+
2
4
  function ConvertBlockNode(config) {
3
5
  RED.nodes.createNode(this, config);
4
6
  const node = this;
@@ -41,16 +43,16 @@ module.exports = function(RED) {
41
43
  ];
42
44
 
43
45
  // Initialize runtime state
44
- node.runtime = {
45
- conversion: validConversions.includes(config.conversion) ? config.conversion : "C to F"
46
- };
46
+ // Initialize state
47
+ node.inputProperty = config.inputProperty || "payload";
48
+ node.conversion = validConversions.includes(config.conversion) ? config.conversion : "C to F";
47
49
 
48
50
  node.on("input", function(msg, send, done) {
49
51
  send = send || function() { node.send.apply(node, arguments); };
50
52
 
51
53
  // Guard against invalid message
52
54
  if (!msg) {
53
- node.status({ fill: "red", shape: "ring", text: "missing message" });
55
+ utils.setStatusError(node, "missing message");
54
56
  if (done) done();
55
57
  return;
56
58
  }
@@ -58,18 +60,18 @@ module.exports = function(RED) {
58
60
  // Handle configuration messages
59
61
  if (msg.hasOwnProperty("context")) {
60
62
  if (typeof msg.context !== "string") {
61
- node.status({ fill: "yellow", shape: "ring", text: "unknown context" });
63
+ utils.setStatusWarn(node, "unknown context");
62
64
  if (done) done();
63
65
  return;
64
66
  }
65
67
  if (msg.context === "conversion") {
66
68
  if (!msg.hasOwnProperty("payload") || !validConversions.includes(msg.payload)) {
67
- node.status({ fill: "red", shape: "ring", text: "invalid conversion" });
69
+ utils.setStatusError(node, "invalid conversion");
68
70
  if (done) done();
69
71
  return;
70
72
  }
71
- node.runtime.conversion = msg.payload;
72
- node.status({ fill: "green", shape: "dot", text: `conversion: ${node.runtime.conversion}` });
73
+ node.conversion = msg.payload;
74
+ utils.setStatusOK(node, `conversion: ${node.conversion}`);
73
75
  if (done) done();
74
76
  return;
75
77
  }
@@ -78,23 +80,29 @@ module.exports = function(RED) {
78
80
  return;
79
81
  }
80
82
 
81
- // Validate input payload
82
- if (!msg.hasOwnProperty("payload")) {
83
- node.status({ fill: "red", shape: "ring", text: "missing payload" });
83
+ // Get input from configured property
84
+ let input;
85
+ try {
86
+ input = RED.util.getMessageProperty(msg, node.inputProperty);
87
+ } catch (err) {
88
+ input = undefined;
89
+ }
90
+ if (input === undefined) {
91
+ utils.setStatusError(node, "missing or invalid input property");
84
92
  if (done) done();
85
93
  return;
86
94
  }
87
95
 
88
- const inputValue = parseFloat(msg.payload);
96
+ const inputValue = parseFloat(input);
89
97
  if (isNaN(inputValue) || !isFinite(inputValue)) {
90
- node.status({ fill: "red", shape: "ring", text: `Invalid payload` });
98
+ utils.setStatusError(node, "invalid input");
91
99
  if (done) done();
92
100
  return;
93
101
  }
94
102
 
95
103
  // Perform conversion
96
104
  let output, inUnit, outUnit;
97
- switch (node.runtime.conversion) {
105
+ switch (node.conversion) {
98
106
  case "F to C":
99
107
  output = (inputValue - 32) * 5 / 9;
100
108
  inUnit = "°F";
@@ -268,11 +276,18 @@ module.exports = function(RED) {
268
276
  }
269
277
 
270
278
  // Format status numbers
271
- const inDisplay = msg.payload % 1 === 0 ? msg.payload : msg.payload.toFixed(2);
279
+ let num = Number(msg.payload);
280
+ let inDisplay = 0
281
+ if (isNaN(num)) {
282
+ inDisplay = 0;
283
+ } else {
284
+ inDisplay = num % 1 === 0 ? num : num.toFixed(2);
285
+ msg.payload = inDisplay;
286
+ }
272
287
  const outDisplay = output % 1 === 0 ? output : output.toFixed(2);
273
288
 
274
289
  // Update status and send output
275
- node.status({ fill: "blue", shape: "dot", text: `${inDisplay} ${inUnit} → ${outDisplay} ${outUnit}` });
290
+ utils.setStatusOK(node, `${inDisplay} ${inUnit} → ${outDisplay} ${outUnit}`);
276
291
 
277
292
  msg.payload = output;
278
293
  send(msg);
@@ -4,6 +4,10 @@
4
4
  <label for="node-input-name" title="Display name shown on the canvas"><i class="fa fa-tag"></i> Name</label>
5
5
  <input type="text" id="node-input-name" placeholder="Name">
6
6
  </div>
7
+ <div class="form-row">
8
+ <label for="node-input-inputProperty" title="Message property to read input from"><i class="fa fa-folder-open"></i> Input Property</label>
9
+ <input type="text" id="node-input-inputProperty" placeholder="payload">
10
+ </div>
7
11
  </script>
8
12
 
9
13
  <!-- JavaScript Section: Registers the node and handles editor logic -->
@@ -12,7 +16,8 @@
12
16
  category: "bldgblocks control",
13
17
  color: "#301934",
14
18
  defaults: {
15
- name: { value: "" }
19
+ name: { value: "" },
20
+ inputProperty: { value: "payload" }
16
21
  },
17
22
  inputs: 1,
18
23
  outputs: 1,
@@ -28,21 +33,22 @@
28
33
 
29
34
  <!-- Help Section -->
30
35
  <script type="text/markdown" data-help-name="count-block">
31
- Counts boolean rising edges in the input.
36
+ Counts boolean rising edges from a configured property.
32
37
 
33
38
  ### Inputs
39
+ : input-property (boolean) : Boolean value to detect rising edges, read from the configured Input Property.
34
40
  : context (string) : Resets count (`"reset"`). Unmatched values trigger error.
35
- : payload (boolean) : Input boolean to detect rising edges.
36
41
 
37
42
  ### Outputs
38
43
  : payload (number) : Current count of rising edges.
39
44
 
40
45
  ### Properties
41
46
  : name (string) : Display name in editor.
47
+ : inputProperty (string) : Message property to read input from (default: `payload`). Supports nested properties (e.g., `data.value`).
42
48
 
43
49
  ### Details
44
- Counts rising edges (false-to-true transitions) in `msg.payload` (boolean), incrementing the count on each transition.
45
- Resets count to 0 via `msg.context = "reset"` with `msg.payload = true`.
50
+ Counts rising edges (false-to-true transitions) in the configured **Input Property** (default: `msg.payload`), incrementing the count on each transition.
51
+ Resets count to 0 via `msg.context = "reset"` with `msg.payload = true`.
46
52
 
47
53
  ### Status
48
54
  - Green (dot): Configuration update
@@ -1,21 +1,23 @@
1
1
  module.exports = function(RED) {
2
+ const utils = require('./utils')(RED);
3
+
2
4
  function CountBlockNode(config) {
3
5
  RED.nodes.createNode(this, config);
4
6
  const node = this;
5
7
 
6
8
  // Initialize runtime state
7
- node.runtime = {
8
- name: config.name,
9
- count: 0,
10
- prevState: false
11
- };
9
+ // Initialize state
10
+ node.name = config.name;
11
+ node.inputProperty = config.inputProperty || "payload";
12
+ node.count = 0;
13
+ node.prevState = false;
12
14
 
13
15
  node.on("input", function(msg, send, done) {
14
16
  send = send || function() { node.send.apply(node, arguments); };
15
17
 
16
18
  // Guard against invalid message
17
19
  if (!msg) {
18
- node.status({ fill: "red", shape: "ring", text: "invalid message" });
20
+ utils.setStatusError(node, "invalid message");
19
21
  if (done) done();
20
22
  return;
21
23
  }
@@ -23,62 +25,62 @@ module.exports = function(RED) {
23
25
  // Handle context updates
24
26
  if (msg.hasOwnProperty("context")) {
25
27
  if (!msg.hasOwnProperty("payload")) {
26
- node.status({ fill: "red", shape: "ring", text: "missing payload for reset" });
28
+ utils.setStatusError(node, "missing payload for reset");
27
29
  if (done) done();
28
30
  return;
29
31
  }
30
32
  if (msg.context === "reset") {
31
- if (typeof msg.payload !== "boolean") {
32
- node.status({ fill: "red", shape: "ring", text: "invalid reset" });
33
+ const boolVal = utils.validateBoolean(msg.payload);
34
+ if (!boolVal.valid) {
35
+ utils.setStatusError(node, boolVal.error);
33
36
  if (done) done();
34
37
  return;
35
38
  }
36
- if (msg.payload === true) {
37
- node.runtime.count = 0;
38
- node.runtime.prevState = false;
39
- node.status({ fill: "green", shape: "dot", text: "state reset" });
40
- send({ payload: node.runtime.count });
39
+ if (boolVal.value === true) {
40
+ node.count = 0;
41
+ node.prevState = false;
42
+ utils.setStatusOK(node, "state reset");
43
+ send({ payload: node.count });
41
44
  }
42
45
  if (done) done();
43
46
  return;
44
47
  } else {
45
- node.status({ fill: "yellow", shape: "ring", text: "unknown context" });
48
+ utils.setStatusWarn(node, "unknown context");
46
49
  if (done) done("Unknown context");
47
50
  return;
48
51
  }
49
52
  }
50
53
 
51
- // Validate input
52
- if (!msg.hasOwnProperty("payload")) {
53
- node.status({ fill: "red", shape: "ring", text: "missing input" });
54
- if (done) done();
55
- return;
54
+ // Get input value from configured property
55
+ let inputValue;
56
+ try {
57
+ inputValue = RED.util.getMessageProperty(msg, node.inputProperty);
58
+ } catch (err) {
59
+ inputValue = undefined;
56
60
  }
57
-
58
- const inputValue = msg.payload;
59
61
  if (typeof inputValue !== "boolean") {
60
- node.status({ fill: "red", shape: "ring", text: "invalid input" });
62
+ utils.setStatusError(node, "missing or invalid input property");
61
63
  if (done) done();
62
64
  return;
63
65
  }
64
66
 
65
67
  // Prevent integer overflow
66
- if (node.runtime.count > Number.MAX_SAFE_INTEGER - 100000) {
67
- node.runtime.count = 0;
68
- node.status({ fill: "yellow", shape: "ring", text: "count overflow reset" });
68
+ if (node.count > Number.MAX_SAFE_INTEGER - 100000) {
69
+ node.count = 0;
70
+ utils.setStatusWarn(node, "count overflow reset");
69
71
  }
70
72
 
71
73
  // Increment on false → true transition
72
- if (!node.runtime.prevState && inputValue === true) {
73
- node.runtime.count++;
74
- node.status({ fill: "blue", shape: "dot", text: `count: ${node.runtime.count}` });
75
- send({ payload: node.runtime.count });
74
+ if (!node.prevState && inputValue === true) {
75
+ node.count++;
76
+ utils.setStatusChanged(node, `count: ${node.count}`);
77
+ send({ payload: node.count });
76
78
  } else {
77
- node.status({ fill: "blue", shape: "ring", text: `count: ${node.runtime.count}` });
79
+ utils.setStatusUnchanged(node, `count: ${node.count}`);
78
80
  }
79
81
 
80
82
  // Update prevState
81
- node.runtime.prevState = inputValue;
83
+ node.prevState = inputValue;
82
84
 
83
85
  if (done) done();
84
86
  });