smart-nodes 0.1.0
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/LICENSE +21 -0
- package/LICENSE.md +21 -0
- package/README.md +127 -0
- package/central/central.html +328 -0
- package/central/central.js +95 -0
- package/compare/compare.html +137 -0
- package/compare/compare.js +151 -0
- package/delay/delay.html +192 -0
- package/delay/delay.js +175 -0
- package/examples/central.json +804 -0
- package/examples/central.png +0 -0
- package/examples/compare.json +916 -0
- package/examples/compare.png +0 -0
- package/examples/delay.json +198 -0
- package/examples/delay.png +0 -0
- package/examples/forwarder.json +152 -0
- package/examples/forwarder.png +0 -0
- package/examples/hysteresis.json +358 -0
- package/examples/hysteresis.png +0 -0
- package/examples/light-control.json +499 -0
- package/examples/light-control.png +0 -0
- package/examples/logic.json +562 -0
- package/examples/logic.png +0 -0
- package/examples/long-press-control.json +113 -0
- package/examples/long-press-control.png +0 -0
- package/examples/multi-press-control.json +136 -0
- package/examples/multi-press-control.png +0 -0
- package/examples/scene-control.json +535 -0
- package/examples/scene-control.png +0 -0
- package/examples/scheduler.json +164 -0
- package/examples/scheduler.png +0 -0
- package/examples/shutter-complex-control.json +489 -0
- package/examples/shutter-complex-control.png +0 -0
- package/examples/shutter-control.json +457 -0
- package/examples/shutter-control.png +0 -0
- package/examples/statistic.json +1112 -0
- package/examples/statistic.png +0 -0
- package/forwarder/forwarder.html +100 -0
- package/forwarder/forwarder.js +95 -0
- package/hysteresis/hysteresis.html +152 -0
- package/hysteresis/hysteresis.js +146 -0
- package/light-control/light-control.html +358 -0
- package/light-control/light-control.js +231 -0
- package/logic/logic.html +168 -0
- package/logic/logic.js +171 -0
- package/long-press-control/long-press-control.html +74 -0
- package/long-press-control/long-press-control.js +75 -0
- package/multi-press-control/multi-press-control.html +135 -0
- package/multi-press-control/multi-press-control.js +68 -0
- package/package.json +59 -0
- package/persistence.js +74 -0
- package/scene-control/scene-control.html +575 -0
- package/scene-control/scene-control.js +265 -0
- package/scheduler/scheduler.html +338 -0
- package/scheduler/scheduler.js +209 -0
- package/shutter-complex-control/shutter-complex-control.html +330 -0
- package/shutter-complex-control/shutter-complex-control.js +399 -0
- package/shutter-control/shutter-control.html +283 -0
- package/shutter-control/shutter-control.js +208 -0
- package/smart_helper.js +156 -0
- package/statistic/statistic.html +107 -0
- package/statistic/statistic.js +196 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 BergenSoft
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Samuel Bergen
|
|
4
|
+
|
|
5
|
+
> Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
> of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
> in the Software without restriction, including without limitation the rights
|
|
8
|
+
> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
> copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
> furnished to do so, subject to the following conditions:
|
|
11
|
+
>
|
|
12
|
+
> The above copyright notice and this permission notice shall be included in
|
|
13
|
+
> all copies or substantial portions of the Software.
|
|
14
|
+
>
|
|
15
|
+
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
> THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# General information
|
|
2
|
+
|
|
3
|
+
The smart nodes was created to control smart home devices like lights, power outlets shutter and some more.
|
|
4
|
+
This controls are designed to work with the [node-red-contrib-knx-ultimate](https://github.com/Supergiovane/node-red-contrib-knx-ultimate) but it could also work with other smart technologies.
|
|
5
|
+
|
|
6
|
+
Sometimes one source node should be connected to multiple smart nodes which requires different topics.
|
|
7
|
+
To avoid requiring many change nodes, the smart node are using a special `msg.topic` notation. You can always send topics in the format `name#number`, e.g. `toggle#1`.
|
|
8
|
+
Smart nodes that requires a name are using only the name part. the # and the number are optional. Smart nodes that requries a number will only use the number, the name and # is also optional.
|
|
9
|
+
|
|
10
|
+
This is usefull when one node provide information to e.g. a light control node, as well as to a logic node.
|
|
11
|
+
|
|
12
|
+
This file only describes the general function of the node. See the documentation shown in NodeRed to find out how to use them, or see the included example flows.
|
|
13
|
+
As I'm german, the internal documentation is only available in german. If you need another language, feel free to add localizations and adding more languages via a pull request.
|
|
14
|
+
|
|
15
|
+
# Nodes
|
|
16
|
+
## 1. Light control
|
|
17
|
+
This node is able to control a light or a power outlet.
|
|
18
|
+
|
|
19
|
+
### **Features:**
|
|
20
|
+
* Auto turn off the light after a fixed time.
|
|
21
|
+
* Auto turn off the light with a custom time which is part of the message object.
|
|
22
|
+
* Toggle light between on and off.
|
|
23
|
+
* Can be triggered by motion sensors.
|
|
24
|
+
* Has an alarm function to go to a specific state (on or off) when the alarm is activated.
|
|
25
|
+
|
|
26
|
+
## 2. Shutter control
|
|
27
|
+
This node is able to control a shutter.
|
|
28
|
+
There is no support for slats and it is also not planned as I don't need them, but feel free to send a pull request.
|
|
29
|
+
|
|
30
|
+
### **Features:**
|
|
31
|
+
* One button control which switch between `up`, `stop`, `down`, `stop`.
|
|
32
|
+
* One button control for each direction `up and stop` or `down and stop`.
|
|
33
|
+
* Send direct position the shutter should be.
|
|
34
|
+
* Stop shutter immediately.
|
|
35
|
+
* Has an alarm function to go to a specific state (Open or close) when the alarm is activated.
|
|
36
|
+
|
|
37
|
+
## 3. Scene control
|
|
38
|
+
This node is tracking the state of multiple outputs and controls them by switching to multiple defined scenes.
|
|
39
|
+
|
|
40
|
+
### **Features:**
|
|
41
|
+
* An input message could name multiple scenes that should be iterated one by one by receiving the same message multiple times
|
|
42
|
+
|
|
43
|
+
## 4. Central control
|
|
44
|
+
This node can control multiple light/scene controls or shutter controls at the same time
|
|
45
|
+
|
|
46
|
+
### **Features:**
|
|
47
|
+
* Take care that multiple light/scene controls are turned of if any of them is one before executing a toggle command. This is helpful to avoid turning one light on and the other off.
|
|
48
|
+
* The same is used for shutters with the up_stop or down_stop commands. A toggle is not supported, it will only be forwarded.
|
|
49
|
+
|
|
50
|
+
## 5. Long press control
|
|
51
|
+
This control is used to detect a short or a long press.
|
|
52
|
+
The time can be configured in this control.
|
|
53
|
+
|
|
54
|
+
### **Features:**
|
|
55
|
+
* Imediately send a message to the long press output when the time is reached. It is not waiting until the button is released.
|
|
56
|
+
|
|
57
|
+
## 6. Multi press control
|
|
58
|
+
This control is used to detect multiple presses on a button.
|
|
59
|
+
The max wait time between presses can be configured in this control.
|
|
60
|
+
You can also choose 2-4 press detection.
|
|
61
|
+
|
|
62
|
+
### **Features:**
|
|
63
|
+
* Imediately send a message to the last output when the max presses are reached. It is not waiting until the button is released.
|
|
64
|
+
|
|
65
|
+
## 7. Hysteresis
|
|
66
|
+
This control is checking if the input value reachs a defined value until the upper message is send. When the lower level is reached, the lower masssage will be send
|
|
67
|
+
|
|
68
|
+
### **Features:**
|
|
69
|
+
* The state can be saved between NodeRed restarts.
|
|
70
|
+
* The last message can automatically be sent 10 seconds after a deployment.
|
|
71
|
+
|
|
72
|
+
## 8. Logic
|
|
73
|
+
This control can be used for AND, OR and XOR logics.
|
|
74
|
+
|
|
75
|
+
### **Features:**
|
|
76
|
+
* All input values could be individual converted as well as the output messsage.
|
|
77
|
+
* A NOT logic is possible when selecting 1 input and convert the output.
|
|
78
|
+
* The setpoint and hysteresis value can be changed in runtime.
|
|
79
|
+
* The state can be saved between NodeRed restarts.
|
|
80
|
+
* The last message can automatically be sent 10 seconds after a deployment.
|
|
81
|
+
|
|
82
|
+
## 9. Statistic
|
|
83
|
+
This control can be used for getting the following types of values: Minimum, Maximum, Sum, Difference, Absolute Value, Absolute Difference, Average and Running average.
|
|
84
|
+
|
|
85
|
+
### **Features:**
|
|
86
|
+
* All input values could be individual converted as well as the output messsage.
|
|
87
|
+
* A NOT logic is possible when selecting 1 input and convert the output.
|
|
88
|
+
* The state can be saved between NodeRed restarts.
|
|
89
|
+
* The last message can automatically be sent 10 seconds after a deployment.
|
|
90
|
+
|
|
91
|
+
## 10. Compare
|
|
92
|
+
This control can compare 2 values with the following operators: < <= == >= > !=
|
|
93
|
+
|
|
94
|
+
### **Features:**
|
|
95
|
+
* Can compare also texts like js would do.
|
|
96
|
+
* The state can be saved between NodeRed restarts.
|
|
97
|
+
* The last message can automatically be sent 10 seconds after a deployment.
|
|
98
|
+
|
|
99
|
+
## 11. Delay
|
|
100
|
+
This control can delay incomming messages.
|
|
101
|
+
|
|
102
|
+
### **Features:**
|
|
103
|
+
* Different times for `msg.payload = true` and `msg.payload = false`.
|
|
104
|
+
* Delays can be changed in runtime.
|
|
105
|
+
* The state can be saved between NodeRed restarts.
|
|
106
|
+
* The last message can automatically be sent 10 seconds after a deployment.
|
|
107
|
+
|
|
108
|
+
## 12. Forwarder
|
|
109
|
+
This control can control if an incomming message should be forwarded or not.
|
|
110
|
+
|
|
111
|
+
### **Features:**
|
|
112
|
+
* The forwarding can be enabled or disabled in runtime.
|
|
113
|
+
* The state can be saved between NodeRed restarts.
|
|
114
|
+
* The last message can automatically be sent 10 seconds after a deployment.
|
|
115
|
+
|
|
116
|
+
## 13. Scheduler
|
|
117
|
+
This control can send a defined message on defined times.
|
|
118
|
+
|
|
119
|
+
### **Features:**
|
|
120
|
+
* The weekdays and the time can be selected as a trigger.
|
|
121
|
+
* Multiple trigger and messages can be defined in one node.
|
|
122
|
+
* The scheduler can be activated or deactivated in runtime.
|
|
123
|
+
* The state can be saved between NodeRed restarts.
|
|
124
|
+
* The last message can automatically be sent 10 seconds after a deployment.
|
|
125
|
+
|
|
126
|
+
# Create node package
|
|
127
|
+
run `node pack` to create a new *.tgz node module.
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
<script type="text/javascript">
|
|
2
|
+
(function ()
|
|
3
|
+
{
|
|
4
|
+
let treeList; // The tree control itself
|
|
5
|
+
let candidateNodesCount = 0; // The total entries shown in the list, used for the search box as info
|
|
6
|
+
let flows = []; // A List with all flow maps
|
|
7
|
+
let flowMap = {}; // A key value list with all flow id => flow details and children
|
|
8
|
+
|
|
9
|
+
function onEditPrepare(node, targetTypes)
|
|
10
|
+
{
|
|
11
|
+
if (!node.links)
|
|
12
|
+
node.links = [];
|
|
13
|
+
|
|
14
|
+
node.oldLinks = [];
|
|
15
|
+
|
|
16
|
+
const activeSubflow = RED.nodes.subflow(node.z);
|
|
17
|
+
|
|
18
|
+
// init tree list control
|
|
19
|
+
treeList = $("<div>")
|
|
20
|
+
.css({ width: "100%", height: "100%" })
|
|
21
|
+
.appendTo(".node-input-link-row")
|
|
22
|
+
.treeList({ autoSelect: false })
|
|
23
|
+
.on("treelistitemmouseover", function (e, item)
|
|
24
|
+
{
|
|
25
|
+
if (item.node)
|
|
26
|
+
{
|
|
27
|
+
item.node.highlighted = true;
|
|
28
|
+
item.node.dirty = true;
|
|
29
|
+
RED.view.redraw();
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
.on("treelistitemmouseout", function (e, item)
|
|
33
|
+
{
|
|
34
|
+
if (item.node)
|
|
35
|
+
{
|
|
36
|
+
item.node.highlighted = false;
|
|
37
|
+
item.node.dirty = true;
|
|
38
|
+
RED.view.redraw();
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// init search box
|
|
43
|
+
const search = $("#node-input-link-target-filter").searchBox({
|
|
44
|
+
style: "compact",
|
|
45
|
+
delay: 300,
|
|
46
|
+
change: function ()
|
|
47
|
+
{
|
|
48
|
+
var val = $(this).val().trim().toLowerCase();
|
|
49
|
+
if (val === "")
|
|
50
|
+
{
|
|
51
|
+
treeList.treeList("filter", null);
|
|
52
|
+
search.searchBox("count", "");
|
|
53
|
+
}
|
|
54
|
+
else
|
|
55
|
+
{
|
|
56
|
+
const count = treeList.treeList("filter", function (item)
|
|
57
|
+
{
|
|
58
|
+
return item.label.toLowerCase().indexOf(val) > -1 || (item.parent && item.parent.label.toLowerCase().indexOf(val) > -1) || (item.node && item.node.type.toLowerCase().indexOf(val) > -1);
|
|
59
|
+
});
|
|
60
|
+
search.searchBox("count", count + " / " + candidateNodesCount);
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// clear shown tree data
|
|
66
|
+
flows = [];
|
|
67
|
+
flowMap = {};
|
|
68
|
+
|
|
69
|
+
// Add all existing flows
|
|
70
|
+
if (activeSubflow)
|
|
71
|
+
{
|
|
72
|
+
flowMap[activeSubflow.id] = {
|
|
73
|
+
id: activeSubflow.id,
|
|
74
|
+
class: "red-ui-palette-header",
|
|
75
|
+
label: "Subflow : " + (activeSubflow.name || activeSubflow.id),
|
|
76
|
+
expanded: true,
|
|
77
|
+
children: [],
|
|
78
|
+
};
|
|
79
|
+
flows.push(flowMap[activeSubflow.id]);
|
|
80
|
+
}
|
|
81
|
+
else
|
|
82
|
+
{
|
|
83
|
+
RED.nodes.eachWorkspace(function (ws)
|
|
84
|
+
{
|
|
85
|
+
if (!ws.disabled)
|
|
86
|
+
{
|
|
87
|
+
flowMap[ws.id] = {
|
|
88
|
+
id: ws.id,
|
|
89
|
+
class: "red-ui-palette-header",
|
|
90
|
+
label: (ws.label || ws.id) + (node.z === ws.id ? " *" : ""),
|
|
91
|
+
expanded: true,
|
|
92
|
+
children: [],
|
|
93
|
+
};
|
|
94
|
+
flows.push(flowMap[ws.id]);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
setTimeout(function ()
|
|
100
|
+
{
|
|
101
|
+
treeList.treeList("show", node.z);
|
|
102
|
+
}, 100);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function initTreeList(node, targetTypes)
|
|
106
|
+
{
|
|
107
|
+
candidateNodesCount = 0;
|
|
108
|
+
for (const key in flowMap)
|
|
109
|
+
{
|
|
110
|
+
flowMap[key].children = [];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
let candidateNodes = [];
|
|
114
|
+
|
|
115
|
+
targetTypes.forEach(function (targetType)
|
|
116
|
+
{
|
|
117
|
+
candidateNodes = candidateNodes.concat(RED.nodes.filterNodes({ type: targetType }));
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// Add all nodes, matching the given types
|
|
121
|
+
candidateNodes.forEach(function (n)
|
|
122
|
+
{
|
|
123
|
+
if (flowMap[n.z])
|
|
124
|
+
{
|
|
125
|
+
const isChecked = node.links.indexOf(n.id) !== -1 || (n.links || []).indexOf(node.id) !== -1;
|
|
126
|
+
if (isChecked) node.oldLinks.push(n.id);
|
|
127
|
+
|
|
128
|
+
flowMap[n.z].children.push({
|
|
129
|
+
id: n.id,
|
|
130
|
+
node: n,
|
|
131
|
+
label: n.name || n.id,
|
|
132
|
+
selected: isChecked,
|
|
133
|
+
checkbox: true,
|
|
134
|
+
radio: false,
|
|
135
|
+
});
|
|
136
|
+
candidateNodesCount++;
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// Sort nodes by name
|
|
141
|
+
for (const key in flowMap)
|
|
142
|
+
{
|
|
143
|
+
flowMap[key].children.sort((a, b) => a.label.localeCompare(b.label));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// filter empty flows
|
|
147
|
+
const flowsFiltered = flows.filter(function (f)
|
|
148
|
+
{
|
|
149
|
+
return f.children.length > 0;
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// clear list and refill it
|
|
153
|
+
treeList.treeList("empty");
|
|
154
|
+
treeList.treeList("data", flowsFiltered);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function resizeNodeList()
|
|
158
|
+
{
|
|
159
|
+
// calculate new heigt if browser is beeing resized
|
|
160
|
+
var rows = $("#dialog-form>div:not(.node-input-link-row)");
|
|
161
|
+
var height = $("#dialog-form").height();
|
|
162
|
+
for (var i = 0; i < rows.length; i++)
|
|
163
|
+
{
|
|
164
|
+
height -= $(rows[i]).outerHeight(true);
|
|
165
|
+
}
|
|
166
|
+
var editorRow = $("#dialog-form>div.node-input-link-row");
|
|
167
|
+
height -= parseInt(editorRow.css("marginTop")) + parseInt(editorRow.css("marginBottom"));
|
|
168
|
+
$(".node-input-link-row").css("height", height + "px");
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function onEditSave(node)
|
|
172
|
+
{
|
|
173
|
+
var flows = treeList.treeList("data");
|
|
174
|
+
node.links = [];
|
|
175
|
+
|
|
176
|
+
// Set own links for selected nodes
|
|
177
|
+
flows.forEach(function (f)
|
|
178
|
+
{
|
|
179
|
+
f.children.forEach(function (n)
|
|
180
|
+
{
|
|
181
|
+
if (n.selected)
|
|
182
|
+
node.links.push(n.id);
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
node.oldLinks.sort();
|
|
187
|
+
node.links.sort();
|
|
188
|
+
|
|
189
|
+
// Mark new and old links
|
|
190
|
+
var nodeMap = {};
|
|
191
|
+
var length = Math.max(node.oldLinks.length, node.links.length);
|
|
192
|
+
for (var i = 0; i < length; i++)
|
|
193
|
+
{
|
|
194
|
+
if (i < node.oldLinks.length)
|
|
195
|
+
{
|
|
196
|
+
nodeMap[node.oldLinks[i]] = nodeMap[node.oldLinks[i]] || {};
|
|
197
|
+
nodeMap[node.oldLinks[i]].old = true;
|
|
198
|
+
}
|
|
199
|
+
if (i < node.links.length)
|
|
200
|
+
{
|
|
201
|
+
nodeMap[node.links[i]] = nodeMap[node.links[i]] || {};
|
|
202
|
+
nodeMap[node.links[i]].new = true;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Remove own node from old linked nodes
|
|
207
|
+
// Add own node to new linked nodes
|
|
208
|
+
var n;
|
|
209
|
+
for (var id in nodeMap)
|
|
210
|
+
{
|
|
211
|
+
if (nodeMap.hasOwnProperty(id))
|
|
212
|
+
{
|
|
213
|
+
n = RED.nodes.node(id);
|
|
214
|
+
if (n)
|
|
215
|
+
{
|
|
216
|
+
// init links and remove duplicate links
|
|
217
|
+
if (!n.links)
|
|
218
|
+
n.links = [];
|
|
219
|
+
else
|
|
220
|
+
n.links = n.links.filter((value, index, array) => array.indexOf(value) === index);
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
if (nodeMap[id].old && !nodeMap[id].new)
|
|
224
|
+
{
|
|
225
|
+
// Removed id
|
|
226
|
+
i = n.links.indexOf(node.id);
|
|
227
|
+
if (i > -1) n.links.splice(i, 1);
|
|
228
|
+
}
|
|
229
|
+
else if (nodeMap[id].new)
|
|
230
|
+
{
|
|
231
|
+
// Added id
|
|
232
|
+
i = n.links.indexOf(node.id);
|
|
233
|
+
if (i === -1) n.links.push(node.id);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// This function is called, when copying and pasting central node
|
|
241
|
+
function onAdd()
|
|
242
|
+
{
|
|
243
|
+
for (var i = 0; i < this.links.length; i++)
|
|
244
|
+
{
|
|
245
|
+
var n = RED.nodes.node(this.links[i]);
|
|
246
|
+
if (n && n.links.indexOf(this.id) === -1)
|
|
247
|
+
{
|
|
248
|
+
n.links.push(this.id);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
RED.nodes.registerType("smart_central-control", {
|
|
254
|
+
category: "Smart Nodes",
|
|
255
|
+
paletteLabel: "Central control",
|
|
256
|
+
color: "#6EE2D9",
|
|
257
|
+
defaults: {
|
|
258
|
+
name: { value: "" },
|
|
259
|
+
mode: { value: "shutter" }, // shutter | light
|
|
260
|
+
links: { value: [], type: "smart_shutter-complex-control[]" },
|
|
261
|
+
},
|
|
262
|
+
inputs: 1,
|
|
263
|
+
outputs: 0,
|
|
264
|
+
icon: function ()
|
|
265
|
+
{
|
|
266
|
+
if (this.mode === "shutter")
|
|
267
|
+
return "font-awesome/fa-align-justify";
|
|
268
|
+
|
|
269
|
+
return "font-awesome/fa-lightbulb-o";
|
|
270
|
+
},
|
|
271
|
+
label: function ()
|
|
272
|
+
{
|
|
273
|
+
return this.name || "Central control";
|
|
274
|
+
},
|
|
275
|
+
oneditprepare: function ()
|
|
276
|
+
{
|
|
277
|
+
let node = this;
|
|
278
|
+
|
|
279
|
+
onEditPrepare(this, ["smart_shutter-complex-control", "smart_shutter-control"]);
|
|
280
|
+
$("#node-input-mode").on("change", function ()
|
|
281
|
+
{
|
|
282
|
+
if (this.value == "shutter") initTreeList(node, ["smart_shutter-complex-control", "smart_shutter-control"]);
|
|
283
|
+
else initTreeList(node, ["smart_light-control", "smart_scene-control"]);
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
if (!this.mode)
|
|
287
|
+
{
|
|
288
|
+
$("#node-input-mode").val("shutter").trigger("change");
|
|
289
|
+
}
|
|
290
|
+
},
|
|
291
|
+
oneditsave: function ()
|
|
292
|
+
{
|
|
293
|
+
onEditSave(this);
|
|
294
|
+
},
|
|
295
|
+
onadd: onAdd,
|
|
296
|
+
oneditresize: resizeNodeList,
|
|
297
|
+
});
|
|
298
|
+
})();
|
|
299
|
+
</script>
|
|
300
|
+
|
|
301
|
+
<script type="text/html" data-template-name="smart_central-control">
|
|
302
|
+
<div class="form-row">
|
|
303
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
304
|
+
<input type="text" id="node-input-name" />
|
|
305
|
+
</div>
|
|
306
|
+
<div class="form-row">
|
|
307
|
+
<label for="node-input-mode">Typ</label>
|
|
308
|
+
<select id="node-input-mode" style="width: 70%">
|
|
309
|
+
<option value="shutter" selected>Rollladen</option>
|
|
310
|
+
<option value="light">Licht/Steckdose</option>
|
|
311
|
+
</select>
|
|
312
|
+
</div>
|
|
313
|
+
<div class="node-input-link-rows" style="position:relative; height: 30px; text-align: right;"
|
|
314
|
+
><div style="display:inline-block"><input type="text" id="node-input-link-target-filter" /></div
|
|
315
|
+
></div>
|
|
316
|
+
<div class="form-row node-input-link-row node-input-link-rows"></div>
|
|
317
|
+
</script>
|
|
318
|
+
|
|
319
|
+
<script type="text/html" data-help-name="smart_central-control">
|
|
320
|
+
<p>
|
|
321
|
+
Der Zentralbaustein leitet einkommende Nachrichten an alle verlinkten Unterbausteine weiter.
|
|
322
|
+
Dabei beachtet der Baustein, die Funktion der Unterbausteine synchron zu halten.
|
|
323
|
+
</p>
|
|
324
|
+
<p>
|
|
325
|
+
D.h. Wird ein <code>toggle</code> an mehrere Lichtbausteine geschickt, so werden diese zuerst ausgeschaltet, falls mindestens eines eingeschaltet war.<br />
|
|
326
|
+
Rollladen werden ebenfalls erst gestoppt, falls einer gerade aktiv ist. Ein <code>toggle</code> an mehrere Rollladen wird derzeit nicht unterstützt.
|
|
327
|
+
</p>
|
|
328
|
+
</script>
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
module.exports = function (RED)
|
|
2
|
+
{
|
|
3
|
+
function CentralControlNode(config)
|
|
4
|
+
{
|
|
5
|
+
const node = this;
|
|
6
|
+
RED.nodes.createNode(node, config);
|
|
7
|
+
|
|
8
|
+
// runtime values
|
|
9
|
+
let states = new Map();
|
|
10
|
+
|
|
11
|
+
// listen to all events sended directly to this node
|
|
12
|
+
let event = "node:" + config.id;
|
|
13
|
+
let handler = function (msg)
|
|
14
|
+
{
|
|
15
|
+
if (typeof msg.source == "undefined" || typeof msg.state == "undefined")
|
|
16
|
+
{
|
|
17
|
+
console.warn("Unknown message received in smart_node central", msg);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Save feedback from linked node
|
|
22
|
+
states.set(msg.source, msg.state);
|
|
23
|
+
|
|
24
|
+
if (isAllOff())
|
|
25
|
+
node.status({ fill: "red", shape: "dot", text: "All off" });
|
|
26
|
+
else
|
|
27
|
+
node.status({ fill: "green", shape: "dot", text: "Something is on" });
|
|
28
|
+
}
|
|
29
|
+
RED.events.on(event, handler);
|
|
30
|
+
|
|
31
|
+
node.status({});
|
|
32
|
+
|
|
33
|
+
node.on("input", function (msg)
|
|
34
|
+
{
|
|
35
|
+
// copy message, so original message stays unchanged
|
|
36
|
+
let newMsg = Object.assign({}, msg);
|
|
37
|
+
|
|
38
|
+
// make connected nodes behavior homogenous
|
|
39
|
+
switch (config.mode)
|
|
40
|
+
{
|
|
41
|
+
case "shutter":
|
|
42
|
+
if (msg.topic == "up_stop")
|
|
43
|
+
{
|
|
44
|
+
if (isAllOff())
|
|
45
|
+
newMsg.topic = "up";
|
|
46
|
+
else
|
|
47
|
+
newMsg.topic = "stop";
|
|
48
|
+
}
|
|
49
|
+
else if (msg.topic == "down_stop")
|
|
50
|
+
{
|
|
51
|
+
if (isAllOff())
|
|
52
|
+
newMsg.topic = "down";
|
|
53
|
+
else
|
|
54
|
+
newMsg.topic = "stop";
|
|
55
|
+
}
|
|
56
|
+
break;
|
|
57
|
+
|
|
58
|
+
case "light":
|
|
59
|
+
if (msg.topic == "toggle")
|
|
60
|
+
{
|
|
61
|
+
if (isAllOff())
|
|
62
|
+
newMsg.topic = "on";
|
|
63
|
+
else
|
|
64
|
+
newMsg.topic = "off";
|
|
65
|
+
}
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Send cloned message to all linked nodes
|
|
70
|
+
config.links.forEach(link =>
|
|
71
|
+
{
|
|
72
|
+
RED.events.emit("node:" + link, newMsg);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
node.on("close", function ()
|
|
77
|
+
{
|
|
78
|
+
// Cleanup event listener
|
|
79
|
+
RED.events.removeListener(event, handler);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// This functions return true if all lights are off or all shutter are stopped
|
|
83
|
+
let isAllOff = () =>
|
|
84
|
+
{
|
|
85
|
+
for (let [key, state] of states)
|
|
86
|
+
{
|
|
87
|
+
if (state)
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
RED.nodes.registerType("smart_central-control", CentralControlNode);
|
|
95
|
+
};
|