node-red-contrib-power-saver 4.1.0 → 4.1.2
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/changelog/README.md +9 -0
- package/docs/examples/example-grid-tariff-capacity-part.md +143 -136
- package/docs/images/example-capacity-flow.png +0 -0
- package/examples/example-grid-tariff-capacity-flow.json +244 -0
- package/package.json +4 -4
- package/src/elvia/elvia-api.js +26 -18
- package/src/elvia/elvia-tariff.js +1 -3
- package/src/handle-output.js +2 -2
- package/test/strategy-best-save.test.js +60 -2
- package/docs/examples/example-grid-tariff-capacity-flow.json +0 -1142
package/docs/changelog/README.md
CHANGED
|
@@ -7,6 +7,15 @@ sidebarDepth: 1
|
|
|
7
7
|
|
|
8
8
|
List the most significant changes.
|
|
9
9
|
|
|
10
|
+
## 4.1.2
|
|
11
|
+
|
|
12
|
+
- Fix so configured values for output are sent, not only true/false.
|
|
13
|
+
- Catch error from Elvia API so NR does not crash.
|
|
14
|
+
|
|
15
|
+
## 4.1.1
|
|
16
|
+
|
|
17
|
+
- Update dependencies
|
|
18
|
+
|
|
10
19
|
## 4.1.0
|
|
11
20
|
|
|
12
21
|
- Fix bug with override function. It did not override longer than until next scheduled change. Now it overrides until set to auto again.
|
|
@@ -1,81 +1,5 @@
|
|
|
1
1
|
# Capacity part of grid tariff
|
|
2
2
|
|
|
3
|
-
::: danger Bug-fix 12. September 2022
|
|
4
|
-
|
|
5
|
-
::: details A bug was found 12. sep 2022. Here is how to fix:
|
|
6
|
-
|
|
7
|
-
### 1. Node "Find highest per day":
|
|
8
|
-
|
|
9
|
-
Replace this line:
|
|
10
|
-
|
|
11
|
-
```js
|
|
12
|
-
const highestToday = days.get(new Date().getDate());
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
With this code:
|
|
16
|
-
|
|
17
|
-
```js
|
|
18
|
-
const highestToday = days.get(new Date().getDate()) ?? {
|
|
19
|
-
consumption: 0,
|
|
20
|
-
from: null,
|
|
21
|
-
};
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
This will set the `highestToday` to 0 during the first hour.
|
|
25
|
-
|
|
26
|
-
### 2. Node "Calculate values":
|
|
27
|
-
|
|
28
|
-
Above this line:
|
|
29
|
-
|
|
30
|
-
```js
|
|
31
|
-
function calculateLevel(hourEstimate, ...
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
Insert this code:
|
|
35
|
-
|
|
36
|
-
```js
|
|
37
|
-
function isNull(value) {
|
|
38
|
-
return value === null || value === undefined;
|
|
39
|
-
}
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
Further down the code you can find these 3 lines with 4 lines between:
|
|
43
|
-
|
|
44
|
-
```js
|
|
45
|
-
if (!highestPerDay) {
|
|
46
|
-
if (!highestToday) {
|
|
47
|
-
if (!hourEstimate) {
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
Change these to:
|
|
51
|
-
|
|
52
|
-
```js
|
|
53
|
-
if (isNull(highestPerDay)) {
|
|
54
|
-
if (isNull(highestToday)) {
|
|
55
|
-
if (isNull(hourEstimate)) {
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
Then these will not fail the first hour.
|
|
59
|
-
|
|
60
|
-
### 3. Node "Build query for consumption":
|
|
61
|
-
|
|
62
|
-
Find this line:
|
|
63
|
-
|
|
64
|
-
```js
|
|
65
|
-
const hour = time.getMinutes(); // NB Change to getMinutes()
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
Change it to:
|
|
69
|
-
|
|
70
|
-
```js
|
|
71
|
-
const hour = time.getHours();
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
The bug fixed on no. 3 does so data for hours are read every minute,
|
|
75
|
-
instead of every hour. This is not necessary.
|
|
76
|
-
However, it does not lead to any error.
|
|
77
|
-
:::
|
|
78
|
-
|
|
79
3
|
## Introduction
|
|
80
4
|
|
|
81
5
|
I Norway, there has been introduced a monthly fee for grid capacity.
|
|
@@ -103,10 +27,10 @@ and you may not want to use them all, so it is a good idea to read through it al
|
|
|
103
27
|
|
|
104
28
|

|
|
105
29
|
|
|
106
|
-
The first part of nodes (upper
|
|
107
|
-
The
|
|
30
|
+
The first part of nodes (upper part) is used to read consumption from Tibber, and to perform all calculations.
|
|
31
|
+
The upper right two nodes are used to update a set of sensors in HA. You can use those sensor for many
|
|
108
32
|
purposes related to the grid capacity.
|
|
109
|
-
The
|
|
33
|
+
The bottom part is used to take actions in order to reduce power consumption,
|
|
110
34
|
or to reset actions when the consumption is lowered.
|
|
111
35
|
|
|
112
36
|
You may use the whole example, or only part of it, so you should read through the whole documentation before you start,
|
|
@@ -145,6 +69,7 @@ If you have the same information from other sources, you may be able to adapt th
|
|
|
145
69
|
- Automatically take actions to reduce consumption if it is recommended or required.
|
|
146
70
|
- Automatically reset actions if the consumption is sufficiently reduced.
|
|
147
71
|
- Log actions taken to a file
|
|
72
|
+
- Actions may also be done by directly and automatically overriding strategy nodes.
|
|
148
73
|
|
|
149
74
|
## Algorithm
|
|
150
75
|
|
|
@@ -176,8 +101,8 @@ See description for each of them for details.
|
|
|
176
101
|
## Calculated sensor values
|
|
177
102
|
|
|
178
103
|
The `Calculate values` node creates a payload with values that are used to create
|
|
179
|
-
sensors in Home Assistant.
|
|
180
|
-
|
|
104
|
+
sensors in Home Assistant. The `Update sensors` node maps these values to entity ids,
|
|
105
|
+
and the `Set entity` node uses the HA API to update sensors.
|
|
181
106
|
|
|
182
107
|
Here is a description of each of those sensor values.
|
|
183
108
|
|
|
@@ -642,7 +567,7 @@ See [Calculated sensor values](#calculated-sensor-values) for description of the
|
|
|
642
567
|
|
|
643
568
|
```js
|
|
644
569
|
const HA_NAME = "homeAssistant"; // Your HA name
|
|
645
|
-
const STEPS = [
|
|
570
|
+
const STEPS = [10, 15, 20];
|
|
646
571
|
const MAX_COUNTING = 3; // Number of days to calculate month
|
|
647
572
|
const BUFFER = 0.5; // Closer to limit increases level
|
|
648
573
|
const SAFE_ZONE = 2; // Further from limit reduces level
|
|
@@ -790,6 +715,8 @@ const payload = {
|
|
|
790
715
|
highestCounting,
|
|
791
716
|
highestCountingWithCurrent,
|
|
792
717
|
highestToday,
|
|
718
|
+
highestTodayConsumption: highestToday.consumption,
|
|
719
|
+
highestTodayFrom: highestToday.from,
|
|
793
720
|
currentMonthlyEstimate: Math.round(currentMonthlyEstimate * RESOLUTION) / RESOLUTION,
|
|
794
721
|
accumulatedConsumptionLastHour: Math.round(accumulatedConsumptionLastHour * RESOLUTION) / RESOLUTION,
|
|
795
722
|
consumptionLeft: Math.round(consumptionLeft * RESOLUTION) / RESOLUTION,
|
|
@@ -815,6 +742,61 @@ return msg;
|
|
|
815
742
|
|
|
816
743
|
:::
|
|
817
744
|
|
|
745
|
+
### Update sensors
|
|
746
|
+
|
|
747
|
+
This function node maps the values from the previous node to entity_ids that shall be updated in HA.
|
|
748
|
+
Then, for each sensor, it sends output that uses the API node to perform the actual update.
|
|
749
|
+
|
|
750
|
+
You can remove sensors that you do not want.
|
|
751
|
+
|
|
752
|
+
::: details Code
|
|
753
|
+
|
|
754
|
+
<CodeGroup>
|
|
755
|
+
<CodeGroupItem title="On Message" active>
|
|
756
|
+
|
|
757
|
+
```js
|
|
758
|
+
const sensors = [
|
|
759
|
+
{ id: "sensor.ps_cap_status", value: "status", uom: null },
|
|
760
|
+
{ id: "binary_sensor.ps_cap_ok", value: "statusOk", uom: null },
|
|
761
|
+
{ id: "binary_sensor.ps_cap_warning", value: "statusWarning", uom: null },
|
|
762
|
+
{ id: "binary_sensor.ps_cap_alarm", value: "statusAlarm", uom: null },
|
|
763
|
+
{ id: "sensor.ps_cap_alarm_level", value: "alarmLevel", uom: null },
|
|
764
|
+
{ id: "sensor.ps_cap_current_step", value: "currentStep", uom: "kW" },
|
|
765
|
+
{ id: "sensor.ps_cap_hour_estimate", value: "hourEstimate", uom: "kW" },
|
|
766
|
+
{ id: "sensor.ps_cap_current_hour_ranking", value: "currentHourRanking", uom: null },
|
|
767
|
+
{ id: "sensor.ps_cap_monthly_estimate", value: "currentMonthlyEstimate", uom: "kW" },
|
|
768
|
+
{ id: "sensor.ps_cap_highest_today", value: "highestTodayConsumption", uom: "kW" },
|
|
769
|
+
{ id: "sensor.ps_cap_highest_today_time", value: "highestTodayFrom", uom: null },
|
|
770
|
+
{ id: "sensor.ps_cap_reduction_required", value: "reductionRequired", uom: "kW" },
|
|
771
|
+
{ id: "sensor.ps_cap_reduction_recommended", value: "reductionRecommended", uom: "kW" },
|
|
772
|
+
{ id: "sensor.ps_cap_increase_possible", value: "increasePossible", uom: "kW" },
|
|
773
|
+
{ id: "sensor.ps_cap_estimate_rest_of_hour", value: "consumptionLeft", uom: "kW" },
|
|
774
|
+
{ id: "sensor.ps_cap_consumption_accumulated_hour", value: "accumulatedConsumptionLastHour", uom: "kW" },
|
|
775
|
+
{ id: "sensor.ps_cap_time_left", value: "timeLeftSec", uom: "s" },
|
|
776
|
+
{ id: "sensor.ps_cap_consumption_now", value: "averageConsumptionNow", uom: "kW" },
|
|
777
|
+
];
|
|
778
|
+
|
|
779
|
+
sensors.forEach((sensor) => {
|
|
780
|
+
const payload = {
|
|
781
|
+
protocol: "http",
|
|
782
|
+
method: "post",
|
|
783
|
+
path: "/api/states/" + sensor.id,
|
|
784
|
+
data: {
|
|
785
|
+
state: msg.payload[sensor.value],
|
|
786
|
+
attributes: { unit_of_measurement: sensor.uom },
|
|
787
|
+
},
|
|
788
|
+
};
|
|
789
|
+
node.send({ payload });
|
|
790
|
+
});
|
|
791
|
+
```
|
|
792
|
+
|
|
793
|
+
</CodeGroupItem>
|
|
794
|
+
</CodeGroup>
|
|
795
|
+
|
|
796
|
+
:::
|
|
797
|
+
|
|
798
|
+
### Set entity
|
|
799
|
+
|
|
818
800
|
### Reduction Actions
|
|
819
801
|
|
|
820
802
|
This is where you set up actions to be taken in case reduction is required or recommended.
|
|
@@ -827,34 +809,74 @@ In the **On Start** tab of this node, you set up the actions by writing a Javasc
|
|
|
827
809
|
the `actions` array.
|
|
828
810
|
The example shows some actions, but you may set up any number of actions.
|
|
829
811
|
|
|
830
|
-
|
|
812
|
+
There are two types of actions that can be sent:
|
|
813
|
+
|
|
814
|
+
#### Call service actions
|
|
815
|
+
|
|
816
|
+
These actions are taken by sending a payload to a HA `call service` node (the `Perform action` node).
|
|
831
817
|
The items in the `actions` array contains the payload you need to send to the `call service` node
|
|
832
818
|
in order to take action, and the payload you need to send to the same `call service` node
|
|
833
819
|
in order to reset the action.
|
|
834
820
|
|
|
821
|
+
#### Override Power Saver actions
|
|
822
|
+
|
|
823
|
+
These actions are taken by sending an override message to one or more strategy nodes.
|
|
824
|
+
Reduction is done by sending override `off`, and reset is done by sending override `auto`.
|
|
825
|
+
To use this type of action, specify the name of the strategy node that shall be overridden
|
|
826
|
+
in the `nameOfStrategyToOverride` attribute of the action.
|
|
827
|
+
|
|
828
|
+
Then send output 2 from the `Reduction Actions` node to the input of the strategy node.
|
|
829
|
+
|
|
830
|
+
You may have multiple actions controlling multiple strategy nodes. They are separated using the `name`
|
|
831
|
+
of the strategy node, so make sure they all have different names.
|
|
832
|
+
Output 2 must be sent to all strategy nodes that shall be controlled.
|
|
833
|
+
|
|
834
|
+
If you send the output to two strategy nodes with the same name, they will both be controlled.
|
|
835
|
+
|
|
836
|
+
You can use this to override the following nodes:
|
|
837
|
+
|
|
838
|
+
- Best Save
|
|
839
|
+
- Lowest Price
|
|
840
|
+
- Schedule Merger
|
|
841
|
+
- Fixed Schedule
|
|
842
|
+
|
|
843
|
+
If you are using the schedule merger node, you do not have to override the strategy nodes preceding the schedule merger,
|
|
844
|
+
only override the schedule merger.
|
|
845
|
+
|
|
846
|
+
#### Entity consumption
|
|
847
|
+
|
|
835
848
|
An action may be to turn on or off a switch, to perform a climate control or what ever else
|
|
836
849
|
you can do to control your entities.
|
|
837
850
|
|
|
838
|
-
In order to know how much power that is saved by turning off an action, each
|
|
839
|
-
|
|
851
|
+
In order to know how much power that is saved by turning off an action, you should specify this for each action, using the `consumption` attribute. This can be used in 3 different ways:
|
|
852
|
+
|
|
853
|
+
- The entity_id of a sensor that gives the consumption in kW (recommended)
|
|
854
|
+
- A number with the consumption in kW
|
|
855
|
+
- A function returning the consumption.
|
|
840
856
|
|
|
841
|
-
::: warning
|
|
842
|
-
|
|
843
|
-
turn off. If this is not possible, the code must be changed in order to work.
|
|
857
|
+
::: warning Consumption must be kW
|
|
858
|
+
If your sensor gives consumption in W, not the required kW, you should find a way to divide it by 1000.
|
|
844
859
|
:::
|
|
845
860
|
|
|
861
|
+
#### Actions configuration
|
|
862
|
+
|
|
846
863
|
Each item in the `actions` array contains the following data:
|
|
847
864
|
|
|
848
|
-
| Variable name
|
|
849
|
-
|
|
|
850
|
-
| consumption
|
|
851
|
-
| name
|
|
852
|
-
| id
|
|
853
|
-
| minAlarmLevel
|
|
854
|
-
| reduceWhenRecommended
|
|
855
|
-
| minTimeOffSec
|
|
856
|
-
| payloadToTakeAction
|
|
857
|
-
| payloadToResetAction
|
|
865
|
+
| Variable name | Description |
|
|
866
|
+
| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
867
|
+
| consumption | The consumption that will be reduced by taking the action, given as either a) (Recommended) The entity_id of a sensor that gives the consumption, or b) A number with the consumption in kWh, or c) a function returning the consumption. |
|
|
868
|
+
| name | The name of the actions. Can be any thing. |
|
|
869
|
+
| id | A unique id of the action. |
|
|
870
|
+
| minAlarmLevel | The minimum alarm level that must be present to take this action. |
|
|
871
|
+
| reduceWhenRecommended | If `true` the action will be taken when `Reduction Recommended` > 0. If `false` the action will be taken only when `Reduction Required` > 0 |
|
|
872
|
+
| minTimeOffSec | The action will not be reset until minimum this number of seconds has passed since the action was taken. |
|
|
873
|
+
| payloadToTakeAction | The payload that shall be sent to the `call service` node to take the action (for example turn off a switch). |
|
|
874
|
+
| payloadToResetAction | The payload that shall be sent to the `call service` node to reset the action (for example turn a switch back on again). |
|
|
875
|
+
| nameOfStrategyToOverride | The name of the strategy node that shall be overridden by this action. |
|
|
876
|
+
|
|
877
|
+
::: warning Action type
|
|
878
|
+
Use either `nameOfStrategyToOverride` or `payloadToTakeAction` and `payloadToResetAction`. If you use them both at the same time, both actions are performed.
|
|
879
|
+
:::
|
|
858
880
|
|
|
859
881
|
::: tip Actions order
|
|
860
882
|
Actions to reduce consumption are taken in the order they appear in the `actions` array until enough reduction has been done,
|
|
@@ -889,20 +911,7 @@ const actions = [
|
|
|
889
911
|
minAlarmLevel: 3,
|
|
890
912
|
reduceWhenRecommended: true,
|
|
891
913
|
minTimeOffSec: 300,
|
|
892
|
-
|
|
893
|
-
domain: "switch",
|
|
894
|
-
service: "turn_off",
|
|
895
|
-
target: {
|
|
896
|
-
entity_id: ["switch.varmtvannsbereder"],
|
|
897
|
-
},
|
|
898
|
-
},
|
|
899
|
-
payloadToResetAction: {
|
|
900
|
-
domain: "switch",
|
|
901
|
-
service: "turn_on",
|
|
902
|
-
target: {
|
|
903
|
-
entity_id: ["switch.varmtvannsbereder"],
|
|
904
|
-
},
|
|
905
|
-
},
|
|
914
|
+
nameOfStrategyToOverride: "Lowest Price VVB",
|
|
906
915
|
},
|
|
907
916
|
{
|
|
908
917
|
consumption: "sensor.varme_gulv_bad_electric_consumption_w_2",
|
|
@@ -911,20 +920,7 @@ const actions = [
|
|
|
911
920
|
minAlarmLevel: 3,
|
|
912
921
|
reduceWhenRecommended: true,
|
|
913
922
|
minTimeOffSec: 300,
|
|
914
|
-
|
|
915
|
-
domain: "climate",
|
|
916
|
-
service: "turn_off",
|
|
917
|
-
target: {
|
|
918
|
-
entity_id: ["climate.varme_gulv_bad_2"],
|
|
919
|
-
},
|
|
920
|
-
},
|
|
921
|
-
payloadToResetAction: {
|
|
922
|
-
domain: "climate",
|
|
923
|
-
service: "turn_on",
|
|
924
|
-
target: {
|
|
925
|
-
entity_id: ["climate.varme_gulv_bad_2"],
|
|
926
|
-
},
|
|
927
|
-
},
|
|
923
|
+
nameOfStrategyToOverride: "Lowest Price Varmekabel",
|
|
928
924
|
},
|
|
929
925
|
{
|
|
930
926
|
consumption: "sensor.varme_gulv_gang_electric_consumption_w",
|
|
@@ -1030,7 +1026,17 @@ function takeAction(action, consumption) {
|
|
|
1030
1026
|
data: msg.payload,
|
|
1031
1027
|
action,
|
|
1032
1028
|
};
|
|
1033
|
-
|
|
1029
|
+
|
|
1030
|
+
// output1 is for actions
|
|
1031
|
+
const output1 = action.payloadToTakeAction ? { payload: action.payloadToTakeAction } : null;
|
|
1032
|
+
// output 2 is for overriding PS strategies
|
|
1033
|
+
const output2 = action.nameOfStrategyToOverride
|
|
1034
|
+
? { payload: { config: { override: "off" }, name: action.nameOfStrategyToOverride } }
|
|
1035
|
+
: null;
|
|
1036
|
+
// output 3 is for logging
|
|
1037
|
+
const output3 = { payload: info };
|
|
1038
|
+
|
|
1039
|
+
node.send([output1, output2, output3]);
|
|
1034
1040
|
reductionRequired = Math.max(0, reductionRequired - consumption);
|
|
1035
1041
|
reductionRecommended = Math.max(0, reductionRecommended - consumption);
|
|
1036
1042
|
action.actionTaken = true;
|
|
@@ -1042,7 +1048,7 @@ function takeAction(action, consumption) {
|
|
|
1042
1048
|
function getConsumption(consumption) {
|
|
1043
1049
|
if (typeof consumption === "string") {
|
|
1044
1050
|
const sensor = ha.states[consumption];
|
|
1045
|
-
return sensor.state;
|
|
1051
|
+
return sensor.state / 1000;
|
|
1046
1052
|
} else if (typeof consumption === "number") {
|
|
1047
1053
|
return consumption;
|
|
1048
1054
|
} else if (typeof consumption === "function") {
|
|
@@ -1102,7 +1108,13 @@ function resetAction(action) {
|
|
|
1102
1108
|
data: msg.paylaod,
|
|
1103
1109
|
action,
|
|
1104
1110
|
};
|
|
1105
|
-
|
|
1111
|
+
const output1 = action.payloadToResetAction ? { payload: action.payloadToResetAction } : null;
|
|
1112
|
+
const output2 = action.nameOfStrategyToOverride
|
|
1113
|
+
? { payload: { config: { override: "auto" }, name: action.nameOfStrategyToOverride } }
|
|
1114
|
+
: null;
|
|
1115
|
+
const output3 = { payload: info };
|
|
1116
|
+
|
|
1117
|
+
node.send([output1, output2, output3]);
|
|
1106
1118
|
increasePossible -= action.savedConsumption;
|
|
1107
1119
|
action.actionTaken = false;
|
|
1108
1120
|
action.savedConsumption = 0;
|
|
@@ -1126,7 +1138,7 @@ actions
|
|
|
1126
1138
|
|
|
1127
1139
|
### Perform action
|
|
1128
1140
|
|
|
1129
|
-
This is a `call service` node used to perform the actions (both taking actions and resetting actions).
|
|
1141
|
+
This is a `call service` node used to perform the call service actions (both taking actions and resetting actions).
|
|
1130
1142
|
There is no setup here except selecting the HA server.
|
|
1131
1143
|
|
|
1132
1144
|
### Save actions to file
|
|
@@ -1138,16 +1150,11 @@ Please make sure the file name configured works for you (for example that the fo
|
|
|
1138
1150
|
|
|
1139
1151
|
This is supposed to catch any errors in the action-related nodes, and log them to the file.
|
|
1140
1152
|
|
|
1141
|
-
### Entity nodes
|
|
1142
|
-
|
|
1143
|
-
There is a large number of `entity` nodes, one for each sensor value that is created with values from the `Calculate values` node.
|
|
1144
|
-
See [Calculated sensor values](#calculated-sensor-values) for description of each sensor value.
|
|
1145
|
-
|
|
1146
1153
|
## The code
|
|
1147
1154
|
|
|
1148
1155
|
Below is the code for the Node-RED flow.
|
|
1149
1156
|
Copy the code and paste it to Node-RED using Import in the NR menu.
|
|
1150
1157
|
|
|
1151
1158
|
::: details Flow code
|
|
1152
|
-
@[code](
|
|
1159
|
+
@[code](../../examples/example-grid-tariff-capacity-flow.json)
|
|
1153
1160
|
:::
|
|
Binary file
|