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.
@@ -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
  };
@@ -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.
@@ -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
+ ![Lovelace image](./lovelace.jpg)
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
+ ![nodes](./nodes.png)
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
+ :::
@@ -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
+ ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-power-saver",
3
- "version": "3.5.0",
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",
@@ -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, node.contextStorage);
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", node.contextStorage);
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, node.contextStorage);
71
+ node.context().set("config", res);
68
72
  }
69
73
  return res;
70
74
  }