node-red-contrib-power-saver 3.0.5 → 3.0.9

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 (38) hide show
  1. package/docs/.vuepress/config.js +1 -1
  2. package/docs/.vuepress/dist/404.html +3 -3
  3. package/docs/.vuepress/dist/assets/js/app.190d977f.js +1 -0
  4. package/docs/.vuepress/dist/assets/js/runtime~app.36bc5048.js +1 -0
  5. package/docs/.vuepress/dist/assets/js/{v-510ed0d4.129ef915.js → v-510ed0d4.01d07aab.js} +1 -1
  6. package/docs/.vuepress/dist/changelog/index.html +4 -4
  7. package/docs/.vuepress/dist/contribute/index.html +3 -3
  8. package/docs/.vuepress/dist/examples/example-nordpool-current-state.html +3 -3
  9. package/docs/.vuepress/dist/examples/example-nordpool-events-state.html +3 -3
  10. package/docs/.vuepress/dist/examples/example-tibber-mqtt.html +3 -3
  11. package/docs/.vuepress/dist/examples/index.html +3 -3
  12. package/docs/.vuepress/dist/guide/index.html +3 -3
  13. package/docs/.vuepress/dist/index.html +3 -3
  14. package/docs/.vuepress/dist/nodes/index.html +3 -3
  15. package/docs/.vuepress/dist/nodes/old-power-saver-doc.html +3 -3
  16. package/docs/.vuepress/dist/nodes/power-saver.html +3 -3
  17. package/docs/.vuepress/dist/nodes/ps-elvia-add-tariff.html +3 -3
  18. package/docs/.vuepress/dist/nodes/ps-receive-price.html +3 -3
  19. package/docs/.vuepress/dist/nodes/ps-strategy-best-save.html +3 -3
  20. package/docs/.vuepress/dist/nodes/ps-strategy-lowest-price.html +3 -3
  21. package/docs/.vuepress/dist/nodes/strategy-input.html +3 -3
  22. package/docs/changelog/README.md +17 -0
  23. package/docs/examples/example-nordpool-current-state.md +172 -152
  24. package/docs/nodes/ps-receive-price.md +4 -0
  25. package/package.json +2 -3
  26. package/src/handle-input.js +1 -7
  27. package/src/power-saver.js +1 -5
  28. package/src/receive-price-functions.js +9 -17
  29. package/src/strategy-lowest-price.js +2 -1
  30. package/src/utils.js +6 -0
  31. package/test/data/lowest-price-input-missing-end.json +197 -0
  32. package/test/data/lowest-price-result-missing-end.json +319 -0
  33. package/test/data/nordpool-events-state-2.json +288 -0
  34. package/test/data/nordpool-prices-in-payload.json +287 -0
  35. package/test/receive-price.test.js +27 -4
  36. package/test/strategy-lowest-price.test.js +33 -0
  37. package/docs/.vuepress/dist/assets/js/app.342dc054.js +0 -1
  38. package/docs/.vuepress/dist/assets/js/runtime~app.0d53f24f.js +0 -1
@@ -10,157 +10,177 @@ In this example, data is read from the Nord Pool sensor in HA via the `current s
10
10
 
11
11
  ```json:no-line-numbers
