node-red-contrib-power-saver 2.1.0 → 3.0.3

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.
Files changed (179) hide show
  1. package/.github/FUNDING.yml +12 -0
  2. package/CHANGELOG.md +1 -43
  3. package/README.md +3 -231
  4. package/docs/.vuepress/config.js +67 -0
  5. package/docs/.vuepress/dist/404.html +15 -0
  6. package/docs/.vuepress/dist/assets/css/styles.e835bef6.css +8 -0
  7. package/docs/.vuepress/dist/assets/img/back-to-top.8b37f773.svg +1 -0
  8. package/docs/.vuepress/dist/assets/img/elvia-config-no-config.b4bb972c.png +0 -0
  9. package/docs/.vuepress/dist/assets/img/elvia-config-no-tariff.3f89aba8.png +0 -0
  10. package/docs/.vuepress/dist/assets/img/elvia-config-select-tariff.0f73fd56.png +0 -0
  11. package/docs/.vuepress/dist/assets/img/elvia-config-subscription-key.8be8ab8a.png +0 -0
  12. package/docs/.vuepress/dist/assets/img/elvia-flow.bae2a4d5.png +0 -0
  13. package/docs/.vuepress/dist/assets/img/example-flow-1.3ff3e23f.png +0 -0
  14. package/docs/.vuepress/dist/assets/img/example-flow-2.b653b58d.png +0 -0
  15. package/docs/.vuepress/dist/assets/img/migrate-best-save.f73420f6.png +0 -0
  16. package/docs/.vuepress/dist/assets/img/migrate-power-saver.aae13f9d.png +0 -0
  17. package/docs/.vuepress/dist/assets/img/node-power-saver.51ff2e5d.png +0 -0
  18. package/docs/.vuepress/dist/assets/img/node-ps-elvia-add-tariff.94ea2b09.png +0 -0
  19. package/docs/.vuepress/dist/assets/img/node-ps-receive-price.76eaa418.png +0 -0
  20. package/docs/.vuepress/dist/assets/img/node-ps-strategy-best-save.392292d5.png +0 -0
  21. package/docs/.vuepress/dist/assets/img/node-ps-strategy-lowest-price.3a4ad347.png +0 -0
  22. package/docs/.vuepress/dist/assets/img/power-saver-nordpool-current-state.bf14afde.png +0 -0
  23. package/docs/.vuepress/dist/assets/img/power-saver-nordpool-events-state.8c392507.png +0 -0
  24. package/docs/.vuepress/dist/assets/img/power-saver-tibber-mqtt.16891dd2.png +0 -0
  25. package/docs/.vuepress/dist/assets/js/293.5e967839.js +1 -0
  26. package/docs/.vuepress/dist/assets/js/491.c183eba3.js +1 -0
  27. package/docs/.vuepress/dist/assets/js/812.79dad458.js +2 -0
  28. package/docs/.vuepress/dist/assets/js/812.79dad458.js.LICENSE.txt +8 -0
  29. package/docs/.vuepress/dist/assets/js/app.4ee3384b.js +1 -0
  30. package/docs/.vuepress/dist/assets/js/runtime~app.cafd6537.js +1 -0
  31. package/docs/.vuepress/dist/assets/js/v-08683c60.07fe8291.js +1 -0
  32. package/docs/.vuepress/dist/assets/js/v-0aca7ba6.aec5ba75.js +1 -0
  33. package/docs/.vuepress/dist/assets/js/v-0b5e3c8c.d008d8bc.js +1 -0
  34. package/docs/.vuepress/dist/assets/js/v-1ad821fa.85407071.js +1 -0
  35. package/docs/.vuepress/dist/assets/js/v-30acb564.73b8e29f.js +1 -0
  36. package/docs/.vuepress/dist/assets/js/v-3706649a.d7f73384.js +1 -0
  37. package/docs/.vuepress/dist/assets/js/v-4637f9e4.22ab9413.js +1 -0
  38. package/docs/.vuepress/dist/assets/js/v-510ed0d4.204a09ec.js +1 -0
  39. package/docs/.vuepress/dist/assets/js/v-5954bcb2.be07962c.js +1 -0
  40. package/docs/.vuepress/dist/assets/js/v-5db8da3a.ac192f35.js +1 -0
  41. package/docs/.vuepress/dist/assets/js/v-61f728ca.802ab15e.js +1 -0
  42. package/docs/.vuepress/dist/assets/js/v-677dfaed.9bbbd037.js +1 -0
  43. package/docs/.vuepress/dist/assets/js/v-7c87f26e.457a1a60.js +1 -0
  44. package/docs/.vuepress/dist/assets/js/v-8daa1a0e.db8b59c6.js +1 -0
  45. package/docs/.vuepress/dist/assets/js/v-b4a42144.6e0c5aa0.js +1 -0
  46. package/docs/.vuepress/dist/assets/js/v-e8c55052.5f85b6cd.js +1 -0
  47. package/docs/.vuepress/dist/assets/js/v-fffb8e28.e815e852.js +1 -0
  48. package/docs/.vuepress/dist/changelog/index.html +15 -0
  49. package/docs/.vuepress/dist/contribute/index.html +15 -0
  50. package/docs/.vuepress/dist/euro.png +0 -0
  51. package/docs/.vuepress/dist/examples/example-nordpool-current-state.html +169 -0
  52. package/docs/.vuepress/dist/examples/example-nordpool-events-state.html +173 -0
  53. package/docs/.vuepress/dist/examples/example-tibber-mqtt.html +182 -0
  54. package/docs/.vuepress/dist/examples/index.html +15 -0
  55. package/docs/.vuepress/dist/guide/index.html +52 -0
  56. package/docs/.vuepress/dist/index.html +15 -0
  57. package/docs/.vuepress/dist/logo.png +0 -0
  58. package/docs/.vuepress/dist/nodes/index.html +15 -0
  59. package/docs/.vuepress/dist/nodes/old-power-saver-doc.html +97 -0
  60. package/docs/.vuepress/dist/nodes/power-saver.html +15 -0
  61. package/docs/.vuepress/dist/nodes/ps-elvia-add-tariff.html +15 -0
  62. package/docs/.vuepress/dist/nodes/ps-receive-price.html +80 -0
  63. package/docs/.vuepress/dist/nodes/ps-strategy-best-save.html +65 -0
  64. package/docs/.vuepress/dist/nodes/ps-strategy-lowest-price.html +89 -0
  65. package/docs/.vuepress/dist/nodes/strategy-input.html +40 -0
  66. package/docs/.vuepress/public/euro.png +0 -0
  67. package/docs/.vuepress/public/logo.png +0 -0
  68. package/docs/README.md +32 -0
  69. package/docs/changelog/README.md +71 -0
  70. package/docs/contribute/README.md +39 -0
  71. package/docs/examples/README.md +5 -0
  72. package/docs/examples/example-nordpool-current-state.md +166 -0
  73. package/docs/examples/example-nordpool-events-state.md +170 -0
  74. package/docs/examples/example-tibber-mqtt.md +179 -0
  75. package/docs/guide/README.md +202 -0
  76. package/docs/images/all-nodes.png +0 -0
  77. package/docs/images/best-save-config.png +0 -0
  78. package/docs/images/elvia-add-tariff-node-used.png +0 -0
  79. package/docs/images/elvia-config-no-config.png +0 -0
  80. package/docs/images/elvia-config-no-tariff.png +0 -0
  81. package/docs/images/elvia-config-select-tariff.png +0 -0
  82. package/docs/images/elvia-config-subscription-key.png +0 -0
  83. package/docs/images/elvia-flow.png +0 -0
  84. package/docs/images/elvia-tariff-config.png +0 -0
  85. package/docs/images/euro.png +0 -0
  86. package/docs/images/example-flow-1.png +0 -0
  87. package/docs/images/example-flow-2.png +0 -0
  88. package/docs/images/logo.png +0 -0
  89. package/docs/images/lowest-price-config.png +0 -0
  90. package/docs/images/migrate-best-save.png +0 -0
  91. package/docs/images/migrate-power-saver.png +0 -0
  92. package/docs/images/node-power-saver.png +0 -0
  93. package/docs/images/node-ps-elvia-add-tariff.png +0 -0
  94. package/docs/images/node-ps-elvia-tariff-types.png +0 -0
  95. package/docs/images/node-ps-elvia-tariff.png +0 -0
  96. package/docs/images/node-ps-receive-price.png +0 -0
  97. package/docs/images/node-ps-strategy-best-save.png +0 -0
  98. package/docs/images/node-ps-strategy-lowest-price.png +0 -0
  99. package/{doc → docs/images}/node-red-contrib-power-saver-flow.png +0 -0
  100. package/docs/images/power-saver-nordpool-current-state.png +0 -0
  101. package/docs/images/power-saver-nordpool-events-state.png +0 -0
  102. package/docs/images/power-saver-tibber-mqtt.png +0 -0
  103. package/docs/nodes/README.md +53 -0
  104. package/docs/nodes/old-power-saver-doc.md +231 -0
  105. package/docs/nodes/power-saver.md +23 -0
  106. package/docs/nodes/ps-elvia-add-tariff.md +52 -0
  107. package/docs/nodes/ps-receive-price.md +153 -0
  108. package/docs/nodes/ps-strategy-best-save.md +142 -0
  109. package/docs/nodes/ps-strategy-lowest-price.md +165 -0
  110. package/docs/nodes/strategy-input.md +39 -0
  111. package/package.json +16 -4
  112. package/src/elvia/elvia-add-tariff.html +70 -0
  113. package/src/elvia/elvia-add-tariff.js +47 -0
  114. package/src/elvia/elvia-api.js +61 -0
  115. package/src/elvia/elvia-config.html +46 -0
  116. package/src/elvia/elvia-config.js +19 -0
  117. package/src/elvia/elvia-tariff-types.html +34 -0
  118. package/src/elvia/elvia-tariff-types.js +25 -0
  119. package/src/elvia/elvia-tariff.html +89 -0
  120. package/src/elvia/elvia-tariff.js +22 -0
  121. package/src/elvia/icons/elvia_hvite.svg +4 -0
  122. package/src/elvia/icons/elvia_positive_4 copy.svg +4 -0
  123. package/src/handle-input.js +162 -0
  124. package/src/power-saver.html +116 -0
  125. package/{power-saver.js → src/power-saver.js} +9 -32
  126. package/src/receive-price-functions.js +99 -0
  127. package/src/receive-price.html +30 -0
  128. package/src/receive-price.js +21 -0
  129. package/src/strategy-best-save-functions.js +110 -0
  130. package/src/strategy-best-save.html +116 -0
  131. package/src/strategy-best-save.js +95 -0
  132. package/src/strategy-lowest-price-functions.js +35 -0
  133. package/src/strategy-lowest-price.html +168 -0
  134. package/src/strategy-lowest-price.js +125 -0
  135. package/{utils.js → src/utils.js} +44 -100
  136. package/test/data/adjustedResult.js +219 -71
  137. package/test/data/adjustedResult_old.js +154 -0
  138. package/test/data/best-save-result.json +357 -0
  139. package/test/data/converted-prices.json +197 -0
  140. package/test/data/elvia-input-grid-tariff.json +760 -0
  141. package/test/data/elvia-input-power-prices.json +194 -0
  142. package/test/data/elvia-output-add-tariff.json +290 -0
  143. package/test/data/lowest-price-result-cont.json +18 -0
  144. package/test/data/lowest-price-result-split-allday.json +21 -0
  145. package/test/data/lowest-price-result-split-allday10.json +20 -0
  146. package/test/data/lowest-price-result-split.json +20 -0
  147. package/test/data/nordpool-current-state-prices.json +283 -0
  148. package/test/data/nordpool-event-prices.json +574 -0
  149. package/test/data/reconfigResult.js +220 -67
  150. package/test/data/reconfigResult_old.js +141 -0
  151. package/test/data/tibber-data-end-0-24h.json +197 -0
  152. package/test/data/tibber-data-end-0.json +101 -0
  153. package/test/data/tibber-prices-single-home.json +64 -0
  154. package/test/data/tibber-prices.json +124 -0
  155. package/test/data/tibber-result-end-0-24h.json +320 -0
  156. package/test/data/tibber-result-end-0.json +168 -0
  157. package/test/data/{tibber_result.json → tibber-result.json} +0 -0
  158. package/test/elvia.test.js +26 -0
  159. package/test/mostSavedStrategy.test.js +22 -55
  160. package/test/power-saver.test.js +21 -5
  161. package/test/receive-price-functions.test.js +153 -0
  162. package/test/receive-price.test.js +122 -0
  163. package/test/send-config-input.test.js +8 -10
  164. package/test/strategy-best-save-test-utils.js +32 -0
  165. package/test/strategy-best-save.test.js +103 -0
  166. package/test/strategy-lowest-price-functions.test.js +40 -0
  167. package/test/strategy-lowest-price.test.js +538 -0
  168. package/test/test-utils.js +0 -18
  169. package/test/utils.test.js +22 -180
  170. package/doc/example-nordpool-current-state.md +0 -166
  171. package/doc/example-nordpool-events-state.md +0 -153
  172. package/doc/example-tibber-mqtt.md +0 -189
  173. package/doc/power-saver-nordpool-current-state.png +0 -0
  174. package/doc/power-saver-nordpool-events-state.png +0 -0
  175. package/doc/power-saver-tibber-mqtt.png +0 -0
  176. package/mostSavedStrategy.js +0 -84
  177. package/power-saver.html +0 -308
  178. package/test/data/tibber_data.json +0 -412
  179. package/test/data/tibber_prices.json +0 -412
