Saltar al contenido
miblo

SDK

Cómo Miblo anima una pantalla con 80 KB de RAM

La memoria de Miblo: qué es fijo, cuánto cuesta una escena, los pisos de 8 y 12 KB, qué se presta a la red y cómo leer los diagnósticos cuando una escena no carga.

Miblo tiene 80 KB de RAM para todo: el firmware, el Wi-Fi, tus sesiones, la mascota, la tarjeta y la escena de la app en la pantalla. No hay framebuffer (una imagen de la pantalla ocuparía 115.200 bytes) ni memoria virtual: lo que no cabe, no corre. Esta página dice adónde va esa memoria, cuánto cuesta una escena, qué le presta Miblo a la red y cómo leer los diagnósticos cuando una escena no carga. La guía de animación (Animaciones en la pantalla de Miblo) explica cómo animar; esto es la aritmética detrás de ella.

El presupuesto

De la app a los píxeles finalesApp + SDKdatos, arte y movimientoPuente en el computadorvalidación y planificaciónEscena residentetiles y comandos compactosTarjetatexto y componentesDibujo por líneafondo, sprites y efectosComposición → SPI → pantallapíxeles finales de la región
El computador prepara la escena. El dispositivo combina movimiento y contenido antes de enviar los píxeles de la región al panel.

De los 80 KB, el firmware toma una parte fija, que está ahí antes de cualquier conexión: 47,7 KB tras la dieta de RAM (51,4 KB en la 1.29.0, 54,8 KB en la 1.28.0). El resto es el heap, que alimenta los búferes del Wi-Fi, cada conexión abierta, cada solicitud del puente mientras se lee, la tarjeta de la app, los búferes de la mascota, las capturas de los elementos y la escena. En un Miblo emparejado con una tarjeta puesta y el flujo del puente activo, unos 17 a 19 KB están libres entre una solicitud y la siguiente (medido en Amon). Una escena tiene que caber dentro de esos KB, junto a los pisos de abajo.

Tres números organizan todo:

NúmeroQué es
12 KBLa reserva de la red. Una solicitud que necesita N bytes (su cuerpo, el documento JSON de su respuesta) se atiende solo cuando hay N + 12 KB libres, en un solo bloque entero de N + 4 KB; si no, la respuesta es 503 busy. Es lo que mantiene vivo al Wi-Fi
8 KBEl piso del reproductor. Una escena carga cuando su necesidad + 8 KB están libres (y su asignación más grande cabe en un bloque con 4 KB de sobra), y devuelve su memoria cuando el heap baja de 8 KB tras una solicitud
16 KBLa reserva de las capas de dibujo fuera de pantalla ordinarias (un elemento mesh relleno, por ejemplo). Las franjas de composición de los elementos no usan esta reserva: usan el piso de 8 KB

Qué es fijo

Dentro de los 47,7 KB están el estado del firmware (38,5 KB de variables, 7,2 KB de ellos solo el contexto: config, emparejamientos, snapshot, alertas, amigos, notas), 6,7 KB de constantes y 2,5 KB de valores iniciales. En el ESP8266 las constantes viven en la RAM a menos que el código las mande a la flash. Desde la 1.28.1, tres cosas que eran fijas siguen al uso en cambio:

  • Los textos de las sesiones. Los nombres, herramientas, modelos y detalles de las filas del snapshot vivían en un bloque fijo de 1536 bytes. Ahora viven en un pool dimensionado al contenido, con cada texto repetido guardado una sola vez: 20 sesiones típicas usan 234 bytes. El tope sigue siendo 1536 bytes (un snapshot mayor deja sus últimas filas sin texto, nunca un texto cortado). Eso son 2,4 KB más libres.

  • Los búferes de la mascota. Una mascota de bitmap (MPET1) dibuja desde un cuadro de 1152 bytes; una mascota vectorial (MPET2) usa tablas de trabajo. Miblo devuelve esos búferes siempre que la mascota está fuera de la pantalla (la pantalla de apps, el Resumen, una alerta de app, la pantalla apagada) y cuando cambia el formato de la mascota; el logo y la identidad se quedan, y el siguiente dibujo de la mascota recarga lo que necesita.

  • Las constantes. Algunos formatos propios de Miblo se pasaron a la flash.

