node-red-contrib-power-saver 3.5.7 → 3.6.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.
@@ -58,16 +58,7 @@ module.exports = {
58
58
  "/changelog/": [{ text: "Changelog", children: ["/changelog/README.md"] }],
59
59
  },
60
60
  },
61
- head: [
62
- ["link", { rel: "shortcut icon", type: "image/x-icon", href: "euro.png" }],
63
- ["script", { async: true, src: "https://www.googletagmanager.com/gtag/js?id=G-Z2QNNCDQZG" }],
64
- [
65
- "script",
66
- {
67
- src: "window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments); } gtag('js', new Date()); gtag('config', 'G-Z2QNNCDQZG');",
68
- },
69
- ],
70
- ],
61
+ head: [["link", { rel: "shortcut icon", type: "image/x-icon", href: "euro.png" }]],
71
62
  plugins: [
72
63
  [
73
64
  "@vuepress/register-components",
@@ -76,5 +67,6 @@ module.exports = {
76
67
  },
77
68
  ],
78
69
  ["@vuepress/plugin-search"],
70
+ ["@vuepress/google-analytics", { ga: "G-Z2QNNCDQZG" }],
79
71
  ],
80
72
  };
@@ -6,6 +6,11 @@ sidebar: "auto"
6
6
 
7
7
  List the most significant changes, starting in version 1.0.9.
8
8
 
9
+ ## 3.6.0
10
+
11
+ - New feature `Max price` for Lowest Price node. Can be set to only turn on if prices is below or equal to the max price.
12
+ - New value in output 3 from the Lowest Price and Best Save nodes, `countHours`, telling the number of hours that the value will stay.
13
+
9
14
  ## 3.5.7
10
15
 
11
16
  - Add day-filter to general-add-tariff node so it can add one tariff for some days, and another tariff for other days.
Binary file
@@ -168,11 +168,13 @@ Example of output:
168
168
  "schedule": [
169
169
  {
170
170
  "time": "2021-09-30T00:00:00.000+02:00",
171
- "value": false
171
+ "value": false,
172
+ "countHours": 1
172
173
  },
173
174
  {
174
175
  "time": "2021-09-30T01:00:00.000+02:00",
175
- "value": true
176
+ "value": true,
177
+ "countHours": 2
176
178
  }
177
179
  ],
