node-red-contrib-power-saver 5.0.0-beta.1 → 5.0.0-beta.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.
@@ -1,7 +1,7 @@
1
1
  # These are supported funding model platforms
2
2
 
3
3
  github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4
- patreon: ottopaulsen
4
+ patreon: # ottopaulsen
5
5
  open_collective: # Replace with a single Open Collective username
6
6
  ko_fi: # Replace with a single Ko-fi username
7
7
  tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
package/= ADDED
File without changes
package/README.md CHANGED
@@ -5,3 +5,6 @@ A Node-RED node to save money when power prices are changing by the hour.
5
5
  ![Logo](docs/.vuepress/public/logo.png)
6
6
 
7
7
  ## Please read more in the [documentation](https://powersaver.no/).
8
+
9
+
10
+ [![Donation](./donation.png)](https://powersaver.no/contribute/#donate)
@@ -83,6 +83,7 @@ export default defineUserConfig({
83
83
  "/examples/example-visualize-on-off/example-visualize-on-off.md",
84
84
  "/examples/example-grid-tariff-capacity-part.md",
85
85
  "/examples/example-stromstotte.md",
86
+ "/examples/example-reduce-minutes-array.md",
86
87
  ],
87
88
  },
88
89
  ],
@@ -7,6 +7,15 @@ sidebarDepth: 1
7
7
 
8
8
  List the most significant changes.
9
9
 
10
+ ## 5.0.0.beta.3
11
+
12
+ - Implement minute support in Heat Capacitor
13
+ - Fix bug so recoveryPercentage is used properly
14
+
15
+ ## 5.0.0.beta.2
16
+
17
+ - Fix bug in Min minutes off
18
+
10
19
  ## 5.0.0 beta.1
11
20
 
12
21
  - Fix bug `dataDayBefore.minutes is not iterable`
@@ -17,6 +17,8 @@
17
17
 
18
18
  [Strømstøtte (governmentall support)](./example-stromstotte)
19
19
 
20
+ [Reduce minutes-array](./example-reduce-minutes-array)
21
+
20
22
  ## User provided examples
21
23
 
22
24
  [Output schedule to a sensor entity](./example-next-schedule-entity.md) (by Stefan)
@@ -0,0 +1,23 @@
1
+ # Reduce minutes array
2
+
3
+ If you want to reduce number of entries in the minutes array on output 3, you can send it through a function node with the following code:
4
+ ```
5
+ const minutes = []
6
+ let previousHour = ""
7
+ let previousSaving = null
8
+ let previousPrice = null
9
+ msg.payload.minutes.forEach(m => {
10
+ const hour = m.start.substring(0, 13)
11
+ if (hour !== previousHour || m.saving !== previousSaving || m.price !== previousPrice) {
12
+ minutes.push(m)
13
+ previousHour = hour
14
+ previousSaving = m.saving
15
+ previousPrice = m.price
16
+ }
17
+ })
18
+
19
+ msg.payload.minutes = minutes
20
+
21
+ return msg;
22
+ ```
23
+ It will remove all entries that are equal to the one before, but still send at least one per hour.
package/donation.png ADDED
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-power-saver",
3
- "version": "5.0.0-beta.1",
3
+ "version": "5.0.0-beta.3",
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": {
@@ -8,7 +8,9 @@ const { fillArray } = require("./utils");
8
8
  *
9
9
  * @param {*} onOff Array of on/off values
10
10
  * @param {*} maxMinutesOff Max number of minutes that can be off in a sequence
11
- * @param {*} minMinutesOff Min number of minutes that must be on after maxOff is reached
11
+ * @param {*} minMinutesOff Min number of minutes that must be off to bother
12
+ * @param {*} recoveryPercentage Percent of off-time that must be on after being off
13
+ * @param {*} recoveryMaxMinutes Maximum recovery time in minutes
12
14
  * @returns
13
15
  */
