smart-nodes 0.3.26 → 0.3.28

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.
@@ -10,8 +10,10 @@ module.exports = function (RED)
10
10
  const smart_context = require("../persistence.js")(RED);
11
11
  const helper = require("../smart_helper.js");
12
12
 
13
+ // persistant values
13
14
  var node_settings = {
14
- active: null,
15
+ last_value: null,
16
+ last_result: null,
15
17
  last_message: null,
16
18
  setpoint: parseFloat(config.setpoint),
17
19
  hysteresis: parseFloat(config.hysteresis)
@@ -22,7 +24,7 @@ module.exports = function (RED)
22
24
  // load old saved values
23
25
  node_settings = Object.assign(node_settings, smart_context.get(node.id));
24
26
 
25
- switch (node_settings.active)
27
+ switch (node_settings.last_result)
26
28
  {
27
29
  case true:
28
30
  node.status({ fill: "green", shape: "dot", text: helper.getCurrentTimeForStatus() + ": Load last state: Higher" });
@@ -47,6 +49,8 @@ module.exports = function (RED)
47
49
  // dynamic config
48
50
  let out_higher = helper.evaluateNodeProperty(RED, config.out_higher, config.out_higher_type);
49
51
  let out_lower = helper.evaluateNodeProperty(RED, config.out_lower, config.out_lower_type);
52
+ let send_only_change = helper.evaluateNodeProperty(RED, config.send_only_change, "bool");
53
+ let outputs = helper.evaluateNodeProperty(RED, config.outputs, "num");
50
54
 
51
55
  // runtime values
52
56
 
@@ -55,7 +59,7 @@ module.exports = function (RED)
55
59
  let value = parseFloat(msg.payload);
56
60
  let real_topic = helper.getTopicName(msg.topic);
57
61
 
58
- if (isNaN(value) && real_topic !== "resend")
62
+ if (isNaN(value))
59
63
  {
60
64
  // node.error("Invalid payload: " + msg.payload);
61
65
  return;
@@ -65,7 +69,7 @@ module.exports = function (RED)
65
69
  {
66
70
  case "setpoint":
67
71
  node_settings.setpoint = value;
68
- node.status({ fill: node_settings.active ? "green" : "red", shape: "ring", text: helper.getCurrentTimeForStatus() + ": New setpoint: " + value + "" });
72
+ node.status({ fill: node_settings.last_result ? "green" : "red", shape: "ring", text: helper.getCurrentTimeForStatus() + ": New setpoint: " + value + "" });
69
73
 
70
74
  if (config.save_state)
71
75
  smart_context.set(node.id, node_settings);
@@ -73,56 +77,47 @@ module.exports = function (RED)
73
77
 
74
78
  case "hysteresis":
75
79
  node_settings.hysteresis = value;
76
- node.status({ fill: node_settings.active ? "green" : "red", shape: "ring", text: helper.getCurrentTimeForStatus() + ": New hysteresis: " + value + "" });
80
+ node.status({ fill: node_settings.last_result ? "green" : "red", shape: "ring", text: helper.getCurrentTimeForStatus() + ": New hysteresis: " + value + "" });
77
81
 
78
82
  if (config.save_state)
79
83
  smart_context.set(node.id, node_settings);
80
84
  break;
81
85
 
82
- case "resend":
83
- if (node_settings.active === true && node_settings.last_message != null)
84
- {
85
- node.status({ fill: "green", shape: "dot", text: helper.getCurrentTimeForStatus() + ": Resend higher value" });
86
- node.send([node_settings.last_message, null]);
87
- }
88
- else if (node_settings.active === false && node_settings.last_message != null)
89
- {
90
- node.status({ fill: "red", shape: "dot", text: helper.getCurrentTimeForStatus() + ": Resend lower value" });
91
- node.send([null, node_settings.last_message]);
92
- }
86
+ default:
87
+ let result = getResult(value);
88
+ let out_msg = null;
89
+
90
+ // Get custom output message
91
+ if (result)
92
+ out_msg = createMessage(out_higher, config.out_higher_type, msg, value);
93
93
  else
94
- {
95
- node.status({ fill: "yellow", shape: "ring", text: helper.getCurrentTimeForStatus() + ": No resend, state is unknown" });
96
- }
97
- break;
94
+ out_msg = createMessage(out_lower, config.out_lower_type, msg, value);
98
95
 
99
- default:
100
- if (value >= node_settings.setpoint + node_settings.hysteresis && node_settings.active !== true)
96
+ // Overwrite automatic values, if not already defined
97
+ if (typeof out_msg.payload === "undefined")
98
+ out_msg.payload = result ?? node_settings.last_result;
99
+
100
+ // Separate outputs if needed
101
+ if (outputs == 2)
101
102
  {
102
- node.status({ fill: "green", shape: "dot", text: helper.getCurrentTimeForStatus() + ": Turned higher by value " + value + "" });
103
- node_settings.active = true;
104
- node_settings.last_message = createMessage(out_higher ?? msg, value);
103
+ if (result)
104
+ out_msg = [out_msg, null];
105
+ else
106
+ out_msg = [null, out_msg];
107
+ }
105
108
 
106
- if (config.save_state)
107
- smart_context.set(node.id, node_settings);
109
+ // Send only if needed
110
+ if (send_only_change == false || node_settings.last_result != result)
111
+ node.send(out_msg);
108
112
 
109
- node.send([node_settings.last_message, null]);
110
- }
111
- else if (value <= node_settings.setpoint - node_settings.hysteresis && node_settings.active !== false)
112
- {
113
- node.status({ fill: "red", shape: "dot", text: helper.getCurrentTimeForStatus() + ": Turned lower by value " + value + "" });
114
- node_settings.active = false;
115
- node_settings.last_message = createMessage(out_lower ?? msg, value);
113
+ node_settings.last_value = value;
114
+ node_settings.last_result = result;
115
+ node_settings.last_message = out_msg;
116
116
 
117
- if (config.save_state)
118
- smart_context.set(node.id, node_settings);
117
+ setStatus();
119
118
 
120
- node.send([null, node_settings.last_message]);
121
- }
122
- else
123
- {
124
- node.status({ fill: node_settings.active ? "green" : "red", shape: "ring", text: helper.getCurrentTimeForStatus() + ": No change by value " + value + "" });
125
- }
119
+ if (config.save_state)
120
+ smart_context.set(node.id, node_settings);
126
121
  break;
127
122
  }
128
123
  });