La dieta de RAM le quitó 3,6 KB más a la parte fija, sin tocar ningún límite (20 sesiones, 6144 bytes de snapshot, 48 espacios, las longitudes de los textos) ni el dibujo:

  • Los arcos sin las tablas de libm. El anillo de Límites, los anillos de las tarjetas, el anillo de enfoque y las cabezas de algunas mascotas usaban el seno y el coseno de libm, que dejaban ~1 KB de tablas en la RAM. El arco ahora es el propio de TFT_eSPI, paso a paso, con un seno y un coseno nuestros que dan los mismos bits; los píxeles son los mismos (la prueba compara primitiva por primitiva).

  • Sin copias vacías. Varias estructuras (la config, las notas, los amigos, la solicitud de Wi-Fi, el gobernador, los gráficos en vivo) guardaban en la RAM una copia vacía de sí mismas, solo para nacer de ella. Ahora nacen limpias; los valores por defecto de la config viven en la flash. Unos 900 bytes.

  • Solo mientras se usan. La capa de dibujo fuera de pantalla, el encabezado de tu mascota (solo con una mascota propia), el DNS de la red de configuración (solo mientras está activa), los gráficos que pinta el puente (solo mientras hay uno) y la lista de redes vistas (solo tras la primera búsqueda) pasaron al heap y se devuelven al terminar. Los nombres de archivo pasaron a la flash.

  • Los textos de las pantallas. El anillo de textos traducidos de las pantallas tenía espacio para el texto más largo de todo el sistema (las pistas de la página de ajustes, que nunca pasan por él). Ahora tiene el tamaño de los textos de las pantallas, con espacio de sobra; una prueba avisa cuando una traducción nueva no cabe.

Se dejó fuera, a propósito: la tabla de escritura de los 48 espacios (el límite de escritura necesita la última escritura de cada espacio en todo momento), el snapshot (el formato que usa el puente), los búferes de lwIP y del SDK, las reservas de 12 KB y de OTA, el generador PWM de la luz de fondo (512 bytes del núcleo; reemplazarlo cambia cómo se atenúa la pantalla) y las claves de texto de la config (el lector JSON de la config).

La tarjeta de la app, cuando hay una, ocupa hasta 1 KB de heap mientras está en la pantalla.

Cuánto cuesta una escena

El caché se adapta a los colores de cada tileTile 8 × 8 · 64 píxeles1 colorconstante · 1 B2 colores1 bit/píxel · 9 B3–4 colores2 bits/píxel · 18 B5–16 colores4 bits/píxel · 32 BDirectorio + datos: usar solo si el total es menor
El directorio cuesta 2 bytes por tile. El caché compacto solo se elige si el total ahorra memoria, con todos los píxeles preservados.

Una escena de tiles (el camino screen.animate para arte que cabe en 256 tiles, con o sin un efecto por línea) cuesta tres partes en RAM:

ParteBytes
Los tiles residentesel tamaño compactado del conjunto (abajo)
El reproductor3104
El caché de paleta528

Los tiles son la parte que varía. En el archivo cada tile ocupa 32 bytes (64 píxeles de 4 bits). En la RAM Miblo guarda cada uno en la forma más corta que reproduce sus píxeles exactamente, decidida cuando carga la escena:

Colores en el tileCómo se guardaBytes
1una constante1
2dos índices y 1 bit por píxel9
3 o 4cuatro índices y 2 bits por píxel18
5 a 16los 4 bits por píxel del archivo32

Más una entrada de directorio de 2 bytes por tile. La forma compacta se usa solo cuando el total, directorio incluido, es menor que los 32 bytes × tiles del archivo; no se pierde ningún color, nada cambia en el archivo ni en el SDK, y el dibujo sigue leyendo de la RAM, nunca de la flash. El Radar, con 237 tiles, pasó de 7584 a 3816 bytes de tiles: la escena completa de 11.724 a 7448 bytes. Con el piso de 8 KB carga cuando hay unos 15,6 KB libres, con su asignación más grande (los tiles, 3,8 KB) en un bloque de 7,9 KB. Una escena pequeña (pocos tiles, colores planos) cuesta de 4 a 5 KB y carga con unos 12 a 13 KB libres.

