Ein rundes Status-Display fürs Smart Home

ESP32-C3, GC9A01 und ein bisschen LVGL-Magie

Ich wollte schon länger ein kleines, rundes Status-Display für mein Smart Home haben – nicht nur „irgendein“ Bildschirm, sondern etwas, das wie ein Instrument aus einem Sci-Fi-Cockpit aussieht: kompakt, rund, immer an der Wand, immer im Blick.

Am Ende ist es eine Kombi geworden aus:

  • einem ESP32-C3 Board mit einem 1,28" runden TFT-Display mit GC9A01-Treiber (z.B. diese Module von AliExpress)
  • ESPHome mit LVGL

Das Ergebnis:
Eine kleine runde Anzeige, die im Wechsel Innenklima und Strahlungswerte zeigt – und bei schlechtem CO₂-Wert automatisch auf eine Warnseite schaltet.

Hardware: Was steckt drin?

Für den Aufbau habe ich verwendet:

  • ESP32-C3 Devkit (oder Super Mini, Hauptsache ESP32-C3) inkl rundem 1,28" TFT-Display (240×240, GC9A01)
    – solche Displays bekommt man z.B. auf AliExpress in der Kategorie „Round TFT GC9A01“ oder ähnlich.
  • USB-C Kabel zum Flashen
  • Optional: ein 3D-gedrucktes Gehäuse oder ein bisschen Heißkleber-Magie

Die Verbindung läuft per SPI + I²C:

  • GC9A01-Display per SPI
  • Touchcontroller (CST816) per I²C (ist nicht in jedem Modell enthalten - also aufpassen)
  • Alles mit 3,3 V versorgt, direkt vom ESP32-Board (aber das seht ihr eh nicht)

Die Zuordnung der Pins (vereinfacht):

Display:
MOSI (SDA) → GPIO7
SCL (CLK) → GPIO6
DC → GPIO2
CS → GPIO10
RST → GPIO1
BL (Backlight) → GPIO3 (PWM)

Touch:
SDA → GPIO4
SCL → GPIO5
INT → GPIO0

(Das lässt sich in den substitutions: im ESPHome-YAML später sehr einfach anpassen.)

Software: ESPHome + LVGL

Die komplette Konfiguration läuft über ESPHome.
Wichtig sind im Prinzip vier Bausteine:

  1. WiFi & API – ganz normaler ESPHome-Node
  2. Display & Touchili9xxx mit GC9A01A und cst816
  3. LVGL-Pages – die einzelnen „Screens“ für Umwelt, Strahlung und CO₂-Alarm
  4. Sensoren aus Home Assistant – Temperatur, Luftfeuchte, CO₂, Strahlung

Fonts & Icons

Fürs Layout verwende ich:

  • Roboto aus Google Fonts für Texte
  • Font Awesome Solid als OTF für Icons (Thermometer, Tropfen, Radioaktiv, CO₂)

Im YAML sieht das so aus:

font:
  - file: "gfonts://Roboto"
    id: roboto_big
    size: 32
    glyphsets:
      - GF_Latin_Kernel

  - file: "gfonts://Roboto"
    id: roboto_small
    size: 14
    glyphsets:
      - GF_Latin_Kernel

  - file: "fonts/Awesome-Free-Solid-900.otf"
    id: fa_solid_small
    size: 38
    glyphs:
      - "\uF2C7"  # Thermometer
      - "\uF043"  # Tropfen

  - file: "fonts/Awesome-Free-Solid-900.otf"
    id: fa_solid_big
    size: 48
    glyphs:
      - "\uF1E2"  # Radioaktiv
      - "\uF4D8"  # CO2 / Pflänzchen

Die kleinen Icons (fa_solid_small) verwende ich auf der Klima-Seite, die großen (fa_solid_big) für Strahlung und CO₂-Warnung.

Die Seiten: Was das Display anzeigt

Ich habe drei „logische“ Seiten gebaut, zwischen denen automatisch gewechselt wird.

Seite 0 – „Umgebung“


Die Standardansicht zeigt:

  • Innen-Temperatur (z.B. vom Xiaomi-Sensor im Wohnzimmer)
  • Innen-Luftfeuchte
  • klein darunter die Außentemperatur aus Home Assistant (weather.schleswig)
  • Dazu passende Icons (Thermometer / Tropfen)

LVGL-Layout (gekürzt):

lvgl:
  top_layer:
    widgets:
      - label:
          id: time_widget
          align: CENTER
          y: 95
          text_font: roboto_big
          text: "--:--:--"

      - label:
          id: date_widget
          align: CENTER
          y: 75
          text_font: roboto_small
          text: "--- --.--"

  pages:
    - id: env_page
      widgets:
        - label:
            id: temp_icon
            align: CENTER
            y: -80
            text: "\uF2C7"       # Thermometer
            text_font: fa_solid_small

        - label:
            id: temp_indoor_value
            align: CENTER
            y: -40
            text_font: roboto_big
            text: "-- °C"

        - label:
            id: humidity_icon
            align: CENTER
            y: -10
            text: "\uF043"       # Tropfen
            text_font: fa_solid_small

        - label:
            id: humidity_value
            align: CENTER
            y: 30
            text_font: roboto_big
            text: "--%"

        - label:
            id: weather_value
            align: CENTER
            y: 55
            text_font: roboto_small
            text: "-- °C"

