Abrechnungs-App für EVCC
  • Python 79.1%
  • JavaScript 15.4%
  • CSS 2.9%
  • Shell 2.2%
  • HTML 0.2%
  • Other 0.2%
Find a file
Daniel Spittank eee6c16608
All checks were successful
ci / validate (push) Successful in 1m13s
release / validate (push) Successful in 1m19s
ci / docs (push) Successful in 22s
release / container (push) Successful in 3m5s
Version 0.5.12, Sperrmodus für Keba hinzugefügt, Dashboardoptimierung
2026-08-16 21:14:26 +02:00
.forgejo/workflows Workflowfix 2026-08-15 20:30:30 +02:00
deploy/truenas Version 0.5.12, Sperrmodus für Keba hinzugefügt, Dashboardoptimierung 2026-08-16 21:14:26 +02:00
docs Version 0.5.12, Sperrmodus für Keba hinzugefügt, Dashboardoptimierung 2026-08-16 21:14:26 +02:00
migrations Version 0.5 ins Repo 2026-08-15 18:38:03 +02:00
scripts Version 0.5.12, Sperrmodus für Keba hinzugefügt, Dashboardoptimierung 2026-08-16 21:14:26 +02:00
snlo Version 0.5.12, Sperrmodus für Keba hinzugefügt, Dashboardoptimierung 2026-08-16 21:14:26 +02:00
static Version 0.5.12, Sperrmodus für Keba hinzugefügt, Dashboardoptimierung 2026-08-16 21:14:26 +02:00
tests Version 0.5.12, Sperrmodus für Keba hinzugefügt, Dashboardoptimierung 2026-08-16 21:14:26 +02:00
.dockerignore Version 0.5 ins Repo 2026-08-15 18:38:03 +02:00
.env.example Version 0.5.12, Sperrmodus für Keba hinzugefügt, Dashboardoptimierung 2026-08-16 21:14:26 +02:00
.gitignore Version 0.5 ins Repo 2026-08-15 18:38:03 +02:00
.python-version Version 0.5 ins Repo 2026-08-15 18:38:03 +02:00
alembic.ini Version 0.5 ins Repo 2026-08-15 18:38:03 +02:00
CHANGELOG.md Version 0.5.12, Sperrmodus für Keba hinzugefügt, Dashboardoptimierung 2026-08-16 21:14:26 +02:00
compose.yaml Version 0.5.12, Sperrmodus für Keba hinzugefügt, Dashboardoptimierung 2026-08-16 21:14:26 +02:00
Dockerfile Version 0.5.10, Keba-Integration beendet 2026-08-16 18:03:39 +02:00
LICENSE Version 0.5.1 ins Repo 2026-08-15 19:22:13 +02:00
MANIFEST.sha256 Version 0.5.12, Sperrmodus für Keba hinzugefügt, Dashboardoptimierung 2026-08-16 21:14:26 +02:00
pyproject.toml Version 0.5.12, Sperrmodus für Keba hinzugefügt, Dashboardoptimierung 2026-08-16 21:14:26 +02:00
README.md Version 0.5.12, Sperrmodus für Keba hinzugefügt, Dashboardoptimierung 2026-08-16 21:14:26 +02:00
requirements-dev.txt Version 0.5 ins Repo 2026-08-15 18:38:03 +02:00
requirements-docs.txt Version 0.5 ins Repo 2026-08-15 18:38:03 +02:00
requirements.txt Version 0.5 ins Repo 2026-08-15 18:38:03 +02:00
snlo-test.sql Version 0.5.10, Keba-Integration beendet 2026-08-16 18:03:39 +02:00
zensical.toml Version 0.5.10, Keba-Integration beendet 2026-08-16 18:03:39 +02:00

SNLO Spittank.net Ladeorganisation 0.5.12

SNLO – Laden organisiert.

Laden organisiert.

SNLO Spittank.net Ladeorganisation ist eine lokale Webapp/PWA für nutzerbezogene Ladeübersichten und Kostenaufstellungen aus beliebig vielen EVCC-Instanzen. EVCC ist verpflichtend und bleibt zuständig für Wallboxanbindung, PV- und Batteriemessung, Ladezustand, Fahrzeugerkennung, Lademodi und reale Sitzungskosten.