Lo que ayuda, en el arte: menos tiles distintos y menos colores por tile. Un fondo plano es un byte por tile; un degradado es un tile de 32 bytes en cada celda. Las reglas del capítulo 4 de la guía valen aquí por la misma razón por la que valen para la velocidad.

Lo que ayuda, en los fps: nada. La tasa no cambia la memoria de una escena. El reproductor lee el mapa de cada paso desde la flash en trozos de 512 bytes; un paso que solo mueve sprites no lee el mapa de nuevo.

Elementos sobre la animación

Texto y animación en el mismo envíoDibujo separadoFondo → paneltexto borrado por un instanteLetras → panelel texto reapareceComposición por franjaFondo + letras en RAMfranja de 240 × 16 píxelesPíxeles finales → paneltexto ya sobre la animación
Cada franja se compone antes de enviarse. El panel deja de recibir primero un fondo sin las letras en esa región.

La tarjeta se dibuja sobre la escena. Un elemento quieto (big, text o bar sin fx y sin live) se compone en memoria con los píxeles de la animación, franja por franja de 240x16 píxeles y solo en las filas donde tiene tinta, y va al panel en un solo envío: el panel nunca recibe el fondo sin las letras. Eso vale la primera vez que aparece el elemento, cuando su valor cambia y en cada cuadro que se mueve debajo de él.

La memoria de eso:

  • Una franja de 240x16 a 4 bits pide 1953 bytes. Se admite con el piso de 8 KB (no con la reserva de 16 KB de las capas ordinarias), así que cabe junto a una escena cargada.

  • Cuando sobra memoria, Miblo guarda la captura de cada elemento quieto (hasta 6 KB para todos ellos, tomada solo de lo que queda por encima de 12 KB + 4 KB) y compone sin dibujar el elemento de nuevo. Cuando no, compone con una franja temporal y la devuelve antes de que la red la necesite.

  • Las capturas son la primera memoria que Miblo presta, antes que la escena.

Un elemento con fx, un ring, un spark o un mesh no va por este camino: en cada paso su caja se repinta entera (primero los píxeles de la animación, luego el elemento). Por eso la guía pide elementos pequeños y pocos efectos sobre el arte.

Qué se presta, y cuándo

Una escena es un caché de la flash: Miblo puede soltarla en cualquier momento y cargarla de nuevo donde estaba, en el mismo paso, con el reloj del efecto siguiendo. Mientras la memoria está prestada, la imagen de la escena se queda en el vidrio (no hay framebuffer que borrar), y los elementos siguen dibujándose a la manera antigua.

El orden de los préstamos, para una solicitud que no cabe con la reserva de 12 KB:

  1. Las capturas de los elementos van primero (su presupuesto baja a cero).

  2. La escena va después, si la solicitud pide más de 2 KB, o si, pidiendo menos, no cabe junto a la escena con 8 KB de sobra. Una solicitud pequeña (un sondeo, un documento de 1 KB) corre junto a la escena.

Y el piso: tras una solicitud, cuando quedan menos de 8 KB con la escena cargada, las capturas y luego la escena se devuelven a la red.

La devolución:

  • Nunca mientras se está leyendo una solicitud.

  • Tras una espera: 150 ms después de un préstamo a una solicitud, 2 s después del piso.

  • Cuando la necesidad de la escena + 8 KB están libres (las capturas cuentan como libres: van primero) y la más grande de sus tres asignaciones (los tiles o el reproductor) cabe en el bloque más grande con 4 KB de sobra; una vez cargada, se deja un bloque entero de 4 KB para la red, verificado ahí mismo (si no, se devuelve y se pide de nuevo). Una escasez en ese instante se reintenta 250 ms después; un archivo que no abre o un reproductor que falla, 5 s después.

  • Solo con la pantalla de apps en el panel. Tres segundos después de que la pantalla de apps se va, la memoria de la escena se devuelve definitivamente.

