Doku: das Meteogramm beschrieben, falsche Quellenangabe richtiggestellt

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>
This commit is contained in:
2026-09-21 14:07:07 +02:00
co-authored by Claude Opus 5
parent 2c48d78880
commit b6ff7c6ad9
5 changed files with 299 additions and 7 deletions
+19 -3
View File
@@ -22,6 +22,7 @@ Zusammenspiel mehrerer Prozesse entsteht. Sie haben eigene, ausführliche
Dokumente unter [`doku/`](doku/README.md):
**[Automatiken](doku/automatiken.md)**, **[Zeitleiste](doku/zeitleiste.md)**,
**[Benachrichtigungen](doku/benachrichtigungen.md)**, **[Batterie](doku/byd.md)**,
**[Meteogramm](doku/meteogramm.md)**,
**[Solar-Übersicht](doku/uebersicht.md)**, **[Datenbanken](doku/datenbank.md)**
und **[Einstellungsseite](doku/einstellungen.md)**.
@@ -191,12 +192,12 @@ genau die Topics, die auf den Kacheln stehen (`tileTopics()` in `rooms.php`).
| Seite (`?action=`) | Vorlage | Seitenskripte | holt per fetch | hört auf MQTT |
|---|---|---|---|---|
| `solar` (Vorgabe) | `solar.php` | `jahresstatistik.js`, `anlageKatalog.js`, `anlage.js`, `speicher.js`, `energieflussKatalog.js`, `energiefluss.js`, `bewaesserungAnzeige.js`, `ladefenster.js`, `bedienfenster.js`, `solarMQTT.js` | `speicher`, `getProdData`, `getConsData`, `getForecastData`, `getSunrise`, `getStats`, `carEG`, `carOG`, `heater`, `watering` | `solarManager/#`, `weatherStation/#`, `wattpilot/#`, `go-eCharger/#`, `Gartenwasser/#` |
| `solar` (Vorgabe) | `solar.php` | `jahresstatistik.js`, `anlageKatalog.js`, `anlage.js`, `speicher.js`, `meteogrammKatalog.js`, `meteogramm.js`, `energieflussKatalog.js`, `energiefluss.js`, `bewaesserungAnzeige.js`, `ladefenster.js`, `bedienfenster.js`, `solarMQTT.js` | `speicher`, `meteogramm`, `getProdData`, `getConsData`, `getForecastData`, `getSunrise`, `getStats`, `carEG`, `carOG`, `heater`, `watering` | `solarManager/#`, `weatherStation/#`, `wattpilot/#`, `go-eCharger/#`, `Gartenwasser/#` |
| `home` | `home.php` | `autoActionFuncs.js`, `zeitleiste.js`, `raumfenster.js`, `homeMQTT.js` | `room.php`, `AutoAction.php` | die Topics der Kacheln, `Raumtemp/#` |
| `heat` | `heat.php` | `heatMQTT.js` | `getHeaterData`, `getWaterData`, `getSunrise` | `solarManager/#`, `weatherStation/#`, Wallbox-Topics |
| `history` | `history.php` | `jahresstatistik.js`, `historyMQTT.js` | `energyHistory` (6 ×), `getStats` | — |
| `skoda` | `skoda.php` | `skodaMQTT.js` | `skoda.php?was=…`, `skodaCmd.php`, `tile.php` | `solarManager/#` |
| `weather` | `weather.php` | `weatherMQTT.js` | nichts — das Meteogramm ist ein fremdes Dokument im `<iframe>` (`js/meteogram.js`) | `weatherStation/#` |
| `weather` | `weather.php` | `weatherMQTT.js` | nichts — hier steht noch das eingebettete Meteogramm von meteoblue (`js/meteogram.js`); das eigene gibt es bisher nur auf der Solar-Seite | `weatherStation/#` |
| `settings` | `settings.php` | `settings.js`, `grundriss.js`, `energieflussKatalog.js`, `energiefluss.js`, `energieflussEinstellungen.js`, `benachrichtigungen.js` | `settings.php?action=…`, `push.php?action=…` | — |
| `logs` | `logs.php` | `logs.js` | `logs.php?action=…` | — |
| `einfuehrung` | `einfuehrung.php` | `einfuehrung.js` | nichts — Folien und Bilder stehen in der Seite | — |
@@ -395,6 +396,18 @@ der Wechselrichter weiß. Die Nennenergie je Modul (2,76 kWh, HVM) steht als
Konstante oben in `speicher.js` — die BMU meldet sie nicht. Wie die Werte
aus der BMU herauskommen, steht in **[doku/byd.md](doku/byd.md)**.
**Karte „Wetter“**`meteogramm.js` zeichnet aus `meteogrammKatalog.js` drei
Felder mit gemeinsamer Zeitachse: Temperatur mit Wettersymbolen und
Tag/Nacht-Schattierung, Niederschlag mit der Bewölkung als Höhenband,
Wind mit Böen und Richtungspfeilen; dazu die Globalstrahlung auf der rechten
Achse des ersten Feldes. Umschaltbar 2 / 5 / 7 Tage, am Handy mit 2 Tagen
startend. **Links vom Jetzt-Strich stehen die gemessenen Werte der eigenen
Station**, rechts die Vorhersage aus `ajax/meteogramm.php`
(`weatherHours`/`weatherDays`, gefüllt von `gatherForecastData.py`). Das
eingebettete Meteogramm von meteoblue ist über einen Umschalter weiter
erreichbar und wird erst beim Hinschalten geladen. Einzelheiten in
**[doku/meteogramm.md](doku/meteogramm.md)**.
**`skodaMQTT.js`** — Fahrzeugseite. Holt alles über `ajax/skoda.php?was=…`
(live, ladungen, kurve, gesundheit, fahrten, strecke), zeichnet das Fahrzeug
als SVG-Draufsicht mit Ladestand im Akku, Ladeknopf und Klimaknopf, und die
@@ -456,6 +469,7 @@ klein: Diagramme füllen bzw. Logzeilen nachladen und einfärben.
| `energyHistory.php` | `?series=prod\|cons&range=month\|year\|decade` | Verbrauchs-/Erzeugungsverlauf (Rohdaten oder Stundenarchiv) |
| `getProdData` `getConsData` `getForecastData` `getHeaterData` `getWaterData` | `?FROM=&TO=` | Chart.js-Datensätze für die jeweilige Karte |
| `getSunrise.php` | `?FROM=&TO=` | Sonnenauf- und -untergänge als Diagramm-Markierungen |
| `meteogramm.php` | `?tage=2..7&rueck=0..72` | Vorhersage und gemessene Stunden zu einer Reihe verschmolzen, für das Meteogramm |
| `skoda.php` | `?was=live\|ladungen\|kurve\|gesundheit\|fahrten\|strecke` | Fahrzeugauswertungen aus `solarLog.skoda` |
| `skodaCmd.php` | `POST befehl=…` | Laden/Klima/Lüftung am Fahrzeug |
| `tile.php` | `?z=&x=&y=` | Kartenkachel aus dem eigenen Zwischenspeicher, sonst einmalig von OSM |
@@ -556,7 +570,9 @@ erDiagram
| `skoda`, `skoda_raw` | Fahrzeugzustand im Verlauf | `gatherSkodaData.py` |
| `skoda_ladepunkte` | Wallbox-Verlauf während einer Ladung, im Minutentakt, wird nicht ausgedünnt (`solarLog_skoda_ladepunkte.sql`) | `gatherSkodaData.py`; ältere Ladungen `skoda_ladepunkte_nachtragen.py` |
| `byd`, `byd_zellen` | BYD-Speicher direkt aus der BMU: Ladestand, SOH, Temperaturen, Spreizung, Zähler alle 5 Minuten; alle 128 Zellspannungen und 64 Temperaturen alle 15 Minuten (`solarLog_byd.sql`) | `gatherBYDData.py` |
| `weatherStation`, `weatherHours`, `weatherDays`, `daylight`, `simPower` | Wetter, Sonnenzeiten, Ertragsprognose | Wetterbrücke, Open-Meteo, Prognose |
| `weatherStation` | die eigene Wetterstation, alle 5 Minuten | die Station selbst (`/volume1/web/weatherStation.php`, außerhalb der Repos) |
| `weatherHours`, `weatherDays`, `weatherTilted`, `weatherForecastLog` | Wettervorhersage und Archiv; nichts wird gelöscht (`solarLog_weather.sql`, → [doku/meteogramm.md](doku/meteogramm.md)) | `gatherForecastData.py` (Open-Meteo), einmalig `wetterarchiv_nachtragen.py` |
| `daylight`, `simPower` | Sonnenzeiten (nur bis morgen), Ertragsprognose | `solarManager.py`; Solcast über `gatherSolar.php` |
| `gridCosts`, `gasCosts`, `fuelCosts` | Preiszeitreihen (Stichtag, kein Enddatum) | Einstellungsseite (`costs.php`) |
**`Logins`** — `users` (Passkeys) und `addUser` (Einmal-Links).
+1
View File
@@ -14,6 +14,7 @@ abzulesen sind.
| [automatiken.md](automatiken.md) | Die Automatiken (AutoActions): Datenmodell, Editor, Runner, wie eine Bedingung wirklich ausgewertet wird, Verkettung, Sperren, Fehlerbilder |
| [zeitleiste.md](zeitleiste.md) | Die Zeitleiste der Automatiken: wie der Server den Tag ausrechnet und wie der Browser ihn zeichnet |
| [benachrichtigungen.md](benachrichtigungen.md) | Meldungen aus Automatiken: Web Push ohne App, E-Mail, das Gerät „Benachrichtigungen“ |
| [meteogramm.md](meteogramm.md) | Das eigene Meteogramm: Vorhersage und eigene Messung in einem Bild, Katalog und Renderer, das Wetterarchiv |
| [uebersicht.md](uebersicht.md) | Die Solar-Übersicht: Katalog und Renderer, wie aus Watt ein Ring, ein Fluss und ein Füllstand wird |
| [byd.md](byd.md) | Die Batterie bis zur Zelle: das nachgebaute BMU-Protokoll, das Zeitfenster des Netzwerkmoduls, Byte-Lagen, Historie, Anzeige |
| [datenbank.md](datenbank.md) | Die drei Schemata: wer was schreibt, wie Messreihen verdichtet werden, welche Tabelle man für welche Auswertung nimmt |
Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

+9 -4
View File
@@ -98,9 +98,11 @@ Stand heute rund 170 MB, verteilt auf wenige große Reihen:
| `puffertemp` | ~247.000 | Puffertemperaturen | `solarManager.py` |
| `wasser`, `zisterne` | ~300.000 | Wasserzähler und Zisterne | `gatherWaterData.py` |
| `windrad`, `kiga_windrad` | ~255.000 | Windräder | `solarManager.py` |
| `weatherStation`, `weatherHours`, `weatherDays` | ~220.000 | eigene Wetterstation, Vorhersage | Wetterbrücke, Open-Meteo |
| `daylight` | 663 | Sonnenauf- und -untergang je Tag | Vorhersage-Job |
| `simPower` | ~82.000 | Ertragsprognose | Prognose-Job |
| `weatherStation` | ~195.000 | die eigene Wetterstation, alle 5 min | die Station selbst, über `/volume1/web/weatherStation.php` |
| `weatherHours`, `weatherDays` | ~40.000 / 1.600 | Wettervorhersage **und Archiv**, stündlich bzw. täglich (→ [meteogramm.md](meteogramm.md)) | `gatherForecastData.py` (Open-Meteo) |
| `weatherTilted`, `weatherForecastLog` | je nach Nutzung | Einstrahlung in Modulebene; die erste Vorhersage je Stunde | `gatherForecastData.py` |
| `daylight` | 663 | Sonnenauf- und -untergang, nur bis morgen | `solarManager.py` (gerechnet, nicht abgerufen) |
| `simPower` | ~82.000 | Ertragsprognose, 7 Tage voraus | Solcast, 4×/Tag über `/volume1/web/gatherSolar.php` |
| `byd`, `byd_zellen` | 1.559 / 492 | BYD-Speicher: alle 5 min Ladestand, SOH, Temperaturen; alle 15 min 128 Zellspannungen und 64 Temperaturen (→ [byd.md](byd.md)) | `gatherBYDData.py` |
| `skoda`, `skoda_raw`, `skoda_ladepunkte` | ~1.900 | Fahrzeugzustand, Rohantwort, Ladeverlauf im Minutentakt | `gatherSkodaData.py` |
| `gridCosts`, `gasCosts`, `fuelCosts` | 13 | Preiszeitreihen | Einstellungsseite |
@@ -146,7 +148,10 @@ Wichtig beim Auswerten:
| `status` | zeigt, bis wohin aggregiert und ab wann ausgedünnt ist |
Ausgedünnt werden `EnergyFlow`, `Heater` und `weatherStation`; das Zielraster
beträgt 15 Minuten. Was man beim Rechnen auf den Rohdaten wissen muss, steht
beträgt 15 Minuten. **`weatherHours` und `weatherDays` stehen bewusst nicht
dabei** — sie sind schon stündlich, und ihre Strahlungswerte sind die eine
Hälfte des Datenpaares für eine künftige Ertragsprognose (die andere ist
`EnergyFlow_hourly.pv_kwh`). Was man beim Rechnen auf den Rohdaten wissen muss, steht
ausführlich im Kopf der SQL-Datei — die wichtigsten Punkte:
* **Alles sind Momentanleistungen**, gemittelt über rund fünf Minuten. kWh
+270
View File
@@ -0,0 +1,270 @@
# 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.
![Das Meteogramm](bilder/meteogramm.png)
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
```mermaid
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.py` schreibt 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.
```json
{ "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)`, aber `AVG(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 aus `daylight`.** `daylight`
reicht 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
1. Spalte in `ajax/meteogramm.php` hinten anhängen
2. Schlüssel in `SPALTEN` hinten anhängen
3. Eintrag in `spuren` des passenden Feldes
Sonst nichts:
```js
{
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 | 01000 W/m² (Strahlung) | Wettersymbole, Tag/Nacht, Tagesmin/-max |
| Niederschlag und Bewölkung | mm/h | 014 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 02 km, mittel
27 km, hoch 714 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` |