Skip to content

EVSE: Ratenbegrenzung für Lade-/Stopp-Wechsel (Schutz vor Relaisschleifen) - #3982

Open
s0170071 wants to merge 2 commits into
openWB:masterfrom
s0170071:feature/evse-transition-filter
Open

s0170071 wants to merge 2 commits into
openWB:masterfrom
s0170071:feature/evse-transition-filter

Conversation

@s0170071

@s0170071 s0170071 commented Sep 20, 2026 •

Copy link
Copy Markdown
Contributor

Problem

Der Regelalgorithmus ist über Lademodi, Lastmanagement, Phasenumschaltung, CP-Unterbrechung und Fehlerbehandlung so verzweigt, dass sich das Ein- und Ausschalten der Ladefreigabe aus mehreren voneinander unabhängigen Stellen ergibt. In der Vergangenheit sind dadurch wiederholt Relaisschleifen aufgetreten: Register 1000 wird im Sekundentakt zwischen 0 und ungleich 0 hin- und hergeschrieben.

Für viele Fahrzeuge ist das unkritisch. Bei MEB-Fahrzeugen — ID.4, Enyaq, Elroq ab Softwarestand > 5.2 — führt zu schnelles An/Aus zu einem Fehlerzustand, aus dem sich das Fahrzeug nicht selbst befreit. Die Ladung bleibt stehen, bis manuell aus- und wieder eingesteckt wird.

Der PR setzt bewusst nicht am Algorithmus an, sondern unmittelbar vor dem Modbus-Schreibzugriff. Das ist die einzige Stelle, an der alle Pfade zusammenlaufen; die Garantie gilt damit unabhängig davon, welcher Zweig den Wechsel ausgelöst hat.

Funktion

  1. Ratenbegrenzung. Ein Wechsel 0 -> ungleich 0 bzw. ungleich 0 -> 0 auf Register 1000 ist erst nach Ablauf eines Mindestfensters erlaubt (MIN_ON_TIME_S / MIN_OFF_TIME_S, je 300 s). Reine Stromänderungen (6 A -> 10 A) laufen immer durch, die Überschussregelung bleibt unangetastet.

    Das Fenster greift nur dann, wenn der letzte Zustandswechsel weniger als 5 Minuten zurückliegt — im Normalbetrieb ist es also nie aktiv. Die Werte stammen aus einem Fork, der seit längerem im Feld läuft; kleinere Werte funktionieren möglicherweise auch, sind aber nicht bestätigt.

  2. Ausnahme Phasenumschaltung (und CP-Unterbrechung). Diese Prozeduren schalten Hardware genau in dem Moment, in dem sie den Strom auf 0 setzen. Ein bloßes Unterdrücken des Schreibzugriffs würde das Relais unter Last schalten lassen. Sie warten deshalb über wait_for_window() aktiv, bis die Abschaltung erlaubt ist, und brechen ab, falls das Fenster nicht aufgeht — die Umschaltung wird später erneut angefordert. Sie laufen ohnehin in eigenen Threads, blockieren also nichts.

    Die so abgewartete Abschaltung zieht kein neues Fenster auf, damit der Wiederanlauf direkt nach der Umschaltung nicht für weitere 5 Minuten gesperrt ist.

  3. Ausnahme Heartbeat-Verlust. Fällt die Verbindung zur übergeordneten openWB aus, muss die Ladung sofort stoppen; auf ein Fenster zu warten wäre hier falsch. Dieser Fall ist selten und erzeugt daher keine Schleife. Der Schreibzugriff geht über force=True sofort raus und wird als Ausnahme protokolliert. Das Fenster wird dabei aufgezogen, der anschließende Wiederanlauf ist also wie üblich begrenzt — der Schutz für das Fahrzeug bleibt vollständig erhalten.

  4. Meldung im Log. Jeder tatsächliche Schreibzugriff auf Register 1000 und jeder unterdrückte Versuch geht nach data/log/openwb_evse_relay.log. Ab 3 Wechseln in 120 s wird zusätzlich mögliche Relaisschleife! protokolliert. Das macht den Fehler erstmals nachweisbar, statt ihn nur zu vermuten.

