node-red-contrib-boolean-logic-ultimate 1.2.8 → 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.
- package/CHANGELOG.md +7 -0
- package/README.md +30 -57
- package/boolean-logic-ultimate/HysteresisUltimate.html +137 -0
- package/boolean-logic-ultimate/HysteresisUltimate.js +197 -0
- package/examples/HysteresisUltimate.json +109 -0
- package/examples/README.md +1 -2
- package/package.json +3 -2
- package/test/hysteresis.spec.js +87 -0
- package/examples/AlarmSystemUltimate.json +0 -286
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,13 @@
|
|
|
4
4
|
|
|
5
5
|
# CHANGELOG
|
|
6
6
|
|
|
7
|
+
<p>
|
|
8
|
+
<b>Version 1.2.9</b> April 2026<br/>
|
|
9
|
+
- Removed the following newly introduced HA nodes because considered too complex: ForDurationUltimate, WatchdogUltimate, PriorityMuxUltimate, GroupStateUltimate.<br/>
|
|
10
|
+
- Removed related examples, tests and documentation sections.<br/>
|
|
11
|
+
- Kept HysteresisUltimate and aligned package registration/examples docs accordingly.<br/>
|
|
12
|
+
</p>
|
|
13
|
+
|
|
7
14
|
<p>
|
|
8
15
|
<b>Version 1.2.8</b> January 2026<br/>
|
|
9
16
|
- Added youtube videos for the missing nodes and the relative links to the UI<br/>
|
package/README.md
CHANGED
|
@@ -720,63 +720,6 @@ Each event in the sequence outputs a message configured in the JSON line. When r
|
|
|
720
720
|
|
|
721
721
|
<br/>
|
|
722
722
|
|
|
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
723
|
# STAIRCASE LIGHT ULTIMATE
|
|
781
724
|
|
|
782
725
|
The purpose of this node is to control staircase lighting with a timer, pre-off warning and optional extension on every trigger.
|
|
@@ -807,6 +750,36 @@ Output 1 delivers the ON/OFF command. Output 2 delivers the warning and includes
|
|
|
807
750
|
|
|
808
751
|
<br/>
|
|
809
752
|
|
|
753
|
+
# HYSTERESIS ULTIMATE
|
|
754
|
+
|
|
755
|
+
Adds hysteresis to numeric sensor values to avoid rapid ON/OFF bouncing around thresholds.
|
|
756
|
+
|
|
757
|
+
Example flow: [`examples/HysteresisUltimate.json`](examples/HysteresisUltimate.json)
|
|
758
|
+
|
|
759
|
+
### NODE CONFIGURATION
|
|
760
|
+
|
|
761
|
+
| Property | Description |
|
|
762
|
+
| ------------------- | ------------------------------------------------------------------------------------------------------------ |
|
|
763
|
+
| Control topic | Topic that receives runtime commands such as threshold updates and reset. |
|
|
764
|
+
| Mode | `high` = ON above threshold, OFF below. `low` = ON below threshold, OFF above. |
|
|
765
|
+
| ON/OFF threshold | Hysteresis limits. |
|
|
766
|
+
| Initial state | Startup output state. |
|
|
767
|
+
| Emit only on change | If enabled, output 1 emits only on state transitions. |
|
|
768
|
+
| With Input | Message property evaluated as numeric value (default `payload`). |
|
|
769
|
+
| Translator | Optional translator-config. |
|
|
770
|
+
| On/Off payload | Typed payloads sent on output 1. |
|
|
771
|
+
|
|
772
|
+
### CONTROL MESSAGES (`msg.topic === controlTopic`)
|
|
773
|
+
|
|
774
|
+
- `msg.onThreshold`, `msg.offThreshold` → update thresholds at runtime.
|
|
775
|
+
- `msg.state = true|false` → force state.
|
|
776
|
+
- `msg.reset = true` → restore initial state.
|
|
777
|
+
- `msg.status = true` → emit current state/thresholds on output 2.
|
|
778
|
+
|
|
779
|
+
Output 1 emits the command payload (`onPayload`/`offPayload`). Output 2 emits diagnostic events.
|
|
780
|
+
|
|
781
|
+
<br/>
|
|
782
|
+
|
|
810
783
|
[license-image]: https://img.shields.io/badge/license-MIT-blue.svg
|
|
811
784
|
[license-url]: https://github.com/Supergiovane/node-red-contrib-boolean-logic-ultimate/master/LICENSE
|
|
812
785
|
[npm-url]: https://npmjs.org/package/node-red-contrib-boolean-logic-ultimate
|
|
@@ -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
|
+
<span style="color:red"><i class="fa fa-question-circle"></i> <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
|
+
};
|
|
@@ -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,11 @@ 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
|
|
15
14
|
- `FilterUltimate.json` — FilterUltimate
|
|
15
|
+
- `HysteresisUltimate.json` — HysteresisUltimate
|
|
16
16
|
- `ImpulseUltimate.json` — ImpulseUltimate
|
|
17
17
|
- `InjectUltimate.json` — InjectUltimate
|
|
18
18
|
- `InterruptFlowUltimate.json` — InterruptFlowUltimate
|
|
@@ -27,4 +27,3 @@ How to import:
|
|
|
27
27
|
- `SumUltimate.json` — SumUltimate
|
|
28
28
|
- `toggleUltimate.json` — toggleUltimate
|
|
29
29
|
- `translator-config.json` — translator-config
|
|
30
|
-
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-red-contrib-boolean-logic-ultimate",
|
|
3
|
-
"version": "1.2.
|
|
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
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { expect } = require('chai');
|
|
4
|
+
const { helper } = require('./helpers');
|
|
5
|
+
|
|
6
|
+
const hysteresisNode = require('../boolean-logic-ultimate/HysteresisUltimate.js');
|
|
7
|
+
|
|
8
|
+
function loadHysteresis(flow, credentials) {
|
|
9
|
+
const normalizedFlow = flow.map((node, index) => {
|
|
10
|
+
if (
|
|
11
|
+
node &&
|
|
12
|
+
node.type &&
|
|
13
|
+
node.type !== 'tab' &&
|
|
14
|
+
node.type !== 'subflow' &&
|
|
15
|
+
node.type !== 'group' &&
|
|
16
|
+
node.z &&
|
|
17
|
+
!(Object.prototype.hasOwnProperty.call(node, 'x') && Object.prototype.hasOwnProperty.call(node, 'y'))
|
|
18
|
+
) {
|
|
19
|
+
return { ...node, x: 100 + index * 10, y: 100 + index * 10 };
|
|
20
|
+
}
|
|
21
|
+
return node;
|
|
22
|
+
});
|
|
23
|
+
return helper.load(hysteresisNode, normalizedFlow, credentials || {});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
describe('HysteresisUltimate node', function () {
|
|
27
|
+
this.timeout(5000);
|
|
28
|
+
|
|
29
|
+
before(function (done) {
|
|
30
|
+
helper.startServer(done);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
after(function (done) {
|
|
34
|
+
helper.stopServer(done);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
afterEach(function () {
|
|
38
|
+
return helper.unload();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('emits on state transitions only', function (done) {
|
|
42
|
+
const flowId = 'hys1';
|
|
43
|
+
const flow = [
|
|
44
|
+
{ id: flowId, type: 'tab', label: 'hys1' },
|
|
45
|
+
{
|
|
46
|
+
id: 'hys',
|
|
47
|
+
type: 'HysteresisUltimate',
|
|
48
|
+
z: flowId,
|
|
49
|
+
mode: 'high',
|
|
50
|
+
onThreshold: 70,
|
|
51
|
+
offThreshold: 60,
|
|
52
|
+
emitOnlyOnChange: true,
|
|
53
|
+
onPayload: true,
|
|
54
|
+
onPayloadType: 'bool',
|
|
55
|
+
offPayload: false,
|
|
56
|
+
offPayloadType: 'bool',
|
|
57
|
+
wires: [['out'], ['diag']],
|
|
58
|
+
},
|
|
59
|
+
{ id: 'in', type: 'helper', z: flowId, wires: [['hys']] },
|
|
60
|
+
{ id: 'out', type: 'helper', z: flowId },
|
|
61
|
+
{ id: 'diag', type: 'helper', z: flowId },
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
loadHysteresis(flow).then(() => {
|
|
65
|
+
const hys = helper.getNode('hys');
|
|
66
|
+
const out = helper.getNode('out');
|
|
67
|
+
const results = [];
|
|
68
|
+
|
|
69
|
+
out.on('input', (msg) => {
|
|
70
|
+
results.push(msg.payload);
|
|
71
|
+
if (results.length === 2) {
|
|
72
|
+
try {
|
|
73
|
+
expect(results).to.deep.equal([true, false]);
|
|
74
|
+
done();
|
|
75
|
+
} catch (error) {
|
|
76
|
+
done(error);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
hys.receive({ topic: 'sensor', payload: 65 });
|
|
82
|
+
hys.receive({ topic: 'sensor', payload: 72 });
|
|
83
|
+
hys.receive({ topic: 'sensor', payload: 68 });
|
|
84
|
+
hys.receive({ topic: 'sensor', payload: 58 });
|
|
85
|
+
}).catch(done);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
@@ -1,286 +0,0 @@
|
|
|
1
|
-
[
|
|
2
|
-
{
|
|
3
|
-
"id": "al_tab_1",
|
|
4
|
-
"type": "tab",
|
|
5
|
-
"label": "AlarmSystemUltimate - demo base",
|
|
6
|
-
"disabled": false,
|
|
7
|
-
"info": "Esempio per il nodo ALARM (Alarm System Ultimate - BETA): armo/disarmo, bypass zone, trigger sensori, eventi e sirena."
|
|
8
|
-
},
|
|
9
|
-
{
|
|
10
|
-
"id": "al_cmt_1",
|
|
11
|
-
"type": "comment",
|
|
12
|
-
"z": "al_tab_1",
|
|
13
|
-
"name": "Topic di controllo = alarm | Topic sirena = siren",
|
|
14
|
-
"info": "Comandi principali (topic=alarm): arm_away/arm_home/arm_night, disarm, status, bypass/unbypass (con msg.zone). I sensori entrano con msg.topic uguale a quello configurato nelle zone e payload booleano true/false.",
|
|
15
|
-
"x": 260,
|
|
16
|
-
"y": 60,
|
|
17
|
-
"wires": []
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
"id": "al_inj_arm_away",
|
|
21
|
-
"type": "inject",
|
|
22
|
-
"z": "al_tab_1",
|
|
23
|
-
"name": "ARM AWAY (code=1234)",
|
|
24
|
-
"props": [
|
|
25
|
-
{ "p": "topic", "v": "alarm", "vt": "str" },
|
|
26
|
-
{ "p": "command", "v": "arm_away", "vt": "str" },
|
|
27
|
-
{ "p": "code", "v": "1234", "vt": "str" }
|
|
28
|
-
],
|
|
29
|
-
"repeat": "",
|
|
30
|
-
"crontab": "",
|
|
31
|
-
"once": false,
|
|
32
|
-
"onceDelay": 0.1,
|
|
33
|
-
"x": 160,
|
|
34
|
-
"y": 140,
|
|
35
|
-
"wires": [["al_alarm_1"]]
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
"id": "al_inj_arm_home",
|
|
39
|
-
"type": "inject",
|
|
40
|
-
"z": "al_tab_1",
|
|
41
|
-
"name": "ARM HOME (code=1234)",
|
|
42
|
-
"props": [
|
|
43
|
-
{ "p": "topic", "v": "alarm", "vt": "str" },
|
|
44
|
-
{ "p": "command", "v": "arm_home", "vt": "str" },
|
|
45
|
-
{ "p": "code", "v": "1234", "vt": "str" }
|
|
46
|
-
],
|
|
47
|
-
"repeat": "",
|
|
48
|
-
"crontab": "",
|
|
49
|
-
"once": false,
|
|
50
|
-
"onceDelay": 0.1,
|
|
51
|
-
"x": 160,
|
|
52
|
-
"y": 180,
|
|
53
|
-
"wires": [["al_alarm_1"]]
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
"id": "al_inj_disarm",
|
|
57
|
-
"type": "inject",
|
|
58
|
-
"z": "al_tab_1",
|
|
59
|
-
"name": "DISARM (code=1234)",
|
|
60
|
-
"props": [
|
|
61
|
-
{ "p": "topic", "v": "alarm", "vt": "str" },
|
|
62
|
-
{ "p": "command", "v": "disarm", "vt": "str" },
|
|
63
|
-
{ "p": "code", "v": "1234", "vt": "str" }
|
|
64
|
-
],
|
|
65
|
-
"repeat": "",
|
|
66
|
-
"crontab": "",
|
|
67
|
-
"once": false,
|
|
68
|
-
"onceDelay": 0.1,
|
|
69
|
-
"x": 160,
|
|
70
|
-
"y": 220,
|
|
71
|
-
"wires": [["al_alarm_1"]]
|
|
72
|
-
},
|
|
73
|
-
{
|
|
74
|
-
"id": "al_inj_status",
|
|
75
|
-
"type": "inject",
|
|
76
|
-
"z": "al_tab_1",
|
|
77
|
-
"name": "STATUS",
|
|
78
|
-
"props": [
|
|
79
|
-
{ "p": "topic", "v": "alarm", "vt": "str" },
|
|
80
|
-
{ "p": "command", "v": "status", "vt": "str" }
|
|
81
|
-
],
|
|
82
|
-
"repeat": "",
|
|
83
|
-
"crontab": "",
|
|
84
|
-
"once": false,
|
|
85
|
-
"onceDelay": 0.1,
|
|
86
|
-
"x": 110,
|
|
87
|
-
"y": 260,
|
|
88
|
-
"wires": [["al_alarm_1"]]
|
|
89
|
-
},
|
|
90
|
-
{
|
|
91
|
-
"id": "al_inj_bypass_front",
|
|
92
|
-
"type": "inject",
|
|
93
|
-
"z": "al_tab_1",
|
|
94
|
-
"name": "BYPASS front_door",
|
|
95
|
-
"props": [
|
|
96
|
-
{ "p": "topic", "v": "alarm", "vt": "str" },
|
|
97
|
-
{ "p": "command", "v": "bypass", "vt": "str" },
|
|
98
|
-
{ "p": "zone", "v": "front_door", "vt": "str" }
|
|
99
|
-
],
|
|
100
|
-
"repeat": "",
|
|
101
|
-
"crontab": "",
|
|
102
|
-
"once": false,
|
|
103
|
-
"onceDelay": 0.1,
|
|
104
|
-
"x": 140,
|
|
105
|
-
"y": 320,
|
|
106
|
-
"wires": [["al_alarm_1"]]
|
|
107
|
-
},
|
|
108
|
-
{
|
|
109
|
-
"id": "al_inj_unbypass_front",
|
|
110
|
-
"type": "inject",
|
|
111
|
-
"z": "al_tab_1",
|
|
112
|
-
"name": "UNBYPASS front_door",
|
|
113
|
-
"props": [
|
|
114
|
-
{ "p": "topic", "v": "alarm", "vt": "str" },
|
|
115
|
-
{ "p": "command", "v": "unbypass", "vt": "str" },
|
|
116
|
-
{ "p": "zone", "v": "front_door", "vt": "str" }
|
|
117
|
-
],
|
|
118
|
-
"repeat": "",
|
|
119
|
-
"crontab": "",
|
|
120
|
-
"once": false,
|
|
121
|
-
"onceDelay": 0.1,
|
|
122
|
-
"x": 150,
|
|
123
|
-
"y": 360,
|
|
124
|
-
"wires": [["al_alarm_1"]]
|
|
125
|
-
},
|
|
126
|
-
{
|
|
127
|
-
"id": "al_cmt_2",
|
|
128
|
-
"type": "comment",
|
|
129
|
-
"z": "al_tab_1",
|
|
130
|
-
"name": "Sensori (topic = zona) | payload true = trigger | payload false = restore",
|
|
131
|
-
"info": "La zona front_door è con entry delay e chime quando disarmata. pir_living è attiva solo in away. smoke è fire 24/7 (sempre attiva).",
|
|
132
|
-
"x": 320,
|
|
133
|
-
"y": 420,
|
|
134
|
-
"wires": []
|
|
135
|
-
},
|
|
136
|
-
{
|
|
137
|
-
"id": "al_inj_front_open",
|
|
138
|
-
"type": "inject",
|
|
139
|
-
"z": "al_tab_1",
|
|
140
|
-
"name": "Front door OPEN (true)",
|
|
141
|
-
"props": [
|
|
142
|
-
{ "p": "topic", "v": "sensor/frontdoor", "vt": "str" },
|
|
143
|
-
{ "p": "payload", "v": "true", "vt": "bool" }
|
|
144
|
-
],
|
|
145
|
-
"repeat": "",
|
|
146
|
-
"crontab": "",
|
|
147
|
-
"once": false,
|
|
148
|
-
"onceDelay": 0.1,
|
|
149
|
-
"x": 170,
|
|
150
|
-
"y": 500,
|
|
151
|
-
"wires": [["al_alarm_1"]]
|
|
152
|
-
},
|
|
153
|
-
{
|
|
154
|
-
"id": "al_inj_front_close",
|
|
155
|
-
"type": "inject",
|
|
156
|
-
"z": "al_tab_1",
|
|
157
|
-
"name": "Front door CLOSE (false)",
|
|
158
|
-
"props": [
|
|
159
|
-
{ "p": "topic", "v": "sensor/frontdoor", "vt": "str" },
|
|
160
|
-
{ "p": "payload", "v": "false", "vt": "bool" }
|
|
161
|
-
],
|
|
162
|
-
"repeat": "",
|
|
163
|
-
"crontab": "",
|
|
164
|
-
"once": false,
|
|
165
|
-
"onceDelay": 0.1,
|
|
166
|
-
"x": 180,
|
|
167
|
-
"y": 540,
|
|
168
|
-
"wires": [["al_alarm_1"]]
|
|
169
|
-
},
|
|
170
|
-
{
|
|
171
|
-
"id": "al_inj_pir",
|
|
172
|
-
"type": "inject",
|
|
173
|
-
"z": "al_tab_1",
|
|
174
|
-
"name": "PIR living (true)",
|
|
175
|
-
"props": [
|
|
176
|
-
{ "p": "topic", "v": "sensor/living_pir", "vt": "str" },
|
|
177
|
-
{ "p": "payload", "v": "true", "vt": "bool" }
|
|
178
|
-
],
|
|
179
|
-
"repeat": "",
|
|
180
|
-
"crontab": "",
|
|
181
|
-
"once": false,
|
|
182
|
-
"onceDelay": 0.1,
|
|
183
|
-
"x": 150,
|
|
184
|
-
"y": 600,
|
|
185
|
-
"wires": [["al_alarm_1"]]
|
|
186
|
-
},
|
|
187
|
-
{
|
|
188
|
-
"id": "al_inj_smoke",
|
|
189
|
-
"type": "inject",
|
|
190
|
-
"z": "al_tab_1",
|
|
191
|
-
"name": "SMOKE (true)",
|
|
192
|
-
"props": [
|
|
193
|
-
{ "p": "topic", "v": "sensor/smoke", "vt": "str" },
|
|
194
|
-
{ "p": "payload", "v": "true", "vt": "bool" }
|
|
195
|
-
],
|
|
196
|
-
"repeat": "",
|
|
197
|
-
"crontab": "",
|
|
198
|
-
"once": false,
|
|
199
|
-
"onceDelay": 0.1,
|
|
200
|
-
"x": 130,
|
|
201
|
-
"y": 640,
|
|
202
|
-
"wires": [["al_alarm_1"]]
|
|
203
|
-
},
|
|
204
|
-
{
|
|
205
|
-
"id": "al_alarm_1",
|
|
206
|
-
"type": "AlarmSystemUltimate",
|
|
207
|
-
"z": "al_tab_1",
|
|
208
|
-
"name": "ALARM",
|
|
209
|
-
"controlTopic": "alarm",
|
|
210
|
-
"payloadPropName": "payload",
|
|
211
|
-
"translatorConfig": "",
|
|
212
|
-
"persistState": true,
|
|
213
|
-
"requireCodeForArm": true,
|
|
214
|
-
"requireCodeForDisarm": true,
|
|
215
|
-
"armCode": "1234",
|
|
216
|
-
"duressCode": "9999",
|
|
217
|
-
"blockArmOnViolations": true,
|
|
218
|
-
"exitDelaySeconds": 10,
|
|
219
|
-
"entryDelaySeconds": 5,
|
|
220
|
-
"sirenDurationSeconds": 15,
|
|
221
|
-
"sirenLatchUntilDisarm": false,
|
|
222
|
-
"sirenTopic": "siren",
|
|
223
|
-
"sirenOnPayload": true,
|
|
224
|
-
"sirenOnPayloadType": "bool",
|
|
225
|
-
"sirenOffPayload": false,
|
|
226
|
-
"sirenOffPayloadType": "bool",
|
|
227
|
-
"emitRestoreEvents": true,
|
|
228
|
-
"maxLogEntries": 50,
|
|
229
|
-
"zones": "{\"id\":\"front_door\",\"name\":\"Porta ingresso\",\"topic\":\"sensor/frontdoor\",\"type\":\"perimeter\",\"entry\":true,\"modes\":[\"away\",\"home\",\"night\"],\"bypassable\":true,\"chime\":true}\n{\"id\":\"pir_living\",\"name\":\"PIR soggiorno\",\"topic\":\"sensor/living_pir\",\"type\":\"motion\",\"modes\":[\"away\"],\"entry\":false,\"bypassable\":true,\"cooldownSeconds\":5}\n{\"id\":\"smoke\",\"name\":\"Fumo\",\"topic\":\"sensor/smoke\",\"type\":\"fire\",\"modes\":[],\"entry\":false,\"bypassable\":false}",
|
|
230
|
-
"x": 450,
|
|
231
|
-
"y": 280,
|
|
232
|
-
"wires": [["al_dbg_evt"], ["al_dbg_siren"]]
|
|
233
|
-
},
|
|
234
|
-
{
|
|
235
|
-
"id": "al_dbg_evt",
|
|
236
|
-
"type": "debug",
|
|
237
|
-
"z": "al_tab_1",
|
|
238
|
-
"name": "Events (output 1)",
|
|
239
|
-
"active": true,
|
|
240
|
-
"tosidebar": true,
|
|
241
|
-
"console": false,
|
|
242
|
-
"tostatus": false,
|
|
243
|
-
"complete": "true",
|
|
244
|
-
"targetType": "full",
|
|
245
|
-
"statusVal": "",
|
|
246
|
-
"statusType": "auto",
|
|
247
|
-
"x": 680,
|
|
248
|
-
"y": 260,
|
|
249
|
-
"wires": []
|
|
250
|
-
},
|
|
251
|
-
{
|
|
252
|
-
"id": "al_dbg_siren",
|
|
253
|
-
"type": "debug",
|
|
254
|
-
"z": "al_tab_1",
|
|
255
|
-
"name": "Siren (output 2)",
|
|
256
|
-
"active": true,
|
|
257
|
-
"tosidebar": true,
|
|
258
|
-
"console": false,
|
|
259
|
-
"tostatus": false,
|
|
260
|
-
"complete": "true",
|
|
261
|
-
"targetType": "full",
|
|
262
|
-
"statusVal": "",
|
|
263
|
-
"statusType": "auto",
|
|
264
|
-
"x": 680,
|
|
265
|
-
"y": 300,
|
|
266
|
-
"wires": []
|
|
267
|
-
},
|
|
268
|
-
{
|
|
269
|
-
"id": "al_inj_disarm_duress",
|
|
270
|
-
"type": "inject",
|
|
271
|
-
"z": "al_tab_1",
|
|
272
|
-
"name": "DISARM (DURESS 9999)",
|
|
273
|
-
"props": [
|
|
274
|
-
{ "p": "topic", "v": "alarm", "vt": "str" },
|
|
275
|
-
{ "p": "command", "v": "disarm", "vt": "str" },
|
|
276
|
-
{ "p": "code", "v": "9999", "vt": "str" }
|
|
277
|
-
],
|
|
278
|
-
"repeat": "",
|
|
279
|
-
"crontab": "",
|
|
280
|
-
"once": false,
|
|
281
|
-
"onceDelay": 0.1,
|
|
282
|
-
"x": 160,
|
|
283
|
-
"y": 400,
|
|
284
|
-
"wires": [["al_alarm_1"]]
|
|
285
|
-
}
|
|
286
|
-
]
|