node-red-contrib-power-saver 3.0.6 → 3.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/docs/.vuepress/config.js +5 -1
  2. package/docs/.vuepress/dist/404.html +3 -3
  3. package/docs/.vuepress/dist/assets/img/best-save-config.93fa3c21.png +0 -0
  4. package/docs/.vuepress/dist/assets/img/lowest-price-config.6d66a8c2.png +0 -0
  5. package/docs/.vuepress/dist/assets/js/app.3cfedce6.js +1 -0
  6. package/docs/.vuepress/dist/assets/js/{runtime~app.0d53f24f.js → runtime~app.f1d7fab8.js} +1 -1
  7. package/docs/.vuepress/dist/assets/js/v-0b5e3c8c.163b80fb.js +1 -0
  8. package/docs/.vuepress/dist/assets/js/v-4637f9e4.60300b77.js +1 -0
  9. package/docs/.vuepress/dist/assets/js/v-510ed0d4.b76b84de.js +1 -0
  10. package/docs/.vuepress/dist/assets/js/v-5954bcb2.9e6d2df1.js +1 -0
  11. package/docs/.vuepress/dist/assets/js/v-61f728ca.64fa763c.js +1 -0
  12. package/docs/.vuepress/dist/assets/js/v-677dfaed.b84f09f5.js +1 -0
  13. package/docs/.vuepress/dist/assets/js/{v-7c87f26e.457a1a60.js → v-7c87f26e.91c245da.js} +1 -1
  14. package/docs/.vuepress/dist/assets/js/v-8daa1a0e.66c9dbce.js +1 -0
  15. package/docs/.vuepress/dist/assets/js/v-b4a42144.d1856a24.js +1 -0
  16. package/docs/.vuepress/dist/assets/js/v-fffb8e28.9e0579a1.js +1 -0
  17. package/docs/.vuepress/dist/changelog/index.html +4 -4
  18. package/docs/.vuepress/dist/contribute/index.html +4 -4
  19. package/docs/.vuepress/dist/examples/example-nordpool-current-state.html +176 -156
  20. package/docs/.vuepress/dist/examples/example-nordpool-events-state.html +3 -3
  21. package/docs/.vuepress/dist/examples/example-tibber-mqtt.html +3 -3
  22. package/docs/.vuepress/dist/examples/index.html +3 -3
  23. package/docs/.vuepress/dist/guide/index.html +5 -5
  24. package/docs/.vuepress/dist/index.html +4 -4
  25. package/docs/.vuepress/dist/logo.png +0 -0
  26. package/docs/.vuepress/dist/nodes/index.html +3 -3
  27. package/docs/.vuepress/dist/nodes/old-power-saver-doc.html +6 -6
  28. package/docs/.vuepress/dist/nodes/power-saver.html +3 -3
  29. package/docs/.vuepress/dist/nodes/ps-elvia-add-tariff.html +3 -3
  30. package/docs/.vuepress/dist/nodes/ps-receive-price.html +6 -6
  31. package/docs/.vuepress/dist/nodes/ps-strategy-best-save.html +5 -5
  32. package/docs/.vuepress/dist/nodes/ps-strategy-lowest-price.html +5 -5
  33. package/docs/.vuepress/dist/nodes/strategy-input.html +5 -5
  34. package/docs/.vuepress/public/logo.png +0 -0
  35. package/docs/README.md +5 -3
  36. package/docs/changelog/README.md +22 -5
  37. package/docs/contribute/README.md +1 -1
  38. package/docs/examples/example-nordpool-current-state.md +172 -152
  39. package/docs/guide/README.md +12 -10
  40. package/docs/images/logo copy.png +0 -0
  41. package/docs/images/logo.png +0 -0
  42. package/docs/images/logo.psd +0 -0
  43. package/docs/images/node-red-icon-2.svg +30 -0
  44. package/docs/nodes/old-power-saver-doc.md +6 -6
  45. package/docs/nodes/ps-receive-price.md +9 -5
  46. package/docs/nodes/ps-strategy-best-save.md +5 -1
  47. package/docs/nodes/ps-strategy-lowest-price.md +3 -3
  48. package/docs/nodes/strategy-input.md +1 -1
  49. package/package.json +1 -1
  50. package/src/elvia/elvia-add-tariff.js +7 -6
  51. package/src/handle-input.js +1 -7
  52. package/src/power-saver.js +1 -5
  53. package/src/receive-price-functions.js +9 -17
  54. package/src/strategy-lowest-price.js +2 -1
  55. package/src/utils.js +6 -0
  56. package/test/data/lowest-price-input-missing-end.json +197 -0
  57. package/test/data/lowest-price-result-missing-end.json +319 -0
  58. package/test/data/nordpool-events-state-2.json +288 -0
  59. package/test/data/nordpool-prices-in-payload.json +287 -0
  60. package/test/elvia.test.js +16 -2
  61. package/test/receive-price.test.js +27 -4
  62. package/test/strategy-lowest-price.test.js +33 -0
  63. package/docs/.vuepress/dist/assets/js/app.342dc054.js +0 -1
  64. package/docs/.vuepress/dist/assets/js/v-0b5e3c8c.d008d8bc.js +0 -1
  65. package/docs/.vuepress/dist/assets/js/v-4637f9e4.22ab9413.js +0 -1
  66. package/docs/.vuepress/dist/assets/js/v-510ed0d4.129ef915.js +0 -1
  67. package/docs/.vuepress/dist/assets/js/v-5954bcb2.be07962c.js +0 -1
  68. package/docs/.vuepress/dist/assets/js/v-61f728ca.802ab15e.js +0 -1
  69. package/docs/.vuepress/dist/assets/js/v-677dfaed.9bbbd037.js +0 -1
  70. package/docs/.vuepress/dist/assets/js/v-8daa1a0e.db8b59c6.js +0 -1
  71. package/docs/.vuepress/dist/assets/js/v-b4a42144.6e0c5aa0.js +0 -1
  72. package/docs/.vuepress/dist/assets/js/v-fffb8e28.e815e852.js +0 -1
