node-red-contrib-power-saver 3.6.2 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +15 -0
- package/docs/.vuepress/components/AdsenseAdd.vue +40 -0
- package/docs/.vuepress/components/DonateButtons.vue +26 -3
- package/docs/.vuepress/components/VippsPlakat.vue +20 -0
- package/docs/.vuepress/config.js +17 -10
- package/docs/.vuepress/public/ads.txt +1 -0
- package/docs/README.md +6 -4
- package/docs/changelog/README.md +61 -1
- package/docs/contribute/README.md +12 -3
- package/docs/examples/README.md +6 -0
- package/docs/examples/example-grid-tariff-capacity-flow.json +23 -7
- package/docs/examples/example-grid-tariff-capacity-part.md +657 -22
- package/docs/faq/README.md +5 -1
- package/docs/faq/best-save-viewer.md +9 -1
- package/docs/guide/README.md +36 -5
- package/docs/images/best-save-config.png +0 -0
- package/docs/images/combine-two-lowest-price.png +0 -0
- package/docs/images/fixed-schedule-config.png +0 -0
- package/docs/images/global-context-window.png +0 -0
- package/docs/images/lowest-price-config.png +0 -0
- package/docs/images/node-ps-schedule-merger.png +0 -0
- package/docs/images/node-ps-strategy-fixed-schedule.png +0 -0
- package/docs/images/ps-strategy-fixed-schedule-example.png +0 -0
- package/docs/images/schedule-merger-config.png +0 -0
- package/docs/images/schedule-merger-example-1.png +0 -0
- package/docs/images/vipps-plakat.png +0 -0
- package/docs/images/vipps-qr.png +0 -0
- package/docs/images/vipps-smiling-rgb-orange-pos.png +0 -0
- package/docs/nodes/README.md +24 -4
- package/docs/nodes/dynamic-commands.md +91 -0
- package/docs/nodes/dynamic-config.md +84 -0
- package/docs/nodes/old-power-saver-doc.md +4 -0
- package/docs/nodes/ps-elvia-add-tariff.md +16 -0
- package/docs/nodes/ps-general-add-tariff.md +22 -0
- package/docs/nodes/ps-receive-price.md +14 -1
- package/docs/nodes/ps-schedule-merger.md +231 -0
- package/docs/nodes/ps-strategy-best-save.md +65 -109
- package/docs/nodes/ps-strategy-fixed-schedule.md +113 -0
- package/docs/nodes/ps-strategy-heat-capacitor.md +14 -1
- package/docs/nodes/ps-strategy-lowest-price.md +61 -110
- package/docs/nodes/strategy-input.md +4 -0
- package/package.json +5 -2
- package/src/elvia/elvia-add-tariff.html +1 -2
- package/src/elvia/elvia-add-tariff.js +0 -1
- package/src/elvia/elvia-api.js +6 -0
- package/src/elvia/elvia-tariff.html +1 -1
- package/src/general-add-tariff.html +14 -8
- package/src/general-add-tariff.js +0 -1
- package/src/handle-input.js +97 -106
- package/src/handle-output.js +117 -0
- package/src/receive-price-functions.js +3 -3
- package/src/schedule-merger-functions.js +98 -0
- package/src/schedule-merger.html +135 -0
- package/src/schedule-merger.js +112 -0
- package/src/strategy-best-save.html +38 -1
- package/src/strategy-best-save.js +17 -63
- package/src/strategy-fixed-schedule.html +339 -0
- package/src/strategy-fixed-schedule.js +84 -0
- package/src/strategy-functions.js +35 -0
- package/src/strategy-lowest-price.html +76 -38
- package/src/strategy-lowest-price.js +16 -35
- package/src/utils.js +75 -2
- package/test/commands-input-best-save.test.js +142 -0
- package/test/commands-input-lowest-price.test.js +149 -0
- package/test/commands-input-schedule-merger.test.js +128 -0
- package/test/data/best-save-overlap-result.json +5 -1
- package/test/data/best-save-result.json +4 -0
- package/test/data/commands-result-best-save.json +383 -0
- package/test/data/commands-result-lowest-price.json +340 -0
- package/test/data/fixed-schedule-result.json +353 -0
- package/test/data/lowest-price-result-cont-max-fail.json +5 -1
- package/test/data/lowest-price-result-cont-max.json +3 -1
- package/test/data/lowest-price-result-cont.json +8 -1
- package/test/data/lowest-price-result-missing-end.json +8 -3
- package/test/data/lowest-price-result-neg-cont.json +27 -0
- package/test/data/lowest-price-result-neg-split.json +23 -0
- package/test/data/lowest-price-result-split-allday.json +3 -1
- package/test/data/lowest-price-result-split-allday10.json +1 -0
- package/test/data/lowest-price-result-split-max.json +3 -1
- package/test/data/lowest-price-result-split.json +3 -1
- package/test/data/merge-schedule-data.js +238 -0
- package/test/data/negative-prices.json +197 -0
- package/test/data/nordpool-event-prices.json +96 -480
- package/test/data/nordpool-zero-prices.json +90 -0
- package/test/data/reconfigResult.js +1 -0
- package/test/data/result.js +1 -0
- package/test/data/tibber-result-end-0-24h.json +12 -2
- package/test/data/tibber-result-end-0.json +12 -2
- package/test/data/tibber-result.json +1 -0
- package/test/receive-price.test.js +22 -0
- package/test/schedule-merger-functions.test.js +101 -0
- package/test/schedule-merger-test-utils.js +27 -0
- package/test/schedule-merger.test.js +130 -0
- package/test/send-config-input.test.js +46 -2
- package/test/strategy-best-save-test-utils.js +1 -1
- package/test/strategy-best-save.test.js +91 -0
- package/test/strategy-fixed-schedule.test.js +117 -0
- package/test/strategy-heat-capacitor.test.js +1 -1
- package/test/strategy-lowest-price-functions.test.js +1 -1
- package/test/strategy-lowest-price-test-utils.js +31 -0
- package/test/strategy-lowest-price.test.js +55 -45
- package/test/test-utils.js +43 -36
- package/test/utils.test.js +13 -0
- package/docs/images/node-power-saver.png +0 -0
- package/docs/nodes/power-saver.md +0 -23
- package/src/power-saver.html +0 -116
- package/src/power-saver.js +0 -260
- package/test/commands-input.test.js +0 -47
- package/test/power-saver.test.js +0 -189
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
const cloneDeep = require("lodash.clonedeep");
|
|
2
|
+
const { DateTime } = require("luxon");
|
|
3
|
+
const expect = require("expect");
|
|
4
|
+
const helper = require("node-red-node-test-helper");
|
|
5
|
+
const fixedSchedule = require("../src/strategy-fixed-schedule.js");
|
|
6
|
+
const prices = require("./data/converted-prices.json");
|
|
7
|
+
const result = require("./data/fixed-schedule-result.json");
|
|
8
|
+
const { testPlan: plan, equalPlan, equalSchedule } = require("./test-utils");
|
|
9
|
+
|
|
10
|
+
helper.init(require.resolve("node-red"));
|
|
11
|
+
|
|
12
|
+
describe("ps-strategy-fixed-schedule node", function () {
|
|
13
|
+
beforeEach(function (done) {
|
|
14
|
+
helper.startServer(done);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
afterEach(function (done) {
|
|
18
|
+
helper.unload().then(function () {
|
|
19
|
+
helper.stopServer(done);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("should be loaded", function (done) {
|
|
24
|
+
const flow = [{ id: "n1", type: "ps-strategy-fixed-schedule", name: "test name" }];
|
|
25
|
+
helper.load(fixedSchedule, flow, function () {
|
|
26
|
+
const n1 = helper.getNode("n1");
|
|
27
|
+
expect(n1).toHaveProperty("name", "test name");
|
|
28
|
+
done();
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("should turn on 2 to 6", function (done) {
|
|
33
|
+
const flow = makeFlow();
|
|
34
|
+
const expected = cloneDeep(result);
|
|
35
|
+
helper.load(fixedSchedule, flow, function () {
|
|
36
|
+
const n1 = helper.getNode("n1");
|
|
37
|
+
const n2 = helper.getNode("n2");
|
|
38
|
+
n2.on("input", function (msg) {
|
|
39
|
+
expect(equalPlan(expected, msg.payload)).toBeTruthy();
|
|
40
|
+
n1.warn.should.not.be.called;
|
|
41
|
+
done();
|
|
42
|
+
});
|
|
43
|
+
n1.receive({ payload: prices, time: prices.priceData[0].start });
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
it("should send correct if no schedule", function (done) {
|
|
47
|
+
const flow = makeFlow(true);
|
|
48
|
+
const expected = cloneDeep(result);
|
|
49
|
+
expected.schedule.push({
|
|
50
|
+
time: "2021-10-13T00:00:00.000+02:00",
|
|
51
|
+
value: true,
|
|
52
|
+
countHours: null,
|
|
53
|
+
});
|
|
54
|
+
expected.config.outputIfNoSchedule = true;
|
|
55
|
+
|
|
56
|
+
helper.load(fixedSchedule, flow, function () {
|
|
57
|
+
const n1 = helper.getNode("n1");
|
|
58
|
+
const n2 = helper.getNode("n2");
|
|
59
|
+
n2.on("input", function (msg) {
|
|
60
|
+
expect(equalPlan(expected, msg.payload)).toBeTruthy();
|
|
61
|
+
n1.warn.should.not.be.called;
|
|
62
|
+
done();
|
|
63
|
+
});
|
|
64
|
+
n1.receive({ payload: prices, time: prices.priceData[0].start });
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
it("should work only on days specified", function (done) {
|
|
68
|
+
const flow = makeFlow(false);
|
|
69
|
+
flow[0].periods.splice(1, 1);
|
|
70
|
+
flow[0].days["Tue"] = false;
|
|
71
|
+
const expected = [
|
|
72
|
+
{
|
|
73
|
+
time: "2021-10-11T00:00:00.000+02:00",
|
|
74
|
+
value: true,
|
|
75
|
+
countHours: 24,
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
time: "2021-10-12T00:00:00.000+02:00",
|
|
79
|
+
value: false,
|
|
80
|
+
countHours: 24,
|
|
81
|
+
},
|
|
82
|
+
];
|
|
83
|
+
|
|
84
|
+
helper.load(fixedSchedule, flow, function () {
|
|
85
|
+
const n1 = helper.getNode("n1");
|
|
86
|
+
const n2 = helper.getNode("n2");
|
|
87
|
+
n2.on("input", function (msg) {
|
|
88
|
+
expect(equalSchedule(expected, msg.payload.schedule)).toBeTruthy();
|
|
89
|
+
done();
|
|
90
|
+
});
|
|
91
|
+
n1.receive({ payload: prices, time: prices.priceData[0].start });
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
function makeFlow(outputIfNoSchedule = false) {
|
|
97
|
+
return [
|
|
98
|
+
{
|
|
99
|
+
id: "n1",
|
|
100
|
+
type: "ps-strategy-fixed-schedule",
|
|
101
|
+
name: "test name",
|
|
102
|
+
days: { Mon: true, Tue: true, Wed: true, Thu: true, Fri: true, Sat: true, Sun: true },
|
|
103
|
+
periods: [
|
|
104
|
+
{ start: "02", value: true },
|
|
105
|
+
{ start: "06", value: false },
|
|
106
|
+
],
|
|
107
|
+
sendCurrentValueWhenRescheduling: true,
|
|
108
|
+
outputIfNoSchedule,
|
|
109
|
+
// validFrom: null,
|
|
110
|
+
// validTo: null,
|
|
111
|
+
wires: [["n3"], ["n4"], ["n2"]],
|
|
112
|
+
},
|
|
113
|
+
{ id: "n2", type: "helper" },
|
|
114
|
+
{ id: "n3", type: "helper" },
|
|
115
|
+
{ id: "n4", type: "helper" },
|
|
116
|
+
];
|
|
117
|
+
}
|
|
@@ -11,7 +11,7 @@ const {
|
|
|
11
11
|
const converted_prices = require("./data/converted-prices.json");
|
|
12
12
|
const decreasing_end_prices = require("./data/tibber-decreasing2-24h.json");
|
|
13
13
|
|
|
14
|
-
describe("
|
|
14
|
+
describe("ps-strategy-heat-capacitor-functions", () => {
|
|
15
15
|
let prices, decreasing_24h_prices, start_date, buy_pattern, sell_pattern;
|
|
16
16
|
|
|
17
17
|
//User input
|
|
@@ -4,7 +4,7 @@ const { getBestContinuous, getBestX } = require("../src/strategy-lowest-price-fu
|
|
|
4
4
|
const convertedPrices = require("./data/converted-prices.json");
|
|
5
5
|
const cloneDeep = require("lodash.clonedeep");
|
|
6
6
|
|
|
7
|
-
describe("strategy-
|
|
7
|
+
describe("strategy-lowest-price-functions", () => {
|
|
8
8
|
it("can find best x", () => {
|
|
9
9
|
const values = convertedPrices.priceData.slice(0, 48).map((p) => p.value);
|
|
10
10
|
const result = cloneDeep(values).fill(false);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const cloneDeep = require("lodash.clonedeep");
|
|
2
|
+
const { DateTime } = require("luxon");
|
|
3
|
+
|
|
4
|
+
function makeFlow(hoursOn, maxPrice = null, doNotSplit = true, fromTime = "10", toTime = "20") {
|
|
5
|
+
return [
|
|
6
|
+
{
|
|
7
|
+
id: "n1",
|
|
8
|
+
type: "ps-strategy-lowest-price",
|
|
9
|
+
name: "test name",
|
|
10
|
+
fromTime,
|
|
11
|
+
toTime,
|
|
12
|
+
hoursOn,
|
|
13
|
+
maxPrice,
|
|
14
|
+
doNotSplit,
|
|
15
|
+
sendCurrentValueWhenRescheduling: true,
|
|
16
|
+
outputIfNoSchedule: true,
|
|
17
|
+
wires: [["n3"], ["n4"], ["n2"]],
|
|
18
|
+
},
|
|
19
|
+
{ id: "n2", type: "helper" },
|
|
20
|
+
{ id: "n3", type: "helper" },
|
|
21
|
+
{ id: "n4", type: "helper" },
|
|
22
|
+
];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function makePayload(prices, time) {
|
|
26
|
+
const payload = cloneDeep(prices);
|
|
27
|
+
payload.time = time;
|
|
28
|
+
return payload;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
module.exports = { makeFlow, makePayload };
|
|
@@ -4,8 +4,9 @@ const expect = require("expect");
|
|
|
4
4
|
const helper = require("node-red-node-test-helper");
|
|
5
5
|
const lowestPrice = require("../src/strategy-lowest-price.js");
|
|
6
6
|
const prices = require("./data/converted-prices.json");
|
|
7
|
-
const
|
|
7
|
+
const negativePrices = require("./data/negative-prices.json");
|
|
8
8
|
const { version } = require("../package.json");
|
|
9
|
+
const { makeFlow, makePayload } = require("./strategy-lowest-price-test-utils");
|
|
9
10
|
|
|
10
11
|
helper.init(require.resolve("node-red"));
|
|
11
12
|
|
|
@@ -83,7 +84,6 @@ describe("ps-strategy-lowest-price node", function () {
|
|
|
83
84
|
const n2 = helper.getNode("n2");
|
|
84
85
|
n2.on("input", function (msg) {
|
|
85
86
|
expect(msg.payload).toHaveProperty("schedule", resultSplitted.schedule);
|
|
86
|
-
expect(msg.payload).toHaveProperty("config", resultSplitted.config);
|
|
87
87
|
n1.warn.should.not.be.called;
|
|
88
88
|
done();
|
|
89
89
|
});
|
|
@@ -91,6 +91,38 @@ describe("ps-strategy-lowest-price node", function () {
|
|
|
91
91
|
n1.receive({ payload: makePayload(prices, time) });
|
|
92
92
|
});
|
|
93
93
|
});
|
|
94
|
+
it("should plan correct negative prices continuous schedule", function (done) {
|
|
95
|
+
const resultContinuous = require("./data/lowest-price-result-neg-cont.json");
|
|
96
|
+
const flow = makeFlow(5, null, true, "00", "00");
|
|
97
|
+
helper.load(lowestPrice, flow, function () {
|
|
98
|
+
const n1 = helper.getNode("n1");
|
|
99
|
+
const n2 = helper.getNode("n2");
|
|
100
|
+
n2.on("input", function (msg) {
|
|
101
|
+
expect(msg.payload).toHaveProperty("schedule", resultContinuous.schedule);
|
|
102
|
+
expect(msg.payload).toHaveProperty("config", resultContinuous.config);
|
|
103
|
+
n1.warn.should.not.be.called;
|
|
104
|
+
done();
|
|
105
|
+
});
|
|
106
|
+
const time = DateTime.fromISO(prices.priceData[10].start);
|
|
107
|
+
n1.receive({ payload: makePayload(negativePrices, time) });
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
it("should plan correct negative prices splitted schedule", function (done) {
|
|
111
|
+
const resultSplitted = require("./data/lowest-price-result-neg-split.json");
|
|
112
|
+
const flow = makeFlow(5, null, true, "00", "00");
|
|
113
|
+
flow[0].doNotSplit = false;
|
|
114
|
+
helper.load(lowestPrice, flow, function () {
|
|
115
|
+
const n1 = helper.getNode("n1");
|
|
116
|
+
const n2 = helper.getNode("n2");
|
|
117
|
+
n2.on("input", function (msg) {
|
|
118
|
+
expect(msg.payload).toHaveProperty("schedule", resultSplitted.schedule);
|
|
119
|
+
n1.warn.should.not.be.called;
|
|
120
|
+
done();
|
|
121
|
+
});
|
|
122
|
+
const time = DateTime.fromISO(prices.priceData[10].start);
|
|
123
|
+
n1.receive({ payload: makePayload(negativePrices, time) });
|
|
124
|
+
});
|
|
125
|
+
});
|
|
94
126
|
it("should plan correct continuous schedule with max price ok", function (done) {
|
|
95
127
|
const resultContinuousMax = require("./data/lowest-price-result-cont-max.json");
|
|
96
128
|
const flow = makeFlow(4, 1.0);
|
|
@@ -99,7 +131,6 @@ describe("ps-strategy-lowest-price node", function () {
|
|
|
99
131
|
const n2 = helper.getNode("n2");
|
|
100
132
|
n2.on("input", function (msg) {
|
|
101
133
|
expect(msg.payload).toHaveProperty("schedule", resultContinuousMax.schedule);
|
|
102
|
-
expect(msg.payload).toHaveProperty("config", resultContinuousMax.config);
|
|
103
134
|
n1.warn.should.not.be.called;
|
|
104
135
|
done();
|
|
105
136
|
});
|
|
@@ -115,7 +146,6 @@ describe("ps-strategy-lowest-price node", function () {
|
|
|
115
146
|
const n2 = helper.getNode("n2");
|
|
116
147
|
n2.on("input", function (msg) {
|
|
117
148
|
expect(msg.payload).toHaveProperty("schedule", resultContinuousMax.schedule);
|
|
118
|
-
expect(msg.payload).toHaveProperty("config", resultContinuousMax.config);
|
|
119
149
|
n1.warn.should.not.be.called;
|
|
120
150
|
done();
|
|
121
151
|
});
|
|
@@ -132,7 +162,6 @@ describe("ps-strategy-lowest-price node", function () {
|
|
|
132
162
|
const n2 = helper.getNode("n2");
|
|
133
163
|
n2.on("input", function (msg) {
|
|
134
164
|
expect(msg.payload).toHaveProperty("schedule", resultSplittedMax.schedule);
|
|
135
|
-
expect(msg.payload).toHaveProperty("config", resultSplittedMax.config);
|
|
136
165
|
n1.warn.should.not.be.called;
|
|
137
166
|
done();
|
|
138
167
|
});
|
|
@@ -151,7 +180,6 @@ describe("ps-strategy-lowest-price node", function () {
|
|
|
151
180
|
const n2 = helper.getNode("n2");
|
|
152
181
|
n2.on("input", function (msg) {
|
|
153
182
|
expect(msg.payload).toHaveProperty("schedule", resultAllDay.schedule);
|
|
154
|
-
expect(msg.payload).toHaveProperty("config", resultAllDay.config);
|
|
155
183
|
n1.warn.should.not.be.called;
|
|
156
184
|
done();
|
|
157
185
|
});
|
|
@@ -171,7 +199,6 @@ describe("ps-strategy-lowest-price node", function () {
|
|
|
171
199
|
n2.on("input", function (msg) {
|
|
172
200
|
const config = cloneDeep(resultAllDay10.config);
|
|
173
201
|
expect(msg.payload).toHaveProperty("schedule", resultAllDay10.schedule);
|
|
174
|
-
expect(msg.payload).toHaveProperty("config", config);
|
|
175
202
|
n1.warn.should.not.be.called;
|
|
176
203
|
done();
|
|
177
204
|
});
|
|
@@ -193,7 +220,6 @@ describe("ps-strategy-lowest-price node", function () {
|
|
|
193
220
|
const config = cloneDeep(resultAllDay10.config);
|
|
194
221
|
config.outputOutsidePeriod = true;
|
|
195
222
|
expect(msg.payload).toHaveProperty("schedule", resultAllDay10.schedule);
|
|
196
|
-
expect(msg.payload).toHaveProperty("config", config);
|
|
197
223
|
n1.warn.should.not.be.called;
|
|
198
224
|
done();
|
|
199
225
|
});
|
|
@@ -215,7 +241,6 @@ describe("ps-strategy-lowest-price node", function () {
|
|
|
215
241
|
const config = cloneDeep(resultAllDay10.config);
|
|
216
242
|
config.outputIfNoSchedule = true;
|
|
217
243
|
expect(msg.payload).toHaveProperty("schedule", resultAllDay10.schedule);
|
|
218
|
-
expect(msg.payload).toHaveProperty("config", config);
|
|
219
244
|
n1.warn.should.not.be.called;
|
|
220
245
|
done();
|
|
221
246
|
});
|
|
@@ -242,8 +267,8 @@ describe("ps-strategy-lowest-price node", function () {
|
|
|
242
267
|
config.outputOutsidePeriod = true;
|
|
243
268
|
const res = msg.payload.schedule.map((s) => ({ time: s.time, value: s.value }));
|
|
244
269
|
const exp = schedule.map((s) => ({ time: s.time, value: s.value }));
|
|
270
|
+
exp.pop();
|
|
245
271
|
expect(res).toEqual(exp);
|
|
246
|
-
expect(msg.payload).toHaveProperty("config", config);
|
|
247
272
|
n1.warn.should.not.be.called;
|
|
248
273
|
done();
|
|
249
274
|
});
|
|
@@ -267,10 +292,10 @@ describe("ps-strategy-lowest-price node", function () {
|
|
|
267
292
|
schedule.splice(4, 0, { time: "2021-10-11T20:00:00.000+02:00", value: true });
|
|
268
293
|
schedule.splice(5, 0, { time: "2021-10-12T10:00:00.000+02:00", value: false });
|
|
269
294
|
schedule.splice(schedule.length, 0, { time: "2021-10-12T20:00:00.000+02:00", value: true });
|
|
270
|
-
// schedule.splice(schedule.length - 1, 1);
|
|
271
295
|
config.outputOutsidePeriod = true;
|
|
272
296
|
const res = msg.payload.schedule.map((s) => ({ time: s.time, value: s.value }));
|
|
273
297
|
const exp = schedule.map((s) => ({ time: s.time, value: s.value }));
|
|
298
|
+
exp.splice(8, 1);
|
|
274
299
|
expect(res).toEqual(exp);
|
|
275
300
|
expect(msg.payload).toHaveProperty("config", config);
|
|
276
301
|
n1.warn.should.not.be.called;
|
|
@@ -281,7 +306,10 @@ describe("ps-strategy-lowest-price node", function () {
|
|
|
281
306
|
});
|
|
282
307
|
});
|
|
283
308
|
it("should work with 0 hours on", function (done) {
|
|
284
|
-
const result = [
|
|
309
|
+
const result = [
|
|
310
|
+
{ time: "2021-10-11T00:00:00.000+02:00", value: false, countHours: 48 },
|
|
311
|
+
{ time: "2021-10-13T00:00:00.000+02:00", value: true, countHours: null },
|
|
312
|
+
];
|
|
285
313
|
const flow = makeFlow(0);
|
|
286
314
|
helper.load(lowestPrice, flow, function () {
|
|
287
315
|
const n1 = helper.getNode("n1");
|
|
@@ -324,6 +352,7 @@ describe("ps-strategy-lowest-price node", function () {
|
|
|
324
352
|
{ time: "2021-10-11T13:00:00.000+02:00", value: false, countHours: 25 },
|
|
325
353
|
{ time: "2021-10-12T14:00:00.000+02:00", value: true, countHours: 1 },
|
|
326
354
|
{ time: "2021-10-12T15:00:00.000+02:00", value: false, countHours: 9 },
|
|
355
|
+
{ time: "2021-10-13T00:00:00.000+02:00", value: true, countHours: null },
|
|
327
356
|
];
|
|
328
357
|
const flow = makeFlow(1);
|
|
329
358
|
helper.load(lowestPrice, flow, function () {
|
|
@@ -345,6 +374,7 @@ describe("ps-strategy-lowest-price node", function () {
|
|
|
345
374
|
{ time: "2021-10-11T20:00:00.000+02:00", value: false, countHours: 14 },
|
|
346
375
|
{ time: "2021-10-12T10:00:00.000+02:00", value: true, countHours: 10 },
|
|
347
376
|
{ time: "2021-10-12T20:00:00.000+02:00", value: false, countHours: 4 },
|
|
377
|
+
{ time: "2021-10-13T00:00:00.000+02:00", value: true, countHours: null },
|
|
348
378
|
];
|
|
349
379
|
const flow = makeFlow(24);
|
|
350
380
|
helper.load(lowestPrice, flow, function () {
|
|
@@ -367,6 +397,7 @@ describe("ps-strategy-lowest-price node", function () {
|
|
|
367
397
|
{ time: "2021-10-11T00:00:00.000+02:00", value: false, countHours: 12 },
|
|
368
398
|
{ time: "2021-10-11T12:00:00.000+02:00", value: true, countHours: 1 },
|
|
369
399
|
{ time: "2021-10-11T13:00:00.000+02:00", value: false, countHours: 11 },
|
|
400
|
+
{ time: "2021-10-12T00:00:00.000+02:00", value: true, countHours: null },
|
|
370
401
|
];
|
|
371
402
|
const flow = makeFlow(1);
|
|
372
403
|
helper.load(lowestPrice, flow, function () {
|
|
@@ -393,7 +424,10 @@ describe("ps-strategy-lowest-price node", function () {
|
|
|
393
424
|
});
|
|
394
425
|
|
|
395
426
|
it("should handle hours on > period", function (done) {
|
|
396
|
-
const result = [
|
|
427
|
+
const result = [
|
|
428
|
+
{ time: "2021-10-11T00:00:00.000+02:00", value: true, countHours: 48 },
|
|
429
|
+
{ time: "2021-10-13T00:00:00.000+02:00", value: false, countHours: null },
|
|
430
|
+
];
|
|
397
431
|
const flow = [
|
|
398
432
|
{
|
|
399
433
|
id: "n1",
|
|
@@ -446,6 +480,7 @@ describe("ps-strategy-lowest-price node", function () {
|
|
|
446
480
|
{ time: "2021-10-11T22:00:00.000+02:00", value: false, countHours: 19 },
|
|
447
481
|
{ time: "2021-10-12T17:00:00.000+02:00", value: true, countHours: 5 },
|
|
448
482
|
{ time: "2021-10-12T22:00:00.000+02:00", value: false, countHours: 2 },
|
|
483
|
+
{ time: "2021-10-13T00:00:00.000+02:00", value: true, countHours: null },
|
|
449
484
|
];
|
|
450
485
|
const flow = [
|
|
451
486
|
{
|
|
@@ -496,6 +531,8 @@ describe("ps-strategy-lowest-price node", function () {
|
|
|
496
531
|
const input = require("./data/tibber-data-end-0.json");
|
|
497
532
|
const result = require("./data/tibber-result-end-0.json");
|
|
498
533
|
result.version = version;
|
|
534
|
+
result.strategyNodeId = "n1";
|
|
535
|
+
result.current = false;
|
|
499
536
|
const flow = [
|
|
500
537
|
{
|
|
501
538
|
id: "n1",
|
|
@@ -509,6 +546,7 @@ describe("ps-strategy-lowest-price node", function () {
|
|
|
509
546
|
sendCurrentValueWhenRescheduling: true,
|
|
510
547
|
outputIfNoSchedule: false,
|
|
511
548
|
outputOutsidePeriod: false,
|
|
549
|
+
override: "auto",
|
|
512
550
|
wires: [["n3"], ["n4"], ["n2"]],
|
|
513
551
|
},
|
|
514
552
|
{ id: "n2", type: "helper" },
|
|
@@ -531,6 +569,8 @@ describe("ps-strategy-lowest-price node", function () {
|
|
|
531
569
|
const input = require("./data/tibber-data-end-0-24h.json");
|
|
532
570
|
const result = require("./data/tibber-result-end-0-24h.json");
|
|
533
571
|
result.version = version;
|
|
572
|
+
result.strategyNodeId = "n1";
|
|
573
|
+
result.current = false;
|
|
534
574
|
const flow = [
|
|
535
575
|
{
|
|
536
576
|
id: "n1",
|
|
@@ -566,6 +606,8 @@ describe("ps-strategy-lowest-price node", function () {
|
|
|
566
606
|
const input = require("./data/lowest-price-input-missing-end.json");
|
|
567
607
|
const result = require("./data/lowest-price-result-missing-end.json");
|
|
568
608
|
result.payload.version = version;
|
|
609
|
+
result.payload.strategyNodeId = "n1";
|
|
610
|
+
result.payload.current = false;
|
|
569
611
|
const flow = [
|
|
570
612
|
{
|
|
571
613
|
id: "n1",
|
|
@@ -599,35 +641,3 @@ describe("ps-strategy-lowest-price node", function () {
|
|
|
599
641
|
});
|
|
600
642
|
});
|
|
601
643
|
});
|
|
602
|
-
|
|
603
|
-
function makeFlow(hoursOn, maxPrice = null) {
|
|
604
|
-
return [
|
|
605
|
-
{
|
|
606
|
-
id: "n1",
|
|
607
|
-
type: "ps-strategy-lowest-price",
|
|
608
|
-
name: "test name",
|
|
609
|
-
fromTime: "10",
|
|
610
|
-
toTime: "20",
|
|
611
|
-
hoursOn: hoursOn,
|
|
612
|
-
maxPrice: maxPrice,
|
|
613
|
-
doNotSplit: true,
|
|
614
|
-
sendCurrentValueWhenRescheduling: true,
|
|
615
|
-
outputIfNoSchedule: true,
|
|
616
|
-
wires: [["n3"], ["n4"], ["n2"]],
|
|
617
|
-
},
|
|
618
|
-
{ id: "n2", type: "helper" },
|
|
619
|
-
{ id: "n3", type: "helper" },
|
|
620
|
-
{ id: "n4", type: "helper" },
|
|
621
|
-
];
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
function makePayload(prices, time) {
|
|
625
|
-
const payload = cloneDeep(prices);
|
|
626
|
-
payload.time = time;
|
|
627
|
-
// let entryTime = DateTime.fromISO(payload.time);
|
|
628
|
-
// payload.priceData.forEach((e) => {
|
|
629
|
-
// e.start = entryTime.toISO();
|
|
630
|
-
// entryTime = entryTime.plus({ milliseconds: 10 });
|
|
631
|
-
// });
|
|
632
|
-
return payload;
|
|
633
|
-
}
|
package/test/test-utils.js
CHANGED
|
@@ -37,59 +37,64 @@ function makePayload(prices, time) {
|
|
|
37
37
|
|
|
38
38
|
function equalPlan(expected, actual) {
|
|
39
39
|
let res = true;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
"Schedules have different lengths: Expected " + expected.schedule.length + ", got " + actual.schedule.length
|
|
43
|
-
);
|
|
40
|
+
|
|
41
|
+
if (!equalSchedule(expected.schedule, actual.schedule)) {
|
|
44
42
|
res = false;
|
|
45
43
|
}
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
|
|
45
|
+
if (!equalHours(expected.hours, actual.hours)) {
|
|
46
|
+
res = false;
|
|
48
47
|
}
|
|
49
|
-
|
|
48
|
+
|
|
49
|
+
["maxHoursToSaveInSequence", "minHoursOnAfterMaxSequenceSaved", "minSaving", "outputIfNoSchedule"].forEach((key) => {
|
|
50
|
+
if (expected.config[key] != actual.config[key]) {
|
|
51
|
+
console.log(
|
|
52
|
+
"Different config values for " + key + ": Expected " + expected.config[key] + ", got " + actual.config[key]
|
|
53
|
+
);
|
|
54
|
+
res = false;
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
return res;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function equalSchedule(expected, actual) {
|
|
62
|
+
let res = true;
|
|
63
|
+
if (expected.length !== actual.length) {
|
|
64
|
+
console.log("Schedules have different lengths: Expected " + expected.length + ", got " + actual.length);
|
|
65
|
+
res = false;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
expected.forEach((s, i) => {
|
|
50
69
|
["time", "value"].forEach((key) => {
|
|
51
|
-
if (s[key] != actual
|
|
70
|
+
if (s[key] != actual[i][key]) {
|
|
52
71
|
console.log(
|
|
53
|
-
"Different schedule values for " +
|
|
54
|
-
key +
|
|
55
|
-
" at index " +
|
|
56
|
-
i +
|
|
57
|
-
": Expected " +
|
|
58
|
-
s[key] +
|
|
59
|
-
", got " +
|
|
60
|
-
actual.schedule[i][key]
|
|
72
|
+
"Different schedule values for " + key + " at index " + i + ": Expected " + s[key] + ", got " + actual[i][key]
|
|
61
73
|
);
|
|
62
74
|
res = false;
|
|
63
75
|
}
|
|
64
76
|
});
|
|
65
77
|
});
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
78
|
+
return res;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function equalHours(expected, actual, properties = ["price", "onOff", "start", "saving"]) {
|
|
82
|
+
let res = true;
|
|
83
|
+
if (expected.length !== actual.length) {
|
|
84
|
+
console.log("Hours have different lengths: Expected " + expected.hours.length + ", got " + actual.hours.length);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
expected.forEach((s, i) => {
|
|
88
|
+
properties.forEach((key) => {
|
|
89
|
+
if (s[key] != actual[i][key]) {
|
|
69
90
|
console.log(
|
|
70
|
-
"Different hour values for " +
|
|
71
|
-
key +
|
|
72
|
-
" at index " +
|
|
73
|
-
i +
|
|
74
|
-
": Expected " +
|
|
75
|
-
s[key] +
|
|
76
|
-
", got " +
|
|
77
|
-
actual.hours[i][key]
|
|
91
|
+
"Different hour values for " + key + " at index " + i + ": Expected " + s[key] + ", got " + actual[i][key]
|
|
78
92
|
);
|
|
79
93
|
res = false;
|
|
80
94
|
}
|
|
81
95
|
});
|
|
82
96
|
});
|
|
83
97
|
|
|
84
|
-
["maxHoursToSaveInSequence", "minHoursOnAfterMaxSequenceSaved", "minSaving", "outputIfNoSchedule"].forEach((key) => {
|
|
85
|
-
if (expected.config[key] != actual.config[key]) {
|
|
86
|
-
console.log(
|
|
87
|
-
"Different config values for " + key + ": Expected " + expected.config[key] + ", got " + actual.config[key]
|
|
88
|
-
);
|
|
89
|
-
res = false;
|
|
90
|
-
}
|
|
91
|
-
});
|
|
92
|
-
|
|
93
98
|
return res;
|
|
94
99
|
}
|
|
95
100
|
|
|
@@ -97,4 +102,6 @@ module.exports = {
|
|
|
97
102
|
testPlan,
|
|
98
103
|
makePayload,
|
|
99
104
|
equalPlan,
|
|
105
|
+
equalHours,
|
|
106
|
+
equalSchedule,
|
|
100
107
|
};
|
package/test/utils.test.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const cloneDeep = require("lodash.clonedeep");
|
|
1
2
|
const { DateTime } = require("luxon");
|
|
2
3
|
const expect = require("expect");
|
|
3
4
|
const {
|
|
@@ -8,10 +9,12 @@ const {
|
|
|
8
9
|
getSavings,
|
|
9
10
|
countAtEnd,
|
|
10
11
|
makeSchedule,
|
|
12
|
+
makeScheduleFromHours,
|
|
11
13
|
fillArray,
|
|
12
14
|
extractPlanForDate,
|
|
13
15
|
isSameDate,
|
|
14
16
|
} = require("../src/utils");
|
|
17
|
+
const testResult = require("./data/best-save-result.json");
|
|
15
18
|
|
|
16
19
|
describe("utils", () => {
|
|
17
20
|
it("can test boolean config", () => {
|
|
@@ -195,4 +198,14 @@ describe("utils", () => {
|
|
|
195
198
|
};
|
|
196
199
|
expect(extractPlanForDate(plan, "2021-06-20T01:50:00.000+02:00")).toEqual(part1);
|
|
197
200
|
});
|
|
201
|
+
it("Can make schedule from hours", () => {
|
|
202
|
+
const hours = cloneDeep(testResult.hours);
|
|
203
|
+
const schedule = makeScheduleFromHours(hours, null);
|
|
204
|
+
const resultToValidate = schedule.map((s) => ({ time: s.time, value: s.value }));
|
|
205
|
+
resultToValidate.push({
|
|
206
|
+
time: "2021-06-20T02:50:00.470+02:00",
|
|
207
|
+
value: false,
|
|
208
|
+
});
|
|
209
|
+
expect(resultToValidate).toEqual(testResult.schedule);
|
|
210
|
+
});
|
|
198
211
|
});
|
|
Binary file
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
next: ./ps-strategy-best-save.md
|
|
3
|
-
---
|
|
4
|
-
|
|
5
|
-
# power-saver <Badge type="warning" text="deprecated" vertical="middle" />
|
|
6
|
-
|
|
7
|
-

|
|
8
|
-
|
|
9
|
-
This is the node from version 2. It is still working, but should be replaced.
|
|
10
|
-
|
|
11
|
-
To migrate, just replace the Power Saver node by a combination of the ps-receive-price and the ps-best-save nodes:
|
|
12
|
-
|
|
13
|
-
Replace the `Power Saver` node from version 2:
|
|
14
|
-
|
|
15
|
-

|
|
16
|
-
|
|
17
|
-
with this combination of `ps-receive-price` and `ps-strategy-best-save` from version 3:
|
|
18
|
-
|
|
19
|
-

|
|
20
|
-
|
|
21
|
-
The configuration is done in the `ps-strategy-best-save` node, and is the same as in the old `Power Saver` node.
|
|
22
|
-
|
|
23
|
-
Should you need it, here is the [old documentation](./old-power-saver-doc) for the PowerSaver node from version 2.
|