178
180
  "hours": [
@@ -18,13 +18,14 @@ The node can work on a specific period from 1 to 24 hours during a 24 hour perio
18
18
 
19
19
  | Value | Description |
20
20
  | ---------------------- | -------------------------------------------------------------------------------- |
21
- | From Time | The start time of the selected period. |
22
- | To Time | The end time of the selected period. |
23
- | Hours On | The number of hours that shall be turned on. |
24
- | Consecutive On-Period | Check this if you need the on-period to be consecutive. |
25
- | Send When Rescheduling | Check this to make sure on or off output is sent immediately after rescheduling. |
26
- | If No Schedule, Send | What to do if there is no valid schedule any more (turn on or off). |
27
- | Outside Period, Send | Select the value to send outside the selected period. |
21
+ | From time | The start time of the selected period. |
22
+ | To time | The end time of the selected period. |
23
+ | Hours on | The number of hours that shall be turned on. |
24
+ | Max price | If set, does not turn on if price is over this limit. See below. |
25
+ | Consecutive on-period | Check this if you need the on-period to be consecutive. |
26
+ | Send when rescheduling | Check this to make sure on or off output is sent immediately after rescheduling. |
27
+ | If no schedule, send | What to do if there is no valid schedule any more (turn on or off). |
28
+ | Outside period, send | Select the value to send outside the selected period. |
28
29
  | Context storage | Select context storage to save data to, if you want other than the default. |
29
30
 
30
31
  If you want to use a period of 24 hours, set the From Time and To Time to the same value. The time you select is significant in the way that it decides which 24 hours that are considered when finding the hours with lowest price.
@@ -49,6 +50,18 @@ Unless the period you select is 24 hours (`From Time` and `To Time` are the same
49
50
  If you select a period for example from 10:00 to 02:00, it may not be possible to calculate before the period starts. This is because electricity prices for the next day (in the Nord Pool area) normally are received around 13:00. The node cannot calculate the period until it has price data for the whole period.
50
51
  :::
51
52
 
53
+ ::: warning Max price
54
+ Use this to set a maximum price for hours to be on.
55
+ Leave this blank if you don't understand how it works.!
56
+ If this is set, the number of hours on may be less then configured for `Hours on`.
57
+
58
+ If `Consecutive on period` is off (not checked), hours will be turned on only if the price is below or equal to `Max price`.
59
+
60
+ If `Consecutive on period` is on (checked), hours will be turned on only if the average price for the whole period is below or equal to `Max price`. If the average price for the hours that are supposed to be turned on is higher then `Max price`, then **all hours** will be off. This is to make sure that if the switch (or whatever you have connected) is turned on, it is turned on the whole period.
61
+
62
+ If you leave `Max price` blank, it has no effect.
63
+ :::
64
+
52
65
  ### Dynamic config
53
66
 
54
67
  It is possible to change config dynamically by sending a config message to the node. The config messages has a payload with a config object like this example:
@@ -57,12 +70,13 @@ It is possible to change config dynamically by sending a config message to the n
57
70
  "payload": {
58
71
  "config": {
59
72
  "contextStorage": "file",
60
- "fromTime" : 10,
61
- "toTime" : 16,
62
- "hoursOn" : 3,
63
- "doNotSplit" : false,
64
- "sendCurrentValueWhenRescheduling" : true,
65
- "outputIfNoSchedule" : false,
73
+ "fromTime": 10,
74
+ "toTime": 16,
75
+ "hoursOn": 3,
76
+ "maxPrice": null,
77
+ "doNotSplit": false,
78
+ "sendCurrentValueWhenRescheduling": true,
79
+ "outputIfNoSchedule": false,
66
80
  "outputOutsidePeriod": false
67
81
  }
68
82
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-power-saver",
3
- "version": "3.5.7",
3
+ "version": "3.6.0",
4
4
  "description": "A module for Node-RED that you can use to turn on and off a switch based on power prices",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -46,6 +46,7 @@
46
46
  },
47
47
  "devDependencies": {
48
48
  "@vuepress/bundler-vite": "^2.0.0-beta.36",
49
+ "@vuepress/plugin-google-analytics": "^2.0.0-beta.36",
49
50
  "@vuepress/plugin-register-components": "^2.0.0-beta.36",
50
51
  "@vuepress/plugin-search": "^2.0.0-beta.38",
51
52
  "@vuepress/utils": "^2.0.0-beta.35",
@@ -43,6 +43,14 @@
43
43
  value: "12",
44
44
  required: true,
45
45
  },
46
+ maxPrice: {
47
+ value: null,
48
+ required: false,
49
+ validate: function (v) {
50
+ console.log("validating " + v);
51
+ return v == null || v == "" || (!isNaN(parseFloat(v)) && isFinite(v));
52
+ },
53
+ },
46
54
  doNotSplit: {
47
55
  value: "false",
48
56
  required: true,
@@ -145,6 +153,10 @@
145
153
  <label for="node-input-hoursOn"><i class="fa fa-arrows-h"></i> Hours on</label>
146
154
  <input type="text" id="node-input-hoursOn" style="width: 80px">
147
155
  </div>
156
+ <div class="form-row">
157
+ <label for="node-input-maxPrice"><i class="fa fa-minus"></i> Max price</label>
158
+ <input type="text" id="node-input-maxPrice" placeholder="Max price" style="width: 80px">
159
+ </div>
148
160
  <div class="form-row">
149
161
  <label for="node-input-doNotSplit">Consecutive on-period</label>
150
162
  <input type="checkbox" id="node-input-doNotSplit" style="display:inline-block; width:22px; vertical-align:top;">
@@ -13,6 +13,7 @@ module.exports = function (RED) {
13
13
  fromTime: config.fromTime,
14
14
  toTime: config.toTime,
15
15
  hoursOn: parseInt(config.hoursOn),
16
+ maxPrice: config.maxPrice == null || config.maxPrice == "" ? null : parseFloat(config.maxPrice),
16
17
  doNotSplit: booleanConfig(config.doNotSplit),
17
18
  sendCurrentValueWhenRescheduling: booleanConfig(config.sendCurrentValueWhenRescheduling),
18
19
  outputIfNoSchedule: booleanConfig(config.outputIfNoSchedule),
@@ -22,7 +23,6 @@ module.exports = function (RED) {
22
23
  node.context().set("config", originalConfig);
23
24
  node.contextStorage = originalConfig.contextStorage;
24
25
 
25
-
26
26
  node.on("close", function () {
27
27
  clearTimeout(node.schedulingTimeout);
28
28
  });
@@ -123,8 +123,17 @@ function makePlan(node, values, onOff, fromIndex, toIndex) {
123
123
  const res = node.doNotSplit
124
124
  ? getBestContinuous(valuesInPeriod, node.hoursOn)
125
125
  : getBestX(valuesInPeriod, node.hoursOn);
126
+ const sumPriceOn = res.reduce((p, v, i) => {
127
+ return p + (v ? valuesInPeriod[i] : 0);
128
+ }, 0);
129
+ const average = sumPriceOn / node.hoursOn;
126
130
  res.forEach((v, i) => {
127
- onOff[fromIndex + i] = v;
131
+ onOff[fromIndex + i] =
132
+ node.maxPrice == null
133
+ ? v
134
+ : node.doNotSplit
135
+ ? v && average <= node.maxPrice
136
+ : v && valuesInPeriod[i] <= node.maxPrice;
128
137
  });
129
138
  return onOff;
130
139
  }
package/src/utils.js CHANGED
@@ -138,13 +138,16 @@ function countAtEnd(arr, value) {
138
138
  function makeSchedule(onOff, startTimes, initial = null) {
139
139
  const res = [];
140
140
  let prev = initial;
141
+ let prevRecord;
141
142
  for (let i = 0; i < startTimes.length; i++) {
142
143
  const value = onOff[i];
143
- if (value !== prev) {
144
+ if (value !== prev || i === 0) {
144
145
  const time = startTimes[i];
145
- res.push({ time, value });
146
+ prevRecord = { time, value, countHours: 0 };
147
+ res.push(prevRecord);
146
148
  prev = value;
147
149
  }
150
+ prevRecord.countHours++;
148
151
  }
149
152
  return res;
150
153
  }
@@ -0,0 +1,14 @@
1
+ {
2
+ "schedule": [{ "time": "2021-10-11T00:00:00.000+02:00", "value": false, "countHours": 48 }],
3
+ "config": {
4
+ "contextStorage": "default",
5
+ "doNotSplit": true,
6
+ "fromTime": "10",
7
+ "hoursOn": 4,
8
+ "maxPrice": 0.23,
9
+ "outputIfNoSchedule": true,
10
+ "outputOutsidePeriod": false,
11
+ "sendCurrentValueWhenRescheduling": true,
12
+ "toTime": "20"
13
+ }
14
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "schedule": [
3
+ { "time": "2021-10-11T00:00:00.000+02:00", "value": false, "countHours": 11 },
4
+ { "time": "2021-10-11T11:00:00.000+02:00", "value": true, "countHours": 4 },
5
+ { "time": "2021-10-11T15:00:00.000+02:00", "value": false, "countHours": 21 },
6
+ { "time": "2021-10-12T12:00:00.000+02:00", "value": true, "countHours": 4 },
7
+ { "time": "2021-10-12T16:00:00.000+02:00", "value": false, "countHours": 8 }
8
+ ],
9
+ "config": {
10
+ "contextStorage": "default",
11
+ "doNotSplit": true,
12
+ "fromTime": "10",
13
+ "hoursOn": 4,
14
+ "maxPrice": 1,
15
+ "outputIfNoSchedule": true,
16
+ "outputOutsidePeriod": false,
17
+ "sendCurrentValueWhenRescheduling": true,
18
+ "toTime": "20"
19
+ }
20
+ }
@@ -1,16 +1,17 @@
1
1
  {
2
2
  "schedule": [
3
- { "time": "2021-10-11T00:00:00.000+02:00", "value": false },
4
- { "time": "2021-10-11T11:00:00.000+02:00", "value": true },
5
- { "time": "2021-10-11T15:00:00.000+02:00", "value": false },
6
- { "time": "2021-10-12T12:00:00.000+02:00", "value": true },
7
- { "time": "2021-10-12T16:00:00.000+02:00", "value": false }
3
+ { "time": "2021-10-11T00:00:00.000+02:00", "value": false, "countHours": 11 },
4
+ { "time": "2021-10-11T11:00:00.000+02:00", "value": true, "countHours": 4 },
5
+ { "time": "2021-10-11T15:00:00.000+02:00", "value": false, "countHours": 21 },
6
+ { "time": "2021-10-12T12:00:00.000+02:00", "value": true, "countHours": 4 },
7
+ { "time": "2021-10-12T16:00:00.000+02:00", "value": false, "countHours": 8 }
8
8
  ],
9
9
  "config": {
10
10
  "contextStorage": "default",
11
11
  "doNotSplit": true,
12
12
  "fromTime": "10",
13
13
  "hoursOn": 4,
14
+ "maxPrice": null,
14
15
  "outputIfNoSchedule": true,
15
16
  "outputOutsidePeriod": false,
16
17
  "sendCurrentValueWhenRescheduling": true,
@@ -3,15 +3,18 @@
3
3
  "schedule": [
4
4
  {
5
5
  "time": "2021-12-15T00:00:00+01:00",
6
- "value": false
6
+ "value": false,
7
+ "countHours": 22
7
8
  },
8
9
  {
9
10
  "time": "2021-12-15T22:00:00+01:00",
10
- "value": true
11
+ "value": true,
12
+ "countHours": 3
11
13
  },
12
14
  {
13
15
  "time": "2021-12-16T01:00:00+01:00",
14
- "value": false
16
+ "value": false,
17
+ "countHours": 23
15
18
  }
16
19
  ],
17
20
  "hours": [
@@ -310,6 +313,7 @@
310
313
  "fromTime": "22",
311
314
  "toTime": "08",
312
315
  "hoursOn": 3,
316
+ "maxPrice": null,
313
317
  "doNotSplit": true,
314
318
  "sendCurrentValueWhenRescheduling": true,
315
319
  "outputIfNoSchedule": false,
@@ -1,19 +1,20 @@
1
1
  {
2
2
  "schedule": [
3
- { "time": "2021-10-11T00:00:00.000+02:00", "value": true },
4
- { "time": "2021-10-11T06:00:00.000+02:00", "value": false },
5
- { "time": "2021-10-11T12:00:00.000+02:00", "value": true },
6
- { "time": "2021-10-11T14:00:00.000+02:00", "value": false },
7
- { "time": "2021-10-12T00:00:00.000+02:00", "value": true },
8
- { "time": "2021-10-12T07:00:00.000+02:00", "value": false },
9
- { "time": "2021-10-12T14:00:00.000+02:00", "value": true },
10
- { "time": "2021-10-12T15:00:00.000+02:00", "value": false }
3
+ { "time": "2021-10-11T00:00:00.000+02:00", "value": true, "countHours": 6 },
4
+ { "time": "2021-10-11T06:00:00.000+02:00", "value": false, "countHours": 6 },
5
+ { "time": "2021-10-11T12:00:00.000+02:00", "value": true, "countHours": 2 },
6
+ { "time": "2021-10-11T14:00:00.000+02:00", "value": false, "countHours": 10 },
7
+ { "time": "2021-10-12T00:00:00.000+02:00", "value": true, "countHours": 7 },
8
+ { "time": "2021-10-12T07:00:00.000+02:00", "value": false, "countHours": 7 },
9
+ { "time": "2021-10-12T14:00:00.000+02:00", "value": true, "countHours": 1 },
10
+ { "time": "2021-10-12T15:00:00.000+02:00", "value": false, "countHours": 9 }
11
11
  ],
12
12
  "config": {
13
13
  "contextStorage": "default",
14
14
  "doNotSplit": false,
15
15
  "fromTime": "00",
16
16
  "hoursOn": 8,
17
+ "maxPrice": null,
17
18
  "outputIfNoSchedule": true,
18
19
  "outputOutsidePeriod": false,
19
20
  "sendCurrentValueWhenRescheduling": true,
@@ -1,18 +1,19 @@
1
1
  {
2
2
  "schedule": [
3
- { "time": "2021-10-11T00:00:00.000+02:00", "value": true },
4
- { "time": "2021-10-11T10:00:00.000+02:00", "value": false },
5
- { "time": "2021-10-11T12:00:00.000+02:00", "value": true },
6
- { "time": "2021-10-11T14:00:00.000+02:00", "value": false },
7
- { "time": "2021-10-12T00:00:00.000+02:00", "value": true },
8
- { "time": "2021-10-12T08:00:00.000+02:00", "value": false },
9
- { "time": "2021-10-12T10:00:00.000+02:00", "value": true }
3
+ { "time": "2021-10-11T00:00:00.000+02:00", "value": true, "countHours": 10 },
4
+ { "time": "2021-10-11T10:00:00.000+02:00", "value": false, "countHours": 2 },
5
+ { "time": "2021-10-11T12:00:00.000+02:00", "value": true, "countHours": 2 },
6
+ { "time": "2021-10-11T14:00:00.000+02:00", "value": false, "countHours": 10 },
7
+ { "time": "2021-10-12T00:00:00.000+02:00", "value": true, "countHours": 8 },
8
+ { "time": "2021-10-12T08:00:00.000+02:00", "value": false, "countHours": 2 },
9
+ { "time": "2021-10-12T10:00:00.000+02:00", "value": true, "countHours": 14 }
10
10
  ],
11
11
  "config": {
12
12
  "contextStorage": "default",
13
13
  "doNotSplit": false,
14
14
  "fromTime": "10",
15
15
  "hoursOn": 10,
16
+ "maxPrice": null,
16
17
  "outputIfNoSchedule": true,
17
18
  "outputOutsidePeriod": false,
18
19
  "sendCurrentValueWhenRescheduling": true,
@@ -0,0 +1,22 @@
1
+ {
2
+ "schedule": [
3
+ { "time": "2021-10-11T00:00:00.000+02:00", "value": false, "countHours": 12 },
4
+ { "time": "2021-10-11T12:00:00.000+02:00", "value": true, "countHours": 3 },
5
+ { "time": "2021-10-11T15:00:00.000+02:00", "value": false, "countHours": 21 },
6
+ { "time": "2021-10-12T12:00:00.000+02:00", "value": true, "countHours": 4 },
7
+ { "time": "2021-10-12T16:00:00.000+02:00", "value": false, "countHours": 3 },
8
+ { "time": "2021-10-12T19:00:00.000+02:00", "value": true, "countHours": 1 },
9
+ { "time": "2021-10-12T20:00:00.000+02:00", "value": false, "countHours": 4 }
10
+ ],
11
+ "config": {
12
+ "contextStorage": "default",
13
+ "doNotSplit": false,
14
+ "fromTime": "10",
15
+ "hoursOn": 6,
16
+ "maxPrice": 0.51,
17
+ "outputIfNoSchedule": true,
18
+ "outputOutsidePeriod": false,
19
+ "sendCurrentValueWhenRescheduling": true,
20
+ "toTime": "20"
21
+ }
22
+ }
@@ -1,18 +1,19 @@
1
1
  {
2
2
  "schedule": [
3
- { "time": "2021-10-11T00:00:00.000+02:00", "value": false },
4
- { "time": "2021-10-11T11:00:00.000+02:00", "value": true },
5
- { "time": "2021-10-11T17:00:00.000+02:00", "value": false },
6
- { "time": "2021-10-12T12:00:00.000+02:00", "value": true },
7
- { "time": "2021-10-12T16:00:00.000+02:00", "value": false },
8
- { "time": "2021-10-12T18:00:00.000+02:00", "value": true },
9
- { "time": "2021-10-12T20:00:00.000+02:00", "value": false }
3
+ { "time": "2021-10-11T00:00:00.000+02:00", "value": false, "countHours": 11 },
4
+ { "time": "2021-10-11T11:00:00.000+02:00", "value": true, "countHours": 6 },
5
+ { "time": "2021-10-11T17:00:00.000+02:00", "value": false, "countHours": 19 },
6
+ { "time": "2021-10-12T12:00:00.000+02:00", "value": true, "countHours": 4 },
7
+ { "time": "2021-10-12T16:00:00.000+02:00", "value": false, "countHours": 2 },
8
+ { "time": "2021-10-12T18:00:00.000+02:00", "value": true, "countHours": 2 },
9
+ { "time": "2021-10-12T20:00:00.000+02:00", "value": false, "countHours": 4 }
10
10
  ],
11
11
  "config": {
12
12
  "contextStorage": "default",
13
13
  "doNotSplit": false,
14
14
  "fromTime": "10",
15
15
  "hoursOn": 6,
16
+ "maxPrice": null,
16
17
  "outputIfNoSchedule": true,
17
18
  "outputOutsidePeriod": false,
18
19
  "sendCurrentValueWhenRescheduling": true,
@@ -2,23 +2,28 @@
2
2
  "schedule": [
3
3
  {
4
4
  "time": "2022-01-03T00:00:00+01:00",
5
- "value": false
5
+ "value": false,
6
+ "countHours": 28
6
7
  },
7
8
  {
8
9
  "time": "2022-01-04T04:00:00+01:00",
9
- "value": true
10
+ "value": true,
11
+ "countHours": 1
10
12
  },
11
13
  {
12
14
  "time": "2022-01-04T05:00:00+01:00",
13
- "value": false
15
+ "value": false,
16
+ "countHours": 22
14
17
  },
15
18
  {
16
19
  "time": "2022-01-05T03:00:00+01:00",
17
- "value": true
20
+ "value": true,
21
+ "countHours": 1
18
22
  },
19
23
  {
20
24
  "time": "2022-01-05T04:00:00+01:00",
21
- "value": false
25
+ "value": false,
26
+ "countHours": 20
22
27
  }
23
28
  ],
24
29
  "hours": [
@@ -3,38 +3,47 @@ module.exports = {
3
3
  {
4
4
  time: "2021-06-20T01:50:00.000+02:00",
5
5
  value: true,
6
+ countHours: 3,
6
7
  },
7
8
  {
8
9
  time: "2021-06-20T01:50:00.030+02:00",
9
10
  value: false,
11
+ countHours: 2,
10
12
  },
11
13
  {
12
14
  time: "2021-06-20T01:50:00.050+02:00",
13
15
  value: true,
16
+ countHours: 1,
14
17
  },
15
18
  {
16
19
  time: "2021-06-20T01:50:00.060+02:00",
17
20
  value: false,
21
+ countHours: 2,
18
22
  },
19
23
  {
20
24
  time: "2021-06-20T01:50:00.080+02:00",
21
25
  value: true,
26
+ countHours: 1,
22
27
  },
23
28
  {
24
29
  time: "2021-06-20T01:50:00.090+02:00",
25
30
  value: false,
31
+ countHours: 3,
26
32
  },
27
33
  {
28
34
  time: "2021-06-20T01:50:00.120+02:00",
29
35
  value: true,
36
+ countHours: 3,
30
37
  },
31
38
  {
32
39
  time: "2021-06-20T01:50:00.150+02:00",
33
40
  value: false,
41
+ countHours: 3,
34
42
  },
35
43
  {
36
44
  time: "2021-06-20T01:50:00.180+02:00",
37
45
  value: true,
46
+ countHours: 2,
38
47
  },
39
48
  ],
40
49
  hours: [
@@ -2,19 +2,23 @@
2
2
  "schedule": [
3
3
  {
4
4
  "time": "2021-12-14T00:00:00.000+01:00",
5
- "value": false
5
+ "value": false,
6
+ "countHours": 21
6
7
  },
7
8
  {
8
9
  "time": "2021-12-14T21:00:00.000+01:00",
9
- "value": true
10
+ "value": true,
11
+ "countHours": 3
10
12
  },
11
13
  {
12
14
  "time": "2021-12-15T00:00:00.000+01:00",
13
- "value": false
15
+ "value": false,
16
+ "countHours": 21
14
17
  },
15
18
  {
16
19
  "time": "2021-12-15T21:00:00.000+01:00",
17
- "value": true
20
+ "value": true,
21
+ "countHours": 3
18
22
  }
19
23
  ],
20
24
  "hours": [
@@ -313,6 +317,7 @@
313
317
  "fromTime": "16",
314
318
  "toTime": "00",
315
319
  "hoursOn": 3,
320
+ "maxPrice": null,
316
321
  "doNotSplit": false,
317
322
  "sendCurrentValueWhenRescheduling": true,
318
323
  "outputIfNoSchedule": false,
@@ -2,11 +2,13 @@
2
2
  "schedule": [
3
3
  {
4
4
  "time": "2021-12-14T00:00:00.000+01:00",
5
- "value": false
5
+ "value": false,
6
+ "countHours": 21
6
7
  },
7
8
  {
8
9
  "time": "2021-12-14T21:00:00.000+01:00",
9
- "value": true
10
+ "value": true,
11
+ "countHours": 3
10
12
  }
11
13
  ],
12
14
  "hours": [
@@ -161,6 +163,7 @@
161
163
  "fromTime": "16",
162
164
  "toTime": "00",
163
165
  "hoursOn": 3,
166
+ "maxPrice": null,
164
167
  "doNotSplit": false,
165
168
  "sendCurrentValueWhenRescheduling": true,
166
169
  "outputIfNoSchedule": false,
@@ -2,59 +2,73 @@
2
2
  "schedule": [
3
3
  {
4
4
  "time": "2021-10-11T00:00:00.000+02:00",
5
- "value": false
5
+ "value": false,
6
+ "countHours": 2
6
7
  },
7
8
  {
8
9
  "time": "2021-10-11T02:00:00.000+02:00",
9
- "value": true
10
+ "value": true,
11
+ "countHours": 5
10
12
  },
11
13
  {
12
14
  "time": "2021-10-11T07:00:00.000+02:00",
13
- "value": false
15
+ "value": false,
16
+ "countHours": 4
14
17
  },
15
18
  {
16
19
  "time": "2021-10-11T11:00:00.000+02:00",
17
- "value": true
20
+ "value": true,
21
+ "countHours": 7
18
22
  },
19
23
  {
20
24
  "time": "2021-10-11T18:00:00.000+02:00",
21
- "value": false
25
+ "value": false,
26
+ "countHours": 2
22
27
  },
23
28
  {
24
29
  "time": "2021-10-11T20:00:00.000+02:00",
25
- "value": true
30
+ "value": true,
31
+ "countHours": 1
26
32
  },
27
33
  {
28
34
  "time": "2021-10-11T21:00:00.000+02:00",
29
- "value": false
35
+ "value": false,
36
+ "countHours": 4
30
37
  },
31
38
  {
32
39
  "time": "2021-10-12T01:00:00.000+02:00",
33
- "value": true
40
+ "value": true,
41
+ "countHours": 8
34
42
  },
35
43
  {
36
44
  "time": "2021-10-12T09:00:00.000+02:00",
37
- "value": false
45
+ "value": false,
46
+ "countHours": 4
38
47
  },
39
48
  {
40
49
  "time": "2021-10-12T13:00:00.000+02:00",
41
- "value": true
50
+ "value": true,
51
+ "countHours": 3
42
52
  },
43
53
  {
44
54
  "time": "2021-10-12T16:00:00.000+02:00",
45
- "value": false
55
+ "value": false,
56
+ "countHours": 4
46
57
  },
47
58
  {
48
59
  "time": "2021-10-12T20:00:00.000+02:00",
49
- "value": true
60
+ "value": true,
61
+ "countHours": 2
50
62
  },
51
63
  {
52
64
  "time": "2021-10-12T22:00:00.000+02:00",
53
- "value": false
65
+ "value": false,
66
+ "countHours": 1
54
67
  },
55
68
  {
56
69
  "time": "2021-10-12T23:00:00.000+02:00",
57
- "value": true
70
+ "value": true,
71
+ "countHours": 1
58
72
  }
59
73
  ],
60
74
  "hours": [
@@ -85,14 +85,17 @@ describe("power-saver Node", function () {
85
85
  {
86
86
  time: "2021-06-20T01:50:00.000+02:00",
87
87
  value: true,
88
+ countHours: 7,
88
89
  },
89
90
  {
90
91
  time: "2021-06-20T01:50:00.070+02:00",
91
92
  value: false,
93
+ countHours: 2,
92
94
  },
93
95
  {
94
96
  time: "2021-06-20T01:50:00.090+02:00",
95
97
  value: true,
98
+ countHours: 1,
96
99
  },
97
100
  ];
98
101
  const flow = makeFlow(4, 2);
@@ -121,14 +124,17 @@ describe("power-saver Node", function () {
121
124
  {
122
125
  time: "2021-06-20T01:50:00.000+02:00",
123
126
  value: true,
127
+ countHours: 7,
124
128
  },
125
129
  {
126
130
  time: "2021-06-20T01:50:00.070+02:00",
127
131
  value: false,
132
+ countHours: 4,
128
133
  },
129
134
  {
130
135
  time: "2021-06-20T01:50:00.110+02:00",
131
136
  value: true,
137
+ countHours: 9,
132
138
  },
133
139
  ];
134
140
  const flow = makeFlow(4, 2);
@@ -91,6 +91,55 @@ describe("ps-strategy-lowest-price node", function () {
91
91
  n1.receive({ payload: makePayload(prices, time) });
92
92
  });
93
93
  });
94
+ it("should plan correct continuous schedule with max price ok", function (done) {
95
+ const resultContinuousMax = require("./data/lowest-price-result-cont-max.json");
96
+ const flow = makeFlow(4, 1.0);
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", resultContinuousMax.schedule);
102
+ expect(msg.payload).toHaveProperty("config", resultContinuousMax.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(prices, time) });
108
+ });
109
+ });
110
+ it("should plan correct continuous schedule with max price too high", function (done) {
111
+ const resultContinuousMax = require("./data/lowest-price-result-cont-max-fail.json");
112
+ const flow = makeFlow(4, 0.23);
113
+ helper.load(lowestPrice, flow, function () {
114
+ const n1 = helper.getNode("n1");
115
+ const n2 = helper.getNode("n2");
116
+ n2.on("input", function (msg) {
117
+ expect(msg.payload).toHaveProperty("schedule", resultContinuousMax.schedule);
118
+ expect(msg.payload).toHaveProperty("config", resultContinuousMax.config);
119
+ n1.warn.should.not.be.called;
120
+ done();
121
+ });
122
+ const time = DateTime.fromISO(prices.priceData[10].start);
123
+ n1.receive({ payload: makePayload(prices, time) });
124
+ });
125
+ });
126
+ it("should plan correct splitted schedule with max price", function (done) {
127
+ const resultSplittedMax = require("./data/lowest-price-result-split-max.json");
128
+ const flow = makeFlow(6, 0.51);
129
+ flow[0].doNotSplit = false;
130
+ helper.load(lowestPrice, flow, function () {
131
+ const n1 = helper.getNode("n1");
132
+ const n2 = helper.getNode("n2");
133
+ n2.on("input", function (msg) {
134
+ expect(msg.payload).toHaveProperty("schedule", resultSplittedMax.schedule);
135
+ expect(msg.payload).toHaveProperty("config", resultSplittedMax.config);
136
+ n1.warn.should.not.be.called;
137
+ done();
138
+ });
139
+ const time = DateTime.fromISO(prices.priceData[10].start);
140
+ n1.receive({ payload: makePayload(prices, time) });
141
+ });
142
+ });
94
143
  it("should plan correct for all day period - 00-00", function (done) {
95
144
  const resultAllDay = require("./data/lowest-price-result-split-allday.json");
96
145
  const flow = makeFlow(8);
@@ -186,12 +235,14 @@ describe("ps-strategy-lowest-price node", function () {
186
235
  const schedule = cloneDeep(resultSplitted.schedule);
187
236
  const config = cloneDeep(resultSplitted.config);
188
237
  schedule[0].value = true;
189
- schedule.splice(1, 0, { time: "2021-10-11T10:00:00.000+02:00", value: false });
190
- schedule.splice(4, 0, { time: "2021-10-11T20:00:00.000+02:00", value: true });
191
- schedule.splice(5, 0, { time: "2021-10-12T10:00:00.000+02:00", value: false });
238
+ schedule.splice(1, 0, { time: "2021-10-11T10:00:00.000+02:00", value: false, countHours: 10 });
239
+ schedule.splice(4, 0, { time: "2021-10-11T20:00:00.000+02:00", value: true, countHours: 14 });
240
+ schedule.splice(5, 0, { time: "2021-10-12T10:00:00.000+02:00", value: false, countHours: 14 });
192
241
  schedule.splice(schedule.length - 1, 1);
193
242
  config.outputOutsidePeriod = true;
194
- expect(msg.payload).toHaveProperty("schedule", schedule);
243
+ const res = msg.payload.schedule.map((s) => ({ time: s.time, value: s.value }));
244
+ const exp = schedule.map((s) => ({ time: s.time, value: s.value }));
245
+ expect(res).toEqual(exp);
195
246
  expect(msg.payload).toHaveProperty("config", config);
196
247
  n1.warn.should.not.be.called;
197
248
  done();
@@ -218,7 +269,9 @@ describe("ps-strategy-lowest-price node", function () {
218
269
  schedule.splice(schedule.length, 0, { time: "2021-10-12T20:00:00.000+02:00", value: true });
219
270
  // schedule.splice(schedule.length - 1, 1);
220
271
  config.outputOutsidePeriod = true;
221
- expect(msg.payload).toHaveProperty("schedule", schedule);
272
+ const res = msg.payload.schedule.map((s) => ({ time: s.time, value: s.value }));
273
+ const exp = schedule.map((s) => ({ time: s.time, value: s.value }));
274
+ expect(res).toEqual(exp);
222
275
  expect(msg.payload).toHaveProperty("config", config);
223
276
  n1.warn.should.not.be.called;
224
277
  done();
@@ -228,7 +281,7 @@ describe("ps-strategy-lowest-price node", function () {
228
281
  });
229
282
  });
230
283
  it("should work with 0 hours on", function (done) {
231
- const result = [{ time: "2021-10-11T00:00:00.000+02:00", value: false }];
284
+ const result = [{ time: "2021-10-11T00:00:00.000+02:00", value: false, countHours: 48 }];
232
285
  const flow = makeFlow(0);
233
286
  helper.load(lowestPrice, flow, function () {
234
287
  const n1 = helper.getNode("n1");
@@ -244,11 +297,11 @@ describe("ps-strategy-lowest-price node", function () {
244
297
  });
245
298
  it("should work with 0 hours on outside on", function (done) {
246
299
  const result = [
247
- { time: "2021-10-11T00:00:00.000+02:00", value: true },
248
- { time: "2021-10-11T10:00:00.000+02:00", value: false },
249
- { time: "2021-10-11T20:00:00.000+02:00", value: true },
250
- { time: "2021-10-12T10:00:00.000+02:00", value: false },
251
- { time: "2021-10-12T20:00:00.000+02:00", value: true },
300
+ { time: "2021-10-11T00:00:00.000+02:00", value: true, countHours: 10 },
301
+ { time: "2021-10-11T10:00:00.000+02:00", value: false, countHours: 10 },
302
+ { time: "2021-10-11T20:00:00.000+02:00", value: true, countHours: 14 },
303
+ { time: "2021-10-12T10:00:00.000+02:00", value: false, countHours: 10 },
304
+ { time: "2021-10-12T20:00:00.000+02:00", value: true, countHours: 4 },
252
305
  ];
253
306
  const flow = makeFlow(0);
254
307
  flow[0].outputOutsidePeriod = true;
@@ -266,11 +319,11 @@ describe("ps-strategy-lowest-price node", function () {
266
319
  });
267
320
  it("should work with 1 hours on", function (done) {
268
321
  const result = [
269
- { time: "2021-10-11T00:00:00.000+02:00", value: false },
270
- { time: "2021-10-11T12:00:00.000+02:00", value: true },
271
- { time: "2021-10-11T13:00:00.000+02:00", value: false },
272
- { time: "2021-10-12T14:00:00.000+02:00", value: true },
273
- { time: "2021-10-12T15:00:00.000+02:00", value: false },
322
+ { time: "2021-10-11T00:00:00.000+02:00", value: false, countHours: 12 },
323
+ { time: "2021-10-11T12:00:00.000+02:00", value: true, countHours: 1 },
324
+ { time: "2021-10-11T13:00:00.000+02:00", value: false, countHours: 25 },
325
+ { time: "2021-10-12T14:00:00.000+02:00", value: true, countHours: 1 },
326
+ { time: "2021-10-12T15:00:00.000+02:00", value: false, countHours: 9 },
274
327
  ];
275
328
  const flow = makeFlow(1);
276
329
  helper.load(lowestPrice, flow, function () {
@@ -287,11 +340,11 @@ describe("ps-strategy-lowest-price node", function () {
287
340
  });
288
341
  it("should work with 24 hours on", function (done) {
289
342
  const result = [
290
- { time: "2021-10-11T00:00:00.000+02:00", value: false },
291
- { time: "2021-10-11T10:00:00.000+02:00", value: true },
292
- { time: "2021-10-11T20:00:00.000+02:00", value: false },
293
- { time: "2021-10-12T10:00:00.000+02:00", value: true },
294
- { time: "2021-10-12T20:00:00.000+02:00", value: false },
343
+ { time: "2021-10-11T00:00:00.000+02:00", value: false, countHours: 10 },
344
+ { time: "2021-10-11T10:00:00.000+02:00", value: true, countHours: 10 },
345
+ { time: "2021-10-11T20:00:00.000+02:00", value: false, countHours: 14 },
346
+ { time: "2021-10-12T10:00:00.000+02:00", value: true, countHours: 10 },
347
+ { time: "2021-10-12T20:00:00.000+02:00", value: false, countHours: 4 },
295
348
  ];
296
349
  const flow = makeFlow(24);
297
350
  helper.load(lowestPrice, flow, function () {
@@ -311,9 +364,9 @@ describe("ps-strategy-lowest-price node", function () {
311
364
  const oneDayPrices = {};
312
365
  oneDayPrices.priceData = prices.priceData.filter((d) => d.start.startsWith("2021-10-11"));
313
366
  const result = [
314
- { time: "2021-10-11T00:00:00.000+02:00", value: false },
315
- { time: "2021-10-11T12:00:00.000+02:00", value: true },
316
- { time: "2021-10-11T13:00:00.000+02:00", value: false },
367
+ { time: "2021-10-11T00:00:00.000+02:00", value: false, countHours: 12 },
368
+ { time: "2021-10-11T12:00:00.000+02:00", value: true, countHours: 1 },
369
+ { time: "2021-10-11T13:00:00.000+02:00", value: false, countHours: 11 },
317
370
  ];
318
371
  const flow = makeFlow(1);
319
372
  helper.load(lowestPrice, flow, function () {
@@ -340,7 +393,7 @@ describe("ps-strategy-lowest-price node", function () {
340
393
  });
341
394
 
342
395
  it("should handle hours on > period", function (done) {
343
- const result = [{ time: "2021-10-11T00:00:00.000+02:00", value: true }];
396
+ const result = [{ time: "2021-10-11T00:00:00.000+02:00", value: true, countHours: 48 }];
344
397
  const flow = [
345
398
  {
346
399
  id: "n1",
@@ -388,11 +441,11 @@ describe("ps-strategy-lowest-price node", function () {
388
441
  });
389
442
  it("should handle hours on > period, false outside", function (done) {
390
443
  const result = [
391
- { time: "2021-10-11T00:00:00.000+02:00", value: false },
392
- { time: "2021-10-11T17:00:00.000+02:00", value: true },
393
- { time: "2021-10-11T22:00:00.000+02:00", value: false },
394
- { time: "2021-10-12T17:00:00.000+02:00", value: true },
395
- { time: "2021-10-12T22:00:00.000+02:00", value: false },
444
+ { time: "2021-10-11T00:00:00.000+02:00", value: false, countHours: 17 },
445
+ { time: "2021-10-11T17:00:00.000+02:00", value: true, countHours: 5 },
446
+ { time: "2021-10-11T22:00:00.000+02:00", value: false, countHours: 19 },
447
+ { time: "2021-10-12T17:00:00.000+02:00", value: true, countHours: 5 },
448
+ { time: "2021-10-12T22:00:00.000+02:00", value: false, countHours: 2 },
396
449
  ];
397
450
  const flow = [
398
451
  {
@@ -451,6 +504,7 @@ describe("ps-strategy-lowest-price node", function () {
451
504
  fromTime: "16",
452
505
  toTime: "00",
453
506
  hoursOn: 3,
507
+ maxPrice: null,
454
508
  doNotSplit: false,
455
509
  sendCurrentValueWhenRescheduling: true,
456
510
  outputIfNoSchedule: false,
@@ -485,6 +539,7 @@ describe("ps-strategy-lowest-price node", function () {
485
539
  fromTime: "16",
486
540
  toTime: "00",
487
541
  hoursOn: 3,
542
+ maxPrice: null,
488
543
  doNotSplit: false,
489
544
  sendCurrentValueWhenRescheduling: true,
490
545
  outputIfNoSchedule: false,
@@ -519,6 +574,7 @@ describe("ps-strategy-lowest-price node", function () {
519
574
  fromTime: "22",
520
575
  toTime: "08",
521
576
  hoursOn: 3,
577
+ maxPrice: null,
522
578
  doNotSplit: true,
523
579
  sendCurrentValueWhenRescheduling: true,
524
580
  outputIfNoSchedule: false,
@@ -544,7 +600,7 @@ describe("ps-strategy-lowest-price node", function () {
544
600
  });
545
601
  });
546
602
 
547
- function makeFlow(hoursOn) {
603
+ function makeFlow(hoursOn, maxPrice = null) {
548
604
  return [
549
605
  {
550
606
  id: "n1",
@@ -553,6 +609,7 @@ function makeFlow(hoursOn) {
553
609
  fromTime: "10",
554
610
  toTime: "20",
555
611
  hoursOn: hoursOn,
612
+ maxPrice: maxPrice,
556
613
  doNotSplit: true,
557
614
  sendCurrentValueWhenRescheduling: true,
558
615
  outputIfNoSchedule: true,
@@ -70,18 +70,19 @@ describe("utils", () => {
70
70
  "2021-06-20T09:00:00+02:00",
71
71
  ];
72
72
  expect(makeSchedule(onOff, startTimes)).toEqual([
73
- { time: "2021-06-20T05:00:00+02:00", value: false },
74
- { time: "2021-06-20T07:00:00+02:00", value: true },
75
- { time: "2021-06-20T09:00:00+02:00", value: false },
73
+ { time: "2021-06-20T05:00:00+02:00", value: false, countHours: 2 },
74
+ { time: "2021-06-20T07:00:00+02:00", value: true, countHours: 2 },
75
+ { time: "2021-06-20T09:00:00+02:00", value: false, countHours: 1 },
76
76
  ]);
77
77
  expect(makeSchedule(onOff, startTimes, true)).toEqual([
78
- { time: "2021-06-20T05:00:00+02:00", value: false },
79
- { time: "2021-06-20T07:00:00+02:00", value: true },
80
- { time: "2021-06-20T09:00:00+02:00", value: false },
78
+ { time: "2021-06-20T05:00:00+02:00", value: false, countHours: 2 },
79
+ { time: "2021-06-20T07:00:00+02:00", value: true, countHours: 2 },
80
+ { time: "2021-06-20T09:00:00+02:00", value: false, countHours: 1 },
81
81
  ]);
82
82
  expect(makeSchedule(onOff, startTimes, false)).toEqual([
83
- { time: "2021-06-20T07:00:00+02:00", value: true },
84
- { time: "2021-06-20T09:00:00+02:00", value: false },
83
+ { time: "2021-06-20T05:00:00+02:00", value: false, countHours: 2 }, // Right???
84
+ { time: "2021-06-20T07:00:00+02:00", value: true, countHours: 2 },
85
+ { time: "2021-06-20T09:00:00+02:00", value: false, countHours: 1 },
85
86
  ]);
86
87
  });
87
88