node-red-contrib-power-saver 3.5.4 → 3.5.7

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,7 +58,16 @@ module.exports = {
58
58
  "/changelog/": [{ text: "Changelog", children: ["/changelog/README.md"] }],
59
59
  },
60
60
  },
61
- head: [["link", { rel: "shortcut icon", type: "image/x-icon", href: "euro.png" }]],
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
+ ],
62
71
  plugins: [
63
72
  [
64
73
  "@vuepress/register-components",
@@ -6,6 +6,20 @@ sidebar: "auto"
6
6
 
7
7
  List the most significant changes, starting in version 1.0.9.
8
8
 
9
+ ## 3.5.7
10
+
11
+ - Add day-filter to general-add-tariff node so it can add one tariff for some days, and another tariff for other days.
12
+ - Fix the elvia-add-tariff node so time is correct. The Elvia API does not handle time zone on the request, so this must be corrected for.
13
+ - Fix link to node-documentation in node edit dialogs.
14
+
15
+ ## 3.5.6
16
+
17
+ - Update Elvia nodes so they use the new `digin` API. NB! There is no guarantee this is working right.
18
+
19
+ ## 3.5.5
20
+
21
+ - Fix config storage for Best Save node
22
+
9
23
  ## 3.5.4
10
24
 
11
25
  - Fix bug in context selection
@@ -0,0 +1,272 @@
1
+ # Capacity part of grid tariff
2
+
3
+ I Norway, there has been introduced a monthly fee for grid capacity. The purpose is to get people to avoid using large capacities at any time.
4
+ The fee is calculated based on the average consumption per hour. The maximum average consumption for each day is calculated. Then the average of the three highest days for the month is used to decide your capacity fee.
5
+
6
+ The capacity is divided in steps, so if your average of the 3 worst days is over the step limit, your fee is picked from that step.
7
+
8
+ Here is an example how this can be controlled. The axample contains several features, and yuo may not want to use them all, so it is a good idea to read through it all before you decide how to use it.
9
+
10
+ ## Requirements
11
+
12
+ You need the following to be able to use this example:
13
+
14
+ - Tibber Pulse to measure consumption continuously.
15
+ - Tibber subscription to get consumption data per hour.
16
+ - Token to access Tibber API.
17
+ - [Node-RED Companion Integration](https://github.com/zachowj/hass-node-red)
18
+ - Tibber nodes in Node-RED.
19
+
20
+ If you have the same information from other sources, you may be able to adapt the example.
21
+
22
+ ## Features
23
+
24
+ - Show status as Ok, Warning or Alarm for the current consumption (estimate calculated for the current hour).
25
+ - Show current step, that is the step that you already are on based on consumption previously this month.
26
+ - Control sensors in HA that you can use to take action to reduce consumption the current hour.
27
+
28
+ ## Algorithm
29
+
30
+ The algorithm is calculating a state that can be:
31
+
32
+ - Ok - Current estimate will not bring you over to another step.
33
+ - Warning - Current estimate may affect the step, if combined with high consumption later this month.
34
+ - Alarm - Current estimate will bring you over to another step.
35
+
36
+ The current consumption is measured continuously (every 2 seconds), along with the total consumption for the current hour until current time.
37
+ The consumption for the rest of the current hour is estimated, assumed to be the same in average as the average consumption for the last minute (can be configured to several minutes). Based on this, the total consumption for the current hour is estimated.
38
+
39
+ Then the current hour is compared to the other hours today. If it is not the worst, status is Ok, as it will not affect the fee.
40
+
41
+ If the current hour is todays worst, it is ranked together with the other days this month. If today is ranked as 4 or better, where 1 is the worst this month, then status is Ok. It will not affect the fee.
42
+
43
+ The `currentStep` is decided based on the 3 worst days, including today, but not considering the current hour.
44
+
45
+ If today is ranked as 1, 2 or 3, it may affect the fee. Then the average of the three worst days, including this hour, is calculated. This is `currentMonthlyEstimate`. So, if the current hour is ranked as 3, and the `currentMonthlyEstimate` is still within `currentStep`, status is still Ok, as the current hour will not participate in braking the step limit.
46
+
47
+ If today is ranked as 1, 2 or 3, and the `currentMonthlyEstimate` is over the `currentStep`, then the status is Alarm.
48
+
49
+ Else the status is Warning. That is if today is ranked as 1 or 2, but `currentMonthlyEstimate` is still under `currentStep`. In this case, the current hour makes the situation worse, and can potentially participate in breaking the limit together with a worse hour coming later this month.
50
+
51
+ ## Output
52
+
53
+ The last step in the flow provides the following output:
54
+
55
+ | Name | Description |
56
+ | ---------------------- | ------------------------------------------------------------------------ |
57
+ | status | The status of the current hour (Ok, Warning or Alarm). |
58
+ | highestPerDay | An array with the worst hour for each day this month |
59
+ | highestCounting | An array with the 3 worst days this month, including passed hours today. |
60
+ | highestToday | The worst hour until now today, not including current hour. |
61
+ | currentMonthlyEstimate | The average of the three worst days, including this hour. |
62
+ | hourEstimate | The estimate for the current hour. |
63
+ | currentStep | The upper limit of the step we are currently on. |
64
+ | currentHourRanking | The rank of the current hour, from 0 to 4. See below. |
65
+
66
+ ### currentHourRanking
67
+
68
+ The current hour ranking has the following meaning:
69
+
70
+ | Value | Description |
71
+ | ----- | ---------------------------------------------------------------------------------- |
72
+ | 0 | Not counting, since there has been another hour earlier today that is higher. |
73
+ | 1 | This is estimated to be the worst hour in the month. |
74
+ | 2 | This is estimated to be the second worst hour in the month. |
75
+ | 3 | This is estimated to be the third worst hour in the month. |
76
+ | 4 | This hour is estimated to be the worst today, but not one of the top 3 this month. |
77
+
78
+ ::: warning
79
+ The highest value here, 4, is based on the value of `MAX_COUNTING` in the `Find highest per day` node.
80
+ If you change `MAX_COUNTING`, the values here will also change.
81
+ :::
82
+
83
+ ### alarmLevel TODO
84
+
85
+ | Value | Description |
86
+ | ----- | --------------------------------------------------------------------------------------------------------------------------- |
87
+ | 0 | There is nothing to worry about. |
88
+ | 1 | The estimate for the current hour sets this as the second to worst hour this month, but it is still way under the next step |
89
+ | 2 | The estimate for the current hour sets this as the worst hour this month, but it is still way under the next step |
90
+ | 3 | The estimate for the current hour is close to the limit for the next step |
91
+ | 4 | The estimate for the current hour is very close to the limit for the next step |
92
+ | 5 | The estimate for the current hour is over the limit for the next step |
93
+ | 6 | The estimate for the current hour is way over the limit for the next step |
94
+ | 6 | This hour increases the estimate for the month |
95
+
96
+ hourEstimate
97
+ maxToday
98
+ highestCounting (1, 2, 3)
99
+ highestCountingAverage
100
+ nextStep
101
+
102
+ hourEstimate < maxToday
103
+ hourEstimate > maxToday && hourEstimate < highestCounting[2]
104
+ maxToday < hourEstimate < nextStep
105
+ maxToday < hourEstimate < highestCounting[2]
106
+ maxToday < hourEstimate < highestCounting[1]
107
+ maxToday < hourEstimate < highestCounting[0]
108
+
109
+ You can define the limits for `close` and `very close` to the next step, and the limit for `way over` the next step.
110
+
111
+ If the current hour is
112
+
113
+ ## Node description
114
+
115
+ ### Get live data
116
+
117
+ This is a `tibber-feed` node. It sets up a subscription of live Tibber data from Tibber Pulse, and uses this data to run the automation. Tick the following check boxes:
118
+
119
+ - Timestamp
120
+ - Power
121
+ - Accumulated consumption
122
+ - Accumulated consumption last hour
123
+
124
+ Uncheck all the others.
125
+
126
+ If you do not use Tibber, but can get this information from another source, convert it to the following format and use it the same way as data from this node is used:
127
+
128
+ ```json
129
+ {
130
+ "timestamp": "2022-06-12T14:42:00.000+02:00",
131
+ "power": 9503,
132
+ "accumulatedConsumption": 33.459665,
133
+ "accumulatedConsumptionLastHour": 7.046665
134
+ }
135
+ ```
136
+
137
+ ### Build query for consumption
138
+
139
+ This is a function node that is used to build a Tibber query. It runs for all the live data, takes the time and calculates how many hours there has been since the beginning of the month. It uses this number to build a Tibber query to get consumption per hour since the beginning of the month. It sends output only when first started and when the hour changes, so it initiates a Tibber query once per hour.
140
+
141
+ ::: warning Tibber Home Id
142
+ This node needs the tibber home id, so you must find it in the [Tibber Developer Pages](https://developer.tibber.com/) and set the vale of `TIBBER_HOME_ID` in the beginning of the code.
143
+ :::
144
+
145
+ ### Get consumption
146
+
147
+ This is a `tibber-query` node used to get consumption per hour for passed hours. It takes a Tibber query as input, and sends the result as output. The query is built by the previous node.
148
+
149
+ If you do not use Tibber, but can get this information from another source, convert it to the following format and use it the same way as data from this node is used:
150
+
151
+ ```json
152
+ {
153
+ "viewer": {
154
+ "home": {
155
+ "consumption": {
156
+ "nodes": [
157
+ {
158
+ "from": "2022-06-01T00:00:00.000+02:00",
159
+ "consumption": 4.307
160
+ },
161
+ {
162
+ "from": "2022-06-01T01:00:00.000+02:00",
163
+ "consumption": 3.648
164
+ },
165
+ {
166
+ "from": "2022-06-01T02:00:00.000+02:00",
167
+ "consumption": 2.406
168
+ },
169
+ // ...
170
+ {
171
+ "from": "2022-06-12T12:00:00.000+02:00",
172
+ "consumption": 0.969
173
+ },
174
+ {
175
+ "from": "2022-06-12T13:00:00.000+02:00",
176
+ "consumption": 7.612
177
+ }
178
+ ]
179
+ }
180
+ }
181
+ }
182
+ }
183
+ ```
184
+
185
+ It must contain data for every hour from the beginning of the month until the last complete hour.
186
+
187
+ ### Collect estimate for hour
188
+
189
+ This is a function node that receives all the live data and estimates the consumption for the rest of the current hour. It sums up the actual consumption from the beginning of the hour until the current time, and adds the estimate for the rest of the hour, giving a total estimate for the hour.
190
+
191
+ In the beginning of the code, there is a constant `ESTIMATION_TIME_MINUTES` that you can use to decide how many minutes that is used
192
+ to calculate assumed average consumption.
193
+
194
+ The function keeps a buffer with all readings for the last period of length given with `ESTIMATION_TIME_MINUTES`.
195
+ It uses this buffer to calculate the average consumption for the period. It uses the result and estimates the consumption for the rest of the hour, assuming that the consumption will be the same.
196
+
197
+ As outputs it sends the following:
198
+
199
+ | Name | Description |
200
+ | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
201
+ | accumulatedConsumption | Accumulated consumption the current day. |
202
+ | periodSec | Period the average is calculated for, in seconds. It will increase in the beginning, until it reaches `ESTIMATION_TIME_MINUTES * 60` |
203
+ | consumptionInPeriod | Consumption in the last `periodSec` seconds. Used as estimate for the remaining of the hour. |
204
+ | timeLeftSec | Number of seconds left in the current hour. |
205
+ | consumptionLeft | Estimated consumption the remaining of the hour. |
206
+ | hourEstimate | The estimated consumption for the total hour. |
207
+
208
+ ### Find highest per day
209
+
210
+ Based on the result from the tibber query, gives the following output:
211
+
212
+ | Name | Description |
213
+ | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
214
+ | highestPerDay | The highest hour for each day until now this month, including current day. |
215
+ | highestCounting | The 3 highest days current month. Can be other than 3 by changing the `MAX_COUNTING` constant in the beginning of the script. |
216
+ | highestToday | The highest hour that has ended until now this day. |
217
+ | currentMonthlyEstimate | The average of the 3 highest days until now this month. That is the capacity that will be used unless it is increased. |
218
+
219
+ Output is sent when the query is run, that is on startup and when the hour changes.
220
+
221
+ ### Check for breach
222
+
223
+ This is where numbers are evaluated to figure out if the current hour is in risk of breaching the limit for the next step.
224
+ The output is as follows:
225
+
226
+ | Name | Description |
227
+ | ---------------------- | ----------------------------------------------------------------------------------- |
228
+ | status | The status of the current hour (Ok, Warning or Alarm). |
229
+ | highestPerDay | An array with the worst hour for each day this month |
230
+ | highestCounting | An array with the 3 worst days this month, including passed hours today. |
231
+ | highestToday | The worst hour until now today, not including current hour. |
232
+ | currentMonthlyEstimate | The average of the three worst days, including this hour. |
233
+ | hourEstimate | The estimate for the current hour. |
234
+ | currentStep | The upper limit of the step we are currently on. |
235
+ | currentHourRanking | The rank of the current hour, from 1 to 4, where 4 is the lowest |
236
+ | reductionRequired | The power that must be reduced the rest of the hour in order to not break the limit |
237
+
238
+ ## Actions
239
+
240
+ You may set up actions to be taken when the status is Warning or Alarm.
241
+ For each action, specify priority and expected reduction in kW,
242
+ as well as what data to send as output for that action.
243
+
244
+ When a warning or an alarm has been on for a specified time, actions are taken.
245
+ Actions are taken in prioritized order, until enough reduction is expected,
246
+ so multiple actions may be taken at the same time.
247
+
248
+ Actions are defined in an array in the beginning of the script.
249
+ The first action has highest priority, that is it will be executed first.
250
+ The rest follow in prioritized order.
251
+
252
+ For each action, the following is specified:
253
+
254
+ - sensor - The the entity id of the sensor that gives you the effect that will be reduced.
255
+ - outputActivate - The data sent to the output to acticate the action.
256
+ - outputDeactivate - The data sent to the output to deactivate the action.
257
+ - activateOnWarning - true if this shall be activated on warning.
258
+
259
+ The node has the same number of outputs as there are actions.
260
+ The first output is for the first action, and so on.
261
+ When an action is activated, the outputActivate data is sent to the corresponding output.
262
+ When the hour is over, the action is deactivated.
263
+ When an action is deativated, the outputDeactivate data is sent to the corresponding output.
264
+
265
+ It is up to you to define how to use the actions.
266
+
267
+ # TO DO
268
+
269
+ - Implement actions
270
+ - Make a safety margin in kW so we reduce a little more than necessary.
271
+ - May need some kind of warning when hourEstimate > currentStep, even if currentMonthlyEstimate is not.
272
+ - Maybe set reductionRequired to hourEstimate - currentStep in this case.
@@ -6,10 +6,17 @@ Node to add a value, for example a variable grid tariff, to the price before it
6
6
 
7
7
  ## Description
8
8
 
9
- This node is useful if there is an addition to the electricity price that varies over the day, as it might be for the grid tariff.
9
+ This node is useful if there is an addition to the electricity price that varies over the day or the week, as it might be for the grid tariff.
10
10
 
11
11
  If there is one price for example from 22:00 to 06:00 every day, and another price from 06:00 to 22:00, this is the right node to use. It can be used for more than two periods, as long as the time it changes is the same every day.
12
12
 
13
+ If the price is different for some days of the week, use two nodes in series, one for some days and one for the other days.
14
+
15
+ ::: warning Different days
16
+ When using two nodes in series to support for example different rates for weekend than weekdays,
17
+ make sure each day is handled by only one node, else both nodes will add to the price.
18
+ :::
19
+
13
20
  Here is how this node is normally used:
14
21
 
15
22
  ![general flow](../images/add-tariff-flow.png)
@@ -28,6 +35,13 @@ You can have from 1 to 24 periods during the day, with different values to add f
28
35
 
29
36
  For each period, select the time of the day the value is valid from, and enter the value.
30
37
 
38
+ ### Days
39
+
40
+ Check which days the price is valid for. For the price to be added, both the time and the day must be correct.
41
+
42
+ If there is one price for Mon-Fri, and another price for Sat-Sun, use two nodes in series,
43
+ where one handles Mon, Tue, Wed, Thu and Fri, and the other one Sat and Sun.
44
+
31
45
  ### Valid from date
32
46
 
33
47
  Fill in the first date the config is valid.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-power-saver",
3
- "version": "3.5.4",
3
+ "version": "3.5.7",
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": {
@@ -72,5 +72,5 @@
72
72
  A node to get the tariff from Elvia and add it to the price before it is sent to the power saver strategy nodes.
73
73
  Use this node between the receive-price node and any of the strategy nodes.
74
74
 
75
- Please read more in the [node documentation](https://ottopaulsen.github.io/node-red-contrib-power-saver/nodes/ps-elvia-add-tariff)
75
+ Please read more in the [node documentation](https://powersaver.no/nodes/ps-elvia-add-tariff)
76
76
  </script>
@@ -20,22 +20,24 @@ module.exports = function (RED) {
20
20
  );
21
21
  return;
22
22
  }
23
- const fromTime = prices[0].start.substr(0, 19);
23
+ // Convert date to UTC to get correct parameter for the Elvia API (no timezone)
24
+ const fromTime = DateTime.fromISO(prices[0].start).toUTC().toISO().substring(0, 19);
24
25
  const toTime = DateTime.fromISO(prices[prices.length - 1].start)
25
26
  .plus({ hours: 1 })
27
+ .toUTC()
26
28
  .toISO()
27
- .substr(0, 19);
29
+ .substring(0, 19);
28
30
 
29
31
  getTariffForPeriod(node, key, node.tariffKey, fromTime, toTime).then((json) => {
30
32
  const tariff = json;
31
- const priceInfo = tariff.gridTariff.tariffPrice.priceInfo;
33
+ const priceInfo = tariff.gridTariff.tariffPrice.hours;
32
34
  if (priceInfo.length !== prices.length) {
33
35
  node.warn(`Elvia tariff count mismatch. Expected ${prices.length} items, but got ${priceInfo.length}`);
34
36
  node.status({ fill: "red", shape: "dot", text: "Tariff error" });
35
37
  } else {
36
38
  prices.forEach((p, i) => {
37
39
  p.powerPrice = p.value;
38
- p.gridTariffVariable = priceInfo[i].variablePrice.total;
40
+ p.gridTariffVariable = priceInfo[i].energyPrice.total;
39
41
  p.value = roundPrice(p.powerPrice + p.gridTariffVariable);
40
42
  });
41
43
  }
@@ -2,7 +2,7 @@ const fetch = require("node-fetch");
2
2
 
3
3
  function ping(node, subscriptionKey, setResultStatus = true) {
4
4
  const url = "https://elvia.azure-api.net/grid-tariff/Ping";
5
- const headers = { "Ocp-Apim-Subscription-Key": subscriptionKey };
5
+ const headers = { "X-API-Key": subscriptionKey };
6
6
  fetch(url, { headers }).then((res) => {
7
7
  if (setResultStatus) {
8
8
  setNodeStatus(node, res.status);
@@ -11,13 +11,14 @@ function ping(node, subscriptionKey, setResultStatus = true) {
11
11
  }
12
12
 
13
13
  function getTariff(node, subscriptionKey, tariffKey, range = "today", setResultStatus = true) {
14
- const url = "https://elvia.azure-api.net/grid-tariff/api/1/tariffquery?TariffKey=" + tariffKey + "&Range=" + range;
14
+ const url =
15
+ "https://elvia.azure-api.net/grid-tariff/digin/api/1.0/tariffquery?TariffKey=" + tariffKey + "&Range=" + range;
15
16
  return get(node, subscriptionKey, url, setResultStatus);
16
17
  }
17
18
 
18
19
  function getTariffForPeriod(node, subscriptionKey, tariffKey, startTime, endTime, setResultStatus = true) {
19
20
  const url =
20
- "https://elvia.azure-api.net/grid-tariff/api/1/tariffquery?TariffKey=" +
21
+ "https://elvia.azure-api.net/grid-tariff/digin/api/1.0/tariffquery?TariffKey=" +
21
22
  tariffKey +
22
23
  "&StartTime=" +
23
24
  startTime +
@@ -27,12 +28,12 @@ function getTariffForPeriod(node, subscriptionKey, tariffKey, startTime, endTime
27
28
  }
28
29
 
29
30
  function getTariffTypes(node, subscriptionKey, setResultStatus = true) {
30
- const url = "https://elvia.azure-api.net/grid-tariff/api/1/tarifftype";
31
+ const url = "https://elvia.azure-api.net/grid-tariff/digin/api/1.0/tarifftype";
31
32
  return get(node, subscriptionKey, url, setResultStatus);
32
33
  }
33
34
 
34
35
  function get(node, subscriptionKey, url, setResultStatus) {
35
- const headers = { "Ocp-Apim-Subscription-Key": subscriptionKey };
36
+ const headers = { "X-API-Key": subscriptionKey };
36
37
  return fetch(url, { headers }).then((res) => {
37
38
  if (setResultStatus && node) {
38
39
  setNodeStatus(node, res.status);
@@ -33,7 +33,9 @@ function addTariffToPrices(node, config, prices) {
33
33
  prices.forEach((p, i) => {
34
34
  const date = DateTime.fromISO(p.start.substr(0, 10));
35
35
  const hour = DateTime.fromISO(p.start).hour;
36
- if (date >= validFrom && date <= validTo) {
36
+ const day = DateTime.fromISO(p.start).weekday;
37
+ const dayName = Object.keys(config.days)[day - 1];
38
+ if (date >= validFrom && date <= validTo && config.days[dayName]) {
37
39
  prices[i].value = roundPrice(prices[i].value + allHours[hour]);
38
40
  }
39
41
  });
@@ -1,6 +1,7 @@
1
1
  <script type="text/javascript">
2
2
  const dateRe = /^$|^\d{4}\-(0[1-9]|1[012])\-(0[1-9]|[12][0-9]|3[01])$/;
3
3
  const priceRe = /^(\d+\.\d*)|(\d+)$/;
4
+ const defaultDays = { Mon: true, Tue: true, Wed: true, Thu: true, Fri: true, Sat: true, Sun: true };
4
5
  RED.nodes.registerType("ps-general-add-tariff", {
5
6
  category: "Power Saver",
6
7
  color: "#a6bbcf",
@@ -17,6 +18,7 @@
17
18
  },
18
19
  validFrom: { value: null, required: false, validate: RED.validators.regex(dateRe) },
19
20
  validTo: { value: null, required: false, validate: RED.validators.regex(dateRe) },
21
+ days: { value: { ...defaultDays } },
20
22
  },
21
23
  hours: [
22
24
  "00",
@@ -46,7 +48,6 @@
46
48
  ],
47
49
  inputs: 1,
48
50
  outputs: 1,
49
- periodCont: 2,
50
51
  icon: "font-awesome/fa-plus",
51
52
  color: "#FFCC66",
52
53
  label: function () {
@@ -124,7 +125,7 @@
124
125
  $("#node-input-fromTime-" + i).typedInput({
125
126
  types: [
126
127
  {
127
- value: "fromtime",
128
+ value: "fromTime",
128
129
  options: hours.map((h) => ({ value: h, label: h + ":00" })),
129
130
  },
130
131
  ],
@@ -140,10 +141,55 @@
140
141
  }
141
142
  };
142
143
 
144
+ const drawDays = function (days) {
145
+ const dayNames = Object.keys(days);
146
+ document.getElementById("node-input-days-container").replaceChildren();
147
+ for (let i = 0; i < dayNames.length; i++) {
148
+ let day = dayNames[i];
149
+
150
+ const id = `node-input-day-${i}`;
151
+ const label = createElement(
152
+ "label",
153
+ [
154
+ ["for", id],
155
+ ["style", " margin: 4px 10px 0px 2px;width: 30px; text-align: left;"],
156
+ ],
157
+ []
158
+ );
159
+ label.innerHTML = day;
160
+ const attrs = [
161
+ ["name", "node-input-day"],
162
+ ["type", "checkbox"],
163
+ ["id", id],
164
+ ["style", `width: 15px; margin: 2px 0px 5px 5px;`],
165
+ ];
166
+ if (days[day]) {
167
+ attrs.push(["checked", ""]);
168
+ }
169
+ const inp = createElement("input", attrs);
170
+ inp.value = dayNames[i];
171
+
172
+ const el = createElement("span", [["style", "text-align: right;"]], [inp, label]);
173
+
174
+ $("#node-input-days-container").append(el);
175
+
176
+ $("#node-input-day-" + i).change(function (e) {
177
+ days[day] = $(this).is(":checked");
178
+ RED.nodes.dirty(true);
179
+ });
180
+ }
181
+ };
182
+
143
183
  drawPeriods(this.periods);
144
184
  $("#add-period-button").on("click", () => {
145
185
  addPeriod(this.periods);
146
186
  });
187
+
188
+ if (!this.days) {
189
+ // To support nodes created before this was developed
190
+ this.days = { ...defaultDays };
191
+ }
192
+ drawDays(this.days);
147
193
  },
148
194
  });
149
195
  </script>
@@ -162,6 +208,11 @@
162
208
  <button type="button" id="add-period-button" class="red-ui-button">Add period</button>
163
209
  </div>
164
210
 
211
+ <div class="form-row">
212
+ <label for="node-input-days-container"><i class="fa fa-calendar-o"></i> Days</label>
213
+ <span id="node-input-days-container"></span>
214
+ </div>
215
+
165
216
  <h3>Optional:</h3>
166
217
 
167
218
  <div class="form-row">
@@ -182,5 +233,5 @@
182
233
  to the price before it is sent to the strategy nodes.
183
234
  Use this node between the receive-price node and any of the strategy nodes.
184
235
 
185
- Please read more in the [node documentation](https://ottopaulsen.github.io/node-red-contrib-power-saver/nodes/ps-general-add-tariff)
236
+ Please read more in the [node documentation](https://powersaver.no/nodes/ps-general-add-tariff.html)
186
237
  </script>
@@ -12,6 +12,7 @@ module.exports = function (RED) {
12
12
  periods: config.periods,
13
13
  validFrom: config.validFrom,
14
14
  validTo: config.validTo,
15
+ days: config.days,
15
16
  };
16
17
  node.context().set("config", originalConfig);
17
18
 
@@ -112,5 +112,5 @@
112
112
  <script type="text/markdown" data-help-name="power-saver">
113
113
  A node you can use to save money by turning off and on a switch based on power prices.
114
114
 
115
- Please read more in the [node documentation](https://ottopaulsen.github.io/node-red-contrib-power-saver/nodes/power-saver)
115
+ Please read more in the [node documentation](https://powersaver.no/nodes/power-saver)
116
116
  </script>
@@ -26,5 +26,5 @@
26
26
  A node to receive price data from one of the supported sources,
27
27
  and adapt it for calculation in the Power Saver strategy nodes.
28
28
 
29
- Please read more in the [node documentation](https://ottopaulsen.github.io/node-red-contrib-power-saver/nodes/ps-receive-price)
29
+ Please read more in the [node documentation](https://powersaver.no/nodes/ps-receive-price)
30
30
  </script>
@@ -105,5 +105,5 @@
105
105
  <script type="text/markdown" data-help-name="ps-strategy-best-save">
106
106
  A node you can use to save money by turning off and on a switch based on power prices.
107
107
 
108
- Please read more in the [node documentation](https://ottopaulsen.github.io/node-red-contrib-power-saver/nodes/ps-strategy-best-save)
108
+ Please read more in the [node documentation](https://powersaver.no/nodes/ps-strategy-best-save)
109
109
  </script>
@@ -1,14 +1,12 @@
1
1
  const { countAtEnd, makeSchedule, getSavings, getDiff } = require("./utils");
2
2
  const { handleStrategyInput } = require("./handle-input");
3
3
  const { loadDayData } = require("./utils");
4
-
5
4
  const mostSavedStrategy = require("./strategy-best-save-functions");
6
5
 
7
6
  module.exports = function (RED) {
8
7
  function StrategyBestSaveNode(config) {
9
8
  RED.nodes.createNode(this, config);
10
9
  const node = this;
11
-
12
10
  node.status({});
13
11
 
14
12
  const originalConfig = {
@@ -19,7 +17,7 @@ module.exports = function (RED) {
19
17
  outputIfNoSchedule: config.outputIfNoSchedule === "true",
20
18
  contextStorage: config.contextStorage || "default",
21
19
  };
22
- node.context().set("config", originalConfig, originalConfig.contextStorage);
20
+ node.context().set("config", originalConfig);
23
21
  node.contextStorage = originalConfig.contextStorage;
24
22
 
25
23
  node.on("close", function () {
@@ -79,7 +79,5 @@
79
79
  | Cooling Boost [C] | An extra decrease in temperature to the setpoint for the divestment period |
80
80
  | Min Savings | The minimum amount of savings required for a buy/sell cycle. |
81
81
 
82
-
83
- Please read more in the [node documentation](https://ottopaulsen.github.io/node-red-contrib-power-saver/nodes/ps-strategy-heat-capacitor)
84
-
82
+ Please read more in the [node documentation](https://powersaver.no/nodes/ps-strategy-heat-capacitor)
85
83
  </script>
@@ -177,5 +177,5 @@
177
177
  <script type="text/markdown" data-help-name="ps-strategy-lowest-price">
178
178
  A node to turn on a switch the hours when the price is lowest.
179
179
 
180
- Please read more in the [node documentation](https://ottopaulsen.github.io/node-red-contrib-power-saver/nodes/ps-strategy-lowest-price)
180
+ Please read more in the [node documentation](https://powersaver.no/nodes/ps-strategy-lowest-price)
181
181
  </script>
@@ -20,6 +20,8 @@ module.exports = function (RED) {
20
20
  contextStorage: config.contextStorage || "default",
21
21
  };
22
22
  node.context().set("config", originalConfig);
23
+ node.contextStorage = originalConfig.contextStorage;
24
+
23
25
 
24
26
  node.on("close", function () {
25
27
  clearTimeout(node.schedulingTimeout);
@@ -45,6 +45,7 @@ describe("general-add-tariff-functions", () => {
45
45
  { start: "06", value: 10 },
46
46
  { start: "22", value: 11 },
47
47
  ],
48
+ days: { Mon: true, Tue: true, Wed: true, Thu: true, Fri: true, Sat: true, Sun: true },
48
49
  };
49
50
  const values = [];
50
51
  fillResult(values, 0, 5, 21);
@@ -75,6 +76,7 @@ describe("general-add-tariff-functions", () => {
75
76
  { start: "06", value: 10 },
76
77
  { start: "22", value: 11 },
77
78
  ],
79
+ days: { Mon: true, Tue: true, Wed: true, Thu: true, Fri: true, Sat: true, Sun: true },
78
80
  };
79
81
  const values = [];
80
82
  fillResult(values, 0, 23, 10);
@@ -24,7 +24,7 @@ const prices = {
24
24
  },
25
25
  {
26
26
  value: 2.1,
27
- start: "2021-10-12T00:00:00.000+02:00",
27
+ start: "2021-10-12T00:00:00.000+02:00", // Tuesday
28
28
  },
29
29
  {
30
30
  value: 2.2,
@@ -66,6 +66,7 @@ describe("general-add-tariff node", function () {
66
66
  { start: "01", value: "0.50" },
67
67
  { start: "03", value: "0.80" },
68
68
  ],
69
+ days: { Mon: true, Tue: true, Wed: true, Thu: true, Fri: true, Sat: true, Sun: true },
69
70
  },
70
71
  { id: "n2", type: "helper" },
71
72
  ];
@@ -89,6 +90,41 @@ describe("general-add-tariff node", function () {
89
90
  n1.receive({ payload });
90
91
  });
91
92
  });
93
+ it("should add to price only for correct days", function (done) {
94
+ const flow = [
95
+ {
96
+ id: "n1",
97
+ type: "ps-general-add-tariff",
98
+ name: "Add tariff",
99
+ wires: [["n2"]],
100
+ periods: [
101
+ { start: "01", value: "0.50" },
102
+ { start: "03", value: "0.80" },
103
+ ],
104
+ days: { Mon: true, Tue: false, Wed: true, Thu: true, Fri: true, Sat: true, Sun: true },
105
+ },
106
+ { id: "n2", type: "helper" },
107
+ ];
108
+ const result = cloneDeep(prices);
109
+ result.priceData[0].value = 1.0;
110
+ result.priceData[1].value = 0.8;
111
+ result.priceData[2].value = 0.6;
112
+ result.priceData[3].value = 2.0345;
113
+ result.priceData[4].value = 2.1;
114
+ result.priceData[5].value = 2.2;
115
+ result.config = { abc: 123 };
116
+ helper.load(addTariff, flow, function () {
117
+ const n1 = helper.getNode("n1");
118
+ const n2 = helper.getNode("n2");
119
+ n2.on("input", function (msg) {
120
+ expect(msg).toHaveProperty("payload", result);
121
+ done();
122
+ });
123
+ const payload = cloneDeep(prices);
124
+ payload.config = { abc: 123 };
125
+ n1.receive({ payload });
126
+ });
127
+ });
92
128
  it("should add to price when only first day is valid", function (done) {
93
129
  const flow = [
94
130
  {
@@ -100,6 +136,7 @@ describe("general-add-tariff node", function () {
100
136
  { start: "01", value: "0.50" },
101
137
  { start: "03", value: "0.80" },
102
138
  ],
139
+ days: { Mon: true, Tue: true, Wed: true, Thu: true, Fri: true, Sat: true, Sun: true },
103
140
  validFrom: "2021-10-11",
104
141
  validTo: "2021-10-11",
105
142
  },
@@ -134,6 +171,7 @@ describe("general-add-tariff node", function () {
134
171
  { start: "01", value: "0.50" },
135
172
  { start: "03", value: "0.80" },
136
173
  ],
174
+ days: { Mon: true, Tue: true, Wed: true, Thu: true, Fri: true, Sat: true, Sun: true },
137
175
  validFrom: "2021-10-12",
138
176
  },
139
177
  { id: "n2", type: "helper" },
@@ -163,6 +201,7 @@ describe("general-add-tariff node", function () {
163
201
  name: "Add tariff",
164
202
  wires: [["n2"]],
165
203
  periods: [{ start: "01", value: "0.50" }],
204
+ days: { Mon: true, Tue: true, Wed: true, Thu: true, Fri: true, Sat: true, Sun: true },
166
205
  },
167
206
  { id: "n2", type: "helper" },
168
207
  ];