smart-nodes 0.3.22 → 0.3.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/LICENSE.md +1 -1
- package/README.md +51 -21
- package/central/central.js +10 -8
- package/compare/compare.html +48 -0
- package/compare/compare.js +36 -20
- package/counter/counter.html +104 -0
- package/counter/counter.js +120 -0
- package/delay/delay.js +39 -37
- package/examples/heating-curve.json +480 -0
- package/examples/heating-curve.png +0 -0
- package/examples/mixing-valve.json +622 -0
- package/examples/mixing-valve.png +0 -0
- package/forwarder/forwarder.js +28 -26
- package/heating-curve/heating-curve.html +348 -0
- package/heating-curve/heating-curve.js +134 -0
- package/hysteresis/hysteresis.js +47 -46
- package/icons/smart_counter.png +0 -0
- package/light-control/light-control.js +30 -28
- package/logic/logic.html +18 -2
- package/logic/logic.js +25 -23
- package/long-press-control/long-press-control.js +6 -4
- package/mixing-valve/mixing-valve.html +188 -0
- package/mixing-valve/mixing-valve.js +357 -0
- package/multi-press-control/multi-press-control.js +3 -1
- package/package.json +8 -1
- package/scene-control/scene-control.js +29 -27
- package/scheduler/scheduler.js +27 -25
- package/shutter-complex-control/shutter-complex-control.js +62 -62
- package/shutter-control/shutter-control.js +39 -37
- package/smart_helper.js +135 -20
- package/statistic/statistic.js +30 -28
- package/text-exec/text-exec.js +3 -1
- package/LICENSE +0 -21
package/forwarder/forwarder.js
CHANGED
|
@@ -1,33 +1,35 @@
|
|
|
1
1
|
module.exports = function (RED)
|
|
2
2
|
{
|
|
3
|
+
"use strict";
|
|
4
|
+
|
|
3
5
|
function ForwarderNode(config)
|
|
4
6
|
{
|
|
5
7
|
const node = this;
|
|
6
8
|
RED.nodes.createNode(node, config);
|
|
7
9
|
|
|
8
|
-
const
|
|
10
|
+
const smart_context = require("../persistence.js")(RED);
|
|
9
11
|
const helper = require("../smart_helper.js");
|
|
10
12
|
|
|
11
|
-
var
|
|
13
|
+
var node_settings = {
|
|
12
14
|
enabled: config.enabled,
|
|
13
|
-
|
|
15
|
+
last_message: null,
|
|
14
16
|
last_msg_was_sended: true
|
|
15
17
|
};
|
|
16
18
|
|
|
17
19
|
if (config.save_state)
|
|
18
20
|
{
|
|
19
21
|
// load old saved values
|
|
20
|
-
|
|
22
|
+
node_settings = Object.assign(node_settings, smart_context.get(node.id));
|
|
21
23
|
}
|
|
22
24
|
else
|
|
23
25
|
{
|
|
24
26
|
// delete old saved values
|
|
25
|
-
|
|
27
|
+
smart_context.del(node.id);
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
// dynamic config
|
|
29
|
-
let
|
|
30
|
-
let
|
|
31
|
+
let forward_true = config.always_forward_true;
|
|
32
|
+
let forward_false = config.always_forward_false;
|
|
31
33
|
let forward_last_on_enable = config.forward_last_on_enable;
|
|
32
34
|
|
|
33
35
|
// runtime values
|
|
@@ -35,29 +37,29 @@ module.exports = function (RED)
|
|
|
35
37
|
|
|
36
38
|
node.on("input", function (msg)
|
|
37
39
|
{
|
|
38
|
-
let
|
|
40
|
+
let new_state = null;
|
|
39
41
|
if (msg.topic == "enable" || (msg.topic == "set_state" && msg.payload))
|
|
40
|
-
|
|
42
|
+
new_state = true;
|
|
41
43
|
else if (msg.topic == "disable" || (msg.topic == "set_state" && !msg.payload))
|
|
42
|
-
|
|
44
|
+
new_state = false;
|
|
43
45
|
|
|
44
46
|
// Already the correct state
|
|
45
|
-
if (
|
|
47
|
+
if (new_state != null && node_settings.enabled == new_state)
|
|
46
48
|
return;
|
|
47
49
|
|
|
48
|
-
switch (
|
|
50
|
+
switch (new_state)
|
|
49
51
|
{
|
|
50
52
|
case true:
|
|
51
53
|
case false:
|
|
52
|
-
|
|
54
|
+
node_settings.enabled = new_state;
|
|
53
55
|
|
|
54
56
|
if (config.save_state)
|
|
55
|
-
|
|
57
|
+
smart_context.set(node.id, node_settings);
|
|
56
58
|
|
|
57
|
-
if (
|
|
59
|
+
if (node_settings.enabled && forward_last_on_enable && node_settings.last_message != null && !node_settings.last_msg_was_sended)
|
|
58
60
|
{
|
|
59
|
-
node.send(
|
|
60
|
-
|
|
61
|
+
node.send(node_settings.last_message);
|
|
62
|
+
node_settings.last_msg_was_sended = true;
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
setStatus();
|
|
@@ -65,34 +67,34 @@ module.exports = function (RED)
|
|
|
65
67
|
|
|
66
68
|
default:
|
|
67
69
|
// Forward if enabled or forced
|
|
68
|
-
if (
|
|
70
|
+
if (node_settings.enabled || (forward_true && msg.payload) || (forward_false && !msg.payload))
|
|
69
71
|
{
|
|
70
72
|
node.send(msg);
|
|
71
|
-
|
|
73
|
+
node_settings.last_msg_was_sended = true;
|
|
72
74
|
}
|
|
73
75
|
else
|
|
74
76
|
{
|
|
75
|
-
|
|
77
|
+
node_settings.last_msg_was_sended = false;
|
|
76
78
|
}
|
|
77
79
|
|
|
78
|
-
|
|
80
|
+
node_settings.last_message = msg;
|
|
79
81
|
break;
|
|
80
82
|
}
|
|
81
83
|
});
|
|
82
84
|
|
|
83
85
|
let setStatus = () =>
|
|
84
86
|
{
|
|
85
|
-
if (
|
|
86
|
-
node.status({ fill: "green", shape: "dot", text:
|
|
87
|
+
if (node_settings.enabled)
|
|
88
|
+
node.status({ fill: "green", shape: "dot", text: helper.getCurrentTimeForStatus() + ": Forwarding enabled" });
|
|
87
89
|
else
|
|
88
|
-
node.status({ fill: "red", shape: "dot", text:
|
|
90
|
+
node.status({ fill: "red", shape: "dot", text: helper.getCurrentTimeForStatus() + ": Forwarding disabled" });
|
|
89
91
|
}
|
|
90
92
|
|
|
91
|
-
if (config.save_state && config.resend_on_start &&
|
|
93
|
+
if (config.save_state && config.resend_on_start && node_settings.last_message != null)
|
|
92
94
|
{
|
|
93
95
|
setTimeout(() =>
|
|
94
96
|
{
|
|
95
|
-
node.send(
|
|
97
|
+
node.send(node_settings.last_message);
|
|
96
98
|
}, 10000);
|
|
97
99
|
}
|
|
98
100
|
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
<script type="text/javascript">
|
|
2
|
+
(function ()
|
|
3
|
+
{
|
|
4
|
+
let node;
|
|
5
|
+
|
|
6
|
+
RED.nodes.registerType("smart_heating-curve", {
|
|
7
|
+
category: "Smart Nodes",
|
|
8
|
+
paletteLabel: "Heating Curve",
|
|
9
|
+
color: "#3FADB5",
|
|
10
|
+
defaults: {
|
|
11
|
+
name: { value: "" },
|
|
12
|
+
room_setpoint: { value: 20 },
|
|
13
|
+
slope: { value: 1.3 },
|
|
14
|
+
offset: { value: 0 },
|
|
15
|
+
flow_max: { value: 75 },
|
|
16
|
+
flow_min: { value: 20 },
|
|
17
|
+
},
|
|
18
|
+
inputs: 1,
|
|
19
|
+
outputs: 1,
|
|
20
|
+
icon: "font-awesome/fa-line-chart",
|
|
21
|
+
label: function ()
|
|
22
|
+
{
|
|
23
|
+
return this.name || "Heating Curve";
|
|
24
|
+
},
|
|
25
|
+
oneditprepare: function ()
|
|
26
|
+
{
|
|
27
|
+
node = this;
|
|
28
|
+
|
|
29
|
+
$("#node-input-room_setpoint")
|
|
30
|
+
.spinner({
|
|
31
|
+
min: 15,
|
|
32
|
+
max: 25,
|
|
33
|
+
step: 0.5,
|
|
34
|
+
change: function (event, ui)
|
|
35
|
+
{
|
|
36
|
+
var value = parseFloat(this.value);
|
|
37
|
+
value = isNaN(value) ? 0 : value;
|
|
38
|
+
value = Math.max(value, parseFloat($(this).attr("aria-valuemin")));
|
|
39
|
+
value = Math.min(value, parseFloat($(this).attr("aria-valuemax")));
|
|
40
|
+
if (value !== this.value) $(this).spinner("value", value);
|
|
41
|
+
drawCanvas();
|
|
42
|
+
},
|
|
43
|
+
}).css("max-width", "4rem");
|
|
44
|
+
|
|
45
|
+
$("#node-input-slope")
|
|
46
|
+
.spinner({
|
|
47
|
+
min: 0.05,
|
|
48
|
+
max: 4,
|
|
49
|
+
step: 0.05,
|
|
50
|
+
change: function (event, ui)
|
|
51
|
+
{
|
|
52
|
+
var value = parseFloat(this.value);
|
|
53
|
+
value = isNaN(value) ? 0 : value;
|
|
54
|
+
value = Math.max(value, parseFloat($(this).attr("aria-valuemin")));
|
|
55
|
+
value = Math.min(value, parseFloat($(this).attr("aria-valuemax")));
|
|
56
|
+
if (value !== this.value) $(this).spinner("value", value);
|
|
57
|
+
drawCanvas();
|
|
58
|
+
},
|
|
59
|
+
}).css("max-width", "4rem");
|
|
60
|
+
|
|
61
|
+
$("#node-input-offset")
|
|
62
|
+
.spinner({
|
|
63
|
+
min: -20,
|
|
64
|
+
max: 20,
|
|
65
|
+
step: 0.5,
|
|
66
|
+
change: function (event, ui)
|
|
67
|
+
{
|
|
68
|
+
var value = parseFloat(this.value);
|
|
69
|
+
value = isNaN(value) ? 0 : value;
|
|
70
|
+
value = Math.max(value, parseFloat($(this).attr("aria-valuemin")));
|
|
71
|
+
value = Math.min(value, parseFloat($(this).attr("aria-valuemax")));
|
|
72
|
+
if (value !== this.value) $(this).spinner("value", value);
|
|
73
|
+
drawCanvas();
|
|
74
|
+
},
|
|
75
|
+
}).css("max-width", "4rem");
|
|
76
|
+
|
|
77
|
+
$("#node-input-flow_max")
|
|
78
|
+
.spinner({
|
|
79
|
+
min: 50,
|
|
80
|
+
max: 85,
|
|
81
|
+
change: function (event, ui)
|
|
82
|
+
{
|
|
83
|
+
var value = parseFloat(this.value);
|
|
84
|
+
value = isNaN(value) ? 0 : value;
|
|
85
|
+
value = Math.max(value, parseFloat($(this).attr("aria-valuemin")));
|
|
86
|
+
value = Math.min(value, parseFloat($(this).attr("aria-valuemax")));
|
|
87
|
+
if (value !== this.value) $(this).spinner("value", value);
|
|
88
|
+
drawCanvas();
|
|
89
|
+
},
|
|
90
|
+
}).css("max-width", "4rem");
|
|
91
|
+
|
|
92
|
+
$("#node-input-flow_min")
|
|
93
|
+
.spinner({
|
|
94
|
+
min: 5,
|
|
95
|
+
max: 30,
|
|
96
|
+
change: function (event, ui)
|
|
97
|
+
{
|
|
98
|
+
var value = parseFloat(this.value);
|
|
99
|
+
value = isNaN(value) ? 0 : value;
|
|
100
|
+
value = Math.max(value, parseFloat($(this).attr("aria-valuemin")));
|
|
101
|
+
value = Math.min(value, parseFloat($(this).attr("aria-valuemax")));
|
|
102
|
+
if (value !== this.value) $(this).spinner("value", value);
|
|
103
|
+
drawCanvas();
|
|
104
|
+
},
|
|
105
|
+
}).css("max-width", "4rem");
|
|
106
|
+
|
|
107
|
+
drawCanvas();
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
let drawCanvas = () =>
|
|
112
|
+
{
|
|
113
|
+
const canvas = $("#heating-curve-diagram")[0];
|
|
114
|
+
const ctx = canvas.getContext("2d");
|
|
115
|
+
ctx.reset();
|
|
116
|
+
|
|
117
|
+
drawAxis(canvas, ctx);
|
|
118
|
+
drawGrid(canvas, ctx);
|
|
119
|
+
drawCurve(canvas, ctx, "#808080", 0.5, 0);
|
|
120
|
+
drawCurve(canvas, ctx, "#808080", 1, 0);
|
|
121
|
+
drawCurve(canvas, ctx, "#808080", 1.5, 0);
|
|
122
|
+
drawCurve(canvas, ctx, "#808080", 2, 0);
|
|
123
|
+
drawCurve(canvas, ctx, "#808080", 2.5, 0);
|
|
124
|
+
drawCurve(canvas, ctx, "#808080", 3, 0);
|
|
125
|
+
drawCurve(canvas, ctx, "#808080", 3.5, 0);
|
|
126
|
+
drawCurve(canvas, ctx, "#808080", 4, 0);
|
|
127
|
+
|
|
128
|
+
drawCurve(canvas, ctx, "#0000FF", parseFloat($("#node-input-slope").val()), parseFloat($("#node-input-offset").val()), true);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
let mapPoint = (out, flow) =>
|
|
132
|
+
{
|
|
133
|
+
const canvas = $("#heating-curve-diagram")[0];
|
|
134
|
+
return [scale(out, -20, 25, canvas.width, 0), scale(flow, 10, 100, canvas.height, 0)];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
let scale = (number, inMin, inMax, outMin, outMax) =>
|
|
138
|
+
{
|
|
139
|
+
return (number - inMin) * (outMax - outMin) / (inMax - inMin) + outMin;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
let drawAxis = (canvas, ctx) =>
|
|
143
|
+
{
|
|
144
|
+
const arrow_length = 10;
|
|
145
|
+
|
|
146
|
+
ctx.strokeStyle = "#000000";
|
|
147
|
+
|
|
148
|
+
ctx.beginPath();
|
|
149
|
+
|
|
150
|
+
// draw axis
|
|
151
|
+
ctx.moveTo(...mapPoint(20, 100));
|
|
152
|
+
ctx.lineTo(...mapPoint(20, 10));
|
|
153
|
+
|
|
154
|
+
ctx.moveTo(...mapPoint(25, 20));
|
|
155
|
+
ctx.lineTo(...mapPoint(-20, 20));
|
|
156
|
+
|
|
157
|
+
// draw arrow up
|
|
158
|
+
let point = mapPoint(20, 100);
|
|
159
|
+
ctx.moveTo(...point);
|
|
160
|
+
ctx.lineTo(point[0] - arrow_length / 2, point[1] + arrow_length);
|
|
161
|
+
ctx.moveTo(...point);
|
|
162
|
+
ctx.lineTo(point[0] + arrow_length / 2, point[1] + arrow_length);
|
|
163
|
+
|
|
164
|
+
// draw arrow right
|
|
165
|
+
point = mapPoint(-20, 20);
|
|
166
|
+
ctx.moveTo(...point);
|
|
167
|
+
ctx.lineTo(point[0] - arrow_length, point[1] - arrow_length / 2);
|
|
168
|
+
ctx.moveTo(...point);
|
|
169
|
+
ctx.lineTo(point[0] - arrow_length, point[1] + arrow_length / 2);
|
|
170
|
+
|
|
171
|
+
ctx.stroke();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
let drawGrid = (canvas, ctx) =>
|
|
175
|
+
{
|
|
176
|
+
ctx.fillStyle = "#000000";
|
|
177
|
+
ctx.font = "15px Arial";
|
|
178
|
+
ctx.strokeStyle = "#CCCCCC";
|
|
179
|
+
|
|
180
|
+
ctx.beginPath();
|
|
181
|
+
|
|
182
|
+
// horizontal lines
|
|
183
|
+
ctx.textAlign = "right";
|
|
184
|
+
ctx.textBaseline = "middle";
|
|
185
|
+
for (let i = 30; i <= 90; i += 10)
|
|
186
|
+
{
|
|
187
|
+
let [x, y] = mapPoint(20, i);
|
|
188
|
+
|
|
189
|
+
ctx.moveTo(x - 15, y);
|
|
190
|
+
ctx.lineTo(canvas.width - 15, y);
|
|
191
|
+
|
|
192
|
+
ctx.fillText(i.toString(), 25, y);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// vertical lines
|
|
196
|
+
ctx.textAlign = "center";
|
|
197
|
+
for (let i = -15; i <= 15; i += 5)
|
|
198
|
+
{
|
|
199
|
+
let [x, y] = mapPoint(i, 20);
|
|
200
|
+
|
|
201
|
+
ctx.moveTo(x, 15);
|
|
202
|
+
ctx.lineTo(x, y + 15);
|
|
203
|
+
|
|
204
|
+
ctx.fillText(i.toString(), x, canvas.height - 10);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
ctx.stroke();
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
let drawCurve = (canvas, ctx, color, slope, offset, useMinMax = false) =>
|
|
211
|
+
{
|
|
212
|
+
console.log("Test");
|
|
213
|
+
const room_setpoint = parseFloat($("#node-input-room_setpoint").val());
|
|
214
|
+
const flow_min = parseFloat($("#node-input-flow_min").val());
|
|
215
|
+
const flow_max = parseFloat($("#node-input-flow_max").val());
|
|
216
|
+
|
|
217
|
+
ctx.textAlign = "center";
|
|
218
|
+
ctx.textBaseline = "middle";
|
|
219
|
+
ctx.font = "12px Arial";
|
|
220
|
+
ctx.strokeStyle = color;
|
|
221
|
+
|
|
222
|
+
ctx.beginPath();
|
|
223
|
+
let first = true;
|
|
224
|
+
|
|
225
|
+
let labels = [];
|
|
226
|
+
debugger;
|
|
227
|
+
|
|
228
|
+
for (let out = 20; out >= -20; out -= 1)
|
|
229
|
+
{
|
|
230
|
+
let dar = out - room_setpoint;
|
|
231
|
+
let flow = room_setpoint + offset - slope * dar * (1.4347 + 0.021 * dar + 247.9 * Math.pow(10, -6) * Math.pow(dar, 2));
|
|
232
|
+
|
|
233
|
+
if (useMinMax)
|
|
234
|
+
flow = Math.min(Math.max(flow, flow_min), flow_max);
|
|
235
|
+
|
|
236
|
+
let point = mapPoint(out, flow);
|
|
237
|
+
|
|
238
|
+
if (first)
|
|
239
|
+
ctx.moveTo(...point);
|
|
240
|
+
else
|
|
241
|
+
ctx.lineTo(...point);
|
|
242
|
+
first = false;
|
|
243
|
+
|
|
244
|
+
if (point[0] >= canvas.width)
|
|
245
|
+
{
|
|
246
|
+
let x = canvas.width - 20;
|
|
247
|
+
let y = Math.max(point[1], 20);
|
|
248
|
+
labels.push({ x, y, text: slope.toString() });
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
if (point[1] <= 0)
|
|
252
|
+
{
|
|
253
|
+
let x = Math.min(point[0] - 20, canvas.width - 20);
|
|
254
|
+
let y = 10;
|
|
255
|
+
labels.push({ x, y, text: slope.toString() });
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
ctx.stroke();
|
|
260
|
+
|
|
261
|
+
ctx.beginPath();
|
|
262
|
+
for (let label of labels)
|
|
263
|
+
{
|
|
264
|
+
ctx.fillStyle = "#FFFFFF";
|
|
265
|
+
|
|
266
|
+
ctx.rect(label.x - 10.5, label.y - 8.5, 21, 16);
|
|
267
|
+
ctx.fillRect(label.x - 10, label.y - 8, 20, 15);
|
|
268
|
+
|
|
269
|
+
ctx.fillStyle = "#000000";
|
|
270
|
+
if (useMinMax)
|
|
271
|
+
ctx.fillStyle = color;
|
|
272
|
+
|
|
273
|
+
ctx.fillText(label.text, label.x, label.y);
|
|
274
|
+
}
|
|
275
|
+
ctx.stroke();
|
|
276
|
+
}
|
|
277
|
+
})();
|
|
278
|
+
|
|
279
|
+
</script>
|
|
280
|
+
|
|
281
|
+
<script type="text/html" data-template-name="smart_heating-curve">
|
|
282
|
+
<div class="form-row">
|
|
283
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
284
|
+
<input type="text" id="node-input-name" placeholder="Name" />
|
|
285
|
+
</div>
|
|
286
|
+
<div class="form-row">
|
|
287
|
+
<label for="node-input-room_setpoint"><i class="fa fa-thermometer-three-quarters"></i> Raum Soll</label>
|
|
288
|
+
<input id="node-input-room_setpoint" value="0" /> °C [15 - 25]
|
|
289
|
+
</div>
|
|
290
|
+
<div class="form-row">
|
|
291
|
+
<label for="node-input-slope"><i class="fa fa-expand"></i> Steilheit</label>
|
|
292
|
+
<input id="node-input-slope" value="0" /> [0,05 - 4]
|
|
293
|
+
</div>
|
|
294
|
+
<div class="form-row">
|
|
295
|
+
<label for="node-input-offset"><i class="fa fa-arrows-v"></i> Verschiebung</label>
|
|
296
|
+
<input id="node-input-offset" value="0" />
|
|
297
|
+
</div>
|
|
298
|
+
<div class="form-row">
|
|
299
|
+
<label for="node-input-flow_max"><i class="fa fa-arrow-up"></i> Max Soll</label>
|
|
300
|
+
<input id="node-input-flow_max" value="0" />
|
|
301
|
+
</div>
|
|
302
|
+
<div class="form-row">
|
|
303
|
+
<label for="node-input-flow_min"><i class="fa fa-arrow-down"></i> Min Soll</label>
|
|
304
|
+
<input id="node-input-flow_min" value="0" />
|
|
305
|
+
</div>
|
|
306
|
+
<div class="form-row">
|
|
307
|
+
<canvas id="heating-curve-diagram" width="450" height="300"></canvas>
|
|
308
|
+
</div>
|
|
309
|
+
</script>
|
|
310
|
+
|
|
311
|
+
<script type="text/html" data-help-name="smart_heating-curve">
|
|
312
|
+
<p>
|
|
313
|
+
Diese Node berechnet anhand den eingestellten Werten, die Vorlaufsolltemperatur. Diese kann verwendet werden um ein Mischventil anzusteuern.
|
|
314
|
+
</p>
|
|
315
|
+
<p>
|
|
316
|
+
<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/>
|
|
317
|
+
Diese Node verwendet nur den Teil <code>name</code>. <code>#</code> und <code>nummer</code> sind dabei optional.
|
|
318
|
+
</p>
|
|
319
|
+
<p>
|
|
320
|
+
Folgende topics werden akzeptiert:
|
|
321
|
+
<table>
|
|
322
|
+
<thead>
|
|
323
|
+
<tr>
|
|
324
|
+
<th>Topic</th>
|
|
325
|
+
<th>Beschreibung</th>
|
|
326
|
+
</tr>
|
|
327
|
+
</thead>
|
|
328
|
+
<tbody>
|
|
329
|
+
<tr>
|
|
330
|
+
<td><code>room_setpoint</code></td>
|
|
331
|
+
<td>Überschreibt die Raum-Solltemperatur.</td>
|
|
332
|
+
</tr>
|
|
333
|
+
<tr>
|
|
334
|
+
<td><code>flow_min</code></td>
|
|
335
|
+
<td>Überschreibt die minimale Vorlaufsolltemperatur.</td>
|
|
336
|
+
</tr>
|
|
337
|
+
<tr>
|
|
338
|
+
<td><code>flow_max</code></td>
|
|
339
|
+
<td>Überschreibt die maximale Vorlaufsolltemperatur.</td>
|
|
340
|
+
</tr>
|
|
341
|
+
<tr>
|
|
342
|
+
<td><code>temperature_outside</code></td>
|
|
343
|
+
<td>Setzt die aktuelle Außentemperatur.</td>
|
|
344
|
+
</tr>
|
|
345
|
+
</tbody>
|
|
346
|
+
</table>
|
|
347
|
+
</p>
|
|
348
|
+
</script>
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
module.exports = function (RED)
|
|
2
|
+
{
|
|
3
|
+
"use strict";
|
|
4
|
+
|
|
5
|
+
function HeatingCurveNode(config)
|
|
6
|
+
{
|
|
7
|
+
const node = this;
|
|
8
|
+
RED.nodes.createNode(node, config);
|
|
9
|
+
|
|
10
|
+
const smart_context = require("../persistence.js")(RED);
|
|
11
|
+
const helper = require("../smart_helper.js");
|
|
12
|
+
|
|
13
|
+
// persistent values
|
|
14
|
+
var node_settings = Object.assign({}, {
|
|
15
|
+
room_setpoint: config.room_setpoint,
|
|
16
|
+
flow_min: config.flow_min,
|
|
17
|
+
flow_max: config.flow_max,
|
|
18
|
+
temperature_outside: 10,
|
|
19
|
+
last_flow_temperature: null
|
|
20
|
+
}, smart_context.get(node.id));
|
|
21
|
+
|
|
22
|
+
// dynamic config
|
|
23
|
+
let slope = config.slope;
|
|
24
|
+
let offset = config.offset;
|
|
25
|
+
|
|
26
|
+
// runtime values
|
|
27
|
+
|
|
28
|
+
node.on("input", function (msg)
|
|
29
|
+
{
|
|
30
|
+
handleTopic(msg);
|
|
31
|
+
sendResult();
|
|
32
|
+
setStatus();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
node.on("close", function ()
|
|
36
|
+
{
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
let handleTopic = msg =>
|
|
41
|
+
{
|
|
42
|
+
let real_topic = helper.getTopicName(msg.topic);
|
|
43
|
+
switch (real_topic)
|
|
44
|
+
{
|
|
45
|
+
case "room_setpoint":
|
|
46
|
+
let new_setpoint = parseFloat(msg.payload);
|
|
47
|
+
if (isNaN(new_setpoint) && !isFinite(new_setpoint))
|
|
48
|
+
{
|
|
49
|
+
node.error("Invalid payload: " + msg.payload);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
node_settings.room_setpoint = msg.payload;
|
|
54
|
+
smart_context.set(node.id, node_settings);
|
|
55
|
+
break;
|
|
56
|
+
|
|
57
|
+
case "temperature_outside":
|
|
58
|
+
let new_temp = parseFloat(msg.payload);
|
|
59
|
+
if (isNaN(new_temp) && !isFinite(new_temp))
|
|
60
|
+
{
|
|
61
|
+
node.error("Invalid payload: " + msg.payload);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
node_settings.temperature_outside = msg.payload;
|
|
66
|
+
smart_context.set(node.id, node_settings);
|
|
67
|
+
break;
|
|
68
|
+
|
|
69
|
+
case "flow_min":
|
|
70
|
+
let new_flow_min = parseFloat(msg.payload);
|
|
71
|
+
if (isNaN(new_flow_min) && !isFinite(new_flow_min))
|
|
72
|
+
{
|
|
73
|
+
node.error("Invalid payload: " + msg.payload);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
node_settings.flow_min = msg.payload;
|
|
78
|
+
smart_context.set(node.id, node_settings);
|
|
79
|
+
break;
|
|
80
|
+
|
|
81
|
+
case "flow_max":
|
|
82
|
+
let new_flow_max = parseFloat(msg.payload);
|
|
83
|
+
if (isNaN(new_flow_max) && !isFinite(new_flow_max))
|
|
84
|
+
{
|
|
85
|
+
node.error("Invalid payload: " + msg.payload);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
node_settings.flow_max = msg.payload;
|
|
90
|
+
smart_context.set(node.id, node_settings);
|
|
91
|
+
break;
|
|
92
|
+
|
|
93
|
+
default:
|
|
94
|
+
node.error("Invalid topic: " + real_topic);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
let sendResult = () =>
|
|
100
|
+
{
|
|
101
|
+
// Formula used was found here:
|
|
102
|
+
// https://community.viessmann.de/t5/Gas/Mathematische-Formel-fuer-Vorlauftemperatur-aus-den-vier/td-p/68843
|
|
103
|
+
|
|
104
|
+
let dar = node_settings.temperature_outside - node_settings.room_setpoint;
|
|
105
|
+
node_settings.last_flow_temperature = node_settings.room_setpoint + offset - slope * dar * (1.4347 + 0.021 * dar + 247.9 * Math.pow(10, -6) * Math.pow(dar, 2));
|
|
106
|
+
|
|
107
|
+
// console.log({
|
|
108
|
+
// set: node_settings.room_setpoint,
|
|
109
|
+
// offset,
|
|
110
|
+
// slope,
|
|
111
|
+
// dar,
|
|
112
|
+
// flow: node_settings.last_flow_temperature
|
|
113
|
+
// });
|
|
114
|
+
|
|
115
|
+
// Check borders
|
|
116
|
+
node_settings.last_flow_temperature = Math.min(Math.max(node_settings.last_flow_temperature, node_settings.flow_min), node_settings.flow_max);
|
|
117
|
+
smart_context.set(node.id, node_settings);
|
|
118
|
+
|
|
119
|
+
node.send({ payload: node_settings.last_flow_temperature });
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
let setStatus = () =>
|
|
123
|
+
{
|
|
124
|
+
node.status({ fill: "green", shape: "dot", text: helper.getCurrentTimeForStatus() + ": Out: " + node_settings.temperature_outside.toFixed(1) + "°C, Flow: " + node_settings.last_flow_temperature?.toFixed(1) + "°C" });
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (node_settings.last_flow_temperature !== null)
|
|
128
|
+
setTimeout(sendResult, 10 * 1000);
|
|
129
|
+
|
|
130
|
+
setStatus();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
RED.nodes.registerType("smart_heating-curve", HeatingCurveNode);
|
|
134
|
+
}
|