12
12
  [
13
- {
14
- "id": "e2578f6a.210a8",
15
- "type": "debug",
16
- "z": "d938c47f.3398f8",
17
- "name": "Nord Pool result",
18
- "active": true,
19
- "tosidebar": true,
20
- "console": false,
21
- "tostatus": false,
22
- "complete": "true",
23
- "targetType": "full",
24
- "statusVal": "",
25
- "statusType": "auto",
26
- "x": 820,
27
- "y": 440,
28
- "wires": []
29
- },
30
- {
31
- "id": "48bcdcca.fe42a4",
32
- "type": "api-current-state",
33
- "z": "d938c47f.3398f8",
34
- "name": "Read Nord Pool",
35
- "server": "ec4a12a1.b2be9",
36
- "version": 2,
37
- "outputs": 1,
38
- "halt_if": "",
39
- "halt_if_type": "str",
40
- "halt_if_compare": "is",
41
- "entity_id": "sensor.nordpool_kwh_trheim_nok_3_095_025",
42
- "state_type": "str",
43
- "blockInputOverrides": false,
44
- "outputProperties": [
45
- {
46
- "property": "payload",
47
- "propertyType": "msg",
48
- "value": "",
49
- "valueType": "entityState"
50
- },
51
- {
52
- "property": "payload",
53
- "propertyType": "msg",
54
- "value": "",
55
- "valueType": "entity"
56
- }
57
- ],
58
- "x": 280,
59
- "y": 380,
60
- "wires": [["428d7c7ca88db95f"]]
61
- },
62
- {
63
- "id": "97cc8e58.4247a",
64
- "type": "inject",
65
- "z": "d938c47f.3398f8",
66
- "name": "",
67
- "props": [
68
- {
69
- "p": "payload"
70
- },
71
- {
72
- "p": "topic",
73
- "vt": "str"
74
- }
75
- ],
76
- "repeat": "3600",
77
- "crontab": "",
78
- "once": true,
79
- "onceDelay": 0.1,
80
- "topic": "",
81
- "payloadType": "date",
82
- "x": 110,
83
- "y": 380,
84
- "wires": [["48bcdcca.fe42a4"]]
85
- },
86
- {
87
- "id": "a6f2769b.1a62a8",
88
- "type": "api-call-service",
89
- "z": "d938c47f.3398f8",
90
- "name": "Turn on VVB",
91
- "server": "ec4a12a1.b2be9",
92
- "version": 3,
93
- "debugenabled": false,
94
- "service_domain": "switch",
95
- "service": "turn_on",
96
- "entityId": "switch.varmtvannsbereder_switch",
97
- "data": "",
98
- "dataType": "jsonata",
99
- "mergecontext": "",
100
- "mustacheAltTags": false,
101
- "outputProperties": [],
102
- "queue": "none",
103
- "x": 810,
104
- "y": 320,
105
- "wires": [[]]
106
- },
107
- {
108
- "id": "9fc75126.65dd3",
109
- "type": "api-call-service",
110
- "z": "d938c47f.3398f8",
111
- "name": "Turn off VVB",
112
- "server": "ec4a12a1.b2be9",
113
- "version": 3,
114
- "debugenabled": true,
115
- "service_domain": "switch",
116
- "service": "turn_off",
117
- "entityId": "switch.varmtvannsbereder_switch",
118
- "data": "",
119
- "dataType": "json",
120
- "mergecontext": "",
121
- "mustacheAltTags": false,
122
- "outputProperties": [],
123
- "queue": "none",
124
- "x": 810,
125
- "y": 360,
126
- "wires": [[]]
127
- },
128
- {
129
- "id": "428d7c7ca88db95f",
130
- "type": "ps-receive-price",
131
- "z": "d938c47f.3398f8",
132
- "name": "Price Receiver",
133
- "x": 460,
134
- "y": 380,
135
- "wires": [["4147bf0b99fe626f"]]
136
- },
137
- {
138
- "id": "4147bf0b99fe626f",
139
- "type": "ps-strategy-best-save",
140
- "z": "d938c47f.3398f8",
141
- "name": "Best Save",
142
- "maxHoursToSaveInSequence": "4",
143
- "minHoursOnAfterMaxSequenceSaved": "1",
144
- "minSaving": "0.03",
145
- "sendCurrentValueWhenRescheduling": true,
146
- "outputIfNoSchedule": "true",
147
- "scheduleOnlyFromCurrentTime": "false",
148
- "x": 630,
149
- "y": 380,
150
- "wires": [["a6f2769b.1a62a8"], ["9fc75126.65dd3"], ["e2578f6a.210a8"]]
151
- },
152
- {
153
- "id": "ec4a12a1.b2be9",
154
- "type": "server",
155
- "name": "Home Assistant",
156
- "version": 2,
157
- "addon": true,
158
- "rejectUnauthorizedCerts": true,
159
- "ha_boolean": "y|yes|true|on|home|open",
160
- "connectionDelay": true,
161
- "cacheJson": true,
162
- "heartbeat": false,
163
- "heartbeatInterval": 30
164
- }
13
+ {
14
+ "id": "e2578f6a.210a8",
15
+ "type": "debug",
16
+ "z": "d938c47f.3398f8",
17
+ "name": "Nord Pool result",
18
+ "active": false,
19
+ "tosidebar": true,
20
+ "console": false,
21
+ "tostatus": false,
22
+ "complete": "true",
23
+ "targetType": "full",
24
+ "statusVal": "",
25
+ "statusType": "auto",
26
+ "x": 820,
27
+ "y": 440,
28
+ "wires": []
29
+ },
30
+ {
31
+ "id": "48bcdcca.fe42a4",
32
+ "type": "api-current-state",
33
+ "z": "d938c47f.3398f8",
34
+ "name": "Read Nord Pool",
35
+ "server": "ec4a12a1.b2be9",
36
+ "version": 2,
37
+ "outputs": 1,
38
+ "halt_if": "",
39
+ "halt_if_type": "str",
40
+ "halt_if_compare": "is",
41
+ "entity_id": "sensor.nordpool_kwh_trheim_nok_3_095_025",
42
+ "state_type": "str",
43
+ "blockInputOverrides": false,
44
+ "outputProperties": [
45
+ {
46
+ "property": "payload",
47
+ "propertyType": "msg",
48
+ "value": "",
49
+ "valueType": "entity"
50
+ }
51
+ ],
52
+ "x": 280,
53
+ "y": 380,
54
+ "wires": [
55
+ [
56
+ "428d7c7ca88db95f"
57
+ ]
58
+ ]
59
+ },
60
+ {
61
+ "id": "97cc8e58.4247a",
62
+ "type": "inject",
63
+ "z": "d938c47f.3398f8",
64
+ "name": "",
65
+ "props": [
66
+ {
67
+ "p": "payload"
68
+ },
69
+ {
70
+ "p": "topic",
71
+ "vt": "str"
72
+ }
73
+ ],
74
+ "repeat": "3600",
75
+ "crontab": "",
76
+ "once": true,
77
+ "onceDelay": 0.1,
78
+ "topic": "",
79
+ "payloadType": "date",
80
+ "x": 110,
81
+ "y": 380,
82
+ "wires": [
83
+ [
84
+ "48bcdcca.fe42a4"
85
+ ]
86
+ ]
87
+ },
88
+ {
89
+ "id": "a6f2769b.1a62a8",
90
+ "type": "api-call-service",
91
+ "z": "d938c47f.3398f8",
92
+ "name": "Turn on VVB",
93
+ "server": "ec4a12a1.b2be9",
94
+ "version": 3,
95
+ "debugenabled": false,
96
+ "service_domain": "switch",
97
+ "service": "turn_on",
98
+ "entityId": "switch.varmtvannsbereder_switch",
99
+ "data": "",
100
+ "dataType": "jsonata",
101
+ "mergecontext": "",
102
+ "mustacheAltTags": false,
103
+ "outputProperties": [],
104
+ "queue": "none",
105
+ "x": 810,
106
+ "y": 340,
107
+ "wires": [
108
+ []
109
+ ]
110
+ },
111
+ {
112
+ "id": "9fc75126.65dd3",
113
+ "type": "api-call-service",
114
+ "z": "d938c47f.3398f8",
115
+ "name": "Turn off VVB",
116
+ "server": "ec4a12a1.b2be9",
117
+ "version": 3,
118
+ "debugenabled": true,
119
+ "service_domain": "switch",
120
+ "service": "turn_off",
121
+ "entityId": "switch.varmtvannsbereder_switch",
122
+ "data": "",
123
+ "dataType": "json",
124
+ "mergecontext": "",
125
+ "mustacheAltTags": false,
126
+ "outputProperties": [],
127
+ "queue": "none",
128
+ "x": 810,
129
+ "y": 380,
130
+ "wires": [
131
+ []
132
+ ]
133
+ },
134
+ {
135
+ "id": "428d7c7ca88db95f",
136
+ "type": "ps-receive-price",
137
+ "z": "d938c47f.3398f8",
138
+ "name": "Price Receiver",
139
+ "x": 460,
140
+ "y": 380,
141
+ "wires": [
142
+ [
143
+ "4147bf0b99fe626f"
144
+ ]
145
+ ]
146
+ },
147
+ {
148
+ "id": "4147bf0b99fe626f",
149
+ "type": "ps-strategy-best-save",
150
+ "z": "d938c47f.3398f8",
151
+ "name": "Best Save",
152
+ "maxHoursToSaveInSequence": "4",
153
+ "minHoursOnAfterMaxSequenceSaved": "1",
154
+ "minSaving": "0.03",
155
+ "sendCurrentValueWhenRescheduling": true,
156
+ "outputIfNoSchedule": "true",
157
+ "scheduleOnlyFromCurrentTime": "false",
158
+ "x": 630,
159
+ "y": 380,
160
+ "wires": [
161
+ [
162
+ "a6f2769b.1a62a8"
163
+ ],
164
+ [
165
+ "9fc75126.65dd3"
166
+ ],
167
+ [
168
+ "e2578f6a.210a8"
169
+ ]
170
+ ]
171
+ },
172
+ {
173
+ "id": "ec4a12a1.b2be9",
174
+ "type": "server",
175
+ "name": "Home Assistant",
176
+ "version": 2,
177
+ "addon": true,
178
+ "rejectUnauthorizedCerts": true,
179
+ "ha_boolean": "y|yes|true|on|home|open",
180
+ "connectionDelay": true,
181
+ "cacheJson": true,
182
+ "heartbeat": false,
183
+ "heartbeatInterval": 30
184
+ }
165
185
  ]