Optional kann je EVCC-Ladepunkt eine KEBA P30 über deren UDP-Schnittstelle ergänzt werden. Diese Zusatzanbindung verwendet für Freigabe und Sperren dieselbe RFID-Syntax wie die Home-Assistant-KEBA-Integration; nach erfolgreicher Aktion kann die Wallbox zusätzlich eine kurze Begrüßungs- bzw. Abschiedsmeldung anzeigen. Messwerte und abgeschlossene Sitzungen stammen weiterhin ausschließlich aus EVCC.

Dokumentation

Die vollständige Dokumentation wird mit Zensical aus den Markdown-Dateien unter docs/ gebaut:

pip install -r requirements-docs.txt
zensical build --config-file zensical.toml

Die Ausgabe liegt unter site/. Forgejo Actions validiert die Anwendung und Dokumentation, veröffentlicht main unter daniel.snii.de/snlo und baut ausschließlich bei v*-Tags das gemeinsame Multi-Arch-Image für API, Migrationen und Worker. Details: docs/CI-CD.md.

Funktionsumfang

Nutzer

  • Anmeldung mit lokalem Konto oder gruppengesteuertem OpenID Connect
  • Dashboard für Heute, Monat, Jahr und Gesamt
  • Statistik zu Energie, Stromkosten, Aufschlägen, Solaranteil, Batterienutzung und Gesamtkosten
  • laufende sichtbare Sitzung mit Fahrzeug, Tarif, Leistung, Energie und Kosten
  • vollständig anonymisierte Anzeige Belegt, wenn die aktive Sitzung keinem zugewiesenen Fahrzeug des Nutzers gehört
  • Sitzungsübersicht mit Zeitraumfilter
  • Filter Nur selbst bezahlte Fahrzeuge
  • Druckansicht der eigenen Kostenübersicht
  • kein CSV-Export für Nutzer
  • EVCC-Lademodus und zugewiesenes EVCC-Fahrzeug steuern; im Dashboard aktiven Lademodus direkt umschalten
  • optional Wallbox mit hinterlegter KEBA-RFID freigeben und sperren; Sperrstatus live aus KEBA report 2/Authreq

Administration

  • beliebig viele EVCC-Instanzen anlegen, bearbeiten, testen, synchronisieren und löschen
  • Nutzerkonten verwalten
  • interne Fahrzeuge mit EVCC-Fahrzeugen aus mehreren Instanzen verknüpfen
  • ein Fahrzeug mehreren Nutzern beziehungsweise Familienmitgliedern zuordnen
  • je Fahrzeug und Zeitraum genau einen zahlenden Nutzer festlegen
  • Fahrzeugzuweisungen bearbeiten und löschen, einschließlich rückwirkender Neuzuordnung
  • benannte Tarife anlegen und bearbeiten
  • globale Tarifzeiträume sowie Nutzer/Zeitraum/Tarif-Zuordnungen verwalten
  • historische Tarifänderung nur nach ausdrücklicher Warnung und anschließender Neuberechnung
  • Druckansicht eines beliebigen zahlenden Nutzers öffnen
  • CSV-Export für administrative Auswertung
  • KEBA-Verbindungen und RFID-Keys je Nutzer verwalten
  • Audit-Protokoll

Fahrzeug- und Zahlerlogik

Die technische Erkennung und die Abrechnung sind getrennt:

EVCC-Fahrzeug
    -> internes Fahrzeug
        -> mehrere berechtigte Nutzer/Familienmitglieder
        -> genau ein zahlender Nutzer je Zeitraum

Dasselbe reale Fahrzeug kann mit EVCC-Fahrzeugen aus mehreren Instanzen verknüpft werden. EVCC liefert in Sitzungen je nach Version oder Konfiguration teilweise den internen Namen und teilweise den sichtbaren Fahrzeugtitel. SNLO gleicht deshalb beide Werte ohne Beachtung der Groß-/Kleinschreibung ab.

