node-red-contrib-power-saver 4.1.1 → 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.
@@ -38,3 +38,9 @@ onMounted(() => {
38
38
  (adsbygoogle = window.adsbygoogle || []).push({});
39
39
  });
40
40
  </script>
41
+
42
+ <style scoped>
43
+ ins {
44
+ margin-bottom: 30px;
45
+ }
46
+ </style>
@@ -7,6 +7,15 @@ sidebarDepth: 1
7
7
 
8
8
  List the most significant changes.
9
9
 
10
+ ## 4.1.3
11
+
12
+ - Fix bug that saved some data in wrong context storage.
13
+
14
+ ## 4.1.2
15
+
16
+ - Fix so configured values for output are sent, not only true/false.
17
+ - Catch error from Elvia API so NR does not crash.
18
+
10
19
  ## 4.1.1
11
20
 
12
21
  - Update dependencies
@@ -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
 
@@ -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
  ![Capacity Flow](../images/example-capacity-flow.png)
105
29
 
106
- The first part of nodes (upper left) is used to read consumption from Tibber and to perform all calculations.
107
- The second part (right) is used to update a set of sensors in HA. You can use those sensor for many
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 third part (bottom left) is used to take actions in order to reduce power consumption,
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. There is one `entity` node for each sensor value.
180
- You may delete the entity nodes for sensor values you don't need.
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
 
@@ -526,7 +451,10 @@ context.set("buffer", buffer);
526
451
 
527
452
  // Calculate buffer
528
453
  const periodMs = buffer[buffer.length - 1].timeMs - buffer[0].timeMs;
529
- const consumptionInPeriod = buffer[buffer.length - 1].accumulatedConsumption - buffer[0].accumulatedConsumption;
454
+ let consumptionInPeriod = buffer[buffer.length - 1].accumulatedConsumption - buffer[0].accumulatedConsumption;
455
+ if (consumptionInPeriod < 0) {
456
+ consumptionInPeriod = 0;
457
+ }
530
458
  if (periodMs === 0) {
531
459
  return; // First item in buffer
532
460
  }
