node-red-contrib-boolean-logic-ultimate 1.2.6 → 1.2.9

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.
@@ -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>
@@ -0,0 +1,197 @@
1
+ 'use strict';
2
+
3
+ module.exports = function (RED) {
4
+ const helpers = require('./lib/node-helpers.js');
5
+
6
+ function HysteresisUltimate(config) {
7
+ RED.nodes.createNode(this, config);
8
+ const node = this;
9
+ const REDUtil = RED.util;
10
+
11
+ const setNodeStatus = helpers.createStatus(node);
12
+
13
+ const controlTopic = config.controlTopic || 'hysteresis';
14
+ const payloadPropName = config.payloadPropName || 'payload';
15
+ const mode = (config.mode || 'high').toLowerCase(); // high | low
16
+ const emitOnlyOnChange = config.emitOnlyOnChange !== false;
17
+
18
+ let onThreshold = Number(config.onThreshold);
19
+ let offThreshold = Number(config.offThreshold);
20
+ let state = Boolean(config.initialState);
21
+
22
+ if (!Number.isFinite(onThreshold)) onThreshold = 70;
23
+ if (!Number.isFinite(offThreshold)) offThreshold = 65;
24
+
25
+ function toNumber(value) {
26
+ if (typeof value === 'number' && Number.isFinite(value)) {
27
+ return value;
28
+ }
29
+ if (typeof value === 'boolean') {
30
+ return value ? 1 : 0;
31
+ }
32
+ if (typeof value === 'string') {
33
+ const v = Number(value.trim());
34
+ return Number.isFinite(v) ? v : undefined;
35
+ }
36
+ return undefined;
37
+ }
38
+
39
+ function evaluateTyped(typedValue, typedType, baseMsg, fallback) {
40
+ try {
41
+ return REDUtil.evaluateNodeProperty(typedValue, typedType || 'bool', node, baseMsg);
42
+ } catch (error) {
43
+ return fallback;
44
+ }
45
+ }
46
+
47
+ function updateStatus(lastValue) {
48
+ const direction = mode === 'low' ? 'LOW' : 'HIGH';
49
+ const valueText = lastValue === undefined ? '-' : Number(lastValue).toFixed(2);
50
+ setNodeStatus({
51
+ fill: state ? 'green' : 'grey',
52
+ shape: state ? 'dot' : 'ring',
53
+ text: `${direction} ${valueText} on:${onThreshold} off:${offThreshold}`,
54
+ });
55
+ }
56
+
57
+ function evaluateState(value) {
58
+ let nextState = state;
59
+ if (mode === 'low') {
60
+ if (value <= onThreshold) {
61
+ nextState = true;
62
+ } else if (value >= offThreshold) {
63
+ nextState = false;
64
+ }
65
+ } else {
66
+ if (value >= onThreshold) {
67
+ nextState = true;
68
+ } else if (value <= offThreshold) {
69
+ nextState = false;
70
+ }
71
+ }
72
+ return nextState;
73
+ }
74
+
75
+ function emitDiagnostics(baseMsg, payload) {
76
+ const msg = baseMsg ? REDUtil.cloneMessage(baseMsg) : {};
77
+ msg.topic = `${controlTopic}/event`;
78
+ msg.payload = payload;
79
+ node.send([null, msg]);
80
+ }
81
+
82
+ function emitState(baseMsg, changed, inputValue) {
83
+ if (!changed && emitOnlyOnChange) {
84
+ updateStatus(inputValue);
85
+ return;
86
+ }
87
+ const msg = baseMsg ? REDUtil.cloneMessage(baseMsg) : {};
88
+ msg.payload = state
89
+ ? evaluateTyped(config.onPayload, config.onPayloadType, baseMsg, true)
90
+ : evaluateTyped(config.offPayload, config.offPayloadType, baseMsg, false);
91
+ msg.hysteresis = {
92
+ state,
93
+ changed,
94
+ mode,
95
+ value: inputValue,
96
+ onThreshold,
97
+ offThreshold,
98
+ };
99
+ msg.event = changed ? 'state_changed' : 'state_confirmed';
100
+ node.send([msg, null]);
101
+ updateStatus(inputValue);
102
+ }
103
+
104
+ function handleControl(msg) {
105
+ let consumed = false;
106
+
107
+ if (msg.reset === true) {
108
+ state = Boolean(config.initialState);
109
+ emitDiagnostics(msg, {
110
+ event: 'reset',
111
+ state,
112
+ onThreshold,
113
+ offThreshold,
114
+ mode,
115
+ });
116
+ consumed = true;
117
+ }
118
+
119
+ if (Object.prototype.hasOwnProperty.call(msg, 'onThreshold')) {
120
+ const next = Number(msg.onThreshold);
121
+ if (Number.isFinite(next)) {
122
+ onThreshold = next;
123
+ consumed = true;
124
+ }
125
+ }
126
+
127
+ if (Object.prototype.hasOwnProperty.call(msg, 'offThreshold')) {
128
+ const next = Number(msg.offThreshold);
129
+ if (Number.isFinite(next)) {
130
+ offThreshold = next;
131
+ consumed = true;
132
+ }
133
+ }
134
+
135
+ if (Object.prototype.hasOwnProperty.call(msg, 'state')) {
136
+ state = Boolean(msg.state);
137
+ emitState(msg, true, undefined);
138
+ consumed = true;
139
+ }
140
+
141
+ if (msg.status === true) {
142
+ emitDiagnostics(msg, {
143
+ event: 'status',
144
+ state,
145
+ mode,
146
+ onThreshold,
147
+ offThreshold,
148
+ });
149
+ consumed = true;
150
+ }
151
+
152
+ if (consumed) {
153
+ updateStatus();
154
+ }
155
+
156
+ return consumed;
157
+ }
158
+
159
+ node.on('input', (msg) => {
160
+ if (msg.topic === controlTopic && handleControl(msg)) {
161
+ return;
162
+ }
163
+
164
+ const resolved = helpers.resolveInput(msg, payloadPropName, config.translatorConfig, RED);
165
+ const inputValue = toNumber(resolved.value);
166
+
167
+ if (inputValue === undefined) {
168
+ emitDiagnostics(msg, {
169
+ event: 'invalid_input',
170
+ value: resolved.value,
171
+ property: payloadPropName,
172
+ });
173
+ return;
174
+ }
175
+
176
+ const nextState = evaluateState(inputValue);
177
+ const changed = nextState !== state;
178
+ state = nextState;
179
+ emitState(msg, changed, inputValue);
180
+
181
+ if (changed) {
182
+ emitDiagnostics(msg, {
183
+ event: 'state_changed',
184
+ state,
185
+ value: inputValue,
186
+ mode,
187
+ onThreshold,
188
+ offThreshold,
189
+ });
190
+ }
191
+ });
192
+
193
+ updateStatus();
194
+ }
195
+
196
+ RED.nodes.registerType('HysteresisUltimate', HysteresisUltimate);
197
+ };
@@ -34,6 +34,7 @@
34
34
  <div class="form-row">
