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 +20 -0
- package/README.md +62 -64
- package/boolean-logic-ultimate/DebouncerUltimate.html +76 -0
- package/boolean-logic-ultimate/DebouncerUltimate.js +190 -0
- package/boolean-logic-ultimate/HysteresisUltimate.html +137 -0
- package/boolean-logic-ultimate/HysteresisUltimate.js +197 -0
- package/boolean-logic-ultimate/RateLimiterUltimate.html +7 -7
- package/examples/DebouncerUltimate.json +168 -0
- package/examples/HysteresisUltimate.json +109 -0
- package/examples/README.md +2 -2
- package/img/debouncer.png +0 -0
- package/package.json +5 -3
- package/test/debouncer.spec.js +146 -0
- package/test/helpers.js +6 -0
- package/test/hysteresis.spec.js +87 -0
- package/examples/AlarmSystemUltimate.json +0 -286
|
@@ -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
|
+
};
|
|
@@ -104,9 +104,9 @@
|
|
|
104
104
|
<div class="form-row rate-config-section rate-config-debounce">
|
|
105
105
|
<label for="node-input-emitOn"><i class="fa fa-exchange"></i> Emit</label>
|
|
106
106
|
<select id="node-input-emitOn">
|
|
107
|
-
<option value="leading">
|
|
108
|
-
<option value="trailing">
|
|
109
|
-
<option value="both">
|
|
107
|
+
<option value="leading">Send first message immediately</option>
|
|
108
|
+
<option value="trailing">Send only the last message after the pause</option>
|
|
109
|
+
<option value="both">Send first immediately and last after the pause</option>
|
|
110
110
|
</select>
|
|
111
111
|
</div>
|
|
112
112
|
|
|
@@ -116,7 +116,7 @@
|
|
|
116
116
|
</div>
|
|
117
117
|
|
|
118
118
|
<div class="form-row rate-config-section rate-config-throttle">
|
|
119
|
-
<label for="node-input-trailing"><i class="fa fa-arrow-circle-right"></i>
|
|
119
|
+
<label for="node-input-trailing"><i class="fa fa-arrow-circle-right"></i> Also send the last queued message</label>
|
|
120
120
|
<input type="checkbox" id="node-input-trailing" style="width:auto; margin-top:7px;">
|
|
121
121
|
</div>
|
|
122
122
|
|
|
@@ -144,8 +144,8 @@ The purpose of this node is to moderate the frequency of incoming messages.
|
|
|
144
144
|
|
|
145
145
|
**Modes**
|
|
146
146
|
|
|
147
|
-
- **Debounce** – waits for the line to be quiet before forwarding
|
|
148
|
-
- **Throttle** – enforces a minimum interval between consecutive messages. When *
|
|
147
|
+
- **Debounce** – waits for the line to be quiet before forwarding a message. You can choose to send the first message immediately, only the last message after the pause, or both.
|
|
148
|
+
- **Throttle** – enforces a minimum interval between consecutive messages. When *Also send the last queued message* is enabled the most recent message is forwarded once the interval has elapsed.
|
|
149
149
|
- **Window** – limits the number of messages in a moving time window. Extra messages can be dropped or queued to play once a slot becomes available.
|
|
150
150
|
|
|
151
151
|
**Outputs**
|
|
@@ -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>
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "db_tab_1",
|
|
4
|
+
"type": "tab",
|
|
5
|
+
"label": "DebouncerUltimate - burst filter",
|
|
6
|
+
"disabled": false,
|
|
7
|
+
"info": "Esempio: filtra burst ravvicinati con il nodo DebouncerUltimate."
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"id": "db_cmt_1",
|
|
11
|
+
"type": "comment",
|
|
12
|
+
"z": "db_tab_1",
|
|
13
|
+
"name": "Burst → Debouncer → Forward",
|
|
14
|
+
"info": "Clicca BURST per generare messaggi ravvicinati. Cambia la modalità con LEADING/TRAILING/BOTH oppure usa FLUSH/RESET via topic \"debouncer\".",
|
|
15
|
+
"x": 300,
|
|
16
|
+
"y": 60,
|
|
17
|
+
"wires": []
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"id": "db_inj_burst",
|
|
21
|
+
"type": "inject",
|
|
22
|
+
"z": "db_tab_1",
|
|
23
|
+
"name": "BURST (10 msgs)",
|
|
24
|
+
"props": [
|
|
25
|
+
{ "p": "count", "v": "10", "vt": "num" },
|
|
26
|
+
{ "p": "interval", "v": "50", "vt": "num" }
|
|
27
|
+
],
|
|
28
|
+
"repeat": "",
|
|
29
|
+
"crontab": "",
|
|
30
|
+
"once": false,
|
|
31
|
+
"onceDelay": 0.1,
|
|
32
|
+
"x": 150,
|
|
33
|
+
"y": 140,
|
|
34
|
+
"wires": [["db_fn_burst"]]
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"id": "db_fn_burst",
|
|
38
|
+
"type": "function",
|
|
39
|
+
"z": "db_tab_1",
|
|
40
|
+
"name": "Burst generator",
|
|
41
|
+
"func": "const count = Number(msg.count || 10);\nconst interval = Number(msg.interval || 50);\nlet i = 0;\n\nconst sendOne = () => {\n node.send({ topic: 'data', payload: i, index: i, ts: Date.now() });\n i += 1;\n if (i < count) {\n setTimeout(sendOne, interval);\n }\n};\n\nsendOne();\nreturn null;",
|
|
42
|
+
"outputs": 1,
|
|
43
|
+
"noerr": 0,
|
|
44
|
+
"initialize": "",
|
|
45
|
+
"finalize": "",
|
|
46
|
+
"libs": [],
|
|
47
|
+
"x": 360,
|
|
48
|
+
"y": 140,
|
|
49
|
+
"wires": [["db_node_1"]]
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"id": "db_inj_leading",
|
|
53
|
+
"type": "inject",
|
|
54
|
+
"z": "db_tab_1",
|
|
55
|
+
"name": "LEADING (400ms)",
|
|
56
|
+
"props": [
|
|
57
|
+
{ "p": "topic", "v": "debouncer", "vt": "str" },
|
|
58
|
+
{ "p": "emitOn", "v": "leading", "vt": "str" },
|
|
59
|
+
{ "p": "wait", "v": "400", "vt": "num" }
|
|
60
|
+
],
|
|
61
|
+
"repeat": "",
|
|
62
|
+
"crontab": "",
|
|
63
|
+
"once": false,
|
|
64
|
+
"onceDelay": 0.1,
|
|
65
|
+
"x": 160,
|
|
66
|
+
"y": 220,
|
|
67
|
+
"wires": [["db_node_1"]]
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"id": "db_inj_trailing",
|
|
71
|
+
"type": "inject",
|
|
72
|
+
"z": "db_tab_1",
|
|
73
|
+
"name": "TRAILING (400ms)",
|
|
74
|
+
"props": [
|
|
75
|
+
{ "p": "topic", "v": "debouncer", "vt": "str" },
|
|
76
|
+
{ "p": "emitOn", "v": "trailing", "vt": "str" },
|
|
77
|
+
{ "p": "wait", "v": "400", "vt": "num" }
|
|
78
|
+
],
|
|
79
|
+
"repeat": "",
|
|
80
|
+
"crontab": "",
|
|
81
|
+
"once": false,
|
|
82
|
+
"onceDelay": 0.1,
|
|
83
|
+
"x": 170,
|
|
84
|
+
"y": 260,
|
|
85
|
+
"wires": [["db_node_1"]]
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"id": "db_inj_both",
|
|
89
|
+
"type": "inject",
|
|
90
|
+
"z": "db_tab_1",
|
|
91
|
+
"name": "BOTH (400ms)",
|
|
92
|
+
"props": [
|
|
93
|
+
{ "p": "topic", "v": "debouncer", "vt": "str" },
|
|
94
|
+
{ "p": "emitOn", "v": "both", "vt": "str" },
|
|
95
|
+
{ "p": "wait", "v": "400", "vt": "num" }
|
|
96
|
+
],
|
|
97
|
+
"repeat": "",
|
|
98
|
+
"crontab": "",
|
|
99
|
+
"once": false,
|
|
100
|
+
"onceDelay": 0.1,
|
|
101
|
+
"x": 150,
|
|
102
|
+
"y": 300,
|
|
103
|
+
"wires": [["db_node_1"]]
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"id": "db_inj_flush",
|
|
107
|
+
"type": "inject",
|
|
108
|
+
"z": "db_tab_1",
|
|
109
|
+
"name": "FLUSH",
|
|
110
|
+
"props": [
|
|
111
|
+
{ "p": "topic", "v": "debouncer", "vt": "str" },
|
|
112
|
+
{ "p": "flush", "v": "true", "vt": "bool" }
|
|
113
|
+
],
|
|
114
|
+
"repeat": "",
|
|
115
|
+
"crontab": "",
|
|
116
|
+
"once": false,
|
|
117
|
+
"onceDelay": 0.1,
|
|
118
|
+
"x": 120,
|
|
119
|
+
"y": 340,
|
|
120
|
+
"wires": [["db_node_1"]]
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"id": "db_inj_reset",
|
|
124
|
+
"type": "inject",
|
|
125
|
+
"z": "db_tab_1",
|
|
126
|
+
"name": "RESET",
|
|
127
|
+
"props": [
|
|
128
|
+
{ "p": "topic", "v": "debouncer", "vt": "str" },
|
|
129
|
+
{ "p": "reset", "v": "true", "vt": "bool" }
|
|
130
|
+
],
|
|
131
|
+
"repeat": "",
|
|
132
|
+
"crontab": "",
|
|
133
|
+
"once": false,
|
|
134
|
+
"onceDelay": 0.1,
|
|
135
|
+
"x": 120,
|
|
136
|
+
"y": 380,
|
|
137
|
+
"wires": [["db_node_1"]]
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
"id": "db_node_1",
|
|
141
|
+
"type": "DebouncerUltimate",
|
|
142
|
+
"z": "db_tab_1",
|
|
143
|
+
"name": "Debouncer",
|
|
144
|
+
"wait": 400,
|
|
145
|
+
"emitOn": "trailing",
|
|
146
|
+
"controlTopic": "debouncer",
|
|
147
|
+
"x": 590,
|
|
148
|
+
"y": 240,
|
|
149
|
+
"wires": [["db_dbg_fwd"]]
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
"id": "db_dbg_fwd",
|
|
153
|
+
"type": "debug",
|
|
154
|
+
"z": "db_tab_1",
|
|
155
|
+
"name": "Forward",
|
|
156
|
+
"active": true,
|
|
157
|
+
"tosidebar": true,
|
|
158
|
+
"console": false,
|
|
159
|
+
"tostatus": false,
|
|
160
|
+
"complete": "true",
|
|
161
|
+
"targetType": "full",
|
|
162
|
+
"statusVal": "",
|
|
163
|
+
"statusType": "auto",
|
|
164
|
+
"x": 780,
|
|
165
|
+
"y": 220,
|
|
166
|
+
"wires": []
|
|
167
|
+
}
|
|
168
|
+
]
|
|
@@ -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
|
+
]
|
package/examples/README.md
CHANGED
|
@@ -8,11 +8,12 @@ How to import:
|
|
|
8
8
|
|
|
9
9
|
## Available example flows
|
|
10
10
|
|
|
11
|
-
- `AlarmSystemUltimate.json` — Alarm System Ultimate (BETA)
|
|
12
11
|
- `BlinkerUltimate.json` — BlinkerUltimate
|
|
13
12
|
- `BooleanLogicUltimate.json` — BooleanLogicUltimate
|
|
14
13
|
- `Comparator.json` — Comparator
|
|
14
|
+
- `DebouncerUltimate.json` — DebouncerUltimate
|
|
15
15
|
- `FilterUltimate.json` — FilterUltimate
|
|
16
|
+
- `HysteresisUltimate.json` — HysteresisUltimate
|
|
16
17
|
- `ImpulseUltimate.json` — ImpulseUltimate
|
|
17
18
|
- `InjectUltimate.json` — InjectUltimate
|
|
18
19
|
- `InterruptFlowUltimate.json` — InterruptFlowUltimate
|
|
@@ -27,4 +28,3 @@ How to import:
|
|
|
27
28
|
- `SumUltimate.json` — SumUltimate
|
|
28
29
|
- `toggleUltimate.json` — toggleUltimate
|
|
29
30
|
- `translator-config.json` — translator-config
|
|
30
|
-
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-red-contrib-boolean-logic-ultimate",
|
|
3
|
-
"version": "1.2.
|
|
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.",
|
|
3
|
+
"version": "1.2.11",
|
|
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
5
|
"author": "Supergiovane (https://github.com/Supergiovane)",
|
|
6
6
|
"dependencies": {
|
|
7
7
|
"fs": "0.0.1-security",
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
},
|
|
29
29
|
"node-red": {
|
|
30
30
|
"nodes": {
|
|
31
|
+
"DebouncerUltimate": "boolean-logic-ultimate/DebouncerUltimate.js",
|
|
31
32
|
"BooleanLogicUltimate": "boolean-logic-ultimate/BooleanLogicUltimate.js",
|
|
32
33
|
"InvertUltimate": "boolean-logic-ultimate/InvertUltimate.js",
|
|
33
34
|
"FilterUltimate": "boolean-logic-ultimate/FilterUltimate.js",
|
|
@@ -45,10 +46,11 @@
|
|
|
45
46
|
"RateLimiterUltimate": "boolean-logic-ultimate/RateLimiterUltimate.js",
|
|
46
47
|
"PresenceSimulatorUltimate": "boolean-logic-ultimate/PresenceSimulatorUltimate.js",
|
|
47
48
|
"StaircaseLightUltimate": "boolean-logic-ultimate/StaircaseLightUltimate.js",
|
|
49
|
+
"HysteresisUltimate": "boolean-logic-ultimate/HysteresisUltimate.js",
|
|
48
50
|
"translator-config": "boolean-logic-ultimate/translator-config.js"
|
|
49
51
|
}
|
|
50
52
|
},
|
|
51
53
|
"scripts": {
|
|
52
54
|
"test": "mocha test/**/*.spec.js"
|
|
53
55
|
}
|
|
54
|
-
}
|
|
56
|
+
}
|