Weather Station / ECOWITT / DNT
1# Ecowitt Weather Dashboard
2
3Dashboard fuer Ecowitt- und DNT-WLAN-Wetterstationen mit Live-Daten, lokaler CSV-Historie, Statistiken und Forecast-Vergleich. Die Anwendung laeuft als Next.js-App mit Node.js-Routen, liest Wetterstationsdateien aus `DNT/`, materialisiert sie als Parquet und fragt Live- sowie Forecast-Daten serverseitig ab.
4
5## Funktionsumfang
6
7- **Echtzeit**: aktuelle Ecowitt-API-v3-Daten ueber einen serverseitigen Proxy und Cache.
8- **Grafik**: Gauges fuer Temperatur, Luftfeuchte, Wind, Luftdruck, Regen, Solarstrahlung, UV und CH1-CH8.
9- **Prognose**: Forecasts aus Geosphere Austria, Open-Meteo DWD ICON, optional Meteoblue und OpenWeatherMap.
10- **Analyse**: Forecast-Genauigkeit gegen lokal gemessene Wetterdaten mit MAE/RMSE.
11- **Gespeicherte Daten**: interaktive Chart.js-Zeitreihen fuer Main- und Allsensors-CSV-Daten.
12- **Statistik**: Jahres-, Monats-, Bereichs- und Kanalstatistiken aus DuckDB/Parquet.
13- **Internationalisierung**: Deutsch und Englisch via `i18next`.
14
15## Screenshots
16
17| Grafik | Grafik 2 |
18| --- | --- |
19|  |  |
20
21| Echtzeit | Archiv |
22| --- | --- |
23|  |  |
24
25## Technik
26
27- Next.js `16.2.4` mit App Router
28- React `19.2`
29- TypeScript `6`
30- Tailwind CSS `4`
31- DuckDB via `@duckdb/node-api`
32- Chart.js mit `chartjs-plugin-zoom`
33- `suncalc` fuer Sonnen-/Mondzeiten
34
35Alle API-Routen, die lokale Dateien oder DuckDB nutzen, laufen im Node.js-Runtime. Das Projekt ist daher fuer einen Server/VPS oder Docker mit persistenten Volumes gedacht, nicht fuer eine serverlose Umgebung ohne persistentes Dateisystem.
36
37## Schnellstart
38
39```bash
40npm install
41
42cp env.example .env
43cp eco.example.ts eco.ts
44
45mkdir -p DNT data
46cp 202501A.CSV 202501Allsensors_A.CSV DNT/
47
48npm run dev
49```
50
51Danach die App unter `http://localhost:3000` oeffnen.
52
53Wichtig: `npm run dev` startet zuerst `npm run prewarm`. Wenn `DNT/` fehlt, kann das Vorwaermen der CSV-Daten nicht laufen. Fuer einen ersten Test koennen die beiden Beispiel-CSV-Dateien aus dem Repository nach `DNT/` kopiert werden.
54
55## Konfiguration
56
57### Ecowitt-Zugangsdaten
58
59`eco.example.ts` nach `eco.ts` kopieren und ausfuellen:
60
61```ts
62applicationKey: "YOUR_APPLICATION_KEY_HERE",
63apiKey: "YOUR_API_KEY_HERE",
64mac: "AA:BB:CC:DD:EE:FF",
65server: "api.ecowitt.net"
66```
67
68`eco.ts` wird nur serverseitig importiert und ist in `.gitignore` ausgeschlossen.
69
70### Umgebungsvariablen
71
72`env.example` nach `.env` kopieren. Relevante Variablen:
73
74| Variable | Bedeutung |
75| --- | --- |
76| `RT_REFRESH_MS` | Intervall des serverseitigen Live-Pollers in Millisekunden, Default `300000`. |
77| `NEXT_PUBLIC_RT_REFRESH_MS` | Clientseitiges Refresh-Intervall im Realtime-Tab. |
78| `FORECAST_STATION_ID` | Geosphere-Station fuer Forecast-Speicherung und Analyse, Default `11035`. `ALL` verarbeitet alle Stationen. |
79| `OPENWEATHER_API_KEY` | Optional, aktiviert OpenWeatherMap-Forecasts. |
80| `METEOBLUE_API_KEY` | Optional, aktiviert Meteoblue-Forecast und Meteogramm. |
81| `ADMIN_API_TOKEN` | Token fuer administrative API-Routen. |
82| `WEATHER_ADMIN_TOKEN` | Fallback-Name fuer denselben Admin-Token. |
83
84Admin-Routen akzeptieren entweder `Authorization: Bearer <token>` oder `x-admin-token: <token>`.
85
86### Kanalnamen
87
88Die Anzeigenamen fuer CH1-CH8 stehen in `src/config/channels.json`:
89
90```json
91{
92 "ch1": { "name": "Vorratskammer" },
93 "ch2": { "name": "Kueche" }
94}
95```
96
97Nicht konfigurierte Kanaele fallen auf ihre ID zurueck.
98
99## Datenablage
100
101Die Anwendung erwartet lokale CSV-Dateien im Ordner `DNT/`.
102
103Typische Dateinamen:
104
105- Main-Daten: `YYYYMMA.CSV`, zum Beispiel `202501A.CSV`
106- Kanal-Daten: `YYYYMMAllsensors_A.CSV`, zum Beispiel `202501Allsensors_A.CSV`
107
108Beobachtete CSV-Eigenschaften:
109
110- Komma als Trennzeichen
111- `--` als Platzhalter fuer fehlende Werte
112- Zeitspalten wie `Zeit`, `Time`, `DateUTC` oder `DateTimeUTC`
113- Zeitformate wie `YYYY/M/D H:MM`, ISO-nahe Varianten und `DD.MM.YYYY HH:MM`
114- deutsche oder englische Sensornamen mit Einheiten in Klammern
115
116Generierte Laufzeitdaten:
117
118- `data/weather.duckdb`
119- `data/parquet/main/YYYYMM.parquet`
120- `data/parquet/allsensors/YYYYMM.parquet`
121- `data/statistics.json`
122- `DNT/rt-last.json`
123- `temp-minmax-data.json`
124
125Diese Datenpfade sind in `.gitignore` ausgeschlossen.
126
127## Hintergrundjobs
128
129`src/instrumentation.ts` registriert beim Serverstart mehrere Jobs:
130
131- Live-Poller: fragt Ecowitt API v3 ab, schreibt `DNT/rt-last.json` und archiviert Messwerte in monatliche Main-/Allsensors-CSV-Dateien.
132- Statistik-Warmup: berechnet Statistik-Caches beim Start und danach taeglich neu.
133- Forecast-Poller: speichert Forecasts fuer `FORECAST_STATION_ID` taeglich im Zeitfenster 20:00-20:30 und fuehrt beim Start/Catchup nach Bedarf einen Lauf aus.
134- Forecast-Analyse: vergleicht gespeicherte Forecasts mit lokalen Main-Daten und schreibt Ergebnisse in DuckDB.
135
136## Scripts
137
138```bash
139npm run prewarm # CSVs aus DNT/ als Parquet materialisieren
140npm run dev # prewarm + Next.js Dev-Server
141npm run build # Produktionsbuild mit Webpack
142npm run start # prewarm + Next.js Production Server
143```
144
145`prewarm` prueft pro Monat die CSV- und Parquet-Mtime und baut nur fehlende oder veraltete Parquet-Dateien neu.
146
147## API-Ueberblick
148
149### Live und Geraet
150
151- `GET /api/config/channels` - CH1-CH8 Anzeigenamen aus `src/config/channels.json`
152- `GET /api/rt/last` - letzter gecachter Live-Datensatz
153- `GET /api/rt` - direkter Ecowitt-Proxy fuer eine kleine Auswahl
154- `GET /api/rt?all=1` - direkter Ecowitt-Proxy fuer den vollen Payload
155- `GET /api/device/info` - Zeitzone, Latitude und Longitude der Station
156- `GET /api/temp-minmax` - lokale Temperatur-Min/Max-Daten
157
158### Historische Daten
159
160- `GET /api/data/months` - erkannte Monate aus `DNT/`
161- `GET /api/data/extent` - globaler Zeitbereich
162- `GET /api/data/main?month=YYYYMM&resolution=minute|hour|day`
163- `GET /api/data/main?start=YYYY-MM-DDTHH:MM&end=YYYY-MM-DDTHH:MM&resolution=minute|hour|day`
164- `GET /api/data/allsensors?month=YYYYMM&resolution=minute|hour|day`
165- `GET /api/data/allsensors?start=YYYY-MM-DDTHH:MM&end=YYYY-MM-DDTHH:MM&resolution=minute|hour|day`
166
167Beispiel:
168
169```bash
170curl 'http://localhost:3000/api/data/main?month=202501&resolution=day'
171curl 'http://localhost:3000/api/data/allsensors?start=2025-01-01T00:00&end=2025-01-31T23:59&resolution=hour'
172```
173
174### Statistik
175
176- `GET /api/statistics`
177- `GET /api/statistics?year=2025`
178- `GET /api/statistics/daily?year=2025`
179- `GET /api/statistics/range?month=YYYYMM`
180- `GET /api/statistics/range?start=YYYY-MM-DDTHH:MM&end=YYYY-MM-DDTHH:MM`
181- `GET /api/statistics/channels?ch=ch1&month=YYYYMM`
182- `GET /api/statistics/channels?ch=ch1&start=YYYY-MM-DDTHH:MM&end=YYYY-MM-DDTHH:MM`
183
184Admin:
185
186- `POST /api/statistics/update`
187- `POST /api/temp-minmax/update`
188
189### Forecast und Analyse
190
191- `GET /api/forecast?action=stations`
192- `GET /api/forecast?action=forecast&stationId=11035` - Geosphere Austria
193- `GET /api/forecast?action=openmeteo&stationId=11035` - Open-Meteo DWD ICON
194- `GET /api/forecast?action=openweather&stationId=11035` - OpenWeatherMap, braucht API-Key
195- `GET /api/forecast?action=meteoblue&stationId=11035` - Meteoblue, braucht API-Key
196- `GET /api/forecast?action=meteogram&stationId=11035` - Meteoblue-WebP-Meteogramm
197- `GET /api/forecast/analysis?stationId=11035&days=30`
198- `GET /api/forecast/compare?stationId=11035&days=30` - Legacy-On-Demand-Vergleich
199- `GET /api/config/forecast-station`
200
201Admin:
202
203- `POST /api/forecast/store`
204- `POST /api/forecast/analyze`
205- `POST /api/forecast/backfill`
206- `GET /api/dforecast`
207- `GET /api/debug/db`
208
209Admin-Beispiel:
210
211```bash
212curl -X POST 'http://localhost:3000/api/forecast/store' \
213 -H 'Authorization: Bearer <ADMIN_API_TOKEN>' \
214 -H 'Content-Type: application/json' \
215 -d '{"stationId":"11035"}'
216```
217
218## Projektstruktur
219
220```text
221src/app/ Next.js Seiten, Layout und API-Routen
222src/components/ React-Komponenten fuer Tabs, Charts, Gauges und Statistik
223src/contexts/ Realtime-Context fuer Clientdaten
224src/lib/ CSV-, Zeit-, Statistik-, Astro-, Realtime- und DB-Logik
225src/lib/db/ DuckDB-Verbindung und CSV->Parquet-Ingestion
226src/config/channels.json CH1-CH8 Anzeigenamen
227src/locales/ i18next-Uebersetzungen
228src/scripts/prewarm.ts Parquet-Vorwaermung
229```
230
231## Docker
232
233```bash
234cp env.example .env
235cp eco.example.ts eco.ts
236mkdir -p DNT data
237
238docker compose up --build -d
239```
240
241`docker-compose.yml` bindet folgende Pfade ein:
242
243- `./DNT:/app/DNT`
244- `./data:/app/data`
245- `./src/config:/app/src/config`
246- `./eco.ts:/app/eco.ts`
247
248Die App lauscht im Compose-Setup auf `127.0.0.1:3010`.
249
250## Fehlersuche
251
252- **`DNT/` fehlt**: Ordner anlegen und CSVs hineinkopieren; `prewarm` liest direkt aus diesem Pfad.
253- **Keine Monate gefunden**: Dateinamen muessen mit `YYYYMM` beginnen und auf `.CSV` enden.
254- **Leere Charts**: CSV-Header, Zeitformat und fehlende Werte (`--`) pruefen.
255- **Forecast-Quelle fehlt**: Open-Meteo und Geosphere funktionieren ohne eigenen Key; Meteoblue und OpenWeatherMap brauchen `.env`-Keys.
256- **Admin-Route liefert 503**: `ADMIN_API_TOKEN` oder `WEATHER_ADMIN_TOKEN` ist nicht gesetzt.
257- **DuckDB/native binding Fehler**: `@duckdb/node-api` muss installiert sein; fuer Builds werden die DuckDB-Pakete in `next.config.ts` serverseitig externalisiert.
258- **Serverless Deployment verliert Daten**: `DNT/`, `data/` und `eco.ts` muessen persistent gemountet werden.
259
260## Sicherheit
261
262`.env*`, `eco.ts`, `DNT/`, `data/`, DuckDB-Dateien und generierte Temperaturdaten werden nicht versioniert. Echte API-Keys und Stationsdaten sollten nicht committet werden.