smart-nodes 0.4.24 → 0.4.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -184,4 +184,12 @@
184
184
 
185
185
  ## Version 0.4.24:
186
186
 
187
- - Added set_inverted to light node.
187
+ - Added set_inverted to light node.
188
+
189
+ ## Version 0.4.25:
190
+
191
+ - Changed some texts.
192
+
193
+ ## Version 0.4.26:
194
+
195
+ - Added possibility to control mixing-valve nodes from central node.
package/README.md CHANGED
@@ -95,7 +95,7 @@ You can also choose 2-4 press detection.
95
95
 
96
96
  ## 7. Hysteresis
97
97
 
98
- This node 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
98
+ This node is checking if the input value reachs a defined value until the upper message is send. When the lower level is reached, the lower message will be send.
99
99
 
100
100
  ### **Features:**
101
101
 
@@ -104,7 +104,7 @@ This node is checking if the input value reachs a defined value until the upper
104
104
 
105
105
  ## 8. Logic
106
106
 
107
- This node can be used for AND, OR and XOR logics.
107
+ This node can be used for AND, OR and XOR logics. Inputs and outputs can be individually selected to be inverted.
108
108
 
109
109
  ### **Features:**
110
110
 
@@ -166,7 +166,7 @@
166
166
  }
167
167
  var editorRow = $("#dialog-form>div.node-input-link-row");
168
168
  height -= parseInt(editorRow.css("marginTop")) + parseInt(editorRow.css("marginBottom"));
169
- $(".node-input-link-row").css("height", height + "px");
169
+ $(".node-input-link-row").css("height", Math.max(height, 200) + "px");
170
170
  }
171
171
 
172
172
  function onEditSave(node)
@@ -257,17 +257,20 @@
257
257
  color: "#6EE2D9",
258
258
  defaults: {
259
259
  name: { value: "" },
260
- mode: { value: "LIGHT" }, // LIGHT | SHUTTER
260
+ mode: { value: "LIGHT" }, // LIGHT | SHUTTER | VALVE
261
261
  links: { value: [], type: "smart_shutter-complex-control[]" },
262
262
  },
263
263
  inputs: 1,
264
264
  outputs: 0,
265
265
  icon: function ()
266
266
  {
267
+ if (this.mode === "LIGHT")
268
+ return "font-awesome/fa-lightbulb-o";
269
+
267
270
  if (this.mode === "SHUTTER")
268
271
  return "font-awesome/fa-align-justify";
269
272
 
270
- return "font-awesome/fa-lightbulb-o";
273
+ return "mixing-valve.png";
271
274
  },
272
275
  label: function ()
273
276
  {
@@ -282,8 +285,10 @@
282
285
  {
283
286
  if (this.value == "SHUTTER")
284
287
  initTreeList(node, ["smart_shutter-complex-control", "smart_shutter-control"]);
285
- else
288
+ else if (this.value == "LIGHT")
286
289
  initTreeList(node, ["smart_light-control", "smart_scene-control"]);
290
+ else
291
+ initTreeList(node, ["smart_mixing-valve"]);
287
292
  });
288
293
 
289
294
  $("#node-input-mode")
@@ -294,7 +299,8 @@
294
299
  value: "mode",
295
300
  options: [
296
301
  { value: "LIGHT", label: node._("central.ui.light_outlet") },
297
- { value: "SHUTTER", label: node._("central.ui.cover") }
302
+ { value: "SHUTTER", label: node._("central.ui.cover") },
303
+ { value: "VALVE", label: node._("central.ui.valve") }
298
304
  ],
299
305
  }],
300
306
  });
@@ -139,8 +139,7 @@ module.exports = function (RED)
139
139
  // Send cloned message to all linked nodes
140
140
  config.links.forEach(link =>
141
141
  {
142
- helper.log(node.id + " -> " + link);
143
- helper.log(new_msg);
142
+ helper.log(node, link, { source: node.id, state: state }, new_msg);
144
143
  RED.events.emit("node:" + link, new_msg);
145
144
  });
146
145
  }
@@ -8,6 +8,7 @@
8
8
  "name": "Name",
9
9
  "type": "Typ",
10
10
  "cover": "Rollladen",
11
+ "valve": "Mischer",
11
12
  "light_outlet": "Licht/Steckdose"
12
13
  }
13
14
  }
@@ -8,6 +8,7 @@
8
8
  "name": "Name",
9
9
  "type": "Type",
10
10
  "cover": "Cover",
11
+ "valve": "Valve",
11
12
  "light_outlet": "Light/Outlet"
12
13
  }
13
14
  }
@@ -78,8 +78,7 @@ module.exports = function (RED)
78
78
  // This is the main function which handles all topics that was received.
79
79
  let handleTopic = msg =>