Darüber hinaus gibt es keine Umgehung — auch nicht für die Fehlerabschaltung.

Verhalten bei Unterdrückung

Der Filter verzögert, er verwirft nicht. set_current kehrt vor write_register zurück; die Regelung stellt ihren Wunsch im nächsten Zyklus erneut, ein dauerhafter Befehl landet also, sobald das Fenster abgelaufen ist. Ein Ein-Zyklus-Zappler wird vollständig geschluckt: der 0-Schreibzugriff wird unterdrückt, der darauf folgende ungleich-0-Schreibzugriff ist wegen des unveränderten evse_current ein No-Op.

Änderungen

Datei Zweck
modules/common/evse_transition_filter.py neu — der Filter, Zustand je evse_id, thread-sicher
modules/common/evse_transition_filter_test.py neu — 30 Unit-Tests
modules/common/evse.py set_current erhält wait und force, ruft den Filter
modules/internal_chargepoint_handler/chargepoint_module.py Phasenumschaltung und CP-Unterbrechung warten auf das Fenster
modules/internal_chargepoint_handler/internal_chargepoint_handler.py Heartbeat-Verlust setzt force=True
modules/internal_chargepoint_handler/socket.py reicht force an die Basisklasse durch
modules/chargepoints/openwb_series2_satellit/chargepoint_module.py Phasenumschaltung wartet auf das Fenster

Tests

30 Unit-Tests mit gestellter Uhr, ohne Hardware: Fensterlogik in beide Richtungen, Nicht-Begrenzung von Stromänderungen, Trennung nach evse_id, aktives Warten inklusive Timeout-Abbruch, beide Ausnahmen inklusive Nachweis, dass sie den Fahrzeugschutz nicht aushebeln, Fail-open bei allow_write gegenüber Fail-safe bei wait_for_window.

pytest packages/modules/common packages/modules/internal_chargepoint_handler
145 passed
flake8  ->  keine Befunde

Offene Punkte für die Diskussion

  • Die Fensterlängen sind aktuell Konstanten. Falls gewünscht, können sie als hardware_configuration-Setting konfigurierbar gemacht werden.
  • Die Fehlerabschaltung im Satellit-Modul (error_counter_exceeded()) läuft bewusst durch den Filter und bekommt kein force. Falls das aus eurer Sicht anders sein soll, ziehe ich das nach.

@s0170071 s0170071 changed the title EVSE: Ratenbegrenzung fuer Lade-/Stopp-Wechsel (Schutz vor Relaisschleifen) EVSE: Ratenbegrenzung für Lade-/Stopp-Wechsel (Schutz vor Relaisschleifen) Sep 20, 2026
@s0170071
s0170071 marked this pull request as ready for review September 20, 2026 09:11
@seaspotter

Copy link
Copy Markdown
Collaborator

Kurzer Hinweis, unabhängig von der eigentlichen Review: seit #3717 (gemerged 2026-08-04, vermutlich nach deinem 2.1.9-Fork) gibt es pro Fahrzeug-Template control_pilot_interruption_retry_interval - eine periodische CP-Unterbrechung, die ausgelöst wird, wenn ein Fahrzeug trotz gültiger Freigabe nicht lädt. Für interne Ladepunkte läuft das am Ende genau über perform_cp_interruption(), also durch deinen neuen Filter.

Das kollidiert konkret mit den festen MIN_ON_TIME_S/MIN_OFF_TIME_S von 300s: in der UI lässt sich control_pilot_interruption_retry_interval nur in 1-Minuten-Schritten einstellen (aus, 1, 2, 3 min, ...). Jeder realistisch konfigurierbare Wert liegt damit unter eurem Fenster - nach dem Merge würde ein Nutzer, der zB 1 min für ein hartnäckiges (aber nicht MEB-betroffenes) Fahrzeug eingestellt hat, real nur noch alle 5 Minuten einen tatsächlichen Schreibzugriff sehen, der Rest wird lautlos unterdrückt. Das ist vermutlich exakt das gewünschte Verhalten für die MEB-Fahrzeuge, aber eine stille Verschärfung einer Einstellung, die an ganz anderer Stelle (Fahrzeug-Template) gesetzt wurde, ohne dass das dort irgendwie sichtbar wird.

