node-red-contrib-boolean-logic-ultimate 1.2.16 → 1.2.17

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/CHANGELOG.md CHANGED
@@ -4,6 +4,15 @@
4
4
 
5
5
  # CHANGELOG
6
6
 
7
+ <p>
8
+ <b>Version 1.2.17</b> July 2026<br/>
9
+
10
+ - HysteresisUltimate: added permanent output control through <code>msg.permanentLockState</code>, supporting <code>lock</code>, <code>lockOn</code>, <code>lockOff</code> and <code>unlock</code>.<br/>
11
+ - HysteresisUltimate: control-topic configuration and diagnostics remain available while state output is locked.<br/>
12
+ - Updated the Hysteresis editor help, README documentation, automated tests and importable example flow.<br/>
13
+ - Reorganized the README with a clearer heading structure, installation instructions and a categorized node index.<br/>
14
+ </p>
15
+
7
16
  <p>
8
17
  <b>Version 1.2.16</b> July 2026<br/>
9
18
 
package/README.md CHANGED
@@ -2,13 +2,9 @@
2
2
  <img src="img/logo-supervibe.png" alt="Boolean Logic Ultimate - Max Supervibe" width="380">
3
3
  </p>
4
4
 
5
- ## Logic and Utilities nodes for your flows
5
+ # Boolean Logic Ultimate
6
6
 
7
- A set of Node-RED enhanced boolean logic and utility nodes, with persistent values after reboot. Compatible also with Homeassistant values.
8
-
9
- <br/>
10
- <br/>
11
- <br/>
7
+ A collection of enhanced logic and utility nodes for Node-RED. It supports persistent values after reboot and conversion of common Home Assistant values.
12
8
 
13
9
  [![NPM version][npm-version-image]][npm-url]
14
10
  [![NPM downloads per month][npm-downloads-month-image]][npm-url]
@@ -22,14 +18,35 @@ A set of Node-RED enhanced boolean logic and utility nodes, with persistent valu
22
18
  <img src="img/readmemain.png" alt="Boolean Logic Ultimate for Node-RED — Max Supervibe" width="70%">
23
19
  </p>
24
20
 
25
- ## CHANGELOG
21
+ ## Getting started
26
22
 
27
- - See <a href="https://github.com/Supergiovane/node-red-contrib-boolean-logic-ultimate/blob/master/CHANGELOG.md">here the changelog</a>
23
+ Install the package from the Node-RED palette manager or run this command in your Node-RED user directory:
28
24
 
29
- <br/>
30
- <br/>
25
+ ```shell
26
+ npm install node-red-contrib-boolean-logic-ultimate
27
+ ```
31
28
 