80
80
  {
81
- helper.log("handle topic:");
82
- helper.log(msg);
81
+ helper.log(node, "handle topic:", msg);
83
82
 
84
83
  // Check if topic has a valid value
85
84
  let real_topic_number = helper.getTopicNumber(msg.topic);
@@ -102,9 +101,7 @@ module.exports = function (RED)
102
101
 
103
102
  let result = getResult();
104
103
 
105
- helper.log("getResult:");
106
- helper.log(result);
107
- helper.log(node_settings);
104
+ helper.log(node, "getResult:", result, node_settings);
108
105
 
109
106
  let out_msg = null;
110
107
 
package/light/light.html CHANGED
@@ -129,7 +129,7 @@
129
129
  }
130
130
  var editorRow = $("#dialog-form>div.node-input-link-row");
131
131
  height -= (parseInt(editorRow.css("marginTop")) + parseInt(editorRow.css("marginBottom")));
132
- $(".node-input-link-row").css("height", height + "px");
132
+ $(".node-input-link-row").css("height", Math.max(height, 200) + "px");
133
133
  }
134
134
 
135
135
  RED.nodes.registerType("smart_light-control", {
package/light/light.js CHANGED
@@ -467,8 +467,7 @@ module.exports = function (RED)
467
467
 
468
468
  config.links.forEach(link =>
469
469
  {
470
- helper.log(node.id + " -> " + link);
471
- helper.log({ source: node.id, state: state });
470
+ helper.log(node, link, { source: node.id, state: state });
472
471
  RED.events.emit("node:" + link, { source: node.id, state: state });
473
472
  });
474
473
  }
@@ -1,7 +1,8 @@
1
1
  <script type="text/html" data-help-name="smart_light-control">
2
2
  <p>
3
3
  Diese Node steuert einen Ausgang. Dies kann ein Licht, eine Steckdose oder ähnliches sein.
4
- Als Ausgang wird immer <code>msg.payload = true</code> oder <code>msg.payload = false</code> gesendet um den Ausgang ein-, bzw. auszuschalten.
4
+ Im Modus true/false wird als Ausgang wird immer <code>msg.payload = true</code> oder <code>msg.payload = false</code> gesendet um den Ausgang ein-, bzw. auszuschalten.
5
+ Im Modus Prozent wird als Ausgang wird immer der Prozentwert, z.B. <code>msg.payload = 75</code> gesendet um den Ausgang zu schalten.
5
6
  </p>
6
7
  <p>
7
8
  <b>Hinweis:</b> Smart Nodes verwenden Topics im Format <code>name#nummer</code>, damit können verschiedene Smart Nodes mit dem gleichen Topic angesteuert werden.<br/>
package/logic/logic.js CHANGED
@@ -79,8 +79,7 @@ module.exports = function (RED)
79
79
  // This is the main function which handles all topics that was received.
80
80
  let handleTopic = msg =>
81
81
  {
82
- helper.log("handle topic:");
83
- helper.log(msg);
82
+ helper.log(node, "handle topic:", msg);
84
83
 
85
84
  let real_topic_number = helper.getTopicNumber(msg.topic);
86
85
 
@@ -97,9 +96,7 @@ module.exports = function (RED)
97
96
 
98
97
  let result = getResult();
99
98
 
100
- helper.log("getResult:");
101
- helper.log(result);
102
- helper.log(node_settings);
99
+ helper.log(node, "getResult:", result, node_settings);
103
100
 
104
101
  let out_msg = null;
105
102
 
@@ -15,10 +15,11 @@
15
15
  "heating": "Heizen (normal)",
16
16
  "cooling": "Kühlen (invertiert)",
17
17
 
18
- "precision": "Hysterese (Präzision)",
19
- "max_change_percent": "Max Positionsänderung",
20
- "max_change_temp_difference": "Temperaturen für max Positionsänderung",
21
- "min_change_time": "Mindeständerungsdauer"
18
+ "precision": "Genauigkeit",
19
+ "max_change_percent": "Max Änderung",
20
+ "max_change_temp_difference": "Temperatur für max Änderung",
21
+ "min_change_time": "Min Änderungsdauer",
22
+ "controlled_by_central": "Dieser Baustein wird von folgenden Zentralbausteinen gesteuert:"
22
23
  }
23
24
  }
24
25
  }
@@ -15,10 +15,11 @@
15
15
  "heating": "Heating (normal)",
16
16
  "cooling": "Cooling (inverted)",
17
17
 
18
- "precision": "Hysteresis (precision)",
18
+ "precision": "Precision",
19
19
  "max_change_percent": "Max change",
20
20
  "max_change_temp_difference": "Temp at max change",
21
- "min_change_time": "Min change duration"
21
+ "min_change_time": "Min change duration",
22
+ "controlled_by_central": "This block is controlled by the following central blocks:"
22
23
  }
23
24
  }
24
25
  }
@@ -1,166 +1,309 @@
1
1
  <script type="text/javascript">