Ich würde dafür aber nicht noch eine Einstellung ergänzen wollen - wir haben in der openWB schon reichlich Konfigurationsoptionen, und jede weitere macht es für Nutzer schwerer, das Gesamtbild zu verstehen. Stattdessen: initiate_control_pilot_interruption() könnte vor dem Auslösen eines stuck-Retries selbst evse_transition_filter.remaining(evse_id, 0) prüfen und den Versuch überspringen (nur loggen), wenn der Filter ihn ohnehin blocken würde - statt einen Schreibzugriff auszulösen, der stumm verschluckt wird. Das ist eine interne Abstimmung zwischen zwei bereits vorhandenen Mechanismen, keine neue Stellschraube für Nutzer - control_pilot_interruption_retry_interval behält seine bisherige Bedeutung, die Hardware-Sicherheitsgrenze deckelt sie bei Bedarf still und nachvollziehbar über das Log.

Nebenbei, ganz ohne Vorwurf: für die Weiterentwicklung lohnt es sich generell, auf Basis von master statt eines älteren Standes wie 2.1.9 zu arbeiten - genau solche Überschneidungen wie mit #3717 wären dir dann vermutlich schon früher aufgefallen.

@s0170071

Copy link
Copy Markdown
Contributor Author

prüfen und den Versuch überspringen (nur loggen), wenn der Filter ihn ohnehin blocken würde

entweder das oder force=true benutzen, so wie das für die Phasenumschaltung gemacht wird. Brauchst Du aber wahrscheinlich gar nicht. Stell die Evse einmalig auf 16A und mach mit CP was Du willst.

@seaspotter

Copy link
Copy Markdown
Collaborator

Kurzer Nachtrag dazu: dein Vorschlag (EVSE einmalig setzen, CP unabhängig davon steuern) klingt nach der saubereren Lösung als das, was ich vorgeschlagen hatte - aber das ist eine echte Verhaltensänderung an perform_cp_interruption(), nicht nur eine Kleinigkeit, und hängt an einer Hardware-Annahme (EVSE braucht kein current=0 während CP umgeschaltet wird), die ich von hier aus nicht verifizieren kann.

Mein Vorschlag: das vor dem Merge dieses PRs klären, nicht erst danach als Follow-up. Der Grund: ohne die Anpassung wäre control_pilot_interruption_retry_interval ab dem Merge für jeden Wert unter 5 Minuten still auf einen faktischen 5-Minuten-Takt gedeckelt - eine bereits produktiv genutzte, in #3717 bewusst konfigurierbar gemachte Einstellung würde sich also lautlos anders verhalten als eingestellt, ohne Hinweis dazu.

@s0170071

Copy link
Copy Markdown
Contributor Author

Mir fallen folgende Optionen ein:

  1. CP Unterbrechungen sind immer langsamer als der Filter. Kleinster einstellbarer Wert 5 Minuten.
  • Also morgens ist EVSE=0, CP=1.
  • t=0 Evse=6
  • t_20 Fahrzeug lädt nicht.
  • t=301 Evse=0
  • t=301 cp=0
  • t= 600 cp=1
  • t= 601 Evse=6

und so weiter. Meine Meinung: ist nicht getestet, ich kann das nicht testen. Hier sind wir auf feedback angewiesen was man nicht so einfach bekommt wenn sich nix ändert am code.

  1. Checkbox Schnellere CP Unterbrechung erlauben. Fügt kleinere Einstellwerte hinzu und macht force=true Meine Meinung: schön ist anders.
  2. Checkbox Evse Filter aktivieren. Meine Meinung: Kann ich mit leben, schön isses nicht. Der Evse Filter sollte eher keinen Kontakt zum Kunden haben. Ist ja ein Sicherheitsnetz.
  3. Evse in Ruhe lassen und mit nur mit CP wackeln (siehe oben). Ehrlich weiß ich nicht ob das geht und kann es auch nicht ausprobieren. Ich halte es aber für ungefährlich, unter Last wird nicht geschaltet weil lädt ja nicht. Meine Meinung: wie 1.
  4. force=true für diese Funktion. Meine Meinung: untergräbt den Filter.
  5. Filter nicht einbauen. Meine Meinung: worst case. Der Filter hat für mich OpenWB überhaupt erst nutzbar gemacht.

