Skip to content

Peak Shaving: Batterie bei Stopp, HA-Neustart und Ausfall zuverlässig zurückgeben (Review D4, D3, B3, E9, B13, B20) - #82

Merged
SolarPower2024 merged 8 commits into
load-peak-featuresfrom
review/batterie-rueckgabe
Oct 12, 2026
Merged

SolarPower2024 merged 8 commits into
load-peak-featuresfrom
review/batterie-rueckgabe

Conversation

@SolarPower2024

Copy link
Copy Markdown
Owner

Behebt B3, D3 (hoch), D4, E9, B13, B20 aus dem Architektur-Review (Paket P4, Entscheidungen D6 und D7). Je Fund ein Commit, dazu zwei Commits aus dem Code-Review.

  • D4: HA ohne Antwort kostet höchstens einen Timeout je Zyklus (Merker), danach keine weiteren HA-Aufrufe im Zyklus; ungelesene Entitäten werden nicht mehr blind geschrieben (auch der EEG-Zähler-Lesezugriff).
  • D3: Ein Dienstaufruf an eine unavailable/unknown Entität zählt nicht mehr als Freigabe; owned bleibt, die Freigabe wird nach der Rückkehr nachgeholt (live beim HA-Start beobachtet: 200 ohne Wirkung).
  • B3: Beim Stoppen von evcc (Update, Neustart) wird die Batterie freigegeben: Marstek/Omnibattery bekommt das reine Peak-Limit, Handsteuerung aus; BYD bekommt den Freiwert und Ladeleistung 0. Danach schreibt der Zyklus nichts mehr. Absturz und SIGKILL deckt weiter das gespeicherte owned ab.
  • E9: der Merker für die gehaltene Batterie geht sofort in die Datenbank, nicht erst im Minuten-Takt.
  • B13: restoreCustom läuft als defer direkt nach dem Test-Guard von restoreSettings, also auch wenn ein evcc-Restore-Schritt scheitert; Vertragstest per AST (Hook steht in einem defer).
  • B20: Omnibattery bekommt nach dem Fensterende nicht mehr den alten erlaubten Wert.
  • Review-Fixes: die Rückgabe beim Stoppen wurde vom HA-Wächter (D4) still verweigert; ein Lese-404 einer number-Entität verhinderte die Freigabe – beides behoben.

Tests: TestPeakReleasedOnShutdown, fakeHA mit unavailable, Test mit hängendem Transport (eine Anfrage je Ausgabe und Zyklus), Persistenz mit SQLite, AST-Vertragstest. wsltest.sh ./core/ ./core/peak/ grün vor und nach den Fixes.

Code-Review (Opus, mittel): zwei Funde mittel (behoben). Offen (niedrig): ein Timeout außerhalb des Zyklus sperrt den ganzen nächsten Zyklus. Bewusst nicht gebaut: Schalter auch ausschalten, wenn das Schreiben der Ladeleistung scheitert – widerspricht dem dokumentierten Ablauf (erst Leistung 0, dann Schalter aus; TestOmniGridChargeEndZeroFails).

Für das Add-on (CHANGELOG): Batterie wird beim Stoppen von evcc an Home Assistant zurückgegeben; nicht verfügbare Entitäten (z. B. beim HA-Start) werden nicht mehr als freigegeben gewertet; hängt HA, verzögert das die Regelung nur noch um einen Timeout. Add-on: timeout: 30 im Manifest, damit die Rückgabe vor dem Beenden fertig wird (eigener PR im Add-on-Repo, ohne Version-Bump).

Hook-Änderung in core/site.go (B13), Touch-Point-Tabelle und forkHooks angepasst. Überschneidung mit P3/P5 in core/site_peakshaving.go.

🤖 Generated with Claude Code

SolarPower2024 and others added 8 commits October 11, 2026 22:02
…elesene Entitaeten werden nicht geschrieben (Review D4)

Ein haengendes Home Assistant verlaengerte den Zyklus um einen Timeout je
Aufruf (Energiesensor, Sollwert, Ladeleistung, Marstek-Entitaeten, EEG-Zaehler:
40 s und mehr). Der Waechter im Transport der gemeinsamen Verbindung
(core/site_ha_guard.go) lehnt nach dem ersten Timeout die weiteren Aufrufe des
Zyklus ab, am Ende des Zyklus (updateBatteryModePeakAware) fragt der naechste
wieder. Der EEG-Zaehler wird nach einem eigenen Timeout eine Minute nicht
gelesen. numberSetter und omniSwitchOff schreiben nach einem Lesefehler nicht
mehr blind, sondern geben den Fehler zurueck; die Freigabe versucht es im
naechsten Zyklus erneut und behaelt solange, was evcc haelt.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…s Freigabe (Review D3)

