Neuer Optimizer/Logik für mehrere Hybrid ESSes

Ok, dann kommen wir doch der Konfiguration mal etwas näher, d.h. folgende Speichersysteme sind konfiguriert?
ess0 Sax?
ess1 Fenecon (DC)
ess4 Yelon (DC)?

Wenn ich dass jetzt nutze um den Screenshot von gestern zu bewerten, bedeutet dass, das der Sax mit 739.0 W entladen sollte, obwohl er einen Soc von 0% hatte.
Entweder passt hier die Implementierung von dem Sax nicht und er erlaubt ein Discharge was er dann nicht umsetzt, oder die Sortierung der ESS passen nicht, sprich die PowerSetPoints werden den falschen ESSen zugewiesen.

Um hier weiter unterstützen zu können, wäre neben der Konfiguration, also welche Controller gibt es und wie sind die konfiguriert, noch das Debug-Log wichtig.
Dabei wären folgende Zeilen wichtig (das log von heute ist leider nicht vollständig):

[ACTIVE] PowerSetPoint: -35.0, Direction: CHARGE
[ACTIVE] ess1: PVProduction: 0.0, min: -35.0, max: -35.0
[ACTIVE] ess0: PVProduction: 0.0, min: 0.0, max: 0.0
[ACTIVE] → remaining power required after pv production solved: -35.0
[ACTIVE] → remaining power required after ess already discharging solved: -35.0
[ACTIVE] ess1: PVProduction: 0.0, min: -35.0, max: -35.0 → ess1 EQUALS -35.0
[ACTIVE] ess0: PVProduction: 0.0, min: 0.0, max: 0.0 → ess0 EQUALS 0.0
[ACTIVE] → remaining power required after solving using all ess: 0.0
[ACTIVE] Add Constraint for ess1: LESS_OR_EQUALS -35.0
[ACTIVE] Add Constraint for ess0: LESS_OR_EQUALS 0.0
[ACTIVE] Solved Solution for ess1: EQUALS -35.0 (should be equal to added constraints!!)
[ACTIVE] Solved Solution for ess0: EQUALS 0.0 (should be equal to added constraints!!)

Wenn ich die Teil-Logausschnitte von heute versuche zu kombinieren, müsste der PowerSetPoint 621.0 W sein.
Der ess0 möchte eine Beladung von 68W (-68.0). Ggf. eine Zwangsbeladung? Da der Logauszug fehlt, kann ich hier nur raten.
Der ess4 soll 689W AC-Leistung erzeugen (689.0). Wieviel DC-Leistung generiert wird, kann ich mangels Informationen zur Konfiguration nicht sehen, da ich nicht weiß welcher charger zum ess4 gehört. Kombiniere ich die anderen Debug-Outputs, müssten es etwa 101W PV sein, d.h. hier soll die Batterie entladen werden.

Jetzt vergleiche ich die generierten Setpoint-Vorgaben mit dem, was die ESS an AC-Leistung liefern (lt. Log):
ess0 -68.0 W SOLL → -60.0 W IST → Passt.
ess4 689.0 W SOLL → -27.0 W IST → Passt nicht. Der ess4 macht nicht dass was er soll. Daher fehlen 662W. Die kommen nun vom Grid.

Mit den anderen Optimizern bestehen keine Probleme?

Kann es sein, dass hier evtl. irgendwo die Sortierung im Optimizer nicht passt, spricht entweder die Vorgaben an die falschen ESS übergeben werden oder die Boundaries min/max nicht zu den jeweiligen ESSen gehören? Mit vollständigem Debug-Logauszug wäre dass besser zu erkennen.

Nach ausgiebigen Testen würde ich sagen, dass du den Optimizer als PR stellen könntest - was hältst du davon? Bei mir funktioniert es nun endlich einwandfrei (das war kein Code - Problem).

Folgende Punkte sind noch offen:

  • Code aufräumen (Logging entfernen oder auf Logging-Klasse umbauen)
  • Umgang mit Reactive Power Vorgaben klären/implementieren
  • Implementation der JUnit Tests

Ich habe aktuell bei mir implementiert, dass bei einer Reactive Power Vorgabe != 0 der Optimizer beendet wird (“return null”), sodass der nächste Optimizer genutzt wird. So wird aber vermutlich kein erfolgreicher JUnit Test möglich sein.

Gibt es irgendwo sinnvoll Hinweise wie mit einer Reactive Power Vorgabe umgegangen werden kann/soll?

Alle oben genannten offenen Punkte sind jetzt abgeschlossen.

  • Der Umgang mit Reactive Power Vorgaben wurde implementiert
  • JUnit Tests wurden implementiert
  • Der Quellcode wurde aufgeräumt
  • Ein paar weitere Optimierungen wurden vorgenommen
  • Der Branch wurde auf den aktuellen develop branch des main repo angehoben

Die Änderungen habe ich soeben in meinen Fork hochgeladen. Alle JUnit Tests laufen durch, jedoch habe ich die neue Version noch nicht auf meinem System getestet.