@@ -131,23 +126,45 @@ module.exports = function (RED)
131
126
  {
132
127
  });
133
128
 
134
- let createMessage = (msg, value) =>
129
+ let getResult = value =>
130
+ {
131
+ if (value >= node_settings.setpoint + node_settings.hysteresis && node_settings.last_result !== true)
132
+ return true;
133
+
134
+ if (value <= node_settings.setpoint - node_settings.hysteresis && node_settings.last_result !== false)
135
+ return false;
136
+
137
+ return null;
138
+ }
139
+
140
+ let setStatus = () =>
141
+ {
142
+ if (node_settings.last_result === true)
143
+ node.status({ fill: "green", shape: "dot", text: helper.getCurrentTimeForStatus() + ": Turned higher by value " + node_settings.last_value + "" });
144
+ else if (node_settings.last_result === false)
145
+ node.status({ fill: "red", shape: "dot", text: helper.getCurrentTimeForStatus() + ": Turned lower by value " + node_settings.last_value + "" });
146
+ else
147
+ node.status({ fill: node_settings.last_result ? "green" : "red", shape: "ring", text: helper.getCurrentTimeForStatus() + ": No change by value " + node_settings.last_value + "" });
148
+ }
149
+
150
+ let createMessage = (out_msg, out_type, msg, value) =>
135
151
  {
136
- return Object.assign({}, msg, {
152
+ return Object.assign({}, out_type == "original" ? msg : out_msg, {
137
153
  smart_info: {
138
- active: node_settings.active,
154
+ last_result: node_settings.last_result,
139
155
  hysteresis: node_settings.hysteresis,
140
156
  setpoint: node_settings.setpoint,
141
- last_value: value
157
+ last_value: node_settings.last_value,
158
+ value: value
142
159
  }
143
160
  })
144
161
  };
145
162
 
146
- if (config.save_state && config.resend_on_start && node_settings.active != null && node_settings.last_message != null)
163
+ if (config.save_state && config.resend_on_start && node_settings.last_result != null && node_settings.last_message != null)
147
164
  {
148
165
  setTimeout(() =>
149
166
  {
150
- if (node_settings.active)
167
+ if (node_settings.last_result)
151
168
  node.send([node_settings.last_message, null]);
152
169
  else
153
170
  node.send([null, node_settings.last_message]);
package/logic/logic.html CHANGED
@@ -13,6 +13,8 @@
13
13
  out_false_type: { value: 'json' },
14
14
  out_true: { value: '{"topic": ""}' },
15
15
  out_true_type: { value: 'json' },
16
+ send_only_change: { value: true },
17
+ outputs: { value: 1 },
16
18
  save_state: { value: true },
17
19
  resend_on_start: { value: true }
18
20
  },
@@ -23,6 +25,10 @@
23
25
  {
24
26
  return this.name || this.logic || "Binary Logic";
25
27
  },
28
+ outputLabels: function (index)
29
+ {
30
+ return this.outputs == 1 ? "" : ["Wahr", "Falsch"][index];
31
+ },
26
32
  oneditprepare: function ()
27
33
  {
28
34
  $("#node-input-logic").typedInput({
@@ -88,6 +94,30 @@
88
94
  typeField: "#node-input-out_false_type"
89
95
  });
90
96
 
97
+ $("#node-input-send_only_change").typedInput({
98
+ type: "bool",
99
+ types: [{
100
+ value: "1",
101
+ options: [
102
+ { value: "true", label: "Nur bei Änderung" },
103
+ { value: "false", label: "Immer" },
104
+ ]
105
+ }]
106
+ });
107
+
108
+ $("#node-input-outputs").typedInput({
109
+ type: "num",
110
+ types: [{
111
+ value: "1",
112
+ options: [
113
+ { value: "1", label: "Gemeinsamer Ausgang" },
114
+ { value: "2", label: "Separate Ausgänge" },
115
+ ]
116
+ }]
117
+ });
118
+
119
+
120
+
91
121
  $("#node-input-save_state").on("change", ev =>
92
122
  {
93
123
  if (ev.target.checked)
@@ -159,7 +189,17 @@
159
189
  <input type="hidden" id="node-input-out_false_type">
160
190
  </div>
161
191
  <div class="form-row">
162
- <span><strong>Hinweis:</strong> <code>msg.payload</code> wird automatisch auf das Logik-Ergebnis gesetzt.</span>
192
+ <label for="node-input-send_only_change"><i class="fa fa-repeat"></i> Senden</label>
193
+ <input id="node-input-send_only_change" />
194
+ </div>
195
+ <div class="form-row">
196
+ <label for="node-input-outputs"><i class="fa fa-hashtag"></i> Ausgänge</label>
197
+ <input id="node-input-outputs" />
198
+ </div>
199
+ <div class="form-row">
200
+ <div><strong>Hinweis:</strong></div>
201
+ <div><code>msg.payload</code> wird automatisch auf das Logik-Ergebnis gesetzt,</div>
202
+ <div>sofern es nicht bereits hier in der Konfiguration gesetzt wurde.</div>
163
203
  </div>
164
204
  <hr/>
165
205
  <h4 style="margin: 0.5rem 0;">Systemstart</h4>
@@ -181,4 +221,7 @@
181
221
  <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/>
182
222
  Diese Node verwendet nur den Teil <code>nummer</code>. <code>name</code> und <code>#</code> sind dabei optional.
183
223
  </p>
224
+ <p>
225
+ <strong>Hinweis:</strong> <code>msg.payload</code> wird automatisch auf das Logik-Ergebnis gesetzt, sofern es nicht bereits in der Konfiguration gesetzt wurde.
226
+ </p>
184
227
  </script>
package/logic/logic.js CHANGED
@@ -10,19 +10,10 @@ module.exports = function (RED)
10
10
  const smart_context = require("../persistence.js")(RED);
11
11
  const helper = require("../smart_helper.js");
12
12
 
13
- // dynamic config
14
- let logic = config.logic;
15
- let out_true = helper.evaluateNodeProperty(RED, config.out_true, config.out_true_type || "json");
16
- let out_false = helper.evaluateNodeProperty(RED, config.out_false, config.out_false_type || "json");
17
- let logic_inputs = config.logic_inputs;
18
- let inverts = config.inverts.split(",").map(n => parseInt(n));
19
- let invert_output = config.invert_output;
20
-
21
- // runtime values
22
-
23
-
13
+ // persistant values
24
14
  var node_settings = {
25
- input_states: Array.from({ length: logic_inputs }).fill(false),
15
+ input_states: Array.from({ length: config.logic_inputs }).fill(false),
16
+ last_result: null,
26
17
  last_message: null,
27
18
  };
28
19
 
@@ -37,6 +28,18 @@ module.exports = function (RED)
37
28
  smart_context.del(node.id);
38
29
  }
39
30
 
31
+ // dynamic config
32
+ let logic = config.logic;
33
+ let out_true = helper.evaluateNodeProperty(RED, config.out_true, config.out_true_type || "json");
34
+ let out_false = helper.evaluateNodeProperty(RED, config.out_false, config.out_false_type || "json");
35
+ let logic_inputs = config.logic_inputs;
36
+ let inverts = config.inverts.split(",").map(n => parseInt(n));
37
+ let invert_output = config.invert_output;
38
+ let send_only_change = helper.evaluateNodeProperty(RED, config.send_only_change, "bool");
39
+ let outputs = helper.evaluateNodeProperty(RED, config.outputs, "num");
40
+
41
+ // runtime values
42
+
40
43
  node.on("input", function (msg)
41
44
  {
42
45
  if (typeof msg.topic === "undefined")
@@ -57,26 +60,45 @@ module.exports = function (RED)
57
60
  else
58
61
  node_settings.input_states[input - 1] = !!msg.payload; // !! => Convert to boolean
59
62
 
60
- setStatus();
61
63
  let result = getResult();
64
+ let out_msg = null;
65
+
66
+ setStatus();
62
67
 
68
+ // Get custom output message
63
69
  if (invert_output ? !result : result)
64
70
  {
65
- result = out_true;
66
- if (result !== null)
67
- result.payload = true;
71
+ if (out_true !== null)
72
+ out_msg = Object.assign({}, out_true);
68
73
  }
69
74
  else
70
75
  {
71
- result = out_false;
72
- if (result !== null)
73
- result.payload = false;
76
+ if (out_false !== null)
77
+ out_msg = Object.assign({}, out_false);
74
78
  }
75
79
 
76
- if (result !== null && node_settings.last_message?.payload != result.payload)
77
- node.send(result);
80
+ if (out_msg !== null)
81
+ {
82
+ // Overwrite automatic values, if not already defined
83
+ if (typeof out_msg.payload === "undefined")
84
+ out_msg.payload = result;
85
+
86
+ // Separate outputs if needed
87
+ if (outputs == 2)
88
+ {
89
+ if (result)
90
+ out_msg = [out_msg, null];
91
+ else
92
+ out_msg = [null, out_msg];
93
+ }
94
+
95
+ // Send only if needed
96
+ if (send_only_change == false || node_settings.last_result != result)
97
+ node.send(out_msg);
98
+ }
78
99
 
79
- node_settings.last_message = result;
100
+ node_settings.last_result = result;
101
+ node_settings.last_message = out_msg;
80
102
 
81
103
  if (config.save_state)
82
104
  smart_context.set(node.id, node_settings);
@@ -15,7 +15,7 @@
15
15
  inputs: 1,
16
16
  outputs: 3,
17
17
  outputLabels: ["Open", "Close", "Status Position"],
18
- icon: "font-awesome/fa-arrows-h",
18
+ icon: "mixing-valve.png",
19
19
  label: function ()
20
20
  {
21
21
  return this.name || "Mixing Valve";
@@ -93,7 +93,7 @@ module.exports = function (RED)
93
93
  let new_setpoint = parseFloat(msg.payload);
94
94
  if (isNaN(new_setpoint) && !isFinite(new_setpoint))
95
95
  {
96
- node.error("Invalid payload: " + msg.payload);
96
+ // node.error("Invalid payload: " + msg.payload);
97
97
  return;
98
98
  }
99
99
  node_settings.setpoint = new_setpoint;
@@ -136,7 +136,7 @@ module.exports = function (RED)
136
136
  let new_temp = parseFloat(msg.payload);
137
137
  if (isNaN(new_temp) && !isFinite(new_temp))
138
138
  {
139
- node.error("Invalid payload: " + msg.payload);
139
+ // node.error("Invalid payload: " + msg.payload);
140
140
  return;
141
141
  }
142
142
  current_temperature = new_temp;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "smart-nodes",
3
- "version": "0.3.26",
3
+ "version": "0.3.28",
4
4
  "description": "Smart Nodes",
5
5
  "keywords": [
6
6
  "node-red",
@@ -140,6 +140,8 @@
140
140
  name: { value: "" },
141
141
  exec_text_names: { value: "" },
142
142
  max_time: { value: 60 },
143
+ max_time_up: { value: 60 },
144
+ max_time_down: { value: 60 },
143
145
  revert_time_ms: { value: 100 },
144
146
  alarm_action: { value: 'NOTHING' }, // NOTHING | UP | DOWN
145
147
  links: { value: [], type: "smart_central-control[]" }
@@ -159,7 +161,7 @@
159
161
  onEditPrepare(this, ["smart_central-control"]);
160
162
  initTreeList(node, ["smart_central-control"]);
161
163
 
162
- $("#node-input-max_time").spinner({
164
+ $("#node-input-max_time_up").spinner({
163
165
  min: 1,
164
166
  change: function (event, ui)
165
167
  {
@@ -171,6 +173,28 @@
171
173
  $(this).spinner("value", value);
172
174
  }
173
175
  });
176
+ $("#node-input-max_time_down").spinner({
177
+ min: 1,
178
+ change: function (event, ui)
179
+ {
180
+ var value = parseInt(this.value);
181
+ value = isNaN(value) ? 0 : value;
182
+ value = Math.max(value, parseInt($(this).attr("aria-valuemin")));
183
+ // value = Math.min(value, parseInt($(this).attr("aria-valuemax")));
184
+ if (value !== this.value)
185
+ $(this).spinner("value", value);
186
+ }
187
+ });
188
+
189
+ // Backward compatibility
190
+ if (typeof node.max_time !== "undefined")
191
+ {
192
+ node.max_time_down = node.max_time;
193
+ node.max_time_up = node.max_time;
194
+ $("#node-input-max_time_down").val(node.max_time);
195
+ $("#node-input-max_time_up").val(node.max_time);
196
+ delete node.max_time;
197
+ }
174
198
 
175
199
  $("#node-input-revert_time_ms").spinner({
176
200
  min: 1,
@@ -218,8 +242,12 @@
218
242
  <div style="max-width: 450px;">Diese Node kann über die eingegebenen Wörter gesteuert werden. Mehrere Wörter werden durch ein Komma getrennt.</div>
219
243
  </div>
220
244
  <div class="form-row">
221
- <label for="node-input-max_time"><i class="fa fa-clock-o"></i> Zeit [s]</label>
222
- <input id="node-input-max_time" placeholder="Zeit für eine komplette Fahrt [s]" />
245
+ <label for="node-input-max_time_up"><i class="fa fa-clock-o"></i> Zeit auf [s]</label>
246
+ <input id="node-input-max_time_up" placeholder="Zeit für eine komplette Fahrt nach oben [s]" />
247
+ </div>
248
+ <div class="form-row">
249
+ <label for="node-input-max_time_down"><i class="fa fa-clock-o"></i> Zeit ab [s]</label>
250
+ <input id="node-input-max_time_down" placeholder="Zeit für eine komplette Fahrt nach unten [s]" />
223
251
  </div>
224
252
  <div class="form-row">
225
253
  <label for="node-input-revert_time_ms"><i class="fa fa-clock-o"></i> Pause Wechsel [ms]</label>
@@ -312,8 +340,8 @@
312
340
  </table>
313
341
  </p>
314
342
  <p>
315
- Diese Node verwaltet keine Laufzeit für den Rollladen selbst. Diese muss über ETS für den Ausgang konfiguriert werden.
316
- Es ist jedoch möglich, den Rollladen nach einer definierten Zeit automatisch abzuschalten.
343
+ Diese Node verwaltet die Laufzeit für den Rollladen selbst.
344
+ Es ist aber auch möglich, den Rollladen nach einer definierten Zeit automatisch abschalten zu lassen.
317
345
  Beispiel: <code>msg = { "topic": "up", "time_on": 5000 }</code> oder <code>msg = { "topic": "up", "time_on": "5s" }</code><br/>
318
346
  Diese Nachricht lässt den Rollladen für 5000 Millisekunden / 5 Sekunden nach oben fahren. Sollte es sich um eine Jalousie halten, werden die Lamellen entsprechend gedreht.
319
347
  Als Einheit für die Zeit können folgende Werte verwendet werden:
@@ -344,6 +372,11 @@
344
372
  </tbody>
345
373
  </table>
346
374
  </p>
375
+ <p>
376
+ Um mögliche Positionsabweichungen zu korrigieren läuft der Rollladen immer für mindestens 5 Sekunden, wenn nichts anderes in <code>msg.on_time</code> eingestellt wurde.
377
+ Dadurch kann der Rollladen auch noch weiter hochgefahren werden, falls er im System als offen erkannt wird, er aber in Wirklichkeit noch nicht ganz oben sein sollte.<br>
378
+ Manchmal kann es aber auch erwünscht sein, dass ein Rollladen bei 0 oder 100 % nicht nochmals startet. Dafür muss dann <code>msg.exact = true</code> gesetzt werden.
379
+ </p>
347
380
  <p>
348
381
  Die Angabe einer Zeit funktioniert nicht mit dem topic <b>position</b>.
349
382
  </p>