@bldgblocks/node-red-contrib-control 0.1.4

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 (98) hide show
  1. package/README.md +43 -0
  2. package/nodes/accumulate-block.html +71 -0
  3. package/nodes/accumulate-block.js +104 -0
  4. package/nodes/add-block.html +67 -0
  5. package/nodes/add-block.js +97 -0
  6. package/nodes/analog-switch-block.html +65 -0
  7. package/nodes/analog-switch-block.js +129 -0
  8. package/nodes/and-block.html +64 -0
  9. package/nodes/and-block.js +73 -0
  10. package/nodes/average-block.html +97 -0
  11. package/nodes/average-block.js +137 -0
  12. package/nodes/boolean-switch-block.html +59 -0
  13. package/nodes/boolean-switch-block.js +88 -0
  14. package/nodes/boolean-to-number-block.html +59 -0
  15. package/nodes/boolean-to-number-block.js +45 -0
  16. package/nodes/cache-block.html +69 -0
  17. package/nodes/cache-block.js +106 -0
  18. package/nodes/call-status-block.html +111 -0
  19. package/nodes/call-status-block.js +274 -0
  20. package/nodes/changeover-block.html +234 -0
  21. package/nodes/changeover-block.js +392 -0
  22. package/nodes/comment-block.html +83 -0
  23. package/nodes/comment-block.js +53 -0
  24. package/nodes/compare-block.html +64 -0
  25. package/nodes/compare-block.js +84 -0
  26. package/nodes/contextual-label-block.html +67 -0
  27. package/nodes/contextual-label-block.js +52 -0
  28. package/nodes/convert-block.html +179 -0
  29. package/nodes/convert-block.js +289 -0
  30. package/nodes/count-block.html +57 -0
  31. package/nodes/count-block.js +92 -0
  32. package/nodes/debounce-block.html +64 -0
  33. package/nodes/debounce-block.js +140 -0
  34. package/nodes/delay-block.html +104 -0
  35. package/nodes/delay-block.js +180 -0
  36. package/nodes/divide-block.html +65 -0
  37. package/nodes/divide-block.js +123 -0
  38. package/nodes/edge-block.html +71 -0
  39. package/nodes/edge-block.js +120 -0
  40. package/nodes/frequency-block.html +55 -0
  41. package/nodes/frequency-block.js +140 -0
  42. package/nodes/hysteresis-block.html +131 -0
  43. package/nodes/hysteresis-block.js +142 -0
  44. package/nodes/interpolate-block.html +74 -0
  45. package/nodes/interpolate-block.js +141 -0
  46. package/nodes/load-sequence-block.html +134 -0
  47. package/nodes/load-sequence-block.js +272 -0
  48. package/nodes/max-block.html +76 -0
  49. package/nodes/max-block.js +103 -0
  50. package/nodes/memory-block.html +90 -0
  51. package/nodes/memory-block.js +241 -0
  52. package/nodes/min-block.html +77 -0
  53. package/nodes/min-block.js +106 -0
  54. package/nodes/minmax-block.html +89 -0
  55. package/nodes/minmax-block.js +119 -0
  56. package/nodes/modulo-block.html +73 -0
  57. package/nodes/modulo-block.js +126 -0
  58. package/nodes/multiply-block.html +63 -0
  59. package/nodes/multiply-block.js +115 -0
  60. package/nodes/negate-block.html +55 -0
  61. package/nodes/negate-block.js +91 -0
  62. package/nodes/nullify-block.html +111 -0
  63. package/nodes/nullify-block.js +78 -0
  64. package/nodes/on-change-block.html +79 -0
  65. package/nodes/on-change-block.js +191 -0
  66. package/nodes/oneshot-block.html +96 -0
  67. package/nodes/oneshot-block.js +169 -0
  68. package/nodes/or-block.html +64 -0
  69. package/nodes/or-block.js +73 -0
  70. package/nodes/pid-block.html +205 -0
  71. package/nodes/pid-block.js +407 -0
  72. package/nodes/priority-block.html +66 -0
  73. package/nodes/priority-block.js +239 -0
  74. package/nodes/rate-limit-block.html +99 -0
  75. package/nodes/rate-limit-block.js +221 -0
  76. package/nodes/round-block.html +73 -0
  77. package/nodes/round-block.js +89 -0
  78. package/nodes/saw-tooth-wave-block.html +87 -0
  79. package/nodes/saw-tooth-wave-block.js +161 -0
  80. package/nodes/scale-range-block.html +90 -0
  81. package/nodes/scale-range-block.js +137 -0
  82. package/nodes/sine-wave-block.html +88 -0
  83. package/nodes/sine-wave-block.js +142 -0
  84. package/nodes/subtract-block.html +64 -0
  85. package/nodes/subtract-block.js +103 -0
  86. package/nodes/thermistor-block.html +81 -0
  87. package/nodes/thermistor-block.js +146 -0
  88. package/nodes/tick-tock-block.html +66 -0
  89. package/nodes/tick-tock-block.js +110 -0
  90. package/nodes/time-sequence-block.html +67 -0
  91. package/nodes/time-sequence-block.js +144 -0
  92. package/nodes/triangle-wave-block.html +86 -0
  93. package/nodes/triangle-wave-block.js +154 -0
  94. package/nodes/tstat-block.html +311 -0
  95. package/nodes/tstat-block.js +499 -0
  96. package/nodes/units-block.html +150 -0
  97. package/nodes/units-block.js +106 -0
  98. package/package.json +73 -0
