Skip to content

PV/Speicher/Verbraucher: nach 60s Fehler auf 0 setzen - #4002

Open
seaspotter wants to merge 8 commits into
openWB:masterfrom
seaspotter:pv-bat-consumer-error-zero
Open

seaspotter wants to merge 8 commits into
openWB:masterfrom
seaspotter:pv-bat-consumer-error-zero

Conversation

@seaspotter

@seaspotter seaspotter commented Sep 23, 2026 •

Copy link
Copy Markdown
Collaborator

Edit 03.10.2026:

Follow-up zu #3959 (und #3951/#3931). PV/Speicher/Verbraucher verhalten sich jetzt wie Zähler/Ladepunkt: nach COMPONENT_ERROR_DURATION (60s) andauerndem Fehler wird nicht mehr mit dem letzten bekannten Wert weitergerechnet bzw. weitergesteuert.

Mechanismus: FaultState bekommt einen on_sustained_error-Callback, der aus store_error() heraus feuert - direkt im Lese-Zyklus (SingleComponentUpdateContext/MultiComponentUpdateContext, innerhalb von ConfigurableDevice.update()), nicht erst beim späteren Publizieren. Verdrahtet einmalig und zentral in ConfigurableDevice.add_component() bzw. ConfigurableConsumer.__init__ - pro Komponente, kein Gerätemodul einzeln angefasst.

