Ik heb voor mijn 3 Marsteks (op 1 groep aangesloten) geprobeerd een verdeelfunctie te maken om het gewenste vermogen (TargetPower) te verdelen over de 3 accu's, rekening houdend met de ingestelde waarden max(ont)laden per accu.
Op 1 groep is TargetPower natuurlijk maximaal 3600W. Per Marsteks is max(ont)laden 2500W.
NB: moet ook voor 3 marsteks werken ieder op eigen groep aangesloten: TargetPower dan bv max 7500W.
NB: ik heb chatGPT gevraagd de verdeelfunctie compact te maken. Helaas komt dat niet ten goede aan de leesbaarheid.
Door met beide waarden te "spelen" kan je de verdeelfunctie goed laten werken:
******************************************************
//targetPower bepalen
if (status.beslissing == "kopen" || status.beslissing == 'bijkopen') { charge_discharge = 'charge'; targetPower = targetPowerLaden }
if (status.beslissing == "verkopen" ) { charge_discharge = 'discharge'; targetPower = targetPowerOntladen }
if (status.beslissing == 'NOM' || status.beslissing == 'NOM+' || status.beslissing == 'NOM-'){
if (hoofdmeter <= 0 ){
targetPower = Math.min(targetPowerLaden, -1 * hoofdmeter);
charge_discharge = 'charge'
if (status.beslissing == 'NOM-') { targetPower = 0 }
} else {
targetPower = Math.min(targetPowerOntladen, hoofdmeter);
charge_discharge = 'discharge'
if (status.beslissing == 'NOM+') { targetPower = 0 }
}
}
if (status.beslissing == 'geen actie'){ charge_discharge = 'charge'; targetPower = 0 }
**************************************************************
Volgens mij is sequentieel de accu's (ont)laden efficienter dan parallel:
****************************************************************
//** zorgt ervoor dat eerste de 1e accu optimaal (met maxontladen vermogen) wordt geladen/ontladen en daarna de 2e met het restvermogen tot targetpower*/
//** met bv vermogen_laden/ontladen = targetPower/3 worden beide accu's gelijkmatig geladen/ontladen */
for (let i = 0; i < aantal_accus; i++) {
//vermogen (ont)laden bepalen
Marstek[i].vermogen_ontladen = (Marstek[i].vulgraad <= Marstek[i].minimum_level ? 0 : Marstek_vermogen_ontladen_max);
Marstek[i].vermogen_laden = (Marstek[i].vulgraad >= Marstek[i].inhoud ? 0 : Marstek_vermogen_laden_max);
//** correcties */
//bij >95% SOC wordt Marstek_vermogen_laden_max getrottled tot 1200W
Marstek[i].vermogen_laden = (Marstek[i].soc >= 95 && Marstek[i].vermogen_laden > 1200 ? 1200 : Marstek[i].vermogen_laden);
//bij <15% SOC wordt Marstek_vermogen_ontladen_max getrottled tot 1200W
Marstek[i].vermogen_ontladen = (Marstek[i].soc <= 15 && Marstek[i].vermogen_ontladen > 1200 ? 1200 : Marstek[i].vermogen_ontladen);
}
*************************************************
aanroep van de verdeelfunctie
************************************************
//** Functie aanroep distributieplan met verdeling targetPower: laden/ontladen per accu, remaining power en buy/sell */
//** Houdt rekening met XOM en reserveGridPower */
var batteries = [];
for (let i = 0; i < aantal_accus; i++){
batteries.push({ id:i, maxCharge: Marstek[i].vermogen_laden, maxDischarge: Marstek[i].vermogen_ontladen, soc: Marstek[i].soc

}
var plan = distributePower(batteries, targetPower, hoofdmeter, charge_discharge, xom_level1, xom_level2, reserveGridPower);
*****************************************
verdeelfunctie. naast verdelen van targetpower of de 3 accu's zijn er andere dingen "ingeslopen""
- corrigeren voor resevegrid (bv om EV laden buiten de accu te houden)
- wisselen van de primaire accu als het verschil Soc 10% is
- nomlevel1: indien gevraagde power van een accu <= nomlevel1 is dan corrigeren naar 0 (bv <100W)
- nomlevel 2: indien gevraagde poer per accu > nomlevel1 en <= nomlevel2 is dat corrigeren naar nomlevel2 (bv 200W)
- bijhouden buy/sell van grid in dien targetpower niet geleverd kan worden
**************************************
function distributePower(batteries, targetPower, gridPower, mode, xomL1, xomL2, reserveGridPower) {
// Accu's compenseren de gridPower binnen toegestane targetPower limiet
// Reserveer een deel van het netvermogen voor bv autoladen
let adjustedTargetPower = targetPower - reserveGridPower;
adjustedTargetPower = (adjustedTargetPower < 0 ? 0 : adjustedTargetPower);
targetPower = (mode === 'charge' ? targetPower : adjustedTargetPower);
let remainingPower = 0;
let totalPower = 0;
let buySell = 0;
let totalAvailable = Math.min(targetPower, batteries.reduce((sum, b) => sum + (mode === 'charge' ? b.maxCharge : b.maxDischarge), 0));
let powerPlan = batteries.map(battery => ({ battery: battery.id, power: 0, xom: 'power', soc: battery.soc

);
//***************** Sorteer de accu's op maximale SOC bij ontladen en minimale SOC bij laden als verschil 10% is *****************//
const getActiveBattery = (batteries, mode, delta, last) => {
// Werk SOC, maxCharge en MaxDischarge van vorige volgorde bij met actuele waarden.
const updatedLast = last.map(b => {const fresh = batteries.find(c => c.id === b.id); return fresh ? { ...b, ...fresh } : b; });
const sorted = [...batteries].sort((a, b) => mode === 'charge' ? a.soc - b.soc : b.soc - a.soc);
const diff = Math.abs((updatedLast[0]?.soc ?? 0) - (updatedLast[1]?.soc ?? 0));
const useNewOrder = !last.length || diff >= delta;
const order = useNewOrder ? sorted : updatedLast;
context.set('Test', { diff: diff, useneworder: useNewOrder });
return { activeAccu: order[0].id, mode: mode, order };
};
let lastFlow = flow.get('Last');
let last = (lastFlow && lastFlow.order) ? lastFlow.order : []; //vorige volgorde en actieve accu
last = getActiveBattery(batteries, charge_discharge, 10, last);
batteries = last.order;
flow.set('Last',last);
// verdelen beschikbaar vermogen over de accu's
for (let i = 0; i < batteries.length && totalAvailable > 0; i++) {
let battery = batteries[i];
let maxPossible = Math.min((mode === 'charge' ? battery.maxCharge : battery.maxDischarge), totalAvailable);
let j = battery.id; //vul power in juiste accu (powerPlan heeft normale volgeorde 0,1,2)
maxPossible = maxPossible;
powerPlan[j].power = maxPossible;
totalAvailable -= maxPossible;
}
// level1: zorgt ervoor dat vermogen 0W is (idle < 80% efficiency)
// Level2: Zorg dat elk vermogen minstens 300W is (XOM > 85/90% efficiency )
powerPlan = powerPlan.map(p => ({
battery: p.battery,
power: p.power != 0 && p.power < xomL2 ? (p.power < xomL1 ? 0 : xomL2) : p.power,
xom: p.power != 0 && p.power < xomL2 ? (p.power < xomL1 ? 'XOM0' : 'XOM'+xomL2) : p.xom,
soc: p.soc

);
for (let i = 0; i < batteries.length ; i++) {
totalPower += powerPlan[i].power;
}
//sorteer de accus op battery (=id) //waarschijnlijk niet nodig
powerPlan.sort((a, b) => a.battery - b.battery);
// laden/ontladen
totalPower = (mode === 'charge' ? totalPower : -1 * totalPower);
// buy of sell
buySell = gridPower + totalPower;
//remaining power
remainingPower = gridPower + totalPower;
//mode idle als totalPower == 0
mode = (totalPower === 0 ? 'idle' : mode);
//powerPlan: per accu
//buySell: kopen/verkopen
//remainingPower: hoofdmeter na charge/discharge
//totalPower: geladen/ontladen
//Mode: charge/discharge/idle
// waardes kunnen positief en negatief zijn
totalPower = Math.abs(totalPower);
//buySell = Math.abs(buySell);
return { powerPlan, gridPower, remainingPower, mode, totalPower, buySell};
}
Panasonic TCAP 12kW J-versie + Heishamon/HA/Node-Red/Grafana/InfluxDB; Atlantic v3 200L; 5* jaga strada 21 & zelfbouw DBE; 3*2400Wp (O,Z,W); KIA EV6 77kWh RWD + EVCC/cFos Wallbox solar; 3* Marstek 5kWh (v151)+CT003 (v114)+ modbus/lilygo/node-red/HA