node-red-contrib-power-saver 3.3.0 → 3.4.1
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/.firebase/hosting.ZG9jcy8udnVlcHJlc3MvZGlzdA.cache +221 -0
- package/.firebaserc +5 -0
- package/.github/workflows/firebase-hosting-merge.yml +20 -0
- package/.github/workflows/firebase-hosting-pull-request.yml +17 -0
- package/CHANGELOG.md +1 -1
- package/README.md +1 -1
- package/docs/.vuepress/clientAppEnhance.js +5 -0
- package/docs/.vuepress/components/BestSaveVerificator.vue +306 -0
- package/docs/.vuepress/components/DonateButtons.vue +87 -0
- package/docs/.vuepress/config.js +40 -7
- package/docs/.vuepress/dist/404.html +24 -6
- package/docs/.vuepress/dist/assets/css/835.styles.c5afb22b.css +1 -0
- package/docs/.vuepress/dist/assets/css/896.styles.21a80cb6.css +1 -0
- package/docs/.vuepress/dist/assets/css/styles.1c48cbd0.css +10 -0
- package/docs/.vuepress/dist/assets/img/best-save-config.79a2f39a.png +0 -0
- package/docs/.vuepress/dist/assets/img/copy-payload-best-save.b9192985.png +0 -0
- package/docs/.vuepress/dist/assets/img/heat-capacitor-temperatureVsPrice.6e74905b.png +0 -0
- package/docs/.vuepress/dist/assets/img/node-ps-strategy-heat-capacitor-cascade-control.2e75ed9e.png +0 -0
- package/docs/.vuepress/dist/assets/img/node-ps-strategy-heat-capacitor-simple-flow-example.29d9bf59.png +0 -0
- package/docs/.vuepress/dist/assets/img/oven-setpoint-calculation.5bda0eec.png +0 -0
- package/docs/.vuepress/dist/assets/img/overshoot-time.b3b5d70e.png +0 -0
- package/docs/.vuepress/dist/assets/js/229.5c5378fa.js +1 -0
- package/docs/.vuepress/dist/assets/js/331.872104cd.js +1 -0
- package/docs/.vuepress/dist/assets/js/405.f4edd94d.js +2 -0
- package/docs/.vuepress/dist/assets/js/{812.79dad458.js.LICENSE.txt → 405.f4edd94d.js.LICENSE.txt} +0 -0
- package/docs/.vuepress/dist/assets/js/490.1e639e05.js +1 -0
- package/docs/.vuepress/dist/assets/js/{491.c183eba3.js → 491.bd938119.js} +1 -1
- package/docs/.vuepress/dist/assets/js/555.d8963d84.js +1 -0
- package/docs/.vuepress/dist/assets/js/811.5f659592.js +1 -0
- package/docs/.vuepress/dist/assets/js/app.999deae8.js +1 -0
- package/docs/.vuepress/dist/assets/js/runtime~app.1774b667.js +1 -0
- package/docs/.vuepress/dist/assets/js/v-0607240a.1304cbb6.js +1 -0
- package/docs/.vuepress/dist/assets/js/v-08683c60.52e94cb6.js +1 -0
- package/docs/.vuepress/dist/assets/js/v-0aca7ba6.cac5d4b9.js +1 -0
- package/docs/.vuepress/dist/assets/js/v-0b5e3c8c.18561f6e.js +1 -0
- package/docs/.vuepress/dist/assets/js/v-1ad821fa.6697a349.js +1 -0
- package/docs/.vuepress/dist/assets/js/v-1b3a0ab8.c6c4e19b.js +1 -0
- package/docs/.vuepress/dist/assets/js/v-1e2b191e.07b8ab21.js +1 -0
- package/docs/.vuepress/dist/assets/js/v-29504124.00be7399.js +1 -0
- package/docs/.vuepress/dist/assets/js/v-30acb564.28af12af.js +1 -0
- package/docs/.vuepress/dist/assets/js/{v-3706649a.d7f73384.js → v-3706649a.c76d575b.js} +1 -1
- package/docs/.vuepress/dist/assets/js/v-4637f9e4.d334c29a.js +1 -0
- package/docs/.vuepress/dist/assets/js/v-4c28314d.8cbb0f9d.js +1 -0
- package/docs/.vuepress/dist/assets/js/v-510ed0d4.14f08cf3.js +1 -0
- package/docs/.vuepress/dist/assets/js/{v-5954bcb2.182daf70.js → v-5954bcb2.dff3fc67.js} +1 -1
- package/docs/.vuepress/dist/assets/js/v-5db8da3a.e5e6d7a6.js +1 -0
- package/docs/.vuepress/dist/assets/js/v-61f728ca.81968036.js +1 -0
- package/docs/.vuepress/dist/assets/js/v-677dfaed.c159b0f4.js +1 -0
- package/docs/.vuepress/dist/assets/js/v-7446a652.88afb5e4.js +1 -0
- package/docs/.vuepress/dist/assets/js/v-7c87f26e.8ed52391.js +1 -0
- package/docs/.vuepress/dist/assets/js/v-84304104.ad4da2d5.js +1 -0
- package/docs/.vuepress/dist/assets/js/{v-8daa1a0e.dde202c9.js → v-8daa1a0e.ed84ca09.js} +1 -1
- package/docs/.vuepress/dist/assets/js/{v-b4a42144.9e5f9728.js → v-b4a42144.9a2a0c9f.js} +1 -1
- package/docs/.vuepress/dist/assets/js/v-e8c55052.b7d52fc6.js +1 -0
- package/docs/.vuepress/dist/assets/js/v-fffb8e28.d09ab959.js +1 -0
- package/docs/.vuepress/dist/changelog/index.html +24 -6
- package/docs/.vuepress/dist/contribute/index.html +24 -6
- package/docs/.vuepress/dist/examples/example-cascade-temperature-control.html +304 -0
- package/docs/.vuepress/dist/examples/example-heat-capacitor.html +247 -0
- package/docs/.vuepress/dist/examples/example-next-schedule-entity.html +28 -10
- package/docs/.vuepress/dist/examples/example-nordpool-current-state.html +25 -8
- package/docs/.vuepress/dist/examples/example-nordpool-events-state.html +25 -7
- package/docs/.vuepress/dist/examples/example-tibber-mqtt.html +25 -8
- package/docs/.vuepress/dist/examples/index.html +24 -6
- package/docs/.vuepress/dist/faq/best-save-viewer.html +33 -0
- package/docs/.vuepress/dist/faq/index.html +24 -6
- package/docs/.vuepress/dist/guide/index.html +25 -7
- package/docs/.vuepress/dist/index.html +24 -6
- package/docs/.vuepress/dist/nodes/index.html +24 -6
- package/docs/.vuepress/dist/nodes/old-power-saver-doc.html +26 -8
- package/docs/.vuepress/dist/nodes/power-saver.html +24 -6
- package/docs/.vuepress/dist/nodes/ps-elvia-add-tariff.html +24 -6
- package/docs/.vuepress/dist/nodes/ps-general-add-tariff.html +24 -6
- package/docs/.vuepress/dist/nodes/ps-receive-price.html +27 -9
- package/docs/.vuepress/dist/nodes/ps-strategy-best-save.html +46 -9
- package/docs/.vuepress/dist/nodes/ps-strategy-heat-capacitor.html +260 -0
- package/docs/.vuepress/dist/nodes/ps-strategy-lowest-price.html +41 -8
- package/docs/.vuepress/dist/nodes/strategy-input.html +25 -7
- package/docs/README.md +4 -1
- package/docs/changelog/README.md +19 -0
- package/docs/contribute/README.md +11 -3
- package/docs/examples/README.md +8 -2
- package/docs/examples/example-cascade-temperature-control.md +346 -0
- package/docs/examples/example-heat-capacitor.md +271 -0
- package/docs/faq/README.md +23 -0
- package/docs/faq/best-save-viewer.md +61 -0
- package/docs/images/copy-payload-best-save.png +0 -0
- package/docs/images/heat-capacitor-temperatureVsPrice.png +0 -0
- package/docs/images/node-ps-strategy-heat-capacitor-cascade-control.png +0 -0
- package/docs/images/node-ps-strategy-heat-capacitor-simple-flow-example.png +0 -0
- package/docs/images/node-ps-strategy-heat-capacitor.png +0 -0
- package/docs/images/oven-setpoint-calculation.png +0 -0
- package/docs/images/overshoot-time.png +0 -0
- package/docs/images/temperature-manipulation-config.png +0 -0
- package/docs/nodes/README.md +7 -1
- package/docs/nodes/ps-strategy-best-save.md +18 -0
- package/docs/nodes/ps-strategy-heat-capacitor.md +346 -0
- package/docs/nodes/ps-strategy-lowest-price.md +14 -0
- package/examples/add-general-tariff.json +103 -0
- package/examples/best-save-for-water-heater.json +140 -0
- package/examples/elvia-add-tariff.json +99 -0
- package/examples/elvia-get-tariff-types.json +58 -0
- package/examples/elvia-get-tariff.json +60 -0
- package/examples/heat-capacitor-for-room-heating.json +186 -0
- package/examples/lowest-price-for-heating-cables.json +159 -0
- package/firebase.json +6 -0
- package/package.json +20 -8
- package/public/404.html +33 -0
- package/public/index.html +89 -0
- package/src/elvia/elvia-tariff.js +2 -1
- package/src/handle-input.js +2 -2
- package/src/strategy-heat-capacitor-functions.js +246 -0
- package/src/strategy-heat-capacitor.html +85 -0
- package/src/strategy-heat-capacitor.js +125 -0
- package/test/data/converted-prices.json +1 -1
- package/test/data/multiple-trades.json +53 -0
- package/test/data/tibber-decreasing-24h.json +101 -0
- package/test/data/tibber-decreasing2-24h.json +101 -0
- package/test/strategy-best-save.test.js +14 -8
- package/test/strategy-heat-capacitor-node.test.js +183 -0
- package/test/strategy-heat-capacitor.test.js +103 -0
- package/test/strategy-lowest-price-functions.test.js +1 -1
- package/test/utils.test.js +0 -2
- package/docs/.vuepress/dist/.nojekyll +0 -0
- package/docs/.vuepress/dist/assets/css/styles.e835bef6.css +0 -8
- package/docs/.vuepress/dist/assets/img/best-save-config.93fa3c21.png +0 -0
- package/docs/.vuepress/dist/assets/js/293.5e967839.js +0 -1
- package/docs/.vuepress/dist/assets/js/812.79dad458.js +0 -2
- package/docs/.vuepress/dist/assets/js/app.49ad6d54.js +0 -1
- package/docs/.vuepress/dist/assets/js/runtime~app.547d0f4e.js +0 -1
- package/docs/.vuepress/dist/assets/js/v-0607240a.661e1808.js +0 -1
- package/docs/.vuepress/dist/assets/js/v-08683c60.a6b9cf5b.js +0 -1
- package/docs/.vuepress/dist/assets/js/v-0aca7ba6.b42fad7f.js +0 -1
- package/docs/.vuepress/dist/assets/js/v-0b5e3c8c.3b37457f.js +0 -1
- package/docs/.vuepress/dist/assets/js/v-1ad821fa.6e2194d0.js +0 -1
- package/docs/.vuepress/dist/assets/js/v-1e2b191e.98cc227b.js +0 -1
- package/docs/.vuepress/dist/assets/js/v-30acb564.f2fcd69f.js +0 -1
- package/docs/.vuepress/dist/assets/js/v-4637f9e4.b320f5e8.js +0 -1
- package/docs/.vuepress/dist/assets/js/v-510ed0d4.3c7e0056.js +0 -1
- package/docs/.vuepress/dist/assets/js/v-5db8da3a.f2de6cb9.js +0 -1
- package/docs/.vuepress/dist/assets/js/v-61f728ca.6fdbbb92.js +0 -1
- package/docs/.vuepress/dist/assets/js/v-677dfaed.0013f083.js +0 -1
- package/docs/.vuepress/dist/assets/js/v-7446a652.d05e2648.js +0 -1
- package/docs/.vuepress/dist/assets/js/v-7c87f26e.1127dcf5.js +0 -1
- package/docs/.vuepress/dist/assets/js/v-e8c55052.8384b053.js +0 -1
- package/docs/.vuepress/dist/assets/js/v-fffb8e28.3406fd88.js +0 -1
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"source": "Tibber",
|
|
3
|
+
"priceData": [
|
|
4
|
+
{ "value": 1.4157, "start": "2022-02-06T00:00:00.000+01:00" },
|
|
5
|
+
{ "value": 1.3705, "start": "2022-02-06T01:00:00.000+01:00" },
|
|
6
|
+
{ "value": 1.2487, "start": "2022-02-06T02:00:00.000+01:00" },
|
|
7
|
+
{ "value": 1.1812, "start": "2022-02-06T03:00:00.000+01:00" },
|
|
8
|
+
{ "value": 0.9632, "start": "2022-02-06T04:00:00.000+01:00" },
|
|
9
|
+
{ "value": 1.0862, "start": "2022-02-06T05:00:00.000+01:00" },
|
|
10
|
+
{ "value": 1.2638, "start": "2022-02-06T06:00:00.000+01:00" },
|
|
11
|
+
{ "value": 1.3266, "start": "2022-02-06T07:00:00.000+01:00" },
|
|
12
|
+
{ "value": 1.3734, "start": "2022-02-06T08:00:00.000+01:00" },
|
|
13
|
+
{ "value": 1.4653, "start": "2022-02-06T09:00:00.000+01:00" },
|
|
14
|
+
{ "value": 1.513, "start": "2022-02-06T10:00:00.000+01:00" },
|
|
15
|
+
{ "value": 1.518, "start": "2022-02-06T11:00:00.000+01:00" },
|
|
16
|
+
{ "value": 1.505, "start": "2022-02-06T12:00:00.000+01:00" },
|
|
17
|
+
{ "value": 1.5038, "start": "2022-02-06T13:00:00.000+01:00" },
|
|
18
|
+
{ "value": 1.5239, "start": "2022-02-06T14:00:00.000+01:00" },
|
|
19
|
+
{ "value": 1.5269, "start": "2022-02-06T15:00:00.000+01:00" },
|
|
20
|
+
{ "value": 1.5464, "start": "2022-02-06T16:00:00.000+01:00" },
|
|
21
|
+
{ "value": 1.5553, "start": "2022-02-06T17:00:00.000+01:00" },
|
|
22
|
+
{ "value": 1.6066, "start": "2022-02-06T18:00:00.000+01:00" },
|
|
23
|
+
{ "value": 1.5541, "start": "2022-02-06T19:00:00.000+01:00" },
|
|
24
|
+
{ "value": 1.5437, "start": "2022-02-06T20:00:00.000+01:00" },
|
|
25
|
+
{ "value": 1.4912, "start": "2022-02-06T21:00:00.000+01:00" },
|
|
26
|
+
{ "value": 1.473, "start": "2022-02-06T22:00:00.000+01:00" },
|
|
27
|
+
{ "value": 1.4281, "start": "2022-02-06T23:00:00.000+01:00" },
|
|
28
|
+
{ "value": 1.4179, "start": "2022-02-07T00:00:00.000+01:00" },
|
|
29
|
+
{ "value": 1.3966, "start": "2022-02-07T01:00:00.000+01:00" },
|
|
30
|
+
{ "value": 1.3953, "start": "2022-02-07T02:00:00.000+01:00" },
|
|
31
|
+
{ "value": 1.3939, "start": "2022-02-07T03:00:00.000+01:00" },
|
|
32
|
+
{ "value": 1.3962, "start": "2022-02-07T04:00:00.000+01:00" },
|
|
33
|
+
{ "value": 1.4335, "start": "2022-02-07T05:00:00.000+01:00" },
|
|
34
|
+
{ "value": 1.5191, "start": "2022-02-07T06:00:00.000+01:00" },
|
|
35
|
+
{ "value": 1.6244, "start": "2022-02-07T07:00:00.000+01:00" },
|
|
36
|
+
{ "value": 1.6508, "start": "2022-02-07T08:00:00.000+01:00" },
|
|
37
|
+
{ "value": 1.6142, "start": "2022-02-07T09:00:00.000+01:00" },
|
|
38
|
+
{ "value": 1.5632, "start": "2022-02-07T10:00:00.000+01:00" },
|
|
39
|
+
{ "value": 1.5516, "start": "2022-02-07T11:00:00.000+01:00" },
|
|
40
|
+
{ "value": 1.4922, "start": "2022-02-07T12:00:00.000+01:00" },
|
|
41
|
+
{ "value": 1.5288, "start": "2022-02-07T13:00:00.000+01:00" },
|
|
42
|
+
{ "value": 1.5522, "start": "2022-02-07T14:00:00.000+01:00" },
|
|
43
|
+
{ "value": 1.5526, "start": "2022-02-07T15:00:00.000+01:00" },
|
|
44
|
+
{ "value": 1.5799, "start": "2022-02-07T16:00:00.000+01:00" },
|
|
45
|
+
{ "value": 1.6909, "start": "2022-02-07T17:00:00.000+01:00" },
|
|
46
|
+
{ "value": 1.7136, "start": "2022-02-07T18:00:00.000+01:00" },
|
|
47
|
+
{ "value": 1.703, "start": "2022-02-07T19:00:00.000+01:00" },
|
|
48
|
+
{ "value": 1.675, "start": "2022-02-07T20:00:00.000+01:00" },
|
|
49
|
+
{ "value": 1.6173, "start": "2022-02-07T21:00:00.000+01:00" },
|
|
50
|
+
{ "value": 1.5638, "start": "2022-02-07T22:00:00.000+01:00" },
|
|
51
|
+
{ "value": 1.4645, "start": "2022-02-07T23:00:00.000+01:00" }
|
|
52
|
+
]
|
|
53
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
{
|
|
2
|
+
"source": "Tibber",
|
|
3
|
+
"priceData": [
|
|
4
|
+
{
|
|
5
|
+
"value": 1.8019,
|
|
6
|
+
"start": "2022-01-12T00:00:00.000+01:00"
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"value": 1.7711,
|
|
10
|
+
"start": "2022-01-12T01:00:00.000+01:00"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"value": 1.7539,
|
|
14
|
+
"start": "2022-01-12T02:00:00.000+01:00"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"value": 1.7508,
|
|
18
|
+
"start": "2022-01-12T03:00:00.000+01:00"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"value": 1.7616,
|
|
22
|
+
"start": "2022-01-12T04:00:00.000+01:00"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"value": 1.7825,
|
|
26
|
+
"start": "2022-01-12T05:00:00.000+01:00"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"value": 1.7829,
|
|
30
|
+
"start": "2022-01-12T06:00:00.000+01:00"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"value": 1.8422,
|
|
34
|
+
"start": "2022-01-12T07:00:00.000+01:00"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"value": 1.8725,
|
|
38
|
+
"start": "2022-01-12T08:00:00.000+01:00"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"value": 1.8356,
|
|
42
|
+
"start": "2022-01-12T09:00:00.000+01:00"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"value": 1.8053,
|
|
46
|
+
"start": "2022-01-12T10:00:00.000+01:00"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"value": 1.7625,
|
|
50
|
+
"start": "2022-01-12T11:00:00.000+01:00"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"value": 1.7601,
|
|
54
|
+
"start": "2022-01-12T12:00:00.000+01:00"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"value": 1.7481,
|
|
58
|
+
"start": "2022-01-12T13:00:00.000+01:00"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"value": 1.7269,
|
|
62
|
+
"start": "2022-01-12T14:00:00.000+01:00"
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"value": 1.717,
|
|
66
|
+
"start": "2022-01-12T15:00:00.000+01:00"
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"value": 1.7181,
|
|
70
|
+
"start": "2022-01-12T16:00:00.000+01:00"
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"value": 1.7169,
|
|
74
|
+
"start": "2022-01-12T17:00:00.000+01:00"
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"value": 1.7015,
|
|
78
|
+
"start": "2022-01-12T18:00:00.000+01:00"
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"value": 1.6708,
|
|
82
|
+
"start": "2022-01-12T19:00:00.000+01:00"
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"value": 1.6499,
|
|
86
|
+
"start": "2022-01-12T20:00:00.000+01:00"
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"value": 1.644,
|
|
90
|
+
"start": "2022-01-12T21:00:00.000+01:00"
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"value": 1.624,
|
|
94
|
+
"start": "2022-01-12T22:00:00.000+01:00"
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"value": 1.6078,
|
|
98
|
+
"start": "2022-01-12T23:00:00.000+01:00"
|
|
99
|
+
}
|
|
100
|
+
]
|
|
101
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
{
|
|
2
|
+
"priceData": [
|
|
3
|
+
{
|
|
4
|
+
"value": 1.7261,
|
|
5
|
+
"start": "2022-01-16T00:00:00.000+01:00"
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
"value": 1.7153,
|
|
9
|
+
"start": "2022-01-16T01:00:00.000+01:00"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"value": 1.6872,
|
|
13
|
+
"start": "2022-01-16T02:00:00.000+01:00"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"value": 1.6842,
|
|
17
|
+
"start": "2022-01-16T03:00:00.000+01:00"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"value": 1.6831,
|
|
21
|
+
"start": "2022-01-16T04:00:00.000+01:00"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"value": 1.6811,
|
|
25
|
+
"start": "2022-01-16T05:00:00.000+01:00"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"value": 1.6117,
|
|
29
|
+
"start": "2022-01-16T06:00:00.000+01:00"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"value": 1.5729,
|
|
33
|
+
"start": "2022-01-16T07:00:00.000+01:00"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"value": 1.5969,
|
|
37
|
+
"start": "2022-01-16T08:00:00.000+01:00"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"value": 1.6049,
|
|
41
|
+
"start": "2022-01-16T09:00:00.000+01:00"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"value": 1.6117,
|
|
45
|
+
"start": "2022-01-16T10:00:00.000+01:00"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"value": 1.6103,
|
|
49
|
+
"start": "2022-01-16T11:00:00.000+01:00"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"value": 1.6004,
|
|
53
|
+
"start": "2022-01-16T12:00:00.000+01:00"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"value": 1.5937,
|
|
57
|
+
"start": "2022-01-16T13:00:00.000+01:00"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"value": 1.5476,
|
|
61
|
+
"start": "2022-01-16T14:00:00.000+01:00"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"value": 1.5866,
|
|
65
|
+
"start": "2022-01-16T15:00:00.000+01:00"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"value": 1.5959,
|
|
69
|
+
"start": "2022-01-16T16:00:00.000+01:00"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"value": 1.6316,
|
|
73
|
+
"start": "2022-01-16T17:00:00.000+01:00"
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"value": 1.5908,
|
|
77
|
+
"start": "2022-01-16T18:00:00.000+01:00"
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"value": 1.5805,
|
|
81
|
+
"start": "2022-01-16T19:00:00.000+01:00"
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"value": 1.5808,
|
|
85
|
+
"start": "2022-01-16T20:00:00.000+01:00"
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"value": 1.5681,
|
|
89
|
+
"start": "2022-01-16T21:00:00.000+01:00"
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"value": 1.5218,
|
|
93
|
+
"start": "2022-01-16T22:00:00.000+01:00"
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"value": 1.46,
|
|
97
|
+
"start": "2022-01-16T23:00:00.000+01:00"
|
|
98
|
+
}
|
|
99
|
+
],
|
|
100
|
+
"source": "Tibber"
|
|
101
|
+
}
|
|
@@ -5,8 +5,9 @@ const helper = require("node-red-node-test-helper");
|
|
|
5
5
|
const bestSave = require("../src/strategy-best-save.js");
|
|
6
6
|
const prices = require("./data/converted-prices.json");
|
|
7
7
|
const result = require("./data/best-save-result.json");
|
|
8
|
-
const { testPlan: plan } = require("./test-utils");
|
|
8
|
+
const { testPlan: plan, equalPlan } = require("./test-utils");
|
|
9
9
|
const { makeFlow } = require("./strategy-best-save-test-utils");
|
|
10
|
+
const { version } = require("../package.json");
|
|
10
11
|
|
|
11
12
|
helper.init(require.resolve("node-red"));
|
|
12
13
|
|
|
@@ -61,6 +62,11 @@ describe("ps-strategy-best-save node", function () {
|
|
|
61
62
|
});
|
|
62
63
|
it("should send new schedule on output 3", function (done) {
|
|
63
64
|
const flow = makeFlow(3, 2);
|
|
65
|
+
const expected = cloneDeep(result);
|
|
66
|
+
expected.version = version;
|
|
67
|
+
expected.time = plan.time;
|
|
68
|
+
expected.source = "Tibber";
|
|
69
|
+
expected.current = false;
|
|
64
70
|
helper.load(bestSave, flow, function () {
|
|
65
71
|
const n1 = helper.getNode("n1");
|
|
66
72
|
const n2 = helper.getNode("n2");
|
|
@@ -69,22 +75,22 @@ describe("ps-strategy-best-save node", function () {
|
|
|
69
75
|
let countOn = 0;
|
|
70
76
|
let countOff = 0;
|
|
71
77
|
n2.on("input", function (msg) {
|
|
72
|
-
expect(msg).
|
|
78
|
+
expect(equalPlan(expected, msg.payload)).toBeTruthy();
|
|
73
79
|
n1.warn.should.not.be.called;
|
|
80
|
+
setTimeout(() => {
|
|
81
|
+
console.log("countOn = " + countOn + ", countOff = " + countOff);
|
|
82
|
+
expect(countOn).toEqual(7);
|
|
83
|
+
expect(countOff).toEqual(7);
|
|
84
|
+
done();
|
|
85
|
+
}, 900);
|
|
74
86
|
});
|
|
75
87
|
n3.on("input", function (msg) {
|
|
76
88
|
countOn++;
|
|
77
89
|
expect(msg).toHaveProperty("payload", true);
|
|
78
|
-
if (countOn === 7 && countOff === 7) {
|
|
79
|
-
done();
|
|
80
|
-
}
|
|
81
90
|
});
|
|
82
91
|
n4.on("input", function (msg) {
|
|
83
92
|
countOff++;
|
|
84
93
|
expect(msg).toHaveProperty("payload", false);
|
|
85
|
-
if (countOn === 7 && countOff === 7) {
|
|
86
|
-
done();
|
|
87
|
-
}
|
|
88
94
|
});
|
|
89
95
|
n1.receive({ payload: makePayload(prices, plan.time) });
|
|
90
96
|
});
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const cloneDeep = require("lodash.clonedeep");
|
|
3
|
+
const { DateTime } = require("luxon");
|
|
4
|
+
const expect = require("expect");
|
|
5
|
+
const helper = require("node-red-node-test-helper");
|
|
6
|
+
const node = require("../src/strategy-heat-capacitor.js");
|
|
7
|
+
const prices = require("./data/converted-prices.json");
|
|
8
|
+
const multiTrade = require("./data/multiple-trades.json");
|
|
9
|
+
|
|
10
|
+
helper.init(require.resolve("node-red"));
|
|
11
|
+
|
|
12
|
+
describe("ps-strategy-heat-capacitor 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-heat-capacitor", name: "Temp. Adj." }];
|
|
25
|
+
helper.load(node, flow, function () {
|
|
26
|
+
const n1 = helper.getNode("n1");
|
|
27
|
+
expect(n1).toHaveProperty("name", "Temp. Adj.");
|
|
28
|
+
done();
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("should log error when illegal data is received", function (done) {
|
|
33
|
+
const flow = [{ id: "n1", type: "ps-strategy-heat-capacitor", name: "Temp. Adj." }];
|
|
34
|
+
helper.load(node, flow, function () {
|
|
35
|
+
const n1 = helper.getNode("n1");
|
|
36
|
+
n1.receive({});
|
|
37
|
+
n1.warn.should.be.calledWithExactly("No payload");
|
|
38
|
+
n1.receive({ payload: "Error" });
|
|
39
|
+
n1.warn.should.be.calledWithExactly("Payload is not an object");
|
|
40
|
+
n1.receive({ payload: [] });
|
|
41
|
+
n1.warn.should.be.calledWithExactly("Payload is missing priceData");
|
|
42
|
+
n1.receive({ payload: { priceData: [] } });
|
|
43
|
+
n1.warn.should.be.calledWithExactly("priceData is empty");
|
|
44
|
+
n1.receive({ payload: { priceData: { today: [], tomorrow: [] } } });
|
|
45
|
+
n1.warn.should.be.calledWithExactly("Illegal priceData in payload. Did you use the receive-price node?");
|
|
46
|
+
|
|
47
|
+
["start", "value"].forEach((attr) => {
|
|
48
|
+
const testData1 = cloneDeep(prices);
|
|
49
|
+
delete testData1.priceData[3][attr];
|
|
50
|
+
n1.receive({ payload: testData1 });
|
|
51
|
+
n1.warn.should.be.calledWithExactly(
|
|
52
|
+
"Malformed entries in priceData. All entries must contain start and value."
|
|
53
|
+
);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
n1.receive({ payload: cloneDeep(prices) });
|
|
57
|
+
n1.warn.should.not.be.called;
|
|
58
|
+
done();
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("should be configurable", function (done) {
|
|
63
|
+
const flow = [{ id: "n1", type: "ps-strategy-heat-capacitor", name: "Heat Capacitor" }];
|
|
64
|
+
helper.load(node, flow, function () {
|
|
65
|
+
const n1 = helper.getNode("n1");
|
|
66
|
+
n1.receive({
|
|
67
|
+
payload: {
|
|
68
|
+
config: {
|
|
69
|
+
timeHeat1C: 1,
|
|
70
|
+
timeCool1C: 2,
|
|
71
|
+
setpoint: 3,
|
|
72
|
+
maxTempAdjustment: 4,
|
|
73
|
+
minSavings: 5,
|
|
74
|
+
boostTempHeat: 6,
|
|
75
|
+
boostTempCool: 7,
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
expect(n1).toHaveProperty("timeHeat1C", 1);
|
|
80
|
+
expect(n1).toHaveProperty("timeCool1C", 2);
|
|
81
|
+
expect(n1).toHaveProperty("boostTempHeat", 6);
|
|
82
|
+
expect(n1).toHaveProperty("boostTempCool", 7);
|
|
83
|
+
expect(n1).toHaveProperty("setpoint", 3);
|
|
84
|
+
expect(n1).toHaveProperty("maxTempAdjustment", 4);
|
|
85
|
+
expect(n1).toHaveProperty("minSavings", 5);
|
|
86
|
+
n1.receive({ payload: { config: { setpoint: 24 } } });
|
|
87
|
+
expect(n1).toHaveProperty("setpoint", 24);
|
|
88
|
+
done();
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("should plan correctly", function (done) {
|
|
93
|
+
const result = 0.5;
|
|
94
|
+
const flow = makeFlow();
|
|
95
|
+
helper.load(node, flow, function () {
|
|
96
|
+
const n1 = helper.getNode("n1");
|
|
97
|
+
const n2 = helper.getNode("n2");
|
|
98
|
+
const n3 = helper.getNode("n3");
|
|
99
|
+
let bothReceived = false;
|
|
100
|
+
n2.on("input", function (msg) {
|
|
101
|
+
expect(msg).toHaveProperty("payload", 22.5);
|
|
102
|
+
n1.warn.should.not.be.called;
|
|
103
|
+
bothReceived ? done() : (bothReceived = true);
|
|
104
|
+
});
|
|
105
|
+
n3.on("input", function (msg) {
|
|
106
|
+
expect(msg).toHaveProperty("payload", -0.5);
|
|
107
|
+
n1.warn.should.not.be.called;
|
|
108
|
+
bothReceived ? done() : (bothReceived = true);
|
|
109
|
+
});
|
|
110
|
+
const time = DateTime.fromISO(prices.priceData[10].start);
|
|
111
|
+
const p = cloneDeep(prices);
|
|
112
|
+
p.time = time;
|
|
113
|
+
n1.receive({ payload: p });
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("should plan correctly, multiTrade", function (done) {
|
|
118
|
+
const result = 0.5;
|
|
119
|
+
const flow = makeFlow();
|
|
120
|
+
helper.load(node, flow, function () {
|
|
121
|
+
const n1 = helper.getNode("n1");
|
|
122
|
+
const n2 = helper.getNode("n2");
|
|
123
|
+
const n3 = helper.getNode("n3");
|
|
124
|
+
let bothReceived = false;
|
|
125
|
+
n2.on("input", function (msg) {
|
|
126
|
+
expect(msg).toHaveProperty("payload", 24.5);
|
|
127
|
+
n1.warn.should.not.be.called;
|
|
128
|
+
bothReceived ? done() : (bothReceived = true);
|
|
129
|
+
});
|
|
130
|
+
n3.on("input", function (msg) {
|
|
131
|
+
expect(msg).toHaveProperty("payload", 1.5);
|
|
132
|
+
n1.warn.should.not.be.called;
|
|
133
|
+
bothReceived ? done() : (bothReceived = true);
|
|
134
|
+
});
|
|
135
|
+
const time = DateTime.fromISO(multiTrade.priceData[4].start).plus({ minutes: 10 });
|
|
136
|
+
multiTrade.time = time;
|
|
137
|
+
n1.receive({ payload: multiTrade });
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("should merge and trim priceData", function (done) {
|
|
142
|
+
const flow = makeFlow();
|
|
143
|
+
helper.load(node, flow, function () {
|
|
144
|
+
const n1 = helper.getNode("n1");
|
|
145
|
+
n1.receive({ payload: multiTrade });
|
|
146
|
+
n1.receive({ payload: prices });
|
|
147
|
+
expect(n1.priceData.length).toEqual(72);
|
|
148
|
+
done();
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
function makeFlow() {
|
|
154
|
+
return [
|
|
155
|
+
{
|
|
156
|
+
id: "n1",
|
|
157
|
+
type: "ps-strategy-heat-capacitor",
|
|
158
|
+
name: "Temp. Adj.",
|
|
159
|
+
timeHeat1C: 60,
|
|
160
|
+
timeCool1C: 50,
|
|
161
|
+
boostTempHeat: 1,
|
|
162
|
+
boostTempCool: 1,
|
|
163
|
+
setpoint: 23,
|
|
164
|
+
maxTempAdjustment: 0.5,
|
|
165
|
+
minSavings: 0.08,
|
|
166
|
+
wires: [["n2"], ["n3"], ["n4"]],
|
|
167
|
+
},
|
|
168
|
+
{ id: "n2", type: "helper" },
|
|
169
|
+
{ id: "n3", type: "helper" },
|
|
170
|
+
{ id: "n4", type: "helper" },
|
|
171
|
+
];
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function makePayload(prices, time) {
|
|
175
|
+
const payload = cloneDeep(prices);
|
|
176
|
+
payload.time = time;
|
|
177
|
+
let entryTime = DateTime.fromISO(payload.time);
|
|
178
|
+
payload.priceData.forEach((e) => {
|
|
179
|
+
e.start = entryTime.toISO();
|
|
180
|
+
entryTime = entryTime.plus({ milliseconds: 10 });
|
|
181
|
+
});
|
|
182
|
+
return payload;
|
|
183
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const { DateTime } = require("luxon");
|
|
3
|
+
const expect = require("expect");
|
|
4
|
+
const {
|
|
5
|
+
calculateSchedule,
|
|
6
|
+
calculateOpportunities,
|
|
7
|
+
findBestBuySellPattern,
|
|
8
|
+
calculateValueDictList,
|
|
9
|
+
removeLowBuySellPairs,
|
|
10
|
+
} = require("../src/strategy-heat-capacitor-functions");
|
|
11
|
+
const converted_prices = require("./data/converted-prices.json");
|
|
12
|
+
const decreasing_end_prices = require("./data/tibber-decreasing2-24h.json");
|
|
13
|
+
|
|
14
|
+
describe("Test heat capacitor strategy functions", () => {
|
|
15
|
+
let prices, decreasing_24h_prices, start_date, buy_pattern, sell_pattern;
|
|
16
|
+
|
|
17
|
+
//User input
|
|
18
|
+
const timeHeat1C = 60;
|
|
19
|
+
const timeCool1C = 40;
|
|
20
|
+
const maxTempAdjustment = 1;
|
|
21
|
+
const boostTempHeat = 1;
|
|
22
|
+
const boostTempCool = 1;
|
|
23
|
+
const minSavings = 0.1;
|
|
24
|
+
|
|
25
|
+
before(function () {
|
|
26
|
+
prices = converted_prices.priceData.slice(0, 1).map((p) => p.value);
|
|
27
|
+
decreasing_24h_prices = decreasing_end_prices.priceData.slice(0, 1).map((p) => p.value);
|
|
28
|
+
start_date = DateTime.fromISO(converted_prices.priceData[0].start);
|
|
29
|
+
buy_pattern = Array(Math.round(timeHeat1C * maxTempAdjustment * 2)).fill(1);
|
|
30
|
+
sell_pattern = Array(Math.round(timeCool1C * maxTempAdjustment * 2)).fill(1);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("Can calculate procurement opportunities", () => {
|
|
34
|
+
const my_prices = prices.slice(0, 1);
|
|
35
|
+
const my_buy_pattern = Array(5).fill(1);
|
|
36
|
+
//Calculate what it will cost to procure/sell 1 kWh as a function of time
|
|
37
|
+
let result = calculateOpportunities(my_prices, my_buy_pattern, 1);
|
|
38
|
+
//Remove float precisions errors by rounding
|
|
39
|
+
result = result.map((x) => Math.round(x * 1000000) / 1000000);
|
|
40
|
+
expect(result).toEqual(Array(56).fill(my_prices[0]));
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("Can find procurement pattern", () => {
|
|
44
|
+
//Use a simple price list
|
|
45
|
+
const my_prices = [1, 2, 2, 1, 8, 1];
|
|
46
|
+
|
|
47
|
+
const buy_prices = calculateOpportunities(my_prices, buy_pattern, 1);
|
|
48
|
+
const sell_prices = calculateOpportunities(my_prices, sell_pattern, 1);
|
|
49
|
+
|
|
50
|
+
const my_buy_sell = findBestBuySellPattern(buy_prices, buy_pattern.length, sell_prices, sell_pattern.length);
|
|
51
|
+
|
|
52
|
+
expect(my_buy_sell).toEqual([
|
|
53
|
+
[0, 141],
|
|
54
|
+
[100, 240],
|
|
55
|
+
]);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("DictList test", () => {
|
|
59
|
+
const my_prices = [1, 2, 2, 1, 8, 1];
|
|
60
|
+
const my_buy_sell_indexes = [
|
|
61
|
+
[0, 173],
|
|
62
|
+
[131, 251],
|
|
63
|
+
];
|
|
64
|
+
const buy_prices = calculateOpportunities(my_prices, buy_pattern, 1);
|
|
65
|
+
const sell_prices = calculateOpportunities(my_prices, sell_pattern, 1);
|
|
66
|
+
const result = calculateValueDictList(my_buy_sell_indexes, buy_prices, sell_prices, start_date);
|
|
67
|
+
|
|
68
|
+
expect(result[0].sellDate).toEqual(start_date.plus({ minutes: 131 }));
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("DictList test at decreasing end", () => {
|
|
72
|
+
const my_prices = decreasing_end_prices.priceData.map((p) => p.value);
|
|
73
|
+
const buy_prices = calculateOpportunities(my_prices, buy_pattern, 1);
|
|
74
|
+
const sell_prices = calculateOpportunities(my_prices, sell_pattern, 1);
|
|
75
|
+
|
|
76
|
+
const my_buy_sell = findBestBuySellPattern(buy_prices, buy_pattern.length, sell_prices, sell_pattern.length);
|
|
77
|
+
const my_schedule = calculateSchedule(
|
|
78
|
+
start_date,
|
|
79
|
+
my_buy_sell,
|
|
80
|
+
buy_prices,
|
|
81
|
+
sell_prices,
|
|
82
|
+
maxTempAdjustment,
|
|
83
|
+
boostTempHeat,
|
|
84
|
+
boostTempCool,
|
|
85
|
+
buy_pattern.length,
|
|
86
|
+
sell_pattern.length
|
|
87
|
+
);
|
|
88
|
+
expect(my_schedule.temperatures[my_schedule.temperatures.length - 1]).toEqual(-maxTempAdjustment);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("Check removal of low benefit buy-sell pairs", () => {
|
|
92
|
+
const my_prices = [1, 2, 1, 1.05, 1, 2];
|
|
93
|
+
const buy_prices = calculateOpportunities(my_prices, buy_pattern, 1);
|
|
94
|
+
const sell_prices = calculateOpportunities(my_prices, sell_pattern, 1);
|
|
95
|
+
const my_buy_sell = findBestBuySellPattern(buy_prices, buy_pattern.length, sell_prices, sell_pattern.length);
|
|
96
|
+
|
|
97
|
+
const result = removeLowBuySellPairs(my_buy_sell, buy_prices, sell_prices, minSavings, start_date);
|
|
98
|
+
//Should remove the sell at 1.05 and the re-buy at 1 (only 0.05 difference)
|
|
99
|
+
const compare = [my_buy_sell[0].slice(0, 2), [my_buy_sell[1][0], my_buy_sell[1][2]]];
|
|
100
|
+
|
|
101
|
+
expect(result).toEqual(compare);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
@@ -2,7 +2,7 @@ const { DateTime } = require("luxon");
|
|
|
2
2
|
const expect = require("expect");
|
|
3
3
|
const { getBestContinuous, getBestX } = require("../src/strategy-lowest-price-functions");
|
|
4
4
|
const convertedPrices = require("./data/converted-prices.json");
|
|
5
|
-
const
|
|
5
|
+
const cloneDeep = require("lodash.clonedeep");
|
|
6
6
|
|
|
7
7
|
describe("strategy-utils", () => {
|
|
8
8
|
it("can find best x", () => {
|
package/test/utils.test.js
CHANGED
|
@@ -5,14 +5,12 @@ const {
|
|
|
5
5
|
sortedIndex,
|
|
6
6
|
firstOn,
|
|
7
7
|
getDiffToNextOn,
|
|
8
|
-
isOnOffSequencesOk,
|
|
9
8
|
getSavings,
|
|
10
9
|
countAtEnd,
|
|
11
10
|
makeSchedule,
|
|
12
11
|
fillArray,
|
|
13
12
|
extractPlanForDate,
|
|
14
13
|
isSameDate,
|
|
15
|
-
getStartAtIndex,
|
|
16
14
|
} = require("../src/utils");
|
|
17
15
|
|
|
18
16
|
describe("utils", () => {
|
|
File without changes
|