node-red-contrib-genetic-charging-strategy
Version:
A module for Node-RED that adds a battery charging strategy to node-red-contrib-power-saver. It uses genetic algorithms to find the best schedule
139 lines (138 loc) • 5.33 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const strategy_battery_charging_functions_1 = require("./strategy-battery-charging-functions");
const zod_1 = __importDefault(require("zod"));
const moment_1 = __importDefault(require("moment"));
const ValueShape = zod_1.default.object({
start: zod_1.default.string(),
value: zod_1.default.number(),
});
const ImportShape = zod_1.default.object({
start: zod_1.default.string(),
importPrice: zod_1.default.number(),
exportPrice: zod_1.default.number(),
});
const PayloadSchema = zod_1.default.object({
priceData: zod_1.default
.array(zod_1.default.union([ValueShape, ImportShape]).transform((data) => {
var _a;
return ({
start: (0, moment_1.default)(data.start).unix(),
importPrice: 'importPrice' in data ? data.importPrice : data.value,
exportPrice: 'exportPrice' in data ? ((_a = data.exportPrice) !== null && _a !== void 0 ? _a : data.importPrice) : data.value,
});
}))
.default([]),
consumptionForecast: zod_1.default
.array(zod_1.default
.object({
start: zod_1.default.string(),
value: zod_1.default.number(),
})
.transform((data) => ({
start: (0, moment_1.default)(data.start).unix(),
value: data.value,
})))
.optional()
.default([]),
productionForecast: zod_1.default
.array(zod_1.default
.object({
start: zod_1.default.string(),
value: zod_1.default.number(),
})
.transform((data) => ({
start: (0, moment_1.default)(data.start).unix(),
value: data.value,
})))
.optional()
.default([]),
soc: zod_1.default
.string()
.transform((val, ctx) => {
const num = Number(val);
if (isNaN(num)) {
ctx.addIssue({
code: zod_1.default.ZodIssueCode.custom,
message: 'Not a valid number',
});
return zod_1.default.NEVER;
}
return num;
})
.pipe(zod_1.default.number().min(1).max(100)),
schedule: zod_1.default
.array(zod_1.default.object({
start: zod_1.default.string(),
activity: zod_1.default.number(),
}))
.optional(),
excessPvEnergyUse: zod_1.default.number().optional(),
cost: zod_1.default.number().optional(),
noBattery: zod_1.default
.object({
schedule: zod_1.default.array(zod_1.default.object({
start: zod_1.default.string(),
activity: zod_1.default.number(),
})),
excessPvEnergyUse: zod_1.default.number().optional(),
cost: zod_1.default.number(),
})
.optional(),
});
exports.default = (RED) => {
function StrategyBatteryChargingConstructor(config) {
RED.nodes.createNode(this, config);
const { populationSize, numberOfPricePeriods, generations, mutationRate, batteryMaxEnergy, batteryMaxInputPower, averageConsumption, batteryIdleLoss, } = config;
this.on('input', (msg, send, done) => {
const result = PayloadSchema.safeParse(msg.payload);
if (!result.success) {
msg.error = result.error;
this.error(`Invalid payload format\n${result.error.message}`, msg);
send(msg);
done();
return;
}
const payload = result.data;
const priceData = payload.priceData;
const consumptionForecast = payload.consumptionForecast;
const productionForecast = payload.productionForecast;
const soc = payload.soc;
const strategy = (0, strategy_battery_charging_functions_1.calculateBatteryChargingStrategy)({
priceData,
consumptionForecast,
productionForecast,
populationSize,
numberOfPricePeriods,
generations,
mutationRate: mutationRate / 100,
batteryMaxEnergy,
batteryMaxOutputPower: batteryMaxInputPower,
batteryMaxInputPower,
averageConsumption,
excessPvEnergyUse: 0, // 0=Fed to grid, 1=Charge
soc: soc / 100,
batteryIdleLoss,
});
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const output = msg.payload;
if (strategy && Object.keys(strategy).length > 0) {
output.schedule = strategy.best.schedule;
output.excessPvEnergyUse = strategy.best.excessPvEnergyUse;
output.cost = strategy.best.cost;
output.noBattery = {
schedule: strategy.noBattery.schedule,
excessPvEnergyUse: strategy.noBattery.excessPvEnergyUse,
cost: strategy.noBattery.cost,
};
}
msg.payload = output;
send(msg);
done();
});
}
RED.nodes.registerType('enell-strategy-genetic-charging', StrategyBatteryChargingConstructor);
};