Grafana Dashboards aufgeteilt und ADS-B Windrose gebaut
Mein Grafana-Dashboard war eine einzige riesige Seite mit allem drin: Server-Stats, Fritzbox, Heizung, Strom, PV - alles in collapsed Rows. Das war nervig zu navigieren. Also hab ich einen Nachmittag investiert und das Ding in 5 spezialisierte Dashboards aufgeteilt. Dabei ist mir aufgefallen dass die Brennerstarts meiner Heizung verdächtig hoch sind, aber das ist eine andere Geschichte.
Dashboard-Umbau
Das monolithische Dashboard war unübersichtlich geworden. Wenn ich nach Heizungs-Daten suchen wollte musste ich erstmal 5 collapsed Rows aufklappen und scrollen. Also hab ich mir einen Nachmittag Zeit genommen und das Ganze in thematische Dashboards aufgeteilt.
Rausgekommen sind 5 Dashboards: Vault Server mit CPU, RAM, ZFS und Disk I/O. Internet & Fritzbox mit WAN Throughput und WLAN Clients. Heizung mit Kessel, Zigbee Thermometern und Öl-Verbrauch. Stromverbrauch mit Grid und Tagesbilanz. PV Anlage mit Einspeisung und Strang-Daten. Einige Panels hab ich cross-referenced, Stromverbrauch ist sowohl im Heizungs- als auch im PV-Dashboard relevant.
Grafana läuft als Docker-Container hinter Traefik auf grafana.malura.org. Das Umbau-Scripting hab ich über die Grafana HTTP API gemacht - Service Account Token erstellt, Dashboard-JSON via curl hochgeladen. Spart Zeit wenn man viele Dashboards umzieht.

ADS-B Windrose: Empfangsrichtung visualisieren
Mein ADS-B Feeder läuft seit ein paar Tagen und loggt Flugzeug-Daten in InfluxDB. Das Dashboard hatte schon Flugzeug-Tracking, aber ich wollte sehen aus welcher Richtung ich die meisten Signale empfange. Das operato-windrose-panel Plugin braucht exakt die Felder wind_direction und wind_speed. Blöd nur dass das track-Feld die Flugrichtung zeigt, nicht die Empfangsrichtung.
Also hab ich die Bearing-Berechnung direkt in meinen Python-Logger (adsb_influx.py) eingebaut:
from math import radians, degrees, atan2, sin, cos
def calculate_bearing(lat1, lon1, lat2, lon2):
dLon = radians(lon2 - lon1)
lat1, lat2 = radians(lat1), radians(lat2)
x = sin(dLon) * cos(lat2)
y = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(dLon)
bearing = degrees(atan2(x, y))
return (bearing + 360) % 360
# Bad Schussenried: 47.993°N, 9.671°E
receiver_lat, receiver_lon = 47.993, 9.671
bearing = calculate_bearing(receiver_lat, receiver_lon, aircraft_lat, aircraft_lon)
Neue InfluxDB-Felder: bearing (0-360°) und distance_km. Query für die Windrose:
SELECT "bearing" AS "wind_direction", "distance_km" AS "wind_speed"
FROM "adsb_aircraft"
WHERE $timeFilter
Funktioniert. Die meisten Flugzeuge kommen aus Süd-West.




Learnings: Was nicht funktionierte
- Barchart-Paneltyp crasht in Grafana 13 mit Zeitreihen ->
timeseriesmitdrawStyle: "bars"verwenden - Plotly-Plugin (
ae3e-plotly-panel) hat kryptische Syntax -> native Panels sind robuster - Dynamic Text Plugin crasht bei Handlebars mit Umlauten -> Stat-Panels im Grid-Layout nutzen
filterByValue-Transformation filterte manchmal ALLE Daten weg -> Split-Queries als Alternative
Am Ende hab ich jetzt 5 Dashboards statt 1, ADS-B mit Windrose, und ein paar Plugin-Enttäuschungen. Dafür läuft jetzt alles sauber über die API und ist reproduzierbar.