node-red-contrib-power-saver 3.5.0 → 3.5.4
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/.vuepress/config.js +2 -0
- package/docs/changelog/README.md +17 -0
- package/docs/examples/README.md +2 -0
- package/docs/examples/example-visualize-on-off/example-visualize-on-off.md +41 -0
- package/docs/examples/example-visualize-on-off/lovelace.jpg +0 -0
- package/docs/examples/example-visualize-on-off/lovelace.yaml +89 -0
- package/docs/examples/example-visualize-on-off/nodes.json +88 -0
- package/docs/examples/example-visualize-on-off/nodes.png +0 -0
- package/package.json +5 -4
- package/src/handle-input.js +5 -5
- package/src/strategy-lowest-price.js +1 -1
- package/src/utils.js +6 -2
package/docs/.vuepress/config.js
CHANGED
|
@@ -49,6 +49,7 @@ module.exports = {
|
|
|
49
49
|
"/examples/example-tibber-mqtt.md",
|
|
50
50
|
"/examples/example-heat-capacitor.md",
|
|
51
51
|
"/examples/example-cascade-temperature-control.md",
|
|
52
|
+
"/examples/example-visualize-on-off/example-visualize-on-off.md",
|
|
52
53
|
],
|
|
53
54
|
},
|
|
54
55
|
],
|
|
@@ -65,5 +66,6 @@ module.exports = {
|
|
|
65
66
|
componentsDir: path.resolve(__dirname, "./components"),
|
|
66
67
|
},
|
|
67
68
|
],
|
|
69
|
+
["@vuepress/plugin-search"],
|
|
68
70
|
],
|
|
69
71
|
};
|
package/docs/changelog/README.md
CHANGED
|
@@ -6,6 +6,23 @@ sidebar: "auto"
|
|
|
6
6
|
|
|
7
7
|
List the most significant changes, starting in version 1.0.9.
|
|
8
8
|
|
|
9
|
+
## 3.5.4
|
|
10
|
+
|
|
11
|
+
- Fix bug in context selection
|
|
12
|
+
- Add example for visualization in Lovelace
|
|
13
|
+
|
|
14
|
+
## 3.5.3
|
|
15
|
+
|
|
16
|
+
- Fix a couple of bugs in how context is used.
|
|
17
|
+
|
|
18
|
+
## 3.5.2
|
|
19
|
+
|
|
20
|
+
- Re-introduce the search bar, after Vuepress upgrade.
|
|
21
|
+
|
|
22
|
+
## 3.5.1
|
|
23
|
+
|
|
24
|
+
- Update github actions to deploy automatically to the npm library.
|
|
25
|
+
|
|
9
26
|
## 3.5.0
|
|
10
27
|
|
|
11
28
|
- Select what context storage to store data in the node configuration.
|
package/docs/examples/README.md
CHANGED
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
|
|
11
11
|
[Cascade temperature control](./example-cascade-temperature-control)
|
|
12
12
|
|
|
13
|
+
[Visualize on/off, price and consumption in Lovelace](example-visualize-on-off/example-visualize-on-off)
|
|
14
|
+
|
|
13
15
|
## User provided examples
|
|
14
16
|
|
|
15
17
|
[Output schedule to a sensor entity](./example-next-schedule-entity.md) (by Stefan)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Lovelace Visualization
|
|
2
|
+
|
|
3
|
+
The source of this example is made by Kim Storøy, and Otto Paulsen has written the documentation and made some changes to the files.
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
## Introduction
|
|
8
|
+
|
|
9
|
+
With this example you can visualize the schedule together with the price and the consumption in Lovelace, as shown above.
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
You need the following installed before you can use this example:
|
|
14
|
+
|
|
15
|
+
- The [Node-RED Companion Integration](https://github.com/zachowj/hass-node-red).
|
|
16
|
+
- The [apexcharts-card](https://github.com/RomRider/apexcharts-card)
|
|
17
|
+
- The Tibber integration (Configuration -> Integrations)
|
|
18
|
+
|
|
19
|
+
If you don't already have them, install them first.
|
|
20
|
+
|
|
21
|
+
In Node-RED, select `Import` from the menu, and paste the code for nodes below. Connect them like this:
|
|
22
|
+
|
|
23
|
+

|
|
24
|
+
|
|
25
|
+
In Lovelace, use the Lovelace code below for the new card.
|
|
26
|
+
Replace the `sensor.accumulated_consumption_current_hour_your_address`
|
|
27
|
+
with the correct name for your corresponding sensor in your Tibber integration.
|
|
28
|
+
|
|
29
|
+
### Code
|
|
30
|
+
|
|
31
|
+
::: details Nodes
|
|
32
|
+
|
|
33
|
+
@[code](./nodes.json)
|
|
34
|
+
|
|
35
|
+
:::
|
|
36
|
+
|
|
37
|
+
::: details Lovelace
|
|
38
|
+
|
|
39
|
+
@[code](./lovelace.yaml)
|
|
40
|
+
|
|
41
|
+
:::
|
|
Binary file
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
type: custom:apexcharts-card
|
|
2
|
+
header:
|
|
3
|
+
show: true
|
|
4
|
+
title: Pris 48t + Forbruk + Powersaver
|
|
5
|
+
now:
|
|
6
|
+
show: true
|
|
7
|
+
label: Nå
|
|
8
|
+
graph_span: 2d
|
|
9
|
+
span:
|
|
10
|
+
start: day
|
|
11
|
+
apex_config:
|
|
12
|
+
stroke:
|
|
13
|
+
width: 2
|
|
14
|
+
dataLabels:
|
|
15
|
+
enabled: true
|
|
16
|
+
fill:
|
|
17
|
+
type: gradient
|
|
18
|
+
gradient:
|
|
19
|
+
shadeIntensity: 1
|
|
20
|
+
inverseColors: false
|
|
21
|
+
opacityFrom: 0.45
|
|
22
|
+
opacityTo: 0.05
|
|
23
|
+
stops:
|
|
24
|
+
- 10
|
|
25
|
+
- 50
|
|
26
|
+
- 75
|
|
27
|
+
- 1000
|
|
28
|
+
legend:
|
|
29
|
+
show: false
|
|
30
|
+
yaxis:
|
|
31
|
+
- id: price
|
|
32
|
+
show: true
|
|
33
|
+
decimalsInFloat: 1
|
|
34
|
+
floating: false
|
|
35
|
+
forceNiceScale: true
|
|
36
|
+
extend_to: end
|
|
37
|
+
- id: usage
|
|
38
|
+
show: true
|
|
39
|
+
opposite: true
|
|
40
|
+
decimalsInFloat: 1
|
|
41
|
+
floating: false
|
|
42
|
+
forceNiceScale: true
|
|
43
|
+
extend_to: end
|
|
44
|
+
- id: powersaver
|
|
45
|
+
show: false
|
|
46
|
+
decimalsInFloat: 0
|
|
47
|
+
floating: false
|
|
48
|
+
extend_to: now
|
|
49
|
+
series:
|
|
50
|
+
- entity: sensor.powersaver
|
|
51
|
+
yaxis_id: price
|
|
52
|
+
extend_to: now
|
|
53
|
+
name: Pris
|
|
54
|
+
type: area
|
|
55
|
+
curve: smooth
|
|
56
|
+
color: tomato
|
|
57
|
+
show:
|
|
58
|
+
legend_value: false
|
|
59
|
+
data_generator: |
|
|
60
|
+
return entity.attributes.hours.map((entry) => {
|
|
61
|
+
return [new Date(entry.start), entry.price];
|
|
62
|
+
});
|
|
63
|
+
- entity: sensor.accumulated_consumption_current_hour_xxxx
|
|
64
|
+
yaxis_id: usage
|
|
65
|
+
type: column
|
|
66
|
+
name: Forbruk
|
|
67
|
+
group_by:
|
|
68
|
+
func: max
|
|
69
|
+
show:
|
|
70
|
+
legend_value: false
|
|
71
|
+
- entity: sensor.powersaver
|
|
72
|
+
data_generator: |
|
|
73
|
+
return entity.attributes.hours.map((entry) => {
|
|
74
|
+
return [new Date(entry.start), entry.onOff];
|
|
75
|
+
});
|
|
76
|
+
yaxis_id: powersaver
|
|
77
|
+
name: ' '
|
|
78
|
+
type: area
|
|
79
|
+
color: rgb(0, 255, 0)
|
|
80
|
+
opacity: 0.2
|
|
81
|
+
stroke_width: 0
|
|
82
|
+
curve: stepline
|
|
83
|
+
group_by:
|
|
84
|
+
func: min
|
|
85
|
+
show:
|
|
86
|
+
legend_value: false
|
|
87
|
+
in_header: false
|
|
88
|
+
name_in_header: false
|
|
89
|
+
datalabels: false
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "eab799518168f5a3",
|
|
4
|
+
"type": "ha-entity",
|
|
5
|
+
"z": "d938c47f.3398f8",
|
|
6
|
+
"name": "Info fra PS til HA",
|
|
7
|
+
"server": "ec4a12a1.b2be9",
|
|
8
|
+
"version": 2,
|
|
9
|
+
"debugenabled": false,
|
|
10
|
+
"outputs": 1,
|
|
11
|
+
"entityType": "sensor",
|
|
12
|
+
"config": [
|
|
13
|
+
{
|
|
14
|
+
"property": "name",
|
|
15
|
+
"value": "Powersaver"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"property": "device_class",
|
|
19
|
+
"value": ""
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"property": "icon",
|
|
23
|
+
"value": ""
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"property": "unit_of_measurement",
|
|
27
|
+
"value": ""
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"property": "state_class",
|
|
31
|
+
"value": ""
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"property": "last_reset",
|
|
35
|
+
"value": ""
|
|
36
|
+
}
|
|
37
|
+
],
|
|
38
|
+
"state": "payload",
|
|
39
|
+
"stateType": "str",
|
|
40
|
+
"attributes": [
|
|
41
|
+
{
|
|
42
|
+
"property": "Schedule",
|
|
43
|
+
"value": "payload.schedule",
|
|
44
|
+
"valueType": "msg"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"property": "Hours",
|
|
48
|
+
"value": "payload.hours",
|
|
49
|
+
"valueType": "msg"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"property": "Control",
|
|
53
|
+
"value": "payload.hours[0].onOff",
|
|
54
|
+
"valueType": "str"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"property": "Current",
|
|
58
|
+
"value": "payload.current",
|
|
59
|
+
"valueType": "str"
|
|
60
|
+
}
|
|
61
|
+
],
|
|
62
|
+
"resend": true,
|
|
63
|
+
"outputLocation": "payload",
|
|
64
|
+
"outputLocationType": "none",
|
|
65
|
+
"inputOverride": "allow",
|
|
66
|
+
"outputOnStateChange": false,
|
|
67
|
+
"outputPayload": "",
|
|
68
|
+
"outputPayloadType": "str",
|
|
69
|
+
"x": 830,
|
|
70
|
+
"y": 630,
|
|
71
|
+
"wires": [[]]
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"id": "cad33a63f66ef72e",
|
|
75
|
+
"type": "function",
|
|
76
|
+
"z": "d938c47f.3398f8",
|
|
77
|
+
"name": "Convert true/false to 1/0",
|
|
78
|
+
"func": "msg.payload.hours.forEach(h => h.onOff = h.onOff ? \"1\" : \"0\")\nreturn msg;",
|
|
79
|
+
"outputs": 1,
|
|
80
|
+
"noerr": 0,
|
|
81
|
+
"initialize": "",
|
|
82
|
+
"finalize": "",
|
|
83
|
+
"libs": [],
|
|
84
|
+
"x": 550,
|
|
85
|
+
"y": 630,
|
|
86
|
+
"wires": [["eab799518168f5a3", "37a23d88cfc668f2"]]
|
|
87
|
+
}
|
|
88
|
+
]
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-red-contrib-power-saver",
|
|
3
|
-
"version": "3.5.
|
|
3
|
+
"version": "3.5.4",
|
|
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": {
|
|
@@ -47,13 +47,14 @@
|
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@vuepress/bundler-vite": "^2.0.0-beta.36",
|
|
49
49
|
"@vuepress/plugin-register-components": "^2.0.0-beta.36",
|
|
50
|
+
"@vuepress/plugin-search": "^2.0.0-beta.38",
|
|
50
51
|
"@vuepress/utils": "^2.0.0-beta.35",
|
|
51
|
-
"sass-loader": "^12.6.0",
|
|
52
|
-
"vuepress": "^2.0.0-beta.36",
|
|
53
52
|
"expect": "^27.5.1",
|
|
54
53
|
"mocha": "^9.2.0",
|
|
55
54
|
"node-red": "^2.2.2",
|
|
56
|
-
"node-red-node-test-helper": "^0.2.7"
|
|
55
|
+
"node-red-node-test-helper": "^0.2.7",
|
|
56
|
+
"sass-loader": "^12.6.0",
|
|
57
|
+
"vuepress": "^2.0.0-beta.36"
|
|
57
58
|
},
|
|
58
59
|
"dependencies": {
|
|
59
60
|
"floating-vue": "^2.0.0-beta.6",
|
package/src/handle-input.js
CHANGED
|
@@ -4,6 +4,8 @@ const { version } = require("../package.json");
|
|
|
4
4
|
|
|
5
5
|
function handleStrategyInput(node, msg, doPlanning) {
|
|
6
6
|
const effectiveConfig = getEffectiveConfig(node, msg);
|
|
7
|
+
// Store config variables in node
|
|
8
|
+
Object.keys(effectiveConfig).forEach((key) => (node[key] = effectiveConfig[key]));
|
|
7
9
|
|
|
8
10
|
if (!validateInput(node, msg)) {
|
|
9
11
|
return;
|
|
@@ -22,11 +24,12 @@ function handleStrategyInput(node, msg, doPlanning) {
|
|
|
22
24
|
.set(["lastPlan", "lastPriceData", "lastSource"], [undefined, undefined, undefined], node.contextStorage);
|
|
23
25
|
deleteSavedScheduleBefore(node, DateTime.now().plus({ days: 2 }), 100);
|
|
24
26
|
}
|
|
27
|
+
|
|
25
28
|
let { priceData, source } = getPriceData(node, msg);
|
|
26
29
|
if (!priceData) {
|
|
27
30
|
// Use last saved price data
|
|
28
|
-
priceData = node.context().get("lastPriceData");
|
|
29
|
-
source = node.context().get("lastSource");
|
|
31
|
+
priceData = node.context().get("lastPriceData", node.contextStorage);
|
|
32
|
+
source = node.context().get("lastSource", node.contextStorage);
|
|
30
33
|
const message = "Using saved prices";
|
|
31
34
|
node.warn(message);
|
|
32
35
|
node.status({ fill: "green", shape: "ring", text: message });
|
|
@@ -39,9 +42,6 @@ function handleStrategyInput(node, msg, doPlanning) {
|
|
|
39
42
|
}
|
|
40
43
|
const planFromTime = msg.payload.time ? DateTime.fromISO(msg.payload.time) : DateTime.now();
|
|
41
44
|
|
|
42
|
-
// Store config variables in node
|
|
43
|
-
Object.keys(effectiveConfig).forEach((key) => (node[key] = effectiveConfig[key]));
|
|
44
|
-
|
|
45
45
|
clearTimeout(node.schedulingTimeout);
|
|
46
46
|
|
|
47
47
|
const dates = [...new Set(priceData.map((v) => DateTime.fromISO(v.start).toISODate()))];
|
|
@@ -19,7 +19,7 @@ module.exports = function (RED) {
|
|
|
19
19
|
outputOutsidePeriod: booleanConfig(config.outputOutsidePeriod),
|
|
20
20
|
contextStorage: config.contextStorage || "default",
|
|
21
21
|
};
|
|
22
|
-
node.context().set("config", originalConfig
|
|
22
|
+
node.context().set("config", originalConfig);
|
|
23
23
|
|
|
24
24
|
node.on("close", function () {
|
|
25
25
|
clearTimeout(node.schedulingTimeout);
|
package/src/utils.js
CHANGED
|
@@ -57,14 +57,18 @@ function getDiff(large, small) {
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
function getEffectiveConfig(node, msg) {
|
|
60
|
-
const res = node.context().get("config"
|
|
60
|
+
const res = node.context().get("config");
|
|
61
|
+
if (!res) {
|
|
62
|
+
node.error("Node has no config");
|
|
63
|
+
return {};
|
|
64
|
+
}
|
|
61
65
|
const isConfigMsg = !!msg?.payload?.config;
|
|
62
66
|
if (isConfigMsg) {
|
|
63
67
|
const inputConfig = msg.payload.config;
|
|
64
68
|
Object.keys(inputConfig).forEach((key) => {
|
|
65
69
|
res[key] = inputConfig[key];
|
|
66
70
|
});
|
|
67
|
-
node.context().set("config", res
|
|
71
|
+
node.context().set("config", res);
|
|
68
72
|
}
|
|
69
73
|
return res;
|
|
70
74
|
}
|