2
- RED.nodes.registerType("smart_mixing-valve", {
3
- category: "Smart Nodes",
4
- paletteLabel: "Mixing Valve",
5
- color: "#3FADB5",
6
- defaults: {
7
- name: { value: "" },
8
- enabled: { value: true },
9
- setpoint: { value: 45 },
10
- time_total: { value: 60 },
11
- time_sampling: { value: 60 },
12
- off_mode: { value: "NOTHING" }, // NOTHING | OPEN | CLOSE
13
- valve_mode: { value: "HEATING" }, // HEATING | COOLING
14
- precision: { value: 1.0 },
15
- max_change_percent: { value: 1 },
16
- max_change_temp_difference: { value: 20 },
17
- min_change_time: { value: 0 }
18
- },
19
- inputs: 1,
20
- outputs: 3,
21
- outputLabels: ["Open", "Close", "Status Position"],
22
- icon: "mixing-valve.png",
23
- label: function ()
24
- {
25
- return this.name || "Mixing Valve";
26
- },
27
- oneditprepare: function ()
2
+ (function ()
3
+ {
4
+ let treeList;
5
+ let candidateNodesCount = 0;
6
+ let flows = [];
7
+ let flowMap = {};
8
+
9
+ function onEditPrepare(node, targetTypes)
28
10
  {
29
- let node = this;
30
-
31
- $("#node-input-setpoint")
32
- .css("max-width", "4rem")
33
- .spinner({
34
- min: -30,
35
- max: 100,
36
- change: function (event, ui)
11
+ if (!node.links)
12
+ node.links = [];
13
+
14
+ const activeSubflow = RED.nodes.subflow(node.z);
15
+
16
+ treeList = $("<div>")
17
+ .css({ width: "100%", height: "100%" })
18
+ .appendTo(".node-input-link-row")
19
+ .treeList({ autoSelect: false })
20
+ .on("treelistitemmouseover", function (e, item)
21
+ {
22
+ if (item.node)
37
23
  {
38
- var value = parseInt(this.value);
39
- value = isNaN(value) ? 0 : value;
40
- value = Math.max(value, parseInt($(this).attr("aria-valuemin")));
41
- value = Math.min(value, parseInt($(this).attr("aria-valuemax")));
42
- if (value !== this.value) $(this).spinner("value", value);
43
- },
24
+ item.node.highlighted = true;
25
+ item.node.dirty = true;
26
+ RED.view.redraw();
27
+ }
28
+ })
29
+ .on("treelistitemmouseout", function (e, item)
30
+ {
31
+ if (item.node)
32
+ {
33
+ item.node.highlighted = false;
34
+ item.node.dirty = true;
35
+ RED.view.redraw();
36
+ }
44
37
  });
45
38
 
46
- $("#node-input-time_total")
47
- .css("max-width", "4rem")
48
- .spinner({
49
- min: 0,
50
- change: function (event, ui)
39
+ flows = [];
40
+ flowMap = {};
41
+
42
+ if (activeSubflow)
43
+ {
44
+ flowMap[activeSubflow.id] = {
45
+ id: activeSubflow.id,
46
+ class: "red-ui-palette-header",
47
+ label: "Subflow : " + (activeSubflow.name || activeSubflow.id),
48
+ expanded: true,
49
+ children: []
50
+ };
51
+ flows.push(flowMap[activeSubflow.id]);
52
+ }
53
+ else
54
+ {
55
+ RED.nodes.eachWorkspace(function (ws)
56
+ {
57
+ if (!ws.disabled)
51
58
  {
52
- var value = parseInt(this.value);
53
- value = isNaN(value) ? 0 : value;
54
- value = Math.max(value, parseInt($(this).attr("aria-valuemin")));
55
- // value = Math.min(value, parseInt($(this).attr("aria-valuemax")));
56
- if (value !== this.value) $(this).spinner("value", value);
57
- },
59
+ flowMap[ws.id] = {
60
+ id: ws.id,
61
+ class: "red-ui-palette-header",
62
+ label: (ws.label || ws.id) + (node.z === ws.id ? " *" : ""),
63
+ expanded: true,
64
+ children: []
65
+ };
66
+ flows.push(flowMap[ws.id]);
67
+ }
58
68
  });
69
+ }
70
+
71
+ setTimeout(function ()
72
+ {
73
+ treeList.treeList("show", node.z);
74
+ }, 100);
75
+ }
59
76
 
60
- $("#node-input-time_sampling")
61
- .css("max-width", "4rem")
62
- .spinner({
63
- min: 0,
64
- change: function (event, ui)
77
+ function initTreeList(node, targetTypes)
78
+ {
79
+ candidateNodesCount = 0;
80
+ for (const key in flowMap)
81
+ {
82
+ flowMap[key].children = [];
83
+ }
84
+
85
+ let candidateNodes = [];
86
+
87
+ targetTypes.forEach(function (targetType)
88
+ {
89
+ candidateNodes = candidateNodes.concat(RED.nodes.filterNodes({ type: targetType }));
90
+ });
91
+
92
+ candidateNodes.forEach(function (n)
93
+ {
94
+ if (flowMap[n.z])
95
+ {
96
+ const isChecked = (node.links.indexOf(n.id) !== -1) || (n.links || []).indexOf(node.id) !== -1;
97
+ if (isChecked)
65
98
  {
66
- var value = parseInt(this.value);
67
- value = isNaN(value) ? 0 : value;
68
- value = Math.max(value, parseInt($(this).attr("aria-valuemin")));
69
- // value = Math.min(value, parseInt($(this).attr("aria-valuemax")));
70
- if (value !== this.value) $(this).spinner("value", value);
71
- },
72
- });
99
+ flowMap[n.z].children.push({
100
+ id: n.id,
101
+ node: n,
102
+ label: n.name || n.id,
103
+ selected: false,
104
+ checkbox: false,
105
+ radio: false
106
+ });
107
+ candidateNodesCount++;
108
+ }
109
+ }
110
+ });
73
111
 
74
- $("#node-input-off_mode")
75
- .css("max-width", "70%")
76
- .typedInput({
77
- types: [{
78
- default: "NOTHING",
79
- value: "off_mode",
80
- options: [
81
- { value: "NOTHING", label: node._("mixing-valve.ui.nothing") },
82
- { value: "OPEN", label: node._("mixing-valve.ui.open") },
83
- { value: "CLOSE", label: node._("mixing-valve.ui.close") }
84
- ],
85
- }],
86
- });
112
+ for (const key in flowMap)
113
+ {
114
+ flowMap[key].children.sort((a, b) => a.label.localeCompare(b.label));
115
+ }
87
116
 
88
- $("#node-input-valve_mode")
89
- .css("max-width", "70%")
90
- .typedInput({
91
- types: [{
92
- default: "HEATING",
93
- value: "valve_mode",
94
- options: [
95
- { value: "HEATING", label: node._("mixing-valve.ui.heating") },
96
- { value: "COOLING", label: node._("mixing-valve.ui.cooling") }
97
- ],
98
- }],
99
- });
117
+ const flowsFiltered = flows.filter(function (f) { return f.children.length > 0 });
118
+ treeList.treeList("empty");
119
+ treeList.treeList("data", flowsFiltered);
120
+ }
121
+
122
+ function resizeNodeList()
123
+ {
124
+ var rows = $("#dialog-form>div:not(.node-input-link-row)");
125
+ var height = $("#dialog-form").height();
126
+ for (var i = 0; i < rows.length; i++)
127
+ {
128
+ height -= $(rows[i]).outerHeight(true);
129
+ }
130
+ var editorRow = $("#dialog-form>div.node-input-link-row");
131
+ height -= (parseInt(editorRow.css("marginTop")) + parseInt(editorRow.css("marginBottom")));
132
+ $(".node-input-link-row").css("height", Math.max(height, 200) + "px");
133
+ }
134
+
135
+ RED.nodes.registerType("smart_mixing-valve", {
136
+ category: "Smart Nodes",
137
+ paletteLabel: "Mixing Valve",
138
+ color: "#3FADB5",
139
+ defaults: {
140
+ name: { value: "" },
141
+ enabled: { value: true },
142
+ setpoint: { value: 45 },
143
+ time_total: { value: 60 },
144
+ time_sampling: { value: 60 },
145
+ off_mode: { value: "NOTHING" }, // NOTHING | OPEN | CLOSE
146
+ valve_mode: { value: "HEATING" }, // HEATING | COOLING
147
+ precision: { value: 1.0 },
148
+ max_change_percent: { value: 1 },
149
+ max_change_temp_difference: { value: 20 },
150
+ min_change_time: { value: 100 },
151
+ links: { value: [], type: "smart_central-control[]" }
152
+ },
153
+ inputs: 1,
154
+ outputs: 3,
155
+ outputLabels: ["Open", "Close", "Status Position"],
156
+ icon: "mixing-valve.png",
157
+ label: function ()
158
+ {
159
+ return this.name || "Mixing Valve";
160
+ },
161
+ oneditprepare: function ()
162
+ {
163
+ let node = this;
164
+
165
+ onEditPrepare(this, ["smart_central-control"]);
166
+ initTreeList(node, ["smart_central-control"]);
167
+
168
+ $("#node-input-setpoint")
169
+ .css("max-width", "4rem")
170
+ .spinner({
171
+ min: -30,
172
+ max: 100,
173
+ change: function (event, ui)
174
+ {
175
+ var value = parseInt(this.value);
176
+ value = isNaN(value) ? 0 : value;
177
+ value = Math.max(value, parseInt($(this).attr("aria-valuemin")));
178
+ value = Math.min(value, parseInt($(this).attr("aria-valuemax")));
179
+ if (value !== this.value) $(this).spinner("value", value);
180
+ },
181
+ });
182
+
183
+ $("#node-input-time_total")
184
+ .css("max-width", "4rem")
185
+ .spinner({
186
+ min: 0,
187
+ change: function (event, ui)
188
+ {
189
+ var value = parseInt(this.value);
190
+ value = isNaN(value) ? 0 : value;
191
+ value = Math.max(value, parseInt($(this).attr("aria-valuemin")));
192
+ // value = Math.min(value, parseInt($(this).attr("aria-valuemax")));
193
+ if (value !== this.value) $(this).spinner("value", value);
194
+ },
195
+ });
196
+
197
+ $("#node-input-time_sampling")
198
+ .css("max-width", "4rem")
199
+ .spinner({
200
+ min: 0,
201
+ change: function (event, ui)
202
+ {
203
+ var value = parseInt(this.value);
204
+ value = isNaN(value) ? 0 : value;
205
+ value = Math.max(value, parseInt($(this).attr("aria-valuemin")));
206
+ // value = Math.min(value, parseInt($(this).attr("aria-valuemax")));
207
+ if (value !== this.value) $(this).spinner("value", value);
208
+ },
209
+ });
210
+
211
+ $("#node-input-off_mode")
212
+ .css("max-width", "70%")
213
+ .typedInput({
214
+ types: [{
215
+ default: "NOTHING",
216
+ value: "off_mode",
217
+ options: [
218
+ { value: "NOTHING", label: node._("mixing-valve.ui.nothing") },
219
+ { value: "OPEN", label: node._("mixing-valve.ui.open") },
220
+ { value: "CLOSE", label: node._("mixing-valve.ui.close") }
221
+ ],
222
+ }],
223
+ });
224
+
225
+ $("#node-input-valve_mode")
226
+ .css("max-width", "70%")
227
+ .typedInput({
228
+ types: [{
229
+ default: "HEATING",
230
+ value: "valve_mode",
231
+ options: [
232
+ { value: "HEATING", label: node._("mixing-valve.ui.heating") },
233
+ { value: "COOLING", label: node._("mixing-valve.ui.cooling") }
234
+ ],
235
+ }],
236
+ });
237
+
238
+ $("#node-input-precision")
239
+ .css("max-width", "4rem")
240
+ .spinner({
241
+ min: 0.1,
242
+ max: 1.0,
243
+ step: 0.1,
244
+ change: function (event, ui)
245
+ {
246
+ var value = parseFloat(this.value);
247
+ value = isNaN(value) ? 0.0 : value;
248
+ value = Math.max(value, parseFloat($(this).attr("aria-valuemin")));
249
+ value = Math.min(value, parseFloat($(this).attr("aria-valuemax")));
250
+ if (value !== this.value) $(this).spinner("value", value);
251
+ },
252
+ });
253
+
254
+ $("#node-input-max_change_percent")
255
+ .css("max-width", "4rem")
256
+ .spinner({
257
+ min: 1,
258
+ max: 40,
259
+ change: function (event, ui)
260
+ {
261
+ var value = parseInt(this.value);
262
+ value = isNaN(value) ? 0 : value;
263
+ value = Math.max(value, parseInt($(this).attr("aria-valuemin")));
264
+ value = Math.min(value, parseInt($(this).attr("aria-valuemax")));
265
+ if (value !== this.value) $(this).spinner("value", value);
266
+ },
267
+ });
268
+
269
+ $("#node-input-max_change_temp_difference")
270
+ .css("max-width", "4rem")
271
+ .spinner({
272
+ min: 1,
273
+ max: 20,
274
+ change: function (event, ui)
275
+ {
276
+ var value = parseInt(this.value);
277
+ value = isNaN(value) ? 0 : value;
278
+ value = Math.max(value, parseInt($(this).attr("aria-valuemin")));
279
+ value = Math.min(value, parseInt($(this).attr("aria-valuemax")));
280
+ if (value !== this.value) $(this).spinner("value", value);
281
+ },
282
+ });
100
283
 
101
- $("#node-input-precision")
102
- .css("max-width", "4rem")
103
- .spinner({
104
- min: 0.1,
105
- max: 1.0,
106
- step: 0.1,
107
- change: function (event, ui)
108
- {
109
- var value = parseFloat(this.value);
110
- value = isNaN(value) ? 0.0 : value;
111
- value = Math.max(value, parseFloat($(this).attr("aria-valuemin")));
112
- value = Math.min(value, parseFloat($(this).attr("aria-valuemax")));
113
- if (value !== this.value) $(this).spinner("value", value);
114
- },
115
- });
116
-
117
- $("#node-input-max_change_percent")
118
- .css("max-width", "4rem")
119
- .spinner({
120
- min: 1,
121
- max: 40,
122
- change: function (event, ui)
123
- {
124
- var value = parseInt(this.value);
125
- value = isNaN(value) ? 0 : value;
126
- value = Math.max(value, parseInt($(this).attr("aria-valuemin")));
127
- value = Math.min(value, parseInt($(this).attr("aria-valuemax")));
128
- if (value !== this.value) $(this).spinner("value", value);
129
- },
130
- });
131
-
132
- $("#node-input-max_change_temp_difference")
133
- .css("max-width", "4rem")
134
- .spinner({
135
- min: 1,
136
- max: 20,
137
- change: function (event, ui)
138
- {
139
- var value = parseInt(this.value);
140
- value = isNaN(value) ? 0 : value;
141
- value = Math.max(value, parseInt($(this).attr("aria-valuemin")));
142
- value = Math.min(value, parseInt($(this).attr("aria-valuemax")));
143
- if (value !== this.value) $(this).spinner("value", value);
144
- },
145
- });
146
-
147
- $("#node-input-min_change_time")
148
- .css("max-width", "4rem")
149
- .spinner({
150
- min: 0,
151
- max: 2000,
152
- step: 100,
153
- change: function (event, ui)
154
- {
155
- var value = parseInt(this.value);
156
- value = isNaN(value) ? 0 : value;
157
- value = Math.max(value, parseInt($(this).attr("aria-valuemin")));
158
- value = Math.min(value, parseInt($(this).attr("aria-valuemax")));
159
- if (value !== this.value) $(this).spinner("value", value);
160
- },
161
- });
162
- },
163
- });
284
+ $("#node-input-min_change_time")
285
+ .css("max-width", "4rem")
286
+ .spinner({
287
+ min: 0,
288
+ max: 2000,
289
+ step: 100,
290
+ change: function (event, ui)
291
+ {
292
+ var value = parseInt(this.value);
293
+ value = isNaN(value) ? 0 : value;
294
+ value = Math.max(value, parseInt($(this).attr("aria-valuemin")));
295
+ value = Math.min(value, parseInt($(this).attr("aria-valuemax")));
296
+ if (value !== this.value) $(this).spinner("value", value);
297
+ },
298
+ });
299
+ },
300
+ onadd: function ()
301
+ {
302
+ this.links = [];
303
+ },
304
+ oneditresize: resizeNodeList
305
+ });
306
+ })();
164
307
  </script>