Für eine Sitzung gilt:

  1. Eine explizite KEBA-App-Freigabe ordnet die Sitzung dem freigebenden Nutzer zu.
  2. Andernfalls wird der zum Sitzungsbeginn gültige zahlende Nutzer des internen Fahrzeugs verwendet.
  3. Fehlt eine eindeutige Zuordnung, bleibt die Sitzung administrativ prüfbar und zunächst unzugeordnet.
  4. Eine administrative Korrektur bleibt erhalten und wird nicht durch automatische Abgleiche überschrieben.

Normale Nutzer können entweder alle Sitzungen ihrer zugeordneten Fahrzeuge oder nur die von ihnen selbst bezahlten Sitzungen anzeigen. Administratorübersichten nennen ausschließlich den zahlenden Nutzer.

Tarifmodell

Tarife sind eigenständige, benannte Objekte, zum Beispiel:

Privat
Privat Plus
Gast
Dienstwagen

Ein Tarif enthält keine direkte Nutzer- oder Zeitraumzuordnung. Stattdessen gibt es:

  • globale Tarifzuordnungen für einen Zeitraum,
  • nutzerbezogene Tarifzuordnungen für einen Zeitraum.

Beispiel:

Alfred bis einschließlich 31.03.2026 -> Privat
Alfred ab 01.04.2026                 -> Privat Plus

Eine nutzerbezogene Zuordnung hat Vorrang vor dem globalen Standard. Zeiträume dürfen innerhalb desselben Geltungsbereichs nicht überlappen.

Aufschlagskomponenten

Komponente Berechnungsbasis
Allgemein gesamte geladene Energie
Solar EVCC-Solaranteil der Sitzung
Netzbezug Restenergie nach Abzug des EVCC-Solaranteils
Batterie während des tatsächlichen Ladens gemessene positive Batterieentladung, höchstens die geladene Sitzungsenergie

Es gibt keinen Monatsbeitrag.

Jede Komponente kann dargestellt werden als:

  • ITEMIZED: unter Aufschläge separat ausgewiesen
  • INCLUDED: in die sichtbaren Stromkosten eingerechnet

Nutzer erhalten niemals den unveränderten EVCC-Rohpreis als eigene Kennzahl. Stromkosten bestehen aus den EVCC-Kosten zuzüglich aller als INCLUDED konfigurierten Komponenten. Intern bleiben EVCC-Basiswert und sämtliche Komponenten getrennt gespeichert.

Historische Änderungen

Abgeschlossene Sitzungen speichern Tarif-ID, Tarifname, Revision, Sätze, Darstellungsarten und berechnete Beträge. Wird ein bereits verwendeter Tarif bearbeitet, warnt die Administration ausdrücklich. Erst nach Bestätigung werden alle Sitzungen dieses Tarifs aus ihren unveränderten Messdaten neu berechnet und der Vorgang auditiert.

Batterieregel

Während ein EVCC-Ladepunkt tatsächlich lädt und die Ladeleistung oberhalb der konfigurierten Schwelle liegt, integriert der Collector die positive EVCC-Batterieleistung:

gemessene Entladung = Summe(max(0, Batterieleistung) × Zeit)
abrechenbare Batterieenergie = min(gemessene Entladung, Sitzungsenergie)
Batterieaufschlag = abrechenbare Batterieenergie × Batteriesatz

Beispiele:

10 kWh geladen, 5 kWh Batterieentladung  -> 5 kWh Batteriebasis
10 kWh geladen, 15 kWh Batterieentladung -> 10 kWh Batteriebasis

Laden mehrere Fahrzeuge derselben EVCC-Instanz gleichzeitig, wird die gemessene Entladung proportional zu deren Ladeleistungen verteilt. Messlücken werden nicht erfunden oder rückwirkend extrapoliert. In Listen wird die konkret gemessene Batterienutzung gesamt angezeigt; der Aufschlag bleibt intern auf die Sitzungsenergie begrenzt.

KEBA-RFID-Freigabe