@@ -0,0 +1,538 @@
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 lowestPrice = require("../src/strategy-lowest-price.js");
6
+ const prices = require("./data/converted-prices.json");
7
+ const { testPlan: plan } = require("./test-utils");
8
+
9
+ helper.init(require.resolve("node-red"));
10
+
11
+ describe("ps-strategy-lowest-price node", function () {
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 be loaded", function (done) {
23
+ const flow = [{ id: "n1", type: "ps-strategy-lowest-price", name: "test name" }];
24
+ helper.load(lowestPrice, flow, function () {
25
+ const n1 = helper.getNode("n1");
26
+ expect(n1).toHaveProperty("name", "test name");
27
+ done();
28
+ });
29
+ });
30
+
31
+ it("should log error when illegal data is received", function (done) {
32
+ const flow = [{ id: "n1", type: "ps-strategy-lowest-price", name: "test name" }];
33
+ helper.load(lowestPrice, flow, function () {
34
+ const n1 = helper.getNode("n1");
35
+ n1.receive({});
36
+ n1.warn.should.be.calledWithExactly("No payload");
37
+ n1.receive({ payload: "Error" });
38
+ n1.warn.should.be.calledWithExactly("Payload is not an object");
39
+ n1.receive({ payload: [] });
40
+ n1.warn.should.be.calledWithExactly("Payload is missing priceData");
41
+ n1.receive({ payload: { priceData: [] } });
42
+ n1.warn.should.be.calledWithExactly("priceData is empty");
43
+ n1.receive({ payload: { priceData: { today: [], tomorrow: [] } } });
44
+ n1.warn.should.be.calledWithExactly("Illegal priceData in payload. Did you use the receive-price node?");
45
+
46
+ ["start", "value"].forEach((attr) => {
47
+ const testData1 = cloneDeep(prices);
48
+ delete testData1.priceData[3][attr];
49
+ n1.receive({ payload: testData1 });
50
+ n1.warn.should.be.calledWithExactly(
51
+ "Malformed entries in priceData. All entries must contain start and value."
52
+ );
53
+ });
54
+
55
+ n1.receive({ payload: cloneDeep(prices) });
56
+ n1.warn.should.not.be.called;
57
+ done();
58
+ });
59
+ });
60
+ it("should plan correct continuous schedule", function (done) {
61
+ const resultContinuous = require("./data/lowest-price-result-cont.json");
62
+ const flow = makeFlow(4);
63
+ helper.load(lowestPrice, flow, function () {
64
+ const n1 = helper.getNode("n1");
65
+ const n2 = helper.getNode("n2");
66
+ n2.on("input", function (msg) {
67
+ expect(msg.payload).toHaveProperty("schedule", resultContinuous.schedule);
68
+ expect(msg.payload).toHaveProperty("config", resultContinuous.config);
69
+ n1.warn.should.not.be.called;
70
+ done();
71
+ });
72
+ const time = DateTime.fromISO(prices.priceData[10].start);
73
+ n1.receive({ payload: makePayload(prices, time) });
74
+ });
75
+ });
76
+ it("should plan correct splitted schedule", function (done) {
77
+ const resultSplitted = require("./data/lowest-price-result-split.json");
78
+ const flow = makeFlow(6);
79
+ flow[0].doNotSplit = false;
80
+ helper.load(lowestPrice, flow, function () {
81
+ const n1 = helper.getNode("n1");
82
+ const n2 = helper.getNode("n2");
83
+ n2.on("input", function (msg) {
84
+ expect(msg.payload).toHaveProperty("schedule", resultSplitted.schedule);
85
+ expect(msg.payload).toHaveProperty("config", resultSplitted.config);
86
+ n1.warn.should.not.be.called;
87
+ done();
88
+ });
89
+ const time = DateTime.fromISO(prices.priceData[10].start);
90
+ n1.receive({ payload: makePayload(prices, time) });
91
+ });
92
+ });
93
+ it("should plan correct for all day period - 00-00", function (done) {
94
+ const resultAllDay = require("./data/lowest-price-result-split-allday.json");
95
+ const flow = makeFlow(8);
96
+ flow[0].doNotSplit = false;
97
+ flow[0].fromTime = "00";
98
+ flow[0].toTime = "00";
99
+ helper.load(lowestPrice, flow, function () {
100
+ const n1 = helper.getNode("n1");
101
+ const n2 = helper.getNode("n2");
102
+ n2.on("input", function (msg) {
103
+ expect(msg.payload).toHaveProperty("schedule", resultAllDay.schedule);
104
+ expect(msg.payload).toHaveProperty("config", resultAllDay.config);
105
+ n1.warn.should.not.be.called;
106
+ done();
107
+ });
108
+ const time = DateTime.fromISO(prices.priceData[10].start);
109
+ n1.receive({ payload: makePayload(prices, time) });
110
+ });
111
+ });
112
+ it("should plan correct for all day period - 10-10", function (done) {
113
+ const resultAllDay10 = require("./data/lowest-price-result-split-allday10.json");
114
+ const flow = makeFlow(10);
115
+ flow[0].doNotSplit = false;
116
+ flow[0].fromTime = "10";
117
+ flow[0].toTime = "10";
118
+ helper.load(lowestPrice, flow, function () {
119
+ const n1 = helper.getNode("n1");
120
+ const n2 = helper.getNode("n2");
121
+ n2.on("input", function (msg) {
122
+ const config = cloneDeep(resultAllDay10.config);
123
+ expect(msg.payload).toHaveProperty("schedule", resultAllDay10.schedule);
124
+ expect(msg.payload).toHaveProperty("config", config);
125
+ n1.warn.should.not.be.called;
126
+ done();
127
+ });
128
+ const time = DateTime.fromISO(prices.priceData[10].start);
129
+ n1.receive({ payload: makePayload(prices, time) });
130
+ });
131
+ });
132
+ it("should plan correct for all day period - 10-10 outside on", function (done) {
133
+ const resultAllDay10 = require("./data/lowest-price-result-split-allday10.json");
134
+ const flow = makeFlow(10);
135
+ flow[0].doNotSplit = false;
136
+ flow[0].fromTime = "10";
137
+ flow[0].toTime = "10";
138
+ flow[0].outputOutsidePeriod = true;
139
+ helper.load(lowestPrice, flow, function () {
140
+ const n1 = helper.getNode("n1");
141
+ const n2 = helper.getNode("n2");
142
+ n2.on("input", function (msg) {
143
+ const config = cloneDeep(resultAllDay10.config);
144
+ config.outputOutsidePeriod = true;
145
+ expect(msg.payload).toHaveProperty("schedule", resultAllDay10.schedule);
146
+ expect(msg.payload).toHaveProperty("config", config);
147
+ n1.warn.should.not.be.called;
148
+ done();
149
+ });
150
+ const time = DateTime.fromISO(prices.priceData[10].start);
151
+ n1.receive({ payload: makePayload(prices, time) });
152
+ });
153
+ });
154
+ it("should plan correct for all day period - 10-10 nosched on", function (done) {
155
+ const resultAllDay10 = require("./data/lowest-price-result-split-allday10.json");
156
+ const flow = makeFlow(10);
157
+ flow[0].doNotSplit = false;
158
+ flow[0].fromTime = "10";
159
+ flow[0].toTime = "10";
160
+ flow[0].outputIfNoSchedule = "true";
161
+ helper.load(lowestPrice, flow, function () {
162
+ const n1 = helper.getNode("n1");
163
+ const n2 = helper.getNode("n2");
164
+ n2.on("input", function (msg) {
165
+ const config = cloneDeep(resultAllDay10.config);
166
+ config.outputIfNoSchedule = true;
167
+ expect(msg.payload).toHaveProperty("schedule", resultAllDay10.schedule);
168
+ expect(msg.payload).toHaveProperty("config", config);
169
+ n1.warn.should.not.be.called;
170
+ done();
171
+ });
172
+ const time = DateTime.fromISO(prices.priceData[10].start);
173
+ n1.receive({ payload: makePayload(prices, time) });
174
+ });
175
+ });
176
+ it("should plan correct outside selected period - split", function (done) {
177
+ const resultSplitted = require("./data/lowest-price-result-split.json");
178
+ const flow = makeFlow(6);
179
+ flow[0].doNotSplit = false;
180
+ flow[0].outputOutsidePeriod = true;
181
+ helper.load(lowestPrice, flow, function () {
182
+ const n1 = helper.getNode("n1");
183
+ const n2 = helper.getNode("n2");
184
+ n2.on("input", function (msg) {
185
+ const schedule = cloneDeep(resultSplitted.schedule);
186
+ const config = cloneDeep(resultSplitted.config);
187
+ schedule[0].value = true;
188
+ schedule.splice(1, 0, { time: "2021-10-11T10:00:00.000+02:00", value: false });
189
+ schedule.splice(4, 0, { time: "2021-10-11T20:00:00.000+02:00", value: true });
190
+ schedule.splice(5, 0, { time: "2021-10-12T10:00:00.000+02:00", value: false });
191
+ schedule.splice(schedule.length - 1, 1);
192
+ config.outputOutsidePeriod = true;
193
+ expect(msg.payload).toHaveProperty("schedule", schedule);
194
+ expect(msg.payload).toHaveProperty("config", config);
195
+ n1.warn.should.not.be.called;
196
+ done();
197
+ });
198
+ const time = DateTime.fromISO(prices.priceData[10].start);
199
+ n1.receive({ payload: makePayload(prices, time) });
200
+ });
201
+ });
202
+ it("should plan correct outside selected period - cont", function (done) {
203
+ const resultContinuous = require("./data/lowest-price-result-cont.json");
204
+ const flow = makeFlow(4);
205
+ flow[0].doNotSplit = true;
206
+ flow[0].outputOutsidePeriod = true;
207
+ helper.load(lowestPrice, flow, function () {
208
+ const n1 = helper.getNode("n1");
209
+ const n2 = helper.getNode("n2");
210
+ n2.on("input", function (msg) {
211
+ const schedule = cloneDeep(resultContinuous.schedule);
212
+ const config = cloneDeep(resultContinuous.config);
213
+ schedule[0].value = true;
214
+ schedule.splice(1, 0, { time: "2021-10-11T10:00:00.000+02:00", value: false });
215
+ schedule.splice(4, 0, { time: "2021-10-11T20:00:00.000+02:00", value: true });
216
+ schedule.splice(5, 0, { time: "2021-10-12T10:00:00.000+02:00", value: false });
217
+ schedule.splice(schedule.length, 0, { time: "2021-10-12T20:00:00.000+02:00", value: true });
218
+ // schedule.splice(schedule.length - 1, 1);
219
+ config.outputOutsidePeriod = true;
220
+ expect(msg.payload).toHaveProperty("schedule", schedule);
221
+ expect(msg.payload).toHaveProperty("config", config);
222
+ n1.warn.should.not.be.called;
223
+ done();
224
+ });
225
+ const time = DateTime.fromISO(prices.priceData[10].start);
226
+ n1.receive({ payload: makePayload(prices, time) });
227
+ });
228
+ });
229
+ it("should work with 0 hours on", function (done) {
230
+ const result = [{ time: "2021-10-11T00:00:00.000+02:00", value: false }];
231
+ const flow = makeFlow(0);
232
+ helper.load(lowestPrice, flow, function () {
233
+ const n1 = helper.getNode("n1");
234
+ const n2 = helper.getNode("n2");
235
+ n2.on("input", function (msg) {
236
+ expect(msg.payload).toHaveProperty("schedule", result);
237
+ n1.warn.should.not.be.called;
238
+ done();
239
+ });
240
+ const time = DateTime.fromISO(prices.priceData[10].start);
241
+ n1.receive({ payload: makePayload(prices, time) });
242
+ });
243
+ });
244
+ it("should work with 0 hours on outside on", function (done) {
245
+ const result = [
246
+ { time: "2021-10-11T00:00:00.000+02:00", value: true },
247
+ { time: "2021-10-11T10:00:00.000+02:00", value: false },
248
+ { time: "2021-10-11T20:00:00.000+02:00", value: true },
249
+ { time: "2021-10-12T10:00:00.000+02:00", value: false },
250
+ { time: "2021-10-12T20:00:00.000+02:00", value: true },
251
+ ];
252
+ const flow = makeFlow(0);
253
+ flow[0].outputOutsidePeriod = true;
254
+ helper.load(lowestPrice, flow, function () {
255
+ const n1 = helper.getNode("n1");
256
+ const n2 = helper.getNode("n2");
257
+ n2.on("input", function (msg) {
258
+ expect(msg.payload).toHaveProperty("schedule", result);
259
+ n1.warn.should.not.be.called;
260
+ done();
261
+ });
262
+ const time = DateTime.fromISO(prices.priceData[10].start);
263
+ n1.receive({ payload: makePayload(prices, time) });
264
+ });
265
+ });
266
+ it("should work with 1 hours on", function (done) {
267
+ const result = [
268
+ { time: "2021-10-11T00:00:00.000+02:00", value: false },
269
+ { time: "2021-10-11T12:00:00.000+02:00", value: true },
270
+ { time: "2021-10-11T13:00:00.000+02:00", value: false },
271
+ { time: "2021-10-12T14:00:00.000+02:00", value: true },
272
+ { time: "2021-10-12T15:00:00.000+02:00", value: false },
273
+ ];
274
+ const flow = makeFlow(1);
275
+ helper.load(lowestPrice, flow, function () {
276
+ const n1 = helper.getNode("n1");
277
+ const n2 = helper.getNode("n2");
278
+ n2.on("input", function (msg) {
279
+ expect(msg.payload).toHaveProperty("schedule", result);
280
+ n1.warn.should.not.be.called;
281
+ done();
282
+ });
283
+ const time = DateTime.fromISO(prices.priceData[10].start);
284
+ n1.receive({ payload: makePayload(prices, time) });
285
+ });
286
+ });
287
+ it("should work with 24 hours on", function (done) {
288
+ const result = [
289
+ { time: "2021-10-11T00:00:00.000+02:00", value: false },
290
+ { time: "2021-10-11T10:00:00.000+02:00", value: true },
291
+ { time: "2021-10-11T20:00:00.000+02:00", value: false },
292
+ { time: "2021-10-12T10:00:00.000+02:00", value: true },
293
+ { time: "2021-10-12T20:00:00.000+02:00", value: false },
294
+ ];
295
+ const flow = makeFlow(24);
296
+ helper.load(lowestPrice, flow, function () {
297
+ const n1 = helper.getNode("n1");
298
+ const n2 = helper.getNode("n2");
299
+ n2.on("input", function (msg) {
300
+ expect(msg.payload).toHaveProperty("schedule", result);
301
+ n1.warn.should.not.be.called;
302
+ done();
303
+ });
304
+ const time = DateTime.fromISO(prices.priceData[10].start);
305
+ n1.receive({ payload: makePayload(prices, time) });
306
+ });
307
+ });
308
+
309
+ it("should work with data for only current day", function (done) {
310
+ const oneDayPrices = {};
311
+ oneDayPrices.priceData = prices.priceData.filter((d) => d.start.startsWith("2021-10-11"));
312
+ const result = [
313
+ { time: "2021-10-11T00:00:00.000+02:00", value: false },
314
+ { time: "2021-10-11T12:00:00.000+02:00", value: true },
315
+ { time: "2021-10-11T13:00:00.000+02:00", value: false },
316
+ ];
317
+ const flow = makeFlow(1);
318
+ helper.load(lowestPrice, flow, function () {
319
+ const n1 = helper.getNode("n1");
320
+ const n2 = helper.getNode("n2");
321
+ const n3 = helper.getNode("n3");
322
+ const n4 = helper.getNode("n4");
323
+ n2.on("input", function (msg) {
324
+ expect(msg.payload).toHaveProperty("schedule", result);
325
+ n1.warn.should.not.be.called;
326
+ done();
327
+ });
328
+ n3.on("input", function (msg) {
329
+ console.log("n3 inout: " + msg);
330
+ expect(msg.payload).toEqual(true);
331
+ });
332
+ n4.on("input", function (msg) {
333
+ console.log("n4 inout: " + msg);
334
+ expect(msg.payload).toEqual(false);
335
+ });
336
+ const time = DateTime.fromISO(prices.priceData[10].start);
337
+ n1.receive({ payload: makePayload(oneDayPrices, time) });
338
+ });
339
+ });
340
+
341
+ it("should handle hours on > period", function (done) {
342
+ const result = [{ time: "2021-10-11T00:00:00.000+02:00", value: true }];
343
+ const flow = [
344
+ {
345
+ id: "n1",
346
+ type: "ps-strategy-lowest-price",
347
+ name: "test name",
348
+ fromTime: "17",
349
+ toTime: "22",
350
+ hoursOn: 6,
351
+ doNotSplit: true,
352
+ sendCurrentValueWhenRescheduling: true,
353
+ outputIfNoSchedule: false,
354
+ outputOutsidePeriod: true,
355
+ wires: [["n3"], ["n4"], ["n2"]],
356
+ },
357
+ { id: "n2", type: "helper" },
358
+ { id: "n3", type: "helper" },
359
+ { id: "n4", type: "helper" },
360
+ ];
361
+ helper.load(lowestPrice, flow, function () {
362
+ const n1 = helper.getNode("n1");
363
+ const n2 = helper.getNode("n2");
364
+ const n3 = helper.getNode("n3");
365
+ const n4 = helper.getNode("n4");
366
+ let countOn = 0;
367
+ let countOff = 0;
368
+ n2.on("input", function (msg) {
369
+ expect(msg.payload).toHaveProperty("schedule", result);
370
+ n1.warn.should.not.be.called;
371
+ setTimeout(() => {
372
+ expect(countOn).toEqual(1);
373
+ expect(countOff).toEqual(0);
374
+ done();
375
+ }, 100);
376
+ });
377
+ n3.on("input", function (msg) {
378
+ countOn++;
379
+ });
380
+ n4.on("input", function (msg) {
381
+ countOff++;
382
+ });
383
+
384
+ const time = DateTime.fromISO(prices.priceData[10].start);
385
+ n1.receive({ payload: makePayload(prices, time) });
386
+ });
387
+ });
388
+ it("should handle hours on > period, false outside", function (done) {
389
+ const result = [
390
+ { time: "2021-10-11T00:00:00.000+02:00", value: false },
391
+ { time: "2021-10-11T17:00:00.000+02:00", value: true },
392
+ { time: "2021-10-11T22:00:00.000+02:00", value: false },
393
+ { time: "2021-10-12T17:00:00.000+02:00", value: true },
394
+ { time: "2021-10-12T22:00:00.000+02:00", value: false },
395
+ ];
396
+ const flow = [
397
+ {
398
+ id: "n1",
399
+ type: "ps-strategy-lowest-price",
400
+ name: "test name",
401
+ fromTime: "17",
402
+ toTime: "22",
403
+ hoursOn: 6,
404
+ doNotSplit: true,
405
+ sendCurrentValueWhenRescheduling: true,
406
+ outputIfNoSchedule: true,
407
+ outputOutsidePeriod: false,
408
+ wires: [["n3"], ["n4"], ["n2"]],
409
+ },
410
+ { id: "n2", type: "helper" },
411
+ { id: "n3", type: "helper" },
412
+ { id: "n4", type: "helper" },
413
+ ];
414
+ helper.load(lowestPrice, flow, function () {
415
+ const n1 = helper.getNode("n1");
416
+ const n2 = helper.getNode("n2");
417
+ const n3 = helper.getNode("n3");
418
+ const n4 = helper.getNode("n4");
419
+ let countOn = 0;
420
+ let countOff = 0;
421
+ n2.on("input", function (msg) {
422
+ expect(msg.payload).toHaveProperty("schedule", result);
423
+ n1.warn.should.not.be.called;
424
+ setTimeout(() => {
425
+ expect(countOn).toEqual(0);
426
+ expect(countOff).toEqual(1);
427
+ done();
428
+ }, 100);
429
+ });
430
+ n3.on("input", function (msg) {
431
+ countOn++;
432
+ });
433
+ n4.on("input", function (msg) {
434
+ countOff++;
435
+ });
436
+
437
+ const time = DateTime.fromISO(prices.priceData[10].start);
438
+ n1.receive({ payload: makePayload(prices, time) });
439
+ });
440
+ });
441
+ it("handles period end on hour 0 - 12 hours", function (done) {
442
+ const input = require("./data/tibber-data-end-0.json");
443
+ const result = require("./data/tibber-result-end-0.json");
444
+ const flow = [
445
+ {
446
+ id: "n1",
447
+ type: "ps-strategy-lowest-price",
448
+ name: "test name",
449
+ fromTime: "16",
450
+ toTime: "00",
451
+ hoursOn: 3,
452
+ doNotSplit: false,
453
+ sendCurrentValueWhenRescheduling: true,
454
+ outputIfNoSchedule: false,
455
+ outputOutsidePeriod: false,
456
+ wires: [["n3"], ["n4"], ["n2"]],
457
+ },
458
+ { id: "n2", type: "helper" },
459
+ { id: "n3", type: "helper" },
460
+ { id: "n4", type: "helper" },
461
+ ];
462
+ helper.load(lowestPrice, flow, function () {
463
+ const n1 = helper.getNode("n1");
464
+ const n2 = helper.getNode("n2");
465
+ n2.on("input", function (msg) {
466
+ expect(msg).toHaveProperty("payload", result);
467
+ n1.warn.should.not.be.called;
468
+ done();
469
+ });
470
+ const time = DateTime.fromISO(prices.priceData[10].start);
471
+ n1.receive({ payload: makePayload(input, time) });
472
+ });
473
+ });
474
+ it("handles period end on hour 0 - 24 hours", function (done) {
475
+ const input = require("./data/tibber-data-end-0-24h.json");
476
+ const result = require("./data/tibber-result-end-0-24h.json");
477
+ const flow = [
478
+ {
479
+ id: "n1",
480
+ type: "ps-strategy-lowest-price",
481
+ name: "test name",
482
+ fromTime: "16",
483
+ toTime: "00",
484
+ hoursOn: 3,
485
+ doNotSplit: false,
486
+ sendCurrentValueWhenRescheduling: true,
487
+ outputIfNoSchedule: false,
488
+ outputOutsidePeriod: false,
489
+ wires: [["n3"], ["n4"], ["n2"]],
490
+ },
491
+ { id: "n2", type: "helper" },
492
+ { id: "n3", type: "helper" },
493
+ { id: "n4", type: "helper" },
494
+ ];
495
+ helper.load(lowestPrice, flow, function () {
496
+ const n1 = helper.getNode("n1");
497
+ const n2 = helper.getNode("n2");
498
+ n2.on("input", function (msg) {
499
+ expect(msg).toHaveProperty("payload", result);
500
+ n1.warn.should.not.be.called;
501
+ done();
502
+ });
503
+ const time = DateTime.fromISO(prices.priceData[10].start);
504
+ n1.receive({ payload: makePayload(input, time) });
505
+ });
506
+ });
507
+ });
508
+
509
+ function makeFlow(hoursOn) {
510
+ return [
511
+ {
512
+ id: "n1",
513
+ type: "ps-strategy-lowest-price",
514
+ name: "test name",
515
+ fromTime: "10",
516
+ toTime: "20",
517
+ hoursOn: hoursOn,
518
+ doNotSplit: true,
519
+ sendCurrentValueWhenRescheduling: true,
520
+ outputIfNoSchedule: true,
521
+ wires: [["n3"], ["n4"], ["n2"]],
522
+ },
523
+ { id: "n2", type: "helper" },
524
+ { id: "n3", type: "helper" },
525
+ { id: "n4", type: "helper" },
526
+ ];
527
+ }
528
+
529
+ function makePayload(prices, time) {
530
+ const payload = cloneDeep(prices);
531
+ payload.time = time;
532
+ // let entryTime = DateTime.fromISO(payload.time);
533
+ // payload.priceData.forEach((e) => {
534
+ // e.start = entryTime.toISO();
535
+ // entryTime = entryTime.plus({ milliseconds: 10 });
536
+ // });
537
+ return payload;
538
+ }
@@ -18,23 +18,6 @@ const testPlan = {
18
18
  time: "2021-06-20T01:50:00+02:00",
19
19
  };
20
20
 
21
- function makeFlow(maxHoursToSaveInSequence, minHoursOnAfterMaxSequenceSaved) {
22
- return [
23
- {
24
- id: "n1",
25
- type: "power-saver",
26
- name: "test name",
27
- maxHoursToSaveInSequence,
28
- minHoursOnAfterMaxSequenceSaved,
29
- minSaving: 0.001,
30
- wires: [["n3"], ["n4"], ["n2"]],
31
- },
32
- { id: "n2", type: "helper" },
33
- { id: "n3", type: "helper" },
34
- { id: "n4", type: "helper" },
35
- ];
36
- }
37
-
38
21
  function makePayload(prices, time) {
39
22
  const payload = cloneDeep(prices);
40
23
  payload.time = time;
@@ -118,7 +101,6 @@ function equalPlan(expected, actual) {
118
101
 
119
102
  module.exports = {
120
103
  testPlan,
121
- makeFlow,
122
104
  makePayload,
123
105
  equalPlan,
124
106
  };