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

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 +464 -0
  45. package/nodes/history-collector.html +29 -1
  46. package/nodes/history-collector.js +46 -16
  47. package/nodes/history-config.html +13 -1
  48. package/nodes/history-service.html +84 -0
  49. package/nodes/history-service.js +66 -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
@@ -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
  <div class="form-row">
8
12
  <label for="node-input-nullToZero"><i class="fa fa-cog"></i> Null Mapping</label>
9
13
  <input type="checkbox" id="node-input-nullToZero" style="width: auto; margin-left: 10px;"> Map null to 0
@@ -17,6 +21,7 @@
17
21
  color: "#301934",
18
22
  defaults: {
19
23
  name: { value: "" },
24
+ inputProperty: { value: "payload" },
20
25
  nullToZero: { value: false }
21
26
  },
22
27
  inputs: 1,
@@ -33,18 +38,26 @@
33
38
 
34
39
  <!-- Help Section -->
35
40
  <script type="text/markdown" data-help-name="boolean-to-number-block">
36
- Converts a boolean or null input payload to a numeric output.
41
+ Converts a boolean or null input from a configured property to a numeric output.
37
42
 
38
43
  ### Inputs
39
- : payload (boolean | null) : Value to convert (`true`, `false`, `null`).
44
+ : input-property (boolean | null) : Value to convert, read from the configured Input Property.
40
45
 
41
46
  ### Outputs
42
- : payload (number) : Converted value `null` to `0` (if `nullToZero`) or `-1`, `false` to `0`, `true` to `1`.
47
+ : payload (number) : Converted value: `null` to `0` (if `nullToZero` is true) or `-1`, `false` to `0`, `true` to `1`.
48
+
49
+ ### Properties
50
+ : name (string) : Display name in editor.
51
+ : inputProperty (string) : Message property to read input from (default: `payload`). Supports nested properties (e.g., `data.value`).
52
+ : nullToZero (boolean) : When checked, `null` maps to `0`; when unchecked, `null` maps to `-1`.
43
53
 
44
54
  ### Details
45
- Converts `msg.payload` (boolean or null) to a number `null` maps to `0` (if `nullToZero` is `true`) or `-1` (if `false`), `false` to `0`, `true` to `1`.
55
+ Converts boolean or null input (read from the configured **Input Property**, default: `msg.payload`) to a number:
56
+ - `true` → `1`
57
+ - `false` → `0`
58
+ - `null` → `0` (if **Null Mapping** is checked) or `-1` (if unchecked)
46
59
 
47
- Passthrough all other input message properties
60
+ Output is always written to `msg.payload`. All other input message properties are passed through unchanged.
48
61
 
49
62
  ### Status
50
63
  - Green (dot): Configuration update