@@ -642,7 +570,7 @@ See [Calculated sensor values](#calculated-sensor-values) for description of the
642
570
 
643
571
  ```js
644
572
  const HA_NAME = "homeAssistant"; // Your HA name
645
- const STEPS = [2, 5, 10, 15, 20];
573
+ const STEPS = [10, 15, 20];
646
574
  const MAX_COUNTING = 3; // Number of days to calculate month
647
575
  const BUFFER = 0.5; // Closer to limit increases level
648
576
  const SAFE_ZONE = 2; // Further from limit reduces level
@@ -790,6 +718,8 @@ const payload = {
790
718
  highestCounting,
791
719
  highestCountingWithCurrent,
792
720
  highestToday,
721
+ highestTodayConsumption: highestToday.consumption,
722
+ highestTodayFrom: highestToday.from,
793
723
  currentMonthlyEstimate: Math.round(currentMonthlyEstimate * RESOLUTION) / RESOLUTION,
794
724
  accumulatedConsumptionLastHour: Math.round(accumulatedConsumptionLastHour * RESOLUTION) / RESOLUTION,
795
725
  consumptionLeft: Math.round(consumptionLeft * RESOLUTION) / RESOLUTION,
@@ -815,6 +745,61 @@ return msg;
815
745
 
816
746
  :::
817
747
 
748
+ ### Update sensors
749
+
750
+ This function node maps the values from the previous node to entity_ids that shall be updated in HA.
751
+ Then, for each sensor, it sends output that uses the API node to perform the actual update.
752
+
753
+ You can remove sensors that you do not want.
754
+
755
+ ::: details Code
756
+
757
+ <CodeGroup>
758
+ <CodeGroupItem title="On Message" active>
759
+
760
+ ```js
761
+ const sensors = [
762
+ { id: "sensor.ps_cap_status", value: "status", uom: null },
763
+ { id: "binary_sensor.ps_cap_ok", value: "statusOk", uom: null },
764
+ { id: "binary_sensor.ps_cap_warning", value: "statusWarning", uom: null },
765
+ { id: "binary_sensor.ps_cap_alarm", value: "statusAlarm", uom: null },
766
+ { id: "sensor.ps_cap_alarm_level", value: "alarmLevel", uom: null },
767
+ { id: "sensor.ps_cap_current_step", value: "currentStep", uom: "kW" },
768
+ { id: "sensor.ps_cap_hour_estimate", value: "hourEstimate", uom: "kW" },
769
+ { id: "sensor.ps_cap_current_hour_ranking", value: "currentHourRanking", uom: null },
770
+ { id: "sensor.ps_cap_monthly_estimate", value: "currentMonthlyEstimate", uom: "kW" },
771
+ { id: "sensor.ps_cap_highest_today", value: "highestTodayConsumption", uom: "kW" },
772
+ { id: "sensor.ps_cap_highest_today_time", value: "highestTodayFrom", uom: null },
773
+ { id: "sensor.ps_cap_reduction_required", value: "reductionRequired", uom: "kW" },
774
+ { id: "sensor.ps_cap_reduction_recommended", value: "reductionRecommended", uom: "kW" },
775
+ { id: "sensor.ps_cap_increase_possible", value: "increasePossible", uom: "kW" },
776
+ { id: "sensor.ps_cap_estimate_rest_of_hour", value: "consumptionLeft", uom: "kW" },
777
+ { id: "sensor.ps_cap_consumption_accumulated_hour", value: "accumulatedConsumptionLastHour", uom: "kW" },
778
+ { id: "sensor.ps_cap_time_left", value: "timeLeftSec", uom: "s" },
779
+ { id: "sensor.ps_cap_consumption_now", value: "averageConsumptionNow", uom: "kW" },
780
+ ];
781
+
782
+ sensors.forEach((sensor) => {
783
+ const payload = {
784
+ protocol: "http",
785
+ method: "post",
786
+ path: "/api/states/" + sensor.id,
787
+ data: {
788
+ state: msg.payload[sensor.value],
789
+ attributes: { unit_of_measurement: sensor.uom },
790
+ },
791
+ };
792
+ node.send({ payload });
793
+ });
794
+ ```
795
+
796
+ </CodeGroupItem>
797
+ </CodeGroup>
798
+
799
+ :::
800
+
801
+ ### Set entity
802
+
818
803
  ### Reduction Actions
819
804
 
820
805
  This is where you set up actions to be taken in case reduction is required or recommended.
@@ -827,34 +812,74 @@ In the **On Start** tab of this node, you set up the actions by writing a Javasc
827
812
  the `actions` array.
828
813
  The example shows some actions, but you may set up any number of actions.
829
814
 
830
- Actions are taken by sending a payload to a HA `call service` node (the `Perform action` node).
815
+ There are two types of actions that can be sent:
816
+
817
+ #### Call service actions
818
+
819
+ These actions are taken by sending a payload to a HA `call service` node (the `Perform action` node).
831
820
  The items in the `actions` array contains the payload you need to send to the `call service` node
832
821
  in order to take action, and the payload you need to send to the same `call service` node
833
822
  in order to reset the action.
834
823
 
824
+ #### Override Power Saver actions
825
+
826
+ These actions are taken by sending an override message to one or more strategy nodes.
827
+ Reduction is done by sending override `off`, and reset is done by sending override `auto`.
828
+ To use this type of action, specify the name of the strategy node that shall be overridden
829
+ in the `nameOfStrategyToOverride` attribute of the action.
830
+
831
+ Then send output 2 from the `Reduction Actions` node to the input of the strategy node.
832
+
833
+ You may have multiple actions controlling multiple strategy nodes. They are separated using the `name`
834
+ of the strategy node, so make sure they all have different names.
835
+ Output 2 must be sent to all strategy nodes that shall be controlled.
836
+
837
+ If you send the output to two strategy nodes with the same name, they will both be controlled.
838
+
839
+ You can use this to override the following nodes:
840
+
841
+ - Best Save
842
+ - Lowest Price
843
+ - Schedule Merger
844
+ - Fixed Schedule
845
+
846
+ If you are using the schedule merger node, you do not have to override the strategy nodes preceding the schedule merger,
847
+ only override the schedule merger.
848
+
849
+ #### Entity consumption
850
+
835
851
  An action may be to turn on or off a switch, to perform a climate control or what ever else
836
852
  you can do to control your entities.
837
853
 
838
- In order to know how much power that is saved by turning off an action, each item must have a sensor
839
- to give this information. This way, if a device is not using any power, the action will not be taken.
854
+ 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:
840
855
 
841
- ::: warning Entity consumption
842
- In the current example, there must be a sensor holding the consumption of the entity to
843
- turn off. If this is not possible, the code must be changed in order to work.
856
+ - The entity_id of a sensor that gives the consumption in kW (recommended)
857
+ - A number with the consumption in kW
858
+ - A function returning the consumption.
859
+
860
+ ::: warning Consumption must be kW
861
+ If your sensor gives consumption in W, not the required kW, you should find a way to divide it by 1000.
844
862
  :::
845
863
 
864
+ #### Actions configuration
865
+
846
866
  Each item in the `actions` array contains the following data:
847
867
 
848
- | Variable name | Description |
849
- | --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
850
- | 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. |
851
- | name | The name of the actions. Can be any thing. |
852
- | id | A unique id of the action. |
853
- | minAlarmLevel | The minimum alarm level that must be present to take this action. |
854
- | reduceWhenRecommended | If `true` the action will be taken when `Reduction Recommended` > 0. If `false` the action will be taken only when `Reduction Required` > 0 |
855
- | minTimeOffSec | The action will not be reset until minimum this number of seconds has passed since the action was taken. |
856
- | payloadToTakeAction | The payload that shall be sent to the `call service` node to take the action (for example turn off a switch). |
857
- | payloadToResetAction | The payload that shall be sent to the `call service` node to reset the action (for example turn a switch back on again). |
868
+ | Variable name | Description |
869
+ | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
870
+ | 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. |
871
+ | name | The name of the actions. Can be any thing. |
872
+ | id | A unique id of the action. |
873
+ | minAlarmLevel | The minimum alarm level that must be present to take this action. |
874
+ | reduceWhenRecommended | If `true` the action will be taken when `Reduction Recommended` > 0. If `false` the action will be taken only when `Reduction Required` > 0 |
875
+ | minTimeOffSec | The action will not be reset until minimum this number of seconds has passed since the action was taken. |
876
+ | payloadToTakeAction | The payload that shall be sent to the `call service` node to take the action (for example turn off a switch). |
877
+ | payloadToResetAction | The payload that shall be sent to the `call service` node to reset the action (for example turn a switch back on again). |
878
+ | nameOfStrategyToOverride | The name of the strategy node that shall be overridden by this action. |
879
+
880
+ ::: warning Action type
881
+ Use either `nameOfStrategyToOverride` or `payloadToTakeAction` and `payloadToResetAction`. If you use them both at the same time, both actions are performed.
882
+ :::
858
883
 
859
884
  ::: tip Actions order
860
885
  Actions to reduce consumption are taken in the order they appear in the `actions` array until enough reduction has been done,
@@ -873,6 +898,8 @@ If you don't want the actions, or you want to control actions another way,
873
898
  you can omit the action-related nodes and only use the nodes creating the sensors.
874
899
  :::
875
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
+
876
903
  ::: details Code
877
904
 
878
905
  <CodeGroup>
@@ -889,20 +916,7 @@ const actions = [
889
916
  minAlarmLevel: 3,
890
917
  reduceWhenRecommended: true,
891
918
  minTimeOffSec: 300,
892
- payloadToTakeAction: {
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
- },
919
+ nameOfStrategyToOverride: "Lowest Price VVB",
906
920
  },
907
921
  {
908
922
  consumption: "sensor.varme_gulv_bad_electric_consumption_w_2",
@@ -911,20 +925,7 @@ const actions = [
911
925
  minAlarmLevel: 3,
912
926
  reduceWhenRecommended: true,
913
927
  minTimeOffSec: 300,
914
- payloadToTakeAction: {
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
- },
928
+ nameOfStrategyToOverride: "Lowest Price Varmekabel",
928
929
  },
929
930
  {
930
931
  consumption: "sensor.varme_gulv_gang_electric_consumption_w",
@@ -1012,6 +1013,7 @@ flow.set("actions", actions);
1012
1013
 
1013
1014
  ```js
1014
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;
1015
1017
 
1016
1018
  const actions = flow.get("actions");
1017
1019
  const ha = global.get("homeassistant").homeAssistant;
@@ -1019,10 +1021,21 @@ const ha = global.get("homeassistant").homeAssistant;
1019
1021
  let reductionRequired = msg.payload.reductionRequired;
1020
1022
  let reductionRecommended = msg.payload.reductionRecommended;
1021
1023
 
1024
+ node.status({});
1025
+
1022
1026
  if (reductionRecommended <= 0) {
1023
1027
  return null;
1024
1028
  }
1025
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
+
1026
1039
  function takeAction(action, consumption) {
1027
1040
  const info = {
1028
1041
  time: new Date().toISOString(),
@@ -1030,7 +1043,17 @@ function takeAction(action, consumption) {
1030
1043
  data: msg.payload,
1031
1044
  action,
1032
1045
  };
1033
- node.send([{ payload: action.payloadToTakeAction }, { payload: info }]);
1046
+
1047
+ // output1 is for actions
1048
+ const output1 = action.payloadToTakeAction ? { payload: action.payloadToTakeAction } : null;
1049
+ // output 2 is for overriding PS strategies
1050
+ const output2 = action.nameOfStrategyToOverride
1051
+ ? { payload: { config: { override: "off" }, name: action.nameOfStrategyToOverride } }
1052
+ : null;
1053
+ // output 3 is for logging
1054
+ const output3 = { payload: info };
1055
+
1056
+ node.send([output1, output2, output3]);
1034
1057
  reductionRequired = Math.max(0, reductionRequired - consumption);
1035
1058
  reductionRecommended = Math.max(0, reductionRecommended - consumption);
1036
1059
  action.actionTaken = true;
@@ -1042,7 +1065,7 @@ function takeAction(action, consumption) {
1042
1065
  function getConsumption(consumption) {
1043
1066
  if (typeof consumption === "string") {
1044
1067
  const sensor = ha.states[consumption];
1045
- return sensor.state;
1068
+ return sensor.state / 1000;
1046
1069
  } else if (typeof consumption === "number") {
1047
1070
  return consumption;
1048
1071
  } else if (typeof consumption === "function") {
@@ -1099,10 +1122,16 @@ function resetAction(action) {
1099
1122
  const info = {
1100
1123
  time: new Date().toISOString(),
1101
1124
  name: "Reset action",
1102
- data: msg.paylaod,
1125
+ data: msg.payload,
1103
1126
  action,
1104
1127
  };
1105
- node.send([{ payload: action.payloadToResetAction }, { payload: info }]);
1128
+ const output1 = action.payloadToResetAction ? { payload: action.payloadToResetAction } : null;
1129
+ const output2 = action.nameOfStrategyToOverride
1130
+ ? { payload: { config: { override: "auto" }, name: action.nameOfStrategyToOverride } }
1131
+ : null;
1132
+ const output3 = { payload: info };
1133
+
1134
+ node.send([output1, output2, output3]);
1106
1135
  increasePossible -= action.savedConsumption;
1107
1136
  action.actionTaken = false;
1108
1137
  action.savedConsumption = 0;
@@ -1126,7 +1155,7 @@ actions
1126
1155
 
1127
1156
  ### Perform action
1128
1157
 
1129
- This is a `call service` node used to perform the actions (both taking actions and resetting actions).
1158
+ This is a `call service` node used to perform the call service actions (both taking actions and resetting actions).
1130
1159
  There is no setup here except selecting the HA server.
1131
1160
 
1132
1161
  ### Save actions to file
@@ -1138,16 +1167,11 @@ Please make sure the file name configured works for you (for example that the fo
1138
1167
 
1139
1168
  This is supposed to catch any errors in the action-related nodes, and log them to the file.
1140
1169
 
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
1170
  ## The code
1147
1171
 
1148
1172
  Below is the code for the Node-RED flow.
1149
1173
  Copy the code and paste it to Node-RED using Import in the NR menu.
1150
1174
 
1151
1175
  ::: details Flow code
1152
- @[code](./example-grid-tariff-capacity-flow.json)
1176
+ @[code](../../examples/example-grid-tariff-capacity-flow.json)
1153
1177
  :::
@@ -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
- <VippsPlakat/>
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.