@Sn0w3y hättest du die Möglichkeit die neue Version noch mal im Live-Betrieb zu testen? Sollte das alles zufriedenstellend laufen würde ich die Implementierung als PR stellen.

Hi,

ich habe die neue Version noch nicht getestet, aber kann eine Sache noch nicht ganz nachvollziehen:

Wieso belädt er den Speicher, anstatt das Haus damit zu versorgen? Das bleibt auch so, bis zu einem gewissen Grenzwert (unbekannt).

Grüße!

Wenn ich raten soll, würde ich auf einen EssLimitTotalDischarge Controller im MIN_SOC (oder auch im FORCE_CHARGE_SOC) state tippen.

Im MIN_SOC state setzt der Controller für den ESS (=Hybrid-Inverter) AC-seitig einen Constraint für ActivePower <= 0. ActivePower (AC-Leistung) <= 0 bedeutet, dass keine positive AC-Leistung vom Hybrid-Inverter abgegeben werden darf/soll, ergo muss sämtliche DC erzeugte Leistung von diesem Hybrid-Inverter in die Batterie gehen.

Ich habe mir den Controller angepasst, sodass ich den HybridModus zwischen TARGET_AC und TARGET_DC konfigurieren kann, wie auch im EssFixActivePower Controller vorgesehen. Wenn gewünscht, kann ich dass gerne auch als PR zur Verfügung stellen.

Falls ich die Situation analysieren soll, benötige ich etwas mehr Input.

Liebe Grüße

Hey,

bei mir gibt es so einen Controller nicht.

Leider funktioniert das ganze nicht wirklich zuverlässig. Siehe Screenshot:

4.2.2026, 11:55:40INFOio.openems.edge.ess.core.power.optimizers.PreferDcPower[REACTIVE] Add Constraint for ess4: EQUALS 0.04.2.2026, 11:55:40INFOio.openems.edge.ess.core.power.optimizers.PreferDcPower[REACTIVE] Add Constraint for ess1: EQUALS 0.04.2.2026, 11:55:40INFOio.openems.edge.ess.core.power.optimizers.PreferDcPower[REACTIVE] PowerSetPoint: 0.0, Direction: KEEP_ZERO4.2.2026, 11:55:40INFOio.openems.edge.ess.core.power.optimizers.PreferDcPower[REACTIVE] Add Constraint for ess0: EQUALS 0.04.2.2026, 11:55:40INFOio.openems.edge.ess.core.power.optimizers.PreferDcPower[ACTIVE] Add Constraint for ess4: EQUALS 0.04.2.2026, 11:55:40INFOio.openems.edge.ess.core.power.optimizers.PreferDcPower[ACTIVE] Add Constraint for ess1: EQUALS 0.04.2.2026, 11:55:40INFOio.openems.edge.ess.core.power.optimizers.PreferDcPower[ACTIVE] → remaining power required after solving using all ess: 0.04.2.2026, 11:55:40INFOio.openems.edge.ess.core.power.optimizers.PreferDcPower[ACTIVE] Add Constraint for ess0: EQUALS 3485.04.2.2026, 11:55:40INFOio.openems.edge.ess.core.power.optimizers.PreferDcPower[ACTIVE] ess4: PVProduction: 0.0, min: -3680.0, max: 0.0 → ess4 EQUALS 0.04.2.2026, 11:55:40INFOio.openems.edge.ess.core.power.optimizers.PreferDcPower[ACTIVE] ess0: PVProduction: 0.0, min: -6515.0, max: 5000.0 → ess0 EQUALS 3485.04.2.2026, 11:55:40INFOio.openems.edge.ess.core.power.optimizers.PreferDcPower[ACTIVE] ess1: PVProduction: 0.0, min: -1515.0, max: 10000.0 → ess1 EQUALS 0.04.2.2026, 11:55:40INFOio.openems.edge.ess.core.power.optimizers.PreferDcPower[ACTIVE] → remaining power required after ess already discharging solved: 3485.04.2.2026, 11:55:40INFOio.openems.edge.ess.core.power.optimizers.PreferDcPower[ACTIVE] → remaining power required after pv production solved: 3485.04.2.2026, 11:55:40INFOio.openems.edge.ess.core.power.optimizers.PreferDcPower[ACTIVE] ess4: PVProduction: 0.0, min: -3680.0, max: 0.0 → EQUALS 0.04.2.2026, 11:55:40INFOio.openems.edge.ess.core.power.optimizers.PreferDcPower[ACTIVE] ess1: PVProduction: 0.0, min: -1515.0, max: 10000.0 → EQUALS 0.04.2.2026, 11:55:40INFOio.openems.edge.ess.core.power.optimizers.PreferDcPower[ACTIVE] ess0: PVProduction: 0.0, min: -6515.0, max: 5000.0 → EQUALS 0.04.2.2026, 11:55:40INFOio.openems.edge.ess.core.power.optimizers.PreferDcPower[ACTIVE] PowerSetPoint: 3485.0, Direction: DISCHARGE4.2.2026, 11:55:40INFOio.openems.edge.ess.core.power.optimizers.PreferDcPoweress1 does not report PV production4.2.2026, 11:55:40INFOio.openems.edge.ess.core.power.optimizers.PreferDcPoweress4 does not report PV production4.2.2026, 11:55:40INFOio.openems.edge.ess.core.power.optimizers.PreferDcPoweress0 does not report PV production4.2.2026, 11:55:40INFOio.openems.edge.ess.core.power.Solver- Channel [SetActivePowerEquals]+ess3P EQUALS 3485

