No description
  • TypeScript 94.5%
  • CSS 2.6%
  • JavaScript 1.8%
  • Dockerfile 0.8%
  • HTML 0.3%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Dominik Bechtel cdf7fa2be9 Leere Seite behoben: Live-State erst bei bestehender Sitzung + Error-Boundary
Ursache der weißen Seite: useLiveState() lief unabhängig vom Login. Ohne Token
lieferten /api/state und /ws HTTP 401; der Fehler-Body ({error:"unauthorized"})
wurde in den State geschrieben und Dashboard/EnergyFlow lasen state.snapshot.power
-> TypeError -> React-Baum ohne Error-Boundary komplett abgerissen (leere Seite,
Login-Screen erschien nie).

- useLiveState(enabled): verbindet erst, wenn eine Sitzung besteht; ignoriert
  nicht-OK-Antworten und übernimmt nur Snapshots mit gültigem Inhalt.
- App: rendert erst nach aufgelöstem initAuth() (kurzer Ladezustand statt Shell),
  Live-Verbindung erst bei sessionOk.
- ErrorBoundary als Sicherheitsnetz: ein Komponenten-Crash weißt nicht mehr die
  gesamte PWA, sondern zeigt eine Meldung mit Neu-laden.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 08:31:35 +02:00
.vscode Initialer PV-Energiemanager: Backend, PWA, Doku 2026-07-22 23:11:40 +02:00
apps Leere Seite behoben: Live-State erst bei bestehender Sitzung + Error-Boundary 2026-07-24 08:31:35 +02:00
deploy Reverse-Proxy (Caddy) für gültiges öffentliches HTTPS ergänzt 2026-07-24 07:52:52 +02:00
tools Projekt konsolidieren: toten Code entfernen, README aktualisieren 2026-07-23 10:03:42 +02:00
.env.example UX-Feinschliff: benannte WR, editierbare Tarife, Sankey, Graph-Toggles, iOS-Safe-Area, Push-Hinweis, WP-Zähler-Fremdlast 2026-07-23 20:04:28 +02:00
.gitignore Projekt konsolidieren: toten Code entfernen, README aktualisieren 2026-07-23 10:03:42 +02:00
CLAUDE.md Initialer PV-Energiemanager: Backend, PWA, Doku 2026-07-22 23:11:40 +02:00
docker-compose.yml Auswertung: Autarkie, Eigenverbrauch, €-Wert + COP-Trend (Phase 1) 2026-07-23 10:16:18 +02:00
icon.png Branding: echtes App-Icon + Logo einbinden, Chart-Tooltips lesbar 2026-07-23 20:48:51 +02:00
logo.png Branding: echtes App-Icon + Logo einbinden, Chart-Tooltips lesbar 2026-07-23 20:48:51 +02:00
package-lock.json OIDC/Keycloak-Authentifizierung (Phase 6) 2026-07-23 13:57:00 +02:00
package.json Initialer PV-Energiemanager: Backend, PWA, Doku 2026-07-22 23:11:40 +02:00
PLAN.md Initialer PV-Energiemanager: Backend, PWA, Doku 2026-07-22 23:11:40 +02:00
README.md Reverse-Proxy (Caddy) für gültiges öffentliches HTTPS ergänzt 2026-07-24 07:52:52 +02:00
tsconfig.base.json Initialer PV-Energiemanager: Backend, PWA, Doku 2026-07-22 23:11:40 +02:00

PV-Energiemanager

Steuert Wärmepumpe (Stiebel Eltron WPL 20A, Modbus TCP) und Wallbox (go-e Charger V4) nach PV-Überschuss Ziel: maximaler Eigenverbrauch / minimale Energiekosten bei schonendem Betrieb der Wärmepumpe. Bedienung per installierbarer PWA (iPhone/iPad).

Konzept, Anlagendaten und Wirtschaftlichkeit: siehe PLAN.md.

Architektur

Ports-&-Adapters (hexagonal). Der Domänenkern ist I/O-frei und voll unit-getestet.

apps/backend    Node.js + TypeScript (Fastify + WebSocket)
  src/domain/       Optimizer (Prioritätskaskade) + WP-Schutzschicht + Prognose-
                    Kalibrierung + Modell  (rein, testbar)
  src/application/  Control-Loop (EnergyManager), Analytics, WP-Leistungs-Kalibrierung
  src/ports/        Schnittstellen (PowerMeter, HeatPump, Wallbox, Meter, Forecast,
                    PvProduction, Battery, Tariff, Persistence)
  src/adapters/     sma (Multicast-Zähler + Wechselrichter-Modbus) · stiebel (Modbus) ·
                    goe (HTTP) · mbus (node-mbus) · forecast (forecast.solar + open-meteo) ·
                    tariff (statisch/aWATTar) · push (Web-Push/VAPID) · persistence (SQLite)
  src/api/          REST + WebSocket + OIDC-Auth (Keycloak/JWKS)