Der optionale Adapter unterstützt aktuell P30_UDP:

  • Verbindung je EVCC-Ladepunkt mit Host, UDP-Port, Timeout und optionalem geheimen Zusatzfeld
  • RFID-UID je Nutzer und KEBA-Verbindung; die 8-stellige UID aus der KEBA-Kartenliste wird unverändert verwendet
  • verschlüsselte Speicherung; der Klartext wird weder an Browser noch in Listen zurückgegeben
  • App-Freigabe mit start <tag> 01010400000000000000; der RFID-Class-Wert wird intern gesetzt
  • Beenden mit stop <tag>
  • best-effort Displaymeldung Hallo <Benutzername> nach erfolgreicher Freigabe und Auf Wiedersehen nach dem Sperren
  • Sperren zuerst über den dokumentierten stop <echte RFID>-Weg mit anschließender Authreq-Verifikation; nur bei einer noch nicht ladenden, weiterhin entsperrten Station optionaler Fallback über eine absichtlich unbekannte Karte (KEBA_LOCK_FALLBACK_RFID, Standard 44556677)
  • Zuordnung einer so gestarteten Sitzung zum freigebenden Nutzer

Die P30-UDP-Schnittstelle selbst verwendet keinen API-Key. Das optionale geheime Feld wird für diesen Treiber gespeichert, aber nicht gesendet; es hält die Konfiguration für weitere Connector-Typen offen. Die RFID muss von der Wallbox akzeptiert werden. SNLO ersetzt keine RFID-Whitelist-Verwaltung der Wallbox.

KEBA P30 verwendet für die UDP-Schnittstelle den Controller-Port 7090/UDP. SNLO startet deshalb den schlanken Dienst keba-gateway mit einem dauerhaft offenen UDP-Listener auf 7090. Jede Anfrage und jede eingehende KEBA-Antwort wird im Gateway protokolliert; RFID-Werte werden dabei nicht geloggt. network_mode: host vermeidet bei einem normalen rootful Docker Engine unter Linux die Docker-Bridge-NAT-Stufe. Port 7090 muss auf dem Docker-Host frei sein. Läuft dort bereits ein anderer KEBA-UDP-Controller (z. B. eine andere KEBA-Integration), können beide nicht gleichzeitig denselben Port verwenden.

Wichtig: Host-Networking kann nur die Docker-eigene NAT-Stufe beseitigen. Rootless-Docker-Versionen, zusätzliche Netzwerk-Namespaces oder ein Router/Firewall mit Source-NAT können den auf dem Leitungsweg sichtbaren Quellport weiterhin verändern. SNLO meldet deshalb im Gateway-Log den gebundenen Socket (0.0.0.0:7090) und die vom Kernel gewählte lokale Quell-IP. Siehst du die Wallbox-Antwort im Paketmitschnitt, aber keine Zeile KEBA UDP RX im Gateway-Log, erreicht das Paket den Netzwerk-Namespace des Gateways nicht.

Der interne Gateway-Kanal verwendet standardmäßig http://host.docker.internal:17090 und wird mit KEBA_GATEWAY_TOKEN authentifiziert. Der Token wird bei Neuinstallationen automatisch erzeugt. Bei einem Upgrade von 0.5.5 oder älter einmalig ergänzen:

printf 'KEBA_GATEWAY_TOKEN=%s\n' "$(openssl rand -hex 32)" >> .env

Das Gateway ist bewusst auf report 1, report 2, start ..., stop ... und display ... beschränkt. Der TCP-Port 17090 sollte zusätzlich durch die Host-Firewall nicht aus fremden Netzen erreichbar sein. Für die Fehlersuche zeigt docker compose logs -f keba-gateway HTTP-Anfragen, den UDP-TX-Socket und jede tatsächlich auf UDP 7090 empfangene Antwort.

Architektur

Browser / PWA ---- OIDC Identity Provider
      |
      v
FastAPI API -------- PostgreSQL
      |                  ^
      |                  |
      +---- EVCC-Steuerung
      +---- optionale KEBA-RFID-Befehle
                         |
Collector ---------------+
  |        |        |
EVCC A   EVCC B   EVCC n