14
16
  function isOnOffSequencesOk(
@@ -16,37 +18,55 @@ function isOnOffSequencesOk(
16
18
  maxMinutesOff,
17
19
  minMinutesOff,
18
20
  recoveryPercentage,
19
- recoveryMaxMinutes = null,
20
- minSaving) {
21
+ recoveryMaxMinutes = null) {
21
22
  let offCount = 0;
22
23
  let onCount = 0;
23
24
  let reachedMaxOff = false;
24
- let reachedMinOn = minMinutesOff === 0;
25
- let minOnAfterOff = null;
25
+ let reachedMinOn = true;
26
+ let reachedMinOff = null;
27
+ let minOnAfterOff = 0;
26
28
  for (let i = 0; i < onOff.length; i++) {
27
29
  if (!onOff[i]) {
28
30
  if (maxMinutesOff === 0 || reachedMaxOff) {
29
31
  return false;
30
32
  }
33
+ if(!reachedMinOn) {
34
+ return false;
35
+ }
36
+ if(reachedMinOff === null) {
37
+ reachedMinOff = false;
38
+ }
31
39
  offCount++;
32
40
  onCount = 0;
33
41
  if (offCount >= maxMinutesOff) {
34
42
  reachedMaxOff = true;
35
43
  }
36
44
  if (offCount >= minMinutesOff) {
37
- reachedMinOn = true;
45
+ reachedMinOff = true;
38
46
  }
39
47
  const minRounded = Math.max(Math.round(offCount * recoveryPercentage / 100), 1)
40
48
  minOnAfterOff = Math.min(minRounded, recoveryMaxMinutes ?? minRounded)
49
+ if(i === onOff.length - 1) {
50
+ // If last minute, consider min reached
51
+ reachedMinOn = true;
52
+ reachedMinOff = true;
53
+ }
41
54
  } else {
55
+ if(reachedMinOff === false) {
56
+ return false;
57
+ }
42
58
  onCount++;
43
59
  if (onCount >= minOnAfterOff) {
44
60
  reachedMaxOff = false;
61
+ reachedMinOn = true;
62
+ } else {
63
+ reachedMinOn = false;
45
64
  }
46
65
  offCount = 0;
66
+ reachedMinOff = null;
47
67
  }
48
68
  }
49
- return reachedMinOn;
69
+ return reachedMinOn && !(reachedMinOff === false);
50
70
  }
51
71
 
52
72
  /**
@@ -2,22 +2,51 @@
2
2
  const { DateTime } = require("luxon");
3
3
  const { roundPrice, getDiffToNextOn } = require("./utils");
4
4
 
5
- function calculateOpportunities(prices, pattern, amount) {
6
- //creating a price vector with minute granularity
7
- const tempPrice = Array(prices.length * 60).fill(0);
8
- for (let i = 0; i < prices.length; i++) {
9
- tempPrice.fill(prices[i], i * 60, (i + 1) * 60);
10
- //debugger;
5
+ function buildMinutePriceVector(priceData) {
6
+ if (!priceData || priceData.length === 0) {
7
+ return { minutePrices: [], startDate: null };
11
8
  }
12
9
 
10
+ const sorted = [...priceData].sort(
11
+ (a, b) => DateTime.fromISO(a.start).toMillis() - DateTime.fromISO(b.start).toMillis()
12
+ );
13
+
14
+ const minutePrices = [];
15
+ let previousIntervalMinutes = 60;
16
+
17
+ for (let i = 0; i < sorted.length; i++) {
18
+ const currentStart = DateTime.fromISO(sorted[i].start);
19
+ let intervalMinutes = previousIntervalMinutes;
20
+
21
+ if (sorted[i + 1]) {
22
+ intervalMinutes = DateTime.fromISO(sorted[i + 1].start).diff(currentStart, "minutes").minutes;
23
+ } else if (sorted[i].end) {
24
+ intervalMinutes = DateTime.fromISO(sorted[i].end).diff(currentStart, "minutes").minutes;
25
+ }
26
+
27
+ intervalMinutes = Math.max(1, Math.round(intervalMinutes));
28
+ previousIntervalMinutes = intervalMinutes;
29
+
30
+ for (let m = 0; m < intervalMinutes; m++) {
31
+ minutePrices.push(sorted[i].value);
32
+ }
33
+ }
34
+
35
+ return { minutePrices, startDate: DateTime.fromISO(sorted[0].start) };
36
+ }
37
+
38
+ function calculateOpportunities(pricesPerMinute, pattern, amount) {
39
+ const tempPrice = pricesPerMinute;
40
+
13
41
  //Calculate weighted pattern
14
42
  const weight = amount / pattern.reduce((a, b) => a + b, 0); //last calculates the sum of all numbers in the pattern
15
43
  const weightedPattern = pattern.map((x) => x * weight);
16
44
 
17
45
  //Calculating procurement opportunities. Sliding the pattern over the price vector to find the price for procuring
18
46
  //at time t
19
- const dot = (a, b) => a.map((x, i) => a[i] * b[i]).reduce((m, n) => m + n);
20
- const procurementOpportunities = Array(prices.length * 60 - pattern.length + 1);
47
+ const dot = (a, b) => a.map((x, i) => a[i] * b[i]).reduce((m, n) => m + n, 0);
48
+ const procurementLength = Math.max(tempPrice.length - pattern.length + 1, 0);
49
+ const procurementOpportunities = Array(procurementLength);
21
50
  for (let i = 0; i < procurementOpportunities.length; i++) {
22
51
  procurementOpportunities[i] = dot(weightedPattern, tempPrice.slice(i, i + pattern.length));
23
52
  }
@@ -232,8 +261,7 @@ function runBuySellAlgorithm(
232
261
  maxTempAdjustment,
233
262
  minSavings
234
263
  ) {
235
- const prices = [...priceData.map((pd) => pd.value)];
236
- const startDate = DateTime.fromISO(priceData[0].start);
264
+ const { minutePrices, startDate } = buildMinutePriceVector(priceData);
237
265
 
238
266
  //pattern for how much power is procured/sold when.
239
267
  //This has, for now, just a flat acquisition/divestment profile
@@ -243,8 +271,8 @@ function runBuySellAlgorithm(
243
271
  const sellPattern = Array(sellDuration).fill(1);
244
272
 
245
273
  //Calculate what it will cost to procure/sell 1 kWh as a function of time
246
- const buyPrices = calculateOpportunities(prices, buyPattern, 1);
247
- const sellPrices = calculateOpportunities(prices, sellPattern, 1);
274
+ const buyPrices = calculateOpportunities(minutePrices, buyPattern, 1);
275
+ const sellPrices = calculateOpportunities(minutePrices, sellPattern, 1);
248
276
 
249
277
  //Find dates for when to procure/sell
250
278
  const buySell = findBestBuySellPattern(buyPrices, buyPattern.length, sellPrices, sellPattern.length);
@@ -80,10 +80,14 @@ module.exports = function (RED) {
80
80
  if (msg.payload.hasOwnProperty("priceData")) {
81
81
  if (node.hasOwnProperty("priceData")) {
82
82
  node.priceData = mergePriceData(node.priceData, msg.payload.priceData);
83
- if (node.priceData.length > 72) node.priceData = node.priceData.slice(-72);
84
83
  } else {
85
84
  node.priceData = msg.payload.priceData;
86
85
  }
86
+ if (node.priceData.length) {
87
+ const latestStart = DateTime.fromISO(node.priceData[node.priceData.length - 1].start);
88
+ const cutoff = latestStart.minus({ hours: 72 });
89
+ node.priceData = node.priceData.filter((entry) => DateTime.fromISO(entry.start) >= cutoff);
90
+ }
87
91
  }
88
92
 
89
93
  if (node.hasOwnProperty("priceData")) {
@@ -136,21 +140,16 @@ module.exports = function (RED) {
136
140
  };
137
141
 
138
142
  function mergePriceData(priceDataA, priceDataB) {
139
- const tempDict = {};
140
- priceDataA.forEach((e) => {
141
- tempDict[e.start] = e.value;
142
- });
143
- priceDataB.forEach((e) => {
144
- tempDict[e.start] = e.value;
143
+ const mergedEntries = new Map();
144
+ priceDataA.concat(priceDataB).forEach((entry) => {
145
+ if (!entry?.start) {
146
+ return;
147
+ }
148
+ const existing = mergedEntries.get(entry.start);
149
+ mergedEntries.set(entry.start, Object.assign({}, existing, entry));
145
150
  });
146
151
 
147
- const keys = Object.keys(tempDict);
148
- keys.sort();
149
-
150
- const res = Array(keys.length);
151
- for (let i = 0; i < res.length; i++) {
152
- res[i] = { value: tempDict[keys[i]], start: keys[i] };
153
- }
154
-
155
- return res;
152
+ return Array.from(mergedEntries.values()).sort(
153
+ (a, b) => DateTime.fromISO(a.start).toMillis() - DateTime.fromISO(b.start).toMillis()
154
+ );
156
155
  }
@@ -58,17 +58,7 @@
58
58
  {
59
59
  "time": "2021-06-20T02:33:00.000+02:00",
60
60
  "value": true,
61
- "countMinutes": 3
62
- },
63
- {
64
- "time": "2021-06-20T02:36:00.000+02:00",
65
- "value": false,
66
- "countMinutes": 1
67
- },
68
- {
69
- "time": "2021-06-20T02:37:00.000+02:00",
70
- "value": true,
71
- "countMinutes": 1
61
+ "countMinutes": 5
72
62
  },
73
63
  {
74
64
  "time": "2021-06-20T02:38:00.000+02:00",
@@ -0,0 +1,198 @@
1
+ {
2
+ "priceData": [
3
+ { "value": 0.4649, "start": "2025-11-07T00:00:00.000+01:00" },
4
+ { "value": 0.458, "start": "2025-11-07T00:15:00.000+01:00" },
5
+ { "value": 0.4438, "start": "2025-11-07T00:30:00.000+01:00" },
6
+ { "value": 0.4435, "start": "2025-11-07T00:45:00.000+01:00" },
7
+ { "value": 0.46, "start": "2025-11-07T01:00:00.000+01:00" },
8
+ { "value": 0.452, "start": "2025-11-07T01:15:00.000+01:00" },
9
+ { "value": 0.448, "start": "2025-11-07T01:30:00.000+01:00" },
10
+ { "value": 0.446, "start": "2025-11-07T01:45:00.000+01:00" },
11
+ { "value": 0.4451, "start": "2025-11-07T02:00:00.000+01:00" },
12
+ { "value": 0.446, "start": "2025-11-07T02:15:00.000+01:00" },
13
+ { "value": 0.4469, "start": "2025-11-07T02:30:00.000+01:00" },
14
+ { "value": 0.4499, "start": "2025-11-07T02:45:00.000+01:00" },
15
+ { "value": 0.4392, "start": "2025-11-07T03:00:00.000+01:00" },
16
+ { "value": 0.444, "start": "2025-11-07T03:15:00.000+01:00" },
17
+ { "value": 0.4469, "start": "2025-11-07T03:30:00.000+01:00" },
18
+ { "value": 0.4558, "start": "2025-11-07T03:45:00.000+01:00" },
19
+ { "value": 0.4451, "start": "2025-11-07T04:00:00.000+01:00" },
20
+ { "value": 0.4604, "start": "2025-11-07T04:15:00.000+01:00" },
21
+ { "value": 0.471, "start": "2025-11-07T04:30:00.000+01:00" },
22
+ { "value": 0.4841, "start": "2025-11-07T04:45:00.000+01:00" },
23
+ { "value": 0.4792, "start": "2025-11-07T05:00:00.000+01:00" },
24
+ { "value": 0.5111, "start": "2025-11-07T05:15:00.000+01:00" },
25
+ { "value": 0.5364, "start": "2025-11-07T05:30:00.000+01:00" },
26
+ { "value": 0.5405, "start": "2025-11-07T05:45:00.000+01:00" },
27
+ { "value": 0.5406, "start": "2025-11-07T06:00:00.000+01:00" },
28
+ { "value": 0.5449, "start": "2025-11-07T06:15:00.000+01:00" },
29
+ { "value": 0.627, "start": "2025-11-07T06:30:00.000+01:00" },
30
+ { "value": 0.9012, "start": "2025-11-07T06:45:00.000+01:00" },
31
+ { "value": 0.9481, "start": "2025-11-07T07:00:00.000+01:00" },
32
+ { "value": 1.0486, "start": "2025-11-07T07:15:00.000+01:00" },
33
+ { "value": 1.2117, "start": "2025-11-07T07:30:00.000+01:00" },
34
+ { "value": 1.5884, "start": "2025-11-07T07:45:00.000+01:00" },
35
+ { "value": 1.5013, "start": "2025-11-07T08:00:00.000+01:00" },
36
+ { "value": 1.7345, "start": "2025-11-07T08:15:00.000+01:00" },
37
+ { "value": 1.5688, "start": "2025-11-07T08:30:00.000+01:00" },
38
+ { "value": 1.2983, "start": "2025-11-07T08:45:00.000+01:00" },
39
+ { "value": 0.9651, "start": "2025-11-07T09:00:00.000+01:00" },
40
+ { "value": 0.9504, "start": "2025-11-07T09:15:00.000+01:00" },
41
+ { "value": 0.9516, "start": "2025-11-07T09:30:00.000+01:00" },
42
+ { "value": 0.9525, "start": "2025-11-07T09:45:00.000+01:00" },
43
+ { "value": 0.9551, "start": "2025-11-07T10:00:00.000+01:00" },
44
+ { "value": 0.9567, "start": "2025-11-07T10:15:00.000+01:00" },
45
+ { "value": 0.9584, "start": "2025-11-07T10:30:00.000+01:00" },
46
+ { "value": 0.96, "start": "2025-11-07T10:45:00.000+01:00" },
47
+ { "value": 0.9554, "start": "2025-11-07T11:00:00.000+01:00" },
48
+ { "value": 0.957, "start": "2025-11-07T11:15:00.000+01:00" },
49
+ { "value": 0.9577, "start": "2025-11-07T11:30:00.000+01:00" },
50
+ { "value": 0.9591, "start": "2025-11-07T11:45:00.000+01:00" },
51
+ { "value": 0.9514, "start": "2025-11-07T12:00:00.000+01:00" },
52
+ { "value": 0.9558, "start": "2025-11-07T12:15:00.000+01:00" },
53
+ { "value": 0.9572, "start": "2025-11-07T12:30:00.000+01:00" },
54
+ { "value": 0.9574, "start": "2025-11-07T12:45:00.000+01:00" },
55
+ { "value": 0.954, "start": "2025-11-07T13:00:00.000+01:00" },
56
+ { "value": 0.9545, "start": "2025-11-07T13:15:00.000+01:00" },
57
+ { "value": 0.9555, "start": "2025-11-07T13:30:00.000+01:00" },
58
+ { "value": 0.957, "start": "2025-11-07T13:45:00.000+01:00" },
59
+ { "value": 0.9567, "start": "2025-11-07T14:00:00.000+01:00" },
60
+ { "value": 0.9578, "start": "2025-11-07T14:15:00.000+01:00" },
61
+ { "value": 1.2984, "start": "2025-11-07T14:30:00.000+01:00" },
62
+ { "value": 1.7027, "start": "2025-11-07T14:45:00.000+01:00" },
63
+ { "value": 1.2913, "start": "2025-11-07T15:00:00.000+01:00" },
64
+ { "value": 1.4679, "start": "2025-11-07T15:15:00.000+01:00" },
65
+ { "value": 1.6917, "start": "2025-11-07T15:30:00.000+01:00" },
66
+ { "value": 2.032, "start": "2025-11-07T15:45:00.000+01:00" },
67
+ { "value": 1.6871, "start": "2025-11-07T16:00:00.000+01:00" },
68
+ { "value": 1.9684, "start": "2025-11-07T16:15:00.000+01:00" },
69
+ { "value": 2.0491, "start": "2025-11-07T16:30:00.000+01:00" },
70
+ { "value": 2.0605, "start": "2025-11-07T16:45:00.000+01:00" },
71
+ { "value": 1.9401, "start": "2025-11-07T17:00:00.000+01:00" },
72
+ { "value": 1.8698, "start": "2025-11-07T17:15:00.000+01:00" },
73
+ { "value": 1.604, "start": "2025-11-07T17:30:00.000+01:00" },
74
+ { "value": 1.3766, "start": "2025-11-07T17:45:00.000+01:00" },
75
+ { "value": 2.0411, "start": "2025-11-07T18:00:00.000+01:00" },
76
+ { "value": 1.8794, "start": "2025-11-07T18:15:00.000+01:00" },
77
+ { "value": 1.4522, "start": "2025-11-07T18:30:00.000+01:00" },
78
+ { "value": 1.2228, "start": "2025-11-07T18:45:00.000+01:00" },
79
+ { "value": 0.996, "start": "2025-11-07T19:00:00.000+01:00" },
80
+ { "value": 0.9816, "start": "2025-11-07T19:15:00.000+01:00" },
81
+ { "value": 0.872, "start": "2025-11-07T19:30:00.000+01:00" },
82
+ { "value": 0.7728, "start": "2025-11-07T19:45:00.000+01:00" },
83
+ { "value": 0.9714, "start": "2025-11-07T20:00:00.000+01:00" },
84
+ { "value": 0.8548, "start": "2025-11-07T20:15:00.000+01:00" },
85
+ { "value": 0.7729, "start": "2025-11-07T20:30:00.000+01:00" },
86
+ { "value": 0.641, "start": "2025-11-07T20:45:00.000+01:00" },
87
+ { "value": 0.8416, "start": "2025-11-07T21:00:00.000+01:00" },
88
+ { "value": 0.7728, "start": "2025-11-07T21:15:00.000+01:00" },
89
+ { "value": 0.6528, "start": "2025-11-07T21:30:00.000+01:00" },
90
+ { "value": 0.6256, "start": "2025-11-07T21:45:00.000+01:00" },
91
+ { "value": 0.6478, "start": "2025-11-07T22:00:00.000+01:00" },
92
+ { "value": 0.6292, "start": "2025-11-07T22:15:00.000+01:00" },
93
+ { "value": 0.6031, "start": "2025-11-07T22:30:00.000+01:00" },
94
+ { "value": 0.5945, "start": "2025-11-07T22:45:00.000+01:00" },
95
+ { "value": 0.6175, "start": "2025-11-07T23:00:00.000+01:00" },
96
+ { "value": 0.5984, "start": "2025-11-07T23:15:00.000+01:00" },
97
+ { "value": 0.576, "start": "2025-11-07T23:30:00.000+01:00" },
98
+ { "value": 0.563, "start": "2025-11-07T23:45:00.000+01:00" },
99
+ { "value": 0.5924, "start": "2025-11-08T00:00:00.000+01:00" },
100
+ { "value": 0.5696, "start": "2025-11-08T00:15:00.000+01:00" },
101
+ { "value": 0.5525, "start": "2025-11-08T00:30:00.000+01:00" },
102
+ { "value": 0.5456, "start": "2025-11-08T00:45:00.000+01:00" },
103
+ { "value": 0.5677, "start": "2025-11-08T01:00:00.000+01:00" },
104
+ { "value": 0.5513, "start": "2025-11-08T01:15:00.000+01:00" },
105
+ { "value": 0.5376, "start": "2025-11-08T01:30:00.000+01:00" },
106
+ { "value": 0.5362, "start": "2025-11-08T01:45:00.000+01:00" },
107
+ { "value": 0.5689, "start": "2025-11-08T02:00:00.000+01:00" },
108
+ { "value": 0.5622, "start": "2025-11-08T02:15:00.000+01:00" },
109
+ { "value": 0.5498, "start": "2025-11-08T02:30:00.000+01:00" },
110
+ { "value": 0.5387, "start": "2025-11-08T02:45:00.000+01:00" },
111
+ { "value": 0.562, "start": "2025-11-08T03:00:00.000+01:00" },
112
+ { "value": 0.5544, "start": "2025-11-08T03:15:00.000+01:00" },
113
+ { "value": 0.5542, "start": "2025-11-08T03:30:00.000+01:00" },
114
+ { "value": 0.5503, "start": "2025-11-08T03:45:00.000+01:00" },
115
+ { "value": 0.5522, "start": "2025-11-08T04:00:00.000+01:00" },
116
+ { "value": 0.5532, "start": "2025-11-08T04:15:00.000+01:00" },
117
+ { "value": 0.5535, "start": "2025-11-08T04:30:00.000+01:00" },
118
+ { "value": 0.5533, "start": "2025-11-08T04:45:00.000+01:00" },
119
+ { "value": 0.5549, "start": "2025-11-08T05:00:00.000+01:00" },
120
+ { "value": 0.558, "start": "2025-11-08T05:15:00.000+01:00" },
121
+ { "value": 0.5642, "start": "2025-11-08T05:30:00.000+01:00" },
122
+ { "value": 0.6096, "start": "2025-11-08T05:45:00.000+01:00" },
123
+ { "value": 0.5685, "start": "2025-11-08T06:00:00.000+01:00" },
124
+ { "value": 0.5644, "start": "2025-11-08T06:15:00.000+01:00" },
125
+ { "value": 0.6212, "start": "2025-11-08T06:30:00.000+01:00" },
126
+ { "value": 0.626, "start": "2025-11-08T06:45:00.000+01:00" },
127
+ { "value": 0.6303, "start": "2025-11-08T07:00:00.000+01:00" },
128
+ { "value": 0.6445, "start": "2025-11-08T07:15:00.000+01:00" },
129
+ { "value": 0.6787, "start": "2025-11-08T07:30:00.000+01:00" },
130
+ { "value": 0.6967, "start": "2025-11-08T07:45:00.000+01:00" },
131
+ { "value": 0.6776, "start": "2025-11-08T08:00:00.000+01:00" },
132
+ { "value": 0.6968, "start": "2025-11-08T08:15:00.000+01:00" },
133
+ { "value": 0.7764, "start": "2025-11-08T08:30:00.000+01:00" },
134
+ { "value": 0.8068, "start": "2025-11-08T08:45:00.000+01:00" },
135
+ { "value": 0.8184, "start": "2025-11-08T09:00:00.000+01:00" },
136
+ { "value": 0.8417, "start": "2025-11-08T09:15:00.000+01:00" },
137
+ { "value": 0.8584, "start": "2025-11-08T09:30:00.000+01:00" },
138
+ { "value": 0.9063, "start": "2025-11-08T09:45:00.000+01:00" },
139
+ { "value": 0.8719, "start": "2025-11-08T10:00:00.000+01:00" },
140
+ { "value": 0.874, "start": "2025-11-08T10:15:00.000+01:00" },
141
+ { "value": 0.8765, "start": "2025-11-08T10:30:00.000+01:00" },
142
+ { "value": 0.877, "start": "2025-11-08T10:45:00.000+01:00" },
143
+ { "value": 0.8902, "start": "2025-11-08T11:00:00.000+01:00" },
144
+ { "value": 0.8982, "start": "2025-11-08T11:15:00.000+01:00" },
145
+ { "value": 0.9025, "start": "2025-11-08T11:30:00.000+01:00" },
146
+ { "value": 0.8984, "start": "2025-11-08T11:45:00.000+01:00" },
147
+ { "value": 0.9157, "start": "2025-11-08T12:00:00.000+01:00" },
148
+ { "value": 0.9229, "start": "2025-11-08T12:15:00.000+01:00" },
149
+ { "value": 0.9233, "start": "2025-11-08T12:30:00.000+01:00" },
150
+ { "value": 0.924, "start": "2025-11-08T12:45:00.000+01:00" },
151
+ { "value": 0.9183, "start": "2025-11-08T13:00:00.000+01:00" },
152
+ { "value": 0.9194, "start": "2025-11-08T13:15:00.000+01:00" },
153
+ { "value": 0.9309, "start": "2025-11-08T13:30:00.000+01:00" },
154
+ { "value": 0.9635, "start": "2025-11-08T13:45:00.000+01:00" },
155
+ { "value": 1.0168, "start": "2025-11-08T14:00:00.000+01:00" },
156
+ { "value": 1.0784, "start": "2025-11-08T14:15:00.000+01:00" },
157
+ { "value": 1.2278, "start": "2025-11-08T14:30:00.000+01:00" },
158
+ { "value": 1.3086, "start": "2025-11-08T14:45:00.000+01:00" },
159
+ { "value": 0.9526, "start": "2025-11-08T15:00:00.000+01:00" },
160
+ { "value": 1.2403, "start": "2025-11-08T15:15:00.000+01:00" },
161
+ { "value": 1.419, "start": "2025-11-08T15:30:00.000+01:00" },
162
+ { "value": 1.8, "start": "2025-11-08T15:45:00.000+01:00" },
163
+ { "value": 1.4804, "start": "2025-11-08T16:00:00.000+01:00" },
164
+ { "value": 1.6303, "start": "2025-11-08T16:15:00.000+01:00" },
165
+ { "value": 1.704, "start": "2025-11-08T16:30:00.000+01:00" },
166
+ { "value": 1.9417, "start": "2025-11-08T16:45:00.000+01:00" },
167
+ { "value": 1.6725, "start": "2025-11-08T17:00:00.000+01:00" },
168
+ { "value": 1.8313, "start": "2025-11-08T17:15:00.000+01:00" },
169
+ { "value": 1.8342, "start": "2025-11-08T17:30:00.000+01:00" },
170
+ { "value": 1.8221, "start": "2025-11-08T17:45:00.000+01:00" },
171
+ { "value": 1.8094, "start": "2025-11-08T18:00:00.000+01:00" },
172
+ { "value": 1.708, "start": "2025-11-08T18:15:00.000+01:00" },
173
+ { "value": 1.367, "start": "2025-11-08T18:30:00.000+01:00" },
174
+ { "value": 1.1441, "start": "2025-11-08T18:45:00.000+01:00" },
175
+ { "value": 1.1921, "start": "2025-11-08T19:00:00.000+01:00" },
176
+ { "value": 1.0164, "start": "2025-11-08T19:15:00.000+01:00" },
177
+ { "value": 0.9805, "start": "2025-11-08T19:30:00.000+01:00" },
178
+ { "value": 0.8764, "start": "2025-11-08T19:45:00.000+01:00" },
179
+ { "value": 0.9077, "start": "2025-11-08T20:00:00.000+01:00" },
180
+ { "value": 0.8316, "start": "2025-11-08T20:15:00.000+01:00" },
181
+ { "value": 0.6575, "start": "2025-11-08T20:30:00.000+01:00" },
182
+ { "value": 0.5803, "start": "2025-11-08T20:45:00.000+01:00" },
183
+ { "value": 0.6191, "start": "2025-11-08T21:00:00.000+01:00" },
184
+ { "value": 0.6142, "start": "2025-11-08T21:15:00.000+01:00" },
185
+ { "value": 0.5656, "start": "2025-11-08T21:30:00.000+01:00" },
186
+ { "value": 0.5521, "start": "2025-11-08T21:45:00.000+01:00" },
187
+ { "value": 0.5593, "start": "2025-11-08T22:00:00.000+01:00" },
188
+ { "value": 0.5466, "start": "2025-11-08T22:15:00.000+01:00" },
189
+ { "value": 0.5369, "start": "2025-11-08T22:30:00.000+01:00" },
190
+ { "value": 0.5341, "start": "2025-11-08T22:45:00.000+01:00" },
191
+ { "value": 0.5377, "start": "2025-11-08T23:00:00.000+01:00" },
192
+ { "value": 0.5303, "start": "2025-11-08T23:15:00.000+01:00" },
193
+ { "value": 0.5271, "start": "2025-11-08T23:30:00.000+01:00" },
194
+ { "value": 0.5256, "start": "2025-11-08T23:45:00.000+01:00", "end": "2025-11-09T00:00:00.000+01:00" }
195
+ ],
196
+ "source": "Tibber",
197
+ "config": { "hoursOn": 10 }
198
+ }