165
308
 
166
309
  <script type="text/html" data-template-name="smart_mixing-valve">
@@ -208,4 +351,6 @@
208
351
  <label for="node-input-min_change_time"><i class="fa fa-sliders"></i> <span data-i18n="mixing-valve.ui.min_change_time"></span></label>
209
352
  <input id="node-input-min_change_time" value="0" /> ms
210
353
  </div>
354
+ <span><i class="fa fa-link"></i> <span data-i18n="mixing-valve.ui.controlled_by_central"></span></span>
355
+ <div class="form-row node-input-link-row node-input-link-rows"></div>
211
356
  </script>
@@ -68,6 +68,17 @@ module.exports = function (RED)
68
68
  let adjusting_start_time = null;
69
69
 
70
70
 
71
+ // #########################
72
+ // # Central node handling #
73
+ // #########################
74
+ var event = "node:" + node.id;
75
+ var handler = function (msg)
76
+ {
77
+ node.receive(msg);
78
+ }
79
+ RED.events.on(event, handler);
80
+
81
+
71
82
  // ###############
72
83
  // # Node events #
73
84
  // ###############
@@ -112,6 +123,8 @@ module.exports = function (RED)
112
123
  // This is the main function which handles all topics that was received.
113
124
  let handleTopic = msg =>