POST /api/screen/anim-play responde 503 busy solo cuando la escena no cabría ni siquiera cuando la solicitud termine (necesidad + 8 KB por encima del heap libre + lo recuperable + 4 KB). En otro caso se acepta y carga cuando el heap tiene espacio.

Modo app

Mientras una app está en la pantalla, el Miblo apaga lo que ni la app ni las obligaciones propias del Miblo necesitan y devuelve esa memoria al heap, para la escena (1.30). Refleja la primera configuración, que ejecuta solo lo que la configuración necesita.

Lo que nunca se apaga:

  • Las alertas, enteras: el snapshot y las alertas que envía el puente (POST /api/state, el flujo), su análisis, las pantallas de alerta y la marca de "te necesita". Las pantallas de alerta no le piden nada al heap (una prueba las dibuja con todas las asignaciones fallando).

  • Lo que saca la app de la pantalla: una sesión que te espera, la rotación, "Mostrar ahora", las solicitudes y alertas de las apps.

  • Lo que usa la app: la tarjeta, los marcos, los gráficos en vivo, la escena y el flujo.

  • La actualización (POST /update/open y la transferencia), el Wi-Fi y el reloj.

  • mDNS: el puente encuentra el Miblo explorando _miblo._tcp, y eso es parte del camino de las alertas (no guarda memoria entre dos pasadas del bucle).

Lo que se apaga:

QuéBytes (medidos con el compilador del dispositivo, con el costo del heap)Vuelve
Los amigos (visitas, balizas, presencia): su socket UDP se cierra y su estado se congela (nada sale ni entra; los amigos que conoce se quedan, y caducan solo tras 95 s sin una baliza, una vez que vuelve a correr)128 (el socket) y los paquetes que conteníaEl socket en la siguiente pasada; nuestra baliza en su turno (hasta 30 s)
La descarga del archivo de una mascota visitante (y el archivo, sin visita)384, solo durante una visitaCon la siguiente visita
La lista de redes Wi-Fi vistas424, solo por 5 min tras una búsquedaEn la siguiente búsqueda, como cuando caduca

El estado de los amigos (2076 bytes) es estático en este firmware; cuando pase al heap, el modo app también podrá devolverlo. Lo que ya seguía a la pantalla se queda como estaba: los búferes de las mascotas se van fuera de las pantallas de mascota, las capturas de los elementos son la primera memoria prestada, los gráficos en vivo y las franjas se van cuando la app se va. El pool de textos de las sesiones se queda: las pantallas de alerta toman el nombre de la sesión de él.

Cuándo está activo: con la pantalla de apps en el panel. Se apaga en la misma pasada cuando llega una alerta (o una sesión te espera), cuando una actualización o la configuración toman el Miblo, cuando el panel duerme, cuando alguien le pide algo al Miblo (un código, un comando) y cuando llega la pantalla de la mascota (sus visitas necesitan a los amigos). Por lo demás, se mantiene activo durante los otros turnos de la rotación más cortos que 30 s (el turno de las sesiones, el arco de los límites): la rotación no reinicia a los amigos en cada turno. Apagado, todo vuelve en una pasada.

Después de una alerta, el Miblo muestra las sesiones (o los límites), no la app, hasta que la hayas visto: con una sesión esperando, hasta que se responda; si no, el turno de las sesiones (sessionsDwellSec, 15 s por defecto). La rotación vuelve a empezar desde ellas; una app fijada, "Mostrar ahora" y las alertas de las apps esperan ese tiempo.

GET /api/screen lleva appMode: on, released (el heap que devolvió su última entrada, medido en ese momento), enters, leaves y left (por qué se apagó la última vez: alert, held, person, pet, away). El log serial dice appmode: on ... released N y appmode: off ... en cada borde.

Los pisos

Los dos números que protegen la red, calculados de nuevo con los picos medidos del formato binario (1.30):

  • El piso del reproductor, 8 KB, es lo que lwIP y el SDK de Wi-Fi pueden tomar del heap mientras un paso de la escena dibuja (hasta ~160 ms, con el bucle incapaz de devolver nada): una ventana de recepción TCP (4 × 536 bytes, cada segmento en su propio búfer con sus encabezados: ~2,5 KB) por cada conexión que puede estar recibiendo junto a la escena (el flujo, la solicitud HTTP del puente, una tercera en su handshake: un flujo que se reconecta o un navegador), más ARP, balizas y las tramas del SDK: 3 × 2560 + 512 = 8192. El modo app apaga solo UDP (los amigos), así que las tres siguen siendo posibles: el piso se queda. Por debajo, el dispositivo ha mostrado a lwIP dejando de aceptar conexiones con 2 a 6 KB libres (1.26.0).

  • Lo que corre junto a la escena, 2 KB. Una solicitud de hasta 2 KB corre con la escena cargada, siempre que queden los 8 KB después. En el formato binario el snapshot pide el pool de textos (1600 bytes; pico medido 2565 bytes con su cuerpo, juzgado en dos pasos) y la tarjeta pide un documento pequeño y la Card (1276 bytes; el pico medido más allá del cuerpo es 227 bytes): ambos quedan por debajo de 2 KB con 256 bytes de sobra, así que los snapshots y las tarjetas del puente nunca toman la memoria de la escena.

  • La reserva del SDK, 12 KB, queda para el resto: un cuerpo JSON (dimensionado por su longitud, como antes; un snapshot JSON alcanza 12.094 bytes) supera los 2 KB y toma la memoria de la escena antes de leerse.

  • El bloque. Una escena son tres asignaciones (el reproductor, el caché de paleta y los tiles), nunca un bloque del tamaño de toda su necesidad. El Miblo pide la más grande de ellas en un bloque con 4 KB de sobra y, una vez cargada, verifica que se deja un bloque entero de 4 KB para la red. Pedir toda la necesidad + 4 KB en un bloque rechazaba escenas que el heap podía contener: el "Excelente" del simulador se detuvo en una escena de 10,2 KB con un bloque más grande de 14,3 KB. Los tiles más grandes posibles (256 × 34 bytes) caben en el bloque medido.

Las pruebas nativas (test_anim, test_appmode) fallan si cualquiera de estas reservas baja del pico medido más su margen.

El espacio que tiene una escena en la pantalla de apps, según la cuenta del simulador (el heap libre medido en la 1.28.1, más lo que el firmware devolvió desde entonces, menos el piso y el margen):

Excelente hastaAtención hasta
Medido en la 1.28.19,7 KB12,0 KB
1.30.0 con el modo app (proyectado)12,9 KB16,6 KB

La proyección suma 3268 bytes de RAM estática (51.216 en la 1.28.1, 47.948 ahora: el conteo del enlazador) y los 128 bytes del socket de los amigos. La próxima lectura larga en la 1.30.0, con la pantalla de apps arriba y el modo app activo, mide el idleHeap real y reemplaza la proyección.

Lo que le cuesta tu app al Miblo

El código de tu app corre en el computador; lo que le cuesta al Miblo es lo que la app pone allí: la tarjeta (sus elementos, los gráficos en vivo), la escena (los tiles residentes, el reproductor y el caché; o, para los marcos, el decodificador y la franja de un paso y los archivos abiertos), los marcos en los espacios de la flash, los fps pedidos por el costo medido de cada paso, y las solicitudes que envía el puente. El simulador (miblo apps dev), miblo screen status y el screen.budget() del SDK (TypeScript y Python) hacen esa suma con los números medidos en un Miblo real y ponen cada recurso en una de tres bandas, con una línea de qué hacer. Es una estimación: los números vienen de las mediciones de abajo y nunca prometen más de lo que muestran.

Estimación, medida en Miblo 1.30.0, 1.30.2 (Amon, 2026-10-12; 275 lecturas de memoria, 1378 pasos de escena).

ExcelenteAtenciónNo cabe
RAM (escena + tarjeta + gráficos en vivo)hasta 12,3 KB12,3 KB a 15,8 KB: carga mientras el Miblo está tranquilo, puede ser prestada y entrecortarsemás de 15,8 KB: no carga
Almacenamientohasta 3 espacios y 23,0 KBhasta 8 espacios de animación, que quepan en la flashmás de 8 espacios en una animación, un marco de más de 60 KB o de más de 650 KB
Procesamientohasta el 81 % del bucle, a los fps pedidosel Miblo baja la tasa, quedándose en la mitad de los fps pedidos o másmenos de la mitad de los fps pedidos

Cómo se hace cada frontera: una parte fija (la regla del firmware) y una parte flexible (la dispersión medida). RAM: el heap libre medido (p10 22,2 KB, mediana 24,0 KB), menos el piso del reproductor (8 KB) y un margen del 7,6 %, y la asignación más grande de la escena (sus tiles o el reproductor) en un bloque entero con 4 KB de sobra (bloque más grande p10 17,9 KB). Los marcos (rectángulo o enteros) cuentan el decodificador de un paso (0,3 KB), la franja de 8 filas (3,8 KB) y cada archivo abierto (0,3 KB: la base y el paso, o solo el paso); sin espacio para la franja un paso se dibuja una fila a la vez (0,5 KB), y solo no cabe cuando ni siquiera eso cabe. Una tarjeta sola (hasta 1 KB, más 2 KB con gráficos en vivo) siempre cabe. Procesamiento: el gobernador (intervalo = el mayor entre 1000 / fps y el dibujo + 2 ms; todo junto hasta el 90 % del bucle) menos un margen del 10 % por la dispersión medida de un paso.

Costo medido de un paso (con los elementos de la tarjeta encima): tiles 42 ms (p90) + 0,04 ms por tile que cambia; perspective 39 ms + 2,44 ms por mil píxeles; rotate 39 ms + 3,33 ms por mil píxeles; scroll 39 ms + 4,65 ms por mil píxeles; wave 39 ms + 3,61 ms por mil píxeles (de la guía, no medido en esta ronda); zoom 39 ms + 3,33 ms por mil píxeles (igual que rotate: aún no medido); gráfico en vivo 7 ms; efecto de elemento 62 ms.

Los números viven en un solo archivo (plugin/assets/budget.json), reconstruido a partir de una lectura larga de GET /api/info y GET /api/screen por plugin/scripts/budget-from-soak.mjs (p10 del heap libre, p90 del dibujo de cada efecto); esta tabla la genera a partir de él plugin/scripts/budget-docs.mjs.

Cómo leer los diagnósticos

Todo lo de abajo se lee en el dispositivo, con el token del computador emparejado (Authorization: Bearer), o a través del puente donde se indica. Ese token es el que el puente guarda para el Miblo Desk que emparejó, en la carpeta de datos de Miblo; nunca se entrega a los programas, y rara vez lo necesitas: miblo screen status imprime lo que importa aquí (la escena, si cargó y por qué no, los fps reales y el presupuesto), y tu programa lee lo mismo mediante screen.status() y screen.budget().

GET /api/info

CampoQué es
heapEl heap libre en el instante de la respuesta, dentro del handler, después de que asignó su propio documento (~2 KB). No lo compares con idleHeap
idleHeapEl heap libre muestreado por el bucle principal, fuera de cualquier solicitud. Este es el número para comparar entre lecturas y entre versiones
maxBlockEl bloque libre entero más grande. Mucho menor que idleHeap significa fragmentación
minHeapParseEl heap libre más bajo visto mientras se leía un snapshot
petDrawingBytes, guestDrawingBytesLos búferes de dibujo de tu mascota y de la mascota visitante, si están cargados (0 cuando se devuelven)
petLogoBytesEl logo de la mascota propia, que se queda
snapshotTextBytes, snapshotTextUsedEl pool de textos de las sesiones: asignado y usado

GET /api/screen