Grund für den Lese- statt Publizier-Zyklus: get_finished_component_obj_by_id() (aus #3958) überspringt bereits fehlerhafte Komponenten beim erneuten Publizieren - die Nullung hätte das sonst nie erreicht. Der Lese-Zyklus läuft davon unabhängig jeden Durchlauf.

  • Gemeinsame Konstante + error_duration_exceeded()-Prüfung statt vier unabhängig benannter Varianten (Counter, Chargepoint, PV, Bat, Consumer nutzen dieselbe Logik/Wortwahl)
  • Aktive Speichersteuerung (BatAll._set_bat_power_active_control()) und Verbraucher-Schaltbefehl-Gate (Consumer.get_parameter()) bleiben bewusst incontrol/- eigene Entscheidung, keine Werte mehr aktiv zu steuern, getrennt von der Frage was gemeldet wird
  • Bewusst keine neue Fehlermeldung/Benachrichtigung obendrauf (Feedback: der bestehende Fault-State/Status-Card-Mechanismus reicht) - bis auf eine offene Detailfrage, siehe Kommentare

Dabei gefunden und mitbehoben:

  • ConfigurableConsumer.error_handler() war nie verdrahtet (wie besprochen)
  • PurgeInverterState.update() korrigierte bei Hybrid-WR den letzten Wert in-place und lief auch ohne neuen Read jeden Zyklus erneut - dieselbe Fehlerklasse wie-PR Wechselrichter: Hybrid-Korrektur nicht erneut anwenden, wenn kein neuer Wert gesetzt wurde #4048, unabhängig davon gefunden und behoben
  • Verbraucher mit externem Zähler (extra_meter) berücksichtigten die Nullung nicht
  • Komponenten, die seit dem Start nie erfolgreich gelesen wurden, blieben stumm statt einen alten MQTT-Retained-Wert zu überschreiben

Getestet

  • Unit-Tests (677 grün, flake8 sauber)
  • Live auf echter Hardware + simulierten HTTP-Geräten (Node-RED): PV, Speicher, Zähler, Verbraucher einzeln und die Hybrid-PV+Speicher-Interaktion (Speicher bleibt unberührt, PV landet exakt bei 0 statt negativ zu werden) sowie Verbraucher mit extra_meter (beide Fehlerrichtungen) durchgespielt

@seaspotter
seaspotter requested a review from LKuemmel September 23, 2026 21:15
@seaspotter

Copy link
Copy Markdown
Collaborator Author

Fixed auch #3990

Comment thread packages/control/consumer/consumer.py
@LKuemmel

Copy link
Copy Markdown
Contributor

Bei den Ladepunkten wird direkt in der Ladepunkt-Klasse bei Ablaufen des Fehler-Timeouts die Leistung auf 0W gesetzt. Nicht erst in der Chargepoint-Klasse, die die Logik für die Regelung abbildet.

Ich finde, dass Zurücksetzen im Fehlerfall sollte noch innerhalb der Aufrufe von loadvars erfolgen. Allerdings wird bei den Komponenten die Werte im Fehlerzustand nicht mehr geschrieben, bei den Ladepunkten aber schon und bei ConfigurableConsumer wird der error_handler gar nicht aufgerufen.

@seaspotter

Copy link
Copy Markdown
Collaborator Author

Bei den Ladepunkten wird direkt in der Ladepunkt-Klasse bei Ablaufen des Fehler-Timeouts die Leistung auf 0W gesetzt. Nicht erst in der Chargepoint-Klasse, die die Logik für die Regelung abbildet.

Ich finde, dass Zurücksetzen im Fehlerfall sollte noch innerhalb der Aufrufe von loadvars erfolgen. Allerdings wird bei den Komponenten die Werte im Fehlerzustand nicht mehr geschrieben, bei den Ladepunkten aber schon und bei ConfigurableConsumer wird der error_handler gar nicht aufgerufen.

Danke für den Hinweis Lena. Wenn ich dich richtig verstehe: du hättest das Zurücksetzen auf 0 im Fehlerfall lieber innerhalb der loadvars-Kette (also auf Modul-/Komponenten-Ebene, so wie es für Geräte über MultiComponentUpdateContext.error_handler im Prinzip schon vorgesehen ist), statt wie jetzt in control/ - richtig?

Falls ja: das würde heißen, die Logik aus pv_all.py/bat_all.py/consumer/consumer.py wieder rauszunehmen und stattdessen in die jeweiligen ConfigurableDevice/ConfigurableConsumer-Klassen zu verschieben - und dabei gleich den Bug mitzunehmen, dass ConfigurableConsumer.error_handler() aktuell nirgends aufgerufen wird, Verbraucher also bislang gar kein Reset im Fehlerfall bekommen. Ist das der Ansatz, den du dir vorstellst, oder meinst du etwas anderes?

Nur als weiteres Beispiel, warum sich das schwer von selbst ableiten lässt: pv.py/pv_all.py und bat.py/bat_all.py liegen direkt unter control/, consumer.py aber schon in einem eigenen control/consumer/-Unterordner - es gibt also nicht mal auf Ordnerebene eine einheitliche Konvention, an der ich mich orientieren könnte.

Und dazu noch ein offenes Wort, auch wenn es unangenehm ist: bei diesem PR und auch schon bei #3899 (dort jetzt die dritte Runde) stehe ich jeweils lange ohne echte Rückmeldung da, wo ein Fix architektonisch hinsoll, bis das erst im Review nach bereits geleisteter Arbeit kommt. Das kostet mich jedes Mal viel Zeit, die eine kurze Einschätzung vorher gespart hätte. Wenn du schon eine klare Vorstellung hast, wo etwas strukturell hingehört, wäre es mir sehr geholfen, die vorher statt hinterher zu bekommen - sonst überlege ich mir, solche architektonischen Entscheidungen lieber dir zu überlassen, statt mehrere Runden Arbeit zu investieren, die dann erneut wieder doch nicht passen.

@LKuemmel

LKuemmel commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

Danke für den Hinweis Lena. Wenn ich dich richtig verstehe: du hättest das Zurücksetzen auf 0 im Fehlerfall lieber innerhalb der loadvars-Kette (also auf Modul-/Komponenten-Ebene, so wie es für Geräte über MultiComponentUpdateContext.error_handler im Prinzip schon vorgesehen ist), statt wie jetzt in control/ - richtig?

Falls ja: das würde heißen, die Logik aus pv_all.py/bat_all.py/consumer/consumer.py wieder rauszunehmen und stattdessen in die jeweiligen ConfigurableDevice/ConfigurableConsumer-Klassen zu verschieben - und dabei gleich den Bug mitzunehmen, dass ConfigurableConsumer.error_handler() aktuell nirgends aufgerufen wird, Verbraucher also bislang gar kein Reset im Fehlerfall bekommen. Ist das der Ansatz, den du dir vorstellst, oder meinst du etwas anderes?

Ja, da muss es hin.

…chnen

Analog zum bestehenden Mechanismus beim Zähler (MAX_EVU_ERROR_DURATION):
wenn eine PV-, Speicher- oder Verbraucher-Komponente dauerhaft im
Fehlerzustand ist, wird sie nach 60s nicht mehr mit ihrem letzten
bekannten Leistungswert in die jeweilige Summe eingerechnet, sondern
mit 0W. Der eigene get.power-Wert der Komponente bleibt unverändert
(gleiches Verhalten wie beim Zähler) - nur die Summenbildung in
PvAll/BatAll/AllConsumers sowie Counter._set_power_left berücksichtigt
den Fehlerfall.
…ler nicht mehr mit altem Wert rechnen

Vereinheitlicht den Fehlerfall-Mechanismus über Counter/Chargepoint/PV/Bat/Consumer:
gemeinsame COMPONENT_ERROR_DURATION-Konstante und error_duration_exceeded()-Prüfung.
PV/Bat setzen get.power nach Ablauf der Gnadenfrist direkt auf 0 (wie Counter/Chargepoint
bereits), pro Komponente einzeln. Consumer.update() ist jetzt die einzige Stelle, die den
Fehlerfall abbildet - behebt eine ~1-Zyklus-Verzögerung zwischen Counter._set_power_left()
und ConsumerAll.get_consumer_sum(). Zusätzliche Gates verhindern, dass die aktive
Speichersteuerung (BatAll._set_bat_power_active_control(), inkl. Ausschluss fehlerhafter
Speicher aus der Kapazitäts-Summe) bzw. die Verbraucher-Ansteuerung (Consumer.get_parameter())
noch auf Basis veralteter Werte weiterarbeiten.
…h nie persistiert

setdata.py kannte /set/error_timer nur für counter, wurde für pv/bat/consumer per
__unknown_topic verworfen. subdata.py routete /pv/<id>/set/ zusätzlich gar nicht erst
in Pv.data.set. Da control/data.py jeden Zyklus frisch aus SubData kopiert, wurde
error_timer dadurch bei jedem Zyklus auf None zurückgesetzt - der Fehlerzustand konnte
nie 60s andauern. Live auf Testsystem gefunden (error_timer stieg bei jedem Print statt
konstant zu bleiben).
…UI zurückgemeldet

Anders als bei PV/Bat fehlte bei Consumer.Get.power die metadata={"topic": "get/power"}.
changed_values_handler publiziert Feldänderungen nur für Felder mit Topic-Metadaten - der
vom Fehlerfall-Mechanismus berechnete Wert (0 nach 60s) blieb dadurch rein im Python-Prozess
hängen und erreichte nie den Broker bzw. die Status-Karte in openwb-ui-settings
(ConsumerCard.vue liest direkt von openWB/consumer/<id>/get/power). Die Summe (Alle
Verbraucher) war davon nicht betroffen, da consumer_all.py's power-Feld die Metadaten
bereits hatte.
…R_DURATION nutzen

War nur noch ein Alias auf COMPONENT_ERROR_DURATION mit genau einer Verwendungsstelle -
konsequent zu Ende geführt, damit über alle Module hinweg dieselbe Konstante sichtbar ist.
@seaspotter
seaspotter force-pushed the pv-bat-consumer-error-zero branch from 6dc6138 to 792f706 Compare October 3, 2026 08:38
…lieren

Analog zu RCT (openWB#3958): ein Fehler bei nur einer Komponente (falsches
Register/Endpoint) markiert nicht mehr fälschlich die anderen als defekt.
@seaspotter
seaspotter force-pushed the pv-bat-consumer-error-zero branch from f578eda to ebe66ce Compare October 3, 2026 10:20
FaultState bekommt einen on_sustained_error-Callback, der aus store_error()
heraus feuert - also im Lese-Zyklus (SingleComponentUpdateContext/
MultiComponentUpdateContext, ConfigurableDevice.update()), nicht erst beim
späteren Publizieren. Der Lese-Zyklus läuft unconditioned jeden Durchlauf,
unabhängig davon, ob eine Komponente bereits als fehlerhaft bekannt ist -
loadvars.py überspringt genau das für bereits fehlerhafte Komponenten beim
Publizieren (get_finished_component_obj_by_id()), was die Nullung sonst nie
erreicht hätte.

Verdrahtet einmalig, zentral: ConfigurableDevice.add_component() für PV/Bat,
ConfigurableConsumer.__init__ für Verbraucher - pro Komponente, kein
Gerätemodul angefasst.

Dabei mitbehoben:
- ConfigurableConsumer.update() hat Exceptions verschluckt, bevor
  error_handler() sie je sah (reraise=False) - Verbraucher bekamen dadurch nie
  dessen Fehlerbehandlung.
- PurgeInverterState.update() korrigierte bei Hybrid-WR den zuletzt gesetzten
  Wert in-place und lief auch ohne vorheriges set() bei einem Lesefehler jeden
  Zyklus erneut - die Speicherleistung wurde dadurch bei jedem Fehler erneut
  vom Wechselrichter-Wert abgezogen. update() korrigiert jetzt eine Kopie.
- Verbraucher mit externem Zähler (extra_meter) richten die Nullung nach dem
  Fehlerzustand des Zählers selbst, nicht des Verbrauchers (dessen eigener
  Fehler ohnehin nur als Statusmeldung relevant ist, da die Leistung komplett
  vom Zähler kommt).

control/: effective_power()/EffectivePowerResult entfernt - pv_all.py/
bat_all.py/consumer.py vertrauen jetzt direkt auf get.power. Aktive
Speichersteuerung und Verbraucher-Schaltbefehl-Gate bleiben in control/
(eigene Entscheidung, keine Werte mehr aktiv zu steuern), dafür neu:
tick_error_timer().
zero_power_on_sustained_error() ging bisher von einem bereits gelesenen state
aus - eine Komponente, die seit dem Programmstart nie erfolgreich gelesen
wurde, hatte nichts zu nullen und blieb stumm. Ein davor über MQTT retained
alter Wert wäre dadurch nie überschrieben worden. Publiziert jetzt in diesem
Fall einen frischen Nullwert.
@seaspotter

Copy link
Copy Markdown
Collaborator Author

Ja, da muss es hin.

PR ist komplett überarbeitet - die Nullung läuft jetzt direkt im Lese-Zyklus der Module, nicht mehr in control/, so wie besprochen.

Zwei Dinge zu Fehlerzuständen bei Verbrauchern mit externem Zähler (extra_meter) sind mir beim Testen aufgefallen, die ich nicht selbst entscheiden wollte:

  • Ist der Zähler andauernd fehlerhaft, wird die Leistung korrekt auf 0 gesetzt - aber der Verbraucher selbst zeigt weiterhin "Kein Fehler", weil sein eigenes Modul davon ja nicht betroffen ist. Für jemanden, der nur auf den Verbraucher schaut, sieht eine echte 0W-Messung und eine durch Fehlerfall genullte Anzeige dadurch identisch aus - keine Warnung, nichts.
  • Umgekehrt: ist der Verbraucher selbst fehlerhaft, der Zähler aber nicht, zeigt der Verbraucher ganz normal eine Fehlermeldung (zB "HTTP 500: Server-Fehler") - obwohl die angezeigte Leistung durchgehend korrekt vom gesunden Zähler weiterläuft und gar nicht betroffen ist.

Ist aus meiner Sicht eine Experience-Entscheidung wie die Benachrichtigungsfrage vorher. Baust du das gern selbst ein, falls du eine Richtung im Kopf hast? Ich finds so etwas unschön, oder zumindest eine Warnung, kein Fehler in beiden Komponenten dann? Deine Entscheidung.

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.

Hausverbrauch/Fehlerfall: PV, Speicher und Verbraucher (Follow-up zu #3951)

3 participants