32
- # EXAMPLES
29
+ Importable flows are available from the Node-RED editor under **Menu (☰) → Import → Examples**, or directly in the [`examples/`](examples/) folder. See the [examples overview](examples/README.md) for the complete list.
30
+
31
+ ## Documentation
32
+
33
+ | Logic and routing | Timing and flow | Values and simulation |
34
+ | ----------------- | --------------- | --------------------- |
35
+ | [Boolean Logic](#boolean-logic) | [Interrupt Flows Ultimate](#interrupt-flows-ultimate) | [Translator node](#translator-node) |
36
+ | [Invert Ultimate](#invert-ultimate) | [Blinker Ultimate](#blinker-ultimate) | [Simple Output Ultimate](#simple-output-ultimate) |
37
+ | [Filter Ultimate](#filter-ultimate) | [Impulse Ultimate](#impulse-ultimate) | [Inject Ultimate](#inject-ultimate) |
38
+ | [Hysteresis Ultimate](#hysteresis-ultimate) | [Rate Limiter Ultimate](#rate-limiter-ultimate) | [Status Ultimate](#status-ultimate) |
39
+ | [Railway Switch Ultimate](#railway-switch-ultimate) | [Debouncer Ultimate](#debouncer-ultimate) | [Math Ultimate](#math-ultimate) |
40
+ | [Toggle Ultimate](#toggle-ultimate) | [Staircase Light Ultimate](#staircase-light-ultimate) | [Kalman Filter Ultimate](#kalman-filter-ultimate) |
41
+ | | | [Presence Simulator Ultimate](#presence-simulator-ultimate) |
42
+
43
+ Each node section contains its configuration, accepted input messages, outputs and runtime commands where applicable.
44
+
45
+ ## Changelog
46
+
47
+ - See the project [changelog](CHANGELOG.md) for release details.
48
+
49
+ ## Examples
33
50
 
34
51
  Importable Node-RED example flows are available in the [`examples/`](examples/) folder (one JSON per node). See [`examples/README.md`](examples/README.md) for a quick overview.
35
52
 
@@ -37,12 +54,9 @@ Importable Node-RED example flows are available in the [`examples/`](examples/)
37
54
 
38
55
  - Node-RED editor → Menu (☰) → **Import** → **Examples** and select an example from `node-red-contrib-boolean-logic-ultimate`
39
56
 
40
- <br/>
41
- <br/>
42
-
43
- # TRANSLATOR NODE
57
+ ## Translator node
44
58
 
45
- Other than true/false, all nodes accepts [Homeassistant](https://www.home-assistant.io) output strings.
59
+ In addition to true/false, the nodes accept common [Home Assistant](https://www.home-assistant.io) state strings.
46
60
 
47
61
  You can **even add your own input translation word list**, thanks to the translator-config node.
48
62
 
@@ -50,20 +64,17 @@ The translator node can translate an input payload, to a true/false boolean valu
50
64
 
51
65
  Each row in the text box, represents a translation command. <br/>
52
66
 
53
- There are some default translation's rows, to make the _boolean-logic-ultimate_ nodes compatible with Homeassistant as default. <br/>
67
+ Default translation rows make the _boolean-logic-ultimate_ nodes compatible with common Home Assistant states. <br/>
54
68
 
55
69
  You can add your own translation row.<br/>
56
70
 
57
71
  | | Description |
58
72
  | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
59
- | Translate | Add, delete or edit your own translation command. The row's translation command must be **input string:true(or false)**. For example: <code>open:true</code> <code>closed:false</code>. You can also use an expressions to be evaluated, like this <code>{{value>=50}}:true</code> and <code>{{value<50}}:false</code>. In this case, the tranlsator will evaluate (javascript eval) the expression and, if true, returns the choosen value. |
73
+ | Translate | Add, delete or edit a translation command. Each row must use the format **input string:true(or false)**, for example <code>open:true</code> or <code>closed:false</code>. Expressions such as <code>{{value>=50}}:true</code> are also supported; when the expression evaluates to true, the translator returns the selected value. |
60
74
 
61
75
  ![alt text](image.png)
62
76
 
63
- <br/>
64
- <br/>
65
-
66
- # BOOLEAN LOGIC
77
+ ## Boolean Logic
67
78
 
68
79
  <img src='https://raw.githubusercontent.com/Supergiovane/node-red-contrib-boolean-logic-ultimate/master/img/bl1.png' width='60%'>
69
80
 
@@ -103,7 +114,7 @@ You can also set the default values of the topic inputs.<br/>
103
114
 
104
115
  The node can convert arbitrary input values to true/false. It supports Homeassistant string to boolean conversion as well. For enabling auto conversion, please be sure to disable **Reject non boolean (true/false) input values** <br/>
105
116
 
106
- ### NODE CONFIGURATION
117
+ ### Configuration
107
118
 
108
119
  | Property | Description |
109
120
  | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
@@ -125,17 +136,11 @@ The node can convert arbitrary input values to true/false. It supports Homeassis
125
136
  | msg.reset = true | Resets all saved input values to undefined |
126
137
  | msg.inputcount | Changes the inputs count property. For example, <b>msg.inputcount = 3</b> Whenever you lower the inputcount from a higher number to a lower one, for example from 3 to 2, it's suggested to do a <b>msg.reset=true</b> to reset all stored input values. |
127
138
 
128
- <br/>
129
- <br/>
130
- <br/>
131
- <br/>
132
- <br/>
133
-
134
- # INTERRUPT FLOWS ULTIMATE
139
+ ## Interrupt Flows Ultimate
135
140
 
136
141
  The interrupt flows is able to stop the input messages to exiting the node.
137
142
 
138
- ### NODE CONFIGURATION
143
+ ### Configuration
139
144
 
140
145
  | Property | Description |
141
146
  | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
@@ -147,13 +152,13 @@ The interrupt flows is able to stop the input messages to exiting the node.
147
152
 
148
153
  **INPUT MSG WITH "TRIGGER" TOPIC**
149
154
 
150
- Pass <code>msg.payload = true</code> to allow messages to pass through</br>
155
+ Pass <code>msg.payload = true</code> to allow messages to pass through<br/>
151
156
 
152
- Pass <code>msg.payload = false</code> to prevent messages from passing through</br>
157
+ Pass <code>msg.payload = false</code> to prevent messages from passing through<br/>
153
158
 
154
- Pass <code>msg.play = true</code> from a message having the "trigger" topic, to replay the last stored message</br>
159
+ Pass <code>msg.play = true</code> from a message having the "trigger" topic, to replay the last stored message<br/>
155
160
 
156
- Pass <code>msg.reset = true</code> from a message having the "trigger" topic, to clear the last stored message</br>
161
+ Pass <code>msg.reset = true</code> from a message having the "trigger" topic, to clear the last stored message<br/>
157
162
 
158
163
  <code>
159
164
  // Assume you set the "trigger by topic" field to "trigger"
@@ -169,7 +174,7 @@ msg.topic = "trigger"
169
174
  msg.reset = true;
170
175
  </code>
171
176
 
172
- </br>
177
+ <br/>
173
178
 
174
179
  See the example below.<br/>
175
180
 
@@ -195,19 +200,13 @@ This allow to save the state of a node and then replay it back whenever you want
195
200
  </code>
196
201
  </details>
197
202
 
198
- <br/>
199
- <br/>
200
- <br/>
201
- <br/>
202
- <br/>
203
-
204
- # INVERT ULTIMATE
203
+ ## Invert Ultimate
205
204
 
206
205
  Outputs the inverted input. For example true -> false<br />
207
206
 
208
207
  The input message is preserved and passed to the output, changing only the topic and the payload. If the input message has it's own topic, it'll be preserved as well.<br/>
209
208
 
210
- ### NODE CONFIGURATION
209
+ ### Configuration
211
210
 
212
211
  | Property | Description |
213
212
  | -------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
@@ -215,13 +214,7 @@ The input message is preserved and passed to the output, changing only the topic
215
214
 
216
215
  <br/>
217
216
 
218
- <br/>
219
- <br/>
220
- <br/>
221
- <br/>
222
- <br/>
223
-
224
- # FILTER ULTIMATE
217
+ ## Filter Ultimate
225
218
 
226
219
  This node has 2 outputs.<br />
227
220
 
@@ -231,7 +224,7 @@ If the input payload is false, the node will send nothing on output 1 and <code>
231
224
 
232
225
  The input message is preserved and passed to the output, changing only the topic and the payload. If the input message has it's own topic, it'll be preserved as well.<br/>
233
226
 
234
- ### NODE CONFIGURATION
227
+ ### Configuration
235
228
 
236
229
  | Property | Description |
237
230
  | -------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
@@ -239,15 +232,9 @@ The input message is preserved and passed to the output, changing only the topic
239
232
 
240
233
  <br/>
241
234
 
242
- <br/>
243
- <br/>
244
- <br/>
245
- <br/>
246
- <br/>
247
-
248
- # BLINKER ULTIMATE
235
+ ## Blinker Ultimate
249
236
 
250
- The pourpose of this node is to blink a led or something.<br />
237
+ This node blinks an LED or another device.<br />
251
238
 
252
239
  output1 : outputs the value true/false<br/>
253
240
 
@@ -255,7 +242,7 @@ output2 : outputs the inverted value false/true<br/>
255
242
 
256
243
  <br/>
257
244
 
258
- ### NODE CONFIGURATION
245
+ ### Configuration
259
246
 
260
247
  | Property | Description |
261
248
  | -------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
@@ -263,11 +250,11 @@ output2 : outputs the inverted value false/true<br/>
263
250
 
264
251
  <br/>
265
252
 
266
- Pass <code>msg.payload = true</code> to start blinking</br>
253
+ Pass <code>msg.payload = true</code> to start blinking<br/>
267
254
 
268
- Pass <code>msg.payload = false</code> to stop blinking</br>
255
+ Pass <code>msg.payload = false</code> to stop blinking<br/>
269
256
 
270
- Pass <code>msg.interval = 2000</code> to change the blinking interval</br>
257
+ Pass <code>msg.interval = 2000</code> to change the blinking interval<br/>
271
258
 
272
259
  - output1 stop behavior : when the blinker receives the stop message, you can select the behavior of the output1<br/>
273
260
  - output2 stop behavior : when the blinker receives the stop message, you can select the behavior of the output2<br/>
@@ -280,19 +267,13 @@ Pass <code>msg.interval = 2000</code> to change the blinking interval</br>
280
267
  </code>
281
268
  </details>
282
269
 
283
- <br/>
284
- <br/>
285
- <br/>
286
- <br/>
287
- <br/>
270
+ ## Simple Output Ultimate
288
271
 
289
- # SIMPLE OUTPUT ULTIMATE
290
-
291
- The pourpose of this node is to send a message with payload TRUE on the first output and FALSE on second output, independently from the msg input.<br />
272
+ This node sends a message with payload TRUE on the first output and FALSE on the second output, independently of the input message.<br />
292
273
 
293
274
  This is useful if you need to simply send a true or false payload.
294
275
 
295
- ### NODE CONFIGURATION
276
+ ### Configuration
296
277
 
297
278
  | Property | Description |
298
279
  | -------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
@@ -308,15 +289,9 @@ This is useful if you need to simply send a true or false payload.
308
289
  </code>
309
290
  </details>
310
291
 
311
- <br/>
312
- <br/>
313
- <br/>
314
- <br/>
315
- <br/>
292
+ ## Inject Ultimate
316
293
 
317
- # INJECT ULTIMATE
318
-
319
- The pourpose of this node is to send a message with payload TRUE on the first output, FALSE on second output and a TOGGLE (true/false) on the third output, by pressing the pushbutton.<br />
294
+ This node sends TRUE on the first output, FALSE on the second output and a toggled true/false value on the third output when its button is pressed.<br />
320
295
 
321
296
  This is useful if you need to simply test your flow.
322
297
 
@@ -328,15 +303,9 @@ This is useful if you need to simply test your flow.
328
303
  </code>
329
304
  </details>
330
305
 
331
- <br/>
332
- <br/>
333
- <br/>
334
- <br/>
335
- <br/>
336
-
337
- # STATUS ULTIMATE
306
+ ## Status Ultimate
338
307
 
339
- The pourpose of this node is to show a status of the passingthrough message.<br />
308
+ This node displays the value of a passing message as its Node-RED status.<br />
340
309
 
341
310
  **Show msg.**
342
311
 
@@ -349,15 +318,9 @@ The pourpose of this node is to show a status of the passingthrough message.<br
349
318
  [{"id":"8c1648bf.58e6","type":"StatusUltimate","z":"5c2de561.6a0de4","name":"Status","property":"testobject.color","x":90,"y":180,"wires":[["b96cd259.3f8398"]]},{"id":"3beb9c6.90d1e64","type":"function","z":"5c2de561.6a0de4","name":"Dummy msg","func":"msg.payload = \"The payload is \" + msg.payload;\nmsg.myproperty = \"This is my custom property\";\nmsg.testobject = {len : 100, color : \"blue\"};\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":230,"y":100,"wires":[["8c1648bf.58e6"]]},{"id":"84080b79.df3f38","type":"debug","z":"5c2de561.6a0de4","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":710,"y":180,"wires":[]},{"id":"b534d2ec.635398","type":"InjectUltimate","z":"5c2de561.6a0de4","name":"Inject","topic":"1","curVal":true,"x":90,"y":100,"wires":[[],[],["3beb9c6.90d1e64"]]},{"id":"9745c77a.0361b","type":"comment","z":"5c2de561.6a0de4","name":"View the status of a message passing through the StatusUltimate node","info":"","x":270,"y":40,"wires":[]},{"id":"b96cd259.3f8398","type":"StatusUltimate","z":"5c2de561.6a0de4","name":"Status","property":"payload","x":230,"y":180,"wires":[["ac2b784b.b44a48"]]},{"id":"ac2b784b.b44a48","type":"StatusUltimate","z":"5c2de561.6a0de4","name":"Status","property":"myproperty","x":450,"y":180,"wires":[["84080b79.df3f38"]]}]
350
319
  </code>
351
320
  </details>
352
- <br/>
353
- <br/>
354
- <br/>
355
- <br/>
356
- <br/>
357
-
358
- # IMPULSE ULTIMATE
321
+ ## Impulse Ultimate
359
322
 
360
- The pourpose of this node is to send a sequence of pulsed commands to for example, open a garage door or to command an appliance requiring a set of timed commands.<br />
323
+ This node sends a timed sequence of pulsed commands, for example to open a garage door or control an appliance.<br />
361
324
 
362
325
  <img src='https://raw.githubusercontent.com/Supergiovane/node-red-contrib-boolean-logic-ultimate/master/img/Impulse.png' width='60%'>
363
326
  <details><summary>CLICK HERE, copy and paste it into your flow</summary>
@@ -366,7 +329,7 @@ The pourpose of this node is to send a sequence of pulsed commands to for exampl
366
329
  </code>
367
330
  </details>
368
331
 
369
- ### NODE CONFIGURATION
332
+ ### Configuration
370
333
 
371
334
  | Property | Description |
372
335
  | -------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
@@ -388,23 +351,17 @@ Restart the sequence from the beginning. Use **_restart_** alone, without **:**
388
351
  comment. For example: **_// This opens the garage_**. The comment are ignored, so you can write what you want.<br />
389
352
  <br />
390
353
 
391
- Pass <code>msg.payload = true</code> to the node to start the sequence</br>
354
+ Pass <code>msg.payload = true</code> to the node to start the sequence<br/>
392
355
 
393
- Pass <code>msg.payload = false</code> to the node to stop the running sequence</br>
356
+ Pass <code>msg.payload = false</code> to the node to stop the running sequence<br/>
394
357
 
395
358
  <br />
396
359
 
397
360
  - Output: the node outputs a message you specified in the command textbox<br/>
398
361
 
399
- <br/>
400
- <br/>
401
- <br/>
402
- <br/>
403
- <br/>
362
+ ## Math Ultimate
404
363
 
405
- # MATH ULTIMATE
406
-
407
- The pourpose of this node is to do maths on the incoming values. Each incoming message MUST HAVE OWN TOPIC.<br />
364
+ This node performs calculations on incoming values. Each incoming message must have its own topic.<br />
408
365
 
409
366
  <img src='https://raw.githubusercontent.com/Supergiovane/node-red-contrib-boolean-logic-ultimate/master/img/sum.png' width='60%'>
410
367
  <details><summary>CLICK HERE, copy and paste it into your flow</summary>
@@ -413,7 +370,7 @@ The pourpose of this node is to do maths on the incoming values. Each incoming m
413
370
  </code>
414
371
  </details>
415
372
 
416
- ### NODE CONFIGURATION
373
+ ### Configuration
417
374
 
418
375
  | Property | Description |
419
376
  | -------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
@@ -439,18 +396,11 @@ resets the values to zero.
439
396
  }
440
397
  </pre>
441
398
 
442
- br/>
399
+ ## Toggle Ultimate
443
400
 
444
- <br/>
445
- <br/>
446
- <br/>
447
- <br/>
448
-
449
- # TOGGLE ULTIMATE
401
+ This node toggles between true and false whenever an inbound message arrives.<br />
450
402
 
451
- The pourpose of this node is to toggle between true/false, everytime an inboud message arrives.<br />
452
-
453
- ### NODE CONFIGURATION
403
+ ### Configuration
454
404
 
455
405
  | Property | Description |
456
406
  | -------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
@@ -460,21 +410,15 @@ The pourpose of this node is to toggle between true/false, everytime an inboud m
460
410
 
461
411
  **INPUT**<br />
462
412
 
463
- Any message that arrives on input, will be passwd through to the output with the payload toggled between true and false.
464
-
465
- <br/>
466
- <br/>
467
- <br/>
468
- <br/>
469
- <br/>
413
+ Every input message is passed to the output with its payload toggled between true and false.
470
414
 
471
- # RAILWAY SWITCH ULTIMATE
415
+ ## Railway Switch Ultimate
472
416
 
473
- The railway switcher, redirect the incoming messages to one ot the avaiable output pins, just like a railway jinction switch.
417
+ The railway switch redirects incoming messages to one of the available outputs, like a railway junction.
474
418
 
475
419
  <img src='https://raw.githubusercontent.com/Supergiovane/node-red-contrib-boolean-logic-ultimate/master/img/railroadSwitchScambio.png' width='80%'>
476
420
 
477
- ### NODE CONFIGURATION
421
+ ### Configuration
478
422
 
479
423
  | Property | Description |
480
424
  | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
@@ -528,7 +472,7 @@ this JSON input message redirects all input messages to the third PIN, and so on
528
472
  }
529
473
  ```
530
474
 
531
- </br>
475
+ <br/>
532
476
 
533
477
  See the example below.<br/>
534
478
 
@@ -625,14 +569,9 @@ Copy and paste it into your flow
625
569
  ];
626
570
  ```
627
571
 
628
- <br/>
629
- <br/>
630
- <br/>
631
- <br/>
632
-
633
- # KALMAN FILTER ULTIMATE
572
+ ## Kalman Filter Ultimate
634
573
 
635
- ## Outputs the Kalman filtered input.
574
+ Outputs the Kalman-filtered input.
636
575
 
637
576
  Please refer to [this](https://github.com/wouterbulten/kalmanjs) link, on how it works.
638
577
 
@@ -653,13 +592,11 @@ Please refer to [this](https://github.com/wouterbulten/kalmanjs) link, on how it
653
592
 
654
593
  : payload (number) : the payload containing the number. If you've changed the incoming evaluation property in the **_Input_** field, the number to be evaluated must be put in such message's property, instead of the _payload_ property.
655
594
 
656
- <br/>
657
-
658
- # RATE LIMITER ULTIMATE
595
+ ## Rate Limiter Ultimate
659
596
 
660
597
  Gateway per sensori e dispositivi troppo “chiacchieroni”: limita burst e rimbalzi con modalità **Debounce**, **Throttle** e **Window**. Lo stato del nodo riporta sempre modalità corrente, contatori di messaggi inoltrati e bloccati.
661
598
 
662
- ### NODE CONFIGURATION
599
+ ### Configuration
663
600
 
664
601
  | Property | Description |
665
602
  | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
@@ -678,23 +615,21 @@ Gateway per sensori e dispositivi troppo “chiacchieroni”: limita burst e rim
678
615
 
679
616
  <br/>
680
617
 
681
- ### OUTPUTS
618
+ ### Outputs
682
619
 
683
620
  - **Output 1**: messaggi inoltrati, invariati.
684
621
  - **Output 2**: diagnostica (`{mode, reason, passed, dropped, msg, propertyValue}`) e statistiche periodiche su `controlTopic/stats`.
685
622
 
686
623
  <br/>
687
624
 
688
- ### CONTROL MESSAGES (`msg.topic === controlTopic`)
625
+ ### Control messages (`msg.topic === controlTopic`)
689
626
 
690
627
  - `msg.reset = true` &rarr; azzera contatori, accoda azzerata e timer cancellati.
691
628
  - `msg.flush = true` &rarr; forza l’emissione immediata del messaggio in attesa.
692
629
  - `msg.mode = 'debounce'|'throttle'|'window'` &rarr; cambia modalità runtime e resetta lo stato.
693
630
  - `msg.interval`, `msg.wait`, `msg.windowSize`, `msg.maxInWindow` &rarr; aggiorna i parametri corrispondenti.
694
631
 
695
- <br/>
696
-
697
- # DEBOUNCER ULTIMATE
632
+ ## Debouncer Ultimate
698
633
 
699
634
  Nodo dedicato al solo debounce: utile quando vuoi filtrare rimbalzi o burst rapidi senza portarti dietro le altre modalità del `RateLimiterUltimate`. Può inoltrare il **primo** messaggio, l’**ultimo** oppure **entrambi**, dopo un intervallo di quiete configurabile.
700
635
 
@@ -704,7 +639,7 @@ Nell'immagine si vede il comportamento tipico: a sinistra arrivano molti messagg
704
639
 
705
640
  Example flow: [`examples/DebouncerUltimate.json`](examples/DebouncerUltimate.json)
706
641
 
707
- ### NODE CONFIGURATION
642
+ ### Configuration
708
643
 
709
644
  | Property | Description |
710
645
  | ------------- | ------------------------------------------------------------------------------------------ |
@@ -712,24 +647,22 @@ Example flow: [`examples/DebouncerUltimate.json`](examples/DebouncerUltimate.jso
712
647
  | Emit | Scegli se inviare subito il primo messaggio, solo l’ultimo dopo la pausa, oppure entrambi. |
713
648
  | Control topic | Topic dei messaggi di controllo (default `debouncer`). |
714
649
 
715
- ### OUTPUT
650
+ ### Outputs
716
651
 
717
652
  - **Output 1**: messaggi inoltrati dopo il debounce.
718
653
 
719
- ### CONTROL MESSAGES (`msg.topic === controlTopic`)
654
+ ### Control messages (`msg.topic === controlTopic`)
720
655
 
721
656
  - `msg.reset = true` &rarr; cancella timer e messaggio pendente.
722
657
  - `msg.flush = true` &rarr; inoltra subito l’ultimo messaggio in attesa.
723
658
  - `msg.wait` &rarr; aggiorna il ritardo di debounce a runtime.
724
659
  - `msg.emitOn = 'leading'|'trailing'|'both'` &rarr; cambia la modalità di emissione.
725
660
 
726
- <br/>
727
-
728
- # PRESENCE SIMULATOR ULTIMATE
661
+ ## Presence Simulator Ultimate
729
662
 
730
663
  The purpose of this node is to replay a programmable sequence of messages in order to simulate occupancy when you are away.
731
664
 
732
- ### NODE CONFIGURATION
665
+ ### Configuration
733
666
 
734
667
  | Property | Description |
735
668
  | ------------- | ---------------------------------------------------------------------------------------------- |
@@ -742,7 +675,7 @@ The purpose of this node is to replay a programmable sequence of messages in ord
742
675
  | Translator | Optional translator-config to convert incoming values. |
743
676
  | Sequence | One JSON object per line, each containing at least `delay` (ms) plus the properties to output. |
744
677
 
745
- ### CONTROL MESSAGES (`msg.topic === controlTopic`)
678
+ ### Control messages (`msg.topic === controlTopic`)
746
679
 
747
680
  - `msg.command = 'start'` / `msg.start = true` &rarr; begin playback.
748
681
  - `msg.command = 'stop'` / `msg.stop = true` &rarr; halt playback.
@@ -752,13 +685,11 @@ The purpose of this node is to replay a programmable sequence of messages in ord
752
685
 
753
686
  Each event in the sequence outputs a message configured in the JSON line. When random delays are enabled, the effective delay is varied within the configured jitter.
754
687
 
755
- <br/>
756
-
757
- # STAIRCASE LIGHT ULTIMATE
688
+ ## Staircase Light Ultimate
758
689
 
759
690
  The purpose of this node is to control staircase lighting with a timer, pre-off warning and optional extension on every trigger.
760
691
 
761
- ### NODE CONFIGURATION
692
+ ### Configuration
762
693
 
763
694
  | Property | Description |
764
695
  | ------------------ | ------------------------------------------------------------------ |
@@ -773,7 +704,7 @@ The purpose of this node is to control staircase lighting with a timer, pre-off
773
704
  | On/Off payload | Values emitted on output 1 to turn the light on/off. |
774
705
  | Warning payload | Value emitted on output 2 when the warning fires. |
775
706
 
776
- ### CONTROL MESSAGES (`msg.topic === controlTopic`)
707
+ ### Control messages (`msg.topic === controlTopic`)
777
708
 
778
709
  - `msg.command = 'on'` / `msg.start = true` &rarr; start the timer and turn on the light.
779
710
  - `msg.command = 'off'` / `msg.stop = true` &rarr; switch off immediately.
@@ -782,35 +713,57 @@ The purpose of this node is to control staircase lighting with a timer, pre-off
782
713
 
783
714
  Output 1 delivers the ON/OFF command. Output 2 delivers the warning and includes `msg.remaining` with the seconds left.
784
715
 
785
- <br/>
786
-
787
- # HYSTERESIS ULTIMATE
716
+ ## Hysteresis Ultimate
788
717
 
789
718
  Adds hysteresis to numeric sensor values to avoid rapid ON/OFF bouncing around thresholds.
790
719
 
791
720
  Example flow: [`examples/HysteresisUltimate.json`](examples/HysteresisUltimate.json)
792
721
 
793
- ### NODE CONFIGURATION
722
+ ### Configuration
723
+
724
+ | Property | Description |
725
+ | ------------------- | ----------- |
726
+ | Name | Optional label shown in the flow editor. |
727
+ | Control topic | Topic used for runtime commands (default `hysteresis`). It is not required for `permanentLockState`. |
728
+ | Mode | `high`: ON at or above the ON threshold and OFF at or below the OFF threshold. `low`: ON at or below the ON threshold and OFF at or above the OFF threshold. Values between the thresholds retain the current state. |
729
+ | ON threshold | Numeric limit that changes the internal state to ON according to the selected mode. |
730
+ | OFF threshold | Numeric limit that changes the internal state to OFF according to the selected mode. |
731
+ | Initial state ON | Initial internal state after deployment or restart. It does not emit at startup and is restored by `msg.reset = true`. |
732
+ | Emit only on change | When enabled, output 1 emits only on transitions. When disabled, every valid numeric input emits the current state. |
733
+ | Input | Message property evaluated as a numeric value (default `msg.payload`). Numbers, booleans and numeric strings are accepted. |
734
+ | Translator | Optional shared translator configuration associated with the input. Hysteresis evaluation uses the numeric value of the selected input property. |
735
+ | On payload | Typed value assigned to `msg.payload` on output 1 while the state is ON. |
736
+ | Off payload | Typed value assigned to `msg.payload` on output 1 while the state is OFF. |
794
737
 
795
- | Property | Description |
796
- | ------------------- | ------------------------------------------------------------------------------ |
797
- | Control topic | Topic that receives runtime commands such as threshold updates and reset. |
798
- | Mode | `high` = ON above threshold, OFF below. `low` = ON below threshold, OFF above. |
799
- | ON/OFF threshold | Hysteresis limits. |
800
- | Initial state | Startup output state. |
801
- | Emit only on change | If enabled, output 1 emits only on state transitions. |
802
- | Input | Message property evaluated as numeric value (default `payload`). |
803
- | Translator | Optional translator-config. |
804
- | On/Off payload | Typed payloads sent on output 1. |
738
+ ### Input messages
805
739
 
806
- ### CONTROL MESSAGES (`msg.topic === controlTopic`)
740
+ Normal messages are evaluated using the configured **Input** property. Missing or non-numeric values generate an `invalid_input` diagnostic on output 2.
741
+
742
+ ### Control messages (`msg.topic === controlTopic`)
807
743
 
808
744
  - `msg.onThreshold`, `msg.offThreshold` &rarr; update thresholds at runtime.
809
- - `msg.state = true|false` &rarr; force state.
810
- - `msg.reset = true` &rarr; restore initial state.
745
+ - `msg.state = true|false` &rarr; force the state and emit it on output 1. While locked, it updates the internal state without emitting on output 1.
746
+ - `msg.reset = true` &rarr; restore the initial state and emit a `reset` diagnostic.
811
747
  - `msg.status = true` &rarr; emit current state/thresholds on output 2.
812
748
 
813
- Output 1 emits the command payload (`onPayload`/`offPayload`). Output 2 emits diagnostic events.
749
+ ### Permanent output lock
750
+
751
+ Regardless of `msg.topic`, `msg.permanentLockState` accepts:
752
+
753
+ - `lock` &rarr; suppress state output until another permanent-lock command arrives.
754
+ - `lockOn` &rarr; force ON, emit the configured ON payload once, then suppress state output.
755
+ - `lockOff` &rarr; force OFF, emit the configured OFF payload once, then suppress state output.
756
+ - `unlock` &rarr; resume normal processing (the unlock message itself is consumed).
757
+
758
+ While locked, normal input messages are ignored and output 1 remains suppressed. Control-topic commands remain active: thresholds can be updated, `state` and `reset` can change the internal state, and `status`/`reset` diagnostics can still be emitted on output 2.
759
+
760
+ ### Outputs
761
+
762
+ 1. **State output** emits a clone of the input message with:
763
+ - the configured `onPayload` or `offPayload` in `msg.payload`;
764
+ - `msg.event` set to `state_changed` or `state_confirmed`;
765
+ - `msg.hysteresis` containing `state`, `changed`, `mode`, input `value`, `onThreshold` and `offThreshold`.
766
+ 2. **Diagnostics** emits state-change, invalid-input, reset and status events. It sets `msg.topic` to `<controlTopic>/event` and places the event details in `msg.payload`.
814
767
 
815
768
  <br/>
816
769
 
@@ -121,17 +121,40 @@
121
121
 
122
122
  |Property|Description|
123
123
  |--|--|
124
- | Mode | `high`: ON above threshold, OFF below. `low`: ON below threshold, OFF above. |
125
- | ON/OFF threshold | The two hysteresis limits. |
126
- | Emit only on change | Emits output only when state changes. |
127
- | Input | Message property to evaluate (default `payload`). |
128
- | Translator | Optional translator-config. |
129
- | On/Off payload | Values sent on output 1. |
124
+ | Name | Optional label shown in the flow editor. |
125
+ | Control topic | Topic used for runtime commands. The default is `hysteresis`. It is not required for `permanentLockState`. |
126
+ | Mode | `high`: switch ON when the value is greater than or equal to the ON threshold and OFF when it is less than or equal to the OFF threshold. `low`: switch ON when the value is less than or equal to the ON threshold and OFF when it is greater than or equal to the OFF threshold. Values between the thresholds keep the current state. |
127
+ | ON threshold | Numeric limit that changes the internal state to ON, according to the selected mode. |
128
+ | OFF threshold | Numeric limit that changes the internal state to OFF, according to the selected mode. |
129
+ | Initial state ON | Initial internal state after deployment or restart. It does not emit a message by itself. It is also restored by `msg.reset = true`. |
130
+ | Emit only on change | When enabled, output 1 emits only when the state changes. When disabled, every valid numeric input emits the current ON/OFF state. |
131
+ | Input | Numeric message property to evaluate. The default is `msg.payload`; numbers, booleans and numeric strings are accepted. |
132
+ | Translator | Optional shared translator configuration associated with the input. Hysteresis evaluation uses the numeric value of the selected input property. |
133
+ | On payload | Typed value written to `msg.payload` on output 1 when the state is ON. |
134
+ | Off payload | Typed value written to `msg.payload` on output 1 when the state is OFF. |
130
135
 
131
- Control topic messages:
136
+ ### Inputs
137
+
138
+ Normal input messages are evaluated using the configured **Input** property. An absent or non-numeric value produces an `invalid_input` diagnostic on output 2.
139
+
140
+ The following commands are consumed only when `msg.topic` equals **Control topic**:
132
141
 
133
142
  - `msg.onThreshold`, `msg.offThreshold` to update thresholds at runtime.
134
- - `msg.state = true|false` to force state.
135
- - `msg.reset = true` to restore initial state.
143
+ - `msg.state = true|false` to force the state and emit it on output 1. While locked, it updates the internal state without emitting on output 1.
144
+ - `msg.reset = true` to restore the configured initial state and emit a `reset` diagnostic.
136
145
  - `msg.status = true` to emit current status on output 2.
137
- </script>
146
+
147
+ `msg.permanentLockState` is handled regardless of `msg.topic`:
148
+
149
+ - `"lock"` suppresses state output until another valid permanent-lock command arrives.
150
+ - `"lockOn"` forces ON and emits the configured **On payload** once, then suppresses state output.
151
+ - `"lockOff"` forces OFF and emits the configured **Off payload** once, then suppresses state output.
152
+ - `"unlock"` resumes normal processing. The unlock message itself is consumed.
153
+
154
+ While locked, normal input messages are ignored and output 1 remains suppressed. Control-topic commands remain active: thresholds can be updated, `state` and `reset` can change the internal state, and `status`/`reset` diagnostics can still be emitted on output 2.
155
+
156
+ ### Outputs
157
+
158
+ 1. **State output**: a clone of the input message with the configured ON/OFF `msg.payload`, `msg.event` (`state_changed` or `state_confirmed`) and `msg.hysteresis`, containing `state`, `changed`, `mode`, input `value`, `onThreshold` and `offThreshold`.
159
+ 2. **Diagnostics**: events for state changes, invalid inputs, reset and status. The topic is `<controlTopic>/event`, and `msg.payload` contains the event details.
160
+ </script>
@@ -18,6 +18,7 @@ module.exports = function (RED) {
18
18
  let onThreshold = Number(config.onThreshold);
19
19
  let offThreshold = Number(config.offThreshold);
20
20
  let state = Boolean(config.initialState);
21
+ let permanentLockState = 'unlock';
21
22
 
22
23
  if (!Number.isFinite(onThreshold)) onThreshold = 70;
23
24
  if (!Number.isFinite(offThreshold)) offThreshold = 65;
@@ -47,10 +48,11 @@ module.exports = function (RED) {
47
48
  function updateStatus(lastValue) {
48
49
  const direction = mode === 'low' ? 'LOW' : 'HIGH';
49
50
  const valueText = lastValue === undefined ? '-' : Number(lastValue).toFixed(2);
51
+ const lockText = permanentLockState === 'unlock' ? '' : ` ${permanentLockState}`;
50
52
  setNodeStatus({
51
53
  fill: state ? 'green' : 'grey',
52
54
  shape: state ? 'dot' : 'ring',
53
- text: `${direction} ${valueText} on:${onThreshold} off:${offThreshold}`,
55
+ text: `${direction} ${valueText} on:${onThreshold} off:${offThreshold}${lockText}`,
54
56
  });
55
57
  }
56
58
 
@@ -101,7 +103,7 @@ module.exports = function (RED) {
101
103
  updateStatus(inputValue);
102
104
  }
103
105
 
104
- function handleControl(msg) {
106
+ function handleControl(msg, suppressStateOutput) {
105
107
  let consumed = false;
106
108
 
107
109
  if (msg.reset === true) {
@@ -134,7 +136,9 @@ module.exports = function (RED) {
134
136
 
135
137
  if (Object.prototype.hasOwnProperty.call(msg, 'state')) {
136
138
  state = Boolean(msg.state);
137
- emitState(msg, true, undefined);
139
+ if (!suppressStateOutput) {
140
+ emitState(msg, true, undefined);
141
+ }
138
142
  consumed = true;
139
143
  }
140
144
 
@@ -156,8 +160,46 @@ module.exports = function (RED) {
156
160
  return consumed;
157
161
  }
158
162
 
163
+ function handlePermanentLock(msg) {
164
+ if (!Object.prototype.hasOwnProperty.call(msg, 'permanentLockState')) {
165
+ return false;
166
+ }
167
+
168
+ const command = msg.permanentLockState;
169
+ if (!['unlock', 'lock', 'lockOff', 'lockOn'].includes(command)) {
170
+ return false;
171
+ }
172
+
173
+ if (command === 'unlock') {
174
+ permanentLockState = 'unlock';
175
+ updateStatus();
176
+ return true;
177
+ }
178
+
179
+ permanentLockState = command;
180
+ if (command === 'lockOn' || command === 'lockOff') {
181
+ state = command === 'lockOn';
182
+ emitState(msg, true, undefined);
183
+ } else {
184
+ updateStatus();
185
+ }
186
+ return true;
187
+ }
188
+
159
189
  node.on('input', (msg) => {
160
- if (msg.topic === controlTopic && handleControl(msg)) {
190
+ if (handlePermanentLock(msg)) {
191
+ return;
192
+ }
193
+
194
+ const isLocked = permanentLockState !== 'unlock';
195
+
196
+ if (msg.topic === controlTopic) {
197
+ if (handleControl(msg, isLocked) || isLocked) {
198
+ return;
199
+ }
200
+ }
201
+
202
+ if (isLocked) {
161
203
  return;
162
204
  }
163
205
 
@@ -76,6 +76,80 @@
76
76
  "y": 240,
77
77
  "wires": [["hys-node"]]
78
78
  },
79
+ {
80
+ "id": "hys-lock-comment",
81
+ "type": "comment",
82
+ "z": "hys-flow",
83
+ "name": "Permanent output lock commands (independent of msg.topic)",
84
+ "info": "lock: suppress state output\nlockOn: emit ON once, then suppress state output\nlockOff: emit OFF once, then suppress state output\nunlock: resume normal processing\n\nControl-topic commands and diagnostics remain active while locked.",
85
+ "x": 310,
86
+ "y": 320,
87
+ "wires": []
88
+ },
89
+ {
90
+ "id": "hys-lock",
91
+ "type": "inject",
92
+ "z": "hys-flow",
93
+ "name": "Lock without output",
94
+ "props": [
95
+ { "p": "permanentLockState", "v": "lock", "vt": "str" }
96
+ ],
97
+ "repeat": "",
98
+ "crontab": "",
99
+ "once": false,
100
+ "onceDelay": 0.1,
101
+ "x": 230,
102
+ "y": 360,
103
+ "wires": [["hys-node"]]
104
+ },
105
+ {
106
+ "id": "hys-lock-on",
107
+ "type": "inject",
108
+ "z": "hys-flow",
109
+ "name": "Force ON and lock",
110
+ "props": [
111
+ { "p": "permanentLockState", "v": "lockOn", "vt": "str" }
112
+ ],
113
+ "repeat": "",
114
+ "crontab": "",
115
+ "once": false,
116
+ "onceDelay": 0.1,
117
+ "x": 230,
118
+ "y": 400,
119
+ "wires": [["hys-node"]]
120
+ },
121
+ {
122
+ "id": "hys-lock-off",
123
+ "type": "inject",
124
+ "z": "hys-flow",
125
+ "name": "Force OFF and lock",
126
+ "props": [
127
+ { "p": "permanentLockState", "v": "lockOff", "vt": "str" }
128
+ ],
129
+ "repeat": "",
130
+ "crontab": "",
131
+ "once": false,
132
+ "onceDelay": 0.1,
133
+ "x": 230,
134
+ "y": 440,
135
+ "wires": [["hys-node"]]
136
+ },
137
+ {
138
+ "id": "hys-unlock",
139
+ "type": "inject",
140
+ "z": "hys-flow",
141
+ "name": "Unlock",
142
+ "props": [
143
+ { "p": "permanentLockState", "v": "unlock", "vt": "str" }
144
+ ],
145
+ "repeat": "",
146
+ "crontab": "",
147
+ "once": false,
148
+ "onceDelay": 0.1,
149
+ "x": 200,
150
+ "y": 480,
151
+ "wires": [["hys-node"]]
152
+ },
79
153
  {
80
154
  "id": "hys-out",
81
155
  "type": "debug",
package/package.json CHANGED
@@ -1,18 +1,22 @@
1
1
  {
2
2
  "name": "node-red-contrib-boolean-logic-ultimate",
3
- "version": "1.2.16",
3
+ "version": "1.2.17",
4
4
  "description": "A set of Node-RED enhanced boolean logic and utility nodes, flow interruption, blinker, debouncer, invert, filter, toggle etc.., with persistent values after reboot. Compatible also with Homeassistant values.",
5
- "author": "Supergiovane (https://github.com/Supergiovane)",
5
+ "author": {
6
+ "name": "Massimo Saccani",
7
+ "email": "maxsupergiovane@icloud.com",
8
+ "url": "https://youtube.com/@maxsupervibe"
9
+ },
6
10
  "dependencies": {
7
- "fs": "0.0.1-security",
8
- "path": ">=0.12.7",
9
- "kalmanjs": "1.1.0"
11
+ "fs": "^0.0.1-security",
12
+ "kalmanjs": "^1.1.0",
13
+ "path": "^0.12.7"
10
14
  },
11
15
  "devDependencies": {
12
- "chai": "^4.3.10",
13
- "mocha": "^10.4.0",
14
- "node-red": "^3.1.0",
15
- "node-red-node-test-helper": "^0.3.5"
16
+ "chai": "^6.2.2",
17
+ "mocha": "^11.7.6",
18
+ "node-red": "^5.0.1",
19
+ "node-red-node-test-helper": "^0.3.6"
16
20
  },
17
21
  "keywords": [
18
22
  "node-red",
@@ -84,4 +84,61 @@ describe('HysteresisUltimate node', function () {
84
84
  hys.receive({ topic: 'sensor', payload: 58 });
85
85
  }).catch(done);
86
86
  });
87
+
88
+ it('supports permanent lock, lockOn, lockOff and unlock commands', function (done) {
89
+ const flowId = 'hys-lock';
90
+ const flow = [
91
+ { id: flowId, type: 'tab', label: 'hys-lock' },
92
+ {
93
+ id: 'hys',
94
+ type: 'HysteresisUltimate',
95
+ z: flowId,
96
+ mode: 'high',
97
+ onThreshold: 70,
98
+ offThreshold: 60,
99
+ emitOnlyOnChange: true,
100
+ onPayload: 'on',
101
+ onPayloadType: 'str',
102
+ offPayload: 'off',
103
+ offPayloadType: 'str',
104
+ wires: [['out'], ['diag']],
105
+ },
106
+ { id: 'out', type: 'helper', z: flowId },
107
+ { id: 'diag', type: 'helper', z: flowId },
108
+ ];
109
+
110
+ loadHysteresis(flow).then(() => {
111
+ const hys = helper.getNode('hys');
112
+ const out = helper.getNode('out');
113
+ const diag = helper.getNode('diag');
114
+ const outputPayloads = [];
115
+ const diagnostics = [];
116
+
117
+ out.on('input', (msg) => outputPayloads.push(msg.payload));
118
+ diag.on('input', (msg) => diagnostics.push(msg.payload));
119
+
120
+ hys.receive({ permanentLockState: 'lockOn' });
121
+ hys.receive({ payload: 55 });
122
+ hys.receive({ topic: 'hysteresis', onThreshold: 74, status: true });
123
+ hys.receive({ topic: 'hysteresis', state: false });
124
+ hys.receive({ permanentLockState: 'lockOff' });
125
+ hys.receive({ payload: 75 });
126
+ hys.receive({ permanentLockState: 'lock' });
127
+ hys.receive({ payload: 75 });
128
+ hys.receive({ permanentLockState: 'unlock' });
129
+ hys.receive({ payload: 75 });
130
+
131
+ setTimeout(() => {
132
+ try {
133
+ expect(outputPayloads).to.deep.equal(['on', 'off', 'on']);
134
+ expect(diagnostics).to.have.length(2);
135
+ expect(diagnostics[0]).to.include({ event: 'status', state: true, onThreshold: 74 });
136
+ expect(diagnostics[1]).to.include({ event: 'state_changed', state: true, onThreshold: 74 });
137
+ done();
138
+ } catch (error) {
139
+ done(error);
140
+ }
141
+ }, 50);
142
+ }).catch(done);
143
+ });
87
144
  });