67fa2de818
INFLUX_URL in docker-compose.prod.yml auf http://influxdb:8086 korrigiert (verursachte 500 bei allen API-Routen, da Host 'influx' nicht aufloesbar). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
149 lines
4.9 KiB
Markdown
149 lines
4.9 KiB
Markdown
# 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`, Measurement `vzlogger`, Feld `arbeit` (Zählerstand in **Wh**)
|
|
- Der Wert ist ein **kumulativer Zählerstand**; der Verbrauch je Intervall
|
|
ergibt sich aus der Differenz aufeinanderfolgender Zählerstände
|
|
(`difference` in 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`:
|
|
|
|
```flux
|
|
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), dann
|
|
`aggregateWindow(sum)` pro Tag/Stunde (zeitzonen-korrekt via `Europe/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:
|
|
|
|
```flux
|
|
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
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
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)
|
|
|
|
```bash
|
|
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)
|
|
|
|
```bash
|
|
./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:
|
|
|
|
```bash
|
|
# .env
|
|
INFLUX_TOKEN=...
|
|
# optional: STROM_PORT=3000
|
|
```
|
|
|
|
Dann:
|
|
|
|
```bash
|
|
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`.
|