Waveshare ESP32-S3 AMOLED: Trasformiamolo in una Mini Dashboard Domotica con ESPHome e LVGL
di Vincenzo Caputo
15 Febbraio 2026
Home Assistant Guide
Vi ricordate il piccolo gioiello della Waveshare di cui abbiamo parlato qualche tempo fa? Sì, parlo del display circolare ESP32-S3 Touch AMOLED da 1.75 pollici. Nel precedente articolo avevamo visto come configurarlo per visualizzare dati statici usando la grafica vettoriale. Un ottimo risultato estetico, ma limitato.
Il dispositivo utilizzati lo potete acquistare su Amazon al seguenti link:
Oppure sul sito del produttore:
https://www.waveshare.com/product/arduino/displays/amoled/esp32-s3-touch-amoled-1.75.htm
Oggi alziamo l'asticella. E di parecchio.
Mi sono chiesto: perché usare questo dispositivo solo come visualizzatore passivo, quando ha un touchscreen capacitivo e un processore dual-core che scalpita? L'idea è quella di trasformare questo piccolo display in una vera e propria Dashboard Domotica da parete, un'alternativa compatta, elegante (e dai consumi irrisori) ai classici tablet che spesso appendiamo in giro per casa.
Tablet Android vs. Microcontrollore: Perché complicarsi la vita?
Qualcuno potrebbe chiedersi: “Ma perché scrivere centinaia di righe di codice YAML per un display da 1.75 pollici (o magari per uno più grande da 7 o 10 pollici) quando posso comprare un tablet Android economico da appendere al muro?”
La domanda è legittima, ma la risposta sta tutta in una parola: Affidabilità.
Chi usa un tablet come dashboard conosce bene i problemi cronici: la batteria che si gonfia se lasciata sempre in carica, l'app di Home Assistant che si chiude in background, gli aggiornamenti di Android che rallentano il dispositivo nel tempo, le notifiche indesiderate e la necessità di configurare app esterne (come Fully Kiosk Browser) per bloccare l'interfaccia. Un tablet è un dispositivo generalista forzato a fare un lavoro specifico.
Un dispositivo basato su ESP32, invece, è un "Elettrodomestico".
Manutenzione Zero: Una volta flashato, il software non cambia. Non ci sono aggiornamenti del sistema operativo che "rompono" le cose il venerdì sera.
Boot Istantaneo: Se va via la corrente, al riavvio la dashboard è operativa in meno di 2 secondi.
Sicurezza e Privacy: Niente account Google, niente tracciamento, niente app in background. Fa solo quello per cui è stato programmato.
Reattività: L'interfaccia non deve passare attraverso i layer di Android. Il tocco dialoga quasi direttamente con l'hardware.
I Contro? Ovviamente ci sono. Perderete la possibilità di usare il dispositivo per altro (niente YouTube mentre cucinate, niente videochiamate) e la configurazione iniziale è decisamente più ostica del "scarica l'app e fai login". Inoltre, gestire grafiche complesse su schermi molto grandi (oltre i 7-10 pollici) con un ESP32 richiede molta ottimizzazione del codice per evitare scatti, cosa che un processore mobile gestisce ad occhi chiusi.
Da Grafica Vettoriale a LVGL: Il Salto di Qualità
Nel progetto precedente usavamo primitive grafiche (linee, cerchi) disegnate direttamente sul display. Funziona, ma creare pulsanti interattivi, slider o menu multipagina è un incubo di programmazione.
Per questo progetto siamo passati a LVGL (Light and Versatile Graphics Library) integrato in ESPHome. LVGL è un motore grafico che permette di creare interfacce "simil-smartphone": pulsanti che cambiano colore quando li premi, ghiere che ruotano al tocco, transizioni tra pagine.
Tuttavia, non è stato tutto rose e fiori. Gestire un display AMOLED 466x466 pixel richiede molta memoria e potenza. Durante i test abbiamo affrontato:
Lag nello scrolling: Le animazioni a trascinamento (swipe) erano troppo pesanti per il bus SPI, creando rallentamenti.
Artefatti grafici: Linee bianche o "tagli" sui bordi dovuti all'arrotondamento software del display circolare.
La soluzione? Abbiamo ottimizzato il codice eliminando le animazioni superflue e creando un sistema di navigazione a pulsanti "istantaneo" che carica le schermate dalla PSRAM in un lampo. Abbiamo inoltre forzato lo sfondo nero "puro" (spegnendo i pixel dell'AMOLED) per integrare perfettamente l'interfaccia con la cornice hardware, eliminando ogni difetto visivo.
Tuttavia, per chi cerca una soluzione "Set and Forget" (installalo e dimenticatene), la strada del microcontrollore è, a mio avviso, la vincente.
Cosa Abbiamo Realizzato: 3 Schermate per il Controllo Totale
Il firmware che vi propongo oggi trasforma il Waveshare in un controller a 3 pagine:
Termostato "Nest Style": Una ghiera arancione e grigia reattiva al tocco per impostare la temperatura, con visualizzazione chiara di setpoint e temperatura attuale.
Controllo Luci: Una pagina dedicata con pulsanti grandi e comodi per gestire tre punti luce (nel mio caso due canali di uno Shelly 2.5 e uno Shelly Wave).
Gestione Tapparelle: Pulsanti "Heavy Duty" per alzare, abbassare e fermare le tapparelle, con grafica ad alto contrasto.
Prima di Iniziare: I Pre-requisiti
Per replicare questo progetto non serve essere programmatori esperti, ma occorre avere l'ambiente pronto. Ecco la lista della spesa (e delle configurazioni):
L'Hardware: Ovviamente, il Waveshare ESP32-S3 Touch AMOLED 1.75". Se lo avete appena tolto dalla scatola e non sapete come collegarlo o flasharlo la prima volta, fate un salto alla mia guida precedente per il setup iniziale oppure guardate questo video su MissingTech per la prima attivazione di un ESP32
https://www.youtube.com/watch?v=xMgDSIG3vuM&t=4s
Home Assistant & ESPHome: Do per scontato che abbiate già un'istanza di Home Assistant operativa e l'Add-on di ESPHome installato e aggiornato all'ultima versione (questo è cruciale per il supporto LVGL e driver recenti).
Le Vostre Entità (La parte più importante!): Il codice che vedrete tra poco è un "abito su misura". Io l'ho cucito sulla mia casa, ma voi dovete adattarlo alla vostra. Prima di copiare il codice, andate negli Strumenti per Sviluppatori di Home Assistant e segnatevi gli Entity ID esatti di:
Un termostato (es. climate.sala, climate.corridoio).
Una tapparella motorizzata (es. cover.finestra_cucina).
Tre luci o interruttori (es. light.lampadario, switch.presa_smart).
Senza questi ID corretti, i pulsanti sul display non faranno nulla!
Connessione Internet in Compilazione: Nel codice usiamo i Google Fonts (gfonts://Montserrat) e delle componenti esterne per il driver del touch (external_components). Assicuratevi che il vostro Home Assistant sia connesso a internet durante la prima compilazione, perché dovrà scaricare queste librerie automaticamente.
Tutto pronto? Bene, passiamo alla parte divertente: il codice.
Il Codice YAML (Verificato e Pronto all'Uso)
Ecco il codice completo per ESPHome. È stato ripulito e testato. Nota: Ho inserito una sezione substitutions all'inizio. Vi basterà cambiare i nomi delle entità lì per adattare tutto il codice alla vostra casa senza dover cercare riga per riga.
substitutions:
name: "jarvis-face"
friendly_name: "Jarvis Face Display"
# --- PERSONALIZZA QUI LE TUE ENTITÀ ---
# Inserisci gli ID esatti dei tuoi dispositivi Home Assistant
climate_entity: "climate.hallway"
cover_entity: "cover.wave_shutter"
light_1: "switch.shellyswitch25_40f520016471_channel_1"
light_2: "switch.shellyswitch25_40f520016471_channel_2"
light_3: "switch.wave_2pm_2"
esphome:
name: "${name}"
friendly_name: "${friendly_name}"
platformio_options:
board_build.flash_mode: dio
board_build.f_flash: 80000000L
board_build.f_cpu: 240000000L
esp32:
variant: esp32s3
flash_size: 16MB
framework:
type: esp-idf
# La PSRAM è fondamentale per LVGL su display ad alta risoluzione
psram:
mode: octal
speed: 80MHz
# --- CONFIGURAZIONE WIFI ---
wifi:
ssid: "IL_TUO_SSID" # <--- Inserisci qui il tuo WiFi
password: "LA_TUA_PASSWORD" # <--- Inserisci qui la tua Password
api:
id: api_server
reboot_timeout: 0s
ota:
platform: esphome
logger:
level: INFO
# Importiamo i driver per il Touch CST9217 (non nativi in ESPHome)
external_components:
- source:
type: git
url: https://github.com/shelson/esphome-cst9217
i2c:
id: bus_a
sda: GPIO15
scl: GPIO14
scan: true
frequency: 400kHz
touchscreen:
- platform: cst9217
id: my_touch
interrupt_pin: GPIO11
reset_pin: GPIO40
transform:
swap_xy: false
mirror_x: true
mirror_y: true
spi:
- id: display_qspi
type: quad
clk_pin: GPIO38
data_pins: [GPIO4, GPIO5, GPIO6, GPIO7]
display:
- platform: mipi_spi
id: disp1
model: CO5300
bus_mode: quad
reset_pin: GPIO39
cs_pin: GPIO12
dimensions:
height: 466
width: 466
update_interval: 33ms # ~30 FPS
# Gestione luminosità display
light:
- platform: monochromatic
id: display_backlight
name: "Backlight"
output: backlight_brightness
restore_mode: ALWAYS_ON
output:
- platform: template
id: backlight_brightness
type: float
write_action:
then:
- lambda: |-
id(disp1).set_brightness(state*255);
# Caricamento Font da Google Fonts
font:
- file: "gfonts://Montserrat"
id: font_huge
size: 80
- file: "gfonts://Montserrat"
id: font_large
size: 40
- file: "gfonts://Montserrat"
id: font_medium
size: 26
- file: "gfonts://Montserrat"
id: font_small
size: 20
# Variabile globale per memorizzare il setpoint locale prima di inviarlo ad HA
globals:
- id: temp_target_buffer
type: float
initial_value: '20.0'
# =========================
# SENSORI & INTEGRAZIONE HA
# =========================
sensor:
# Legge la temperatura attuale dal clima di casa
- platform: homeassistant
id: ha_current_temp
entity_id: ${climate_entity}
attribute: current_temperature
on_value:
then:
- lambda: |-
if (isnan(x)) return;
std::string val = esphome::to_string(x).substr(0,4) + "°";
lv_label_set_text(id(lbl_curr_temp), val.c_str());
# Legge il setpoint attuale
- platform: homeassistant
id: ha_target_temp
entity_id: ${climate_entity}
attribute: temperature
on_value:
then:
- lambda: |-
if (isnan(x)) return;
int val = (int)(x * 10);
lv_arc_set_value(id(my_arc), val);
std::string txt = esphome::to_string(x).substr(0,4) + "°";
lv_label_set_text(id(lbl_target_temp), txt.c_str());
# Script per inviare la temperatura ad HA (con un piccolo delay per non floodare)
script:
- id: send_temp_to_ha
mode: restart
then:
- delay: 1s
- homeassistant.service:
service: climate.set_temperature
data:
entity_id: ${climate_entity}
temperature: !lambda 'return lv_arc_get_value(id(my_arc)) / 10.0;'
# =========================
# INTERFACCIA GRAFICA LVGL
# =========================
lvgl:
displays:
- disp1
touchscreens:
- my_touch
# Buffer al 20% è il "sweet spot" per evitare artefatti grafici su questo chip
buffer_size: 20%
# Definizione degli stili grafici riutilizzabili
style_definitions:
# --- STILI TERMOSTATO ---
- id: st_arc_track
arc_color: 0x1A1A1A # Grigio scuro (sfondo ghiera)
arc_width: 30
arc_rounded: true
- id: st_arc_indicator
arc_color: 0xFF4500 # Arancione (valore attivo)
arc_width: 30
arc_rounded: true
- id: st_text_huge
text_color: 0xFFFFFF
text_font: font_huge
# --- STILI BOTTONI ---
- id: st_nav_btn
bg_color: 0x222222
bg_opa: 80%
radius: 50%
text_color: 0x888888
text_font: font_medium
- id: st_btn_light
bg_color: 0x222222
radius: 15
border_width: 2
border_color: 0x444444
text_color: 0xFFFFFF
text_font: font_medium
- id: st_btn_shutter
bg_color: 0x222222
radius: 20
border_width: 0
text_color: 0x00D0FF # Ciano Jarvis
text_font: font_large
- id: st_btn_stop
bg_color: 0x880000 # Rosso
radius: 20
text_color: 0xFFFFFF
text_font: font_medium
pages:
# ---------------------------
# PAGINA 1: TERMOSTATO
# ---------------------------
- id: pg_thermostat
widgets:
- obj:
width: 466
height: 466
bg_color: 0x000000 # Nero assoluto per fondersi con la cornice
radius: 0
scrollbar_mode: "OFF"
widgets:
# Ghiera interattiva
- arc:
id: my_arc
width: 420
height: 420
align: CENTER
min_value: 100
max_value: 300
start_angle: 135
end_angle: 45
adjustable: true
styles: st_arc_track
indicator:
styles: st_arc_indicator
on_value:
- lambda: |-
int val_int = lv_arc_get_value(id(my_arc));
float temp = val_int / 10.0;
std::string txt = esphome::to_string(temp).substr(0,4) + "°";
lv_label_set_text(id(lbl_target_temp), txt.c_str());
- script.execute: send_temp_to_ha
# Etichette di testo
- label:
id: lbl_curr_temp
text: "--.-°"
align: CENTER
y: -20
styles: st_text_huge
- label:
text: "NEST THERMOSTAT"
align: CENTER
y: -75
text_font: font_small
text_color: 0xAAAAAA
- label:
id: lbl_target_temp
text: "--.-°"
align: CENTER
y: 70
text_font: font_medium
text_color: 0xFF4500
- label:
text: "SETPOINT"
align: CENTER
y: 100
text_font: font_small
text_color: 0xAAAAAA
# Navigazione -> Vai a Luci
- button:
width: 50
height: 50
align: RIGHT_MID
x: -65
styles: st_nav_btn
widgets:
- label:
text: ">"
align: CENTER
on_click:
- lvgl.page.show: pg_lights
# ---------------------------
# PAGINA 2: LUCI
# ---------------------------
- id: pg_lights
widgets:
- obj:
width: 466
height: 466
bg_color: 0x000000
radius: 0
widgets:
- label:
text: "LUCI SALA"
align: TOP_MID
y: 40
text_font: font_medium
text_color: 0xFFFF00
# Pulsanti Luci (Chiamano direttamente il servizio HA)
- button:
width: 220
height: 70
align: CENTER
y: -80
styles: st_btn_light
widgets:
- label:
text: "LUCE 1"
align: CENTER
on_click:
- homeassistant.service:
service: switch.toggle
data:
entity_id: ${light_1}
- button:
width: 220
height: 70
align: CENTER
y: 0
styles: st_btn_light
widgets:
- label:
text: "LUCE 2"
align: CENTER
on_click:
- homeassistant.service:
service: switch.toggle
data:
entity_id: ${light_2}
- button:
width: 220
height: 70
align: CENTER
y: 80
styles: st_btn_light
widgets:
- label:
text: "WAVE"
align: CENTER
on_click:
- homeassistant.service:
service: switch.toggle
data:
entity_id: ${light_3}
# Navigazione
- button:
width: 50
height: 50
align: LEFT_MID
x: 65
styles: st_nav_btn
widgets:
- label:
text: "<"
align: CENTER
on_click:
- lvgl.page.show: pg_thermostat
- button:
width: 50
height: 50
align: RIGHT_MID
x: -65
styles: st_nav_btn
widgets:
- label:
text: ">"
align: CENTER
on_click:
- lvgl.page.show: pg_shutter
# ---------------------------
# PAGINA 3: TAPPARELLA
# ---------------------------
- id: pg_shutter
widgets:
- obj:
width: 466
height: 466
bg_color: 0x000000
radius: 0
widgets:
- label:
text: "TAPPARELLA"
align: TOP_MID
y: 40
text_font: font_medium
text_color: 0x00D0FF
# Comandi Tapparella
- button:
width: 140
height: 90
align: CENTER
y: -100
styles: st_btn_shutter
widgets:
- label:
text: "▲"
align: CENTER
on_click:
- homeassistant.service:
service: cover.open_cover
data:
entity_id: ${cover_entity}
- button:
width: 140
height: 80
align: CENTER
y: 0
styles: st_btn_stop
widgets:
- label:
text: "STOP"
align: CENTER
on_click:
- homeassistant.service:
service: cover.stop_cover
data:
entity_id: ${cover_entity}
- button:
width: 140
height: 90
align: CENTER
y: 100
styles: st_btn_shutter
widgets:
- label:
text: "▼"
align: CENTER
on_click:
- homeassistant.service:
service: cover.close_cover
data:
entity_id: ${cover_entity}
# Navigazione -> Torna a Luci
- button:
width: 50
height: 50
align: LEFT_MID
x: 65
styles: st_nav_btn
widgets:
- label:
text: "<"
align: CENTER
on_click:
- lvgl.page.show: pg_lights
Considerazioni Finali
Questo progetto dimostra che anche con hardware "piccolo" si possono ottenere risultati professionali, a patto di ottimizzare bene il software. L'uso di dispositivi dedicati come questo Waveshare spesso supera i vecchi tablet Android riciclati in termini di affidabilità, estetica e soprattutto consumi.
Fatemi sapere nei commenti se proverete a replicarlo!
Restiamo Connessi
Se volete ricevere una notifica istantanea ogni volta che pubblico una nuova guida o un aggiornamento su questi progetti (e altri interessanti contenuti), vi invito a iscrivervi al mio Canale Telegram ufficiale:
Iscriviti al Canale Telegram di Vincenzo Caputo
Guarda il video su MissingTech
Volete vedere il Waveshare in azione? Nel video dedicato sul mio canale YouTube MissingTech, vi mostrerò il test dal vivo e una prova completa con la stupenda grafica di questo display touch!
Produrre e aggiornare contenuti su vincenzocaputo.com richiede molto tempo e lavoro. Se il contenuto che hai appena letto è di tuo gradimento e vuoi supportarmi, clicca uno dei link qui sotto per fare una donazione.
