- TypeScript 94.5%
- CSS 2.6%
- JavaScript 1.8%
- Dockerfile 0.8%
- HTML 0.3%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
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>
|
||
| .vscode | ||
| apps | ||
| deploy | ||
| tools | ||
| .env.example | ||
| .gitignore | ||
| CLAUDE.md | ||
| docker-compose.yml | ||
| icon.png | ||
| logo.png | ||
| package-lock.json | ||
| package.json | ||
| PLAN.md | ||
| README.md | ||
| tsconfig.base.json | ||
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)
→ Optimizer → Schutzschicht (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 inadapters/stiebel/registers.tszentral). - 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):
forecastAdvisorpasst WP-Schwellen an die PV-Prognose an (Geduld vor Peak, Rest-Sonne abends). Abschaltbar überstrategy.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) undTariffProvider(StaticTarifffix,AwattarTariffdynamisch,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