@bldgblocks/node-red-contrib-control 0.1.36 → 0.1.38
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.
- package/README.md +10 -0
- package/nodes/alarm-collector.html +11 -0
- package/nodes/alarm-collector.js +13 -0
- package/nodes/alarm-service.js +2 -2
- package/nodes/and-block.js +1 -1
- package/nodes/call-status-block.html +83 -56
- package/nodes/call-status-block.js +335 -248
- package/nodes/changeover-block.html +30 -31
- package/nodes/changeover-block.js +287 -389
- package/nodes/contextual-label-block.js +3 -3
- package/nodes/delay-block.js +74 -13
- package/nodes/global-getter.js +29 -14
- package/nodes/global-setter.js +8 -5
- package/nodes/history-buffer.js +37 -29
- package/nodes/history-collector.js +13 -4
- package/nodes/history-service.js +21 -7
- package/nodes/network-point-read.js +5 -0
- package/nodes/network-service-bridge.js +43 -11
- package/nodes/or-block.js +1 -1
- package/nodes/priority-block.js +1 -1
- package/nodes/tstat-block.html +34 -79
- package/nodes/tstat-block.js +223 -345
- package/nodes/utils.js +1 -1
- package/package.json +90 -75
|
@@ -112,10 +112,18 @@ module.exports = function(RED) {
|
|
|
112
112
|
const pending = node.pendingRequests[data.requestId];
|
|
113
113
|
delete node.pendingRequests[data.requestId];
|
|
114
114
|
|
|
115
|
-
// Suppress error notification during startup phase
|
|
116
|
-
// (allows network to come online without nuisance errors)
|
|
117
115
|
if (pending.isStartupPhase) {
|
|
118
|
-
//
|
|
116
|
+
// During startup: still notify point-read to reset isPollPending,
|
|
117
|
+
// but suppress error logging (network may still be coming online)
|
|
118
|
+
RED.events.emit('pointReference:response', {
|
|
119
|
+
sourceNodeId: data.sourceNodeId,
|
|
120
|
+
pointId: data.pointId,
|
|
121
|
+
value: null,
|
|
122
|
+
error: true,
|
|
123
|
+
errorMessage: "Startup timeout",
|
|
124
|
+
requestId: data.requestId,
|
|
125
|
+
isStartupPhase: true
|
|
126
|
+
});
|
|
119
127
|
return;
|
|
120
128
|
}
|
|
121
129
|
|
|
@@ -195,10 +203,16 @@ module.exports = function(RED) {
|
|
|
195
203
|
const pending = node.pendingRequests[data.requestId];
|
|
196
204
|
delete node.pendingRequests[data.requestId];
|
|
197
205
|
|
|
198
|
-
// Suppress error notification during startup phase
|
|
199
|
-
// (allows network to come online without nuisance errors)
|
|
200
206
|
if (pending.isStartupPhase) {
|
|
201
|
-
//
|
|
207
|
+
// During startup: still notify point-write to reset pending state,
|
|
208
|
+
// but suppress error logging
|
|
209
|
+
RED.events.emit('pointWrite:response', {
|
|
210
|
+
sourceNodeId: pending.sourceNodeId,
|
|
211
|
+
pointId: data.pointId,
|
|
212
|
+
error: "Startup timeout",
|
|
213
|
+
requestId: data.requestId,
|
|
214
|
+
isStartupPhase: true
|
|
215
|
+
});
|
|
202
216
|
return;
|
|
203
217
|
}
|
|
204
218
|
|
|
@@ -240,14 +254,16 @@ module.exports = function(RED) {
|
|
|
240
254
|
// ================================================================
|
|
241
255
|
|
|
242
256
|
// Check if this looks like a point response (has network.pointId or status.pointId)
|
|
243
|
-
const
|
|
257
|
+
const rawPointId = msg.network?.pointId ?? msg.status?.pointId ?? msg.pointId;
|
|
258
|
+
// Normalize to number - point-read stores pointId as int, but WebSocket responses may return strings
|
|
259
|
+
const responsePointId = rawPointId !== undefined && rawPointId !== null ? parseInt(rawPointId, 10) : undefined;
|
|
244
260
|
const responseValue = msg.value ?? msg.payload;
|
|
245
261
|
const statusCode = msg.status?.code;
|
|
246
262
|
const statusMessage = msg.status?.message || "";
|
|
247
263
|
const isError = statusCode === "error";
|
|
248
264
|
|
|
249
|
-
// Valid response if we have a
|
|
250
|
-
const isValidResponse = responsePointId !== undefined;
|
|
265
|
+
// Valid response if we have a valid numeric pointId
|
|
266
|
+
const isValidResponse = responsePointId !== undefined && !isNaN(responsePointId);
|
|
251
267
|
|
|
252
268
|
if (isValidResponse) {
|
|
253
269
|
// Find ALL matching pending requests by pointId
|
|
@@ -313,8 +329,10 @@ module.exports = function(RED) {
|
|
|
313
329
|
updateStatus();
|
|
314
330
|
}
|
|
315
331
|
} else {
|
|
316
|
-
// Response without matching request -
|
|
317
|
-
|
|
332
|
+
// Response without matching request - duplicate, stale, or already timed-out
|
|
333
|
+
// This is normal when remote has multiple WebSocket nodes or response arrives after timeout cleanup
|
|
334
|
+
// Don't change node status - just log at trace level
|
|
335
|
+
node.trace(`Ignoring duplicate/stale response for point #${responsePointId}`);
|
|
318
336
|
}
|
|
319
337
|
|
|
320
338
|
if (done) done();
|
|
@@ -358,6 +376,11 @@ module.exports = function(RED) {
|
|
|
358
376
|
// Node lifecycle
|
|
359
377
|
// ====================================================================
|
|
360
378
|
node.on("close", function(done) {
|
|
379
|
+
// Clear startup timer
|
|
380
|
+
if (node.startupTimer) {
|
|
381
|
+
clearTimeout(node.startupTimer);
|
|
382
|
+
node.startupTimer = null;
|
|
383
|
+
}
|
|
361
384
|
// Clear pending requests on close
|
|
362
385
|
node.pendingRequests = {};
|
|
363
386
|
// Remove event listeners
|
|
@@ -369,6 +392,15 @@ module.exports = function(RED) {
|
|
|
369
392
|
// ====================================================================
|
|
370
393
|
// Initialize
|
|
371
394
|
// ====================================================================
|
|
395
|
+
// One-shot timer to guarantee startup delay completes even if no messages arrive
|
|
396
|
+
node.startupTimer = setTimeout(() => {
|
|
397
|
+
if (!node.startupComplete) {
|
|
398
|
+
node.startupComplete = true;
|
|
399
|
+
updateStatus();
|
|
400
|
+
}
|
|
401
|
+
node.startupTimer = null;
|
|
402
|
+
}, node.startupDelay * 1000);
|
|
403
|
+
|
|
372
404
|
updateStatus();
|
|
373
405
|
}
|
|
374
406
|
|
package/nodes/or-block.js
CHANGED
|
@@ -48,7 +48,7 @@ module.exports = function(RED) {
|
|
|
48
48
|
node.inputs[slotVal.index - 1] = Boolean(msg.payload);
|
|
49
49
|
const result = node.inputs.some(v => v === true);
|
|
50
50
|
const isUnchanged = result === lastResult && node.inputs.every((v, i) => v === lastInputs[i]);
|
|
51
|
-
const statusText = `
|
|
51
|
+
const statusText = `[${node.inputs.join(", ")}] -> ${result}`;
|
|
52
52
|
|
|
53
53
|
// ================================================================
|
|
54
54
|
// Debounce: Suppress consecutive same outputs within 500ms
|
package/nodes/priority-block.js
CHANGED
|
@@ -174,7 +174,7 @@ module.exports = function(RED) {
|
|
|
174
174
|
send(currentOutput);
|
|
175
175
|
const inDisplay = typeof msg.payload === "number" ? msg.payload.toFixed(2) : typeof msg.payload === "object" ? JSON.stringify(msg.payload).slice(0, 20) : msg.payload;
|
|
176
176
|
const outDisplay = currentOutput.payload === null ? "null" : typeof currentOutput.payload === "number" ? currentOutput.payload.toFixed(2) : currentOutput.payload;
|
|
177
|
-
const statusText = `
|
|
177
|
+
const statusText = `out: ${outDisplay}, slot: ${currentOutput.diagnostics.activePriority || "none"}`;
|
|
178
178
|
utils.setStatusChanged(node, statusText);
|
|
179
179
|
|
|
180
180
|
if (done) done();
|
package/nodes/tstat-block.html
CHANGED
|
@@ -63,6 +63,10 @@
|
|
|
63
63
|
<input type="text" id="node-input-isHeating" style="width: auto; vertical-align: middle;">
|
|
64
64
|
<input type="hidden" id="node-input-isHeatingType">
|
|
65
65
|
</div>
|
|
66
|
+
<div class="form-row">
|
|
67
|
+
<label for="node-input-startupDelay" title="Seconds to suppress heating/cooling calls after deploy (0 = disabled)"><i class="fa fa-hourglass-start"></i> Startup Delay</label>
|
|
68
|
+
<input type="number" id="node-input-startupDelay" placeholder="30" min="0" step="1">
|
|
69
|
+
</div>
|
|
66
70
|
</script>
|
|
67
71
|
|
|
68
72
|
<script type="text/javascript">
|
|
@@ -94,7 +98,8 @@
|
|
|
94
98
|
ignoreAnticipatorCycles: { value: "1" },
|
|
95
99
|
ignoreAnticipatorCyclesType: { value: "num" },
|
|
96
100
|
isHeating: { value: false },
|
|
97
|
-
isHeatingType: { value: "bool" }
|
|
101
|
+
isHeatingType: { value: "bool" },
|
|
102
|
+
startupDelay: { value: 30 }
|
|
98
103
|
},
|
|
99
104
|
inputs: 1,
|
|
100
105
|
outputs: 3,
|
|
@@ -223,100 +228,50 @@
|
|
|
223
228
|
</script>
|
|
224
229
|
|
|
225
230
|
<script type="text/markdown" data-help-name="tstat-block">
|
|
226
|
-
Thermostat controller for heating/cooling with single, split, or specified setpoint operation, hysteresis, and anticipation
|
|
231
|
+
Thermostat controller for heating/cooling with single, split, or specified setpoint operation, hysteresis, and anticipation.
|
|
227
232
|
|
|
228
233
|
### Inputs
|
|
229
|
-
:
|
|
230
|
-
:
|
|
234
|
+
: payload (number) : Temperature reading.
|
|
235
|
+
: isHeating (boolean) : Optional. Overrides the configured heating mode (typically wired from a changeover node).
|
|
231
236
|
|
|
232
237
|
### Outputs
|
|
233
|
-
|
|
234
|
-
:
|
|
235
|
-
:
|
|
236
|
-
: below (boolean) : `true` if input is below heating on threshold.
|
|
237
|
-
: status (object) : Contains detailed runtime information including:
|
|
238
|
-
- `algorithm`: Current algorithm in use
|
|
239
|
-
- `input`: Current temperature input value
|
|
240
|
-
- `isHeating`: Current heating mode
|
|
241
|
-
- `above/below`: Current output states
|
|
242
|
-
- Algorithm-specific setpoints and values
|
|
243
|
-
- `modeChanged`: If mode recently changed
|
|
244
|
-
- `cyclesSinceModeChange`: Count since last mode change
|
|
245
|
-
- `effectiveAnticipator`: Current anticipator value after mode change adjustments
|
|
238
|
+
: isHeating (boolean) : Output 1. Current heating mode. Includes `msg.context = "isHeating"`.
|
|
239
|
+
: above (boolean) : Output 2. `true` when cooling call is active (temperature exceeded cooling threshold).
|
|
240
|
+
: below (boolean) : Output 3. `true` when heating call is active (temperature dropped below heating threshold).
|
|
246
241
|
|
|
247
|
-
|
|
248
|
-
All outputs include comprehensive status information in `msg.status`. Example:
|
|
249
|
-
```json
|
|
250
|
-
{
|
|
251
|
-
"status": {
|
|
252
|
-
"algorithm": "single",
|
|
253
|
-
"input": 68.5,
|
|
254
|
-
"isHeating": true,
|
|
255
|
-
"above": false,
|
|
256
|
-
"below": true,
|
|
257
|
-
"setpoint": 70,
|
|
258
|
-
"diff": 2,
|
|
259
|
-
"anticipator": 0.5,
|
|
260
|
-
"modeChanged": false,
|
|
261
|
-
"cyclesSinceModeChange": 3,
|
|
262
|
-
"effectiveAnticipator": 0.5
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
```
|
|
242
|
+
All outputs include a `msg.status` object with runtime diagnostics: `algorithm`, `input`, `isHeating`, `above`, `below`, `activeSetpoint`, `onThreshold`, `offThreshold`, `diff`, `anticipator`, `effectiveAnticipator`, `modeChanged`, `cyclesSinceModeChange`.
|
|
266
243
|
|
|
267
244
|
### Algorithms
|
|
268
|
-
- **Single Setpoint**:
|
|
269
|
-
- Uses `setpoint`, `diff`, and `anticipator`.
|
|
270
|
-
- Sets `above` if `input > setpoint + diff/2`, clears when `input < setpoint + anticipator`.
|
|
271
|
-
- Sets `below` if `input < setpoint - diff/2`, clears when `input > setpoint - anticipator`.
|
|
272
|
-
- For positive `anticipator`, stops early to prevent overshoot. For negative `anticipator` (testing only), delays turn-off to overshoot setpoint.
|
|
273
|
-
- Example: `setpoint=70`, `diff=2`, `anticipator=-0.5`, `above` if `input > 71`, clears at `< 69.5` (overshoots); `below` if `input < 69`, clears at `> 70.5` (overshoots).
|
|
274
|
-
|
|
275
|
-
- **Split Setpoint**:
|
|
276
|
-
- Uses `heatingSetpoint`, `coolingSetpoint`, `diff`, and `anticipator`.
|
|
277
|
-
- For `isHeating = true`:
|
|
278
|
-
- Sets `below` if `input < heatingSetpoint - diff/2`, clears when `input > heatingSetpoint - anticipator`.
|
|
279
|
-
- `above` is `false`.
|
|
280
|
-
- For `isHeating = false`:
|
|
281
|
-
- Sets `above` if `input > coolingSetpoint + diff/2`, clears when `input < coolingSetpoint + anticipator`.
|
|
282
|
-
- `below` is `false`.
|
|
283
|
-
- Ensures `heatingSetpoint < coolingSetpoint`.
|
|
284
|
-
- For negative `anticipator`, delays turn-off (e.g., heating off above `heatingSetpoint`).
|
|
285
|
-
- Example: `heatingSetpoint=68`, `coolingSetpoint=74`, `diff=2`, `anticipator=-0.5`, heating mode sets `below` if `input < 67`, clears at `> 68.5`; cooling mode sets `above` if `input > 75`, clears at `< 73.5`.
|
|
286
245
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
- Sets `above` if `input > coolingOn`, clears when `input < coolingOff + anticipator`.
|
|
291
|
-
- `below` is `false`.
|
|
292
|
-
- For `isHeating = true`:
|
|
293
|
-
- Sets `below` if `input < heatingOn`, clears when `input > heatingOff - anticipator`.
|
|
294
|
-
- `above` is `false`.
|
|
295
|
-
- Validates `coolingOn >= coolingOff >= heatingOff >= heatingOn`.
|
|
296
|
-
- For negative `anticipator`, delays turn-off (e.g., heating off above `heatingOff`).
|
|
297
|
-
- Example: `coolingOn=74`, `coolingOff=72`, `heatingOff=68`, `heatingOn=66`, `anticipator=-0.5`, cooling mode sets `above` if `input > 74`, clears at `< 71.5`; heating mode sets `below` if `input < 66`, clears at `> 68.5`.
|
|
246
|
+
**Single** — One setpoint with differential hysteresis.
|
|
247
|
+
- Heating: call ON when `temp < setpoint - diff/2`, OFF when `temp > setpoint - anticipator`
|
|
248
|
+
- Cooling: call ON when `temp > setpoint + diff/2`, OFF when `temp < setpoint + anticipator`
|
|
298
249
|
|
|
299
|
-
|
|
300
|
-
|
|
250
|
+
**Split** — Separate heating/cooling setpoints with differential.
|
|
251
|
+
- Heating: call ON when `temp < heatingSetpoint - diff/2`, OFF when `temp > heatingSetpoint - anticipator`
|
|
252
|
+
- Cooling: call ON when `temp > coolingSetpoint + diff/2`, OFF when `temp < coolingSetpoint + anticipator`
|
|
301
253
|
|
|
302
|
-
|
|
254
|
+
**Specified** — Explicit on/off trigger temperatures.
|
|
255
|
+
- Heating: call ON when `temp < heatingOn`, OFF when `temp > heatingOff - anticipator`
|
|
256
|
+
- Cooling: call ON when `temp > coolingOn`, OFF when `temp < coolingOff + anticipator`
|
|
303
257
|
|
|
304
|
-
|
|
305
|
-
|
|
258
|
+
### Anticipator
|
|
259
|
+
Positive values stop early to prevent overshoot. Negative values (≥ -2, testing only) delay turn-off to allow overshoot.
|
|
306
260
|
|
|
307
|
-
The `
|
|
261
|
+
The `ignoreAnticipatorCycles` setting disables the anticipator for N cycles after a mode change to reduce short-cycling.
|
|
308
262
|
|
|
309
|
-
|
|
263
|
+
### Startup Delay
|
|
264
|
+
Configurable delay (default 30s, 0 = disabled) suppresses `above`/`below` calls after deployment. Prevents false calls before upstream mode selection has initialized. During delay, `isHeating` passes through normally and internal state is tracked. Status shows `[startup]` during suppression.
|
|
310
265
|
|
|
311
|
-
|
|
312
|
-
|
|
266
|
+
### Configuration
|
|
267
|
+
All numeric inputs support `num`, `msg`, `flow`, or `global` types via typed inputs. The `isHeating` flag supports `bool`, `msg`, `flow`, or `global`. Algorithm supports dropdown, `msg`, `flow`, or `global`.
|
|
313
268
|
|
|
314
269
|
### Status
|
|
315
|
-
- Green (dot): Configuration
|
|
316
|
-
- Blue (dot):
|
|
317
|
-
- Blue (ring):
|
|
318
|
-
- Red (ring):
|
|
319
|
-
- Yellow (ring):
|
|
270
|
+
- Green (dot): Configuration update
|
|
271
|
+
- Blue (dot): State changed
|
|
272
|
+
- Blue (ring): State unchanged
|
|
273
|
+
- Red (ring): Error
|
|
274
|
+
- Yellow (ring): Warning / startup delay
|
|
320
275
|
|
|
321
276
|
### References
|
|
322
277
|
- [Node-RED Documentation](https://nodered.org/docs/)
|