114
125
  {
126
+ notifyCentral(msg.topic);
127
+
115
128
  let real_topic = helper.getTopicName(msg.topic);
116
129
 
117
130
  if (real_topic == "set_state_inverted")
@@ -209,6 +222,8 @@ module.exports = function (RED)
209
222
  helper.warn(this, "Invalid topic: " + real_topic);
210
223
  return;
211
224
  }
225
+
226
+ notifyCentral(node_settings.enabled);
212
227
  }
213
228
 
214
229
  let startSampling = () =>
@@ -249,7 +264,7 @@ module.exports = function (RED)
249
264
  // No current temperature available or in calibration => no action
250
265
  if (current_temperature === null || calibration_timeout !== null || !node_settings.enabled)
251
266
  {
252
- helper.log("No sample possible", {
267
+ helper.log(node, "No sample possible", {
253
268
  current_temperature,
254
269
  calibration_timeout,
255
270
  enabled: node_settings.enabled
@@ -297,7 +312,7 @@ module.exports = function (RED)
297
312
 
298
313
  let startChanging = (adjustAction, time_ms) =>
299
314
  {
300
- helper.log("Start changing", adjustAction, time_ms)
315
+ helper.log(node, "Start changing", adjustAction, time_ms)
301
316
  stopChanging();
302
317
 
303
318
  // Already oppened/closed
@@ -319,6 +334,7 @@ module.exports = function (RED)
319
334
  stopChanging();
320
335
  adjusting = null;
321
336
  setStatus();
337
+ notifyCentral(node_settings.enabled);
322
338
  }, time_ms);
323
339
 
324
340
  setStatus();
@@ -376,6 +392,7 @@ module.exports = function (RED)
376
392
  stopSampling();
377
393
 
378
394
  setStatus();
395
+ notifyCentral(node_settings.enabled);
379
396
  }, time_total_s * 1000);
380
397
  }
381
398
 
@@ -407,6 +424,23 @@ module.exports = function (RED)
407
424
  node.status({ fill: node_settings.enabled ? "green" : "red", shape: "dot", text: helper.getCurrentTimeForStatus() + ": " + (node_settings.valve_mode == "HEATING" ? "🔥" : "❄️") + " Set: " + helper.toFixed(node_settings.setpoint, 1) + "°C, Cur: " + helper.toFixed(current_temperature, 1) + "°C, Pos: " + helper.toFixed(node_settings.last_position, 1) + "%" });
408
425
  }
