UNPKG

node-red-contrib-power-saver

Version:

A module for Node-RED that you can use to turn on and off a switch based on power prices

149 lines (139 loc) 4.81 kB
"use strict"; const { fillArray } = require("./utils"); /** * Takes an array of true/false values where true means on and false means off. * Evaluates of the on/off sequences are valid according to other arguments. * * @param {*} onOff Array of on/off values * @param {*} maxMinutesOff Max number of minutes that can be off in a sequence * @param {*} minMinutesOff Min number of minutes that must be off to bother * @param {*} recoveryPercentage Percent of off-time that must be on after being off * @param {*} recoveryMaxMinutes Maximum recovery time in minutes * @returns */ function isOnOffSequencesOk( onOff, maxMinutesOff, minMinutesOff, recoveryPercentage, recoveryMaxMinutes = null) { let offCount = 0; let onCount = 0; let reachedMaxOff = false; let reachedMinOn = true; let reachedMinOff = null; let minOnAfterOff = 0; for (let i = 0; i < onOff.length; i++) { if (!onOff[i]) { if (maxMinutesOff === 0 || reachedMaxOff) { return false; } if(!reachedMinOn) { return false; } if(reachedMinOff === null) { reachedMinOff = false; } offCount++; onCount = 0; if (offCount >= maxMinutesOff) { reachedMaxOff = true; } if (offCount >= minMinutesOff) { reachedMinOff = true; } const minRounded = Math.max(Math.round(offCount * recoveryPercentage / 100), 1) minOnAfterOff = Math.min(minRounded, recoveryMaxMinutes ?? minRounded) if(i === onOff.length - 1) { // If last minute, consider min reached reachedMinOn = true; reachedMinOff = true; } } else { if(reachedMinOff === false) { return false; } onCount++; if (onCount >= minOnAfterOff) { reachedMaxOff = false; reachedMinOn = true; } offCount = 0; reachedMinOff = null; } } return reachedMinOn && !(reachedMinOff === false); } /** * Turn off the minutes where you save most compared to the next minute on. * * @param {*} values Array of prices * @param {*} maxMinutesOff Max number of minutes that can be saved in a row * @param {*} minMinutesOff Min number of minutes to turn off in a row * @param {*} recoveryPercentage Min percent of time off that must be on after being off * @param {*} recoveryMaxMinutes Maximum recovery time in minutes * @param {*} minSaving Minimum amount that must be saved in order to turn off * @param {*} lastValueDayBefore Value of the last minute the day before * @param {*} lastCountDayBefore Number of lastValueDayBefore in a row * @returns Array with same number of values as in values array, where true is on, false is off */ function calculate( values, maxMinutesOff, minMinutesOff, recoveryPercentage, recoveryMaxMinutes, minSaving, lastValueDayBefore = undefined, lastCountDayBefore = 0 ) { const dayBefore = fillArray(lastValueDayBefore, lastCountDayBefore); const last = values.length - 1; // Create matrix with saving per minute const savingPerMinute = []; for (let minutes = 0; minutes < last; minutes++) { const row = []; for (let count = 1; count <= maxMinutesOff; count++) { const on = minutes + count; const saving = values[minutes] - values[on >= last ? last : on]; row.push(saving); } savingPerMinute.push(row); } // Create list with summary saving per sequence let savingsList = []; for (let minute = 0; minute < last; minute++) { for (let count = 1; count <= maxMinutesOff; count++) { let saving = 0; for (let offset = 0; offset < count && minute + offset < last; offset++) { saving += savingPerMinute[minute + offset][count - offset - 1]; } if (saving > minSaving * count && minute + count <= last && values[minute] > values[minute + count] + minSaving) { savingsList.push({ minute, count, saving }); } } } savingsList.sort((a, b) => b.saving === a.saving ? a.count - b.count : b.saving - a.saving); let onOff = values.map((v) => true); // Start with all on // Find the best possible sequences while (savingsList.length > 0) { const { minute, count } = savingsList[0]; const onOffCopy = [...onOff]; let alreadyTaken = false; for (let c = 0; c < count; c++) { if (!onOff[minute + c]) { alreadyTaken = true; } onOff[minute + c] = false; } if (isOnOffSequencesOk([...dayBefore, ...onOff], maxMinutesOff, minMinutesOff, recoveryPercentage, recoveryMaxMinutes) && !alreadyTaken) { savingsList = savingsList.filter((s) => s.minute < minute || s.minute >= minute + count); } else { onOff = [...onOffCopy]; savingsList.splice(0, 1); } } return onOff; } module.exports = { calculate, isOnOffSequencesOk };