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 widgets

  • Actores → 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: contiene widgets y un layout

  • bind: 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 con lv_scr_load()

  • pop() → vuelve atrás

  • Extra ú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) → devuelve lv_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_list

  • por cada item → lv_list_add_btn(list, NULL, label)

  • en el callback guardas el to (node_id) y haces nav.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 + callbacks

  • build_screen(node) → layout + create_widget(spec)

  • ui_update_timer() → recorre bindings y 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

Deja una respuesta

Marcador de posición del avatar

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *