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:
- WiFi & API – ganz normaler ESPHome-Node
- Display & Touch –
ili9xxxmitGC9A01Aundcst816 - LVGL-Pages – die einzelnen „Screens“ für Umwelt, Strahlung und CO₂-Alarm
- 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
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.