Portrait Michael Malura

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.

Stromverbrauch Dashboard

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.

Flugzeugkarte mit Live-Positionen

Empfangsrichtung Windrose

Flugaktivität nach Tageszeit

Flugzeuge pro Stunde

Learnings: Was nicht funktionierte

  • Barchart-Paneltyp crasht in Grafana 13 mit Zeitreihen -> timeseries mit drawStyle: "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.

16.04.2026 aktualisiert 16.04.2026