409
426
 
427
+ /**
428
+ * Notify all connected central nodes
429
+ * @param {boolean} state The state if the valve is enabled
430
+ */
431
+ let notifyCentral = state =>
432
+ {
433
+ helper.log(node, "notifyCentral", config.links, node);
434
+ if (!config.links)
435
+ return;
436
+
437
+ config.links.forEach(link =>
438
+ {
439
+ helper.log(node, link, { source: node.id, state: state });
440
+ RED.events.emit("node:" + link, { source: node.id, state: state });
441
+ });
442
+ }
443
+
410
444
  if (node_settings.last_position == null)
411
445
  {
412
446
  // Start calibration after 10s
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "smart-nodes",
3
- "version": "0.4.24",
4
- "description": "Smart Nodes",
3
+ "version": "0.4.26",
4
+ "description": "Controls light, shutters and more. Includes common used logic and statistic nodes to control your home.",
5
5
  "keywords": [
6
6
  "node-red",
7
7
  "smart nodes",
package/scene/scene.html CHANGED
@@ -154,7 +154,7 @@
154
154
  height -= (parseInt(tabRow.css("marginTop")) + parseInt(tabRow.css("marginBottom"))) + tabRow.outerHeight(true);
155
155
  var editorRow = $("#dialog-form div.node-input-link-row");
156
156
  height -= (parseInt(editorRow.css("marginTop")) + parseInt(editorRow.css("marginBottom")));
157
- $(".node-input-link-row").css("height", height + "px");
157
+ $(".node-input-link-row").css("height", Math.max(height, 200) + "px");
158
158
  }
159
159
 
160
160
  function getScenes(el)
package/scene/scene.js CHANGED
@@ -314,8 +314,7 @@ module.exports = function (RED)
314
314
 
315
315
  config.links.forEach(link =>
316
316
  {
317
- helper.log(node.id + " -> " + link);
318
- helper.log({ source: node.id, state: state });
317
+ helper.log(node, link, { source: node.id, state: state });
319
318
  RED.events.emit("node:" + link, { source: node.id, state: state });
320
319
  });
321
320
  }