Compose-Dienste:

  • db: PostgreSQL
  • migrate: einmalige Alembic-Migration
  • api: FastAPI und PWA
  • worker: dynamischer EVCC-Collector und Sitzungsimport
  • keba-gateway: Host-Netz-UDP-Gateway für KEBA 7090

Schnellstart

python3 scripts/init-env.py
docker compose up -d --build

Standardadresse:

http://<docker-host>:8080

Danach:

  1. Administratorpasswort ändern.
  2. EVCC-Instanzen anlegen und synchronisieren.
  3. Interne Fahrzeuge anlegen und EVCC-Fahrzeuge verknüpfen.
  4. Familienmitglieder und den zahlenden Nutzer mit Gültigkeitszeiträumen zuweisen.
  5. Benannte Tarife anlegen.
  6. Globalen Standard und nutzerbezogene Tarifzeiträume zuordnen.
  7. Optional KEBA-Verbindungen und Nutzer-RFID-Keys hinterlegen.

Ausführlicher: docs/QUICKSTART.md

Konfiguration und Veröffentlichung: docs/CONFIGURATION.md und docs/CI-CD.md

TrueNAS / Reverse Proxy

Für TrueNAS SCALE liegt eine fertige Produktionsvorlage unter deploy/truenas/compose.yaml. Die interne Docker-IP des API-Containers darf bei einem Recreate wechseln und wird deshalb nicht gepinnt. Der Reverse Proxy zeigt stattdessen auf die stabile LAN-IP des TrueNAS-Hosts und den veröffentlichten Port:

http://<TRUENAS-IP>:8080

Die dazugehörige snlo.env-Vorlage liegt unter deploy/truenas/snlo.env.example.

OpenID Connect

OIDC kann zusätzlich zum lokalen Login oder als alleiniger Anmeldeweg aktiviert werden. SNLO verwendet den Authorization-Code-Flow mit PKCE, prüft ID-Token-Signatur, Issuer, Audience, Ablauf, Nonce und Authorized Party und übernimmt die Rolle aus konfigurierten Nutzer- beziehungsweise Administratorgruppen.

Minimalkonfiguration hinter HTTPS:

APP_ORIGIN=https://snlo.de
COOKIE_SECURE=true
OIDC_ENABLED=true
OIDC_ISSUER_URL=https://sso.example.org/realms/home
OIDC_CLIENT_ID=snlo
OIDC_CLIENT_SECRET=...
OIDC_GROUPS_CLAIM=groups
OIDC_USER_GROUPS=snlo-users
OIDC_ADMIN_GROUPS=snlo-admins
OIDC_AUTO_REDIRECT=true
OIDC_LOCAL_LOGIN_ENABLED=true

Die beim Identity Provider einzutragende Callback-URL lautet exakt:

https://snlo.de/api/auth/oidc/callback

Bei aktivem Auto-Redirect bleibt der lokale Login sofern nicht deaktiviert unter /?local=1 erreichbar. OIDC-Konten können automatisch angelegt oder über eine verifizierte identische E-Mail-Adresse mit vorhandenen lokalen Konten verknüpft werden. Rolle, Gruppen, E-Mail und Anzeigename werden bei jeder OIDC-Anmeldung neu synchronisiert.

Normale Browser-Sitzungen verwenden SESSION_TTL_HOURS (Standard 24 Stunden). Eine Anmeldung aus der installierten PWA verwendet eine getrennte langlebige Anwendungssitzung (PWA_SESSION_TTL_DAYS, Standard 3650 Tage), die beim PWA-Start erneuert wird und damit auf persönlichen Geräten praktisch bis zum aktiven Abmelden bestehen bleibt. Bei Browser und installierter PWA derselben Origin wird der HTTP-Cookie-Speicher je nach Plattform geteilt; nach einer PWA-Anmeldung kann die langlebige Sitzung deshalb auch im normalen Browser derselben Geräte-/Browserumgebung gelten.

Vollständige Anleitung: docs/OIDC.md

Update von 0.1.1 oder 0.3.0