Die Werte werden per on_value aus Home Assistant aktualisiert – z.B.:

- platform: homeassistant
  id: temperatur_wz
  entity_id: sensor.temperatur_xiaomi_wz_temperature
  unit_of_measurement: "°C"
  accuracy_decimals: 1
  on_value:
    then:
      - lvgl.label.update:
          id: temp_indoor_value
          text: !lambda |-
            static char str[16];
            float v = id(temperatur_wz).state;
            if (isnan(v)) {
              snprintf(str, sizeof(str), "-- °C");
            } else {
              snprintf(str, sizeof(str), "%.1f °C", v);
            }
            return str;

Seite 1 – Strahlung

Die zweite Seite ist ganz der Strahlungsleistung gewidmet – in meinem Fall vom selbstgebauten Geigerzähler, der als Sensor in Home Assistant hängt (sensor.geiger01_strahlungsleistung).

Layout:

  • Oben ein großes Radioaktiv-Icon
  • Darunter der Wert mit vier Nachkommastellen
  • Und direkt darunter die Einheit uSv/h (im selben Label per Zeilenumbruch)
- platform: homeassistant
  id: strahlung
  entity_id: sensor.geiger01_strahlungsleistung
  unit_of_measurement: "uSv/h"
  accuracy_decimals: 4
  on_value:
    then:
      - lvgl.label.update:
          id: radiation_value
          text: !lambda |-
            static char str[48];
            float v = id(strahlung).state;
            if (isnan(v)) {
              snprintf(str, sizeof(str), "--\nuSv/h");
            } else {
              snprintf(str, sizeof(str), "%.4f\nuSv/h", v);
            }
            return str;

Und dazu die LVGL-Seite:

- id: radiation_page
  widgets:
    - label:
        id: radiation_icon
        align: CENTER
        y: -60
        text: "\uF1E2"       # Radioaktiv
        text_font: fa_solid_big

    - label:
        id: radiation_value
        align: CENTER
        y: 5
        text_font: roboto_big
        text: "--"

Seite 2 – CO₂-Alarm

Die dritte Seite ist kein „normales“ Dashboard, sondern ein Alarmbildschirm:

  • Großes CO₂-/Pflänzchen-Icon
  • CO₂-Wert in ppm
  • Darunter der Hinweis „CO2 HOCH!“

Sobald der CO₂-Wert in Home Assistant einen gewissen Schwellenwert überschreitet, wird diese Seite bevorzugt angezeigt.

Logik:

globals:
  - id: co2_alert_active
    type: bool
    restore_value: no
    initial_value: 'false'

# im CO2-Sensor:
- lambda: |-
    float v = id(co2_konzentration).state;
    if (!isnan(v) && v > 1000.0f) {
      id(co2_alert_active) = true;
    } else {
      id(co2_alert_active) = false;
    }

Und der automatische Seitenwechsel:

interval:
  - interval: 10s
    then:
      - if:
          condition:
            lambda: "return id(co2_alert_active);"
          then:
            # CO2-Alarm: immer Seite 2
            - lambda: |-
                id(watchface_lvgl).show_page(2, LV_SCR_LOAD_ANIM_MOVE_LEFT, 300);
          else:
            # Normalmodus: abwechselnd Seite 0 und 1
            - lambda: |-
                static bool show_rad = false;
                show_rad = !show_rad;
                if (show_rad) {
                  id(watchface_lvgl).show_page(1, LV_SCR_LOAD_ANIM_MOVE_LEFT, 300);
                } else {
                  id(watchface_lvgl).show_page(0, LV_SCR_LOAD_ANIM_MOVE_LEFT, 300);
                }

WiFi-Tuning für den ESP32-C3

Der ESP32-C3 ist etwas zickig, wenn das WLAN nicht perfekt ist.
Bei mir hat folgendes geholfen:

wifi:
  networks:
    - ssid: !secret wifi_ssid
      password: !secret wifi_password
    - ssid: !secret wifi_ssid2
      password: !secret wifi_password2
    - ssid: !secret wifi_ssid3
      password: !secret wifi_password3
    - ssid: !secret wifi_ssid4
      password: !secret wifi_password4
  fast_connect: true
  power_save_mode: NONE
  output_power: 17dB

Fazit

Unterm Strich ist dieses kleine runde Display genau das, was ich wollte:

  • Immer sichtbare Infos zu Temperatur, Luftfeuchte und Außenwetter
  • Eine separate Strahlungsseite mit feiner Auflösung
  • Ein CO₂-Warnbildschirm, der sich automatisch meldet, wenn die Luft schlecht wird
  • Und das Ganze in einem kleinen runden Formfaktor, der eher nach Gadget aussieht als nach Bastelprojekt
0:00
/0:11

Der Charme: das meiste passiert in ESPHome-YAML.

Wenn Home Assistant schon läuft, ist der Rest nur noch ein bisschen LVGL-Gefrickel – und plötzlich hängt ein kleines „Science-Fiction-Instrument“ an der Wand.

Wenn Du direkt über neue Posts informiert werden, willst: einfach per Mail abbonieren