node-red-contrib-power-saver 4.1.2 → 4.1.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.
- package/docs/.vuepress/components/AdsenseAdd.vue +6 -0
- package/docs/changelog/README.md +4 -0
- package/docs/contribute/README.md +1 -1
- package/docs/examples/example-grid-tariff-capacity-part.md +19 -2
- package/docs/guide/README.md +5 -1
- package/examples/example-grid-tariff-capacity-flow.json +3 -3
- package/package.json +1 -1
- package/src/handle-output.js +1 -1
- package/src/schedule-merger-functions.js +2 -2
- package/src/schedule-merger.js +1 -1
- package/src/strategy-functions.js +1 -1
- package/test/strategy-lowest-price-3days.test.js +31 -0
package/docs/changelog/README.md
CHANGED
|
@@ -49,7 +49,7 @@ Main developer: [Otto Paulsen](https://github.com/ottopaulsen)
|
|
|
49
49
|
|
|
50
50
|
Heat Capacitor developer: [Arne Klaveness](https://github.com/TomTorger)
|
|
51
51
|
|
|
52
|
-
Example contributors: [Stefan](https://github.com/oakhill87/node-red-contrib-power-saver)
|
|
52
|
+
Example contributors: [Stefan](https://github.com/oakhill87/node-red-contrib-power-saver), [Kim Storøy](https://www.facebook.com/kim.storoy)
|
|
53
53
|
|
|
54
54
|
###
|
|
55
55
|
|
|
@@ -451,7 +451,10 @@ context.set("buffer", buffer);
|
|
|
451
451
|
|
|
452
452
|
// Calculate buffer
|
|
453
453
|
const periodMs = buffer[buffer.length - 1].timeMs - buffer[0].timeMs;
|
|
454
|
-
|
|
454
|
+
let consumptionInPeriod = buffer[buffer.length - 1].accumulatedConsumption - buffer[0].accumulatedConsumption;
|
|
455
|
+
if (consumptionInPeriod < 0) {
|
|
456
|
+
consumptionInPeriod = 0;
|
|
457
|
+
}
|
|
455
458
|
if (periodMs === 0) {
|
|
456
459
|
return; // First item in buffer
|
|
457
460
|
}
|
|
@@ -895,6 +898,8 @@ If you don't want the actions, or you want to control actions another way,
|
|
|
895
898
|
you can omit the action-related nodes and only use the nodes creating the sensors.
|
|
896
899
|
:::
|
|
897
900
|
|
|
901
|
+
The `MIN_MINUTES_INTO_HOUR_TO_TAKE_ACTION` constant in the `On Message` code sets a period (of default 5 minutes) in the beginning of the hour when no reduction action is taken. This is to avoid that a high consumption at the end of the previous hour causes reduction actions to be taken as soon as the hour changes.
|
|
902
|
+
|
|
898
903
|
::: details Code
|
|
899
904
|
|
|
900
905
|
<CodeGroup>
|
|
@@ -1008,6 +1013,7 @@ flow.set("actions", actions);
|
|
|
1008
1013
|
|
|
1009
1014
|
```js
|
|
1010
1015
|
const MIN_CONSUMPTION_TO_CARE = 0.05; // Do not reduce unless at least 50W
|
|
1016
|
+
const MIN_MINUTES_INTO_HOUR_TO_TAKE_ACTION = 5;
|
|
1011
1017
|
|
|
1012
1018
|
const actions = flow.get("actions");
|
|
1013
1019
|
const ha = global.get("homeassistant").homeAssistant;
|
|
@@ -1015,10 +1021,21 @@ const ha = global.get("homeassistant").homeAssistant;
|
|
|
1015
1021
|
let reductionRequired = msg.payload.reductionRequired;
|
|
1016
1022
|
let reductionRecommended = msg.payload.reductionRecommended;
|
|
1017
1023
|
|
|
1024
|
+
node.status({});
|
|
1025
|
+
|
|
1018
1026
|
if (reductionRecommended <= 0) {
|
|
1019
1027
|
return null;
|
|
1020
1028
|
}
|
|
1021
1029
|
|
|
1030
|
+
if (3600 - msg.payload.timeLeftSec < MIN_MINUTES_INTO_HOUR_TO_TAKE_ACTION * 60) {
|
|
1031
|
+
node.status({
|
|
1032
|
+
fill: "yellow",
|
|
1033
|
+
shape: "ring",
|
|
1034
|
+
text: "No action during first " + MIN_MINUTES_INTO_HOUR_TO_TAKE_ACTION + " minutes",
|
|
1035
|
+
});
|
|
1036
|
+
return;
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1022
1039
|
function takeAction(action, consumption) {
|
|
1023
1040
|
const info = {
|
|
1024
1041
|
time: new Date().toISOString(),
|
|
@@ -1105,7 +1122,7 @@ function resetAction(action) {
|
|
|
1105
1122
|
const info = {
|
|
1106
1123
|
time: new Date().toISOString(),
|
|
1107
1124
|
name: "Reset action",
|
|
1108
|
-
data: msg.
|
|
1125
|
+
data: msg.payload,
|
|
1109
1126
|
action,
|
|
1110
1127
|
};
|
|
1111
1128
|
const output1 = action.payloadToResetAction ? { payload: action.payloadToResetAction } : null;
|
package/docs/guide/README.md
CHANGED
|
@@ -239,4 +239,8 @@ You may directly replace the `Power Saver` node by two of the new nodes (`ps-rec
|
|
|
239
239
|
|
|
240
240
|
See more details in the [documentation for the `ps-strategy-best-save`](../nodes/ps-strategy-best-save.md) node.
|
|
241
241
|
|
|
242
|
-
|
|
242
|
+
## Disclaimer
|
|
243
|
+
|
|
244
|
+
This software is offered for free as open source. You use it totally on your own risk. The developers take no responsibility of any consequences caused by use or misuse of this software.
|
|
245
|
+
|
|
246
|
+
It is not recommended to reduce the temperature of the water heater or similar over longer periods, due to the risk of legionella. Please read the recommendations of [FHI](https://www.fhi.no/sv/smittsomme-sykdommer/legionella/) about this. You do this at your own risk.
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
"type": "function",
|
|
83
83
|
"z": "d938c47f.3398f8",
|
|
84
84
|
"name": "Collect estimate for hour",
|
|
85
|
-
"func": "\n// Number of minutes used to calculate assumed consumption:\nconst ESTIMATION_TIME_MINUTES = 1\n\nconst buffer = context.get(\"buffer\") || []\n\n// Add new record to buffer\nconst time = new Date(msg.payload.timestamp)\nconst timeMs = time.getTime()\nconst accumulatedConsumption = msg.payload.accumulatedConsumption\nconst accumulatedConsumptionLastHour = msg.payload.accumulatedConsumptionLastHour\nbuffer.push({timeMs, accumulatedConsumption})\n\nconst currentHour = new Date(msg.payload.timestamp)\ncurrentHour.setMinutes(0)\ncurrentHour.setSeconds(0)\n\n// Remove too old records from buffer\nconst maxAgeMs = ESTIMATION_TIME_MINUTES * 60 * 1000\nlet oldest = buffer[0]\nwhile ((timeMs - oldest.timeMs) > maxAgeMs) {\n buffer.splice(0, 1)\n oldest = buffer[0]\n}\ncontext.set(\"buffer\", buffer)\n\n// Calculate buffer\nconst periodMs = buffer[buffer.length - 1].timeMs - buffer[0].timeMs\
|
|
85
|
+
"func": "\n// Number of minutes used to calculate assumed consumption:\nconst ESTIMATION_TIME_MINUTES = 1\n\nconst buffer = context.get(\"buffer\") || []\n\n// Add new record to buffer\nconst time = new Date(msg.payload.timestamp)\nconst timeMs = time.getTime()\nconst accumulatedConsumption = msg.payload.accumulatedConsumption\nconst accumulatedConsumptionLastHour = msg.payload.accumulatedConsumptionLastHour\nbuffer.push({timeMs, accumulatedConsumption})\n\nconst currentHour = new Date(msg.payload.timestamp)\ncurrentHour.setMinutes(0)\ncurrentHour.setSeconds(0)\n\n// Remove too old records from buffer\nconst maxAgeMs = ESTIMATION_TIME_MINUTES * 60 * 1000\nlet oldest = buffer[0]\nwhile ((timeMs - oldest.timeMs) > maxAgeMs) {\n buffer.splice(0, 1)\n oldest = buffer[0]\n}\ncontext.set(\"buffer\", buffer)\n\n// Calculate buffer\nconst periodMs = buffer[buffer.length - 1].timeMs - buffer[0].timeMs\nlet consumptionInPeriod = buffer[buffer.length - 1].accumulatedConsumption - buffer[0].accumulatedConsumption\nif (consumptionInPeriod < 0) {\nconsumptionInPeriod = 0\n}\nif (periodMs === 0) {\n return // First item in buffer\n}\n\n// Estimate remaining of current hour\nconst timeLeftMs = (60 * 60 * 1000) - (time.getMinutes() * 60000 + time.getSeconds() * 1000 + time.getMilliseconds())\nconst consumptionLeft = consumptionInPeriod / periodMs * timeLeftMs\nconst averageConsumptionNow = consumptionInPeriod / periodMs * 60 * 60 * 1000\n\n// Estimate total hour\nconst hourEstimate = accumulatedConsumptionLastHour + consumptionLeft + 0 // Change for testing\n\nmsg.payload = {\n accumulatedConsumption,\n accumulatedConsumptionLastHour,\n periodMs,\n consumptionInPeriod,\n averageConsumptionNow,\n timeLeftMs,\n consumptionLeft,\n hourEstimate,\n currentHour\n}\n\nreturn msg;",
|
|
86
86
|
"outputs": 1,
|
|
87
87
|
"noerr": 0,
|
|
88
88
|
"initialize": "// Code added here will be run once\n// whenever the node is started.\ncontext.set(\"buffer\", [])",
|
|
@@ -112,7 +112,7 @@
|
|
|
112
112
|
"type": "function",
|
|
113
113
|
"z": "d938c47f.3398f8",
|
|
114
114
|
"name": "Reduction Actions",
|
|
115
|
-
"func": "const MIN_CONSUMPTION_TO_CARE = 0.05 // Do not reduce unless at least 50W\n\nconst actions = flow.get(\"actions\")\nconst ha = global.get(\"homeassistant\").homeAssistant\n\nlet reductionRequired = msg.payload.reductionRequired\nlet reductionRecommended = msg.payload.reductionRecommended\n\nif(reductionRecommended <= 0) {\n return null\n}\n\nfunction takeAction(action, consumption ) {\n const info = {\n time: new Date().toISOString(),\n name: \"Reduction action\",\n data: msg.payload,\n action\n }\n\n // output1 is for actions\n const output1 = action.payloadToTakeAction ? { payload: action.payloadToTakeAction } : null\n // output 2 is for overriding PS strategies\n const output2 = action.nameOfStrategyToOverride ? { payload: { config: { override: \"off\" }, name: action.nameOfStrategyToOverride} } : null\n // output 3 is for logging\n const output3 = { payload: info }\n\n node.send([output1, output2, output3])\n reductionRequired = Math.max(0, reductionRequired - consumption)\n reductionRecommended = Math.max(0, reductionRecommended - consumption)\n action.actionTaken = true\n action.actionTime = Date.now()\n action.savedConsumption = consumption\n flow.set(\"actions\", actions)\n}\n\nfunction getConsumption(consumption) {\n if(typeof consumption === \"string\") {\n const sensor = ha.states[consumption]\n return sensor.state / 1000\n } else if (typeof consumption === \"number\") {\n return consumption\n } else if(typeof consumption === \"function\") {\n return consumption()\n } else {\n node.warn(\"Config error: consumption has illegal type: \" + typeof consumption)\n return 0\n }\n}\n\nactions\n.filter(a => msg.payload.alarmLevel >= a.minAlarmLevel && !a.actionTaken)\n.forEach(a => {\n const consumption = getConsumption(a.consumption)\n if (consumption < MIN_CONSUMPTION_TO_CARE) {\n return\n }\n if (reductionRequired > 0 || (reductionRecommended > 0 && a.reduceWhenRecommended)) {\n takeAction(a, consumption)\n }\n})\n \n",
|
|
115
|
+
"func": "const MIN_CONSUMPTION_TO_CARE = 0.05 // Do not reduce unless at least 50W\nconst MIN_MINUTES_INTO_HOUR_TO_TAKE_ACTION = 5\n\nconst actions = flow.get(\"actions\")\nconst ha = global.get(\"homeassistant\").homeAssistant\n\nlet reductionRequired = msg.payload.reductionRequired\nlet reductionRecommended = msg.payload.reductionRecommended\n\nnode.status({})\n\nif(reductionRecommended <= 0 ) {\n return null\n}\n\nif (3600 - msg.payload.timeLeftSec < MIN_MINUTES_INTO_HOUR_TO_TAKE_ACTION * 60) {\n node.status({ fill: \"yellow\", shape: \"ring\", text: \"No action during first \" + MIN_MINUTES_INTO_HOUR_TO_TAKE_ACTION + \" minutes\"});\n return\n}\n\nfunction takeAction(action, consumption ) {\n const info = {\n time: new Date().toISOString(),\n name: \"Reduction action\",\n data: msg.payload,\n action\n }\n\n // output1 is for actions\n const output1 = action.payloadToTakeAction ? { payload: action.payloadToTakeAction } : null\n // output 2 is for overriding PS strategies\n const output2 = action.nameOfStrategyToOverride ? { payload: { config: { override: \"off\" }, name: action.nameOfStrategyToOverride} } : null\n // output 3 is for logging\n const output3 = { payload: info }\n\n node.send([output1, output2, output3])\n reductionRequired = Math.max(0, reductionRequired - consumption)\n reductionRecommended = Math.max(0, reductionRecommended - consumption)\n action.actionTaken = true\n action.actionTime = Date.now()\n action.savedConsumption = consumption\n flow.set(\"actions\", actions)\n}\n\nfunction getConsumption(consumption) {\n if(typeof consumption === \"string\") {\n const sensor = ha.states[consumption]\n return sensor.state / 1000\n } else if (typeof consumption === \"number\") {\n return consumption\n } else if(typeof consumption === \"function\") {\n return consumption()\n } else {\n node.warn(\"Config error: consumption has illegal type: \" + typeof consumption)\n return 0\n }\n}\n\nactions\n.filter(a => msg.payload.alarmLevel >= a.minAlarmLevel && !a.actionTaken)\n.forEach(a => {\n const consumption = getConsumption(a.consumption)\n if (consumption < MIN_CONSUMPTION_TO_CARE) {\n return\n }\n if (reductionRequired > 0 || (reductionRecommended > 0 && a.reduceWhenRecommended)) {\n takeAction(a, consumption)\n }\n})\n \n",
|
|
116
116
|
"outputs": 3,
|
|
117
117
|
"noerr": 0,
|
|
118
118
|
"initialize": "// You MUST edit the actions array with your own actions.\n\nconst actions = [\n { \n consumption: \"sensor.varmtvannsbereder_electric_consumption_w\",\n name: \"Varmtvannsbereder\",\n id: \"vvb\",\n minAlarmLevel: 3,\n reduceWhenRecommended: true,\n minTimeOffSec: 300,\n nameOfStrategyToOverride: \"Best Save\",\n },\n { \n consumption: \"sensor.varme_gulv_bad_electric_consumption_w_2\",\n name: \"Varme gulv bad 1. etg.\",\n id: \"gulvbad\",\n minAlarmLevel: 3,\n reduceWhenRecommended: true,\n minTimeOffSec: 300,\n payloadToTakeAction: {\n domain: \"climate\",\n service: \"turn_off\",\n target: {\n entity_id: [\"climate.varme_gulv_bad_2\"]\n }\n },\n payloadToResetAction: {\n domain: \"climate\",\n service: \"turn_on\",\n target: {\n entity_id: [\"climate.varme_gulv_bad_2\"]\n }\n }\n },\n { \n consumption: \"sensor.varme_gulv_gang_electric_consumption_w\",\n name: \"Varme gulv gang 1. etg.\",\n id: \"gulvgang\",\n minAlarmLevel: 3,\n reduceWhenRecommended: true,\n minTimeOffSec: 300,\n payloadToTakeAction: {\n domain: \"climate\",\n service: \"turn_off\",\n target: {\n entity_id: [\"climate.varme_gulv_gang\"]\n }\n },\n payloadToResetAction: {\n domain: \"climate\",\n service: \"turn_on\",\n target: {\n entity_id: [\"climate.varme_gulv_gang\"]\n }\n }\n },\n {\n consumption: \"sensor.varme_gulv_kjellerstue_electric_consumption_w\",\n name: \"Varme gulv kjellerstue\",\n id: \"gulvkjeller\",\n minAlarmLevel: 3,\n reduceWhenRecommended: true,\n minTimeOffSec: 300,\n payloadToTakeAction: {\n domain: \"climate\",\n service: \"turn_off\",\n target: {\n entity_id: [\"climate.varme_gulv_kjellerstue\"]\n }\n },\n payloadToResetAction: {\n domain: \"climate\",\n service: \"turn_on\",\n target: {\n entity_id: [\"climate.varme_gulv_kjellerstue\"]\n }\n }\n }\n]\n// End of actions array\n\n// DO NOT DELETE THE CODE BELOW\n\n// Set default values for all actions\nactions.forEach(a => {\n a.actionTaken = false\n a.savedConsumption = 0\n})\n\nflow.set(\"actions\", actions)\n",
|
|
@@ -127,7 +127,7 @@
|
|
|
127
127
|
"type": "function",
|
|
128
128
|
"z": "d938c47f.3398f8",
|
|
129
129
|
"name": "Reset Actions",
|
|
130
|
-
"func": "\nconst actions = flow.get(\"actions\")\nconst ha = global.get(\"homeassistant\").homeAssistant\n\nconst BUFFER_TO_RESET = 1 // Must have 1kW extra to perform reset\n\nlet increasePossible = msg.payload.increasePossible\n\nif (increasePossible <= 0) {\n return null\n}\n\nfunction resetAction(action) {\n const info = {\n time: new Date().toISOString(),\n name: \"Reset action\",\n data: msg.
|
|
130
|
+
"func": "\nconst actions = flow.get(\"actions\")\nconst ha = global.get(\"homeassistant\").homeAssistant\n\nconst BUFFER_TO_RESET = 1 // Must have 1kW extra to perform reset\n\nlet increasePossible = msg.payload.increasePossible\n\nif (increasePossible <= 0) {\n return null\n}\n\nfunction resetAction(action) {\n const info = {\n time: new Date().toISOString(),\n name: \"Reset action\",\n data: msg.payload,\n action\n }\n const output1 = action.payloadToResetAction ? { payload: action.payloadToResetAction } : null\n const output2 = action.nameOfStrategyToOverride ? { payload: { config: { override: \"auto\" }, name: action.nameOfStrategyToOverride } } : null\n const output3 = { payload: info }\n\n node.send([output1, output2, output3])\n increasePossible -= action.savedConsumption\n action.actionTaken = false\n action.savedConsumption = 0\n flow.set(\"actions\", actions)\n}\n\nactions\n .filter(a => a.actionTaken\n && (a.savedConsumption + BUFFER_TO_RESET) <= increasePossible\n && (Date.now() - a.actionTime > a.minTimeOffSec * 1000)\n ).forEach(a => resetAction(a))\n",
|
|
131
131
|
"outputs": 3,
|
|
132
132
|
"noerr": 0,
|
|
133
133
|
"initialize": "",
|
package/package.json
CHANGED
package/src/handle-output.js
CHANGED
|
@@ -62,7 +62,7 @@ function sendSwitch(node, onOff) {
|
|
|
62
62
|
const output1 = onOff ? { payload: node.outputValueForOn } : null;
|
|
63
63
|
const output2 = onOff ? null : { payload: node.outputValueForOff };
|
|
64
64
|
node.send([output1, output2, null]);
|
|
65
|
-
node.context().set("currentOutput", onOff);
|
|
65
|
+
node.context().set("currentOutput", onOff, node.contextStorage);
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
function runSchedule(node, schedule, time, currentSent = false) {
|
|
@@ -11,7 +11,7 @@ function validateSchedule(msg) {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
function saveSchedule(node, msg) {
|
|
14
|
-
let savedSchedules = node.context().get("savedSchedules") || {};
|
|
14
|
+
let savedSchedules = node.context().get("savedSchedules", node.contextStorage) || {};
|
|
15
15
|
|
|
16
16
|
// If the saved schedule has a different start period, delete them
|
|
17
17
|
const ids = Object.keys(savedSchedules);
|
|
@@ -35,7 +35,7 @@ function saveSchedule(node, msg) {
|
|
|
35
35
|
function mergeSchedules(node, logicFunction) {
|
|
36
36
|
// Transpose all schedules
|
|
37
37
|
const transposed = {};
|
|
38
|
-
const savedSchedules = node.context().get("savedSchedules");
|
|
38
|
+
const savedSchedules = node.context().get("savedSchedules", node.contextStorage);
|
|
39
39
|
if (!savedSchedules) {
|
|
40
40
|
const msg = "No schedules";
|
|
41
41
|
node.warn(msg);
|
package/src/schedule-merger.js
CHANGED
|
@@ -87,7 +87,7 @@ module.exports = function (RED) {
|
|
|
87
87
|
};
|
|
88
88
|
|
|
89
89
|
const planFromTime = msg.payload.time ? DateTime.fromISO(msg.payload.time) : DateTime.now();
|
|
90
|
-
const currentOutput = node.context().get("currentOutput");
|
|
90
|
+
const currentOutput = node.context().get("currentOutput", node.contextStorage);
|
|
91
91
|
const plannedOutputNow = getOutputForTime(plan.schedule, planFromTime, node.outputIfNoSchedule);
|
|
92
92
|
|
|
93
93
|
const outputCommands = {
|
|
@@ -12,7 +12,7 @@ function strategyOnInput(node, msg, doPlanning, calcSavings) {
|
|
|
12
12
|
const { plan, commands } = handleStrategyInput(node, msg, config, doPlanning, calcSavings);
|
|
13
13
|
if (plan) {
|
|
14
14
|
const planFromTime = msg.payload.time ? DateTime.fromISO(msg.payload.time) : DateTime.now();
|
|
15
|
-
const currentOutput = node.context().get("currentOutput");
|
|
15
|
+
const currentOutput = node.context().get("currentOutput", node.contextStorage);
|
|
16
16
|
const plannedOutputNow =
|
|
17
17
|
node.override === "auto"
|
|
18
18
|
? getOutputForTime(plan.schedule, planFromTime, node.outputIfNoSchedule)
|
|
@@ -54,6 +54,37 @@ describe("ps-strategy-lowest-price with data day before", function () {
|
|
|
54
54
|
}, 100);
|
|
55
55
|
});
|
|
56
56
|
});
|
|
57
|
+
|
|
58
|
+
it("should handle new price data after midnight", function (done) {
|
|
59
|
+
const flow = makeFlow(1);
|
|
60
|
+
const pricesDay1 = cloneDeep(prices);
|
|
61
|
+
const pricesDay2 = cloneDeep(prices);
|
|
62
|
+
const res = cloneDeep(result);
|
|
63
|
+
res.schedule.splice(3, 2);
|
|
64
|
+
res.hours.splice(48, 24);
|
|
65
|
+
res.schedule[2].countHours = 19;
|
|
66
|
+
pricesDay1.priceData.splice(48, 24);
|
|
67
|
+
pricesDay2.priceData.splice(48, 24);
|
|
68
|
+
pricesDay2.priceData.splice(0, 24);
|
|
69
|
+
helper.load(lowestPrice, flow, function () {
|
|
70
|
+
const n1 = helper.getNode("n1");
|
|
71
|
+
const n2 = helper.getNode("n2");
|
|
72
|
+
let count = 0;
|
|
73
|
+
n2.on("input", function (msg) {
|
|
74
|
+
if (count === 1) {
|
|
75
|
+
expect(msg.payload).toHaveProperty("schedule", res.schedule);
|
|
76
|
+
done();
|
|
77
|
+
}
|
|
78
|
+
count++;
|
|
79
|
+
});
|
|
80
|
+
let time = DateTime.fromISO(pricesDay1.priceData[10].start);
|
|
81
|
+
n1.receive({ payload: makePayload(pricesDay1, time) });
|
|
82
|
+
setTimeout(() => {
|
|
83
|
+
time = DateTime.fromISO(pricesDay2.priceData[1].start);
|
|
84
|
+
n1.receive({ payload: makePayload(pricesDay2, time) });
|
|
85
|
+
}, 100);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
57
88
|
});
|
|
58
89
|
|
|
59
90
|
function makeFlow(hoursOn) {
|