@seaspotter

Copy link
Copy Markdown
Collaborator

Hast du denn überhaupt die funktion mit cp_retry aus #3717 überhaupt mal getestet? Weil Auslöser war ja genau jemand mit einem Elroq und dafür hab ich diese Option eingebaut, das man dann bei aktivem cp_interrupt auch die cp Unterbrechnung erneut anstößt.

Unds zu 6. es würden sich ja haufenweise Meldungen sammeln wenn andere auch ein riesen Thema damit hätten und openWB nicht nutzbar wäre. Die Tickets würden sich stapeln und es hätte schon eventuell ne Alternative/Lösung dafür gegeben. Also glaub ich nicht, das der Filter jetzt das Allheilmittel ist wie du behauptest :)

@s0170071

Copy link
Copy Markdown
Contributor Author

Also
Keine Phasenumschaltung, da das Fahrzeug nicht mit der vorgegebenen Stromstärke +/- der erlaubten Stromabweichung aus dem Fahrzeug-Profil/Minimalen Dauerstrom lädt. Es darf um max 5A über den aktuell genutzten Strom geregelt werden. Die Einstellungen in dem Ladepunkt-Profil beschränken den Strom auf maximal 16 A.
hatte ich auch schon. Das liegt daran, dass meiner bis zu 1A unter dem Sollwert lädt. Hatte in meiner 2.1.9 custom ein Offset Feld eingebaut, dann war der Spuk vorbei. Aber die erlaubte Phasenabweichung hochstellen hilft auch.

Mein Problem war ein anderes. Bei mir ist er auf Störung gegangen. Led am Port rot, nicht rücksetzbar außer durch Stecker abziehen. Wobei Stecker abziehen nicht ausschließlich per CP geprüft wird sondern auch durch PP. Und eine PP Unterbrechung gibts nicht, der ist hart verdrahtet im Stecker.

Der Grund mindestens einer Störung war eine Relaisschleife bei Phasenwechsel. #3319 Zufällig stand ich neben der Box als es passierte, sonst hätt ich das nie gefunden. Taucht in keinem Log auf.

Aus der Erfahrung heraus hab ich dann diesen low level Filter implementiert. Als Robustheitsmaßnahme.

@LKuemmel

Copy link
Copy Markdown
Contributor

Der Regelalgorithmus ist über Lademodi, Lastmanagement, Phasenumschaltung, CP-Unterbrechung und Fehlerbehandlung so verzweigt, dass sich das Ein- und Ausschalten der Ladefreigabe aus mehreren voneinander unabhängigen Stellen ergibt

Auf welche Stellen beziehst Du Dich hier? Es gibt genau einen Soll-Strom am Ende des Algorithmus und eine Stelle, die den Strom setzt.

Deinen Lösungsansatz sehe ich eher kritisch, er behebt ein Symptom, ohne den Bug zu identifizieren und bringt gleichzeitig sehr viel Komplexität in die Abhängigkeiten zu den restlichen Einstellungen.

@s0170071

Copy link
Copy Markdown
Contributor Author

Ja natürlich ist es besser den Code zu fixen. Sieh das hier als Robustheitsmaßnahme.
Is nicht lange her da gabs noch Relais Schleifen. Klassische race-condition. Sowas passiert. Und wenn das so ist, wäre es gut es gibt mindestens nen Log Eintrag damit man weiß das da was passiert ist. Und wenn man das schon detektier, kann mans auch gleich verhindern.
Diese low-level Evse writes tauchen ja nicht mal im Log auf und dann muss man noch daneben stehen, weil es in 20 Minuten weg rotiert. War damals echt Zufall dass ich das klackern gehört hab.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants