Ha llegado el momento de pasar al siguiente nivel con nuestros dispositivos DIY y empezar a potenciar las pantallas con LVGL en ESPHome.
Índice
¿Qué es LVGL y para qué sirve?
Previamente ya hemos personalizado otras pantallas utilizando los componentes tradicionales de ESPHome (como la Lilygo T-Display S3 Long, el M5Stack Dial o el CrowPanel E-paper). En todos estos ejercicios he aprendido mucho sobre su funcionamiento y he creado dispositivos funcionales con pantallas muy baratas.
Sin embargo, también me he encontrado con algunas limitaciones en la navegabilidad. Por ejemplo, a la hora de tener controles táctiles o navegar entre pantallas. La verdad es que nunca me ha supuesto un problema y lo he podido solucionar con un poco de creatividad.
No obstante, cada vez caen en nuestras manos dispositivos mas potentes con pantallas de calidad y me parece un desperdicio no explotar todo su potencial. Aquí es donde aparece LVGL en escena, permitiendo crear interfaces avanzadas dignas de cualquier aplicación.
LVGL («Light and Versatile Graphics Library») es una biblioteca gráfica de código abierto diseñada para crear interfaces gráficas en dispositivos con recursos limitados, como nuestras queridas placas ESP32. Aunque esta biblioteca se encuentra programada en ‘C’, hace ya unas cuantas versiones que podemos utilizarla en ESPHome, a través del componente correspondiente.
Con este objetivo en mente, vamos a aprender a crear pantallas con LVGL en ESPHome!

Requisitos previos
Para programar pantallas con LVGL en ESPHome previamente necesitas:
- Alguna pantalla soportada con ESPHome, preferentemente con PSRAM y táctil (por ejemplo, un SenseCAP Indicator o un Lilygo T-Display S3 Long).
- Un cable USB-C para alimentar la placa de DATOS (con un cable de carga no vas a poder instalar el software).
- Haber instalado ESPHome en Home Assistant.
Principios a tener en cuenta
Si, como yo, ya habías experimentado con otras pantallas y componentes, o simplemente quieres descubrir cómo crear pantallas con LVGL en ESPHome estas ideas clave te serán de ayuda:
- Para usar LVGL con ESPHome, necesitarás una placa ESP32 o un RP2040. Se recomienda que tengan PSRAM (sobretodo para pantallas a color grandes), aunque no es un requisito estricto.
- La configuración de cada pantalla en ESPHome es diferente (te sugiero que te guíes por algún proyecto documentado). En esta entrada voy a dar por hecho que ya tienes tu pantalla integrada, ya que a partir de ahí LVGL funciona exactamente igual en cualquier dispositivo.
- LVGL es una librería muy personalizable, pero también más compleja de configurar. Si tienes una pantalla con poca memoria, no táctil o simplemente quieres desplegar algunos datos, te recomiendo que sigas una configuración «típica».
- El uso de LVGL es incompatible con el uso de ‘lambdas’. Por lo tanto, en tu componente ‘display’ debes elegir entre uno u otro.
- El componente display debe incluir la configuración ‘auto_clear_enabled: false’ y (en la mayoría de pantallas) ‘update_interval: never’.
- Una interfaz creada con LVGL se organiza una o varias páginas, que contienen uno o varios ‘widgets’ u «objetos». Cada uno de estos widgets representa un elemento con el que puedes interactuar y en la documentación de ESPHome encontrarás un listado de los que puedes usar.
- Puedes encontrar un montón de códigos de ejemplo en la documentación de ESPHome.
Teniendo en cuenta todo lo anterior (y la parte correspondiente a la configuración de tu placa y tu pantalla), este podría ser un código de partida:
display:
## Aquí iría la configuración del componente de tu pantalla ##
touchscreen:
- platform: XXXXXX
id: display_touchscreen
## Aquí iría el resto de la configuración del componente táctil de tu pantalla ##
lvgl:
touchscreens: display_touchscreen
buffer_size: 100%
widgets:
- label:
align: CENTER
text: 'Hello World!'

Creación de widgets
Esta es una de las partes más interesantes que ofrece LVGL. Como te decía, puedes incluir tantos ‘widgets’ u objetos interactivos como quieras. Además, un widget puede contener, a su vez, otros widgets.
Todos los widgets comparten algunas propiedades comunes (como la posición en ‘x’ e ‘y’, el tamaño en ‘width’ y ‘height’, o su ‘id). A partir de ahí cada tipo de widget tiene unas propiedades específicas y se configura de forma diferente. Además, ESPHome permite definir acciones, condiciones y desencadenantes para interactuar con los widgets (por ejemplo, la acción ‘lvgl.widget.show’ sirve para mostrar un widget oculto).
Vamos a empezar creando unos componentes básicos para un panel de control, como un texto (‘label’) y un botón (‘button’).
display:
## Aquí iría la configuración del componente de tu pantalla ##
touchscreen:
- platform: XXXXXX
id: display_touchscreen
## Aquí iría el resto de la configuración del componente táctil de tu pantalla ##
lvgl:
touchscreens: display_touchscreen
buffer_size: 100%
widgets:
- label:
align: TOP_LEFT
id: lbl_title
text_font: montserrat_28
text: "Aguacatec"
- button:
x: 50
y: 50
width: 70
height: 30
id: btn_id
checkable: true
widgets:
- label:
align: center
text: "Light"

Repasa el código modificado y observa lo siguiente:
- En este caso he incluido dos widgets, y el segundo (botón) contiene a su vez otro widget en su interior (texto).
- En ambos casos les he indicado su posición. En el primer widget he utilizado el parámetro de alineación y en el segundo las coordenadas ‘x’ e ‘y’ de la pantalla. Algo que me gusta de LVGL es que sus elementos heredan las propiedades de sus contenedores. Por ejemplo, si un texto tiene alineación centrada, y está dentro de un botón, aparecerá en el centro del mismo (sin especificar coordenadas).
Uso de variables
Por supuesto, como en el resto de dispositivos que hemos creado anteriormente, vamos a poder utilizar nuestra información de Home Assistant para mostrar nuestros sensores y controlar nuestros dispositivos.
La diferencia respecto a como lo haciamos con la forma tradicional es que, por un lado, vamos a definir la estructura de la interfaz y, por otro, actualizaremos los valores mostrados mediante otros eventos utilizando la acción ‘lvgl.label.update’. Por ejemplo, voy a añadir dos widget tipo texto para mostrar el reloj y la temperatura.
El primero se actualizará una vez cada 60 segundos y editará el valor por defecto de la etiqueta correspondiente. El segundo hará lo mismo, cada vez que Home Assistant le reporte la temperatura.
display:
## Aquí iría la configuración del componente de tu pantalla ##
touchscreen:
- platform: XXXXXX
id: display_touchscreen
## Aquí iría el resto de la configuración del componente táctil de tu pantalla ##
lvgl:
touchscreens: display_touchscreen
buffer_size: 100%
widgets:
- label:
align: TOP_LEFT
id: lbl_clock
text_font: montserrat_28
text: "00:00"
- label:
align: TOP_LEFT
id: lbl_weather
text_font: montserrat_28
text: "25C"
pad_top: 30
- button:
x: 100
y: 100
width: 70
height: 30
id: btn_id
checkable: true
widgets:
- label:
align: center
text: "Light"
time:
- platform: homeassistant
id: esptime
interval:
- interval: 60s
then:
- lvgl.label.update:
id: lbl_clock
text: !lambda 'return id(esptime).now().strftime("%H:%M");'
sensor:
- platform: homeassistant
id: sensor_temperature
entity_id: sensor.openweathermap_temperature
internal: true
on_value:
then:
- lvgl.label.update:
id: lbl_weather
text:
format: "%.0fC"
args:
- id(sensor_temperature).state

Dando estilo a la interfaz
Una de las características de las pantallas con LVGL en ESPHome es que tienen un aspecto más profesional y sofisticado. Para ello, lógicamente tenemos que aprender a darles un poco de estilo.
De nuevo, puedes consultar la documentación para encontrar todas las opciones disponibles. No obstante, me parece destacable comentar lo siguiente:
- Con el atributo ‘bg_color’ puedes personalizar el color de fondo (en formato hexadecimal o usando sus nombres estandarizados).
- Con el atributo ‘default_font’ puedes indicar un tipo de fuente por defecto para la interfaz (indicando el nombre y el tamaño, o cargando la fuente como lo hemos hecho «siempre»).
- Con el atributo ‘text_color’ puedes personalizar el color del texto.
- Usando la acción ‘lvgl.style.update’ puedes cambiar cualquier aspecto estético al producirse un evento (como el encendido de una luz).
display:
## Aquí iría la configuración del componente de tu pantalla ##
touchscreen:
- platform: XXXXXX
id: display_touchscreen
## Aquí iría el resto de la configuración del componente táctil de tu pantalla ##
lvgl:
touchscreens: display_touchscreen
buffer_size: 100%
bg_color: 0x062e20
default_font: font_clock
widgets:
- label:
align: TOP_LEFT
id: lbl_clock
text_font: montserrat_28
text: "00:00"
- label:
align: TOP_LEFT
id: lbl_weather
text_font: montserrat_28
text: "25C"
pad_top: 30
- button:
x: 100
y: 100
width: 70
height: 30
id: btn_id
checkable: true
widgets:
- label:
align: center
text: "Light"
time:
- platform: homeassistant
id: esptime
interval:
- interval: 60s
then:
- lvgl.label.update:
id: lbl_clock
text: !lambda 'return id(esptime).now().strftime("%H:%M");'
sensor:
- platform: homeassistant
id: sensor_temperature
entity_id: sensor.openweathermap_temperature
internal: true
on_value:
then:
- lvgl.label.update:
id: lbl_weather
text:
format: "%.0fC"
args:
- id(sensor_temperature).state
font:
- file: "gfonts://Space Grotesk"
id: font_clock
size: 30