@@ -1,36 +1,44 @@
1
1
  module.exports = function(RED) {
2
+ const utils = require('./utils')(RED);
3
+
2
4
  function BooleanToNumberBlockNode(config) {
3
5
  RED.nodes.createNode(this, config);
4
6
  const node = this;
5
7
 
6
8
  // Initialize runtime for editor display
7
- node.runtime = {
8
- name: config.name,
9
- nullToZero: Boolean(config.nullToZero)
10
- };
9
+ // Initialize state
10
+ node.name = config.name;
11
+ node.inputProperty = config.inputProperty || "payload";
12
+ node.nullToZero = Boolean(config.nullToZero);
11
13
 
12
14
  node.on("input", function(msg, send, done) {
13
15
  send = send || function() { node.send.apply(node, arguments); };
14
16
 
15
- // Check for missing payload
16
- if (!msg.hasOwnProperty("payload")) {
17
- node.status({ fill: "red", shape: "ring", text: "missing payload" });
17
+ // Check for missing input property
18
+ let inputValue;
19
+ try {
20
+ inputValue = RED.util.getMessageProperty(msg, node.inputProperty);
21
+ } catch (err) {
22
+ inputValue = undefined;
23
+ }
24
+ if (inputValue === undefined) {
25
+ utils.setStatusError(node, "missing or invalid input property");
18
26
  if (done) done();
19
27
  return;
20
28
  }
21
29
 
22
- // Validate and convert payload
23
- const inputDisplay = msg.payload === null ? "null" : String(msg.payload);
24
- if (msg.payload === null) {
25
- msg.payload = node.runtime.nullToZero ? 0 : -1;
26
- node.status({ fill: "blue", shape: "dot", text: `in: ${inputDisplay}, out: ${msg.payload}` });
30
+ // Validate and convert input
31
+ const inputDisplay = inputValue === null ? "null" : String(inputValue);
32
+ if (inputValue === null) {
33
+ msg.payload = node.nullToZero ? 0 : -1;
34
+ utils.setStatusChanged(node, `in: ${inputDisplay}, out: ${msg.payload}`);
27
35
  send(msg);
28
- } else if (typeof msg.payload === "boolean") {
29
- msg.payload = msg.payload ? 1 : 0;
30
- node.status({ fill: "blue", shape: "dot", text: `in: ${inputDisplay}, out: ${msg.payload}` });
36
+ } else if (typeof inputValue === "boolean") {
37
+ msg.payload = inputValue ? 1 : 0;
38
+ utils.setStatusChanged(node, `in: ${inputDisplay}, out: ${msg.payload}`);
31
39
  send(msg);
32
40
  } else {
33
- node.status({ fill: "red", shape: "ring", text: "invalid payload" });
41
+ utils.setStatusError(node, "invalid input type");
34
42
  }
35
43
 
36
44
  if (done) done();
@@ -1,28 +1,29 @@
1
1
  module.exports = function(RED) {
2
+ const utils = require('./utils')(RED);
3
+
2
4
  function CacheBlockNode(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
- operationMode: config.operationMode,
10
- cachedMessage: null
11
- };
9
+ // Initialize state
10
+ node.name = config.name;
11
+ node.operationMode = config.operationMode;
12
+ node.cachedMessage = null;
12
13
 
13
14
  node.on("input", function(msg, send, done) {
14
15
  send = send || function() { node.send.apply(node, arguments); };
15
16
 
16
17
  // Guard against invalid message
17
18
  if (!msg) {
18
- node.status({ fill: "red", shape: "ring", text: "invalid message" });
19
+ utils.setStatusError(node, "invalid message");
19
20
  if (done) done();
20
21
  return;
21
22
  }
22
23
 
23
24
  // Validate context
24
25
  if (!msg.hasOwnProperty("context") || typeof msg.context !== "string") {
25
- node.status({ fill: "red", shape: "ring", text: "missing context" });
26
+ utils.setStatusError(node, "missing context");
26
27
  if (done) done();
27
28
  return;
28
29
  }
@@ -31,39 +32,29 @@ module.exports = function(RED) {
31
32
  case "update":
32
33
  // Validate payload
33
34
  if (!msg.hasOwnProperty("payload")) {
34
- node.status({ fill: "red", shape: "ring", text: "missing payload" });
35
+ utils.setStatusError(node, "missing payload");
35
36
  if (done) done();
36
37
  return;
37
38
  }
38
39
 
39
- node.runtime.cachedMessage = RED.util.cloneMessage(msg);
40
- node.status({
41
- fill: "green",
42
- shape: "dot",
43
- text: `update: ${typeof msg.payload === "number" ? msg.payload.toFixed(2) : JSON.stringify(msg.payload).slice(0, 20)}`
44
- });
40
+ node.cachedMessage = RED.util.cloneMessage(msg);
41
+ const updateText = `update: ${typeof msg.payload === "number" ? msg.payload.toFixed(2) : JSON.stringify(msg.payload).slice(0, 20)}`;
42
+ utils.setStatusOK(node, updateText);
45
43
  if (done) done();
46
44
  return;
47
45
  case "execute":
48
- if (node.runtime.cachedMessage === null) {
49
- node.status({
50
- fill: "blue",
51
- shape: "dot",
52
- text: "execute: null"
53
- });
46
+ if (node.cachedMessage === null) {
47
+ utils.setStatusChanged(node, "execute: null");
54
48
  send({ payload: null });
55
49
  } else {
56
50
  let outputMsg;
57
- if (node.runtime.operationMode === "clone") {
58
- outputMsg = RED.util.cloneMessage(node.runtime.cachedMessage);
51
+ if (node.operationMode === "clone") {
52
+ outputMsg = RED.util.cloneMessage(node.cachedMessage);
59
53
  } else {
60
- outputMsg = { payload: node.runtime.cachedMessage.payload };
54
+ outputMsg = { payload: node.cachedMessage.payload };
61
55
  }
62
- node.status({
63
- fill: "blue",
64
- shape: "dot",
65
- text: `execute: ${typeof outputMsg.payload === "number" ? outputMsg.payload.toFixed(2) : JSON.stringify(outputMsg.payload).slice(0, 20)}`
66
- });
56
+ const executeText = `execute: ${typeof outputMsg.payload === "number" ? outputMsg.payload.toFixed(2) : JSON.stringify(outputMsg.payload).slice(0, 20)}`;
57
+ utils.setStatusChanged(node, executeText);
67
58
  send(outputMsg);
68
59
  }
69
60
  if (done) done();
@@ -71,27 +62,23 @@ module.exports = function(RED) {
71
62
  case "reset":
72
63
  // Validate payload
73
64
  if (!msg.hasOwnProperty("payload")) {
74
- node.status({ fill: "red", shape: "ring", text: "missing payload" });
65
+ utils.setStatusError(node, "missing payload");
75
66
  if (done) done();
76
67
  return;
77
68
  }
78
69
 
79
70
  if (typeof msg.payload !== "boolean" || !msg.payload) {
80
- node.status({ fill: "red", shape: "ring", text: "invalid reset" });
71
+ utils.setStatusError(node, "invalid reset");
81
72
  if (done) done();
82
73
  return;
83
74
  }
84
75
 
85
- node.runtime.cachedMessage = null;
86
- node.status({
87
- fill: "green",
88
- shape: "dot",
89
- text: "reset"
90
- });
76
+ node.cachedMessage = null;
77
+ utils.setStatusOK(node, "reset");
91
78
  if (done) done();
92
79
  return;
93
80
  default:
94
- node.status({ fill: "yellow", shape: "ring", text: "unknown context" });
81
+ utils.setStatusWarn(node, "unknown context");
95
82
  if (done) done("Unknown context");
96
83
  return;
97
84
  }
@@ -4,13 +4,29 @@
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 for call (request) signal"><i class="fa fa-sign-in"></i> Call Property</label>
9
+ <input type="text" id="node-input-inputProperty" placeholder="payload">
10
+ </div>
11
+ <div class="form-row">
12
+ <label for="node-input-statusInputProperty" title="Message property for status (response) signal"><i class="fa fa-sign-out"></i> Status Property</label>
13
+ <input type="text" id="node-input-statusInputProperty" placeholder="status">
14
+ </div>
7
15
  <div class="form-row">
8
16
  <label for="node-input-statusTimeout" title="Time to wait for status response (seconds, positive number)"><i class="fa fa-clock-o"></i> Status Timeout</label>
9
17
  <input type="number" id="node-input-statusTimeout" placeholder="30" min="0.01" step="any">
10
18
  </div>
11
19
  <div class="form-row">
12
- <label for="node-input-clearDelay" title="Delay before clearing status and alarm after call ends (seconds, positive number)"><i class="fa fa-clock-o"></i> Clear Delay</label>
13
- <input type="number" id="node-input-clearDelay" placeholder="10" min="0.01" step="any">
20
+ <label for="node-input-clearDelay" title="Delay before clearing status and alarm after call ends (seconds, 0=immediate)"><i class="fa fa-hourglass"></i> Clear Delay</label>
21
+ <input type="number" id="node-input-clearDelay" placeholder="10" min="0" step="any">
22
+ </div>
23
+ <div class="form-row">
24
+ <label for="node-input-debounce" title="Debounce status flicker (milliseconds, 0=disabled)"><i class="fa fa-filter"></i> Debounce</label>
25
+ <input type="number" id="node-input-debounce" placeholder="100" min="0" step="any">
26
+ </div>
27
+ <div class="form-row">
28
+ <label for="node-input-heartbeatTimeout" title="Heartbeat window for continuous status monitoring (seconds, 0=disabled)"><i class="fa fa-pulse"></i> Heartbeat Timeout</label>
29
+ <input type="number" id="node-input-heartbeatTimeout" placeholder="30" min="0" step="any">
14
30
  </div>
15
31
  <div class="form-row">
16
32
  <label for="node-input-runLostStatus" title="Alarm if status is lost during call (boolean)"><i class="fa fa-exclamation-triangle"></i> Run Lost Status</label>
@@ -37,8 +53,12 @@
37
53
  color: "#301934",
38
54
  defaults: {
39
55
  name: { value: "" },
56
+ inputProperty: { value: "payload" },
57
+ statusInputProperty: { value: "status" },
40
58
  statusTimeout: { value: 30, required: true, validate: function(v) { return !isNaN(parseFloat(v)) && parseFloat(v) > 0; } },
41
- clearDelay: { value: 10, required: true, validate: function(v) { return !isNaN(parseFloat(v)) && parseFloat(v) > 0; } },
59
+ clearDelay: { value: 10, required: true, validate: function(v) { return !isNaN(parseFloat(v)) && parseFloat(v) >= 0; } },
60
+ debounce: { value: 100, required: true, validate: function(v) { return !isNaN(parseFloat(v)) && parseFloat(v) >= 0; } },
61
+ heartbeatTimeout: { value: 30, required: true, validate: function(v) { return !isNaN(parseFloat(v)) && parseFloat(v) >= 0; } },
42
62
  runLostStatus: { value: false },
43
63
  noStatusOnRun: { value: true },
44
64
  runLostStatusMessage: { value: "Status lost during run" },
@@ -58,39 +78,78 @@
58
78
 
59
79
  <!-- Help Section -->
60
80
  <script type="text/markdown" data-help-name="call-status-block">
61
- Monitors call and status signals with alarm diagnostics.
81
+ Monitors call and status signals to detect equipment faults, communication losses, and synchronization errors.
62
82
 
63
83
  ### Inputs
64
- - payload (boolean): Call activation/deactivation signal
65
- - status (boolean): Status response from equipment (only processed when call is active)
84
+
85
+ : payload (boolean) : Requested equipment state (call signal). Default property name for sending call commands. When true, block expects equipment to respond with status=true within timeout period. Property name configurable via inputProperty setting.
86
+ : status (boolean) : Equipment response signal. Updates actual equipment state and triggers state transitions. Default property name `msg.status`. Accepts `msg.context="status"` with `msg.payload` as fallback routing.
66
87
 
67
88
  ### Outputs
68
- - call (boolean): Current call state
69
- - status (object): `{ call: boolean, status: boolean, alarm: boolean, alarmMessage: string, timeout: boolean, neverReceivedStatus: boolean }`
89
+
90
+ : payload (boolean) : Current call/requested state (true=on, false=off).
91
+ : status (object) : State information object with properties: call (boolean), status (boolean), alarm (boolean), alarmMessage (string).
92
+ : diagnostics (object) : Internal state for monitoring with properties: state (state machine state), initialTimeout (boolean), heartbeatActive (boolean), neverReceivedStatus (boolean), lastStatusTime (milliseconds or null), timeSinceLastStatus (milliseconds or null).
70
93
 
71
94
  ### Properties
72
- - name (string): Display name in editor
73
- - statusTimeout (number): Time to wait for status response (seconds, > 0)
74
- - clearDelay (number): Delay before clearing status and alarm after call ends (seconds, > 0)
75
- - runLostStatus (boolean): Alarm if status is lost during call
76
- - noStatusOnRun (boolean): Alarm if no status received during call
77
- - runLostStatusMessage (string): Message for status lost alarm
78
- - noStatusOnRunMessage (string): Message for no status alarm
79
-
80
- ### Alarm Conditions
81
- 1. Status without call: Status=true when call=false (immediate alarm)
82
- 2. No status during call: No status received within timeout period (after statusTimeout)
83
- 3. Lost status during call: Status=false when call=true and status was previously received (immediate alarm)
84
-
85
- ### Behavior
86
- - When call is activated: Expects status=true response
87
- - Status is only processed when call is active
88
- - Clears all state after clearDelay when call ends
89
- - Timer acts as a blocking gate for status reception
90
-
91
- ### Status Indicators
92
- - Green (ring): Configuration errors
93
- - Blue (dot): Normal operation, no alarms
94
- - Red (dot): Active alarm condition
95
- - Yellow (ring): Invalid input
95
+
96
+ - name (string) - Display name in editor. Default: `"call status"`.
97
+ - inputProperty (string) - Input message property for call signal. Default: `"payload"`.
98
+ - statusInputProperty (string) - Input message property for status signal. Default: `"status"`.
99
+ - statusTimeout (number) - Seconds to wait for initial status response (0.01–3600s). Default: `30`. If heartbeat monitoring is disabled, this is the only timeout check.
100
+ - heartbeatTimeout (number) - Seconds for continuous status heartbeat monitoring while call=true (0–3600s). Default: `30`. Set to 0 to disable heartbeat monitoring.
101
+ - clearDelay (number) - Seconds to hold actual state after call deactivated (0–3600s). Default: `10`.
102
+ - debounce (number) - Milliseconds to filter status flicker (0–10000ms). Default: `100`.
103
+ - runLostStatus (checkbox) - Alarm if equipment status becomes false while call is true. Default: checked.
104
+ - noStatusOnRun (checkbox) - Alarm if no status response within initial timeout. Default: checked.
105
+ - runLostStatusMessage (string) - Alarm text for status lost. Default: `"Status lost during run"`.
106
+ - noStatusOnRunMessage (string) - Alarm text for no status. Default: `"No status received during run"`.
107
+
108
+ ### Details
109
+
110
+ The block implements a 4-state controller: IDLE (call=false), WAITING_FOR_STATUS (call=true, status pending), RUNNING (call=true, status=true), and STATUS_LOST (call=true, status=false).
111
+
112
+ When call=true, the block expects two things: (1) Initial status arrival within statusTimeout seconds, and (2) if heartbeatTimeout is enabled, continuous status updates at least once every heartbeatTimeout seconds. If either requirement is violated, an alarm is triggered.
113
+
114
+ When call=false but status=true, the block monitors that status goes false within clearDelay + 1 second. If status remains true beyond this window, an alarm is triggered indicating the equipment did not respond to the deactivation.
115
+
116
+ Input routing processes messages in priority order: Status Update (from `msg.status` or `msg.context="status"`), Call Request (from configured input property).
117
+
118
+ Debounce collapses rapid status changes within the configured window into a single state transition, preventing false alarms from sensor noise. All alarm conditions include 100ms hysteresis to prevent false triggers.
119
+
120
+ #### Heartbeat Monitoring
121
+
122
+ When call=true and heartbeatTimeout > 0, the block continuously verifies that status updates arrive within the heartbeat window. This detects communication loss or equipment failures mid-cycle. Set heartbeatTimeout=0 to disable this feature.
123
+
124
+ #### Alarm Conditions
125
+
126
+ Status Active Without Call (Hysteresis 100ms): Triggered when status=true AND call=false, indicating potential equipment fault, check valve failure, or external signal interference.
127
+
128
+ No Status Response on Run (Hysteresis: statusTimeout seconds): Triggered when call=true but no status arrives within initial timeout, indicating equipment not responding or wiring failure at call initiation.
129
+
130
+ Status Lost During Run (Hysteresis 100ms): Triggered when call=true, status=true initially, but either: (1) status goes false mid-cycle, or (2) heartbeat monitoring detects no status update within heartbeatTimeout window. Indicates mid-cycle shutdown or communication loss.
131
+
132
+ Status Not Clearing (Hysteresis 100ms): Triggered when call=false, status=true initially, but status does not go false within clearDelay + 1 second window. Indicates equipment failed to deactivate.
133
+
134
+ #### HVAC Scenarios
135
+
136
+ Scenario 1: Chiller with unloader valve feedback. Send `{payload: true}` to request call. Chiller responds with `{context: "status", payload: true}` within 30s. As long as call=true and status updates arrive every 30 seconds, no alarm. If 31 seconds elapse without update, heartbeat alarm triggers. Configuration: Status Timeout=30s, Heartbeat Timeout=30s, Run Lost Status=true.
137
+
138
+ Scenario 2: VAV box with wireless controller. Send `{payload: true}` to activate damper. If wireless module not responding, No Status alarm triggers after 10s. Configuration: Status Timeout=10s, Heartbeat Timeout=0 (heartbeat disabled), No Status On Run=true.
139
+
140
+ Scenario 3: Pump with 5-second startup lag. Send `{payload: true}` at t=0. Pump motor spins up for 5 seconds, status arrives at t=5s. Send `{payload: false}` at t=10s. Clear Delay holds actual state for 3 seconds. At t=13s, state fully cleared. Configuration: Status Timeout=8s, Heartbeat Timeout=15s, Clear Delay=3s, Debounce=200ms.
141
+
142
+ Scenario 4: Equipment with 60-second status heartbeat. Enable heartbeat monitoring with 60-second window. Configuration: Status Timeout=30s, Heartbeat Timeout=60s. Status must arrive at least once every 60 seconds to avoid "Status Lost" alarm.
143
+
144
+ ### Status
145
+ - Green (dot): Configuration update
146
+ - Blue (dot): State changed
147
+ - Blue (ring): State unchanged
148
+ - Red (ring): Error
149
+ - Yellow (ring): Warning
150
+
151
+ ### References
152
+
153
+ - [Node-RED Documentation](https://nodered.org/docs/)
154
+ - [GitHub Repository](https://github.com/BldgBlocks/node-red-contrib-buildingblocks-control.git)
96
155
  </script>