Naja, eigentlich macht er das was er soll.

  • SetActivePowerEquals 3485 (AC-Seitige Entladung von 3485W gewünscht)
  • ess0, ess1, ess4 reporten keine PV Produktion (somit keine DC-Optimierung möglich)
  • Verteilung erfolgt nach Order (Highest SOC ESS first)
    • ess0 → Entladung erlaubt (min: -6515.0, max: 5000.0) → Entladung 3485W

Damit der Optimizer DC optimiert arbeiten kann muss er die PV Produktion kennen. Dafür muss im ManagedSymmetricEss die Methode getPvProduction implementiert werden. Am Beispiel GoodWeEss wie folgt (in GoodWeEssImpl.java):

public Integer getPvProduction() {
	return calculatePvProduction();
}

Die Reihenfolge der Debug-Logauszüge scheinen zudem nicht zu passen, kannst du dass nochmal prüfen? Und was ist ess0, ess1 und ess4?

@MrT und @Sn0w3y: zu eurer Info, wir (@pooran.c und ich) testen intern gerade die Implementierung eines Solvers, der ohne Lineares Gleichungssystem auskommt. Für “einfachere Fälle” wie diesen hier, dürfte das die Implementierung und das Testen deutlich vereinfachen. Ich kann noch keinen Code teilen, versuche das aber in den nächsten Wochen zu veröffentlichen.

1 Like

Hi Stefan,

danke für die tollen Infos! Ich habe mittlerweile den PID mit Keep All Near Equal im Einsatz. Bis jetzt echt vielversprechend! Schauen wir mal, wie es weiter geht, wenn heute Produktion kommt :wink:

Grüße !

Hi Stefan,

die aktuelle Implementierung des PreferDcPower Optimizers verzichtet ebenfalls auf ein lineares Gleichungssystem und verteilt die Leistung direkt in Watt. Das funktioniert nach meinen bisherigen Erfahrungen so auch recht gut. Der Optimizer ist seit Monaten bei mir produktiv im Einsatz.

Was ich bislang von @Sn0w3y mitbekommen habe, funktioniert er bei ihm ebenfalls. Die Themen die dort auftreten lagen bislang an anderen Stellen, zB erlaubt der Goodwe mit Yelon Speicher die Entladung mit fast 10 kW. Wenn sie aber abgerufen wird, wird die abgerufene Leistung (zB bei SOC 13%) nicht eingespeist. Die abgerufene Leistungsvorgabe kommt aber in der applyPower-Methode des Goodwe an. Ich vermute dass das BMS die Entladung blockiert, die Info aber nicht an den Goodwe weitergegeben wird.

Klar, bei KeepNearEqual mit PID wird mE die Leistung über alle ESS-Systeme proportional verteilt und wenn die erbrachte Leistung nicht der gewünschten entspricht wird die gesamte ActivePower Vorgabe des ESS-Clusters nach oben korrigiert, bis das Ergebnis stimmt. Liefert ein eingeplantes ESS nichts, wird das dann über diesen Weg korrigiert. Verzichtet man auf ein lineares Gleichunssystem passiert das erstmal nicht automatisch.

Vielleicht kann man, wenn überhaupt gewünscht, das auch anders adressieren? ZB ein ESS in den failed State bringen, wenn über eine bestimmte Zeit Leistung abgefragt aber nicht erbracht wird. Natürlich nach definierten Kriterien wieder zurücksetzen um ein Recovery zu erlauben.

Mein Optimizer wertet den failed state aus und würde dann ohne diesen ESS planen.

Oder verfolgt Ihr einen ganz anderen Ansatz im Vergleich zu dem wie ich es gelöst habe?

Gruß

Timo

Ich habe unseren Ansatz jetzt einfach mal hier als WIP PR online gestellt:

Ihr nutzt intern immer noch das lineare Gleichungssystem. Unser Ansatz vermeidet das komplett, so dass man nicht mehr mühsam den eigentlichen Set-Point aus den Constraints herausfiltern muss. Außerdem ist das Gleichungssystem aktuell in der Praxis relativ langsam bei großen Cluster-Systemen. Die weitere Verteilung der Leistung wäre dann ähnlich zu eurem Ansatz.

Solche Probleme lösen wir bisher immer in der ESS-Komponente. Diese muss sicherstellen, dass sie die maximal mögliche Leistung richtig als Constraints “modelliert”, sonst kann ESS-Power nicht wissen, wie viel verteilt werden kann.

Gruß,
Stefan