35
35
  <b>Presence Simulator Ultimate</b>
36
36
  &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>
37
+ &nbsp;&nbsp;<span style="color:red"><i class="fa fa-youtube-play"></i>&nbsp;<a target="_blank" href="https://youtu.be/VqI3Qeyx0Bg"><u>Youtube Sample</u></a></span>
37
38
  </div>
38
39
 
39
40
  <div class="form-row">
@@ -59,7 +59,7 @@
59
59
  <div class="form-row">
60
60
  <b>Rate Limiter Ultimate</b>&nbsp;&nbsp;
61
61
  <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>
62
- &nbsp;&nbsp;<span style="color:red"><i class="fa fa-youtube-play"></i>&nbsp;<a target="_blank" href="https://youtu.be/1T1g0HCeYA8"><u>Youtube Sample</u></a></span>
62
+ &nbsp;&nbsp;<span style="color:red"><i class="fa fa-youtube-play"></i>&nbsp;<a target="_blank" href="https://youtu.be/_L1tIbxmqRI"><u>Youtube Sample</u></a></span>
63
63
  </div>
64
64
 
65
65
  <div class="form-row">
@@ -161,4 +161,4 @@ The purpose of this node is to moderate the frequency of incoming messages.
161
161
  - `msg.interval`, `msg.wait`, `msg.windowSize`, `msg.maxInWindow` – adjust the related thresholds.
162
162
 