@@ -211,7 +211,7 @@ module.exports = function (RED)
211
211
  schedule.second
212
212
  );
213
213
 
214
- // helper.log({
214
+ // helper.log(node, {
215
215
  // i,
216
216
  // findNextDay,
217
217
  // nextEvent
@@ -129,7 +129,7 @@
129
129
  }
130
130
  var editorRow = $("#dialog-form>div.node-input-link-row");
131
131
  height -= (parseInt(editorRow.css("marginTop")) + parseInt(editorRow.css("marginBottom")));
132
- $(".node-input-link-row").css("height", height + "px");
132
+ $(".node-input-link-row").css("height", Math.max(height, 200) + "px");
133
133
  }
134
134
 
135
135
  RED.nodes.registerType("smart_shutter-control", {
@@ -91,8 +91,7 @@ module.exports = function (RED)
91
91
  // This is the main function which handles all topics that was received.
92
92
  let handleTopic = msg =>
93
93
  {
94
- helper.log("handle topic:");
95
- helper.log(msg);
94
+ helper.log(node, "handle topic:", msg);
96
95
 
97
96
  var resultUpDown = null;
98
97
  var resultStop = null;
@@ -129,7 +128,7 @@ module.exports = function (RED)
129
128
  real_topic = "up";
130
129
  }
131
130
 
132
- helper.log("handle real topic: " + real_topic);
131
+ helper.log(node, "handle real topic: " + real_topic);
133
132
  switch (real_topic)
134
133
  {
135
134
  case "status":
@@ -273,8 +272,7 @@ module.exports = function (RED)
273
272
 
274
273
  config.links.forEach(link =>
275
274
  {
276
- helper.log(node.id + " -> " + link);
277
- helper.log({ source: node.id, state: state });
275
+ helper.log(node, link, { source: node.id, state: state });
278
276
  RED.events.emit("node:" + link, { source: node.id, state: state });
279
277
  });
280
278
  };
@@ -129,7 +129,7 @@
129
129
  }
130
130
  var editorRow = $("#dialog-form>div.node-input-link-row");
131
131
  height -= (parseInt(editorRow.css("marginTop")) + parseInt(editorRow.css("marginBottom")));
132
- $(".node-input-link-row").css("height", height + "px");
132
+ $(".node-input-link-row").css("height", Math.max(height, 200) + "px");
133
133
  }
134
134
 
