ChatGPT – LVGL ESP32 UI
Saved on 28/1/2026, 12:09:43
USER
Quiero hacer un interface LVGL para controlar procesos y leer lecturas de sensores.
Los sensores y procesos vendrán en tiempo de ejecución.
Pueden ser fichero .json o de texto.
Lo que quiero es poder crear sistemas nidados de menus que lleven a pantallas.
Y en cada una de esas pantallas que se puedan mostrar widgets para mostrar valores, texto, etc.
Como hacer esto, un constructor dinámico de menus y de UIs?
ASSISTANT
Vale: lo que quieres es una UI “dirigida por datos”. No “pantallas escritas a mano”, sino un teatro:
Guion → un
.json(o texto) que describe menús, pantallas y widgetsActores → una factory de widgets LVGL (label, bar, chart, switch, button…)
Regidor → un navigation manager (push/pop) que construye pantallas bajo demanda
Música en vivo → un data bus donde van llegando sensores/procesos, y los widgets se “enganchan” por nombre (
bind)
Abajo te dejo una arquitectura que funciona muy bien en ESP32 (LVGL 8/9) sin volverte loco.
1) Define un esquema simple de UI (JSON)
Ejemplo minimalista con menú anidado y pantallas con widgets:
json`{
"root": {
"type": "menu",
"title": "Inicio",
"items": [
{ "label": "Sensores", "to": "menu_sensores" },
{ "label": "Control", "to": "screen_control" }
]
},
"nodes": {
"menu_sensores": {
"type": "menu",
"title": "Sensores",
"items": [
{ "label": "Clima", "to": "screen_clima" },
{ "label": "Energía", "to": "screen_energia" }
]
},
"screen_clima": {
"type": "screen",
"title": "Clima",
"layout": { "kind": "flex", "flow": "column", "gap": 10, "pad": 12 },
"widgets": [
{ "type": "label", "text": "Temperatura:" },
{ "type": "value", "bind": "sensors.temp", "format": "%.1f °C" },
{ "type": "bar", "bind": "sensors.hum", "range": [0,100] }
]
},
"screen_control": {
"type": "screen",
"title": "Control",
"layout": { "kind": "grid", "cols": 2, "gap": 10, "pad": 12 },
"widgets": [
{ "type": "switch", "label": "Bomba", "bind": "proc.pump", "action": "set:proc.pump" },
{ "type": "slider", "label": "Ventilador", "bind": "proc.fan_pwm", "range": [0,255], "action": "set:proc.fan_pwm" }
]
}
}
}
`
Ideas clave:
menu: lista de items que llevan a otro nodo (to)screen: contienewidgetsy unlayoutbind: ruta a un valor que aparecerá en tiempo de ejecución (sensors.temp,proc.pump, etc.)action: qué hacer cuando el usuario cambia un control (ej. “set:proc.pump”)
2) Núcleo: 3 capas (para que no se te convierta en espagueti)
A) Modelo (árbol de nodos)
Estructuras tipo:
UINode { id, type(menu/screen), title, items[], widgets[] }Se construye parseando JSON (con cJSON en ESP-IDF o ArduinoJson en Arduino)
B) Navegación (stack)
Un gestor con:
push(node_id)→ crea pantalla si no existe, la carga conlv_scr_load()pop()→ vuelve atrásExtra útil: lazy build (crear sólo al entrar), y destroy al salir si vas justo de RAM
C) Factory de widgets + “bindings”
create_widget(spec)→ devuelvelv_obj_t*Cada widget “bindable” se registra en una lista:
{ obj, type, bind_path, format, min/max, ... }
Luego un timer (ej. cada 200–500 ms) recorre bindings y actualiza.
3) Data Bus: sensores/procesos que llegan “en caliente”
Haz un registro tipo diccionario:
clave:
"sensors.temp"valor:
float/int/bool/string + timestamp
Cuando llega un sensor nuevo:
lo guardas en el bus
si hay widgets con ese
bind, empezarán a pintarlo en el siguiente tick
Esto te permite enchufar sensores “desconocidos” sin recompilar.
Importante en ESP32/LVGL:
LVGL debe tocarse desde su mismo hilo/tarea. Si los sensores llegan desde otra tarea, guarda en el bus con mutex/cola, y la UI sólo lee.
4) Menús anidados: patrón que no falla
Para un nodo menu:
creas un
lv_listpor cada item →
lv_list_add_btn(list, NULL, label)en el callback guardas el
to(node_id) y hacesnav.push(to)
Añade un botón “Atrás” arriba (o gesto) y listo: laberinto domado 🧵🐂
5) Layout: huye de coordenadas absolutas (te lo agradecerás)
En JSON define layout y en C aplica:
Flex para columnas/filas rápidas (
lv_obj_set_flex_flow,lv_obj_set_style_pad_*,gap)Grid para paneles tipo “dashboard” (
lv_obj_set_grid_dsc_array,lv_obj_set_layout(LV_LAYOUT_GRID))
Así el mismo JSON se ve decente en pantallas distintas.
6) Mini-esqueleto en C (idea, no “copia-pega mágico”)
c`typedef enum { NODE_MENU, NODE_SCREEN } node_type_t;
typedef struct {
const char *label;
const char *to;
} menu_item_t;
typedef struct {
const char *type; // "label", "value", "bar", "switch"...
const char *bind; // "sensors.temp"
const char *format; // "%.1f °C"
// ... props extra
} widget_spec_t;
typedef struct {
const char *id;
node_type_t type;
const char *title;
menu_item_t *items; int items_len;
widget_spec_t *widgets; int widgets_len;
} ui_node_t;
// Binding runtime
typedef struct {
lv_obj_t *obj;
const char *wtype;
char bind[48];
char fmt[24];
} ui_binding_t;
`
build_menu(node)→ crea lista + callbacksbuild_screen(node)→ layout +create_widget(spec)ui_update_timer()→ recorrebindingsy pinta valores del bus
7) Texto plano en vez de JSON (si te apetece lo “monje”)
Puedes usar un formato INI súper fácil de parsear:
ini`[menu:root]
title=Inicio
item=Sensores->menu_sensores
item=Control->screen_control
[screen:screen_clima]
title=Clima widget=label|Temperatura: widget=value|bind=sensors.temp|fmt=%.1f °C widget=bar|bind=sensors.hum|min=0|max=100 `
Pros: parseo más simple y rápido. Contras: menos estructurado.
8) Lo más importante (la trampa típica)
No construyas “todo el universo” a la vez.
Construye sólo lo que el usuario ve y destruye lo viejo si necesitas RAM. LVGL en ESP32 puede ir sobrado… hasta que de repente no 😄
Si me dices:
¿ESP-IDF o Arduino?
¿Lees JSON desde SD/SPIFFS/LittleFS/OTA?
¿Qué widgets mínimos quieres sí o sí (label, bar, chart, table, button, slider…)?
…te dejo un esqueleto de proyecto con:
parser (cJSON/ArduinoJson),
nav stack,
widget factory,
data bus + bindings,
y 2–3 pantallas demo generadas 100% desde archivo.
0 comentarios