node-red-contrib-boolean-logic-ultimate 1.2.8 → 1.2.11

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,26 @@
4
4
 
5
5
  # CHANGELOG
6
6
 
7
+ <p>
8
+ <b>Version 1.2.11</b> April 2026<br/>
9
+
10
+ - DebouncerUltimate: simplified the node to a single output, with clearer editor labels for non-technical users.<br/>
11
+ - Updated Debouncer help and README, adding a dedicated explanatory image.<br/>
12
+ </p>
13
+
14
+ <p>
15
+ <b>Version 1.2.10</b> April 2026<br/>
16
+
17
+ - NEW: DebouncerUltimate node for dedicated debounce flows with trailing/leading/both emission modes.<br/>
18
+ - Updated README, examples and tests accordingly.<br/>
19
+ </p>
20
+
21
+ <p>
22
+ <b>Version 1.2.9</b> April 2026<br/>
23
+
24
+ - NEW: Hysteresis node and aligned package registration/examples docs accordingly.<br/>
25
+ </p>
26
+
7
27
  <p>
8
28
  <b>Version 1.2.8</b> January 2026<br/>
9
29
  - Added youtube videos for the missing nodes and the relative links to the UI<br/>
package/README.md CHANGED
@@ -1,17 +1,11 @@
1
1
  ![Logo](img/logo.png)
2
2
 
3
3
  [![NPM version][npm-version-image]][npm-url]
4
-
5
4
  [![NPM downloads per month][npm-downloads-month-image]][npm-url]
6
-
7
5
  [![NPM downloads total][npm-downloads-total-image]][npm-url]
8
-
9
6
  [![MIT License][license-image]][license-url]
10
-
11
7
  [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com)
12
-
13
8
  [![Donate via PayPal](https://img.shields.io/badge/Donate-PayPal-blue.svg?style=flat-square)](https://www.paypal.me/techtoday)
14
-
15
9
  [![youtube][youtube-image]][youtube-url]
16
10
 
17
11
  A set of Node-RED enhanced boolean logic and utility nodes, with persistent values after reboot. Compatible also with Homeassistant values.
@@ -662,7 +656,7 @@ Gateway per sensori e dispositivi troppo “chiacchieroni”: limita burst e rim
662
656
  | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
663
657
  | Mode | Seleziona la logica: _Debounce_ (attende quiete), _Throttle_ (impone un intervallo minimo), _Window_ (massimo N messaggi per finestra temporale). |
664
658
  | Wait (ms) | Ritardo di quiete per la modalità debounce. |
665
- | Emit | Per debounce: scegli tra _Leading_ (subito), _Trailing_ (ultimo), _Both_. |
659
+ | Emit | Per debounce: scegli se inviare subito il primo messaggio, solo l’ultimo dopo la pausa, oppure entrambi. |
666
660
  | Interval (ms) | Intervallo minimo tra messaggi in modalità throttle. |
667
661
  | Emit trailing | In throttle, inoltra l’ultimo messaggio ricevuto allo scadere dell’intervallo. |
668
662
  | Window size (ms) | Larghezza della finestra mobile in modalità window. |
@@ -691,6 +685,37 @@ Gateway per sensori e dispositivi troppo “chiacchieroni”: limita burst e rim
691
685
 
692
686
  <br/>
693
687
 
688
+ # DEBOUNCER ULTIMATE
689
+
690
+ 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.
691
+
692
+ <img src='img/debouncer.png' width='80%'>
693
+
694
+ Nell'immagine si vede il comportamento tipico: a sinistra arrivano molti messaggi ravvicinati, il nodo aspetta una breve pausa, e a destra lascia passare solo il messaggio utile.
695
+
696
+ Example flow: [`examples/DebouncerUltimate.json`](examples/DebouncerUltimate.json)
697
+
698
+ ### NODE CONFIGURATION
699
+
700
+ | Property | Description |
701
+ | ------------- | --------------------------------------------------------------------------------------------- |
702
+ | Wait (ms) | Tempo di quiete richiesto prima di chiudere la finestra di debounce. |
703
+ | Emit | Scegli se inviare subito il primo messaggio, solo l’ultimo dopo la pausa, oppure entrambi. |
704
+ | Control topic | Topic dei messaggi di controllo (default `debouncer`). |
705
+
706
+ ### OUTPUT
707
+
708
+ - **Output 1**: messaggi inoltrati dopo il debounce.
709
+
710
+ ### CONTROL MESSAGES (`msg.topic === controlTopic`)
711
+
712
+ - `msg.reset = true` &rarr; cancella timer e messaggio pendente.
713
+ - `msg.flush = true` &rarr; inoltra subito l’ultimo messaggio in attesa.
714
+ - `msg.wait` &rarr; aggiorna il ritardo di debounce a runtime.
715
+ - `msg.emitOn = 'leading'|'trailing'|'both'` &rarr; cambia la modalità di emissione.
716
+
717
+ <br/>
718
+
694
719
  # PRESENCE SIMULATOR ULTIMATE
695
720
 
696
721
  The purpose of this node is to replay a programmable sequence of messages in order to simulate occupancy when you are away.
@@ -720,63 +745,6 @@ Each event in the sequence outputs a message configured in the JSON line. When r
720
745
 
721
746
  <br/>
722
747
 
723
- # ALARM SYSTEM ULTIMATE (BETA)
724
-
725
- This node implements an alarm control panel with multi-mode arming, zones, entry/exit delays, bypass, tamper/fire 24h zones, siren control, status and event log.
726
-
727
- Example flow: [`examples/AlarmSystemUltimate.json`](examples/AlarmSystemUltimate.json)
728
-
729
- ### NODE CONFIGURATION
730
-
731
- | Property | Description |
732
- | --------------------------- | ------------------------------------------------------------------------------------------------------------ |
733
- | Control topic | Topic that receives runtime commands such as arm/disarm/status/bypass. |
734
- | With Input | Message property evaluated as sensor value (default `payload`). |
735
- | Persist state | Persists arming mode, bypass list and last log entries across restarts. |
736
- | Require code for arm/disarm | Enables PIN checks using `msg.code` (or `msg.pin`). |
737
- | Exit/Entry delay (s) | Global exit/entry delays (each zone can override entry delay). |
738
- | Siren topic | Topic used on output 2 to turn the siren on/off. |
739
- | Siren payloads | Values emitted on output 2 for siren on/off (typed). |
740
- | Siren duration (s) | Auto stop duration (0 = latch until disarm). |
741
- | Emit restore events | Emits `zone_restore` when a zone returns to false. |
742
- | Event log size | Max stored log entries in node context. |
743
- | Zones | One JSON object per line (legacy) or a JSON array (formatted). Use **Format** in the editor to pretty-print. |
744
-
745
- Esempio JSON di una zona:
746
-
747
- ```json
748
- {
749
- "id": "front_door",
750
- "name": "Front Door",
751
- "topic": "house/door/front",
752
- "type": "perimeter",
753
- "modes": ["away", "night"],
754
- "entry": true,
755
- "entryDelaySeconds": 30,
756
- "bypassable": true,
757
- "chime": true
758
- }
759
- ```
760
-
761
- ### OUTPUTS
762
-
763
- - Output 1 (Events): `msg.topic = <controlTopic>/event`, with `msg.event` and `msg.payload` (state + details).
764
- - Output 2 (Siren): emits siren on/off commands on `sirenTopic` with the configured payloads.
765
-
766
- ### CONTROL MESSAGES (`msg.topic === controlTopic`)
767
-
768
- - Arm: `msg.command = 'arm_away'|'arm_home'|'arm_night'` or `msg.arm = 'away'|'home'|'night'`
769
- - Disarm: `msg.command = 'disarm'` or `msg.disarm = true`
770
- - Status: `msg.command = 'status'` or `msg.status = true`
771
- - Bypass: `msg.command = 'bypass'|'unbypass'` with `msg.zone = '<zone id>'`
772
- - Panic: `msg.command = 'panic'` or `msg.command = 'panic_silent'`
773
- - Siren: `msg.command = 'siren_on'|'siren_off'`
774
- - Reset: `msg.command = 'reset'` or `msg.reset = true`
775
-
776
- When codes are enabled, pass `msg.code` (or `msg.pin`). If `duressCode` matches, the node raises a silent duress alarm event.
777
-
778
- <br/>
779
-
780
748
  # STAIRCASE LIGHT ULTIMATE
781
749
 
782
750
  The purpose of this node is to control staircase lighting with a timer, pre-off warning and optional extension on every trigger.
@@ -807,6 +775,36 @@ Output 1 delivers the ON/OFF command. Output 2 delivers the warning and includes
807
775
 
808
776
  <br/>
809
777
 
778
+ # HYSTERESIS ULTIMATE
779
+
780
+ Adds hysteresis to numeric sensor values to avoid rapid ON/OFF bouncing around thresholds.
781
+
782
+ Example flow: [`examples/HysteresisUltimate.json`](examples/HysteresisUltimate.json)
783
+
784
+ ### NODE CONFIGURATION
785
+
786
+ | Property | Description |
787
+ | ------------------- | ------------------------------------------------------------------------------ |
788
+ | Control topic | Topic that receives runtime commands such as threshold updates and reset. |
789
+ | Mode | `high` = ON above threshold, OFF below. `low` = ON below threshold, OFF above. |
790
+ | ON/OFF threshold | Hysteresis limits. |
791
+ | Initial state | Startup output state. |
792
+ | Emit only on change | If enabled, output 1 emits only on state transitions. |
793
+ | With Input | Message property evaluated as numeric value (default `payload`). |
794
+ | Translator | Optional translator-config. |
795
+ | On/Off payload | Typed payloads sent on output 1. |
796
+
797
+ ### CONTROL MESSAGES (`msg.topic === controlTopic`)
798
+
799
+ - `msg.onThreshold`, `msg.offThreshold` &rarr; update thresholds at runtime.
800
+ - `msg.state = true|false` &rarr; force state.
801
+ - `msg.reset = true` &rarr; restore initial state.
802
+ - `msg.status = true` &rarr; emit current state/thresholds on output 2.
803
+
804
+ Output 1 emits the command payload (`onPayload`/`offPayload`). Output 2 emits diagnostic events.
805
+
806
+ <br/>
807
+
810
808
  [license-image]: https://img.shields.io/badge/license-MIT-blue.svg
811
809
  [license-url]: https://github.com/Supergiovane/node-red-contrib-boolean-logic-ultimate/master/LICENSE
812
810
  [npm-url]: https://npmjs.org/package/node-red-contrib-boolean-logic-ultimate
@@ -0,0 +1,76 @@
1
+ <script type="text/javascript">
2
+ RED.nodes.registerType('DebouncerUltimate', {
3
+ category: 'Boolean Logic Ultimate',
4
+ color: '#ff8080',
5
+ defaults: {
6
+ name: { value: '' },
7
+ wait: { value: 500, validate: RED.validators.number() },
8
+ emitOn: { value: 'trailing' },
9
+ controlTopic: { value: 'debouncer' }
10
+ },
11
+ inputs: 1,
12
+ outputs: 1,
13
+ outputLabels: ['Forward'],
14
+ icon: 'file-in.png',
15
+ label: function () {
16
+ return this.name || 'Debouncer';
17
+ },
18
+ paletteLabel: function () {
19
+ return 'Debouncer';
20
+ }
21
+ });
22
+ </script>
23
+
24
+ <script type="text/html" data-template-name="DebouncerUltimate">
25
+ <div class="form-row">
26
+ <b>Debouncer Ultimate</b>
27
+ &nbsp;&nbsp;<span style="color:red"><i class="fa fa-youtube-play"></i>&nbsp;<a target="_blank" href="https://youtu.be/t45kaMQzm5Q"><u>Youtube Video</u></a></span>
28
+ </div>
29
+
30
+ <div class="form-row">
31
+ <label for="node-input-name"><i class="icon-tag"></i> Name</label>
32
+ <input type="text" id="node-input-name" placeholder="Name">
33
+ </div>
34
+
35
+ <div class="form-row">
36
+ <label for="node-input-wait"><i class="fa fa-hourglass-o"></i> Wait (ms)</label>
37
+ <input type="number" id="node-input-wait" min="0">
38
+ </div>
39
+
40
+ <div class="form-row">
41
+ <label for="node-input-emitOn"><i class="fa fa-exchange"></i> Emit</label>
42
+ <select id="node-input-emitOn">
43
+ <option value="leading">Send first message immediately</option>
44
+ <option value="trailing">Send only the last message after the pause</option>
45
+ <option value="both">Send first immediately and last after the pause</option>
46
+ </select>
47
+ </div>
48
+
49
+ <div class="form-row">
50
+ <label for="node-input-controlTopic"><i class="fa fa-tag"></i> Control topic</label>
51
+ <input type="text" id="node-input-controlTopic">
52
+ </div>
53
+ </script>
54
+
55
+ <script type="text/markdown" data-help-name="DebouncerUltimate">
56
+ <p>Dedicated debounce node to suppress rapid bursts and forward only the first, last or both messages of a quiet-time window.</p>
57
+
58
+ <p><img src="/resources/node-red-contrib-boolean-logic-ultimate/img/debouncer.png" alt="Debouncer example" style="max-width:100%; border-radius:6px;" /></p>
59
+
60
+ |Property|Description|
61
+ |--|--|
62
+ | Wait (ms) | Quiet time before the debounce window closes. |
63
+ | Emit | Choose whether to send the first message immediately, only the last one after the pause, or both. |
64
+ | Control topic | Topic used for runtime commands (default `debouncer`). |
65
+
66
+ Output:
67
+
68
+ - Output 1 forwards the debounced message.
69
+
70
+ Control topic messages:
71
+
72
+ - `msg.reset = true` clears the timer and pending message.
73
+ - `msg.flush = true` emits the pending message immediately.
74
+ - `msg.wait` updates the debounce time.
75
+ - `msg.emitOn = 'leading'|'trailing'|'both'` changes the emission mode.
76
+ </script>
@@ -0,0 +1,190 @@
1
+ 'use strict';
2
+
3
+ module.exports = function (RED) {
4
+ function DebouncerUltimate(config) {
5
+ RED.nodes.createNode(this, config);
6
+ const node = this;
7
+ const REDUtil = RED.util;
8
+ const helpers = require('./lib/node-helpers.js');
9
+
10
+ const setNodeStatus = helpers.createStatus(node);
11
+ const timerBag = helpers.createTimerBag(node);
12
+
13
+ const controlTopic = config.controlTopic || 'debouncer';
14
+
15
+ let wait = Number(config.wait);
16
+ if (!Number.isFinite(wait) || wait < 0) {
17
+ wait = 500;
18
+ }
19
+
20
+ let emitOn = normalizeEmitOn(config.emitOn);
21
+ let passedCount = 0;
22
+ let droppedCount = 0;
23
+ let currentState = 'idle';
24
+ let debounceTimer = null;
25
+ let leadingSent = false;
26
+ let pendingMessage = null;
27
+
28
+ function normalizeEmitOn(value) {
29
+ const normalized = String(value || 'trailing').toLowerCase();
30
+ return ['leading', 'trailing', 'both'].includes(normalized)
31
+ ? normalized
32
+ : 'trailing';
33
+ }
34
+
35
+ function clone(msg) {
36
+ return REDUtil.cloneMessage ? REDUtil.cloneMessage(msg) : JSON.parse(JSON.stringify(msg));
37
+ }
38
+
39
+ function sendStatus() {
40
+ const colour = currentState === 'idle' ? 'green' : 'yellow';
41
+ const modeLabel = {
42
+ leading: 'L',
43
+ trailing: 'T',
44
+ both: 'B',
45
+ }[emitOn] || 'T';
46
+
47
+ setNodeStatus({
48
+ fill: colour,
49
+ shape: 'dot',
50
+ text: `DB|${modeLabel}|${currentState} pass:${passedCount} drop:${droppedCount}`,
51
+ });
52
+ }
53
+
54
+ function emitForward(msg, nextState = 'idle') {
55
+ passedCount += 1;
56
+ currentState = nextState;
57
+ node.send(msg);
58
+ sendStatus();
59
+ }
60
+
61
+ function emitDrop() {
62
+ droppedCount += 1;
63
+ currentState = 'waiting';
64
+ sendStatus();
65
+ }
66
+
67
+ function clearDebounce() {
68
+ if (debounceTimer) {
69
+ timerBag.clearTimeout(debounceTimer);
70
+ debounceTimer = null;
71
+ }
72
+ pendingMessage = null;
73
+ leadingSent = false;
74
+ }
75
+
76
+ function resetDebounce() {
77
+ clearDebounce();
78
+ currentState = 'idle';
79
+ sendStatus();
80
+ }
81
+
82
+ function flushPending() {
83
+ if (!pendingMessage) {
84
+ currentState = 'idle';
85
+ sendStatus();
86
+ return false;
87
+ }
88
+
89
+ const toSend = pendingMessage;
90
+ if (debounceTimer) {
91
+ timerBag.clearTimeout(debounceTimer);
92
+ debounceTimer = null;
93
+ }
94
+ pendingMessage = null;
95
+ leadingSent = false;
96
+ emitForward(toSend, 'idle');
97
+ return true;
98
+ }
99
+
100
+ function handleControlMessage(msg) {
101
+ let consumed = false;
102
+
103
+ if (Object.prototype.hasOwnProperty.call(msg, 'wait')) {
104
+ const nextWait = Number(msg.wait);
105
+ if (Number.isFinite(nextWait) && nextWait >= 0) {
106
+ wait = nextWait;
107
+ consumed = true;
108
+ }
109
+ }
110
+
111
+ if (typeof msg.emitOn === 'string') {
112
+ emitOn = normalizeEmitOn(msg.emitOn);
113
+ consumed = true;
114
+ }
115
+
116
+ if (msg.reset === true) {
117
+ resetDebounce();
118
+ consumed = true;
119
+ }
120
+
121
+ if (msg.flush === true) {
122
+ flushPending();
123
+ consumed = true;
124
+ }
125
+
126
+ if (msg.status === true) {
127
+ consumed = true;
128
+ sendStatus();
129
+ }
130
+
131
+ if (consumed) {
132
+ sendStatus();
133
+ }
134
+
135
+ return consumed;
136
+ }
137
+
138
+ function handleDebounce(msg) {
139
+ const cloned = clone(msg);
140
+
141
+ if (debounceTimer) {
142
+ timerBag.clearTimeout(debounceTimer);
143
+ debounceTimer = null;
144
+ }
145
+
146
+ pendingMessage = cloned;
147
+ currentState = 'waiting';
148
+
149
+ const shouldEmitLeading =
150
+ (emitOn === 'leading' || emitOn === 'both') && !leadingSent;
151
+
152
+ if (shouldEmitLeading) {
153
+ emitForward(cloned, 'waiting');
154
+ leadingSent = true;
155
+ }
156
+
157
+ if (emitOn === 'leading' && !shouldEmitLeading) {
158
+ emitDrop();
159
+ }
160
+
161
+ debounceTimer = timerBag.setTimeout(() => {
162
+ debounceTimer = null;
163
+ leadingSent = false;
164
+ const shouldEmitTrailing = emitOn === 'trailing' || emitOn === 'both';
165
+
166
+ if (shouldEmitTrailing && pendingMessage) {
167
+ const toSend = pendingMessage;
168
+ pendingMessage = null;
169
+ emitForward(toSend, 'idle');
170
+ } else {
171
+ pendingMessage = null;
172
+ currentState = 'idle';
173
+ sendStatus();
174
+ }
175
+ }, wait);
176
+ }
177
+
178
+ node.on('input', function (msg) {
179
+ if (msg.topic === controlTopic && handleControlMessage(msg)) {
180
+ return;
181
+ }
182
+
183
+ handleDebounce(msg);
184
+ });
185
+
186
+ sendStatus();
187
+ }
188
+
189
+ RED.nodes.registerType('DebouncerUltimate', DebouncerUltimate);
190
+ };
@@ -0,0 +1,137 @@
1
+ <script type="text/javascript">
2
+ RED.nodes.registerType('HysteresisUltimate', {
3
+ category: 'Boolean Logic Ultimate',
4
+ color: '#ff8080',
5
+ defaults: {
6
+ name: { value: '' },
7
+ controlTopic: { value: 'hysteresis' },
8
+ payloadPropName: { value: 'payload', required: false },
9
+ translatorConfig: { type: 'translator-config', required: false },
10
+ mode: { value: 'high' },
11
+ onThreshold: { value: 70, validate: RED.validators.number() },
12
+ offThreshold: { value: 65, validate: RED.validators.number() },
13
+ initialState: { value: false },
14
+ emitOnlyOnChange: { value: true },
15
+ onPayload: { value: true },
16
+ onPayloadType: { value: 'bool' },
17
+ offPayload: { value: false },
18
+ offPayloadType: { value: 'bool' }
19
+ },
20
+ inputs: 1,
21
+ outputs: 2,
22
+ outputLabels: ['State output', 'Diagnostics'],
23
+ icon: 'font-awesome/fa-sliders',
24
+ label: function () {
25
+ return this.name || 'Hysteresis';
26
+ },
27
+ paletteLabel: function () {
28
+ return 'Hysteresis';
29
+ },
30
+ oneditprepare: function () {
31
+ const payloadField = $('#node-input-payloadPropName');
32
+ if (payloadField.val() === '') payloadField.val('payload');
33
+ payloadField.typedInput({ default: 'msg', types: ['msg'] });
34
+
35
+ $('#node-input-onPayload').typedInput({
36
+ default: this.onPayloadType || 'bool',
37
+ types: ['str', 'num', 'bool', 'json']
38
+ });
39
+ $('#node-input-onPayload').typedInput('type', this.onPayloadType || 'bool');
40
+
41
+ $('#node-input-offPayload').typedInput({
42
+ default: this.offPayloadType || 'bool',
43
+ types: ['str', 'num', 'bool', 'json']
44
+ });
45
+ $('#node-input-offPayload').typedInput('type', this.offPayloadType || 'bool');
46
+ },
47
+ oneditsave: function () {
48
+ this.onPayloadType = $('#node-input-onPayload').typedInput('type');
49
+ this.offPayloadType = $('#node-input-offPayload').typedInput('type');
50
+ }
51
+ });
52
+ </script>
53
+
54
+ <script type="text/html" data-template-name="HysteresisUltimate">
55
+ <div class="form-row">
56
+ <b>Hysteresis Ultimate</b>
57
+ &nbsp;&nbsp;<span style="color:red"><i class="fa fa-question-circle"></i>&nbsp;<a target="_blank" href="https://github.com/Supergiovane/node-red-contrib-boolean-logic-ultimate"><u>Help online</u></a></span>
58
+ </div>
59
+
60
+ <div class="form-row">
61
+ <label for="node-input-name"><i class="icon-tag"></i> Name</label>
62
+ <input type="text" id="node-input-name" placeholder="Name">
63
+ </div>
64
+
65
+ <div class="form-row">
66
+ <label for="node-input-controlTopic"><i class="fa fa-tag"></i> Control topic</label>
67
+ <input type="text" id="node-input-controlTopic">
68
+ </div>
69
+
70
+ <div class="form-row">
71
+ <label for="node-input-mode"><i class="fa fa-arrows-v"></i> Mode</label>
72
+ <select id="node-input-mode">
73
+ <option value="high">High threshold (ON above)</option>
74
+ <option value="low">Low threshold (ON below)</option>
75
+ </select>
76
+ </div>
77
+
78
+ <div class="form-row">
79
+ <label for="node-input-onThreshold"><i class="fa fa-long-arrow-up"></i> ON threshold</label>
80
+ <input type="number" id="node-input-onThreshold" step="any">
81
+ </div>
82
+
83
+ <div class="form-row">
84
+ <label for="node-input-offThreshold"><i class="fa fa-long-arrow-down"></i> OFF threshold</label>
85
+ <input type="number" id="node-input-offThreshold" step="any">
86
+ </div>
87
+
88
+ <div class="form-row">
89
+ <label for="node-input-initialState"><i class="fa fa-power-off"></i> Initial state ON</label>
90
+ <input type="checkbox" id="node-input-initialState" style="width:auto; margin-top:7px;">
91
+ </div>
92
+
93
+ <div class="form-row">
94
+ <label for="node-input-emitOnlyOnChange"><i class="fa fa-filter"></i> Emit only on change</label>
95
+ <input type="checkbox" id="node-input-emitOnlyOnChange" style="width:auto; margin-top:7px;">
96
+ </div>
97
+
98
+ <div class="form-row">
99
+ <label for="node-input-payloadPropName"><i class="fa fa-ellipsis-h"></i> With Input</label>
100
+ <input type="text" id="node-input-payloadPropName">
101
+ </div>
102
+
103
+ <div class="form-row">
104
+ <label for="node-input-translatorConfig"><i class="fa fa-language"></i> Translator</label>
105
+ <input type="text" id="node-input-translatorConfig">
106
+ </div>
107
+
108
+ <div class="form-row">
109
+ <label for="node-input-onPayload"><i class="fa fa-sign-in"></i> On payload</label>
110
+ <input type="text" id="node-input-onPayload">
111
+ </div>
112
+
113
+ <div class="form-row">
114
+ <label for="node-input-offPayload"><i class="fa fa-sign-out"></i> Off payload</label>
115
+ <input type="text" id="node-input-offPayload">
116
+ </div>
117
+ </script>
118
+
119
+ <script type="text/markdown" data-help-name="HysteresisUltimate">
120
+ <p>Adds hysteresis to numeric values to avoid rapid ON/OFF bouncing around a threshold.</p>
121
+
122
+ |Property|Description|
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
+ | With Input | Message property to evaluate (default `payload`). |
128
+ | Translator | Optional translator-config. |
129
+ | On/Off payload | Values sent on output 1. |
130
+
131
+ Control topic messages:
132
+
133
+ - `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.
136
+ - `msg.status = true` to emit current status on output 2.
137
+ </script>