CampoQué es
panelLo que está ahora en el vidrio: app, main, desk, summary, alert, pet, limits, preview, boot, setup, code, disconnected, updating, off (pantalla apagada) u other. shown solo dice si la pantalla de apps está fijada; no dice que esté en el vidrio
anim.loadedLa escena está cargada en la RAM
anim.yieldedNo lo está, porque su memoria fue prestada; vuelve sola
anim.needLo que cuesta cargarla: tiles compactados + reproductor + caché (mientras no está cargada)
anim.whyMientras no está cargada: cuántas pasadas del bucle se rechazaron por cada motivo desde la última carga. notShown (la pantalla de apps no está en el panel), pending (una solicitud leyéndose), hold (la espera tras un préstamo), free (need + 8 KB no estaban libres; lastFree dice cuánto había), room (ningún bloque entero; lastFree y lastBlock), failed (el archivo o el reproductor)
anim.yields, anim.yieldNeedCuántas veces se prestó la memoria de la escena a una solicitud desde que empezó a reproducirse, y qué pidió la última
anim.floorYieldsCuántas veces la devolvió el piso de 8 KB
anim.composeok: franjas compuestas sobre la animación; noLayer: franjas que no cabían en memoria en ese instante (el elemento se repintó a la manera antigua); inexact: capturas que no representaban el elemento exactamente (igual). ok cuenta franjas, no cuadros, y un elemento dibujado desde su captura guardada no cuenta
anim.heap, anim.tileBytes, anim.tileRawBytesCon la escena cargada: lo que ocupa, los tiles residentes compactados y lo que serían sin compactar
anim.real{ fps, drawMs }: la tasa lograda y el último dibujo
appModeModo app (1.30): on, released (bytes que devolvió su última entrada), enters, leaves, left (por qué se apagó: alert, held, person, pet, away)
governorCada tipo que anima (anim, fx, meshWire, meshFlat, transition, live): sus fps actuales, su último dibujo y el promedio

miblo screen status

El puente lee loaded, yielded, why y yields del dispositivo e imprime un motivo en la línea de la animación:

Animação: blocos de Clima · 8 fps pedidos · — fps real · — ms · não carregada (sem memória livre: 7,9 KB livres).
Animação: blocos de Radar · 10 fps pedidos · 9 fps real · 48 ms · memória emprestada 3×.

panel, floorYields y compose no pasan por el puente: léelos en el dispositivo.

Por qué la escena no cargó

miblo screen status diceanim.whyQué está pasandoQué hacer
"a tela do app não está no Miblo"notShownLa pantalla de apps no está en el panel (otra pantalla, la rotación en otra app, la pantalla apagada). La escena carga solo con ella en el vidrioNada: carga en el turno de la app. Revisa panel
"uma requisição em curso"pendingEl puente está enviando algo (una tarjeta, un lote en vivo) en cada pasadaNormal por momentos; constante, es un puente que envía demasiado (una tarjeta cada segundo, por ejemplo)
"devolvendo a memória emprestada"holdLa espera de 150 ms o 2 s tras un préstamoNada
"sem memória livre: N KB livres"freeneed + 8 KB no estaban libres. lastFree dice cuánto habíaUna escena más pequeña (menos tiles distintos, menos colores por tile); menos cosas abiertas en el Miblo (el flujo de otro computador, una mascota visitante)
"sem um bloco de memória inteiro: N KB livres"roomHay bytes libres, pero la asignación más grande de la escena no cabe en un bloque con 4 KB de sobra, o, una vez cargada, no quedaría un bloque de 4 KB: fragmentaciónIgual que arriba; maxBlock en /api/info muestra el bloque
"o arquivo ou o player falhou"failedEl archivo del espacio no abre o no validaEnvía la escena de nuevo (screen.animate); el SDK reescribe el espacio
"(memória emprestada a uma requisição)"yielded sin conteosLa escena estaba cargada y cedió el paso a una solicitud; vuelve en 150 msNada
"memória emprestada N×"yieldsLa escena se reproduce, pero cede el paso a menudoNormal unas pocas veces por minuto; cada segundo, es el puente enviando solicitudes demasiado grandes (tarjetas grandes, snapshots cada segundo)

Una escena atascada en free o room con la pantalla de apps en el vidrio es la aritmética de esta página: su necesidad (anim.need) + 8 KB no cabe en idleHeap. Los números de anim.compose dicen si los elementos se están componiendo (ok creciendo) o repintando a la manera antigua (noLayer creciendo): en el segundo caso el heap está en su límite durante el dibujo, incluso con la escena cargada.

Qué vive en la flash y qué vive en la partición de datos