166
186
  ```
@@ -125,6 +125,10 @@ This is especially designed to work for Home Assistant (HA), and the [Nord Pool
125
125
 
126
126
  Data can be sent from both the `current state` node or the `events: state` node.
127
127
 
128
+ ::: tip Output properties
129
+ When using the `current state` node, configure the output properties to set `msg.payload` to `entity`.
130
+ :::
131
+
128
132
  [See example with Nord Pool and `current state` node](doc/example-nordpool-current-state.md)
129
133
 
130
134
  [See example with Nord Pool and `events: state` node](doc/example-nordpool-events-state.md)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-power-saver",
3
- "version": "3.0.5",
3
+ "version": "3.0.9",
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": {
@@ -28,8 +28,7 @@
28
28
  "ps-elvia-config": "src/elvia/elvia-config.js",
29
29
  "ps-elvia-tariff-types": "src/elvia/elvia-tariff-types.js",
30
30
  "ps-elvia-tariff": "src/elvia/elvia-tariff.js",
31
- "ps-elvia-add-tariff": "src/elvia/elvia-add-tariff.js",
32
- "ps-general-add-tariff": "src/general-add-tariff.js"
31
+ "ps-elvia-add-tariff": "src/elvia/elvia-add-tariff.js"
33
32
  }
34
33
  },
35
34
  "prettier": {
@@ -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
  };