node-red-contrib-power-saver 5.2.0 → 5.2.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/eslint.config.cjs +16 -0
- package/package.json +3 -1
- package/src/general-add-tariff-functions.js +0 -1
- package/src/handle-input.js +2 -2
- package/src/handle-output.js +0 -1
- package/src/light-saver-functions.js +9 -8
- package/src/light-saver.html +1 -1
- package/src/light-saver.js +3 -3
- package/src/receive-price-functions.js +1 -2
- package/src/schedule-merger-functions.js +1 -1
- package/src/strategy-best-save-functions.js +1 -1
- package/src/strategy-fixed-schedule.js +0 -1
- package/src/strategy-heat-capacitor-functions.js +1 -1
- package/src/strategy-heat-capacitor.js +19 -19
- package/src/utils.js +2 -2
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const js = require("@eslint/js");
|
|
2
|
+
const globals = require("globals");
|
|
3
|
+
|
|
4
|
+
module.exports = [
|
|
5
|
+
js.configs.recommended,
|
|
6
|
+
{
|
|
7
|
+
languageOptions: {
|
|
8
|
+
ecmaVersion: "latest",
|
|
9
|
+
globals: {
|
|
10
|
+
...globals.browser,
|
|
11
|
+
...globals.commonjs,
|
|
12
|
+
...globals.es2021,
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-red-contrib-power-saver",
|
|
3
|
-
"version": "5.2.
|
|
3
|
+
"version": "5.2.2",
|
|
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": {
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
"url": "https://github.com/ottopaulsen/node-red-contrib-power-saver.git"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
|
+
"@eslint/js": "^10.0.1",
|
|
51
52
|
"@vuepress/bundler-vite": "2.0.0-rc.26",
|
|
52
53
|
"@vuepress/plugin-google-analytics": "2.0.0-rc.123",
|
|
53
54
|
"@vuepress/plugin-register-components": "2.0.0-rc.123",
|
|
@@ -57,6 +58,7 @@
|
|
|
57
58
|
"chai": "6.2.2",
|
|
58
59
|
"eslint": "10.0.0",
|
|
59
60
|
"expect": "30.2.0",
|
|
61
|
+
"globals": "^17.4.0",
|
|
60
62
|
"mocha": "^11.7.5",
|
|
61
63
|
"node-red": "^4.1.5",
|
|
62
64
|
"node-red-node-test-helper": "0.3.6",
|
package/src/handle-input.js
CHANGED
|
@@ -86,7 +86,7 @@ function makePlanFromPriceData(node, msg, config, doPlanning, calcSavings) {
|
|
|
86
86
|
const schedule = trimScheduleToStart(fullSchedule, priceData[0].start);
|
|
87
87
|
addLastSwitchIfNoSchedule(schedule, minutes, config);
|
|
88
88
|
|
|
89
|
-
plan = {
|
|
89
|
+
const plan = {
|
|
90
90
|
minutes,
|
|
91
91
|
schedule,
|
|
92
92
|
source,
|
|
@@ -174,7 +174,7 @@ function trimScheduleToStart(schedule, startTime) {
|
|
|
174
174
|
|
|
175
175
|
function deleteSavedScheduleBefore(node, day, checkDays = 0) {
|
|
176
176
|
let date = day;
|
|
177
|
-
let data
|
|
177
|
+
let data;
|
|
178
178
|
let count = 0;
|
|
179
179
|
do {
|
|
180
180
|
date = date.plus({ days: -1 });
|
package/src/handle-output.js
CHANGED
|
@@ -73,7 +73,6 @@ function runSchedule(node, schedule, time, currentSent = false) {
|
|
|
73
73
|
const entry = remainingSchedule[0];
|
|
74
74
|
const nextTime = DateTime.fromISO(entry.time);
|
|
75
75
|
const wait = nextTime - time;
|
|
76
|
-
const onOff = entry.value ? "on" : "off";
|
|
77
76
|
const statusMessage = `${remainingSchedule.length} changes - ${
|
|
78
77
|
remainingSchedule[0].value ? "on" : "off"
|
|
79
78
|
} at ${nextTime.toFormat("HH:mm")}`;
|
|
@@ -249,7 +249,6 @@ function handleStateChange(event, config, state, node, homeAssistant, clock = nu
|
|
|
249
249
|
// Check if it's the brightness sensor
|
|
250
250
|
if (config.brightnessSensor && config.brightnessSensor.entity_id === entityId) {
|
|
251
251
|
const wasBrightnessAllowing = isBrightnessAllowingLights(config); // Check before update
|
|
252
|
-
const oldBrightness = config.brightnessSensor.state;
|
|
253
252
|
config.brightnessSensor.lastChanged = timestamp;
|
|
254
253
|
config.brightnessSensor.state = newState.state;
|
|
255
254
|
|
|
@@ -715,14 +714,16 @@ function fetchMissingStates(config, state, node, homeAssistant, clock = null) {
|
|
|
715
714
|
`Initial timedOut set to ${state.timedOut} (all triggers actually timed out: ${allTimedOut})`,
|
|
716
715
|
);
|
|
717
716
|
|
|
718
|
-
// If motion is detected at startup (timedOut is false), turn lights on
|
|
719
717
|
if (!allTimedOut) {
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
718
|
+
const levelConfig = findLevelConfig(config, clock);
|
|
719
|
+
state.lastImmediateTime = levelConfig && levelConfig.immediate === true ? levelConfig.fromTime : null;
|
|
720
|
+
debugLog(
|
|
721
|
+
config,
|
|
722
|
+
node,
|
|
723
|
+
`Startup preserved current light state while motion is still active${state.lastImmediateTime ? ` (immediate period ${state.lastImmediateTime} already active)` : ""}`,
|
|
724
|
+
);
|
|
725
|
+
} else {
|
|
726
|
+
state.lastImmediateTime = null;
|
|
726
727
|
}
|
|
727
728
|
|
|
728
729
|
return true; // Indicates that initial timedOut was set
|
package/src/light-saver.html
CHANGED
|
@@ -715,7 +715,7 @@
|
|
|
715
715
|
|
|
716
716
|
// Limit input (without spinner arrows)
|
|
717
717
|
const limitInput = $(
|
|
718
|
-
'<input type="number" id="node-input-brightnessLimit" class="brightness-limit-input"
|
|
718
|
+
'<input type="number" id="node-input-brightnessLimit" class="brightness-limit-input" max="100000" step="1"/>',
|
|
719
719
|
).css({
|
|
720
720
|
width: "60px",
|
|
721
721
|
padding: "3px",
|
package/src/light-saver.js
CHANGED
|
@@ -22,7 +22,7 @@ module.exports = function (RED) {
|
|
|
22
22
|
let runtimeOverride = null;
|
|
23
23
|
try {
|
|
24
24
|
runtimeOverride = node.context().get("override", node.contextStorage);
|
|
25
|
-
} catch
|
|
25
|
+
} catch {
|
|
26
26
|
// Context storage might not be available (e.g., in tests)
|
|
27
27
|
}
|
|
28
28
|
|
|
@@ -89,7 +89,7 @@ module.exports = function (RED) {
|
|
|
89
89
|
try {
|
|
90
90
|
node.context().set("override", nodeConfig.override, node.contextStorage);
|
|
91
91
|
debugLog(`Override saved to context storage: ${nodeConfig.override}`);
|
|
92
|
-
} catch
|
|
92
|
+
} catch {
|
|
93
93
|
// Silently fail if context storage not available
|
|
94
94
|
}
|
|
95
95
|
};
|
|
@@ -298,7 +298,7 @@ module.exports = function (RED) {
|
|
|
298
298
|
|
|
299
299
|
// Function to fetch current states from Home Assistant
|
|
300
300
|
const fetchMissingStates = function () {
|
|
301
|
-
|
|
301
|
+
funcs.fetchMissingStates(nodeConfig, state, nodeWrapper, homeAssistant);
|
|
302
302
|
|
|
303
303
|
// Fetch initial light states
|
|
304
304
|
try {
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
const { addEndToLast, validationFailure } = require("./utils");
|
|
2
|
-
const { DateTime } = require("luxon");
|
|
3
2
|
|
|
4
3
|
function getPriceData(node, msg) {
|
|
5
4
|
const isConfigMsg = !!msg?.payload?.config;
|
|
@@ -12,7 +11,7 @@ function getPriceData(node, msg) {
|
|
|
12
11
|
return null;
|
|
13
12
|
}
|
|
14
13
|
|
|
15
|
-
priceData = [...input.today, ...input.tomorrow];
|
|
14
|
+
const priceData = [...input.today, ...input.tomorrow];
|
|
16
15
|
|
|
17
16
|
addEndToLast(priceData);
|
|
18
17
|
|
|
@@ -119,7 +119,7 @@ function calculate(
|
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
savingsList.sort((b, a) => (b.saving === a.saving ? a.count - b.count : b.saving - a.saving));
|
|
122
|
-
let onOff = values.map((
|
|
122
|
+
let onOff = values.map(() => true); // Start with all on
|
|
123
123
|
|
|
124
124
|
// Find the best possible sequences
|
|
125
125
|
while (savingsList.length > 0) {
|
|
@@ -27,21 +27,21 @@ module.exports = function (RED) {
|
|
|
27
27
|
|
|
28
28
|
this.on("input", function (msg) {
|
|
29
29
|
if (!validateInput(node, msg)) return;
|
|
30
|
-
if (!
|
|
30
|
+
if (!("payload" in msg)) return;
|
|
31
31
|
|
|
32
|
-
if (msg.payload
|
|
32
|
+
if ("commands" in msg.payload) {
|
|
33
33
|
//Commands override input
|
|
34
|
-
if (
|
|
34
|
+
if ("schedule" in node) {
|
|
35
35
|
//Do not execute if schedule is missing
|
|
36
|
-
if (msg.payload
|
|
36
|
+
if ("time" in msg.payload) {
|
|
37
37
|
node.dT = findTemp(msg.payload.time, node.schedule);
|
|
38
38
|
} else {
|
|
39
39
|
node.dT = findTemp(DateTime.now(), node.schedule);
|
|
40
40
|
}
|
|
41
41
|
node.T = node.setpoint + node.dT;
|
|
42
|
-
if (msg.payload.commands
|
|
42
|
+
if ("sendSchedule" in msg.payload.commands) {
|
|
43
43
|
// Send output if schedule exists
|
|
44
|
-
if (
|
|
44
|
+
if ("schedule" in node && msg.payload.commands.sendSchedule == true) {
|
|
45
45
|
node.send([
|
|
46
46
|
null,
|
|
47
47
|
null,
|
|
@@ -50,7 +50,7 @@ module.exports = function (RED) {
|
|
|
50
50
|
]);
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
|
-
if (msg.payload.commands
|
|
53
|
+
if ("sendOutput" in msg.payload.commands && msg.payload.commands.sendOutput == true) {
|
|
54
54
|
// Send output if schedule exists
|
|
55
55
|
node.send([
|
|
56
56
|
{ payload: node.T, topic: "setpoint", time: node.schedule.time, version: version },
|
|
@@ -63,22 +63,22 @@ module.exports = function (RED) {
|
|
|
63
63
|
return;
|
|
64
64
|
}
|
|
65
65
|
// Using msg.payload.config to change specific properties
|
|
66
|
-
if (msg.payload
|
|
67
|
-
if (msg.payload.config
|
|
68
|
-
if (msg.payload.config
|
|
69
|
-
if (msg.payload.config
|
|
70
|
-
if (msg.payload.config
|
|
66
|
+
if ("config" in msg.payload) {
|
|
67
|
+
if ("timeHeat1C" in msg.payload.config) node.timeHeat1C = Number(msg.payload.config.timeHeat1C);
|
|
68
|
+
if ("timeCool1C" in msg.payload.config) node.timeCool1C = Number(msg.payload.config.timeCool1C);
|
|
69
|
+
if ("setpoint" in msg.payload.config) node.setpoint = Number(msg.payload.config.setpoint);
|
|
70
|
+
if ("maxTempAdjustment" in msg.payload.config)
|
|
71
71
|
node.maxTempAdjustment = Number(msg.payload.config.maxTempAdjustment);
|
|
72
|
-
if (msg.payload.config
|
|
72
|
+
if ("boostTempHeat" in msg.payload.config)
|
|
73
73
|
node.boostTempHeat = Number(msg.payload.config.boostTempHeat);
|
|
74
|
-
if (msg.payload.config
|
|
74
|
+
if ("boostTempCool" in msg.payload.config)
|
|
75
75
|
node.boostTempCool = Number(msg.payload.config.boostTempCool);
|
|
76
|
-
if (msg.payload.config
|
|
76
|
+
if ("minSavings" in msg.payload.config) node.minSavings = Number(msg.payload.config.minSavings);
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
//merge pricedata to escape some midnight issues. Store max 72 hour history
|
|
80
|
-
if (msg.payload
|
|
81
|
-
if (
|
|
80
|
+
if ("priceData" in msg.payload) {
|
|
81
|
+
if ("priceData" in node) {
|
|
82
82
|
node.priceData = mergePriceData(node.priceData, msg.payload.priceData);
|
|
83
83
|
} else {
|
|
84
84
|
node.priceData = msg.payload.priceData;
|
|
@@ -90,7 +90,7 @@ module.exports = function (RED) {
|
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
if (
|
|
93
|
+
if ("priceData" in node) {
|
|
94
94
|
node.schedule = runBuySellAlgorithm(
|
|
95
95
|
node.priceData,
|
|
96
96
|
node.timeHeat1C,
|
|
@@ -102,7 +102,7 @@ module.exports = function (RED) {
|
|
|
102
102
|
node.minSavings,
|
|
103
103
|
);
|
|
104
104
|
|
|
105
|
-
if (msg.payload
|
|
105
|
+
if ("time" in msg.payload) {
|
|
106
106
|
node.dT = findTemp(msg.payload.time, node.schedule);
|
|
107
107
|
} else {
|
|
108
108
|
node.dT = findTemp(DateTime.now(), node.schedule);
|
package/src/utils.js
CHANGED
|
@@ -5,7 +5,7 @@ function booleanConfig(value) {
|
|
|
5
5
|
return value === "true" || value === true;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
function calcNullSavings(values
|
|
8
|
+
function calcNullSavings(values) {
|
|
9
9
|
return values.map(() => null);
|
|
10
10
|
}
|
|
11
11
|
|
|
@@ -322,7 +322,7 @@ function fillArray(value, count) {
|
|
|
322
322
|
if (value === undefined || count <= 0) {
|
|
323
323
|
return [];
|
|
324
324
|
}
|
|
325
|
-
res = [];
|
|
325
|
+
let res = [];
|
|
326
326
|
for (let i = 0; i < count; i++) {
|
|
327
327
|
res.push(value);
|
|
328
328
|
}
|