163
163
  The **With Input** field selects which message property to evaluate (default `msg.payload`). When configured, it can be translated through the **translator-config** node for Home Assistant compatibility.
164
- </script>
164
+ </script>
@@ -64,6 +64,7 @@
64
64
  <div class="form-row">
65
65
  <b>Staircase Light Ultimate</b>
66
66
  &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>
67
+ &nbsp;&nbsp;<span style="color:red"><i class="fa fa-youtube-play"></i>&nbsp;<a target="_blank" href="https://youtu.be/W8iwIqOSB30"><u>Youtube Sample</u></a></span>
67
68
  </div>
68
69
 
69
70
  <div class="form-row">
@@ -0,0 +1,109 @@
1
+ [
2
+ {
3
+ "id": "hys-flow",
4
+ "type": "tab",
5
+ "label": "HysteresisUltimate"
6
+ },
7
+ {
8
+ "id": "hys-node",
9
+ "type": "HysteresisUltimate",
10
+ "z": "hys-flow",
11
+ "name": "Bathroom Humidity",
12
+ "controlTopic": "hysteresis",
13
+ "payloadPropName": "payload",
14
+ "translatorConfig": "",
15
+ "mode": "high",
16
+ "onThreshold": 70,
17
+ "offThreshold": 62,
18
+ "initialState": false,
19
+ "emitOnlyOnChange": true,
20
+ "onPayload": "on",
21
+ "onPayloadType": "str",
22
+ "offPayload": "off",
23
+ "offPayloadType": "str",
24
+ "x": 520,
25
+ "y": 200,
26
+ "wires": [["hys-out"], ["hys-diag"]]
27
+ },
28
+ {
29
+ "id": "hys-in-1",
30
+ "type": "inject",
31
+ "z": "hys-flow",
32
+ "name": "Humidity 68",
33
+ "props": [
34
+ { "p": "payload", "v": "68", "vt": "num" },
35
+ { "p": "topic", "v": "sensor/bathroom/humidity", "vt": "str" }
36
+ ],
37
+ "repeat": "",
38
+ "crontab": "",
39
+ "once": false,
40
+ "onceDelay": 0.1,
41
+ "x": 260,
42
+ "y": 160,
43
+ "wires": [["hys-node"]]
44
+ },
45
+ {
46
+ "id": "hys-in-2",
47
+ "type": "inject",
48
+ "z": "hys-flow",
49
+ "name": "Humidity 72",
50
+ "props": [
51
+ { "p": "payload", "v": "72", "vt": "num" },
52
+ { "p": "topic", "v": "sensor/bathroom/humidity", "vt": "str" }
53
+ ],
54
+ "repeat": "",
55
+ "crontab": "",
56
+ "once": false,
57
+ "onceDelay": 0.1,
58
+ "x": 260,
59
+ "y": 200,
60
+ "wires": [["hys-node"]]
61
+ },
62
+ {
63
+ "id": "hys-in-3",
64
+ "type": "inject",
65
+ "z": "hys-flow",
66
+ "name": "Humidity 60",
67
+ "props": [
68
+ { "p": "payload", "v": "60", "vt": "num" },
69
+ { "p": "topic", "v": "sensor/bathroom/humidity", "vt": "str" }
70
+ ],
71
+ "repeat": "",
72
+ "crontab": "",
73
+ "once": false,
74
+ "onceDelay": 0.1,
75
+ "x": 260,
76
+ "y": 240,
77
+ "wires": [["hys-node"]]
78
+ },
79
+ {
80
+ "id": "hys-out",
81
+ "type": "debug",
82
+ "z": "hys-flow",
83
+ "name": "HA Fan Command",
84
+ "active": true,
85
+ "tosidebar": true,
86
+ "console": false,
87
+ "tostatus": false,
88
+ "complete": "true",
89
+ "targetType": "full",
90
+ "x": 780,
91
+ "y": 180,
92
+ "wires": []
93
+ },
94
+ {
95
+ "id": "hys-diag",
96
+ "type": "debug",
97
+ "z": "hys-flow",
98
+ "name": "Diagnostics",
99
+ "active": true,
100
+ "tosidebar": true,
101
+ "console": false,
102
+ "tostatus": false,
103
+ "complete": "payload",
104
+ "targetType": "msg",
105
+ "x": 760,
106
+ "y": 240,
107
+ "wires": []
108
+ }
109
+ ]
@@ -0,0 +1,29 @@
1
+ # Examples
2
+
3
+ Importable Node-RED example flows (one JSON per node).
4
+
5
+ How to import:
6
+
7
+ - Node-RED editor → Menu (☰) → **Import** → **Examples** → `node-red-contrib-boolean-logic-ultimate`
8
+
9
+ ## Available example flows
10
+
11
+ - `BlinkerUltimate.json` — BlinkerUltimate
12
+ - `BooleanLogicUltimate.json` — BooleanLogicUltimate
13
+ - `Comparator.json` — Comparator
14
+ - `FilterUltimate.json` — FilterUltimate
15
+ - `HysteresisUltimate.json` — HysteresisUltimate
16
+ - `ImpulseUltimate.json` — ImpulseUltimate
17
+ - `InjectUltimate.json` — InjectUltimate
18
+ - `InterruptFlowUltimate.json` — InterruptFlowUltimate
19
+ - `InvertUltimate.json` — InvertUltimate
20
+ - `KalmanFilterUltimate.json` — KalmanFilterUltimate
21
+ - `PresenceSimulatorUltimate.json` — PresenceSimulatorUltimate
22
+ - `RailwaySwitchUltimate.json` — RailwaySwitchUltimate
23
+ - `RateLimiterUltimate.json` — RateLimiterUltimate
24
+ - `SimpleOutputUltimate.json` — SimpleOutputUltimate
25
+ - `StaircaseLightUltimate.json` — StaircaseLightUltimate
26
+ - `StatusUltimate.json` — StatusUltimate
27
+ - `SumUltimate.json` — SumUltimate
28
+ - `toggleUltimate.json` — toggleUltimate
29
+ - `translator-config.json` — translator-config
@@ -0,0 +1,69 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="1200" height="500" viewBox="0 0 1200 500" role="img" aria-label="Alarm System Ultimate illustration">
2
+ <defs>
3
+ <linearGradient id="g" x1="0" x2="1" y1="0" y2="1">
4
+ <stop offset="0" stop-color="#ff8080"/>
5
+ <stop offset="1" stop-color="#ff4d4d"/>
6
+ </linearGradient>
7
+ <filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
8
+ <feDropShadow dx="0" dy="10" stdDeviation="12" flood-color="#000" flood-opacity="0.25"/>
9
+ </filter>
10
+ </defs>
11
+
12
+ <rect x="0" y="0" width="1200" height="500" fill="#0b1220"/>
13
+
14
+ <g filter="url(#shadow)">
15
+ <rect x="90" y="70" width="1020" height="360" rx="28" fill="#111a2e" stroke="#263450" stroke-width="2"/>
16
+ </g>
17
+
18
+ <g transform="translate(140 120)">
19
+ <rect x="0" y="0" width="320" height="260" rx="22" fill="#0f1730" stroke="#2b3b5a" stroke-width="2"/>
20
+ <text x="22" y="44" fill="#cfe2ff" font-family="system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif" font-size="22" font-weight="700">ALARM PANEL</text>
21
+
22
+ <g transform="translate(24 70)">
23
+ <circle cx="16" cy="16" r="10" fill="url(#g)"/>
24
+ <text x="40" y="22" fill="#d9e6ff" font-family="system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif" font-size="16">Mode: Away</text>
25
+
26
+ <circle cx="16" cy="56" r="10" fill="#ffd166"/>
27
+ <text x="40" y="62" fill="#d9e6ff" font-family="system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif" font-size="16">Entry delay</text>
28
+
29
+ <circle cx="16" cy="96" r="10" fill="#64dfdf"/>
30
+ <text x="40" y="102" fill="#d9e6ff" font-family="system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif" font-size="16">Zones</text>
31
+
32
+ <rect x="0" y="126" width="272" height="90" rx="12" fill="#0b1220" stroke="#2b3b5a" stroke-width="2"/>
33
+ <text x="14" y="156" fill="#cfe2ff" font-family="ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace" font-size="13">front_door: true</text>
34
+ <text x="14" y="180" fill="#cfe2ff" font-family="ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace" font-size="13">pir_living: false</text>
35
+ <text x="14" y="204" fill="#cfe2ff" font-family="ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace" font-size="13">smoke: false</text>
36
+ </g>
37
+ </g>
38
+
39
+ <g transform="translate(520 110)">
40
+ <path d="M260 0c92 0 168 76 168 168 0 135-120 232-168 260-48-28-168-125-168-260C92 76 168 0 260 0z" fill="url(#g)" opacity="0.95"/>
41
+ <path d="M260 42c-70 0-126 56-126 126 0 104 84 184 126 208 42-24 126-104 126-208 0-70-56-126-126-126z" fill="#0b1220" opacity="0.25"/>
42
+
43
+ <g transform="translate(180 120)">
44
+ <path d="M80 0c38 0 70 32 70 70v14c0 8-6 14-14 14H24c-8 0-14-6-14-14V70C10 32 42 0 80 0z" fill="#0b1220" opacity="0.55"/>
45
+ <path d="M80 6c34 0 62 28 62 62v16H18V68C18 34 46 6 80 6z" fill="#ffffff" opacity="0.9"/>
46
+ <rect x="32" y="92" width="96" height="82" rx="18" fill="#ffffff" opacity="0.9"/>
47
+ <circle cx="80" cy="194" r="14" fill="#ffffff" opacity="0.9"/>
48
+ </g>
49
+
50
+ <text x="260" y="468" text-anchor="middle" fill="#cfe2ff" font-family="system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif" font-size="22" font-weight="700">Alarm System Ultimate</text>
51
+ <text x="260" y="494" text-anchor="middle" fill="#9fb7e0" font-family="system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif" font-size="14">Zones • Entry/Exit • Bypass • Siren</text>
52
+ </g>
53
+
54
+ <g transform="translate(900 140)">
55
+ <rect x="0" y="0" width="220" height="240" rx="22" fill="#0f1730" stroke="#2b3b5a" stroke-width="2"/>
56
+ <text x="20" y="42" fill="#cfe2ff" font-family="system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif" font-size="18" font-weight="700">SIREN</text>
57
+
58
+ <g transform="translate(40 70)">
59
+ <rect x="40" y="28" width="100" height="92" rx="18" fill="#ffffff" opacity="0.9"/>
60
+ <path d="M60 28c0-28 40-28 40 0" fill="none" stroke="#ffffff" stroke-width="10" opacity="0.9" stroke-linecap="round"/>
61
+ <path d="M26 84c-18 0-26 16-26 30 0 18 10 34 26 34" fill="none" stroke="#ffd166" stroke-width="10" stroke-linecap="round"/>
62
+ <path d="M154 84c18 0 26 16 26 30 0 18-10 34-26 34" fill="none" stroke="#ffd166" stroke-width="10" stroke-linecap="round"/>
63
+ </g>
64
+
65
+ <text x="20" y="210" fill="#9fb7e0" font-family="ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace" font-size="13">topic: siren</text>
66
+ <text x="20" y="232" fill="#9fb7e0" font-family="ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace" font-size="13">payload: true/false</text>
67
+ </g>
68
+ </svg>
69
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-boolean-logic-ultimate",
3
- "version": "1.2.6",
3
+ "version": "1.2.9",
4
4
  "description": "A set of Node-RED enhanced boolean logic and utility nodes, flow interruption, blinker, invert, filter, toggle etc.., with persistent values after reboot. Compatible also with Homeassistant values.",
5
5
  "author": "Supergiovane (https://github.com/Supergiovane)",
6
6
  "dependencies": {
@@ -45,10 +45,11 @@
45
45
  "RateLimiterUltimate": "boolean-logic-ultimate/RateLimiterUltimate.js",
46
46
  "PresenceSimulatorUltimate": "boolean-logic-ultimate/PresenceSimulatorUltimate.js",
47
47
  "StaircaseLightUltimate": "boolean-logic-ultimate/StaircaseLightUltimate.js",
48
+ "HysteresisUltimate": "boolean-logic-ultimate/HysteresisUltimate.js",
48
49
  "translator-config": "boolean-logic-ultimate/translator-config.js"
49
50
  }
50
51
  },
51
52
  "scripts": {
52
53
  "test": "mocha test/**/*.spec.js"
53
54
  }
54
- }
55
+ }