@@ -12,7 +12,7 @@ The picture at the bottom of the page, under [Integration with MagicMirror](#int
12
12
 
13
13
  ## Configuration
14
14
 
15
- ![Best Save Config](/best-save-config.png)
15
+ ![Best Save Config](../images/best-save-config.png)
16
16
 
17
17
  | Value | Description |
18
18
  | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
@@ -27,6 +27,10 @@ The picture at the bottom of the page, under [Integration with MagicMirror](#int
27
27
  NB! The `Min recover` only has effect if the previous save-period is of length `Max per sequence`. If the save-period is shorter, the following on-period may be as short as one hour.
28
28
  :::
29
29
 
30
+ ::: tip Legionella
31
+ Many people ask if there is a danger that legionella bacteria will grow and become dangerous when the temperature of the water heater is lowered. As long as the water is heated to at least 65 °C every day, or at least every week, the risk of infection is not considered significant, according to the norwegian [FHI](https://www.fhi.no/nettpub/legionellaveilederen/).
32
+ :::
33
+
30
34
  ### Dynamic config
31
35
 
32
36
  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:
@@ -10,11 +10,11 @@ Strategy node to turn on power the hours when the price is lowest during a given
10
10
 
11
11
  ## Description
12
12
 
13
- The node can work on a specific period from 1 to 24 hours during a 24 hour period. Inside this period, you can decide how many hours that shall be on. The rest of the period will be off. Outside the peiod, you can select that the output shall be either on or off. You can also decide that the hours on shall be consequtive (one continuous period) or spread around in multiple on-periods.
13
+ The node can work on a specific period from 1 to 24 hours during a 24 hour period. Inside this period, you can decide how many hours that shall be on. The rest of the period will be off. Outside the period, you can select that the output shall be either on or off. You can also decide that the hours on shall be consecutive (one continuous period) or spread around in multiple on-periods.
14
14
 
15
15
  ## Configuration
16
16
 
17
- ![Node Configuration](/lowest-price-config.png)
17
+ ![Node Configuration](../images/lowest-price-config.png)
18
18
 
19
19
  | Value | Description |
20
20
  | ---------------------- | -------------------------------------------------------------------------------- |
@@ -26,7 +26,7 @@ The node can work on a specific period from 1 to 24 hours during a 24 hour perio
26
26
  | If No Schedule, Send | What to do if there is no valid schedule any more (turn on or off). |
27
27
  | Outside Period, Send | Select the value to send outside the selected period. |
28
28
 
29
- 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 signficant in the way that it decides which 24 hours that are considered when finding the hours with lowest price.
29
+ 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.
30
30
 
31
31
  ::: tip Example with Consecutive On-Period
32
32
  One example to need a consecutive on-period can be if you want to control the washing machine. Let's say it needs 3 hours, and you want it to run between 22:00 and 06:00. Set `From Time = 22:00`, `To Time = 06:00` and check the `Consecutive On-Period` flag. This will turn on the cheapest 3-hour period from 22:00 to 06:00.
@@ -1,6 +1,6 @@
1
1
  # Strategy input format
2
2
 
3
- The common input for strategy nodes is a payload with a `priceData` array containing an object for each hour. Each object has a `value` wich is the price, and a `start` wich is the start time for the hour.
3
+ The common input for strategy nodes is a payload with a `priceData` array containing an object for each hour. Each object has a `value` which is the price, and a `start` which is the start time for the hour.
4
4
 
5
5
  Example:
6
6
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-power-saver",
3
- "version": "3.0.6",
3
+ "version": "3.0.10",
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": {
@@ -6,25 +6,26 @@ module.exports = function (RED) {
6
6
  function PsElviaAddTariffNode(config) {
7
7
  RED.nodes.createNode(this, config);
8
8
  this.elviaConfig = RED.nodes.getNode(config.elviaConfig);
9
+ const key = this.elviaConfig.credentials.elviaSubscriptionKey;
9
10
  this.tariffKey = config.tariffKey;
10
11
  this.range = config.range;
11
12
  const node = this;
12
-
13
- const configList = node.context().global.get("elviaConfigList") || [];
14
- const key = configList.find((c) => c.id == node.elviaConfig.id)?.elviaSubscriptionKey;
15
13
  ping(node, key);
16
14
 
17
15
  node.on("input", function (msg) {
18
16
  const prices = msg.payload.priceData;
17
+ if (!prices) {
18
+ node.warn(
19
+ "No price data received on input. Did you use the ps-receive-price node or convert to correct format otherwise?"
20
+ );
21
+ return;
22
+ }
19
23
  const fromTime = prices[0].start.substr(0, 19);
20
24
  const toTime = DateTime.fromISO(prices[prices.length - 1].start)
21
25
  .plus({ hours: 1 })
22
26
  .toISO()
23
27
  .substr(0, 19);
24
28
 
25
- const configList = node.context().global.get("elviaConfigList") || [];
26
- const key = configList.find((c) => c.id == node.elviaConfig.id)?.elviaSubscriptionKey;
27
-
28
29
  getTariffForPeriod(node, key, node.tariffKey, fromTime, toTime).then((json) => {
29
30
  const tariff = json;
30
31
  const priceInfo = tariff.gridTariff.tariffPrice.priceInfo;
@@ -1,5 +1,4 @@
1
- const { getEffectiveConfig } = require("./utils");
2
- const { extractPlanForDate } = require("./utils");
1
+ const { extractPlanForDate, getEffectiveConfig, validationFailure } = require("./utils");
3
2
  const { DateTime } = require("luxon");
4
3
 
5
4
  function handleStrategyInput(node, msg, doPlanning) {
@@ -152,11 +151,6 @@ function validateInput(node, msg) {
152
151
  return true;
153
152
  }
154
153
 
155
- function validationFailure(node, message, status = null) {
156
- node.status({ fill: "red", shape: "ring", text: status ?? message });
157
- node.warn(message);
158
- }
159
-
160
154
  module.exports = {
161
155
  handleStrategyInput,
162
156
  };
@@ -8,6 +8,7 @@ const {
8
8
  getStartAtIndex,
9
9
  loadDayData,
10
10
  makeSchedule,
11
+ validationFailure,
11
12
  } = require("./utils");
12
13
  const { convertMsg } = require("./receive-price-functions");
13
14
  const { calculate } = require("./strategy-best-save-functions");
@@ -189,11 +190,6 @@ function makePlan(node, values, startTimes, onOffBefore, firstValueNextDay) {
189
190
  };
190
191
  }
191
192
 
192
- function validationFailure(node, message) {
193
- node.status({ fill: "red", shape: "ring", text: message });
194
- node.warn(message);
195
- }
196
-
197
193
  function validateMsg(node, msg) {
198
194
  if (!msg.payload && !msg.data?.new_state?.attributes) {
199
195
  validationFailure(node, "Payload missing");
@@ -6,9 +6,6 @@ function getPriceData(node, msg) {
6
6
  return node.context().get("lastPriceData");
7
7
  }
8
8
 
9
- if (!validateMsg(node, msg)) {
10
- return null;
11
- }
12
9
  const input = convertMsg(msg);
13
10
  if (!validateInput(node, input)) {
14
11
  return null;
@@ -20,19 +17,6 @@ function getPriceData(node, msg) {
20
17
  return { priceData, source };
21
18
  }
22
19
 
23
- function validateMsg(node, msg) {
24
- if (!msg.payload && !msg.data?.new_state?.attributes) {
25
- validationFailure(node, "Payload missing");
26
- return false;
27
- }
28
- const payload = msg.data?.new_state?.attributes ?? msg.data?.attributes ?? msg.payload;
29
- if (typeof payload !== "object") {
30
- validationFailure(node, "Payload must be an object");
31
- return false;
32
- }
33
- return true;
34
- }
35
-
36
20
  function validateInput(node, input) {
37
21
  ["today", "tomorrow"].forEach((arr) => {
38
22
  if (
@@ -80,7 +64,15 @@ function convertMsg(msg) {
80
64
  value: v.value,
81
65
  start: v.start,
82
66
  }));
83
- } else if (msg.payload?.attributes && msg.payload?.attributes["raw_" + day]) {
67
+ } else if (msg.data?.attributes && msg.data?.attributes["raw_" + day]) {
68
+ result.source = "Nordpool";
69
+ result[day] = msg.data.attributes["raw_" + day]
70
+ .filter((v) => v.value)
71
+ .map((v) => ({
72
+ value: v.value,
73
+ start: v.start,
74
+ }));
75
+ } else if (msg.payload?.attributes && msg.payload.attributes["raw_" + day]) {
84
76
  result.source = "Nordpool";
85
77
  result[day] = msg.payload.attributes["raw_" + day]
86
78
  .filter((v) => v.value)
@@ -48,7 +48,7 @@ function doPlanning(node, _, priceData, _, dateDayBefore, _) {
48
48
  if (hour === to && to === from && currentStatus === "Inside") {
49
49
  endIndexes.push(i - 1);
50
50
  }
51
- if (hour === to && to !== from && i > 0) {
51
+ if (hour === to && to !== from && i > 0 && currentStatus !== "StartMissing") {
52
52
  currentStatus = "Outside";
53
53
  endIndexes.push(i - 1);
54
54
  }
@@ -66,6 +66,7 @@ function doPlanning(node, _, priceData, _, dateDayBefore, _) {
66
66
  hour = DateTime.fromISO(priceData[i].start).hour;
67
67
  i--;
68
68
  } while (periodStatus[i] === "Inside" && hour !== from);
69
+ startIndexes.splice(startIndexes.length - 1, 1);
69
70
  }
70
71
  if (hour === (to === 0 ? 23 : to - 1)) {
71
72
  endIndexes.push(priceData.length - 1);
package/src/utils.js CHANGED
@@ -175,6 +175,11 @@ function getStartAtIndex(effectiveConfig, priceData, time) {
175
175
  }
176
176
  }
177
177
 
178
+ function validationFailure(node, message, status = null) {
179
+ node.status({ fill: "red", shape: "ring", text: status ?? message });
180
+ node.warn(message);
181
+ }
182
+
178
183
  module.exports = {
179
184
  booleanConfig,
180
185
  countAtEnd,
@@ -191,4 +196,5 @@ module.exports = {
191
196
  makeSchedule,
192
197
  roundPrice,
193
198
  sortedIndex,
199
+ validationFailure,
194
200
  };
@@ -0,0 +1,197 @@
1
+ {
2
+ "priceData": [
3
+ {
4
+ "value": 1.588,
5
+ "start": "2021-12-15T00:00:00+01:00"
6
+ },
7
+ {
8
+ "value": 1.521,
9
+ "start": "2021-12-15T01:00:00+01:00"
10
+ },
11
+ {
12
+ "value": 1.492,
13
+ "start": "2021-12-15T02:00:00+01:00"
14
+ },
15
+ {
16
+ "value": 1.487,
17
+ "start": "2021-12-15T03:00:00+01:00"
18
+ },
19
+ {
20
+ "value": 1.497,
21
+ "start": "2021-12-15T04:00:00+01:00"
22
+ },
23
+ {
24
+ "value": 1.514,
25
+ "start": "2021-12-15T05:00:00+01:00"
26
+ },
27
+ {
28
+ "value": 1.583,
29
+ "start": "2021-12-15T06:00:00+01:00"
30
+ },
31
+ {
32
+ "value": 1.667,
33
+ "start": "2021-12-15T07:00:00+01:00"
34
+ },
35
+ {
36
+ "value": 1.729,
37
+ "start": "2021-12-15T08:00:00+01:00"
38
+ },
39
+ {
40
+ "value": 1.724,
41
+ "start": "2021-12-15T09:00:00+01:00"
42
+ },
43
+ {
44
+ "value": 1.707,
45
+ "start": "2021-12-15T10:00:00+01:00"
46
+ },
47
+ {
48
+ "value": 1.703,
49
+ "start": "2021-12-15T11:00:00+01:00"
50
+ },
51
+ {
52
+ "value": 1.692,
53
+ "start": "2021-12-15T12:00:00+01:00"
54
+ },
55
+ {
56
+ "value": 1.692,
57
+ "start": "2021-12-15T13:00:00+01:00"
58
+ },
59
+ {
60
+ "value": 1.692,
61
+ "start": "2021-12-15T14:00:00+01:00"
62
+ },
63
+ {
64
+ "value": 1.717,
65
+ "start": "2021-12-15T15:00:00+01:00"
66
+ },
67
+ {
68
+ "value": 1.72,
69
+ "start": "2021-12-15T16:00:00+01:00"
70
+ },
71
+ {
72
+ "value": 1.718,
73
+ "start": "2021-12-15T17:00:00+01:00"
74
+ },
75
+ {
76
+ "value": 1.679,
77
+ "start": "2021-12-15T18:00:00+01:00"
78
+ },
79
+ {
80
+ "value": 1.661,
81
+ "start": "2021-12-15T19:00:00+01:00"
82
+ },
83
+ {
84
+ "value": 1.629,
85
+ "start": "2021-12-15T20:00:00+01:00"
86
+ },
87
+ {
88
+ "value": 1.652,
89
+ "start": "2021-12-15T21:00:00+01:00"
90
+ },
91
+ {
92
+ "value": 1.612,
93
+ "start": "2021-12-15T22:00:00+01:00"
94
+ },
95
+ {
96
+ "value": 1.587,
97
+ "start": "2021-12-15T23:00:00+01:00"
98
+ },
99
+ {
100
+ "value": 1.682,
101
+ "start": "2021-12-16T00:00:00+01:00"
102
+ },
103
+ {
104
+ "value": 1.676,
105
+ "start": "2021-12-16T01:00:00+01:00"
106
+ },
107
+ {
108
+ "value": 1.669,
109
+ "start": "2021-12-16T02:00:00+01:00"
110
+ },
111
+ {
112
+ "value": 1.673,
113
+ "start": "2021-12-16T03:00:00+01:00"
114
+ },
115
+ {
116
+ "value": 1.692,
117
+ "start": "2021-12-16T04:00:00+01:00"
118
+ },
119
+ {
120
+ "value": 1.768,
121
+ "start": "2021-12-16T05:00:00+01:00"
122
+ },
123
+ {
124
+ "value": 1.888,
125
+ "start": "2021-12-16T06:00:00+01:00"
126
+ },
127
+ {
128
+ "value": 1.971,
129
+ "start": "2021-12-16T07:00:00+01:00"
130
+ },
131
+ {
132
+ "value": 2.098,
133
+ "start": "2021-12-16T08:00:00+01:00"
134
+ },
135
+ {
136
+ "value": 2.076,
137
+ "start": "2021-12-16T09:00:00+01:00"
138
+ },
139
+ {
140
+ "value": 2.073,
141
+ "start": "2021-12-16T10:00:00+01:00"
142
+ },
143
+ {
144
+ "value": 2.039,
145
+ "start": "2021-12-16T11:00:00+01:00"
146
+ },
147
+ {
148
+ "value": 2.003,
149
+ "start": "2021-12-16T12:00:00+01:00"
150
+ },
151
+ {
152
+ "value": 1.968,
153
+ "start": "2021-12-16T13:00:00+01:00"
154
+ },
155
+ {
156
+ "value": 1.971,
157
+ "start": "2021-12-16T14:00:00+01:00"
158
+ },
159
+ {
160
+ "value": 2.029,
161
+ "start": "2021-12-16T15:00:00+01:00"
162
+ },
163
+ {
164
+ "value": 2.138,
165
+ "start": "2021-12-16T16:00:00+01:00"
166
+ },
167
+ {
168
+ "value": 2.258,
169
+ "start": "2021-12-16T17:00:00+01:00"
170
+ },
171
+ {
172
+ "value": 2.248,
173
+ "start": "2021-12-16T18:00:00+01:00"
174
+ },
175
+ {
176
+ "value": 2.076,
177
+ "start": "2021-12-16T19:00:00+01:00"
178
+ },
179
+ {
180
+ "value": 2.009,
181
+ "start": "2021-12-16T20:00:00+01:00"
182
+ },
183
+ {
184
+ "value": 2.051,
185
+ "start": "2021-12-16T21:00:00+01:00"
186
+ },
187
+ {
188
+ "value": 1.926,
189
+ "start": "2021-12-16T22:00:00+01:00"
190
+ },
191
+ {
192
+ "value": 1.863,
193
+ "start": "2021-12-16T23:00:00+01:00"
194
+ }
195
+ ],
196
+ "source": "Nordpool"
197
+ }