Ursache: Container lief in UTC, waehrend die Flux-Aggregation Europe/Berlin nutzt. Dadurch lagen die in JS (Lokalzeit) berechneten Monats-/Tagesgrenzen eine Stunde neben den Flux-Balken: - Januar-Balken (Berlin = 31.12. 23:00 UTC) lag vor 'start' (UTC) und wurde weggefiltert. - Ladetag-Zeitstempel (UTC-Mitternacht) passten nicht auf die Verbrauchsbalken (Berliner Mitternacht) -> keine Markierung. Fix: tzdata ins Image + TZ=Europe/Berlin (Dockerfile und compose). Damit stimmt die JS-Lokalzeit mit der Flux-Location ueberein. Verifiziert: im Container ergibt new Date(2026,0,1) den Berliner Monatsanfang. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Stromverbrauch (strom-next)
Next.js-App zur Visualisierung des Stromverbrauchs aus einer InfluxDB. Design/Layout angelehnt an das Projekt Werte-Log (Ansicht „Verlauf").
Datenquelle
- InfluxDB Bucket
strom, Measurementvzlogger, Feldarbeit(Zählerstand in Wh) - Der Wert ist ein kumulativer Zählerstand; der Verbrauch je Intervall
ergibt sich aus der Differenz aufeinanderfolgender Zählerstände
(
differencein Flux). - Die Rohdaten haben 1-Sekunden-Auflösung. Für die langen Zeiträume wäre die Live-Aggregation zu langsam (Jahr ≈ 70 s), daher gibt es einen Downsampling-Task.
Downsampling-Task (InfluxDB)
Ein InfluxDB-Task arbeit_hourly_rollup (stündlich, in der InfluxDB unter
Tasks zu finden) schreibt fortlaufend den letzten Zählerstand je Stunde als
Measurement arbeit_hourly (Feld arbeit) zurück in den Bucket strom:
option task = {name: "arbeit_hourly_rollup", every: 1h, offset: 2m}
from(bucket: "strom")
|> range(start: -3h)
|> filter(fn: (r) => r._measurement == "vzlogger" and r._field == "arbeit")
|> aggregateWindow(every: 1h, fn: last, createEmpty: false)
|> set(key: "_measurement", value: "arbeit_hourly")
|> to(bucket: "strom")
- 24h-Chart liest die Rohdaten (
vzlogger) → immer top-aktuell. - 7d/31d/365d lesen den Rollup (
arbeit_hourly) → sub-sekunden-schnell. - Tages-/Stundenwerte: erst
difference(stündlicher Verbrauch), dannaggregateWindow(sum)pro Tag/Stunde (zeitzonen-korrekt viaEurope/Berlin). Liefert exakt dieselben Werte wie die Rohdaten-Aggregation.
Backfill (einmalig, schon ausgeführt)
Die Historie ab 2022-10-09 wurde einmalig in arbeit_hourly geschrieben.
Falls erneut nötig (z. B. nach Datenlücke), jahresweise:
from(bucket:"strom")
|> range(start: 2025-01-01T00:00:00Z, stop: 2026-01-01T00:00:00Z)
|> filter(fn:(r)=>r._measurement=="vzlogger" and r._field=="arbeit")
|> aggregateWindow(every:1h, fn:last, createEmpty:false)
|> set(key:"_measurement", value:"arbeit_hourly")
|> to(bucket:"strom")
Diagramme (alle als Balken-Diagramme)
| Zeitraum | Balken | Auswahl |
|---|---|---|
| Letzte 24 Stunden | 1 / Stunde | Tag (leer = letzte 24h ab jetzt) |
| Letzte 7 Tage | 1 / Stunde | Tag |
| Letzte 31 Tage | 1 / Tag | Monat |
| Letzte 365 Tage | 1 / Tag | Jahr |
X-Achse: Datum/Zeit · Y-Achse: kWh
E-Auto-Lade-Erkennung (31d)
Im 31d-Diagramm werden Tage rot markiert, an denen das E-Auto geladen wurde:
Die Leistung (Feld leistung, W) lag länger als LADEN_MIN_HOURS (Default 2 h)
ununterbrochen über LADEN_THRESHOLD_KW (Default 11 kW) — dem Anschlusswert des
Autos. Erkennung serverseitig per Flux stateDuration auf den Sekundendaten
(/api/laden?month=YYYY-MM); läuft ~15 s je Monat und wird daher gecacht
(abgeschlossene Monate dauerhaft). Die Markierung wird asynchron nachgeladen,
sodass die Balken sofort erscheinen. Schwelle/Dauer sind per Env einstellbar.
Setup
npm install
cp .env.local.example .env.local
# .env.local mit echtem INFLUX_TOKEN (und ggf. Feldname/Einheit) befüllen
.env.local
INFLUX_URL=http://nuccy:8086
INFLUX_TOKEN=...
INFLUX_ORG=citysensor
INFLUX_BUCKET=strom
INFLUX_MEASUREMENT=vzlogger
INFLUX_FIELD=arbeit
INFLUX_UNIT_FACTOR=0.001 # Zählerstand liegt in Wh vor
Den Feldnamen und die Einheit ggf. einmalig prüfen:
curl -s -XPOST "http://nuccy:8086/api/v2/query?org=citysensor" \
-H "Authorization: Token $INFLUX_TOKEN" \
-H "Accept: application/csv" \
-H "Content-type: application/vnd.flux" \
-d 'import "influxdata/influxdb/schema"
schema.fieldKeys(bucket: "strom", predicate: (r) => r._measurement == "arbeit")'
Starten (lokal)
npm run dev # http://localhost:3000
npm run build # Produktionsbuild
npm start
Docker / Deployment auf nuccy
Die App läuft als Container auf nuccy im externen Docker-Netzwerk
smarthome und erreicht die InfluxDB dort unter dem Host influxdb
(http://influxdb:8086).
Image bauen & in die Registry pushen (vom Entwicklungsrechner)
./deploy.sh # Tag latest
./deploy.sh v1.2.0 # eigener Tag
Baut für linux/amd64 und pusht nach
docker.citysensor.de/strom-next:<tag>.
Auf nuccy starten
docker-compose.prod.yml neben eine .env legen (aus
.env.production.example) und das InfluxDB-Read-Token dort eintragen:
# .env
INFLUX_TOKEN=...
# optional: STROM_PORT=3000
Dann:
docker compose -f docker-compose.prod.yml pull
docker compose -f docker-compose.prod.yml up -d
Die App ist anschließend auf nuccy unter Port 3000 (bzw. STROM_PORT)
erreichbar. Alle InfluxDB-/Lade-Einstellungen sind in docker-compose.prod.yml
gesetzt; nur das Token kommt aus der .env.