node-red-contrib-power-saver 1.0.7 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/README.md +4 -0
- package/mostSavedStrategy.js +55 -39
- package/mostSavedStrategy_v2.js +68 -0
- package/package.json +7 -2
- package/power-saver.html +0 -12
- package/power-saver.js +37 -46
- package/test/data/result.js +169 -0
- package/test/data/tibber_data.json +412 -0
- package/test/data/tibber_prices.json +412 -0
- package/test/data/tibber_result.json +357 -0
- package/test/mostSavedStrategy.test.js +12 -25
- package/test/power-saver.test.js +130 -32
- package/test/utils.test.js +107 -0
- package/utils.js +25 -2
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Change Log
|
|
2
|
+
|
|
3
|
+
List the most significant changes, starting in version 1.0.9.
|
|
4
|
+
|
|
5
|
+
## 2.0.1
|
|
6
|
+
|
|
7
|
+
* Fix bug that caused no schedule
|
|
8
|
+
* Add config to output
|
|
9
|
+
|
|
10
|
+
## 2.0.0
|
|
11
|
+
|
|
12
|
+
* New and better algorithm to calculates savings, resulting in a better schedule.
|
|
13
|
+
* Removed possibility to configure maximum hours to save per day, as this does not really make much sense.
|
|
14
|
+
* Round savings to 4 decimals.
|
|
15
|
+
* Set last savings hour to null when 0.
|
|
16
|
+
|
|
17
|
+
## 1.0.9
|
|
18
|
+
|
|
19
|
+
* Fix bug in saving last hour of the day.
|
|
20
|
+
|
|
21
|
+
|
package/README.md
CHANGED
|
@@ -155,3 +155,7 @@ Are you using [MagicMirror](https://magicmirror.builders/)? Are you also using [
|
|
|
155
155
|
The purple lines show savings.
|
|
156
156
|
|
|
157
157
|
Read more about this in the [MMM-Tibber documentation](https://github.com/ottopaulsen/MMM-Tibber#show-savings).
|
|
158
|
+
|
|
159
|
+
## Change Log
|
|
160
|
+
|
|
161
|
+
[Change Log](CHANGELOG.md)
|
package/mostSavedStrategy.js
CHANGED
|
@@ -1,24 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
isOnOffSequencesOk,
|
|
5
|
-
fillArray,
|
|
6
|
-
} = require("./utils");
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { isOnOffSequencesOk, fillArray } = require("./utils");
|
|
7
4
|
/**
|
|
8
5
|
* Turn off the hours where you save most compared to the next hour on.
|
|
9
6
|
*
|
|
10
|
-
* Algorithm:
|
|
11
|
-
* 1. For each hour, find out how much is saved if turned off.
|
|
12
|
-
* Compare price with the net hour that is on.
|
|
13
|
-
* 2. Turn off the hour that saves the most.
|
|
14
|
-
* 3. Validate. Keep if valid. If not, turn off the next best. And so on.
|
|
15
|
-
* When validating, include hours from day before, but do not fail on day before.
|
|
16
|
-
* 4. If something was turned off, repeat from 1.
|
|
17
|
-
*
|
|
18
7
|
* @param {*} values Array of prices
|
|
19
|
-
* @param {*} maxOffCount Max number of hours that can be saved in total
|
|
20
8
|
* @param {*} maxOffInARow Max number of hours that can be saved in a row
|
|
21
9
|
* @param {*} minOnAfterMaxOffInARow Min number of hours that must be on after maxOffInARow is saved
|
|
10
|
+
* @param {*} minSaving Minimum amount that must be saved in order to turn off
|
|
22
11
|
* @param {*} lastValueDayBefore Value of the last hour the day before
|
|
23
12
|
* @param {*} lastCountDayBefore Number of lastValueDayBefore in a row
|
|
24
13
|
* @returns Array with same number of values as in values array, where true is on, false is off
|
|
@@ -26,7 +15,6 @@ const {
|
|
|
26
15
|
module.exports = {
|
|
27
16
|
calculate: function (
|
|
28
17
|
values,
|
|
29
|
-
maxOffCount,
|
|
30
18
|
maxOffInARow,
|
|
31
19
|
minOnAfterMaxOffInARow,
|
|
32
20
|
minSaving,
|
|
@@ -34,35 +22,63 @@ module.exports = {
|
|
|
34
22
|
lastCountDayBefore = 0
|
|
35
23
|
) {
|
|
36
24
|
const dayBefore = fillArray(lastValueDayBefore, lastCountDayBefore);
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
25
|
+
const last = values.length - 1;
|
|
26
|
+
|
|
27
|
+
// Create matrix with saving per hour
|
|
28
|
+
const savingPerHour = [];
|
|
29
|
+
for (let hour = 0; hour < last; hour++) {
|
|
30
|
+
const row = [];
|
|
31
|
+
for (let count = 1; count <= maxOffInARow; count++) {
|
|
32
|
+
const on = hour + count;
|
|
33
|
+
const saving = values[hour] - values[on >= last ? last : on];
|
|
34
|
+
row.push(saving);
|
|
35
|
+
}
|
|
36
|
+
savingPerHour.push(row);
|
|
41
37
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
38
|
+
|
|
39
|
+
// Create list with summary saving per sequence
|
|
40
|
+
let savingsList = [];
|
|
41
|
+
for (let hour = 0; hour < last; hour++) {
|
|
42
|
+
for (let count = 1; count <= maxOffInARow; count++) {
|
|
43
|
+
let saving = 0;
|
|
44
|
+
for (let offset = 0; offset < count && hour + offset < last; offset++) {
|
|
45
|
+
saving += savingPerHour[hour + offset][count - offset - 1];
|
|
46
|
+
}
|
|
51
47
|
if (
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
minOnAfterMaxOffInARow
|
|
56
|
-
)
|
|
48
|
+
saving > minSaving * count &&
|
|
49
|
+
hour + count <= last &&
|
|
50
|
+
values[hour] > values[hour + count] + minSaving
|
|
57
51
|
) {
|
|
58
|
-
|
|
59
|
-
} else {
|
|
60
|
-
onOff[sorted[tryToTurnOffIndex]] = true;
|
|
61
|
-
tryToTurnOffIndex++;
|
|
52
|
+
savingsList.push({ hour, count, saving });
|
|
62
53
|
}
|
|
63
54
|
}
|
|
64
|
-
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
savingsList.sort((a, b) => b.saving - a.saving);
|
|
58
|
+
let onOff = values.map((v) => true); // Start with all on
|
|
65
59
|
|
|
60
|
+
// Find the best possible sequences
|
|
61
|
+
while (savingsList.length > 0) {
|
|
62
|
+
const { hour, count } = savingsList[0];
|
|
63
|
+
const onOffCopy = [...onOff];
|
|
64
|
+
for (let c = 0; c < count; c++) {
|
|
65
|
+
onOff[hour + c] = false;
|
|
66
|
+
}
|
|
67
|
+
if (
|
|
68
|
+
isOnOffSequencesOk(
|
|
69
|
+
[...dayBefore, ...onOff],
|
|
70
|
+
maxOffInARow,
|
|
71
|
+
minOnAfterMaxOffInARow
|
|
72
|
+
)
|
|
73
|
+
) {
|
|
74
|
+
savingsList = savingsList.filter(
|
|
75
|
+
(s) => s.hour < hour || s.hour >= hour + count
|
|
76
|
+
);
|
|
77
|
+
} else {
|
|
78
|
+
onOff = [...onOffCopy];
|
|
79
|
+
savingsList.splice(0, 1);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
66
82
|
return onOff;
|
|
67
83
|
},
|
|
68
84
|
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
const {
|
|
2
|
+
sortedIndex,
|
|
3
|
+
getDiffToNextOn,
|
|
4
|
+
isOnOffSequencesOk,
|
|
5
|
+
fillArray,
|
|
6
|
+
} = require("./utils");
|
|
7
|
+
/**
|
|
8
|
+
* Turn off the hours where you save most compared to the next hour on.
|
|
9
|
+
*
|
|
10
|
+
* Algorithm:
|
|
11
|
+
* 1. For each hour, find out how much is saved if turned off.
|
|
12
|
+
* Compare price with the net hour that is on.
|
|
13
|
+
* 2. Turn off the hour that saves the most.
|
|
14
|
+
* 3. Validate. Keep if valid. If not, turn off the next best. And so on.
|
|
15
|
+
* When validating, include hours from day before, but do not fail on day before.
|
|
16
|
+
* 4. If something was turned off, repeat from 1.
|
|
17
|
+
*
|
|
18
|
+
* @param {*} values Array of prices
|
|
19
|
+
* @param {*} maxOffInARow Max number of hours that can be saved in a row
|
|
20
|
+
* @param {*} minOnAfterMaxOffInARow Min number of hours that must be on after maxOffInARow is saved
|
|
21
|
+
* @param {*} minSaving Minimum amount that must be saved in order to turn off
|
|
22
|
+
* @param {*} lastValueDayBefore Value of the last hour the day before
|
|
23
|
+
* @param {*} lastCountDayBefore Number of lastValueDayBefore in a row
|
|
24
|
+
* @returns Array with same number of values as in values array, where true is on, false is off
|
|
25
|
+
*/
|
|
26
|
+
module.exports = {
|
|
27
|
+
calculate: function (
|
|
28
|
+
values,
|
|
29
|
+
maxOffInARow,
|
|
30
|
+
minOnAfterMaxOffInARow,
|
|
31
|
+
minSaving,
|
|
32
|
+
lastValueDayBefore = undefined,
|
|
33
|
+
lastCountDayBefore = 0
|
|
34
|
+
) {
|
|
35
|
+
const dayBefore = fillArray(lastValueDayBefore, lastCountDayBefore);
|
|
36
|
+
let foundImprovement;
|
|
37
|
+
const onOff = values.map((v) => true); // Start with all on
|
|
38
|
+
do {
|
|
39
|
+
foundImprovement = false;
|
|
40
|
+
const diffToNextOn = getDiffToNextOn(
|
|
41
|
+
values,
|
|
42
|
+
onOff,
|
|
43
|
+
values[values.length - 1]
|
|
44
|
+
);
|
|
45
|
+
const sorted = sortedIndex(diffToNextOn).filter(
|
|
46
|
+
(v) => onOff[v] && diffToNextOn[v] >= minSaving
|
|
47
|
+
);
|
|
48
|
+
let tryToTurnOffIndex = 0;
|
|
49
|
+
while (tryToTurnOffIndex < sorted.length && !foundImprovement) {
|
|
50
|
+
onOff[sorted[tryToTurnOffIndex]] = false;
|
|
51
|
+
if (
|
|
52
|
+
isOnOffSequencesOk(
|
|
53
|
+
[...dayBefore, ...onOff],
|
|
54
|
+
maxOffInARow,
|
|
55
|
+
minOnAfterMaxOffInARow
|
|
56
|
+
)
|
|
57
|
+
) {
|
|
58
|
+
foundImprovement = true;
|
|
59
|
+
} else {
|
|
60
|
+
onOff[sorted[tryToTurnOffIndex]] = true;
|
|
61
|
+
tryToTurnOffIndex++;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
} while (foundImprovement);
|
|
65
|
+
|
|
66
|
+
return onOff;
|
|
67
|
+
},
|
|
68
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-red-contrib-power-saver",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
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": {
|
|
@@ -9,7 +9,12 @@
|
|
|
9
9
|
"author": "Otto Paulsen <ottpau@gmail.com>",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"keywords": [
|
|
12
|
-
"node-red"
|
|
12
|
+
"node-red",
|
|
13
|
+
"tibber",
|
|
14
|
+
"energy",
|
|
15
|
+
"smarthome",
|
|
16
|
+
"home-automation",
|
|
17
|
+
"power"
|
|
13
18
|
],
|
|
14
19
|
"node-red": {
|
|
15
20
|
"nodes": {
|
package/power-saver.html
CHANGED
|
@@ -4,11 +4,6 @@
|
|
|
4
4
|
color: "#a6bbcf",
|
|
5
5
|
defaults: {
|
|
6
6
|
name: { value: "Power Saver" },
|
|
7
|
-
maxHoursToSavePerDay: {
|
|
8
|
-
value: 12,
|
|
9
|
-
required: true,
|
|
10
|
-
validate: RED.validators.number(),
|
|
11
|
-
},
|
|
12
7
|
maxHoursToSaveInSequence: {
|
|
13
8
|
value: 3,
|
|
14
9
|
required: true,
|
|
@@ -47,10 +42,6 @@
|
|
|
47
42
|
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
48
43
|
<input type="text" id="node-input-name" placeholder="Name">
|
|
49
44
|
</div>
|
|
50
|
-
<div class="form-row">
|
|
51
|
-
<label for="node-input-maxHoursToSavePerDay"><i class="fa fa-align-justify"></i> Max per day</label>
|
|
52
|
-
<input type="text" id="node-input-maxHoursToSavePerDay" placeholder="Max hours to save in a day">
|
|
53
|
-
</div>
|
|
54
45
|
<div class="form-row">
|
|
55
46
|
<label for="node-input-maxHoursToSaveInSequence"><i class="fa fa-arrows-h"></i> Max per sequence</label>
|
|
56
47
|
<input type="text" id="node-input-maxHoursToSaveInSequence" placeholder="Max hours to save in sequence">
|
|
@@ -80,9 +71,6 @@
|
|
|
80
71
|
|
|
81
72
|
|
|
82
73
|
<div>
|
|
83
|
-
<p>
|
|
84
|
-
<strong>Max per day</strong> is the maximum number of hours that can be saved (turned off) during a day.
|
|
85
|
-
</p>
|
|
86
74
|
<p>
|
|
87
75
|
<strong>Max per sequence</strong> is the maximum number of hours that can be saved (turned off) in a sequence.
|
|
88
76
|
</p>
|
package/power-saver.js
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
const { DateTime } = require("luxon");
|
|
2
|
-
const {
|
|
2
|
+
const {
|
|
3
|
+
convertMsg,
|
|
4
|
+
countAtEnd,
|
|
5
|
+
makeSchedule,
|
|
6
|
+
getSavings,
|
|
7
|
+
extractPlanForDate,
|
|
8
|
+
} = require("./utils");
|
|
3
9
|
const mostSavedStrategy = require("./mostSavedStrategy");
|
|
4
10
|
|
|
5
11
|
let schedulingTimeout = null;
|
|
@@ -10,11 +16,10 @@ module.exports = function (RED) {
|
|
|
10
16
|
const node = this;
|
|
11
17
|
|
|
12
18
|
// Save config in node
|
|
13
|
-
this.maxHoursToSavePerDay = config.maxHoursToSavePerDay;
|
|
14
19
|
this.maxHoursToSaveInSequence = config.maxHoursToSaveInSequence;
|
|
15
20
|
this.minHoursOnAfterMaxSequenceSaved =
|
|
16
21
|
config.minHoursOnAfterMaxSequenceSaved;
|
|
17
|
-
this.minSaving = config.minSaving;
|
|
22
|
+
this.minSaving = parseFloat(config.minSaving);
|
|
18
23
|
this.sendCurrentValueWhenRescheduling =
|
|
19
24
|
config.sendCurrentValueWhenRescheduling;
|
|
20
25
|
this.outputIfNoSchedule = config.outputIfNoSchedule === "true";
|
|
@@ -33,55 +38,43 @@ module.exports = function (RED) {
|
|
|
33
38
|
return;
|
|
34
39
|
}
|
|
35
40
|
|
|
36
|
-
const
|
|
37
|
-
const tomorrow = input.tomorrow;
|
|
38
|
-
const source = input.source;
|
|
41
|
+
const priceData = [...input.today, ...input.tomorrow];
|
|
39
42
|
|
|
40
43
|
clearTimeout(schedulingTimeout);
|
|
41
44
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const tomorrowDate = todaysDate.plus({ days: 1 });
|
|
45
|
+
const dates = [
|
|
46
|
+
...new Set(priceData.map((v) => DateTime.fromISO(v.start).toISODate())),
|
|
47
|
+
];
|
|
46
48
|
|
|
47
|
-
// Load data from
|
|
48
|
-
const
|
|
49
|
+
// Load data from day before
|
|
50
|
+
const dateDayBefore = DateTime.fromISO(dates[0]).plus({ days: -1 });
|
|
51
|
+
const dataDayBefore = loadDayData(node, dateDayBefore);
|
|
49
52
|
|
|
50
53
|
// Make plan
|
|
51
|
-
const
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
-
const startTimesTomorrow = tomorrow.map((d) => d.start);
|
|
55
|
-
|
|
56
|
-
const planToday = makePlan(
|
|
57
|
-
node,
|
|
58
|
-
valuesToday,
|
|
59
|
-
startTimesToday,
|
|
60
|
-
dataYesterday.onOff
|
|
61
|
-
);
|
|
62
|
-
const planTomorrow = makePlan(
|
|
63
|
-
node,
|
|
64
|
-
valuesTomorrow,
|
|
65
|
-
startTimesTomorrow,
|
|
66
|
-
planToday.onOff
|
|
67
|
-
);
|
|
54
|
+
const values = priceData.map((d) => d.value);
|
|
55
|
+
const startTimes = priceData.map((d) => d.start);
|
|
56
|
+
const plan = makePlan(node, values, startTimes, dataDayBefore.onOff);
|
|
68
57
|
|
|
69
58
|
// Save schedule
|
|
70
|
-
saveDayData(node,
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
59
|
+
dates.forEach((d) => saveDayData(node, d, extractPlanForDate(plan, d)));
|
|
60
|
+
|
|
61
|
+
const config = {
|
|
62
|
+
maxHoursToSaveInSequence: this.maxHoursToSaveInSequence,
|
|
63
|
+
minHoursOnAfterMaxSequenceSaved: this.minHoursOnAfterMaxSequenceSaved,
|
|
64
|
+
minSaving: this.minSaving,
|
|
65
|
+
sendCurrentValueWhenRescheduling: this.sendCurrentValueWhenRescheduling,
|
|
66
|
+
outputIfNoSchedule: this.outputIfNoSchedule,
|
|
67
|
+
};
|
|
76
68
|
|
|
77
69
|
// Prepare output
|
|
78
70
|
let output1 = null;
|
|
79
71
|
let output2 = null;
|
|
80
72
|
let output3 = {
|
|
81
73
|
payload: {
|
|
82
|
-
schedule,
|
|
83
|
-
hours,
|
|
84
|
-
source,
|
|
74
|
+
schedule: plan.schedule,
|
|
75
|
+
hours: plan.hours,
|
|
76
|
+
source: input.source,
|
|
77
|
+
config,
|
|
85
78
|
},
|
|
86
79
|
};
|
|
87
80
|
|
|
@@ -89,7 +82,7 @@ module.exports = function (RED) {
|
|
|
89
82
|
const time = msg.payload.time
|
|
90
83
|
? DateTime.fromISO(msg.payload.time)
|
|
91
84
|
: DateTime.now();
|
|
92
|
-
const pastSchedule = schedule.filter(
|
|
85
|
+
const pastSchedule = plan.schedule.filter(
|
|
93
86
|
(entry) => DateTime.fromISO(entry.time) <= time
|
|
94
87
|
);
|
|
95
88
|
const outputCurrent = node.sendCurrentValueWhenRescheduling;
|
|
@@ -101,13 +94,13 @@ module.exports = function (RED) {
|
|
|
101
94
|
}
|
|
102
95
|
|
|
103
96
|
// Delete old data
|
|
104
|
-
deleteSavedScheduleBefore(node,
|
|
97
|
+
deleteSavedScheduleBefore(node, dateDayBefore);
|
|
105
98
|
|
|
106
99
|
// Send output
|
|
107
100
|
node.send([output1, output2, output3]);
|
|
108
101
|
|
|
109
102
|
// Run schedule
|
|
110
|
-
schedulingTimeout = runSchedule(node, schedule, time);
|
|
103
|
+
schedulingTimeout = runSchedule(node, plan.schedule, time);
|
|
111
104
|
});
|
|
112
105
|
}
|
|
113
106
|
|
|
@@ -130,8 +123,7 @@ function loadDayData(node, date) {
|
|
|
130
123
|
}
|
|
131
124
|
|
|
132
125
|
function saveDayData(node, date, plan) {
|
|
133
|
-
|
|
134
|
-
node.context().set(key, plan);
|
|
126
|
+
node.context().set(date, plan);
|
|
135
127
|
}
|
|
136
128
|
|
|
137
129
|
function deleteSavedScheduleBefore(node, day) {
|
|
@@ -142,7 +134,7 @@ function deleteSavedScheduleBefore(node, day) {
|
|
|
142
134
|
} while (data);
|
|
143
135
|
}
|
|
144
136
|
|
|
145
|
-
function makePlan(node, values, startTimes, onOffBefore) {
|
|
137
|
+
function makePlan(node, values, startTimes, onOffBefore, firstValueNextDay) {
|
|
146
138
|
const strategy = "mostSaved"; // TODO: Get from node settings
|
|
147
139
|
const lastValueDayBefore = onOffBefore[onOffBefore.length - 1];
|
|
148
140
|
const lastCountDayBefore = countAtEnd(onOffBefore, lastValueDayBefore);
|
|
@@ -150,7 +142,6 @@ function makePlan(node, values, startTimes, onOffBefore) {
|
|
|
150
142
|
strategy === "mostSaved"
|
|
151
143
|
? mostSavedStrategy.calculate(
|
|
152
144
|
values,
|
|
153
|
-
node.maxHoursToSavePerDay,
|
|
154
145
|
node.maxHoursToSaveInSequence,
|
|
155
146
|
node.minHoursOnAfterMaxSequenceSaved,
|
|
156
147
|
node.minSaving,
|
|
@@ -160,7 +151,7 @@ function makePlan(node, values, startTimes, onOffBefore) {
|
|
|
160
151
|
: [];
|
|
161
152
|
|
|
162
153
|
const schedule = makeSchedule(onOff, startTimes, lastValueDayBefore);
|
|
163
|
-
const savings = getSavings(values, onOff);
|
|
154
|
+
const savings = getSavings(values, onOff, firstValueNextDay);
|
|
164
155
|
const hours = values.map((v, i) => ({
|
|
165
156
|
price: v,
|
|
166
157
|
onOff: onOff[i],
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
schedule: [
|
|
3
|
+
{
|
|
4
|
+
time: "2021-06-20T01:50:00.000+02:00",
|
|
5
|
+
value: true,
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
time: "2021-06-20T01:50:00.030+02:00",
|
|
9
|
+
value: false,
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
time: "2021-06-20T01:50:00.050+02:00",
|
|
13
|
+
value: true,
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
time: "2021-06-20T01:50:00.060+02:00",
|
|
17
|
+
value: false,
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
time: "2021-06-20T01:50:00.080+02:00",
|
|
21
|
+
value: true,
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
time: "2021-06-20T01:50:00.090+02:00",
|
|
25
|
+
value: false,
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
time: "2021-06-20T01:50:00.120+02:00",
|
|
29
|
+
value: true,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
time: "2021-06-20T01:50:00.150+02:00",
|
|
33
|
+
value: false,
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
time: "2021-06-20T01:50:00.180+02:00",
|
|
37
|
+
value: true,
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
hours: [
|
|
41
|
+
{
|
|
42
|
+
price: 0.3,
|
|
43
|
+
onOff: true,
|
|
44
|
+
start: "2021-06-20T01:50:00.000+02:00",
|
|
45
|
+
saving: null,
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
price: 0.4,
|
|
49
|
+
onOff: true,
|
|
50
|
+
start: "2021-06-20T01:50:00.010+02:00",
|
|
51
|
+
saving: null,
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
price: 0.8,
|
|
55
|
+
onOff: true,
|
|
56
|
+
start: "2021-06-20T01:50:00.020+02:00",
|
|
57
|
+
saving: null,
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
price: 0.9,
|
|
61
|
+
onOff: false,
|
|
62
|
+
start: "2021-06-20T01:50:00.030+02:00",
|
|
63
|
+
saving: 0.3,
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
price: 0.7,
|
|
67
|
+
onOff: false,
|
|
68
|
+
start: "2021-06-20T01:50:00.040+02:00",
|
|
69
|
+
saving: 0.1,
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
price: 0.6,
|
|
73
|
+
onOff: true,
|
|
74
|
+
start: "2021-06-20T01:50:00.050+02:00",
|
|
75
|
+
saving: null,
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
price: 0.5,
|
|
79
|
+
onOff: false,
|
|
80
|
+
start: "2021-06-20T01:50:00.060+02:00",
|
|
81
|
+
saving: 0.3,
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
price: 0.75,
|
|
85
|
+
onOff: false,
|
|
86
|
+
start: "2021-06-20T01:50:00.070+02:00",
|
|
87
|
+
saving: 0.55,
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
price: 0.2,
|
|
91
|
+
onOff: true,
|
|
92
|
+
start: "2021-06-20T01:50:00.080+02:00",
|
|
93
|
+
saving: null,
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
price: 0.85,
|
|
97
|
+
onOff: false,
|
|
98
|
+
start: "2021-06-20T01:50:00.090+02:00",
|
|
99
|
+
saving: 0.05,
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
price: 1.5,
|
|
103
|
+
onOff: false,
|
|
104
|
+
start: "2021-06-20T01:50:00.100+02:00",
|
|
105
|
+
saving: 0.7,
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
price: 1.4,
|
|
109
|
+
onOff: false,
|
|
110
|
+
start: "2021-06-20T01:50:00.110+02:00",
|
|
111
|
+
saving: 0.6,
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
price: 0.8,
|
|
115
|
+
onOff: true,
|
|
116
|
+
start: "2021-06-20T01:50:00.120+02:00",
|
|
117
|
+
saving: null,
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
price: 0.9,
|
|
121
|
+
onOff: true,
|
|
122
|
+
start: "2021-06-20T01:50:00.130+02:00",
|
|
123
|
+
saving: null,
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
price: 0.7,
|
|
127
|
+
onOff: true,
|
|
128
|
+
start: "2021-06-20T01:50:00.140+02:00",
|
|
129
|
+
saving: null,
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
price: 0.6,
|
|
133
|
+
onOff: false,
|
|
134
|
+
start: "2021-06-20T01:50:00.150+02:00",
|
|
135
|
+
saving: 0.4,
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
price: 0.5,
|
|
139
|
+
onOff: false,
|
|
140
|
+
start: "2021-06-20T01:50:00.160+02:00",
|
|
141
|
+
saving: 0.3,
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
price: 0.75,
|
|
145
|
+
onOff: false,
|
|
146
|
+
start: "2021-06-20T01:50:00.170+02:00",
|
|
147
|
+
saving: 0.55,
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
price: 0.2,
|
|
151
|
+
onOff: true,
|
|
152
|
+
start: "2021-06-20T01:50:00.180+02:00",
|
|
153
|
+
saving: null,
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
price: 0.85,
|
|
157
|
+
onOff: true,
|
|
158
|
+
start: "2021-06-20T01:50:00.190+02:00",
|
|
159
|
+
saving: null,
|
|
160
|
+
},
|
|
161
|
+
],
|
|
162
|
+
source: "Other",
|
|
163
|
+
config: {
|
|
164
|
+
maxHoursToSaveInSequence: 3,
|
|
165
|
+
minHoursOnAfterMaxSequenceSaved: 2,
|
|
166
|
+
minSaving: 0.001,
|
|
167
|
+
outputIfNoSchedule: false,
|
|
168
|
+
},
|
|
169
|
+
};
|