doku/meteogramm.md: der Weg der Daten, welche Groessen links vom Jetzt-Strich gemessen und welche vorhergesagt sind, die vier Tabellen samt der Regel, dass nichts geloescht wird, das Tupel-Schema des Endpunkts, das Katalog-Vokabular mit dem Dreizeiler "so kommt eine Spur dazu", und die beiden Entscheidungen beim Zeichnen (ein SVG ueber alle Felder, Wolkenband als Farbverlauf statt als Raster). Dazu zwei Fallen, die sonst niemand wiederfindet: die Archiv-Schnittstelle von Open-Meteo rechnet alle Zeiten mit dem heute gueltigen Zeitzonenversatz um, und die Spaltenlage des Endpunkts steht an zwei Stellen und muss zueinander passen. Richtiggestellt: doku/datenbank.md und README.md nannten Open-Meteo als Quelle von weatherHours/weatherDays. Das war nie so - es war OpenWeatherMap, und seit dem 27.10.2024 gar nichts mehr. Beide Zeilen warfen ausserdem Messung und Vorhersage in einen Topf; sie sind jetzt getrennt, mit dem richtigen Schreiber je Tabelle. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
13 KiB
Das Meteogramm
Die Karte „Wetter“ auf der Solar-Seite: drei Felder mit gemeinsamer Zeitachse — Temperatur mit Wettersymbolen, Niederschlag mit Bewölkung nach Höhe, Wind mit Böen. Wie die Übersicht ist sie ein Katalog plus ein Renderer, und genau diese Trennung macht sie erweiterbar.
Bis September 2026 stand hier ein Widget von meteoblue in einem <iframe>.
Es passte farblich nicht zum Rest, ließ sich nicht ändern, wurde auf schmalen
Schirmen als Ganzes heruntergerechnet — und konnte das eine nicht, was diese
Anlage auszeichnet: zeigen, was die eigene Wetterstation gemessen hat.
Links vom Jetzt-Strich steht deshalb die Messung, rechts die Vorhersage.
| Datei | Aufgabe |
|---|---|
SolarManager/gatherForecastData.py |
holt die Vorhersage von Open-Meteo, alle 20 Minuten |
SolarManager/wetterarchiv_nachtragen.py |
füllt das Archiv rückwirkend (einmalig) |
solarLog_weather.sql |
die vier Tabellen samt Begründung |
ajax/meteogramm.php |
verschmilzt Vorhersage und Messung zu einer Reihe |
js/solar/meteogrammKatalog.js |
was gezeigt wird: Felder, Spuren, Bänder, Symbole |
js/solar/meteogramm.js |
wie gezeichnet wird. Kennt keine Spur beim Namen |
js/solar/solarMQTT.js |
setzt es auf und schaltet zwischen eigenem und meteoblue um |
1. Der Weg der Daten
flowchart LR
OM["Open-Meteo<br/><small>Vorhersage + ERA5-Archiv</small>"] --> G["gatherForecastData.py<br/><small>alle 20 Min.</small>"]
G --> WH[("weatherHours<br/>weatherDays")]
G --> FL[("weatherForecastLog<br/><small>erste Aussage je Stunde</small>")]
ST["Wetterstation"] --> WS[("weatherStation<br/><small>alle 5 Min.</small>")]
ST -->|MQTT| B
WH --> A["ajax/meteogramm.php"]
WS --> A
A --> B["meteogramm.js<br/><small>+ meteogrammKatalog.js</small>"]
Zwei Quellen, ein Bild. Der Endpunkt legt beide übereinander — aber nicht vollständig, und das ist der Kern:
| Größe | links vom Jetzt-Strich | rechts davon |
|---|---|---|
| Temperatur, Wind, Böe, Richtung, Feuchte, Druck | gemessen (weatherStation) |
Vorhersage |
| Niederschlag, Bewölkung, Strahlung, Wettercode | Vorhersage, nachgerechnet | Vorhersage |
Die Station hat weder Regenmesser noch Pyranometer noch Wolkenkamera. Für diese vier Größen bleibt es deshalb auch in der Vergangenheit bei Open-Meteo — dort allerdings bei der Analyse, nicht mehr bei der Vorhersage. Genau dafür holt der Sammler zwei Tage Vergangenheit mit.
2. Die Tabellen
| Tabelle | Inhalt | Wächst um |
|---|---|---|
weatherHours |
eine Zeile je Stunde, −2 bis +7 Tage, dauerhaft aufbewahrt | 8.760 Zeilen/Jahr, ~1,5 MB |
weatherDays |
eine Zeile je Tag: Sonnenzeiten, Min/Max, Tagessummen | 365 Zeilen/Jahr |
weatherTilted |
Einstrahlung in Modulebene, je Stunde und Fläche | nur wenn Winkel konfiguriert |
weatherForecastLog |
die erste Vorhersage je Stunde, unveränderlich | 8.760 Zeilen/Jahr |
Wie eine Stunde erwachsen wird. Schlüssel ist der Zeitpunkt, geschrieben
wird mit REPLACE INTO: dieselbe Stunde kommt alle zwanzig Minuten wieder und
wird dabei genauer — erst Vorhersage für übermorgen, zuletzt Analyse des
vergangenen Tages. Woran man das sieht, steht in zwei Spalten: abgerufen
sagt, wann zuletzt geschrieben wurde, ist_vorhersage, ob die Stunde dabei
noch in der Zukunft lag.
Warum nichts gelöscht wird. Die Tabellen sind zugleich ein Archiv. Aus der
Einstrahlung und EnergyFlow_hourly.pv_kwh (gemessener Ertrag je Stunde seit
dem 02.04.2022) soll eine eigene Ertragsprognose entstehen — wer alte Zeilen
wegräumt, wirft die Hälfte jedes Datenpaares weg. Deshalb stehen sie auch
nicht in THIN_TABLES des Rollup-Jobs.
Warum weatherForecastLog extra. weatherHours hält immer den neuesten
Stand — für die Anzeige richtig, für die Frage „wie gut war die Vorhersage?“
unbrauchbar, weil die Vorhersage dort längst von der Analyse überschrieben
wurde. Hier landet per INSERT IGNORE, was zuerst über eine Stunde gesagt
wurde. Die Differenz zu datetime ist der Vorlauf.
Eine Falle beim Nachtragen: Die Archiv-Schnittstelle rechnet alle Zeiten mit dem heute gültigen Zeitzonenversatz um — die Antwort für einen Dezembertag meldet selbst
utc_offset_seconds: 7200. Sonnenaufgang am 21.12. käme so als 09:04 statt 08:04. Bei Stundenwerten fällt das nicht ins Gewicht (sie hängen an ihrem Zeitstempel), bei einer Uhrzeit schon.wetterarchiv_nachtragen.pyschreibt Sonnenzeiten deshalb ausdrücklich nicht; gebraucht werden sie nur für die sichtbaren Tage, und die schreibt der Sammler richtig.
Was vorher da war
weatherHours und weatherDays gab es schon — in der Form der
OpenWeatherMap-One-Call-2.5-API (weatherStr, icon, pop, uvi,
moonrise, Wind in m/s, Bewölkung als ein Prozentwert). Ihr Schreiber liegt
außerhalb beider Repos unter /volume1/web/gatherWeather.php, wurde von
keiner Aufgabe mehr gestartet, und die API ist Ende 2024 abgeschaltet worden:
letzte Zeile 27.10.2024. Gelesen hat sie zuletzt niemand.
Von 17 bzw. 29 Spalten wären keine fünf übriggeblieben, also wurden sie neu
aufgebaut statt erweitert. Die alten liegen als weatherHours_alt und
weatherDays_alt daneben und dürfen nach ein paar Wochen Ruhe weg.
3. Der Endpunkt
ajax/meteogramm.php?tage=5&rueck=24 tage 2…7, rueck 0…72 Stunden
Antwort wie im Haus üblich: Unix-Sekunden, Messreihen als Tupel statt Objekte — bei 200 Stunden mit 16 Feldern spart das ein Vielfaches.
{ "von":…, "bis":…, "jetzt":…, "stand":…,
"stunden": [[t, gemessen, temp, regen, schnee, wind, boe, richtung,
strahlung, wtief, wmittel, whoch, code, feuchte, druck, regenWkt], …],
"tage": [[t0, tmin, tmax, code, sonnenauf, sonnenunter], …] }
Die Spaltenlage steht zweimal: im Kopfkommentar des Endpunkts und als
SPALTEN im Katalog. Beide müssen zueinander passen — neue Größen hängen
deshalb hinten an, dann bleiben die Stellen bestehender Spuren gültig.
Drei Feinheiten, die man beim Lesen der SQL sonst übersieht:
MAX(gustSpeed), aberAVG(windSpeed). Open-Meteos Böe ist die Spitze der Stunde. Mit einem Mittelwert auf der Messseite spränge die Böenlinie genau am Jetzt-Strich.- Die Windrichtung wird über die Vektorsumme gemittelt
(
ATAN2(AVG(SIN(…)), AVG(COS(…)))). Der arithmetische Mittelwert aus 350° und 10° wäre 180° — die Gegenrichtung. - Sonnenzeiten kommen aus
weatherDays, nicht ausdaylight.daylightreicht nur bis morgen, das Meteogramm bis zu sieben Tage.
4. Katalog und Renderer
Der Renderer kennt kein Feld, keine Spur und keine Farbe beim Namen. Er liest nur, was der Katalog deklariert.
So kommt eine Spur dazu
- Spalte in
ajax/meteogramm.phphinten anhängen - Schlüssel in
SPALTENhinten anhängen - Eintrag in
spurendes passenden Feldes
Sonst nichts:
{
id: "wind", name: "Wind", spalte: "wind",
achse: "links", art: "linie", farbe: FARBE.wind, breit: 2.2,
legende: true,
format: w => komma(w, 1) + " km/h",
},
Ein Feld ist ein waagerechter Streifen mit eigener senkrechter Achse; alle
teilen sich die Zeitachse. Es hat links/rechts (Achsen), nacht
(Schattierung), symbole, pfeile, baender und spuren. Eine Spur hat
art (linie · flaeche · balken), farbe, format() und die Filter
nurGemessen / nurVorhersage.
Datenfarben stehen im Katalog, Strukturfarben im Stylesheet — dieselbe Regel wie beim Energiefluss.
Die drei Felder
| Feld | Links | Rechts | Dazu |
|---|---|---|---|
| Temperatur | °C | 0–1000 W/m² (Strahlung) | Wettersymbole, Tag/Nacht, Tagesmin/-max |
| Niederschlag und Bewölkung | mm/h | 0–14 km (Wolkenband) | Tag/Nacht |
| Wind | km/h | — | Richtungspfeile |
Warum die Strahlung kein eigenes Feld hat: gleicher Tagesrhythmus, gleiche Nacht-Schattierung wie die Temperatur — und ein viertes Feld machte das Modul am Handy höher als das externe Widget, also genau das Gegenteil des Ziels. Über den Katalog ist es ein Zweizeiler, sie später auszulagern.
Zwei Entscheidungen beim Zeichnen
Ein einziges SVG über alle drei Felder. Dadurch gibt es genau eine
X-Skala, und Nachtstreifen, Tagesgrenzen, Jetzt-Linie und der Zeiger laufen
ohne Kunstgriff durch alle Felder. Jedes Feld ist ein <g transform= "translate(0, oben)"> mit eigener Y-Abbildung. Auch der Tooltip gibt es
deshalb nur einmal — er zeigt alle Spuren aller Felder zu einer Stunde.
Das Wolkenband ist ein Farbverlauf, kein Raster.
je Stunde ein Rechteck je Schicht ein Rechteck mit Verlauf
▐▌▐▌▌▐▐▌▌▐▌▐▐▌▌▐▌▐▌▐▐▌ ▓▓▒▒░░▒▒▓▓▓▒░░░▒▒▓▓▓▒
zweihundert harte Kanten dieselben Zahlen, weich verbunden
Der erste Versuch zeichnete je Stunde und Schicht ein Rechteck und sah aus wie Fernsehrauschen. Der Verlauf setzt einen Haltepunkt je Stunde und blendet dazwischen über — Bewölkung springt ohnehin nicht zur vollen Stunde um.
Und eine Vereinfachung, die man kennen sollte: Open-Meteo liefert kein stufenloses Höhenprofil, sondern drei Schichten (tief 0–2 km, mittel 2–7 km, hoch 7–14 km). Mehr Auflösung hat die Quelle nicht.
Am Handy
Unter 520 px schalten die Feldhöhen auf schmal (120/100/92 statt
150/130/115) — zusammen rund 345 px statt der 500 px des eingebetteten
Widgets. Außerdem startet das Modul dort mit zwei Tagen statt fünf: auf
358 nutzbaren Pixeln blieben für fünf Tage drei Pixel je Stunde.
Wettersymbole und Windpfeile dünnen sich selbst aus: beide verdoppeln ihre
Schrittweite, bis der Abstand mindestAbstand erreicht. Das ist die eine
Regel, die am Handy alles rettet.
touch-action: pan-y auf dem SVG ist Pflicht — sonst fängt der Tooltip die
Wischgeste ab und die Seite lässt sich nicht mehr scrollen.
5. Umschalter und Einbau
Die Karte trägt zwei Flächen: id="meteogramm" (zwei m, das eigene Modul) und
id="meteogram" (ein m, das Ziel des meteoblue-Rahmens — so bleibt
mountMeteogram() in common.js unverändert). Umgeschaltet wird über
data-mtg-quelle, nicht über data-enf-ansicht: zeigeAnsicht() in
solarMQTT.js greift mit document.querySelectorAll seitenweit zu und würde
sonst die Realtime-Karte mitschalten.
mountMeteogram() läuft jetzt erst beim ersten Umschalten auf meteoblue.
Wer es nie ansieht, lädt das fremde Dokument auch nicht mehr — nebenbei der
größte Ladezeitgewinn dieser Karte.
Die Karte heißt „Wetter“ und nicht mehr „Forecast“: direkt darunter steht
bereits eine zweite Karte dieses Namens, die aus simPower die PV-Ernte
vorhersagt.
6. Fehlerbilder
| Beobachtung | Wahrscheinliche Ursache |
|---|---|
| „Noch keine Vorhersage vorhanden“ | gatherForecastData.py läuft nicht — ps aux | grep gatherForecast, dann forecastOutput.log |
| Fuß färbt sich gelb, „Stand“ liegt Stunden zurück | Sammler läuft, kommt aber nicht an Open-Meteo heran |
| Links vom Jetzt-Strich fehlt die gemessene Kurve | weatherStation bekommt nichts mehr — das schreibt die Station selbst über /volume1/web/weatherStation.php, kein Skript in den Repos |
| Bewölkung fehlt in der Vergangenheit | past_days in der config.ini steht auf 0 |
| Wolkenband bleibt leer | Die Zeilen stammen aus dem Nachtragen und haben keine Schichtwerte — Archiv und laufende Vorhersage liefern dieselben Felder, prüfen mit SELECT wolken_tief … WHERE datetime = … |
| Symbole überlappen | mindestAbstand im Katalog zu klein für diese Breite |
7. Wo fange ich an, wenn ich …
| Vorhaben | Ort |
|---|---|
| … eine weitere Spur zeigen | drei Zeilen, siehe Abschnitt 4 |
| … die Farben ändern | FARBE in meteogrammKatalog.js |
| … ein Feld höher machen | HOEHEN im Katalog, je Breite getrennt |
| … einen weiteren Zeitraum anbieten | zeitraeume im Katalog — der Endpunkt klemmt auf 2…7 Tage |
| … ein anderes Wettersymbol | WETTER[code] im Katalog; Codepunkte stehen in css/bootstrap-icons.min.css |
| … öfter abrufen | intervall_minuten in [vorhersage] der config.ini — öfter als stündlich rechnet Open-Meteo nicht |
| … die Einstrahlung in Modulebene | flaechen = dach:30:10, … in [vorhersage]; füllt weatherTilted, rückwirkend über wetterarchiv_nachtragen.py |
| … das Meteogramm auch auf der Wetterseite | restricted/weather.php bekommt dieselben zwei Flächen, index.php die zwei Skripte, weatherMQTT.js den Aufruf |
| … meteoblue endgültig loswerden | Umschalter aus solar.php, "meteogram" => false in index.php, js/meteogram.js und mountMeteogram() in common.js |