Vor dem Update Datenbank und .env sichern. Bei einer bestehenden Compose-Installation muss zusätzlich der bisherige PostgreSQL-Volume-Name in .env übernommen werden:

docker volume ls --format '{{.Name}}' | grep 'postgres-data'
POSTGRES_VOLUME_NAME=<bestehender-volume-name>

Damit verwendet der neue Compose-Projektname weiterhin das vorhandene Datenvolume. Bestehende Werte für POSTGRES_DB, POSTGRES_USER, OIDC-Client und OIDC-Gruppen bleiben unverändert gültig. Bereits verschlüsselte EVCC-/KEBA-Geheimnisse können weiterhin gelesen werden; bestehende Browser-Sitzungen müssen nach der Umstellung neu angemeldet werden. Anschließend genügt:

docker compose up -d --build

Die Migration:

  • ergänzt Familienmitgliedschaften und den Zahlerstatus,
  • ergänzt OIDC-Identitäten, gruppengesteuerte Rollen und einmalige Login-States,
  • übernimmt bestehende Fahrzeugzuweisungen als zahlend,
  • erzeugt einen globalen Tarif Standard,
  • überführt unterschiedliche historische Tarif-Snapshots in benannte, inaktive Tarife,
  • verknüpft vorhandene Sitzungen mit dem jeweils übernommenen Tarif,
  • übernimmt aktuell wirksame alte Nutzerüberschreibungen als benannte Nutzertarife,
  • entfernt vorhandene Reservierungszeilen.

Alte Tarifregelzeilen bleiben aus Gründen der Nachvollziehbarkeit in der Datenbank, werden aber von der neuen Tarifauflösung nicht mehr verwendet.

EVCC-Zugriff

Als Instanz-URL wird nur die Basis-URL eingetragen:

http://192.168.10.20:7070

Kein /api anhängen. Der EVCC-API-Key ist für die aktuell verwendeten Status-, Sitzungs- und Ladepunktaktionen meist nicht erforderlich, kann aber je Instanz hinterlegt werden. Gespeicherte EVCC- und KEBA-Geheimnisse sowie RFID-Werte werden mit CREDENTIAL_ENCRYPTION_KEY verschlüsselt.

Backup

Zu sichern sind:

  • PostgreSQL-Datenbank beziehungsweise das über POSTGRES_VOLUME_NAME benannte Volume
  • .env, insbesondere CREDENTIAL_ENCRYPTION_KEY
  • der verwendete Quellstand oder das Container-Image
docker compose exec -T db sh -c 'pg_dump -U "$POSTGRES_USER" "$POSTGRES_DB"' > snlo.sql

Entwicklung und Prüfung

python -m venv .venv
. .venv/bin/activate
pip install -r requirements.txt -r requirements-dev.txt
./scripts/validate-release.sh

API-Dokumentation: /api/docs

Grenzen

  • SNLO steuert keine zentrale KEBA-RFID-Whitelist.
  • Direkte Bedienung an Wallbox oder EVCC kann außerhalb der App-Berechtigungen erfolgen; fremde aktive Sitzungen werden dann lediglich datensparsam als Belegt angezeigt.
  • Batterieaufschläge sind nur für live vom Collector beobachtete Intervalle verfügbar.
  • Die Anwendung erzeugt Verbrauchs- und Kostenaufstellungen. Rechtliche und messrechtliche Eignung für einen konkreten Abrechnungsfall muss separat geprüft werden.
  • Der lokale Logout beendet nicht die Sitzung beim OIDC-Provider; ein providerseitiger Single Logout ist nicht implementiert.
  • Vor Produktivbetrieb sind reale OIDC-, EVCC- und KEBA-Tests, Backup/Restore und Reverse-Proxy-Konfiguration erforderlich.

Lizenz

Copyright © 2026 Daniel Spittank

SNLO Spittank.net Ladeorganisation steht unter der GNU Affero General Public License, Version 3 (AGPL-3.0-only). Der vollständige Lizenztext steht in LICENSE. Wer eine veränderte Version über ein Netzwerk bereitstellt, muss die Anforderungen der AGPL, insbesondere Abschnitt 13, beachten.