Last active
April 3, 2026 11:01
-
-
Save andreaskueffel/f6e8ee8b97204affb3aacaed9e655d69 to your computer and use it in GitHub Desktop.
Zero Export function Node for NodeRed to get target values for 2 independent batteries
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /***************************************************** | |
| * Marstek Dual‑Regler (1‑Sekunden Zyklus) | |
| * Ziel: Netz = -5 Watt (leichte Einspeisung) | |
| * Zwei Speicher werden dynamisch geregelt. | |
| * | |
| * Features: | |
| * - PI‑Regler für Gesamtleistung | |
| * - SOC‑Balancing | |
| * - Anti‑Flattern: schneller Anstieg / langsamer Abfall | |
| * - Glättung der Stellgrößen | |
| *****************************************************/ | |
| // ----------------------- | |
| // KONFIGURATION | |
| // ----------------------- | |
| const TARGET = -5; // Netzsollwert | |
| const MAX_W = 800; // Max. Leistung pro Speicher | |
| const MIN_W = 40; | |
| const CYCLE = 1; // 1 Sekunde | |
| // PI‑Parameter | |
| const KP = 0.8; | |
| const KI = 0.1; | |
| // Glättungsfaktor | |
| const SMOOTHING = 0.6; | |
| // Boost für schnellen Anstieg | |
| const BOOST_MULTIPLIER = 3; // >1 = schnelleres Hochregeln | |
| const BOOST_THRESHOLD = 300; // Grid > 150W Bezug => sofort nach oben | |
| // Verlangsamen des Abfalls | |
| const DECAY_FACTOR = 0.8; // <1 = langsames Herunterregeln | |
| // Mindest‑SOC Schutz | |
| const SOC_MIN = 2; // Unter 10% kein Entladen | |
| // ----------------------- | |
| // STATES (persistent) | |
| // ----------------------- | |
| let integral = context.get("integral") || 0; | |
| let lastPA = context.get("lastPA") || 0; | |
| let lastPB = context.get("lastPB") || 0; | |
| // ----------------------- | |
| // INPUTS | |
| // ----------------------- | |
| const powerGrid = msg.payload.StatusSNS.eBZ.Power; // +W = Bezug, -W = Einspeisung | |
| const socA = flow.get("soc1") || 50; | |
| const socB = flow.get("soc2") || 50; | |
| // ----------------------- | |
| // 1) PI‑REGELUNG | |
| // ----------------------- | |
| let error = powerGrid - TARGET; | |
| // Boost: schneller Anstieg bei hohem Netzbezug | |
| if (powerGrid > BOOST_THRESHOLD) { | |
| error *= BOOST_MULTIPLIER; | |
| } | |
| let rawOutput = error * KP + integral; | |
| let saturated = Math.max(0, Math.min(rawOutput, MAX_W * 2)); | |
| // Anti-Windup | |
| if (rawOutput === saturated) { | |
| integral += error * KI; | |
| } | |
| let P_total = saturated; | |
| // ----------------------- | |
| // 2) VERZÖGERTER ABFALL | |
| // ----------------------- | |
| if (P_total < lastPA + lastPB) { | |
| // herunterfahren nur langsam | |
| P_total = (lastPA + lastPB) * DECAY_FACTOR + P_total * (1 - DECAY_FACTOR); | |
| } | |
| P_total = Math.round(P_total); | |
| // ----------------------- | |
| // 3) SOC‑BALANCING | |
| // ----------------------- | |
| const BALANCE_FACTOR = 2.0; // 1 = linear, 2 = stärker, 3 = aggressiv | |
| let PA, PB; | |
| if (socA < SOC_MIN && socB < SOC_MIN) { | |
| PA = 0; | |
| PB = 0; | |
| } | |
| else { | |
| // Verstärkte SOC-Gewichtung | |
| let wA = Math.pow(socA, BALANCE_FACTOR); | |
| let wB = Math.pow(socB, BALANCE_FACTOR); | |
| let sum = Math.max(wA + wB, 1); | |
| let weightA = wA / sum; | |
| let weightB = wB / sum; | |
| PA = P_total * weightA; | |
| PB = P_total * weightB; | |
| // Maximalleistung beachten | |
| PA = Math.min(PA, MAX_W); | |
| PB = Math.min(PB, MAX_W); | |
| //Wenn einer voll ist → Rest auf den anderen umschichten | |
| let consumed = PA + PB; | |
| let rest = P_total - consumed; | |
| if (rest > 10) { | |
| if (PA >= MAX_W) { | |
| PB = Math.min(MAX_W, PB + rest); | |
| } | |
| else if (PB >= MAX_W) { | |
| PA = Math.min(MAX_W, PA + rest); | |
| } | |
| } | |
| // Not-Abschaltung | |
| if (socA < SOC_MIN) PA = 0; | |
| if (socB < SOC_MIN) PB = 0; | |
| } | |
| // ----------------------- | |
| // 4) GLÄTTUNG | |
| // ----------------------- | |
| PA = lastPA * (1 - SMOOTHING) + PA * SMOOTHING; | |
| PB = lastPB * (1 - SMOOTHING) + PB * SMOOTHING; | |
| PA = Math.round(PA); | |
| PB = Math.round(PB); | |
| // Status anzeigen | |
| node.status({ | |
| fill: "green", | |
| shape: "ring", | |
| text: "Power: " + powerGrid + "W, PTotal: " + P_total + "W (target1: " + PA + " | target2: " + PB + ")" | |
| }); | |
| // ----------------------- | |
| // STATES SPEICHERN | |
| // ----------------------- | |
| context.set("integral", integral); | |
| context.set("lastPA", PA); | |
| context.set("lastPB", PB); | |
| // ----------------------- | |
| // OUTPUT | |
| // ----------------------- | |
| return { | |
| payload: { | |
| setA: Math.round(PA), | |
| setB: Math.round(PB), | |
| debug: { | |
| powerGrid, | |
| socA, | |
| socB, | |
| error, | |
| integral, | |
| P_total | |
| } | |
| } | |
| }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment