- JavaScript 94.3%
- Python 3%
- Shell 2.7%
Am Geraet gefiel der rote Geschwindigkeitstext, und die Windrose passt in Rot besser dazu als in Grau. WIND_FARBE_HELL/MITTEL/DUNKEL bleiben als reiner Rotkanal bei denselben Zahlenwerten (204/102/51), der Text wieder ueber textfarbe(jetztStrahlung) wie die uebrigen Seiten. |
||
|---|---|---|
| test | ||
| werkzeuge | ||
| awtrix_wetter.js | ||
| README.md | ||
| Windrichtung.ods | ||
AWTRIX-Wetter
Regen-Countdown und DWD-Warnung für die Pixel-Uhr (Ulanzi TC001, AWTRIX 3), angesteuert aus ioBroker.
Die Uhr zeigt nicht dauernd alles an, sondern nur, wenn es etwas zu melden gibt: Die Regen-App erscheint, solange innerhalb der nächsten 24 Stunden Niederschlag vorhergesagt ist; die Warn-App nur, wenn eine amtliche DWD-Warnung anliegt.
openweathermap.0.forecast.period0..39 ──┐
(date, precipitation, 3-Stunden-Raster) │
├─► awtrix_wetter.js ──► 0_userdata.0.awtrix.*
dwd.0.numberOfWarnings ──┘ (Skript) │
dwd.0.warning.object / .headline │
▼
awtrix-light.0 customApps
"regen" / "warnung"
│
▼
Ulanzi TC001, 32 x 8 Pixel
Anzeige
| Datenpunkt | Inhalt | App |
|---|---|---|
0_userdata.0.awtrix.regen_text |
"jetzt", "3h" oder leer |
regen |
0_userdata.0.awtrix.regen_aktiv |
Regen liegt im Horizont | steuert apps.regen.visible |
0_userdata.0.awtrix.warn_text |
z. B. "WINDBÖEN" oder leer |
warnung |
0_userdata.0.awtrix.warn_aktiv |
Warnung liegt an | steuert apps.warnung.visible |
0_userdata.0.awtrix.temp_text |
"17/25°" |
temperatur |
0_userdata.0.awtrix.sonne_verlauf |
Tagesreihe von Open-Meteo (Zwischenspeicher) | speist sonne |
0_userdata.0.awtrix.regen_vorhersage |
"12% 17mm", steuert auch regenwahrcheinlichkeits Sichtbarkeit ("0% 0mm" → aus) |
regenwahrcheinlichkeit |
0_userdata.0.awtrix.regen_prozent |
Wahrscheinlichkeit als Zahl (%) | — (für eigene Auswertungen) |
| — (direkt ins Basisobjekt) | Countdown und Regenkurve der nächsten 3 Stunden | regenvorschau |
| — (direkt ins Basisobjekt) | Kompassnadel (Windrichtung) und "14km/h" |
wind |
Temperatur
"12/24°" — aktueller Wert und das Tagesextrem, das gerade zählt. Über das
Extrem entscheidet der Tiefstwert: Sinkt er auf null oder darunter, ist der
Frost die Nachricht ("2/-7°"), sonst zählt die Höchsttemperatur. Ohne
Nachkommastellen und mit einem Gradzeichen am Ende, damit es auf einen Screen passt.
Warum das Tagesmaximum früher schrumpfte
Der Höchstwert kam aus openweathermap.0.forecast.day0.temperatureMax. Das ist
nicht der Tageshöchstwert, sondern das Maximum der noch verbleibenden
3-Stunden-Perioden. Je später es wird, desto weniger warme Stunden liegen noch
vor uns — also sank der Wert im Lauf des Tages von 24° auf 23°.
Nachgewiesen am 29.08.2026 um 18:06, ohne Rückgriff auf die Doku:
day0.temperatureMax |
23,43 |
period0.temperatureMax |
23,43 — und period0 beginnt erst um 20:00 |
current.temperature |
24,03 |
Dass der aktuelle Wert über dem angeblichen Tagesmaximum lag, ist der Beweis.
Die Extreme kommen jetzt aus daily=temperature_2m_max,temperature_2m_min von
Open-Meteo — echte Extreme des Ortstages, deshalb dort timezone=auto statt
UTC. Mit UTC wäre es der Höchstwert eines um zwei Stunden verschobenen
Fensters: derselbe Fehler, nur kleiner.
Auch der aktuelle Wert kommt aus derselben Quelle (current=temperature_2m).
Zwei Quellen für eine Größe vertragen sich nicht von selbst: Am 29.08. um 18:45
meldete openweathermap 23,6 °C bei einem Tagesmaximum von 23,1, Open-Meteo
22,8 °C bei 23,8 — jede Mischung erzeugt Widersprüche.
Wie der aktuelle Wert trotz 30-Minuten-Takt in Bewegung bleibt
Die Reihe wird nur alle 30 Minuten geholt. Einfach eingefroren wäre die Temperatur bis zu 1,7 K daneben und bliebe sichtbar stehen — gemessen über drei Tage: im Extrem 3,3 K/h, Median 0,6 K/h.
Deshalb wird beim Abruf zusätzlich die Steigung der Stundenreihe gemerkt
(temperaturSteigung) und der Wert bis zum aktuellen Lauf fortgeschrieben
(temperaturJetzt). Die Fortschreibung ist auf METEO_TAKT_MS gedeckelt:
Bleibt der Abruf länger aus, wäre eine lineare Verlängerung reine Erfindung —
dann steht lieber ein etwas alter echter Wert da.
Kosten: eine Variable mehr in derselben Anfrage. Sieben Variablen über zwei Tage zählen weiterhin als 1,0 Aufrufe.
openweathermap bleibt nur noch als Rückfall, falls Open-Meteo gar nichts
geliefert hat. Für diesen Fall gilt im Anzeigetext weiter die Regel: der
aktuelle Wert kann nicht außerhalb der Tagesspanne liegen — steht er darüber,
gibt das Maximum nach, aus 24,03 und 23,43 wird 24/24° statt 24/23°.
Der längste Fall ist geprüft: -22/-24° sind acht Zeichen und füllen die
32 Pixel genau aus, ohne zu scrollen. Ein Sonderformat für Frosttage ist
also nicht nötig.
Niederschlag der nächsten 12 Stunden (regenwahrcheinlichkeit)
Zeigt Wahrscheinlichkeit und erwartete Menge: 12% 17mm. Die Prozentzahl
allein lässt die entscheidende Frage offen — 90 % für 0,2 mm Niesel und 90 % für
20 mm Gewitterregen sehen sonst gleich aus.
| Fall | Anzeige |
|---|---|
| keine nennenswerte Menge | 18% 0mm |
| unter 1 mm | 18% .7mm |
| ab 1 mm | 65% 2mm |
| Überlänge | 100%17mm — das Leerzeichen fällt, statt zu scrollen |
Die Menge steht immer da, auch als 0mm. Sie wegzulassen wäre zweideutig —
der Betrachter könnte sie für unbekannt halten. 18% 0mm sagt dagegen klar:
Regen ist möglich, aber es kommt nichts Nennenswertes zusammen.
Acht Zeichen sind die Grenze (nachgemessen an -22/-24°). Die App steht nur
zwei Sekunden; ein scrollender Text wäre in dieser Zeit nicht zu lesen.
Zwei Fehlbindungen, beide mit derselben Ursache
Erst hing die App an der falschen Größe. Bis zum 29.08.2026 an
weatherunderground.0.forecast.current.precipitationDay — das ist
current_observation.precip_today_metric, also der heute schon gefallene
Regen in Millimetern, keine Wahrscheinlichkeit. Sie zeigte deshalb tagelang
0,5 mm: Es hatte um 01:18 kurz geregnet und danach nicht mehr. Der Datenpunkt
wurde durchaus gepflegt — nur sein Wert änderte sich nicht. ts sah frisch
aus, erst lc (last change) verriet die 16 Stunden Stillstand.
Dann am falschen Zeitraum. Die Zwischenlösung
weatherunderground.0.forecast.0d.precipitationChance zeigte 14 %, während
jedes andere Vorhersageprogramm 0 % meldete. Beide Zahlen stimmten:
| Quelle | Wert | Bezug |
|---|---|---|
weatherunderground 0d |
14 % | ganzer Kalendertag |
Open-Meteo precipitation_probability_max |
100 % | ganzer Kalendertag |
| Open-Meteo, verbleibende Stunden | 0 · 0 · 3 · 0 · 0 % | was noch kommt |
Es hatte in jener Nacht um 00:45 und 01:00 geregnet (0,3 mm). Diese Nacht steckt in jedem Tageswert. Die anderen Programme meinten das noch Kommende.
Deshalb jetzt ein rollendes 12-Stunden-Fenster aus
hourly=precipitation_probability von Open-Meteo (WAHRSCH_H), Menge aus
derselben Reihe wie die Regenkurve. Ein Tageswert ist abends fast immer null und
enthält umgekehrt die Nacht, die schon war — beides beantwortet nicht die Frage,
die vor der Uhr gestellt wird. Das Fenster reicht über Mitternacht, deshalb
forecast_days=2.
Weder der openweathermap- noch der yr-Adapter liefert eine
Niederschlagswahrscheinlichkeit — geprüft über die vollständige Feldliste von
period0 bzw. 0h.
Der Schwellwert dieser App war eine Falle. Er stand auf „> 1 → Icon
terain" — mit dem bekannten Tippfehler für terrain, den es auf dem Gerät
nicht gibt, und der eine App spurlos verschwinden lässt. Bei Millimetern griff
er nie; bei Prozentwerten hätte er ab 1 % sofort gegriffen und die App beim
ersten Regenrisiko unsichtbar gemacht. Er ist deshalb abgeschaltet.
Der Tippfehler im App-Namen (regenwahrcheinlichkeit, ohne „s") bleibt
stehen: Ein Umbenennen legt neue Datenpunkte unter apps.<name>.* an und lässt
die alten als Waisen zurück.
Die App blendet sich aus, wenn der Text "0% 0mm" lautet — dann ist nichts
zu melden. Anders als der abgeschaltete native Schwellwert (siehe oben) prüft
das Skript den fertigen Anzeigetext, nicht einen Rohwert: Ein Zahlenschwellwert
hätte dieselbe Falle wieder aufgemacht, sobald sich Prozent oder Millimeter
unabhängig voneinander bewegen.
Regenvorschau: Countdown und Kurve auf einer Seite (regenvorschau)
Bis zum 29.08.2026 waren das zwei Seiten: der Countdown 3h als eigene
App und die Kurve als zweite. Beide blau, beide über Regen, kurz hintereinander
im Loop — schwer auseinanderzuhalten. Jetzt eine Seite:
Zeile
0 | |
1 |T T T | 1 | 2 | 3 | | T Countdown in Stunden
2 |T T T | 1 | 2 | 3 | | | y-Achse (jetzt) und Stundenlinien
3 |T T T | 1 | 2 | 3 | | 1 Stundenzahl, schwaches Grau
4 |T T T | 1 | 2 | 3 | | o Regen erst nach dem Fenster
5 |T T T | 1 | 2 | 3 | |
6 | | | | |o| <- Nulllinie der Kurve
7 | --------------------| <- x-Achse
0 11 12 31
| Spalten | Inhalt |
|---|---|
| 0–11 | Countdown in Stunden (24h braucht rund 11 px) |
| 12 | y-Achse — hier ist jetzt |
| 13–17 | Stunde 1, fünf Spalten zu je 12 Minuten, Ziffer 1 bei 14 |
| 18 | senkrechte Linie 1:00 |
| 19–23 | Stunde 2, Ziffer 2 bei 20 |
| 24 | senkrechte Linie 2:00 |
| 25–29 | Stunde 3, Ziffer 3 bei 26 |
| 30 | senkrechte Linie 3:00 |
| 31 | alles Weitere: 3:01 bis 24:00 |
Die Aufteilung schöpft die 32 Spalten genau aus — ein Test rechnet das nach,
damit eine Änderung an STD_SPALTEN oder VORSCHAU_H nicht stillschweigend
über den Rand läuft.
Fünf Spalten je Stunde heißt zwölf Minuten je Spalte — eine runde Zahl.
Die Datenspalten hängen dabei nicht zusammen: Nach jeder Stunde belegt die
Stundenlinie eine eigene Spalte. Wer die x-Position ausrechnet, statt sie bei
datenSpalten() zu holen, trifft die Linien.
Die Stundenziffern liegen im Diagramm, weil daneben kein Platz ist. Sie
werden vor der Kurve gezeichnet — wo Regen fällt, gewinnt die Kurve. Zwischen
Gitter (#484848) und Ziffern (#282828) liegen 32 Graustufen; bei den ersten
Werten (#303030 gegen #282828) waren es nur 8 von 255, und die Ziffern
waren vom Raster nicht zu unterscheiden. Angehoben wurde deshalb das Gitter,
nicht gesenkt die Ziffer — sonst wäre sie ganz verschwunden.
Der Aufbau von unten nach oben: Zeile 7 trägt die x-Achse, Zeile 6 ist die Nulllinie der Kurve, darüber wächst sie bis Zeile 0. Die Kurve steht damit auf einer Achse statt auf der Gehäusekante.
Die senkrechten Linien laufen von Zeile 1 bis zur Nulllinie und berühren die
Achse. Oben bleibt eine Zeile frei (GITTER_RAND) — von Kante zu Kante gezogen
lasen sie sich als Rahmen statt als Hintergrund. Die Regenkurve darf über sie
hinaus.
Weil eine Zeile für die Achse abgeht, ist der Vollausschlag 12 mm/h statt
14. MM_JE_PIXEL bleibt bewusst bei 2, damit diese Kurve und die Regenkurve im
Sonnentag dieselbe Skala haben und vergleichbar bleiben.
Der Punkt rechts auf der Nulllinie heißt: Regen kommt, aber erst nach dem
Fenster.
Ohne ihn ist ein leerer Graph zweideutig — er könnte auch heißen, dass keine
Daten vorliegen. Er saß zuerst auf der x-Achse und ging dort optisch unter; auf Wunsch steht er
jetzt eine Zeile höher, wo er frei steht. Vorbehalt, bewusst in Kauf
genommen: Dort kann er wie ein Messwert von 0 mm/h am Fensterende gelesen
werden. Mit der Kurve kollidiert er nicht — die Marke erscheint nur, wenn im
ganzen Fenster kein Regen liegt, die letzte Spalte also ohnehin leer ist. Am 30.08.2026
stand dort 18h im Textfeld, ein leerer Graph und dieser Punkt — Regen lag
17,8 Stunden voraus.
| Fenster | 3 Stunden (VORSCHAU_H) |
| Auflösung | 5 Datenspalten je Stunde (STD_SPALTEN), also 12 Minuten je Spalte |
| Skala | 2 mm/h je Pixelzeile (MM_JE_PIXEL), 14 mm/h Vollausschlag |
| Quelle Kurve | Open-Meteo minutely_15, je Pixelspalte der Höchstwert |
| Quelle Countdown | Open-Meteo hourly=precipitation, erste Stunde ≥ REGEN_SCHWELLE |
| sichtbar | wenn ein Countdown anliegt oder Regen im Fenster liegt |
Countdown und Kurve stammen aus derselben Vorhersage. Bis zum 30.08.2026
kam der Countdown aus den 3-Stunden-Zeiträumen von openweathermap, die Kurve
aus Open-Meteo — zwei Quellen für dieselbe Aussage, die sich widersprechen
können (gemessen: 18,1 h gegen 19 h). Dieselbe Falle wie zuvor bei der
Temperatur. openweathermap bleibt nur der Rückfall, wenn Open-Meteo gar
nichts geliefert hat — nicht, wenn es „kein Regen" sagt.
Text und Grafik in einem Bild gehen über den Zeichenbefehl dt:
{"dt":[x,y,"3h","#RRGGBB"]}. Der ist in der Adapter-Dokumentation nicht
erwähnt (dort stehen nur dc, dl, dp) — der Adapter reicht das Objekt aber
unverändert durch, und die Firmware 0.98 kann ihn. Am Gerät nachgemessen.
Der Countdown wird gekürzt. "3h" belegt gemessene 7 Pixel, also rund
3,5 je Zeichen; "jetzt" wären 18 Pixel und würden den Graphen erdrücken.
Auch der längste Fall bleibt bei drei Zeichen: 24h, 45m.
Warum Minuten nur manchmal
Die Anzeige darf nur so genau sein wie ihre Quelle. Eine Stundenreihe kann
nicht sagen, in welcher Minute der Regen einsetzt — 47m wäre erfundene
Genauigkeit, genau die Sorte Fehler, die dieses Projekt schon zweimal gekostet
hat. Deshalb trägt jeder Treffer seine Auflösung mit (schritt), und
countdownKurz wagt Minuten nur, wenn sie fein genug ist:
| Vorlauf | Quelle | Auflösung | Anzeige |
|---|---|---|---|
| bis ~5 h | regenFein, 15 min |
± 15 min | 2h, dann 45m · 30m · 15m · 0m |
| darüber | regenGrob, 1 h |
± 1 h | 18h |
| Rückfall openweathermap | 3-h-Zeiträume | ± 3 h | 18h |
Der Übergang liegt bei 52,5 Minuten: darüber 1h, darunter 45m. Gerundet
wird auf die nächste Viertelstunde — 23 Minuten werden zu 30m, 17 zu 15m.
Krumme Minutenwerte kann es deshalb gar nicht geben; ein Test hält das fest.
Die Gitterabstände müssen aufgehen. Zuerst begann der Graph bei Spalte 13
und war damit 19 Pixel breit — 18 Intervalle, und 18 lässt sich nicht in vier
gleiche Teile schneiden. Die Linien standen im Muster 5-4-5-4, was auf
32 × 8 Pixeln sofort auffällt. Mit Spalte 15 sind es 17 Pixel, also 16
Intervalle, und 16 / (VORSCHAU_GITTER + 1) = 4 geht glatt auf.
Die Bedingung lautet: (Graphbreite − 1) muss durch (Gitterlinien + 1) teilbar
sein. Ein Test prüft das, damit eine spätere Änderung an GRAPH_X0 oder
VORSCHAU_GITTER nicht stillschweigend wieder ein schiefes Raster erzeugt.
Wo kein Regen fällt, wird nichts gezeichnet — auch keine Null. Vorher lief eine durchgezogene blaue Linie über die unterste Zeile, quer durch trockene Stunden; das sieht aus wie eine Aussage („ganz wenig Regen"), ist aber nur der Boden des Diagramms. Eine einzelne Regenspalte zwischen trockenen wird als Punkt gezeichnet, statt spurlos zu verschwinden.
Sonnentag (sonne)
Das Gegenstück zur Strahlungsgrafik von wetter-zorneding.de, auf 32 x 8 Pixeln:
- gefüllte Glocke in sehr dunklem Gelb — theoretisch mögliche Strahlung, gerechnet aus der Sonnenhöhe mit Luftmassen-Dämpfung nach Kasten-Young
- helle Kurve davor — die von Open-Meteo im Viertelstundentakt modellierte Globalstrahlung, über den ganzen Tag: auch für die schon vergangenen Stunden (siehe „Woher der vergangene Tag kommt")
- blaue Regenkurve — auf derselben Tagesachse, wo Niederschlag erwartet wird
- graue senkrechte Marke — der aktuelle Zeitpunkt im Tag; ohne sie wäre der Bogen jeden Tag dasselbe statische Bild. Sie reicht vom Boden nur bis zur Oberkante des Bogens, bleibt also in der Silhouette, statt durch den leeren Himmel darüber zu schneiden.
Sonne und Regen erzählen dabei dieselbe Geschichte von zwei Seiten: Dieselbe Bewölkung, die den Regen bringt, drückt die Strahlungskurve nach unten. Die Regenkurve trägt eine andere Einheit (mm/h statt W/m², Skala wie in der Regenvorschau) — physikalisch unsauber auf einer gemeinsamen Höhenachse, auf 32 x 8 Pixeln trennt die Farbe das hinreichend.
Was vor der Jetzt-Marke liegt, wird auf 45 % abgedunkelt (VERGANGEN_FAKTOR)
— Bogen, Sonnenkurve und Regenkurve gleichermaßen. Auf 32 × 8 Pixeln ist die
Helligkeit der einzige verfügbare Unterschied; für eine zweite Form oder ein
Muster ist kein Platz. Das Segment, das die Marke überspannt, bleibt hell, damit
der Wechsel genau auf der Marke sitzt und nicht eine Spalte davor.
| vergangen | ab jetzt | |
|---|---|---|
| Sonnenkurve | #735A00 |
#FFC800 |
| Bogen | #1A1500 |
#3A2E00 |
| Regenkurve | #004873 |
#00A0FF |
Der Faktor stand zuerst auf 0,75 — am Gerät war der Unterschied nicht zu
erkennen (das PNG der Aufnahme hellt um 2,6 auf und täuscht hier). Der Bogen
ist mit #3A2E00 schon fast schwarz und verträgt das Abdunkeln am schlechtesten;
bleibt seine Vergangenheit unsichtbar, ist ein getrennter Faktor für Fläche
und Kurve die nächste Stufe.
Gezeichnet wird in der Reihenfolge Fläche, Jetzt-Marke, Sonnenkurve, Regen — wo sich beide Kurven kreuzen, gewinnt Blau.
Beide Kurven brechen ab, wo sie nichts zu sagen haben: die gelbe am Rand des Bogens, die blaue dort, wo kein Regen fällt. Sonst liefe eine Nulllinie über das ganze Bild. Innerhalb des Bogens wird eine gelbe Null dagegen gezeichnet — dort ist es wirklich düster, und das ist eine Aussage.
Die Regenvorschau bleibt als eigene Seite bestehen: Sie deckt auch die Nacht ab, aus der der Tagesbogen herausfällt, und zeigt mit drei Stunden ein viel feineres Fenster.
Die Breite spannt von Sonnenauf- bis Sonnenuntergang, der Bogen füllt das Bild
also immer aus; nachts bleibt die App aus. Die Höhe hat einen festen Bezug
(STRAHLUNG_MAX, 800 W/m²), damit ein Dezembertag sichtbar flacher ausfällt als
ein Junitag — mit Autoskalierung sähe jeder Tag gleich aus.
Die Kurve ist ein Modellwert, keine Messung. Ein Strahlungssensor ist nicht vorhanden (der einzige Lichtsensor sitzt in der Uhr selbst und misst den Raum). Sie kommt aus dem Wettermodell hinter Open-Meteo, das die Bewölkung analysiert und daraus die Globalstrahlung rechnet — stündlich, dazwischen wird linear interpoliert.
Die Kurve darf über den Bogen hinausgehen. Bei aufgerissener Bewölkung übersteigt die reale Globalstrahlung den Klarhimmelwert (Reflexion an Wolkenkanten) — am 29.08.2026 um 16 Uhr meldete Open-Meteo 615 W/m² gegen 509 W/m² gerechnet. Genau so zeigt es auch die Strahlungsgrafik von wetter-zorneding.de, an der sich diese App orientiert: Die orange Linie läuft dort mittags weit über die blasse Glocke. Ein Kappen hätte eine Aussage unterschlagen — dass der Tag ausgesprochen hell war.
Nach unten bleibt es beim Bogen als Grenze: Wo der Bogen null ist (vor Aufgang, nach Untergang), wird keine Kurve gezeichnet.
Woher der vergangene Tag kommt
Das war lange falsch, und der Fehler war nicht harmlos: yr liefert erst ab der laufenden Stunde. Die Interpolation hält außerhalb ihrer Stützstellen den Randwert fest — der ganze Vormittag bekam also die Bewölkung von jetzt.
Das Ergebnis sah nicht kaputt aus, sondern nur falsch: Zog es sich nachmittags zu, sackte rückwirkend der ganze Tag ab. Am 29.08.2026 zeigte die Uhr um 17:21 einen durchgehend trüben Tag (erwartetes Maximum 343 W/m²), obwohl es bis 16 Uhr klar war (tatsächlich 615 W/m²). Wer über den Tag hinsieht, bekommt den Eindruck, die Sonnenintensität nehme immerzu ab.
Gelöst über Open-Meteo: Die Antwort deckt den ganzen laufenden Tag ab, auch die schon vergangenen Stunden. Damit muss nichts selbst mitgeschrieben werden — die Reihe ist rückwirkend vollständig und übersteht auch einen Neustart mitten am Tag.
https://api.open-meteo.com/v1/forecast
?latitude=…&longitude=…
&minutely_15=shortwave_radiation,precipitation
&hourly=precipitation_probability,temperature_2m
&daily=temperature_2m_max,temperature_2m_min
¤t=temperature_2m
&forecast_days=2&timeformat=unixtime&timezone=auto
timeformat=unixtime spart das Deuten von Zeitzonen; timezone=auto sorgt
dafür, dass die daily-Werte die Extreme des Ortstages sind. Ein
zusätzliches past_days wäre dreifache Datenmenge ohne Gewinn — der laufende
Tag ist ohnehin ganz enthalten. forecast_days=2, weil das
12-Stunden-Fenster der Niederschlags-App abends über Mitternacht reicht.
Sieben Variablen über zwei Tage zählen weiterhin als 1,0 Aufrufe (bis zu 10 Variablen und 2 Wochen sind ein Aufruf) — der Verbrauch bleibt bei 48/Tag.
Zeitstempel: Open-Meteo meint das Intervall-ENDE
Der Wert bei 07:00 ist der Mittelwert von 06:00 bis 07:00, nicht die Strahlung um 07:00. Bewiesen am 29.08.2026, ohne Rückgriff auf die Dokumentation:
hourly 07:00 |
9,5 W/m² |
minutely_15 06:15 / 06:30 / 06:45 / 07:00 |
0 / 0 / 9 / 29 |
| deren Mittel | 9,5 |
Bei der anderen Lesart müssten dort ~90 W/m² stehen. Der zweite, unabhängige Beleg kam aus dem Niederschlag: Die vier Viertelstundensummen von 13:15 bis 14:00 ergaben exakt den Stundenwert von 14:00.
Wer den Stempel als Intervall-Anfang liest, schiebt den ganzen Tag um eine halbe Intervalllänge nach hinten — bei Stundenwerten 30 Minuten, also mehr als eine Pixelspalte.
Das Skript legt jeden Wert deshalb auf die Mitte seines Intervalls. Die
Intervalllänge nimmt es aus den Daten selbst (meteoBlock), damit dasselbe für
Stunden- wie für Viertelstundenwerte gilt.
Warum die Strahlung im Viertelstundentakt kommt
Stundenmittel glätten genau das weg, was einen Tag ausmacht. Am 29.08.2026 lagen alle Stundenwerte von 10:49 bis 16:31 zwischen 487 und 608 W/m² — bei 800 W/m² Vollausschlag auf 8 Zeilen ist eine Zeile 114 W/m² breit, der ganze Zeitraum fiel also auf eine einzige Zeile. Die Kurve war ein Plateau, obwohl die Messung von wetter-zorneding.de am selben Tag Einbrüche auf ~150 W/m² und Spitzen um 800 zeigte.
Mit minutely_15 liegt der Hub im selben Fenster bei 437–640 W/m² und damit
über zwei Zeilen; die Dellen um 11:15, 12:08 und 14:19 werden sichtbar. Die
Auflösung passt auch zur Anzeige: 32 Spalten über 13,6 Stunden sind rund
25 Minuten je Spalte, jede Spalte trägt also einen echten Stützpunkt statt einer
Interpolation zwischen zwei weit entfernten Stundenmarken.
Der Regen kommt aus derselben Quelle — dort verschlucken Stundenwerte noch mehr. Am 28.08.2026 meldete die Stunde ab 16 Uhr 5,8 mm/h; die Viertelstunden darin waren 6,4 und 16,8 mm/h. Auf 8 Zeilen zu 2 mm/h sind das 3 Pixel gegen volle Höhe.
Achtung bei der Einheit: In minutely_15 ist precipitation eine Summe je
Viertelstunde, nicht mm/h. 4,2 mm in der Viertelstunde sind 16,8 mm/h. Ohne
Umrechnung wäre der Regen auf ein Viertel geschrumpft — und niemandem
aufgefallen, weil die Kurve weiter plausibel aussieht. Bei Stundenwerten sind
mm und mm/h zufällig dieselbe Zahl, deshalb fällt der Fehler beim Umstieg auf
ein feineres Raster erst auf, wenn man ihn sucht. Der Faktor kommt aus der
gemessenen Intervalllänge (meteoBlock(…, proStunde)), nicht aus einer
Annahme über die Anfrage.
So ist das Diagramm zu lesen
- Die y-Achse bei Spalte 12 ist jetzt. Nach rechts läuft die Zeit.
- Die Kurve zeigt eine Intensität in mm/h, keine Menge: 2 mm/h je Pixelzeile, Vollausschlag 12 mm/h. Je Spalte steht der Höchstwert ihrer zwölf Minuten, nicht das Mittel — ein kurzer Guss soll als Guss erscheinen.
- Der Text links ist der Countdown bis zum nächsten Regen innerhalb von
24 Stunden — in Stunden, unter einer Stunde in Viertelstundenschritten:
45m,30m,15m,0m. - Der Punkt in Spalte 31 ist eine Ja/Nein-Marke: Regen kommt, liegt aber jenseits der drei Stunden. Er sagt nichts über die Menge und wandert nicht — er verschwindet, sobald das Ereignis ins Fenster rutscht, und die Kurve erscheint an seiner Stelle.
Ein Grenzfall: Zwischen 3,0 und 3,5 Stunden rundet der Countdown auf 3h,
während das Ereignis noch außerhalb des Fensters liegt. Dann steht 3h bei
leerem Graphen — und die Marke in Spalte 31 ist genau der Hinweis darauf.
Warum je Spalte aggregiert und nicht abgetastet wird
Eine Pixelspalte ist rund 26 Minuten breit, das Datenraster 15 Minuten. Wer an der Spaltenmitte abtastet, verfehlt alles dazwischen: Der Guss vom 28.08. um 16:22 (16,8 mm/h) lag genau zwischen den Spalten 16:05 und 16:32 und tauchte in beiden nur als 5,8 mm/h auf. Feinere Daten punktweise abzutasten macht sie nicht genauer, sondern zufälliger.
Jede Spalte fasst deshalb alle Messwerte zusammen, die sie überlappt — mit unterschiedlicher Regel, und das ist keine Geschmacks-, sondern eine Sachfrage:
| Reihe | Regel | Warum |
|---|---|---|
| Strahlung | zeitgewichtetes Mittel | Ein Zustand. Die Spalte sagt, wie hell dieser Abschnitt war. Am 28.08. wich das Mittel höchstens 45 W/m² vom abgetasteten Wert ab — weniger als eine halbe Pixelzeile. |
| Regen | Höchstwert | Ein Ereignis. Die Aussage ist „es hat gegossen". Über 26 Minuten gemittelt schrumpfte derselbe Guss von 7 auf 3 Pixel — das hätte genau den Fehler wiederholt, wegen dem die Stundenwerte nicht mehr verwendet werden. |
Die Regenkurve zeigt damit die Spitzenintensität der Spalte, nicht die gefallene Menge. Für „wie viel kam runter" ist sie die falsche Grafik; für „wann und wie heftig" die richtige.
Der 28.08. als Beispiel — dieselben Daten liegen als Prüfgrundlage in
test/daten/openmeteo-2026-08-28.json:
+--------------------------------+
| RR |
| O........ R | O erwartete Strahlung
| .OO....O....R | . möglicher Bogen
| .OO.OO...OOOO.R. | R Regen (16,8 mm/h um 16:20)
| OOO.....OOO....R.... |
| OO...............R.OO.. |
| O.................ROO.OOOO |
| ..................RO...... |
+--------------------------------+
Wie viel Traffic das kostet
Open-Meteo ist kostenlos für nichtkommerzielle Nutzung, ohne Schlüssel und ohne Rate-Limit-Header. Die Grenzen stehen nur in den Nutzungsbedingungen:
| Limit | 600/min · 5.000/h · 10.000/Tag · 300.000/Monat |
| Gewicht dieser Anfrage | 1,0 (bis 10 Variablen und ≤ 2 Wochen zählen als ein Aufruf; hier 7 Variablen, 2 Tage) |
| Takt | höchstens alle 30 min (METEO_TAKT_MS) |
| Verbrauch | 48/Tag ≈ 0,5 % des Limits, ~1.460/Monat |
Dreißig Minuten reichen, weil die Antwort den ganzen Tag enthält — auch die kommenden Stunden. Häufiger zu fragen brächte nur eine neue Modelllaufnummer.
Die Sperre gilt auch nach einem Fehlschlag. Sonst fragt ein Netzausfall im Fünf-Minuten-Takt des Skripts weiter und macht aus 48 Aufrufen am Tag 288 — genau so wird aus einer Störung bei uns eine Last beim anderen.
Datenquelle: Open-Meteo, lizenziert unter CC BY 4.0.
Die letzte geglückte Antwort liegt als 32-Spalten-Reihe in
0_userdata.0.awtrix.sonne_verlauf, mit dem Tagesdatum als Schlüssel. Fällt der
Abruf aus, trägt dieser Zwischenspeicher den Tag zu Ende; passt er nicht zum
heutigen Datum, verfällt er von selbst. Erst wenn beides fehlt, greift wieder
die alte Rechnung aus yr-Bewölkung — die den Vormittag naturgemäß nicht kennen
kann.
Spalten außerhalb der gelieferten Reihe bleiben leer statt auf den Randwert gesetzt zu werden. Genau dieses Festhalten war der ursprüngliche Fehler.
shortwave_radiation_clear_sky wäre der naheliegende Bogen — taugt hier aber
nicht: Für vergangene Stunden liefert Open-Meteo dort null (geprüft am
29.08.2026). Der Bogen bleibt deshalb die eigene Kasten-Young-Rechnung.
Nicht verwendet: die banyaro-Wetterdatenbank. Sie speichert Tagesaggregate
(weather_log: eine Zeile je Datum und Ort, mit precip_prob, temp_max, …) —
keine stündliche Strahlung und keine Bewölkung. Für einen Tagesbogen mit 32
Stützstellen ist das die falsche Auflösung, und es würde die Uhr an einen
weiteren Container binden. Die Quelle hinter banyaro ist allerdings dieselbe:
Open-Meteo.
Das theoretische Maximum liegt rund 12 % über dem von wetter-zorneding.de
(696 gegen 620 W/m² am 29.08.) — Modellfrage, nicht Fehler: Hier wird mit klarer
Atmosphäre gerechnet. Über TRUEBUNG ließe sich das angleichen.
Der Sonnentag hat kein Gitter: Der gefüllte Bogen ist selbst schon Fläche, ein Raster dahinter wäre nicht zu erkennen. Gitterlinien gibt es nur in der Regenvorschau, wo die Kurve vor schwarzem Grund steht.
Woher der Sonnenstand kommt
Die Tagesgrenzen aus getAstroDate('sunrise'/'sunset') des javascript-Adapters,
die Sonnenhöhe für jede Pixelspalte aus einer eigenen Rechnung (Standardverfahren
über Deklination und Stundenwinkel).
Vier Quellen wurden am 2026-08-29 gegeneinander geprüft und stimmten überein:
| Quelle | Aussage |
|---|---|
getAstroDate |
Aufgang 06:26, Untergang 20:02 |
yr-Symbole (_day/_night) |
Wechsel zwischen 19:00 und 20:00 |
followthesun.0.current.altitude |
37,1° um 10:21 |
| eigene Rechnung | 37,13° zur selben Zeit |
followthesun liefert nur den aktuellen Stand, keine Zeitreihe und keine
Auf-/Untergangszeiten — als Quelle für einen Tagesbogen taugt er deshalb nicht,
wohl aber als unabhängige Gegenprobe. Das Skript vergleicht bei jedem Lauf seine
Rechnung gegen altitude und warnt bei mehr als einem Grad Abweichung. So fällt
auf, wenn etwa der Standort in system.config nicht stimmt.
Ein erster Versuch mit senkrechten Punktlinien auf den geraden Zeilen plus einer
waagerechten auf Zeile 3 sah unruhig aus: Beide Muster teilten keinen einzigen
Punkt, und auf 32 x 8 Pixeln liest das Auge so etwas als Streuung statt als
Raster. Stellschraube ist GITTER_FARBE.
Die Regenkurve ist mit 2 mm/h je Pixelzeile skaliert, 14 mm/h sind also Vollausschlag.
Der Countdown wird alle fünf Minuten neu gerechnet, zusätzlich sofort bei
Änderung von dwd.0.numberOfWarnings oder openweathermap.0.forecast.period0.date.
Die Stellschrauben stehen als Konstanten oben im Skript: Schwellwert
(REGEN_SCHWELLE, mm je 3 h), Vorausschau (HORIZONT_H), ab wann „jetzt“
statt einer Stundenzahl steht (JETZT_GRENZE_H) und ob der Text kurz („3h“)
oder lang („Regen 3h“, scrollt) ist (KURZTEXT).
Wind (wind)
Kompassring links auf 8 x 8 Pixeln plus Geschwindigkeit rechts als Text, z. B.
14km/h. Quelle ist openweathermap.0.forecast.current — derselbe Live-
Messwert, der auch als Temperatur-Rückfall dient (nicht die stündliche
Vorhersage von yr): Für eine aktuelle Windanzeige zählt der jetzige Wert, keine
Prognose.
Acht feste Bilder (N, NO, O, SO, S, SW, W, NW im Uhrzeigersinn,
WIND_SYMBOLE), gerundet auf 45 Grad — mehr ließe sich auf so wenigen Pixeln
ohnehin nicht unterscheiden. „Oben“ auf dem Icon ist Norden.
Drei frühere Entwürfe verwarf der Nutzer, jeweils nach Betrachtung am Gerät (alle 2026-09-05): eine reine Linie von der Mitte zum Rand (Nordost- und Südwest-Wind sahen gleich aus, weil eine Strecke richtungslos ist), dieselbe Linie mit Pfeilspitze (ließ sich trotzdem nicht sicher einer Richtung zuordnen), und ein Ring aus acht gleich behandelten Zonen — nur die aktuelle hell, der Rest dunkel als Bezugsrahmen (ging als einzelner Pixel je Zone optisch unter).
Das aktuelle Design hat der Nutzer selbst entworfen, als Tabelle
(Windrichtung.ods, ein Blatt je Richtung, Zellhintergrundfarben als Pixel).
WIND_SYMBOLE ist eine direkte Übernahme dieser acht Blätter — aus der
Datei extrahiert, nicht abgetippt, um Übertragungsfehler auszuschließen.
Jedes Bild ist 7 x 7 Pixel (das 8 x 8-Icon lässt eine Spalte und eine Zeile
frei, WIND_TEXT_X rückt entsprechend eine Spalte näher an den Rand):
- ein statischer Ring aus acht dunklen Punkten (
WIND_FARBE_DUNKEL) an N/NO/O/SO/S/SW/W/NW als Bezugsrahmen, in jedem der acht Bilder gleich; - eine helle Häufung (
WIND_FARBE_HELL) an der physischen Zielrichtung — wohin der Wind weht; - eine Reihe mittlerer Punkte (
WIND_FARBE_MITTEL) auf dem Weg dorthin. Der Ringpunkt, der dem Blatttitel entspricht (z. B. „Südwind“ = Herkunft Süden, meteorologisch), bleibt bewusst dunkel und verschmilzt so mit dem Rahmen, statt sich hervorzuheben.
current.windDirection liefert die meteorologische Richtung, AUS der der
Wind kommt, und geht unverändert in windRoseBefehle() — keine Drehung
(die Funktion selbst kennt ohnehin keine Konvention, sie wählt schlicht eines
der acht Bilder nach dem übergebenen Winkel). Am Gerät fällt das helle
Büschel als Auffälligstes im Bild ins Auge und wirkt wie eine Pfeilspitze;
mit einer früheren 180-Grad-Drehung vor der Auswahl landete dieses Büschel
deshalb auf der Herkunft statt auf dem Ziel — vom Nutzer am Gerät bemerkt und
mit einem Vergleich gegen Apple Wetter bestätigt (2026-09-05: 270° West sollte
einen nach Osten weisenden Pfeil ergeben, das helle Büschel saß aber auf der
Westseite). Ohne Drehung sitzt es automatisch an der Zielrichtung.
Die drei Farben stehen als eigene Konstanten im Skript (WIND_FARBE_HELL,
WIND_FARBE_MITTEL, WIND_FARBE_DUNKEL), nicht über dunkler() aus einer
einzigen Farbe abgeleitet — der Nutzer möchte hier noch mit unterschiedlichen
Farben experimentieren, nicht nur mit Helligkeitsstufen. Ursprünglich
Graustufen wie in der eingereichten Tabelle, seit 2026-09-05 auf Rot
umgestellt (#CC0000/#660000/#330000, reiner Rotkanal mit denselben
Zahlenwerten wie zuvor) — passt zum roten Geschwindigkeitstext und wirkt am
Gerät stimmiger als Grau gegen Rot.
current.windSpeed kommt in m/s, umgerechnet mit dem Faktor 3,6. Die Seite ist
sichtbar, sobald überhaupt ein Live-Messwert vorliegt — anders als bei der
Regenwahrscheinlichkeit gibt es hier keine „nichts zu melden“-Schwelle, weil
auch Windstille (0km/h) eine Aussage ist.
Aufbau
awtrix_wetter.js das ioBroker-Skript (script.js.Umweltdaten.Awtrix_Wetter)
test/test.js prüft die reine Logik, ohne ioBroker
test/daten/perioden.json echte Vorhersagedaten der Anlage als Prüfgrundlage
test/daten/openmeteo-2026-08-28.json
echter Regentag (14,6 mm, Guss 16,8 mm/h)
werkzeuge/deploy.sh Testlauf, dann Skript auf die DiskStation laden
werkzeuge/display.sh Displayinhalt als Bildserie holen
werkzeuge/display.py daraus PNG und ASCII-Vorschau bauen
Prüfen
node test/test.js
werkzeuge/deploy.sh ruft denselben Testlauf vor jedem Deployment auf und
bricht bei einem Fehlschlag ab, ohne die DiskStation anzufassen.
Das ersetzt nicht node --check, sondern ergänzt es: Die Syntaxprüfung lässt
Fehler durch, die das Skript erst beim Laden töten. Am 30.08.2026 zweimal
dieselbe temporale Totzone — eine Konstante, die vor ihrer Abhängigkeit stand:
node --check -> Syntax in Ordnung (hätte deployt)
deploy.sh -> ReferenceError: Cannot access 'BREITE' before initialization
Testlauf fehlgeschlagen — nicht deployt
Auf der Uhr wäre das Skript stumm gestorben. Gefangen hat es beide Male der Testlauf, weil er den Kern wirklich ausführt.
Der Testlauf schneidet den zu prüfenden Teil direkt aus awtrix_wetter.js
(alles bis zur Marke TESTBAR-ENDE) — es gibt also keine zweite Kopie der
Logik, die veralten könnte. Geprüft wird gegen echte Vorhersagedaten der Anlage
plus Grenzfälle.
Nachsehen, was die Uhr wirklich zeigt
werkzeuge/display.sh 9 > aufnahmen.txt
werkzeuge/display.py aufnahmen.txt display.png
Das ist der einzige verlässliche Weg, eine Änderung zu prüfen: Ob eine App auf dem Gerät ankommt, steht weder in der Konfiguration noch zuverlässig im Log.
Zwei Dinge dabei beachten:
-
Immer alle Bilder ansehen, nie eines automatisch auswählen. Eine Heuristik wie „das Bild mit den meisten Pixeln“ trifft zuverlässig die Uhrzeit-App und nicht die dünne Kurve, die man sucht — so ist hier schon einmal ein funktionierendes Feature fälschlich für kaputt erklärt worden.
-
Das PNG hellt die Farben um Faktor 2,6 auf, sonst wäre am Bildschirm nichts zu erkennen. Wie dezent etwas wirklich wirkt, entscheidet sich nur am Gerät.
-
Kurz stehende Seiten fallen durch das Raster. Der Adapter holt den Displayinhalt nur alle
downloadScreenContentIntervalSekunden (Standard 10) vom Gerät. Steht eine App nur 2 Sekunden, trifft man sie kaum — auchapps.<name>.activatehilft dann wenig, weil der Screenshot schon veraltet ist. Für eine Messung das Intervall vorübergehend senken und danach zurückstellen:iobroker object set system.adapter.awtrix-light.0 native.downloadScreenContentInterval=2 # ... messen, z. B. 16 Aufnahmen im 3-Sekunden-Takt ... iobroker object set system.adapter.awtrix-light.0 native.downloadScreenContentInterval=10Jede Änderung an
native.*startet den Adapter neu, die Uhr flackert also zweimal kurz. Nicht als Dauerzustand lassen — es kostet WLAN und Akku.
Erstinstallation
iobroker object set ändert nur vorhandene Objekte. Ein neues Skript-Objekt
entsteht mit object extend — und weil die CLI ihr Argument am ersten =
in propertyname=wert zerlegt, darf das JSON kein = enthalten. Deshalb in
zwei Zügen: erst die Hülle ohne Quelltext, dann der Quelltext als eigener Wert.
# im Container iobroker-1
iobroker object extend script.js.Umweltdaten.Awtrix_Wetter \
'{"_id":"script.js.Umweltdaten.Awtrix_Wetter","type":"script","common":{"name":"Awtrix_Wetter","engineType":"Javascript/js","engine":"system.adapter.javascript.0","source":"","debug":false,"verbose":false,"enabled":false,"expert":false},"native":{}}'
Danach werkzeuge/deploy.sh, dann einschalten:
iobroker object set script.js.Umweltdaten.Awtrix_Wetter common.enabled=true
Die beiden Apps werden in system.adapter.awtrix-light.0 unter
native.customApps eingetragen (text: "%s", objId auf den jeweiligen
Datenpunkt). Nur dieses Teilfeld schreiben, nicht das ganze Objekt — im
Adapter steht das verschlüsselte Gerätepasswort.
Fallstricke, die hier Zeit gekostet haben
Ein Icon, das es nicht gibt, lässt die App verschwinden. Mit
icon: "terain" (Tippfehler für „terrain“, auf dem Gerät nicht vorhanden)
erschien die Regen-App überhaupt nicht auf dem Display — obwohl das Debug-Log
[refreshCustomApp] Refreshing custom app "regen" meldete und kein Fehler kam.
Ohne Icon war sie sofort da. Icons müssen erst über die AWTRIX-Oberfläche auf
das Gerät.
schedule() und on() müssen synchron registriert werden. Aus einem
.then() heraus nachgereicht zählt der javascript-Adapter sie nicht mit
(registered 0 subscriptions, 0 schedules) — der Countdown bliebe stehen.
precipitation ist null, wenn kein Regen fällt, nicht 0.
Ein Zeitraum kann abgelaufen sein. period0 ist nicht zwangsläufig der
nächste: Stockt openweathermap, liegt er in der Vergangenheit. Ohne Prüfung auf
stunden <= -PERIODE_H meldet die Uhr „Regen jetzt“, wenn es längst aufgehört
hat. Diesen Fehler hat erst der Testlauf gezeigt.
Custom-Apps brauchen ack: true. Der Adapter übernimmt den Wert nur, wenn
er bestätigt ist (val: state && state.ack ? state.val : undefined) — sonst
entfernt er die App wieder vom Gerät. Deshalb schreibt das Skript mit
setState(id, val, true).
Die Farbe kann nicht mit dem Wert wechseln. Eine customApp hat eine feste
textColor; Schwellwert-Farben greifen nur bei numerischen Datenpunkten. Für
eine Warnfarbe je DWD-Stufe wären expertApps nötig. Die Warn-App ist deshalb
fest rot, die Stufe steht im Log.
Ein Text kostet die Schwellwerte. Die Temperatur-App hängt an einem
String-Datenpunkt, damit zwei Werte auf einen Screen passen. Damit greifen ihre
Schwellwerte nicht mehr — die Icons cold/hot und der Farbwechsel sind weg,
und eingeschaltete Schwellwerte würden bei jedem Lauf data type is invalid
ins Log schreiben. Sie sind deshalb deaktiviert.
Bei Expert-Apps gewinnen die Attribut-Datenpunkte gegen das Basisobjekt.
Der Adapter baut das App-Objekt als {...baseObject, color, duration, …} —
was im Basisobjekt steht, wird danach überschrieben. Farbe und Standzeit gehören
deshalb in apps.<name>.textColor bzw. .duration, nicht ins JSON.
lineColor gibt es nur in der Adapter-Konfiguration, an der Uhr heißt das
Feld color. Und weil color global für die ganze App gilt, kann eine line
immer nur eine Farbe haben.
Mehrfarbig geht nur über draw. Jeder Befehl trägt seine eigene Farbe:
{"dl":[x0,y0,x1,y1,"#RRGGBB"]} für Linien, {"dp":[x,y,"#RRGGBB"]} für Punkte.
So liegen Regen, Sonne und Raster in einem Bild. Die Zeichenreihenfolge
entscheidet, was oben liegt.
Bei autoscale: false sind die Werte Pixelzeilen, nicht Prozent: 0 ist die
unterste Zeile, 7 die oberste. Mit autoscale: true skaliert die Uhr auf das
Maximum der jeweiligen Reihe — dann sieht ein Nieselregen aus wie ein
Wolkenbruch, weshalb hier selbst normiert wird.
iobroker state set verschluckt Werte mit führendem Minuszeichen. Der
Argument-Parser hält sie für eine Option und schreibt das nächste Argument in
den Datenpunkt — aus
iobroker state set 0_userdata.0.awtrix.temp_text "-22/-24°" true
wurde The state … was set to "true" with flag ack=false. Ohne Fehlermeldung,
mit Erfolgsmeldung. Wer danach nur die Anzeige prüft, sucht an der falschen
Stelle. Sicher geht es über simple-api mit URL-kodiertem Wert:
curl "http://localhost:8087/set/<id>?value=-22%2F-24%C2%B0&ack=true"
Der Adapter legt eine Custom-App nur an, wenn sich ihr Quellwert ÄNDERT.
Nach jedem Adapter-Neustart (und den löst schon jede Änderung an native.* aus)
fehlt deshalb jede App, deren Wert gerade gleich bleibt — ohne Fehlermeldung,
ohne Logzeile. Am 29.08.2026 gemessen:
Wert unverändert erneut schreiben → {"Time":0,"temperatur":1,"sonne":2}
Wert geändert schreiben → {"Time":0,"temperatur":1,"regenwahrcheinlichkeit":2,"sonne":3}
Die Temperatur-App überlebte damals nur, weil sich ihr Wert zufällig gerade
änderte. Das Skript ruft deshalb appsErzwingen() beim Start und bei jeder
Verbindungsmeldung des Adapters (awtrix-light.0.info.connection) auf: Es
schaltet die betroffenen Apps über visible aus und wieder ein.
Das erzwungene „wieder ein" kennt keinen Inhalt. Seit regenwahrcheinlichkeit
sich bei "0% 0mm" selbst ausblendet (siehe oben), darf appsErzwingen() diese
App nicht mehr blind wieder einschalten — sonst stünde sie nach jedem
Adapter-Neustart falsch da, im schlimmsten Fall dauerhaft: Ein erster Versuch
rief nach dem blinden „ein" sofort aktualisieren() hinterher, aber beide
Schreibzugriffe laufen asynchron und liefen sich gegenseitig den Rang ab —
beobachtet am 2026-09-05 nach dem Eintragen der wind-App in
native.expertApps (das startet den Adapter neu): apps.regenwahrcheinlichkeit. visible blieb true stehen, obwohl der Text "0% 0mm" lautete, und zwar ohne
sich beim nächsten Takt zu korrigieren. Jetzt bekommt nur temperatur (die
einzige Text-App ohne eigene Sichtbarkeitsregel) das blinde „ein"; für
regenwahrcheinlichkeit entscheidet aktualisieren() danach ganz allein, ohne
Wettlauf um den letzten Schreibzugriff.
iobroker state set über ssh frisst die Anführungszeichen im JSON. Eine
Zeichenliste, die über eine geschachtelte Quotierung geschickt wird, kommt als
{draw:[{dt:[0,1,3h,#00A0FF]}]} an — ohne ein einziges ". Der Adapter meldet
dann Failed to parse base object, und man sucht den Fehler im JSON statt in
der Shell. Sicher geht es über eine Datei:
cat bild.json | ssh ds "... docker exec -i iobroker-1 tee /tmp/bild.json"
# und im Container:
iobroker state set <id> "$(cat /tmp/bild.json)" false
Kurze Apps erwischt man bei Stichproben kaum. Die Temperatur-App steht nur
zwei Sekunden, der Displayinhalt wird aber bloß alle zehn Sekunden vom Gerät
geholt. Statt viele Aufnahmen zu machen, die App gezielt über
apps.<name>.activate in den Vordergrund holen.
Interpolation hält außerhalb den Randwert fest — und das sieht nie nach Fehler
aus. Eine Kurve, die über den Rand ihrer Stützstellen hinausgezeichnet wird,
setzt dort stillschweigend den letzten bekannten Wert fort. Das erzeugt ein
plausibles, vollständiges Bild aus Daten, die es nicht gibt. Hier hat es den
ganzen Vormittag mit der Bewölkung des Nachmittags gefüllt. Wo eine Reihe endet,
gehört null hin, nicht ihr Randwert.
Ein Datenpunkt kann frisch aussehen und trotzdem stillstehen. ts sagt nur,
wann der Adapter zuletzt geschrieben hat, lc wann sich der Wert zuletzt
geändert hat. Bei der Niederschlagswahrscheinlichkeit lagen dazwischen
16 Stunden.
Ein Schwellwert kann eine App unsichtbar machen, ohne je aufzufallen. Der
Schwellwert der Regenwahrscheinlichkeits-App trug das nicht existierende Icon
terain. Solange der gebundene Wert unter der Schwelle blieb, war das folgenlos
— beim Umbinden auf eine andere Einheit hätte er sofort gegriffen. Vor dem
Ändern einer objId gehören die Schwellwerte derselben App mit geprüft.
yr nennt den Niederschlag 1h_precipitation_amount, nicht precipitation.
Ein null bei einem einzelnen Feld heißt also nicht, dass der Adapter nichts
liefert — erst die Feldliste prüfen.
DWD-Warnstufen (aus dem Quelltext von iobroker.dwd):
1 Vorabinformation · 2 Wetterwarnung · 3 markantes Wetter · 4 Unwetter ·
5 extremes Unwetter. severity ist level - 1 (bzw. 0). Für die Anzeige ist
das Feld event aus warning.object brauchbarer als headline:
"WINDBÖEN" statt "Amtliche WARNUNG vor WINDBÖEN".
Displayhelligkeit
Die eingebaute Automatik ist abgeschaltet; die Helligkeit setzt das Skript.
Grund ist der Lichtsensor der Uhr: Er reagiert zwar (gemessen am 2026-08-29,
Rollo geöffnet: 0 → 4 → 19), liefert aber keine echten Lux, sondern Werte
zwischen 0 und 19. Die Automatik hielt die Anzeige deshalb den ganzen Tag auf
Stufe 2 — abends angenehm, tagsüber unlesbar. brightness folgte dem
Sensorwert dabei überhaupt nicht.
Stattdessen folgt die Helligkeit der erwarteten Strahlung, die für den Sonnentag ohnehin berechnet wird. Sie enthält Sonnenstand und Bewölkung, ein trüber Tag fällt also von selbst dunkler aus als ein klarer.
HELL_NACHT = 2 |
ohne Sonne — vom Nutzer als angenehm bestätigt |
HELL_TAG = 40 |
heller Raum — vom Nutzer gewählt |
HELL_VOLL_AB = 250 W/m² |
ab hier volle Helligkeit, darunter gleitend |
HELL_TAG stand zuerst auf 80. Am 29.08.2026 blendete die Anzeige damit
(gemessen: Stufe 44 am späten Nachmittag), der Wert wurde auf 40 halbiert.
Als Strahlung zählt der Open-Meteo-Wert der laufenden Stunde, nicht mehr die eigene Rechnung aus Sonnenstand und yr-Bewölkung. Dieselbe Zahl trägt den Sonnenbogen — Helligkeit, Textfarbe und Kurve können also nicht auseinander laufen. Fehlt der Abruf, bleibt die alte Rechnung als Rückfall.
Im Winter wird die volle Helligkeit nie erreicht — am 21. Dezember erreicht
selbst ein klarer Mittag nur 199 W/m² und damit Stufe 32; ein trüber Dezembertag
bleibt deutlich darunter. Das ist gewollt, weil es im Zimmer dann tatsächlich
dunkler ist. Reicht es zum Ablesen nicht, ist HELL_VOLL_AB die Stellschraube:
Auf 180 gesenkt, erreicht auch ein Wintermittag die volle Stufe.
Textfarbe
Wandert mit derselben Strahlung von #320000 (nachts) zu #B50000 (heller
Raum), kanalweise interpoliert.
Gesetzt wird die globale settings.textColor, denn Custom-Apps haben keinen
Datenpunkt für ihre Farbe — die steht in der Adapter-Konfiguration, und jede
Änderung dort startet den Adapter neu. Damit die Temperatur mitwandert, wurde
ihre eigene Farbe (#360000) geleert; sie folgt jetzt der globalen, ebenso
Uhrzeit, Datum und Regenwahrscheinlichkeit.
Die Apps regen (blau) und warnung (rot) behalten ihre eigenen Farben — dort
ist die Farbe Bedeutungsträger, nicht Helligkeitsregelung.
Reihenfolge und Standzeiten
Vom Nutzer so gewählt (2026-08-29, Wind eingefügt 2026-09-05 direkt nach dem Niederschlag):
| Seite | Typ | Dauer | wo eingestellt | erscheint |
|---|---|---|---|---|
| Uhrzeit | nativ | 5 s | UHR_DAUER → settings.appDisplayTime |
immer |
| Temperatur | customApp | 2,5 s | native.customApps |
immer |
| Niederschlag 12 h | customApp | 2,5 s | native.customApps |
ausser bei "0% 0mm" |
| DWD-Warnung | customApp | 2,5 s | native.customApps |
Warnung liegt an |
| Wind | expertApp | 2,5 s | WIND_DAUER im Skript |
immer, sobald ein Live-Messwert da ist |
| Sonnentag | expertApp | 2,5 s | SONNE_DAUER im Skript |
zwischen Auf- und Untergang |
| Regenvorschau | expertApp | 2,5 s | VORSCHAU_DAUER im Skript |
Regen in 24 h oder in 3 h |
Die Reihenfolge kommt nicht vom position-Feld in
native.customApps/.expertApps, obwohl native.customPositions = true
gesetzt ist — eine reine Positionsänderung blieb am Gerät wirkungslos
(2026-09-05: wind stand mit position: 4 trotzdem hinter sonne mit
position: 6). Im Adapter-Quellcode nachgesehen und am Gerät über
curl http://<ip>/api/loop bestätigt: Der Adapter registriert beim Start
erst alle native.customApps (in Array-Reihenfolge), danach alle
native.expertApps (ebenso) — customApps stehen also strukturell immer vor
expertApps, unabhängig vom position-Wert. Wind, Sonnentag und Regenvorschau
müssen expertApps sein, weil sie eigene Pixel über baseObject zeichnen;
Temperatur, Niederschlag und Warnung sind reine Textvorlagen (customApps).
Die Warnung lässt sich deshalb nicht hinter Wind schieben, ohne den App-Typ
zu wechseln — erreichbar und umgesetzt war stattdessen: Wind als erste
expertApp direkt hinter dem customApps-Block, vor den beiden Kurven-Apps
(Array-Reihenfolge in native.expertApps auf wind, sonne, regenvorschau
gesetzt, Adapterinstanz neu gestartet).
Nachkommastellen versteht die Firmware. Der Adapter reicht duration
ungerundet durch, und die Uhr setzt es um — nachgemessen, nicht angenommen.
Der frühere Regen-Countdown als eigene App ist entfallen — er steht jetzt links in der Regenvorschau.
Die beiden Kurven-Apps bekommen ihre Standzeit vom Skript gesetzt — aber nur, solange sie sichtbar sind. Ändert man die Konstante, während die App gerade ausgeblendet ist, steht dort noch der alte Wert; Standzeit und Bild werden aber in demselben Lauf gesetzt, in dem die App wieder erscheint, also greift der neue Wert rechtzeitig.
Standzeiten am Gerät messen
/api/stats der Uhr meldet die gerade laufende App. Wer das eng abtastet,
bekommt die tatsächlichen Standzeiten — die Konfiguration sagt sie nicht:
# im Container, misst 60 Sekunden lang
python3 -c '
import urllib.request, time, json
letzte=start=None
t0=time.time()
while time.time()-t0 < 60:
a=json.load(urllib.request.urlopen("http://<uhr-ip>/api/stats", timeout=2))["app"]
t=time.time()
if a!=letzte:
if letzte: print("%-24s %.2f s" % (letzte, t-start))
letzte, start = a, t
time.sleep(0.15)'
Messung vom 30.08.2026 nach dem Umstellen auf 2,5 s:
| Seite | gemessen | Mittel |
|---|---|---|
| Uhrzeit | 5,74 · 5,84 · 5,83 · 5,72 | 5,78 s |
| Temperatur | 2,45 · 2,46 · 2,46 · 2,66 | 2,51 s |
| Niederschlag | 2,66 · 2,66 · 2,56 | 2,63 s |
| Sonnentag | 2,46 · 2,46 · 2,57 | 2,50 s |
| Regenvorschau | 2,66 · 2,67 · 2,66 | 2,67 s |
Umlauf über alle sichtbaren Seiten: 15,8 s.
Zwei Dinge fallen daran auf. Die Messung selbst trägt rund 0,15 s Aufschlag
(Abtastintervall plus HTTP-Antwortzeit), das erklärt die 2,5 → 2,6. Die Uhrzeit
liegt mit 5,78 s aber deutlicher über ihren 5 s — der Grund ist nicht geklärt;
möglicherweise zählt der Übergangseffekt bei nativen Apps anders. Wer exakt 5 s
will, stellt UHR_DAUER entsprechend niedriger.
Wie die Reihenfolge zustande kommt
Ohne native.customPositions = true nummeriert der Adapter bei jedem Start
neu durch — erst die customApps in Konfigurationsreihenfolge, dann
historyApps, dann expertApps. Die Positionen aus der Konfiguration werden
dabei überschrieben.
Mit eingeschalteter Option sendet der Adapter das Feld pos je App an das
Gerät. Für die nativen Apps (Time, Date, Temperature, Humidity,
Battery) sendet er keine Position — die Uhr ordnet sie selbst ein.
Das hat eine Folge, die sich nur am Gerät zeigt: Mit den Positionen 1…6 landete
Time auf Loop-Index 2, also mitten zwischen den eigenen Apps. Erst mit
Positionen ab 2 (die 0 und 1 bleiben frei) rutschte die Uhr nach vorn:
curl -s http://<uhr-ip>/api/loop
# {"Time":0,"temperatur":1,"regenwahrcheinlichkeit":2,"sonne":3}
/api/loop ist die einzige verlässliche Auskunft darüber, was die Uhr wirklich
tut — die Konfiguration sagt es nicht. Nach jeder Änderung an der Reihenfolge
dort nachsehen, und ein paar Sekunden warten: Direkt nach dem Adapter-Neustart
meldet die Uhr nur {"Time":0}, weil die anderen Apps noch nicht registriert
sind.
Bei der DWD-Warnung ist die Standzeit kein reiner Geschmack: Ist der Warntext länger als das Display, scrollt er. Zu kurz gewählt, sieht man nur die ersten Buchstaben und weiß nicht, wovor gewarnt wird.
Umgebung
| ioBroker | Docker-Container iobroker-1 auf der DS1621, js-controller 7.2.2 |
| Adapter | awtrix-light 3.0.0, openweathermap 1.4.0, dwd 2.8.5, weatherunderground, yr, javascript 9.0.18 |
| Gerät | Ulanzi TC001 unter 10.47.11.35, AWTRIX-Firmware 0.98, 32 x 8 Pixel |
| externer Dienst | api.open-meteo.com, ohne Schlüssel, direkt aus dem Skript, 48 Aufrufe/Tag |
Der HTTP-Abruf läuft über httpGetAsync() des javascript-Adapters (ab 9.x
vorhanden) — kein zusätzliches Modul nötig. Schlägt er fehl, protokolliert das
Skript eine Warnung und arbeitet mit dem Zwischenspeicher weiter.
Stand
Eingerichtet und im Betrieb geprüft am 2026-08-28, erweitert am 2026-08-29 um Vorschaukurven, Raster und das Temperaturformat.
Am 2026-08-29 abends nachgebessert (alles am Gerät bzw. am erzeugten
draw-Objekt nachgemessen, nicht aus dem Quelltext abgeleitet):
- Niederschlagswahrscheinlichkeit hing an der falschen Größe (gefallener Regen in mm statt Wahrscheinlichkeit in %) — umgebunden, Schwellwert-Falle entschärft
- Tageshelligkeit von 80 auf 40 halbiert
- Vergangenes im Sonnentag auf 45 % abgedunkelt (0,75 war am Gerät nicht zu unterscheiden)
- Sonnenkurve holt den vergangenen Tag jetzt von Open-Meteo, statt ihn aus der aktuellen Bewölkung zu erfinden. Gegenprobe am selben Tag: erwartetes Maximum 571 statt 343 W/m², das Log meldet „32/32 Spalten aus Open-Meteo".
- Die Sonnenkurve darf über den Bogen hinaus, statt darauf gekappt zu werden
- Strahlung im Viertelstundentakt statt stündlich — das Plateau zwischen 11 und 16 Uhr war Rundung, nicht Wetter
- Open-Meteos Zeitstempel meinen das Intervall-Ende; die Werte liegen jetzt auf der Intervallmitte statt eine halbe Intervalllänge zu spät
- Abrufsperre auf 30 Minuten und auch nach Fehlschlägen wirksam — 48 Aufrufe am Tag, 0,5 % des Open-Meteo-Limits
- Regen-Countdown und Regenkurve zu einer Seite zusammengefasst: links der
Countdown über
dt, rechts der 3-Stunden-Graph mit drei senkrechten Gitterlinien. Keine Nulllinie mehr durch trockene Stunden - Regen ebenfalls im Viertelstundentakt, mit Umrechnung der Viertelstundensumme auf mm/h; je Spalte wird aggregiert statt abgetastet (Strahlung gemittelt, Regen als Höchstwert). Geprüft gegen den echten Regentag 28.08.2026
- Temperatur vollständig aus Open-Meteo: Tagesextreme aus
daily, der aktuelle Wert auscurrentund zwischen den Abrufen mit der Stundensteigung fortgeschrieben.openweathermap.day0.temperatureMaxwar das Maximum der noch verbleibenden 3-Stunden-Perioden und schrumpfte im Lauf des Tages - Niederschlags-App auf ein rollendes 12-Stunden-Fenster umgestellt und zeigt
jetzt Wahrscheinlichkeit und Menge (
12% 17mm) statt einer Tageszahl, die die schon vergangene Nacht enthielt Der Regen-Countdown wurde am Display nachgemessen (3h→2h), die Warn-App mit einem gesetzten Testwert (STURMBOEEN, scrollend in Rot). Mit einer echten DWD-Warnung ist sie noch nicht gelaufen — währenddessen lag keine vor.