135
135
  RED.nodes.registerType("smart_shutter-complex-control", {
@@ -119,8 +119,7 @@ module.exports = function (RED)
119
119
  // This is the main function which handles all topics that was received.
120
120
  let handleTopic = msg =>
121
121
  {
122
- helper.log("handle topic:");
123
- helper.log(msg);
122
+ helper.log(node, "handle topic:", msg);
124
123
 
125
124
  let real_topic = helper.getRealTopic(msg.topic, "toggle", ["up", "up_stop", "down", "down_stop", "stop", "toggle", "up_down", "position", "alarm"]);
126
125
 
@@ -163,7 +162,7 @@ module.exports = function (RED)
163
162
  }
164
163
  }
165
164
 
166
- helper.log("handle real topic: " + real_topic);
165
+ helper.log(node, "handle real topic: " + real_topic);
167
166
  switch (real_topic)
168
167
  {
169
168
  case "up":
@@ -252,8 +251,7 @@ module.exports = function (RED)
252
251
  */
253
252
  let startAction = (action, data = null, exact = false, ignoreAlarm = false) =>
254
253
  {
255
- helper.log("startAction");
256
- helper.log({ action, data, exact, ignoreAlarm });
254
+ helper.log(node, "startAction", { action, data, exact, ignoreAlarm });
257
255
 
258
256
  // Nothing allowed if alarm is on
259
257
  if (ignoreAlarm === false && node_settings.alarm_active)
@@ -392,7 +390,7 @@ module.exports = function (RED)
392
390
  {
393
391
  // This happens if the time needs to be changed, but the direction is the same
394
392
  clearTimeout(timeout);
395
- helper.log("stop after " + run_time_ms + "ms");
393
+ helper.log(node, "stop after " + run_time_ms + "ms");
396
394
 
397
395
  off_time = Date.now();
398
396
  on_time = off_time;
@@ -412,7 +410,7 @@ module.exports = function (RED)
412
410
  else if (off_time + revert_time_ms - now > 0 && dirChange)
413
411
  {
414
412
  // revert time is not fully passed
415
- helper.log("revert time is not fully passed, wait for " + (off_time + revert_time_ms - now) + "ms");
413
+ helper.log(node, "revert time is not fully passed, wait for " + (off_time + revert_time_ms - now) + "ms");
416
414
  wait_timeout = setTimeout(() =>
417
415
  {
418
416
  wait_timeout = null;
@@ -434,12 +432,12 @@ module.exports = function (RED)
434
432
  switch (action)
435
433
  {
436
434
  case ACTION_UP:
437
- helper.log("start ACTION_UP");
435
+ helper.log(node, "start ACTION_UP");
438
436
  sendToOutput(true, false);
439
437
  break;
440
438
 
441
439
  case ACTION_DOWN:
442
- helper.log("start ACTION_DOWN");
440
+ helper.log(node, "start ACTION_DOWN");
443
441
  sendToOutput(false, true);
444
442
  break;
445
443
  }
@@ -449,7 +447,7 @@ module.exports = function (RED)
449
447
 
450
448
  on_time = Date.now();
451
449
 
452
- helper.log("stop after " + run_time_ms + "ms");
450
+ helper.log(node, "stop after " + run_time_ms + "ms");
453
451
  timeout = setTimeout(() =>
454
452
  {
455
453
  startAction(ACTION_STOP, null, null, ignoreAlarm);
@@ -470,8 +468,7 @@ module.exports = function (RED)
470
468
  */
471
469
  let sendToOutput = (up, down) =>
472
470
  {
473
- helper.log("sendToOutput");
474
- helper.log({ up, down });
471
+ helper.log(node, "sendToOutput", { up, down });
475
472
 
476
473
  if (up && down)
477
474
  {
@@ -553,8 +550,7 @@ module.exports = function (RED)
553
550
 
554
551
  config.links.forEach(link =>
555
552
  {
556
- helper.log(node.id + " -> " + link);
557
- helper.log({ source: node.id, state: state });
553
+ helper.log(node, link, { source: node.id, state: state });
558
554
  RED.events.emit("node:" + link, { source: node.id, state: state });
559
555
  });
560
556
  };
package/smart_helper.js CHANGED
@@ -362,7 +362,7 @@ module.exports = {
362
362
  log(node, ...args)
363
363
  {
364
364
  // uncomment to see all log values
365
- // console.log(typeof node + " (" + node.id + "):", ...args);
365
+ // console.log(node.constructor.name + " (" + node.id + "):", ...args);
366
366
  },
367
367
 
368
368
  /**
@@ -371,6 +371,6 @@ module.exports = {
371
371
  warn(node, ...args)
372
372
  {
373
373
  // uncomment to see all warn values
374
- console.warn(typeof node + " (" + node.id + "):", ...args);
374
+ // console.warn(node.constructor.name + " (" + node.id + "):", ...args);
375
375
  }
376
376
  };
@@ -174,7 +174,7 @@
174
174
  }
175
175
  var editorRow = $("#dialog-form>div.node-input-link-row");
176
176
  height -= parseInt(editorRow.css("marginTop")) + parseInt(editorRow.css("marginBottom"));
177
- $(".node-input-link-row").css("height", height + "px");
177
+ $(".node-input-link-row").css("height", Math.max(height, 200) + "px");
178
178
  }
179
179
 
180
180
  function onEditSave(node)