apps/frontend   React + Vite PWA (Dashboard, Verlauf, Einstellungen)

Datenfluss

SMA (Netzbilanz) + WP-Zustände + Wallbox + M-Bus (WP-Verbrauch) + Ist-PV (Wechselrichter)OptimizerSchutzschicht (Veto/Begrenzung) → Aktoren (modusabhängig) → SQLite + WebSocket-Push. Die PV-Prognose (forecast.solar mit open-meteo-Fallback) wird laufend gegen die gemessene Erzeugung selbstkalibriert.

Betriebsmodi

Modus Verhalten
off nur Monitoring, keine Steuerung
dry-run Optimizer rechnet & loggt, greift nicht ein (Standard)
assist nur die Wallbox wird gesteuert
full Wallbox + Wärmepumpe (WW-Sollwert) werden gesteuert

Fail-Safe: Bei veralteten Netzdaten (SMA-Timeout) wird nicht gesteuert. Fehler einzelner Geräte brechen den Zyklus nicht ab. Die WP-Schutzschicht (Mindestlauf-/Stillstandszeit, Hysterese, Rate-Limiting, Schrittbegrenzung, Tages-Startlimit, EVU-/Abtau-/Fehler-Veto) steht hierarchisch über dem Optimizer.

Entwicklung

npm install
npm test                       # Domänen-Tests (Optimizer, Schutz, SMA-Decoder)
npm run typecheck              # Backend + Frontend

# Backend (liest Hardware, dry-run):
cp .env.example .env
npm run dev                    # http://localhost:8080

# Frontend (Vite, proxyt /api + /ws auf :8080):
npm run dev:web                # http://localhost:5173

Native Module (better-sqlite3, node-mbus) werden beim Install gebaut. Auf manchen Systemen müssen Install-Skripte freigegeben werden (npm rebuild better-sqlite3 node-mbus).

Produktion (Docker, Ubuntu-Server)

npm run build                  # baut Backend (dist) + PWA (dist)
docker compose up -d --build

docker-compose.yml nutzt network_mode: host (nötig für den SMA-Multicast) und reicht /dev/ttyUSB0 (M-Bus) durch. Die gebaute PWA wird vom Backend unter / mit ausgeliefert.

Öffentliche HTTPS-Erreichbarkeit (Reverse-Proxy)

