node-red-contrib-power-saver 3.2.3 → 3.3.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/docs/.vuepress/config.js +4 -0
- package/docs/changelog/README.md +9 -0
- package/docs/examples/example-nordpool-current-state.md +0 -1
- package/docs/examples/example-tibber-mqtt.md +0 -1
- package/docs/faq/README.md +5 -1
- package/docs/images/best-save-config.png +0 -0
- package/docs/nodes/ps-strategy-best-save.md +77 -8
- package/docs/nodes/ps-strategy-lowest-price.md +43 -0
- package/package.json +1 -1
- package/src/handle-input.js +30 -8
- package/src/strategy-best-save.html +0 -21
- package/src/strategy-best-save.js +8 -11
- package/test/commands-input.test.js +47 -0
- package/test/data/best-save-result.json +1 -2
- package/test/data/current-undefined-prices.json +197 -0
- package/test/data/current-undefined-result.json +208 -0
- package/test/data/lowest-price-result-missing-end.json +2 -1
- package/test/data/reconfigResult.js +56 -294
- package/test/data/reconfigResult_old2.js +75 -0
- package/test/data/tibber-result-end-0-24h.json +1 -0
- package/test/data/tibber-result-end-0.json +1 -0
- package/test/send-config-input.test.js +3 -37
- package/test/strategy-best-save-test-utils.js +2 -1
- package/test/test-utils.js +1 -7
- package/test/utils.test.js +0 -33
- package/test/data/adjustedResult.js +0 -302
- package/test/data/adjustedResult_old.js +0 -154
package/docs/.vuepress/config.js
CHANGED
package/docs/changelog/README.md
CHANGED
|
@@ -6,6 +6,15 @@ sidebar: "auto"
|
|
|
6
6
|
|
|
7
7
|
List the most significant changes, starting in version 1.0.9.
|
|
8
8
|
|
|
9
|
+
## 3.3.0
|
|
10
|
+
|
|
11
|
+
- Remove the config option to schedule from the current hour. The feature did not work, and it was not clear how it should work.
|
|
12
|
+
- Added a dynamic command feature to make it possible to dynamically
|
|
13
|
+
1. Tell the node to send the schedule to output 3.
|
|
14
|
+
2. Reset saved data making the next schedule to start without historical data.
|
|
15
|
+
- Fix node status so it says "No price data" when there is no price data available.
|
|
16
|
+
- Added an FAQ section to the doc.
|
|
17
|
+
|
|
9
18
|
## 3.2.3
|
|
10
19
|
|
|
11
20
|
- Remove unused imports
|
|
@@ -154,7 +154,6 @@ In this example, data is read from the Nord Pool sensor in HA via the `current s
|
|
|
154
154
|
"minSaving": "0.03",
|
|
155
155
|
"sendCurrentValueWhenRescheduling": true,
|
|
156
156
|
"outputIfNoSchedule": "true",
|
|
157
|
-
"scheduleOnlyFromCurrentTime": "false",
|
|
158
157
|
"x": 630,
|
|
159
158
|
"y": 380,
|
|
160
159
|
"wires": [
|
|
@@ -120,7 +120,6 @@ In this example, data is read from Tibber and used to turn on/off a switch, sche
|
|
|
120
120
|
"minSaving": "0.05",
|
|
121
121
|
"sendCurrentValueWhenRescheduling": true,
|
|
122
122
|
"outputIfNoSchedule": "true",
|
|
123
|
-
"scheduleOnlyFromCurrentTime": "false",
|
|
124
123
|
"x": 490,
|
|
125
124
|
"y": 160,
|
|
126
125
|
"wires": [["467a5fe.d0bbba", "5e485ff7.db156"], ["9c978d1c.ee76", "467a5fe.d0bbba"], ["42d8b632.402e38"]]
|
package/docs/faq/README.md
CHANGED
|
@@ -12,7 +12,7 @@ Why does it not save the most expensive period?
|
|
|
12
12
|
|
|
13
13
|
If you move the saving-period 4-5 hours earlier, the most expensive hours would have been saved. Why is this not better?
|
|
14
14
|
|
|
15
|
-
Remember that the power that is not used during the saving-period, will be used immediately after. At least, that is the idea, and the normal
|
|
15
|
+
Remember that the power that is not used during the saving-period, will be used immediately after. At least, that is the idea, and the normal behavior if the power consumer is a water heater or another heater controlled by a thermostat. So, then the power would be used one of the blue hours in stead of the green cheaper hour.
|
|
16
16
|
|
|
17
17
|
Also remember that when saving, you do not save the full price. You only save what is the difference between the price during the saved hours and the price the hour immediately after.
|
|
18
18
|
|
|
@@ -21,3 +21,7 @@ So the best saving is found where this difference is the largest, and that is no
|
|
|
21
21
|
Of course, if you expand the saving period from 5 to 10 hours you would save even more, but that would be a different case, and you can do that if it is ok for you to turn off for that long time.
|
|
22
22
|
|
|
23
23
|
Another alternative is to reduce the minimum saving from 0.05 to 0.001. Then the 3 first red hours would be turned off, but the last red would have to be on, in order to get one hour on until the next 5-hour period off. This would however not save you for much money, since the hour that is on is almost as expensive as the hours you would turn off.
|
|
24
|
+
|
|
25
|
+
## Can we get Legionella bacteria when turning off the water heater?
|
|
26
|
+
|
|
27
|
+
Many people ask if there is a danger that legionella bacteria will grow and become dangerous when the temperature of the water heater is lowered. As long as the water is heated to at least 65 °C every day, or at least every week, the risk of infection is not considered significant, according to the norwegian [FHI](https://www.fhi.no/nettpub/legionellaveilederen/).
|
|
Binary file
|
|
@@ -19,7 +19,6 @@ The picture at the bottom of the page, under [Integration with MagicMirror](#int
|
|
|
19
19
|
| Max per sequence | Maximum number of hours to turn off in a sequence. |
|
|
20
20
|
| Min recover | Minimum hours to turn on immediately after a period when turned off the maximum number of hours that is allowed to be turned off |
|
|
21
21
|
| Min saving | Minimum amount to save per kWh in order to bother turning it off. It is recommended to have some amount here, e.g. 2 cents / 2 øre. No point in saving 0.001, is it? |
|
|
22
|
-
| Schedule for | Select to schedule for the whole data set or only from the current hour. |
|
|
23
22
|
| Send when rescheduling | Check this to make sure on or off output is sent immediately after rescheduling |
|
|
24
23
|
| If no schedule, send | What to do if there is no valid schedule any more (turn on or off). |
|
|
25
24
|
|
|
@@ -27,10 +26,6 @@ The picture at the bottom of the page, under [Integration with MagicMirror](#int
|
|
|
27
26
|
NB! The `Min recover` only has effect if the previous save-period is of length `Max per sequence`. If the save-period is shorter, the following on-period may be as short as one hour.
|
|
28
27
|
:::
|
|
29
28
|
|
|
30
|
-
::: tip Legionella
|
|
31
|
-
Many people ask if there is a danger that legionella bacteria will grow and become dangerous when the temperature of the water heater is lowered. As long as the water is heated to at least 65 °C every day, or at least every week, the risk of infection is not considered significant, according to the norwegian [FHI](https://www.fhi.no/nettpub/legionellaveilederen/).
|
|
32
|
-
:::
|
|
33
|
-
|
|
34
29
|
### Dynamic config
|
|
35
30
|
|
|
36
31
|
It is possible to change config dynamically by sending a config message to the node. The config messages has a payload with a config object like this example:
|
|
@@ -43,7 +38,6 @@ It is possible to change config dynamically by sending a config message to the n
|
|
|
43
38
|
"minSaving": 0.02,
|
|
44
39
|
"sendCurrentValueWhenRescheduling": true,
|
|
45
40
|
"outputIfNoSchedule": true,
|
|
46
|
-
"scheduleOnlyFromCurrentTime": false
|
|
47
41
|
}
|
|
48
42
|
}
|
|
49
43
|
```
|
|
@@ -54,11 +48,58 @@ The config sent like this will be valid until a new config is sent the same way,
|
|
|
54
48
|
|
|
55
49
|
When a config is sent like this, and without price data, the schedule will be replanned based on the last previously received price data. If no price data has been received, no scheduling is done.
|
|
56
50
|
|
|
57
|
-
However, you can send config and price data in the same message. Then both will be used
|
|
51
|
+
However, you can send config and price data in the same message. Then both will be used.
|
|
52
|
+
|
|
53
|
+
### Dynamic commands
|
|
54
|
+
|
|
55
|
+
You can dynamically send some commands to the node via its input, by using a `commands` object in the payload as described below.
|
|
56
|
+
|
|
57
|
+
Commands can be sent together with config and/or price data, but the exact behavior is not defined.
|
|
58
|
+
|
|
59
|
+
#### sendSchedule
|
|
60
|
+
|
|
61
|
+
You can get the schedule sent to output 3 any time by sending a message like this to the node:
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
"payload": {
|
|
65
|
+
"commands": {
|
|
66
|
+
"sendSchedule": true,
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
When you do this, the current schedule is actually recalculated based on the last received data, and then sent to output 3 the same way as when it was originally planned.
|
|
72
|
+
|
|
73
|
+
#### reset
|
|
74
|
+
|
|
75
|
+
You can reset data the node has saved in context by sending this message:
|
|
76
|
+
|
|
77
|
+
```json
|
|
78
|
+
"payload": {
|
|
79
|
+
"commands": {
|
|
80
|
+
"reset": true,
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
When you do this, all historical data the node has saved is deleted, including the current schedule, so the result will be
|
|
86
|
+
that the node shows status "No price data". When new price data is received, a schedule is calculated without considering any history.
|
|
87
|
+
|
|
88
|
+
The nodes config is not deleted, as the node depends on it to work.
|
|
89
|
+
|
|
90
|
+
::: warning
|
|
91
|
+
This operation cannot be undone.
|
|
92
|
+
|
|
93
|
+
However, it is normally not a big loss, as you can just feed the node with new price data and start from scratch.
|
|
94
|
+
:::
|
|
58
95
|
|
|
59
96
|
## Input
|
|
60
97
|
|
|
61
|
-
The input is the [common strategy input format](./strategy-input.md)
|
|
98
|
+
The input is the [common strategy input format](./strategy-input.md).
|
|
99
|
+
|
|
100
|
+
In addition to the prices sent as input,
|
|
101
|
+
the node is using the schedule for the day before it receives data for,
|
|
102
|
+
so that it can calculate the schedule in the beginning of the day according to the configured rules. This requires of course that the node was run the day before.
|
|
62
103
|
|
|
63
104
|
## Output
|
|
64
105
|
|
|
@@ -125,6 +166,12 @@ Example of output:
|
|
|
125
166
|
|
|
126
167
|
The `schedule` array shows every time the switch is turned on or off. The `hours` array shows values per hour containing the price (received as input), whether that hour is on or off, the start time of the hour and the amount per kWh that is saved on hours that are turned off, compared to the next hour that is on.
|
|
127
168
|
|
|
169
|
+
### Data saved in context
|
|
170
|
+
|
|
171
|
+
The node saves some data in the nodes context, so that it can be used on restarts and also taken into consideration when calculating the schedule over midnight.
|
|
172
|
+
|
|
173
|
+
You can see the saved data if you select the node in Node-RED, and view "Context data", and refresh the Node context.
|
|
174
|
+
|
|
128
175
|
## Algorithm
|
|
129
176
|
|
|
130
177
|
The calculation that decides what hours to turn off works as follows:
|
|
@@ -139,6 +186,28 @@ The calculation that decides what hours to turn off works as follows:
|
|
|
139
186
|
|
|
140
187
|
I say "in most cases", because there is a chance that a group of two or more sequences combined can give a better plan than a single sequence preceeding those two, but where the selection of the one sequence causes the group to be discarded. If anyone encounters this situation, I would be happy to receive the price data set, and try to improve the algorithm even further.
|
|
141
188
|
|
|
189
|
+
## Data used for calculation
|
|
190
|
+
|
|
191
|
+
Normally data is received for one or two whole days, and all this data is used to do the calculation. In addition, if the node has run before, so there is historical data, the last period on or off before the period data is received for, is considered in the calculation, so that the rules in the configuration are followed also between days.
|
|
192
|
+
|
|
193
|
+
## Restarts
|
|
194
|
+
|
|
195
|
+
The node saves data in the nodes context, so if Node-RED is configured to save context between restarts, the node will replan with the last received data when it restarts.
|
|
196
|
+
|
|
197
|
+
::: warning
|
|
198
|
+
In Home Assistant, Node-RED is by default configured to save context between restarts. However, if you run Node-RED another way, this may not be the case by default. If context is only stored in memory, it is lost between restarts, and even between re-deployments. This can be changed in the `settings.js` file for Node-RED like this:
|
|
199
|
+
|
|
200
|
+
```js
|
|
201
|
+
contextStorage: {
|
|
202
|
+
default: {
|
|
203
|
+
module: "localfilesystem"
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Please read the [Node-RED documentation](https://nodered.org/docs/user-guide/context) for more details about this.
|
|
209
|
+
:::
|
|
210
|
+
|
|
142
211
|
## Integration with MagicMirror
|
|
143
212
|
|
|
144
213
|
Are you using [MagicMirror](https://magicmirror.builders/)? Are you also using [Tibber](https://tibber.com/)? If so, there is a module for MM called [MMM-Tibber](https://github.com/ottopaulsen/MMM-Tibber), that easily can be used to show savings from this node.
|
|
@@ -74,6 +74,49 @@ When a config is sent like this, and without price data, the schedule will be re
|
|
|
74
74
|
|
|
75
75
|
However, you can send config and price data in the same message. Then both will be used .
|
|
76
76
|
|
|
77
|
+
### Dynamic commands
|
|
78
|
+
|
|
79
|
+
You can dynamically send some commands to the node via its input, by using a `commands` object in the payload as described below.
|
|
80
|
+
|
|
81
|
+
Commands can be sent together with config and/or price data, but the exact behavior is not defined.
|
|
82
|
+
|
|
83
|
+
#### sendSchedule
|
|
84
|
+
|
|
85
|
+
You can get the schedule sent to output 3 any time by sending a message like this to the node:
|
|
86
|
+
|
|
87
|
+
```json
|
|
88
|
+
"payload": {
|
|
89
|
+
"commands": {
|
|
90
|
+
"sendSchedule": true,
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
When you do this, the current schedule is actually recalculated based on the last received data, and then sent to output 3 the same way as when it was originally planned.
|
|
96
|
+
|
|
97
|
+
#### reset
|
|
98
|
+
|
|
99
|
+
You can reset data the node has saved in context by sending this message:
|
|
100
|
+
|
|
101
|
+
```json
|
|
102
|
+
"payload": {
|
|
103
|
+
"commands": {
|
|
104
|
+
"reset": true,
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
When you do this, all historical data the node has saved is deleted, including the current schedule, so the result will be
|
|
110
|
+
that the node shows status "No price data". When new price data is received, a schedule is calculated without considering any history.
|
|
111
|
+
|
|
112
|
+
The nodes config is not deleted, as the node depends on it to work.
|
|
113
|
+
|
|
114
|
+
::: warning
|
|
115
|
+
This operation cannot be undone.
|
|
116
|
+
|
|
117
|
+
However, it is normally not a big loss, as you can just feed the node with new price data and start from scratch.
|
|
118
|
+
:::
|
|
119
|
+
|
|
77
120
|
## Input
|
|
78
121
|
|
|
79
122
|
The input is the [common strategy input format](./strategy-input.md)
|
package/package.json
CHANGED
package/src/handle-input.js
CHANGED
|
@@ -8,8 +8,17 @@ function handleStrategyInput(node, msg, doPlanning) {
|
|
|
8
8
|
if (!validateInput(node, msg)) {
|
|
9
9
|
return;
|
|
10
10
|
}
|
|
11
|
-
|
|
11
|
+
if (msg.payload.commands && msg.payload.commands.reset) {
|
|
12
|
+
node.warn("Resetting node context by command");
|
|
13
|
+
// Reset all saved data
|
|
14
|
+
node.context().set(["lastPlan", "lastPriceData", "lastSource"], [undefined, undefined, undefined]);
|
|
15
|
+
deleteSavedScheduleBefore(node, DateTime.now().plus({ days: 1 }), 100);
|
|
16
|
+
}
|
|
17
|
+
const { priceData, source } = getPriceData(node, msg);
|
|
12
18
|
if (!priceData) {
|
|
19
|
+
const message = "No price data";
|
|
20
|
+
node.warn(message);
|
|
21
|
+
node.status({ fill: "yellow", shape: "dot", text: message });
|
|
13
22
|
return;
|
|
14
23
|
}
|
|
15
24
|
const planFromTime = msg.payload.time ? DateTime.fromISO(msg.payload.time) : DateTime.now();
|
|
@@ -32,6 +41,8 @@ function handleStrategyInput(node, msg, doPlanning) {
|
|
|
32
41
|
node.context().set("lastPlan", plan);
|
|
33
42
|
dates.forEach((d) => saveDayData(node, d, extractPlanForDate(plan, d)));
|
|
34
43
|
|
|
44
|
+
const sentOnCommand = !!msg.payload.commands?.sendSchedule;
|
|
45
|
+
|
|
35
46
|
// Prepare output
|
|
36
47
|
let output1 = null;
|
|
37
48
|
let output2 = null;
|
|
@@ -39,8 +50,9 @@ function handleStrategyInput(node, msg, doPlanning) {
|
|
|
39
50
|
payload: {
|
|
40
51
|
schedule: plan.schedule,
|
|
41
52
|
hours: plan.hours,
|
|
42
|
-
source
|
|
53
|
+
source,
|
|
43
54
|
config: effectiveConfig,
|
|
55
|
+
sentOnCommand,
|
|
44
56
|
time: planFromTime.toISO(),
|
|
45
57
|
version,
|
|
46
58
|
},
|
|
@@ -49,7 +61,7 @@ function handleStrategyInput(node, msg, doPlanning) {
|
|
|
49
61
|
// Find current output, and set output (if configured to do)
|
|
50
62
|
const pastSchedule = plan.schedule.filter((entry) => DateTime.fromISO(entry.time) <= planFromTime);
|
|
51
63
|
|
|
52
|
-
const sendNow = node.sendCurrentValueWhenRescheduling && pastSchedule.length > 0;
|
|
64
|
+
const sendNow = !!node.sendCurrentValueWhenRescheduling && pastSchedule.length > 0 && !sentOnCommand;
|
|
53
65
|
const currentValue = pastSchedule[pastSchedule.length - 1]?.value;
|
|
54
66
|
if (sendNow) {
|
|
55
67
|
output1 = currentValue ? { payload: true } : null;
|
|
@@ -69,13 +81,18 @@ function handleStrategyInput(node, msg, doPlanning) {
|
|
|
69
81
|
|
|
70
82
|
function getPriceData(node, msg) {
|
|
71
83
|
const isConfigMsg = !!msg?.payload?.config;
|
|
84
|
+
const isCommandMsg = !!msg?.payload?.commands;
|
|
72
85
|
const isPriceMsg = !!msg?.payload?.priceData;
|
|
73
|
-
if (isConfigMsg && !isPriceMsg) {
|
|
74
|
-
|
|
86
|
+
if ((isConfigMsg || isCommandMsg) && !isPriceMsg) {
|
|
87
|
+
const priceData = node.context().get("lastPriceData");
|
|
88
|
+
const source = node.context().get("lastSource");
|
|
89
|
+
return { priceData, source };
|
|
75
90
|
}
|
|
76
91
|
const priceData = msg.payload.priceData;
|
|
92
|
+
const source = msg.payload.source;
|
|
77
93
|
node.context().set("lastPriceData", priceData);
|
|
78
|
-
|
|
94
|
+
node.context().set("lastSource", source);
|
|
95
|
+
return { priceData, source };
|
|
79
96
|
}
|
|
80
97
|
|
|
81
98
|
function runSchedule(node, schedule, time, currentSent = false) {
|
|
@@ -106,12 +123,14 @@ function runSchedule(node, schedule, time, currentSent = false) {
|
|
|
106
123
|
}
|
|
107
124
|
}
|
|
108
125
|
|
|
109
|
-
function deleteSavedScheduleBefore(node, day) {
|
|
126
|
+
function deleteSavedScheduleBefore(node, day, checkDays = 0) {
|
|
110
127
|
let date = day;
|
|
128
|
+
let count = 0;
|
|
111
129
|
do {
|
|
112
130
|
date = date.plus({ days: -1 });
|
|
113
131
|
data = node.context().set(date.toISO(), undefined);
|
|
114
|
-
|
|
132
|
+
count++;
|
|
133
|
+
} while (data || count <= checkDays);
|
|
115
134
|
}
|
|
116
135
|
|
|
117
136
|
function saveDayData(node, date, plan) {
|
|
@@ -136,6 +155,9 @@ function validateInput(node, msg) {
|
|
|
136
155
|
if (msg.payload.config !== undefined) {
|
|
137
156
|
return true; // Got config msg
|
|
138
157
|
}
|
|
158
|
+
if (msg.payload.commands !== undefined) {
|
|
159
|
+
return true; // Got command msg
|
|
160
|
+
}
|
|
139
161
|
if (msg.payload.priceData === undefined) {
|
|
140
162
|
validationFailure(node, "Payload is missing priceData");
|
|
141
163
|
return;
|
|
@@ -26,11 +26,6 @@
|
|
|
26
26
|
align: "left",
|
|
27
27
|
},
|
|
28
28
|
outputIfNoSchedule: { value: "true", required: true, align: "left" },
|
|
29
|
-
scheduleOnlyFromCurrentTime: {
|
|
30
|
-
value: "true",
|
|
31
|
-
required: true,
|
|
32
|
-
align: "left",
|
|
33
|
-
},
|
|
34
29
|
},
|
|
35
30
|
inputs: 1,
|
|
36
31
|
outputs: 3,
|
|
@@ -52,17 +47,6 @@
|
|
|
52
47
|
},
|
|
53
48
|
],
|
|
54
49
|
});
|
|
55
|
-
$("#node-input-scheduleOnlyFromCurrentTime").typedInput({
|
|
56
|
-
types: [
|
|
57
|
-
{
|
|
58
|
-
value: "nowOrStart",
|
|
59
|
-
options: [
|
|
60
|
-
{ value: "false", label: "Whole data set" },
|
|
61
|
-
{ value: "true", label: "From current hour" },
|
|
62
|
-
],
|
|
63
|
-
},
|
|
64
|
-
],
|
|
65
|
-
});
|
|
66
50
|
},
|
|
67
51
|
});
|
|
68
52
|
</script>
|
|
@@ -88,11 +72,6 @@
|
|
|
88
72
|
<label for="node-input-minSaving"><i class="fa fa-eur"></i> Min saving</label>
|
|
89
73
|
<input type="text" id="node-input-minSaving" placeholder="Minimum to save for turning off" style="width: 80px">
|
|
90
74
|
</div>
|
|
91
|
-
<div class="form-row">
|
|
92
|
-
<label for="node-input-scheduleOnlyFromCurrentTime">Schedule for</label>
|
|
93
|
-
<input type="text" id="node-input-scheduleOnlyFromCurrentTime" style="width: 160px">
|
|
94
|
-
</label>
|
|
95
|
-
</div>
|
|
96
75
|
<h3>Output</h3>
|
|
97
76
|
<div class="form-row">
|
|
98
77
|
<label for="node-input-sendCurrentValueWhenRescheduling" style="width:240px">
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { countAtEnd, makeSchedule, getSavings,
|
|
1
|
+
const { countAtEnd, makeSchedule, getSavings, getDiff } = require("./utils");
|
|
2
2
|
const { handleStrategyInput } = require("./handle-input");
|
|
3
3
|
const { loadDayData } = require("./utils");
|
|
4
4
|
|
|
@@ -15,7 +15,6 @@ module.exports = function (RED) {
|
|
|
15
15
|
minSaving: parseFloat(config.minSaving),
|
|
16
16
|
sendCurrentValueWhenRescheduling: config.sendCurrentValueWhenRescheduling,
|
|
17
17
|
outputIfNoSchedule: config.outputIfNoSchedule === "true",
|
|
18
|
-
scheduleOnlyFromCurrentTime: config.scheduleOnlyFromCurrentTime === "true",
|
|
19
18
|
};
|
|
20
19
|
node.context().set("config", originalConfig);
|
|
21
20
|
|
|
@@ -43,20 +42,18 @@ function adjustSavingsPassedHours(plan, includeFromLastPlanHours) {
|
|
|
43
42
|
}
|
|
44
43
|
}
|
|
45
44
|
|
|
46
|
-
function loadDataJustBefore(node, dateDayBefore
|
|
45
|
+
function loadDataJustBefore(node, dateDayBefore) {
|
|
47
46
|
const dataDayBefore = loadDayData(node, dateDayBefore);
|
|
48
|
-
const dataToday = loadDayData(node, dateToday);
|
|
49
47
|
return {
|
|
50
|
-
schedule: [...dataDayBefore.schedule
|
|
51
|
-
hours: [...dataDayBefore.hours
|
|
48
|
+
schedule: [...dataDayBefore.schedule],
|
|
49
|
+
hours: [...dataDayBefore.hours],
|
|
52
50
|
};
|
|
53
51
|
}
|
|
54
52
|
|
|
55
|
-
function doPlanning(node,
|
|
56
|
-
const
|
|
57
|
-
const
|
|
58
|
-
const
|
|
59
|
-
const startTimes = priceData.map((d) => d.start).slice(startAtIndex);
|
|
53
|
+
function doPlanning(node, _, priceData, _, dateDayBefore, _) {
|
|
54
|
+
const dataJustBefore = loadDataJustBefore(node, dateDayBefore);
|
|
55
|
+
const values = priceData.map((d) => d.value);
|
|
56
|
+
const startTimes = priceData.map((d) => d.start);
|
|
60
57
|
const onOffBefore = dataJustBefore.hours.map((h) => h.onOff);
|
|
61
58
|
const lastPlanHours = node.context().get("lastPlan")?.hours ?? [];
|
|
62
59
|
const plan = makePlan(node, values, startTimes, onOffBefore);
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const expect = require("expect");
|
|
2
|
+
const helper = require("node-red-node-test-helper");
|
|
3
|
+
const bestSave = require("../src/strategy-best-save.js");
|
|
4
|
+
const prices = require("./data/converted-prices.json");
|
|
5
|
+
const result = require("./data/best-save-result.json");
|
|
6
|
+
const { testPlan, equalPlan } = require("./test-utils");
|
|
7
|
+
const { makeFlow, makePayload } = require("./strategy-best-save-test-utils");
|
|
8
|
+
|
|
9
|
+
helper.init(require.resolve("node-red"));
|
|
10
|
+
|
|
11
|
+
describe("send command as input", () => {
|
|
12
|
+
beforeEach(function (done) {
|
|
13
|
+
helper.startServer(done);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
afterEach(function (done) {
|
|
17
|
+
helper.unload().then(function () {
|
|
18
|
+
helper.stopServer(done);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("should send output on command", function (done) {
|
|
23
|
+
const flow = makeFlow(3, 2, true);
|
|
24
|
+
let pass = 1;
|
|
25
|
+
helper.load(bestSave, flow, function () {
|
|
26
|
+
const n1 = helper.getNode("n1");
|
|
27
|
+
const n2 = helper.getNode("n2");
|
|
28
|
+
n1.sendCurrentValueWhenRescheduling = true;
|
|
29
|
+
n2.on("input", function (msg) {
|
|
30
|
+
switch (pass) {
|
|
31
|
+
case 1:
|
|
32
|
+
pass++;
|
|
33
|
+
expect(equalPlan(result, msg.payload)).toBeTruthy();
|
|
34
|
+
expect(msg.payload.sentOnCommand).toBeFalsy();
|
|
35
|
+
n1.receive({ payload: { commands: { sendSchedule: true } } });
|
|
36
|
+
break;
|
|
37
|
+
case 2:
|
|
38
|
+
expect(equalPlan(result, msg.payload)).toBeTruthy();
|
|
39
|
+
expect(msg.payload.sentOnCommand).toBeTruthy();
|
|
40
|
+
done();
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
n1.receive({ payload: makePayload(prices, testPlan.time) });
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
});
|