omniRead und numberState melden unknown/unavailable wie GetState als
api.ErrNotAvailable. Dann wird nicht geschrieben (Home Assistant antwortet mit
200 und tut nichts), peakShavingOwned und peakShavingProtOwned bleiben gesetzt,
und die Freigabe wird im naechsten Zyklus wiederholt, auch waehrend Home
Assistant startet und die Entitaeten noch nicht kennt. Nur ein Schalter, den
Home Assistant gar nicht kennt (404), gilt als aus, sonst blockierte die
Freigabe jede Aenderung der Entitaeten. fakeHA kennt jetzt nicht verfuegbare
und fehlende Entitaeten.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Beim Stoppen gab evcc nur seinen Batteriemodus zurueck. Was der Fork ueber Home
Assistant haelt, blieb bis zum ersten Zyklus nach dem naechsten Start: Marstek
mit Ladeleistung und Handsteuerung (Netzladen bis voll ohne Peak-Gate), das
Limit von Omnibatterys Peak Shaving auf dem letzten Fensterwert, BYD mit
Sollwert und Ladeleistung. Eine Shutdown-Funktion (aus restorePeakSettings, in
core/site_peak_shutdown.go, kein neuer Upstream-Hook) setzt ein Flag, gegen das
der noch laufende Zyklus nichts mehr schreibt, und gibt in der Reihenfolge
Sicherheit zuerst zurueck: Marstek Ladeleistung 0 und Handsteuerung aus,
Omnibattery behaelt Peak Shaving mit dem Peak-Limit, BYD Ladeleistung 0 und
freier Wert. Best Effort in hoechstens 8 s, blockiert den Stopp nicht; was nicht
ankommt, bleibt als gehalten markiert. Abgelehnte Aufrufe waehrend des Stopps
(Supervisor meldet 400/503 not ready) stehen als info im Log.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
settings.SetBool merkt sich einen Wert nur im Speicher, in die Datenbank kommt
er mit dem Minuten-Takt oder beim Stopp. Ein Absturz in der ersten Minute nach
der Uebernahme verlor peakShavingOwned und peakShavingProtOwned, die Batterie
wurde nach dem Neustart nicht mehr zurueckgegeben. setPeakOwned und
setProtOwned speichern bei einer Aenderung sofort; ein Fehler dabei wird einmal
je Ursache geloggt. Die Tests lesen die Zeilen aus der Datenbank (SQLite im
Speicher) und werden ohne den Aufruf rot.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Der Aufruf stand am Ende von evccs restoreSettings, hinter neun fruehen
Returns. Scheitert ein Restore-Schritt von evcc (oder ein kuenftiges neues
return err), lud nichts vom Fork: Peak Shaving still aus, kein Kreis, eine
gehaltene Batterie wurde nie zurueckgegeben. Der Hook steht jetzt als defer
direkt hinter der Pruefung fuer Tests, die alte Zeile entfaellt. Neuer
Vertragstest TestForkDeferHooks prueft per Syntaxbaum, dass restoreCustom ein
defer im Funktionsrumpf ist und kein evcc-Return davor steht (vor der Aenderung
rot). Tabelle und Kommentar angepasst.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…bten Wert (Review B20)

Das Fenster mit dem erlaubten Wert wird nur in einem Zyklus mit Zaehlerwerten
erneuert. Faellt der Zaehler am Ende eines Fensters aus, behielt Omnibattery bis
zum Zaehlerausfall (2 min) den im alten Fenster eingefrorenen Wert, bis zum
Doppelten des Limits. applyOmniProtection nimmt Allowed jetzt nur, wenn das
Fenster das laufende ist, sonst das Peak-Limit wie bei Zaehlerausfall. Test mit
Allowed 7000, Uhr ueber der Grenze und ohne Zaehlerwerte (ohne die Aenderung rot).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Der Wächter lehnt nach einem Timeout alle Aufrufe ab, bis der Zyklus endet.
Die Shutdown-Funktion läuft neben dem Zyklus: lief dessen erster HA-Aufruf in
einen Timeout, bekamen Ladeleistung, Schalter und Sollwert der Rückgabe
errHAHung, und logWrite schrieb das nur auf TRACE. Die Batterie blieb
gehalten, ohne Zeile im Log, und das gerade bei langsamem Home Assistant.

releasePeakOnShutdown sagt dem Wächter jetzt zuerst, dass evcc stoppt
(haStop, baut die Verbindung bei Bedarf): er lehnt dann nichts mehr ab.
logWrite prüft das Stoppen vor errHAHung, eine Verweigerung beim Stoppen
steht auf INFO. Test mit einem Timeout im laufenden Zyklus für Marstek und
BYD, Test für den Wächter einer erst beim Stoppen gebauten Verbindung.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… Marstek bleibt in manueller Steuerung

Seit numberSetter bei jedem Lesefehler einen Fehler zurückgibt (statt blind zu
schreiben), scheiterte die Freigabe an einer Ladeleistungs- oder
Sollwert-Entität, die Home Assistant nicht kennt (gelöscht, umbenannt): Bei
einem Marstek blieb die manuelle Steuerung im Modus Charge an und lud weiter
aus dem Netz, in jedem Zyklus, beim Stoppen und bei Typ- oder
Entitätswechsel (errHandBack). Bei BYD blockierte der Sollwert den Typwechsel.
Das Konzept (D3) sieht vor, dass ein Lese-404 im Freigabepfad als freigegeben
gilt, umgesetzt war es nur für Schalter.

releaseOutput schreibt wie writeOutput, wertet einen 404 aber als
zurückgegeben (WARN im Log). Es gilt für die Ladeleistung in releaseOmni, für
releasePeak (Marstek und BYD), den BYD-Zweig von SetPeakShavingBatteryType und
die Freigabe des alten Ziels in SetPeakShavingEntity. Jeder andere Fehler
hält weiter fest. Die Reihenfolge Ladeleistung 0, dann Schalter aus bleibt:
Omnibattery nimmt die Leistung nur in manueller Steuerung an
(TestOmniGridChargeEndZeroFails). TestOmniEntityNotLoaded prüft jetzt eine
nicht verfügbare statt eine fehlende Entität, der 404-Fall steht in
TestOmniReleaseEntityGone.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@SolarPower2024
SolarPower2024 merged commit 8e31a84 into load-peak-features Oct 12, 2026
6 checks passed
@SolarPower2024
SolarPower2024 deleted the review/batterie-rueckgabe branch October 12, 2026 16:01
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.

1 participant