Für Login (OIDC), PWA-Installation und Web-Push ist ein öffentlich vertrautes TLS-Zertifikat nötig. Dafür liegt in deploy/ ein Caddy-Reverse-Proxy (automatisches Let's Encrypt):

docker compose -f deploy/docker-compose.proxy.yml up -d

Voraussetzungen im Router (FritzBox): DynDNS/MyFRITZ auf die öffentliche IP, Port 80 und 443 an den Proxy-Host weiterleiten, und den FritzBox-eigenen Fernzugriff von Port 443 wegnehmen (sonst terminiert die FritzBox selbst 443 mit ihrem — für die Domain ungültigen — Zertifikat). Domain/Backend-Adresse in deploy/Caddyfile anpassen.

Konfiguration (.env)

Siehe .env.example. Wichtig: CONTROL_MODE, Geräte-Adressen, Tarife (PRICE_*), MBUS_*, SMA_INVERTER_* (Ist-PV der Wechselrichter), FORECAST_* (inkl. open-meteo-Fallback und Selbstkalibrierung) und OIDC_* (Keycloak-Auth, aktiv sobald Issuer + Client-ID gesetzt sind).

Strategie- und Schutzparameter sowie PV-Flächen, Standort und Geräte-Adressen sind zur Laufzeit über die PWA änderbar (Tab „Strategie" bzw. „Anlage"). Diese Overrides werden persistiert (config_overrides in der DB) und beim Start über die env-Config gelegt; Geräteänderungen greifen nach einem Neustart (POST /api/restart).

API

Methode Pfad Zweck
GET /api/state aktueller Systemzustand (inkl. Alarme, Ladeplan, Tagesplan)
GET /api/health Kurzstatus + Geräte-Health (öffentlich)
GET /api/config Modus, Strategie, Tarife, Geräte
POST /api/mode `{ "mode": "dry-run
PATCH /api/strategy Strategie-/Schutzparameter setzen (persistent)
GET/PATCH /api/settings Geräte-/Prognose-/Anlagen-Config (persistente Overrides)
POST /api/restart Neustart zur Übernahme geänderter Geräte-Config
GET /api/history?hours=24 Verlaufsdaten
GET /api/analytics?hours=24 Bilanz: Autarkie, Eigenverbrauch, €-Wert
GET /api/cop-history?days=14 COP je Tag (Trend)
GET /api/forecast PV-Prognose
GET /api/auth/config OIDC-Parameter für die PWA (öffentlich)
GET/POST /api/push/{vapid,subscribe,unsubscribe} Web-Push-Abos
WS /ws Live-Push des Zustands (Token via ?token=)

Bei aktiver Authentifizierung (OIDC) sind alle Endpunkte außer /api/health und /api/auth/config sowie die statische PWA tokenpflichtig (Authorization: Bearer).

Vor dem Scharfschalten (full) validieren

Read-only wurde gegen echte Hardware getestet (SMA, WP-Modbus, go-e erreichbar, plausible Werte). Vor assist/full bitte in dry-run verifizieren:

  • Modbus-Registersemantik: EVU-Freigabe (32502) und Puffer-Solltemperatur (30519) gegen das ISG-Display abgleichen (Adressierung/Skalierung sind in adapters/stiebel/registers.ts zentral).
  • go-e-Leistung: Skalierung der Strom-/Leistungsfelder mit realer Ladung prüfen (adapters/goe/wallboxGoe.ts).
  • M-Bus: Primäradresse & VIF des WP-Zählers verifizieren (adapters/mbus/).

Diagnose-Tools (auf dem Server ausführen)

node tools/modbus-dump.mjs [host] [port]   # WPL-20A-Register roh dumpen (Semantik prüfen)
node tools/mbus-scan.mjs [device] [baud]   # M-Bus-Zähler finden + Rohframe dumpen

Umgesetzt / Roadmap

Bereits umgesetzt:

  • COP heute aus Wärmemengen-/Leistungs-Tagesregistern (33501/33504/33511/33514), in der PWA sichtbar
  • Vorausschauende Steuerung (Lastverschiebung): forecastAdvisor passt WP-Schwellen an die PV-Prognose an (Geduld vor Peak, Rest-Sonne abends). Abschaltbar über strategy.predictive.
  • Ist-PV-Erzeugung der drei Sunny Tripower über Modbus TCP (smaInverterModbus), im Dashboard je Wechselrichter sichtbar und als Grundlage für die Kalibrierung.
  • Prognose mit Fallback & Selbstkalibrierung: forecast.solar + open-meteo (CombinedForecast, kein Rate-Limit-Ausfall), Bias-Korrektur gegen die gemessene Erzeugung (ForecastCalibrator).
  • Selbstkalibrierte WP-Aufnahme aus dem M-Bus-Zähler (wpPowerCalibrator) setzt die Überschuss-Schwellen automatisch.
  • Legionellenschutz als App-Programm (wöchentlich, bevorzugt solar).
  • Auswertung: Autarkiegrad, Eigenverbrauchsquote, €-Wert des Eigenverbrauchs und COP-Trend (Tageshistorie) in der PWA (computeAnalytics, /api/cop-history).
  • Alarme & Web-Push (alertMonitor + pushService/VAPID): WP-Fehler, ausgefallene Datenquellen, schwacher Wechselrichter — Banner in der PWA und Push-Benachrichtigung.
  • E-Auto-Ladeplanung (evChargePlanner): Energieziel bis Uhrzeit, PV-Vorrang mit garantierter Netz-Nachladung; prognose- und (dyn.) tarifbewusst.
  • Prädiktive Tagesplanung (dayPlanner): 24h-Ausblick mit Ertragsfenster, Rest-Ertrag und günstigen Netzstunden; speist die Ladeplanung.
  • Dynamischer Tarif im Optimierer: günstige Netzstunden werden für die Ladung genutzt.
  • Konfiguration in der PWA: PV-Flächen, Standort und Geräte-Adressen editierbar (persistente Overlays, config/overrides.ts).
  • Authentifizierung via OIDC/Keycloak (Authorization Code + PKCE, JWKS-Verifikation).
  • Erweiterungspunkte: BatteryPort (+ NullBattery) und TariffProvider (StaticTariff fix, AwattarTariff dynamisch, TARIFF_PROVIDER=awattar).

Weiter geplant:

  • Hausakku aktiv in die Verteillogik einbeziehen (SoC-abhängige Priorität), sobald Hardware da ist
  • E-Auto-SoC direkt einbeziehen, sobald ein BEV mit auslesbarem Ladestand vorhanden ist