El ESP8266 mapea 1.044.464 bytes de flash de programa. La imagen de Miblo 1.29.0 ocupaba 983.360 (94 %), y 159 KB de ella eran datos, no código: las fuentes (44,6 KB), las cadenas de los siete idiomas (42,8 KB), los scripts de la página de ajustes (26 KB). El código no se puede cargar dinámicamente en el ESP8266, pero los datos pueden vivir en la partición de datos (LittleFS, 1 MB) como ya lo hacen las escenas de las apps. Desde la 1.30.0 el firmware lleva solo lo que debe existir antes de cualquier internet, y descarga el resto de miblo.ai por sí mismo (lib/miblo_core/src/miblo_assets.h):

Se queda en el firmwareVa a la partición de datos (/assets/)
Inglés y portugués de Brasil completos; de los otros cinco idiomas solo las cadenas de las pantallas de configuración, emparejamiento, actualización y descarga (104 cada uno)La tabla completa de cada otro idioma (strings-<code>.bin, 7 a 8 KB)
ASCII, las letras del portugués, los signos comunes (° º ª · £ ¥ ± × ÷ µ) y cada letra que dibuja una cadena integrada (¡ ä è ñ ò), en cada fuente (17,8 KB), las fuentes de dígitosLos demás glifos latinos (Latin-1, Latin Extended-A, Ș Ț) de cada fuente: el paquete font-latin.bin (31,7 KB). Los paquetes para otras escrituras (cirílico, griego, CJK...) usan el mismo mecanismo una vez publicados
El CSS, el selector de zona horaria, el script principal de la página de ajustes, los resúmenes de los Términos y la PrivacidadLos scripts extra de la página (el panel Sistema, la tarjeta de Wi-Fi, Mi mascota) y el selector de miblo.local: page-extra.bin (15 KB)

Sin un recurso nada se queda en blanco: un idioma sin su archivo se muestra en inglés (fuera de las pantallas de configuración, que hablan los siete), un glifo que solo tiene el paquete dibuja el cuadro de reemplazo, y una tarjeta de la página cuyo script falta dice qué falta y que Miblo lo descarga por sí mismo. Con los recursos en el dispositivo cada pantalla es píxel por píxel lo que era (test_fonts compara las fuentes completas con la parte integrada más el paquete, glifo por glifo).

Cómo llega: una vez en Wi-Fi (nunca en la red de configuración, nunca durante una actualización de firmware ni con el heap corto), Miblo descarga http://miblo.ai/dl/assets/<version>/catalog.sbin, el catálogo de su versión firmado con la clave de lanzamiento del proyecto (Ed25519; la clave pública está en el firmware), verifica la firma y la generación (una generación por debajo de la instalada se rechaza), y descarga un archivo a la vez en /assets/dl.tmp, verificando su SHA-256 mientras escribe; solo un archivo completo y verificado reemplaza al anterior. La primera descarga tras un inicio se muestra en la pantalla ("Descargando recursos… 3 / 8", el elemento, su tamaño, "desde miblo.ai"); las siguientes (diarias, o cuando a una pantalla le falta algo) aparecen en la lista "Recursos descargados" de la página de ajustes, con fecha. La solicitud a miblo.ai lleva solo la ruta del archivo y la versión del firmware (User-Agent: Miblo/<version>).

En RAM los recursos cuestan poco: la tabla de lo que está instalado (16 × 8 bytes), el archivo del idioma activo mantenido abierto entre cuadros (solo cuando el idioma no es inglés ni portugués: ~230 bytes de heap más su tabla de pares, ~270) y cada paquete de fuente abierto (~500 bytes de heap, con el último glifo leído). Las cadenas y los glifos se leen del archivo en trozos pequeños, como las escenas. El firmware 1.30.0 pesa 952.704 bytes (91 %, −30,7 KB respecto de la 1.29.0), con el verificador Ed25519 (4,1 KB), la descarga, la pantalla y las cadenas nuevas incluidos.

Diagnóstico: GET /api/assets (un token o la sesión de la página) lista cada recurso con su tamaño, fecha, origen, si fue verificado, si es obligatorio (y por qué) y el último error (dns, connect, timeout, http, signature, catalog, rollback, sha256, no_space, storage, memory); el Serial dice "assets: ..." en cada paso.