Algo muy interesante que puedes hacer cuando utilices elementos de forma recurrente (por ejemplo, un botón) es crear una plantilla de estilo y aplicarla a todos los elementos que quieras. De esta forma podrás editar todos a la vez muy fácilmente.
lvgl:
touchscreens: ft5x06_touchscreen
buffer_size: 100%
style_definitions:
- id: style_button
width: 205
height: 145
bg_color: 0x021911
border_color: 0x021911
widgets:
- obj:
styles: style_button
x: 30
y: 155
- obj:
styles: style_button
x: 245
y: 155
- obj:
styles: style_button
x: 30
y: 315
- obj:
styles: style_button
x: 245
y: 315
Inclusión de imágenes e iconos
Por supuesto, también podemos incluir imágenes en nuestra interfaz usando el widget ‘image’. La configuración de la ruta de la imagen se realiza, como siempre, con el componente ‘image’. La única particularidad es que el único tipo de imagen soportado es ‘RGB565’ (si indicas otro no la cargará).
image:
- file: https://img.icons8.com/?size=100&id=qA3w9Yp2vY7r&format=png&color=000000
id: icon_weather
resize: 70x70
type: RGB565
transparency: alpha_channel
lvgl:
touchscreens: display_touchscreen
buffer_size: 100%
widgets:
- image:
align: TOP_RIGHT
src: icon_weather
id: widget_weather
pad_top: 45
pad_right: 20

También puedes usar iconos en tu interfaz de forma sencilla. En mi caso lo he hecho agregando la fuente Material Symbols de Google Fonts, y agregando los iconos que me interesan (puedes consultar el código -code point- correspondiente desde su web).
font:
- file: "gfonts://Material+Symbols+Outlined"
id: font_icons
size: 50
glyphs: [
"\U0000e846", # thermometer
"\U0000f87e", # humidity
"\U0000e7b0", # co2
"\U0000f55b", # poluttion
]
lvgl:
widgets:
- label:
text: "\U0000e846"
text_font: font_icons
Paginado y navegación
Uno de los motivos por los que quería aprender a crear pantallas con LVGL en ESPHome es por la facilidad de navegación en sus interfaces.
La forma «ortodoxa» de hacerlo es encerrar nuestros widgets dentro de una página y asignarle un ‘id’. Luego tendríamos que añadir unos controles en el componente táctil para que al pulsar en el margen izquierdo o derecho de la pantalla cambie de página.
binary_sensor:
- platform: touchscreen
name: "Previous Page"
internal: true
x_min: 0
x_max: 50
y_min: 0
y_max: 480
on_press:
then:
lvgl.page.previous:
- platform: touchscreen
name: "Next Page"
internal: true
x_min: 430
x_max: 480
y_min: 0
y_max: 480
on_press:
then:
lvgl.page.next:
lvgl:
pages:
- id: pagina1
widgets:
- id: pagina2
widgets:
Otra opción, si prefieres navegar deslizando el dedo por la pantalla, es simplemente dibujar elementos fuera de la misma (excediendo la resolución de la pantalla). Automáticamente LVGL creará un ‘scroll’ para acceder a esta parte «oculta».
Apagado automático de la pantalla
Una de las funciones más interesantes que he realizado creando dispositivos con pantalla, es apagarla cuando está inactiva. Es decir, que se apague automáticamente cuando no la tocas, y se encienda cuando si. La verdad es que hacer esto con LVGL es muy sencillo!
light:
- platform: monochromatic
output: ledc_gpio4
name: Display Backlight
id: display_backlight
restore_mode: ALWAYS_ON
output:
- platform: ledc
pin:
number: GPIO45
ignore_strapping_warning: true
id: ledc_gpio4
frequency: 100Hz
touchscreen:
- platform: XXXXXX
id: display_touchscreen
on_release:
then:
- if:
condition: lvgl.is_paused
then:
- light.turn_on: display_backlight
- lvgl.resume:
- lvgl.widget.redraw:
lvgl:
touchscreens: ft5x06_touchscreen
buffer_size: 100%
bg_color: 0x062e20
on_idle:
- timeout: 10s
then:
- logger.log: idle timeout
- if:
condition:
lvgl.is_idle:
timeout: 5s
then:
- logger.log: LVGL is idle
- timeout: 60s
then:
- logger.log: idle 60s timeout
- lvgl.pause:
- light.turn_off:
id: display_backlight
transition_length: 5s
Agradecimientos
Para elaborar esta entrada nos hemos apoyado en este video de nuestro amigo Luis de ProgramarFácil, canal que te recomiendo totalmente! 😎