@@ -0,0 +1,311 @@
1
+ <script type="text/html" data-template-name="tstat-block">
2
+ <style>
3
+ .input-error {
4
+ border: 2px solid red !important;
5
+ background-color: #ffe6e6 !important;
6
+ }
7
+ </style>
8
+ <div class="form-row">
9
+ <label for="node-input-name" title="Display name shown on the canvas"><i class="fa fa-tag"></i> Name</label>
10
+ <input type="text" id="node-input-name" placeholder="Name">
11
+ </div>
12
+ <div class="form-row">
13
+ <label for="node-input-algorithm" title="Algorithm: single setpoint with diff, split setpoints, or specified setpoints"><i class="fa fa-cog"></i> Algorithm</label>
14
+ <select id="node-input-algorithm">
15
+ <option value="single">Single Setpoint</option>
16
+ <option value="split">Split Setpoint</option>
17
+ <option value="specified">Specified Setpoint</option>
18
+ </select>
19
+ </div>
20
+ <div class="form-row single-setpoint">
21
+ <label for="node-input-setpoint" title="Target temperature setpoint (number from num, msg, flow, or global)"><i class="fa fa-crosshairs"></i> Setpoint</label>
22
+ <input type="text" id="node-input-setpoint" laceholder="70">
23
+ <input type="hidden" id="node-input-setpointType">
24
+ </div>
25
+ <div class="form-row split-setpoint" style="display: none;">
26
+ <label for="node-input-heatingSetpoint" title="Target temperature for heating (number from num, msg, flow, or global)"><i class="fa fa-crosshairs"></i> Heating Setpoint</label>
27
+ <input type="text" id="node-input-heatingSetpoint" placeholder="68">
28
+ <input type="hidden" id="node-input-heatingSetpointType">
29
+ </div>
30
+ <div class="form-row split-setpoint" style="display: none;">
31
+ <label for="node-input-coolingSetpoint" title="Target temperature for cooling (number from num, msg, flow, or global)"><i class="fa fa-crosshairs"></i> Cooling Setpoint</label>
32
+ <input type="text" id="node-input-coolingSetpoint" placeholder="74">
33
+ <input type="hidden" id="node-input-coolingSetpointType">
34
+ </div>
35
+ <div class="form-row split-setpoint" style="display: none;">
36
+ <div id="split-setpoint-warning" style="color: red; display: none; margin-top: 5px;">
37
+ Cooling setpoint must be greater than heating setpoint
38
+ </div>
39
+ </div>
40
+ <div class="form-row specified-setpoint" style="display: none;">
41
+ <label for="node-input-coolingOn" title="Temperature to turn cooling on (number from num, msg, flow, or global)"><i class="fa fa-snowflake-o"></i> Cooling On</label>
42
+ <input type="text" id="node-input-coolingOn" placeholder="74">
43
+ <input type="hidden" id="node-input-coolingOnType">
44
+ </div>
45
+ <div class="form-row specified-setpoint" style="display: none;">
46
+ <label for="node-input-coolingOff" title="Temperature to turn cooling off (number from num, msg, flow, or global)"><i class="fa fa-snowflake-o"></i> Cooling Off</label>
47
+ <input type="text" id="node-input-coolingOff" placeholder="72">
48
+ <input type="hidden" id="node-input-coolingOffType">
49
+ </div>
50
+ <div class="form-row specified-setpoint" style="display: none;">
51
+ <label for="node-input-heatingOff" title="Temperature to turn heating off (number from num, msg, flow, or global)"><i class="fa fa-fire"></i> Heating Off</label>
52
+ <input type="text" id="node-input-heatingOff" placeholder="68">
53
+ <input type="hidden" id="node-input-heatingOffType">
54
+ </div>
55
+ <div class="form-row specified-setpoint" style="display: none;">
56
+ <label for="node-input-heatingOn" title="Temperature to turn heating on (number from num, msg, flow, or global)"><i class="fa fa-fire"></i> Heating On</label>
57
+ <input type="text" id="node-input-heatingOn" placeholder="66">
58
+ <input type="hidden" id="node-input-heatingOnType">
59
+ </div>
60
+ <div class="form-row specified-setpoint" style="display: none;">
61
+ <div id="specified-setpoint-warning" style="color: red; display: none; margin-top: 5px;">
62
+ Cooling setpoints must be greater than or equal to heating setpoints (coolingOn >= coolingOff >= heatingOff >= heatingOn)
63
+ </div>
64
+ </div>
65
+ <div class="form-row single-setpoint">
66
+ <label for="node-input-diff" title="Differential for hysteresis (positive number, used in single algorithm)"><i class="fa fa-arrows-v"></i> Differential</label>
67
+ <input type="text" id="node-input-diff" placeholder="2">
68
+ <input type="hidden" id="node-input-diffType">
69
+ </div>
70
+ <div class="form-row">
71
+ <label for="node-input-anticipator" title="Temperature offset to stop heating/cooling early (non-negative number from num, msg, flow, or global)"><i class="fa fa-tachometer"></i> Anticipator</label>
72
+ <input type="text" id="node-input-anticipator" placeholder="0.5">
73
+ <input type="hidden" id="node-input-anticipatorType">
74
+ </div>
75
+ <div class="form-row">
76
+ <label for="node-input-ignoreAnticipatorCycles" title="Number of cycles to ignore anticipator after mode change (non-negative integer from num, msg, flow, or global)"><i class="fa fa-repeat"></i> Ignore Anticipator Cycles</label>
77
+ <input type="text" id="node-input-ignoreAnticipatorCycles" placeholder="1">
78
+ <input type="hidden" id="node-input-ignoreAnticipatorCyclesType">
79
+ </div>
80
+ <div class="form-row">
81
+ <label for="node-input-isHeating" title="Heating mode (true) or cooling mode (false)"><i class="fa fa-fire"></i> Heating Mode</label>
82
+ <input type="checkbox" id="node-input-isHeating" style="width: auto; vertical-align: middle;">
83
+ </div>
84
+ </script>
85
+
86
+ <script type="text/javascript">
87
+ RED.nodes.registerType("tstat-block", {
88
+ category: "control",
89
+ color: "#301934",
90
+ defaults: {
91
+ name: { value: "" },
92
+ algorithm: { value: "single" },
93
+ setpoint: { value: "70" },
94
+ setpointType: { value: "num" },
95
+ heatingSetpoint: { value: "68" },
96
+ heatingSetpointType: { value: "num" },
97
+ coolingSetpoint: { value: "74" },
98
+ coolingSetpointType: { value: "num" },
99
+ coolingOn: { value: "74" },
100
+ coolingOnType: { value: "num" },
101
+ coolingOff: { value: "72" },
102
+ coolingOffType: { value: "num" },
103
+ heatingOff: { value: "68" },
104
+ heatingOffType: { value: "num" },
105
+ heatingOn: { value: "66" },
106
+ heatingOnType: { value: "num" },
107
+ diff: { value: "2" },
108
+ diffType: { value: "num" },
109
+ anticipator: { value: "0.5" },
110
+ anticipatorType: { value: "num" },
111
+ ignoreAnticipatorCycles: { value: "1" },
112
+ ignoreAnticipatorCyclesType: { value: "num" },
113
+ isHeating: { value: false }
114
+ },
115
+ inputs: 1,
116
+ outputs: 3,
117
+ outputLabels: ["isHeating", "above", "below"],
118
+ inputLabels: ["input"],
119
+ icon: "font-awesome/fa-thermometer-half",
120
+ paletteLabel: "tstat",
121
+ label: function() {
122
+ return this.name || "tstat";
123
+ },
124
+ oneditprepare: function() {
125
+ const node = this;
126
+ try {
127
+ const $algorithm = $("#node-input-algorithm");
128
+ const $singleFields = $(".single-setpoint");
129
+ const $splitFields = $(".split-setpoint");
130
+ const $specifiedFields = $(".specified-setpoint");
131
+
132
+ $("#node-input-name").val(node.name || "");
133
+ $("#node-input-algorithm").val(node.algorithm || "single");
134
+ $("#node-input-isHeating").prop("checked", node.isHeating === true);
135
+
136
+ const typedInputs = [
137
+ { id: "node-input-setpoint", typeId: "node-input-setpointType", defaultValue: "70", defaultType: "num" },
138
+ { id: "node-input-heatingSetpoint", typeId: "node-input-heatingSetpointType", defaultValue: "68", defaultType: "num" },
139
+ { id: "node-input-coolingSetpoint", typeId: "node-input-coolingSetpointType", defaultValue: "74", defaultType: "num" },
140
+ { id: "node-input-coolingOn", typeId: "node-input-coolingOnType", defaultValue: "74", defaultType: "num" },
141
+ { id: "node-input-coolingOff", typeId: "node-input-coolingOffType", defaultValue: "72", defaultType: "num" },
142
+ { id: "node-input-heatingOff", typeId: "node-input-heatingOffType", defaultValue: "68", defaultType: "num" },
143
+ { id: "node-input-heatingOn", typeId: "node-input-heatingOnType", defaultValue: "66", defaultType: "num" },
144
+ { id: "node-input-diff", typeId: "node-input-diffType", defaultValue: "2", defaultType: "num" },
145
+ { id: "node-input-anticipator", typeId: "node-input-anticipatorType", defaultValue: "0.5", defaultType: "num" },
146
+ { id: "node-input-ignoreAnticipatorCycles", typeId: "node-input-ignoreAnticipatorCyclesType", defaultValue: "1", defaultType: "num" }
147
+ ];
148
+
149
+ typedInputs.forEach(input => {
150
+ try {
151
+ const fieldName = input.id.replace("node-input-", "");
152
+ const storedValue = node[fieldName] !== undefined ? node[fieldName] : input.defaultValue;
153
+ const storedType = node[`${fieldName}Type`] || input.defaultType;
154
+
155
+ $(`#${input.id}`).typedInput({
156
+ default: input.defaultType,
157
+ types: ["num", "msg", "flow", "global"],
158
+ typeField: `#${input.typeId}`
159
+ }).typedInput("type", storedType)
160
+ .typedInput("value", storedValue);
161
+ } catch (err) {
162
+ console.error(`Error initializing typedInput for ${input.id}:`, err);
163
+ }
164
+ });
165
+
166
+
167
+ function toggleFields() {
168
+ const algorithm = $algorithm.val();
169
+ if (algorithm === "single") {
170
+ $singleFields.show();
171
+ $splitFields.hide();
172
+ $specifiedFields.hide();
173
+ $("#split-setpoint-warning").hide();
174
+ $("#specified-setpoint-warning").hide();
175
+ $("#node-input-heatingSetpoint").removeClass("input-error");
176
+ $("#node-input-coolingSetpoint").removeClass("input-error");
177
+ $("#node-input-coolingOn").removeClass("input-error");
178
+ $("#node-input-coolingOff").removeClass("input-error");
179
+ $("#node-input-heatingOff").removeClass("input-error");
180
+ $("#node-input-heatingOn").removeClass("input-error");
181
+ } else if (algorithm === "split") {
182
+ $singleFields.hide();
183
+ $splitFields.show();
184
+ $specifiedFields.hide();
185
+ $("#specified-setpoint-warning").hide();
186
+ $("#node-input-coolingOn").removeClass("input-error");
187
+ $("#node-input-coolingOff").removeClass("input-error");
188
+ $("#node-input-heatingOff").removeClass("input-error");
189
+ $("#node-input-heatingOn").removeClass("input-error");
190
+ validateSplitSetpoints();
191
+ } else {
192
+ $singleFields.hide();
193
+ $splitFields.hide();
194
+ $specifiedFields.show();
195
+ $("#split-setpoint-warning").hide();
196
+ $("#node-input-heatingSetpoint").removeClass("input-error");
197
+ $("#node-input-coolingSetpoint").removeClass("input-error");
198
+ validateSpecifiedSetpoints();
199
+ }
200
+ }
201
+
202
+ $algorithm.on("change", function() {
203
+ toggleFields();
204
+ });
205
+
206
+ toggleFields();
207
+ } catch (err) {
208
+ console.error("Error in tstat-block oneditprepare:", err.message, err.stack);
209
+ $("#node-runtime-changes").text("Effective Setpoints: Error initializing UI - check browser console");
210
+ }
211
+ },
212
+ oneditsave: function() {
213
+ // Standard config properties are automatically saved by Node-RED
214
+ },
215
+ oneditvalidate: function() {
216
+ return validateInputs();
217
+ }
218
+ });
219
+ </script>
220
+
221
+ <script type="text/markdown" data-help-name="tstat-block">
222
+ Thermostat controller for heating/cooling with single, split, or specified setpoint operation, hysteresis, and anticipation to prevent or allow overshoot for testing.
223
+
224
+ ### Inputs
225
+ : context (string) : Configures node (`"algorithm"`, `"setpoint"`, `"heatingSetpoint"`, `"coolingSetpoint"`, `"coolingOn"`, `"coolingOff"`, `"heatingOff"`, `"heatingOn"`, `"diff"`, `"anticipator"`, `"ignoreAnticipatorCycles"`, `"isHeating"`, `"status"`).
226
+ : payload (number | boolean | string) : Number for temperature or config values, boolean for `isHeating`, string for `algorithm` (`"single"`, `"split"`, `"specified"`).
227
+
228
+ ### Outputs
229
+ All output messages include a `msg.status` object containing runtime information
230
+ : isHeating (boolean) : `true` for heating mode, `false` for cooling mode. Includes `msg.context = "isHeating"`.
231
+ : above (boolean) : `true` if input exceeds cooling on threshold.
232
+ : below (boolean) : `true` if input is below heating on threshold.
233
+ : status (object) : Contains detailed runtime information including
234
+ - `algorithm` Current algorithm in use
235
+ - `input` Current temperature input value
236
+ - `isHeating` Current heating mode
237
+ - `above/below` Current output states
238
+ - Algorithm-specific setpoints and values
239
+ - `modeChanged` If mode recently changed
240
+ - `cyclesSinceModeChange` Count since last mode change
241
+ - `effectiveAnticipator` Current anticipator value after mode change adjustments
242
+
243
+ ### Status Monitoring
244
+ Instead of a dedicated status output, all outputs include comprehensive status information
245
+ ```json
246
+ {
247
+ "status": {
248
+ "algorithm": "single",
249
+ "input": 68.5,
250
+ "isHeating": true,
251
+ "above": false,
252
+ "below": true,
253
+ "setpoint": 70,
254
+ "diff": 2,
255
+ "anticipator": 0.5,
256
+ "modeChanged": false,
257
+ "cyclesSinceModeChange": 3,
258
+ "effectiveAnticipator": 0.5
259
+ }
260
+ }
261
+ ```
262
+
263
+ ### Algorithms
264
+ - Single Setpoint
265
+ - Uses `setpoint`, `diff`, and `anticipator`.
266
+ - Sets `above` if `input > setpoint + diff/2`, clears when `input < setpoint + anticipator`.
267
+ - Sets `below` if `input < setpoint - diff/2`, clears when `input > setpoint - anticipator`.
268
+ - For positive `anticipator`, stops early to prevent overshoot. For negative `anticipator`, delays turn-off to overshoot setpoint, lengthen runtime, or straddle setpoint.
269
+
270
+ - Split Setpoint
271
+ - Uses `heatingSetpoint`, `coolingSetpoint`, `diff`, and `anticipator`.
272
+ - For `isHeating = true`
273
+ - Sets `below` if `input < heatingSetpoint - diff/2`, clears when `input > heatingSetpoint - anticipator`.
274
+ - `above` is `false`.
275
+ - For `isHeating = false`
276
+ - Sets `above` if `input > coolingSetpoint + diff/2`, clears when `input < coolingSetpoint + anticipator`.
277
+ - `below` is `false`.
278
+ - Ensures `heatingSetpoint < coolingSetpoint`.
279
+
280
+ - Specified Setpoint
281
+ - Uses `coolingOn`, `coolingOff`, `heatingOff`, `heatingOn`, and `anticipator`.
282
+ - For `isHeating = false`
283
+ - Sets `above` if `input > coolingOn`, clears when `input < coolingOff + anticipator`.
284
+ - `below` is `false`.
285
+ - For `isHeating = true`
286
+ - Sets `below` if `input < heatingOn`, clears when `input > heatingOff - anticipator`.
287
+ - `above` is `false`.
288
+ - Validates `coolingOn >= coolingOff >= heatingOff >= heatingOn`.
289
+
290
+ ### Details
291
+ Compares a numeric temperature input (`msg.payload`) against setpoints to control heating or cooling.
292
+
293
+ The `differential` (`diff`) applies to the `single` and `split` setpoint algorithms, providing hysteresis.
294
+
295
+ The `anticipator` adjusts turn-off points. Positive values stop early to prevent overshoot (subtracts for heating, adds for cooling);
296
+
297
+ The `isHeating` flag (typically from a changeover node) sets output 1 and selects the active setpoint(s).
298
+
299
+ The `ignoreAnticipatorCycles` setting allows ignoring the anticipator for a specified number of cycles after a mode change to reduce short-cycling due to latent heat effects.
300
+
301
+ ### Status
302
+ - Green (dot): Configuration update
303
+ - Blue (dot): State changed
304
+ - Blue (ring): State unchanged
305
+ - Red (ring): Error
306
+ - Yellow (ring): Warning
307
+
308
+ ### References
309
+ - [Node-RED Documentation](https://nodered.org/docs/)
310
+ - [GitHub Repository](https://github.com/BldgBlocks/node-red-contrib-buildingblocks-control.git)
311
+ </script>