Lopaka Basics 10: papel electrónico y publicación

Dos pantallas muestran el mismo cartel. Desconecta el enchufe y la brillante se apaga al instante. La otra mantiene su imagen con cero energía y seguirá ahí días después. Eso es el papel electrónico.
En este episodio descubrirás cómo la tinta electrónica mantiene una imagen sin energía, y por qué tiene dos formas de cambiarla. Después diseñarás un cartel de sala de reuniones en Lopaka para la biblioteca GxEPD2, actualizarás solo los minutos sin un destello, probarás una versión roja para un panel de tres colores, publicarás el diseño y compartirás un enlace a él. Antes de publicar, una lista de los cinco errores que más vemos.
Lo que vas a crear: un cartel de sala de 296×128 para una pantalla de papel electrónico de 2,9 pulgadas con dos pantallas. Busy muestra la sala, la reunión, un bloque BUSY grande y los minutos que quedan. Free muestra la próxima reunión. Lo que necesitas: un navegador. Para ejecutar el cartel, un ESP32 y un módulo de papel electrónico de 2,9 pulgadas (Waveshare V2).
Cómo funciona el papel electrónico
Desconectado, aún visible
Una pantalla TFT como la del episodio 3 brilla con su propia luz y su imagen vive solo mientras dura la energía. Corta la energía y se apaga.
El papel electrónico funciona como tinta en papel. Cuando la imagen está dibujada, permanece sin energía alguna. Eso lo hace adecuado para carteles, etiquetas y etiquetas de precio que cambian unas veces por hora y funcionan con batería.

Cápsulas con partículas blancas y negras
Cada píxel de papel electrónico está lleno de cápsulas diminutas. Dentro de cada una flotan partículas blancas y partículas negras con cargas eléctricas opuestas. Entre dos electrodos, un voltaje atrae un color a la parte superior, el lado que miras, y el píxel se vuelve blanco o negro.
Después la energía desaparece y las partículas se quedan donde están. No hace falta energía para mantener la imagen.

Actualización completa y actualización parcial
Cambiar la imagen lleva trabajo y hay dos formas de hacerlo:
- Una actualización completa sacude cada píxel. Todo el panel parpadea en blanco y negro para borrar la imagen antigua y después muestra la nueva. En el panel blanco y negro de 2,9 pulgadas de este episodio lleva unos 4 segundos.
- Una actualización parcial mueve solo los píxeles que cambiaron, en menos de un segundo (unos 0,7 s en este panel), sin destello.

Cada actualización parcial deja un fantasma tenue de la imagen antigua, como el dígito antiguo tras el nuevo de arriba. Tras unas actualizaciones parciales, haz otra actualización completa para borrar los fantasmas.
Paso 1: un proyecto de papel electrónico nuevo
Haz clic en + New Project y abre la lista Platform. Elige GxEPD2 (e-paper), una biblioteca C++ de ZinggJM. Su descripción en Lopaka resume lo que importa: «sits on top of Adafruit GFX, so drawing calls and fonts are the familiar ones, but you must name your exact panel class», y su color «is a fixed set of nine constants such as GxEPD_BLACK and GxEPD_RED, and anything else maps to black».
Completa el resto del diálogo:
- Title: «Room Sign».
- Screen Size: 296×128, el tamaño de un panel común de 2,9 pulgadas.
El Background Color ya es #F8F8F8, un casi blanco, como el papel. Haz clic en Save.

Paso 2: dibuja el cartel
Haz doble clic en el nombre de la pantalla sobre el lienzo y renombra Screen 1 a Busy. Después dibuja el cartel de arriba abajo. Todos los textos usan las fuentes Adafruit GFX de la lista Font en el inspector:
- El nombre de la sala. Elige la herramienta text (
Aa), escribeROOM 3y eligeFreeSansBold12pt7b. Ponlo en la esquina superior izquierda. - La hora de actualización. Añade
Updated 15:25enFreeSans9pt7b, en la esquina superior derecha. - Un divisor. Elige la herramienta line y dibuja una línea bajo el encabezado, de x = 10 a x = 285 en y = 32.
- El bloque de estado. Elige la herramienta rectangle, arrastra un rectángulo y pon X
10, Y42, W128, H76, R6, después activa Filled. - El estado. Añade
BUSYenFreeSansBold18pt7bsobre el bloque y pon su Fill color en#FFFFFF, blanco sobre negro. - La reunión. A la derecha del bloque, añade tres líneas de texto:
Design reviewenFreeSansBold9pt7b,until 15:30enFreeSans9pt7by5 min leftenFreeSansBold12pt7b.

Los números mantienen el mismo margen de 10 píxeles en cada lado. La línea termina en x = 285, diez píxeles antes de la última columna, 295. El bloque empieza a 10 píxeles del borde izquierdo y su fila inferior es 117, diez filas sobre la última, 127.
Nueve colores, y tu panel muestra unos pocos
Abre el Fill color de cualquier forma y verás qué corta es la paleta. Estos nueve son los únicos colores que conoce la biblioteca GxEPD2: GxEPD_BLACK, GxEPD_WHITE, GxEPD_LIGHTGREY, GxEPD_DARKGREY, GxEPD_RED, GxEPD_YELLOW, GxEPD_BLUE, GxEPD_GREEN y GxEPD_ORANGE.

Tu panel muestra solo algunos de ellos. El panel de este episodio es blanco y negro; muchos otros añaden un tercer color, rojo o amarillo. Diseña con los colores que tiene tu panel.
Paso 3: duplica para «free»
Cuando la reunión termina, el cartel necesita una segunda pantalla. En lugar de dibujarla otra vez, selecciona la pantalla Busy en el panel Capas y haz clic en Duplicate en el inspector. Renombra la copia a Free y cámbiala:
BUSYse convierte enFREE, con el Fill color de vuelta a#000000.- Selecciona
rect 4y desactiva Filled: el bloque se convierte en un marco. Updated 15:25se convierte enUpdated 15:30.- Las líneas de reunión se convierten en
Next: Standup,at 16:00y30 min free.

Cada pantalla se convierte en su propia función en el código, drawBusy() y drawFree(), y el panel de código tiene una pestaña para cada una.
Paso 4: el código
Llamadas de dibujo familiares
Abre la pestaña Busy del panel de código. El código es el mismo tipo de llamadas de dibujo que en los episodios anteriores:
// [BEGIN lopaka generated]
void drawBusy(void) {
display.fillScreen(GxEPD_WHITE);
// string 1
display.setTextColor(GxEPD_BLACK);
display.setTextWrap(false);
display.setFont(&FreeSansBold12pt7b);
display.setCursor(10, 23);
display.print("ROOM 3");
// string 2
display.setFont(&FreeSans9pt7b);
display.setCursor(170, 21);
display.print("Updated 15:25");
// line 3
display.drawLine(10, 32, 285, 32, GxEPD_BLACK);
// rect 4
display.fillRoundRect(10, 42, 128, 76, 6, GxEPD_BLACK);
// string 5
display.setTextColor(GxEPD_WHITE);
display.setFont(&FreeSansBold18pt7b);
display.setCursor(27, 92);
display.print("BUSY");
// string 6
display.setTextColor(GxEPD_BLACK);
display.setFont(&FreeSansBold9pt7b);
display.setCursor(152, 59);
display.print("Design review");
// string 7
display.setFont(&FreeSans9pt7b);
display.setCursor(152, 81);
display.print("until 15:30");
// string 8
display.setFont(&FreeSansBold12pt7b);
display.setCursor(152, 111);
display.print("5 min left");
display.display();
}
// [END lopaka generated]fillScreen(GxEPD_WHITE)limpia la imagen. El fondo de lienzo casi blanco se asigna a blanco en el panel.fillRoundRect(10, 42, 128, 76, 6, …)es el bloque: x, y, width, height y el radio de esquina R. En la pantalla Free el mismo bloque esdrawRoundRect, solo el contorno.setCursor(x, y)pone el texto en su línea base, la línea sobre la que se apoyan las letras, como en el episodio 1. Para «5 min left» esa es la fila 111.display.display()al final envía toda la imagen al panel con una actualización completa.
La pestaña Free tiene las mismas llamadas con los textos nuevos y drawRoundRect, en una función llamada drawFree().
Las fuentes vienen con la biblioteca
Las cuatro fuentes forman parte de la biblioteca Adafruit GFX, sobre la que está construida GxEPD2. Las incluyes tú, con el resto de tu configuración, así que desactiva Include fonts en los interruptores junto al código.

Tu código arriba: la biblioteca, las fuentes y tu panel exacto
Sobre la parte generada, incluye la biblioteca y las cuatro fuentes, y nombra tu panel exacto:
#include <GxEPD2_BW.h>
#include <Fonts/FreeSans9pt7b.h>
#include <Fonts/FreeSansBold9pt7b.h>
#include <Fonts/FreeSansBold12pt7b.h>
#include <Fonts/FreeSansBold18pt7b.h>
// 2.9" e-paper 296x128 (Waveshare V2): CS, DC, RST, BUSY
GxEPD2_BW<GxEPD2_290_T94_V2, GxEPD2_290_T94_V2::HEIGHT>
display(GxEPD2_290_T94_V2(5, 17, 16, 4));
#define MINUTE 60000ULGxEPD2_BW es la clase de pantalla blanco y negro. Dentro de los paréntesis angulares:
GxEPD2_290_T94_V2es el controlador para el módulo Waveshare blanco y negro de 2,9 pulgadas, versión 2. GxEPD2 tiene una clase aparte para casi cada panel, y varios paneles de 2,9 pulgadas parecen iguales pero necesitan clases distintas. Comprueba el modelo impreso en el cable plano del panel y encuentra la clase que coincide en los ejemplos de GxEPD2.GxEPD2_290_T94_V2::HEIGHTes cuántas filas guarda el programa en memoria. Con la altura completa, toda la imagen cabe en un búfer de fotogramas de 296 × 128 / 8 = 4.736 bytes, un bit por píxel.
Los cuatro números son los pines del ESP32 a los que está cableado el módulo: CS 5, DC 17, RST 16 y BUSY 4. MINUTE es un minuto en milisegundos.
Tu código abajo: completa, parcial, dormir
Bajo la parte generada, dibuja la pantalla busy una vez y después actualiza solo los minutos:
// partial refresh: only the minutes change
void showMinutes(int m) {
display.fillRect(152, 94, 136, 24, GxEPD_WHITE);
display.setFont(&FreeSansBold12pt7b);
display.setTextColor(GxEPD_BLACK);
display.setCursor(152, 111);
display.print(m);
display.print(" min left");
display.displayWindow(152, 94, 136, 24);
}
void setup() {
display.init();
display.setRotation(1);
drawBusy(); // full refresh
for (int m = 4; m >= 1; m--) {
delay(MINUTE);
showMinutes(m); // partial refresh
}
delay(MINUTE);
drawFree(); // full refresh
display.hibernate(); // panel off, the picture stays
}
void loop() {}setRotation(1): el panel tiene 128 píxeles de ancho y 296 de alto cuando está vertical. La rotación 1 lo gira de lado, para que coincida con el lienzo de 296×128.showMinutes()pinta un rectángulo blanco sobre el antiguo «5 min left», de la fila 94 a la fila 117 y de la columna 152 a la 287, imprime el número nuevo en la misma línea base, 111, y llama adisplayWindow()con el mismo rectángulo. Solo esa ventana se envía al panel, como una actualización parcial, sin destello.setup()dibuja Busy con una actualización completa, actualiza los minutos cuatro veces con actualizaciones parciales y después dibuja Free con una actualización completa. Cuatro actualizaciones parciales y después una completa: los fantasmas se borran cuando la reunión termina.hibernate()apaga el panel y lo pone en sueño profundo. La imagen permanece.
Todo ocurre una vez en setup(), así que loop() está vacío.
Puedes escribir estas líneas directamente en el panel de código de Lopaka, sobre el marcador BEGIN y bajo el marcador END. Después haz clic en Copy en la pestaña Busy y todo el sketch viene con ello. Pega drawFree() de la pestaña Free al final.
Todo el sketch
#include <GxEPD2_BW.h>
#include <Fonts/FreeSans9pt7b.h>
#include <Fonts/FreeSansBold9pt7b.h>
#include <Fonts/FreeSansBold12pt7b.h>
#include <Fonts/FreeSansBold18pt7b.h>
// 2.9" e-paper 296x128 (Waveshare V2): CS, DC, RST, BUSY
GxEPD2_BW<GxEPD2_290_T94_V2, GxEPD2_290_T94_V2::HEIGHT>
display(GxEPD2_290_T94_V2(5, 17, 16, 4));
#define MINUTE 60000UL
// [BEGIN lopaka generated]
void drawBusy(void) {
display.fillScreen(GxEPD_WHITE);
// string 1
display.setTextColor(GxEPD_BLACK);
display.setTextWrap(false);
display.setFont(&FreeSansBold12pt7b);
display.setCursor(10, 23);
display.print("ROOM 3");
// string 2
display.setFont(&FreeSans9pt7b);
display.setCursor(170, 21);
display.print("Updated 15:25");
// line 3
display.drawLine(10, 32, 285, 32, GxEPD_BLACK);
// rect 4
display.fillRoundRect(10, 42, 128, 76, 6, GxEPD_BLACK);
// string 5
display.setTextColor(GxEPD_WHITE);
display.setFont(&FreeSansBold18pt7b);
display.setCursor(27, 92);
display.print("BUSY");
// string 6
display.setTextColor(GxEPD_BLACK);
display.setFont(&FreeSansBold9pt7b);
display.setCursor(152, 59);
display.print("Design review");
// string 7
display.setFont(&FreeSans9pt7b);
display.setCursor(152, 81);
display.print("until 15:30");
// string 8
display.setFont(&FreeSansBold12pt7b);
display.setCursor(152, 111);
display.print("5 min left");
display.display();
}
// [END lopaka generated]
// partial refresh: only the minutes change
void showMinutes(int m) {
display.fillRect(152, 94, 136, 24, GxEPD_WHITE);
display.setFont(&FreeSansBold12pt7b);
display.setTextColor(GxEPD_BLACK);
display.setCursor(152, 111);
display.print(m);
display.print(" min left");
display.displayWindow(152, 94, 136, 24);
}
void setup() {
display.init();
display.setRotation(1);
drawBusy(); // full refresh
for (int m = 4; m >= 1; m--) {
delay(MINUTE);
showMinutes(m); // partial refresh
}
delay(MINUTE);
drawFree(); // full refresh
display.hibernate(); // panel off, the picture stays
}
void loop() {}
// [BEGIN lopaka generated]
void drawFree(void) {
display.fillScreen(GxEPD_WHITE);
// string 1
display.setTextColor(GxEPD_BLACK);
display.setTextWrap(false);
display.setFont(&FreeSansBold12pt7b);
display.setCursor(10, 23);
display.print("ROOM 3");
// string 2
display.setFont(&FreeSans9pt7b);
display.setCursor(170, 21);
display.print("Updated 15:30");
// line 3
display.drawLine(10, 32, 285, 32, GxEPD_BLACK);
// rect 4
display.drawRoundRect(10, 42, 128, 76, 6, GxEPD_BLACK);
// string 5
display.setFont(&FreeSansBold18pt7b);
display.setCursor(27, 92);
display.print("FREE");
// string 6
display.setFont(&FreeSansBold9pt7b);
display.setCursor(152, 59);
display.print("Next: Standup");
// string 7
display.setFont(&FreeSans9pt7b);
display.setCursor(152, 81);
display.print("at 16:00");
// string 8
display.setFont(&FreeSansBold12pt7b);
display.setCursor(152, 111);
display.print("30 min free");
display.display();
}
// [END lopaka generated]drawFree() viene después de setup(), que la llama. Eso está bien en un sketch de Arduino: las herramientas de Arduino declaran cada función al inicio del sketch por ti.
Paso 5: ejecútalo
Cablea el módulo a un ESP32
El módulo tiene ocho cables. Conéctalos a un ESP32 DevKit así:
- VCC a 3V3 y GND a GND. Los paneles de papel electrónico necesitan 3,3 V, en la energía y en las líneas de datos. Si tu módulo tiene un noveno pin, PWR, conéctalo también a 3V3.
- DIN a 23 y CLK a 18, los pines SPI por hardware del ESP32.
- CS a 5, DC a 17, RST a 16 y BUSY a 4, los pines del constructor.
En el IDE de Arduino, instala GxEPD2 de Jean-Marc Zingg desde el Library Manager y acepta Adafruit GFX Library con él. Elige la placa ESP32 Dev Module y sube el código. El sketch es pequeño para un ESP32:
Sketch uses 322220 bytes (24%) of program storage space. Maximum is 1310720 bytes.
Global variables use 28460 bytes (8%) of dynamic memory, leaving 299220 bytes for local variables. Maximum is 327680 bytes.El búfer de fotogramas de 4.736 bytes forma parte de esos 28.460 bytes.
Lo que verás
En el video el cartel se ejecuta en un ESP32 simulado con un módulo de papel electrónico de 2,9 pulgadas, con un «minuto» acelerado a dos segundos. Mira el inicio: el panel parpadea en blanco y negro y aparece el cartel. Esa es la actualización completa.
Después los minutos bajan, 4, 3, 2, 1, y solo cambia ese número, sin destello. Cuando la reunión termina, una actualización completa más dibuja la pantalla Free y el panel se duerme. El cartel permanece.

Paso 6: hazlo rojo
¿Quieres algo de rojo? Muchos paneles añaden un tercer color. Selecciona la pantalla Busy, selecciona su rect 4 y pon el Fill color en #FF0000, rojo de la paleta. El código cambia una palabra:
display.fillRoundRect(10, 42, 128, 76, 6, GxEPD_RED);
Para un panel de tres colores, cambia la parte superior del sketch a la clase de tres colores y el controlador de un panel rojo de 2,9 pulgadas:
#include <GxEPD2_3C.h>
// 2.9" three-colour e-paper 296x128 (black, white, red): CS, DC, RST, BUSY
GxEPD2_3C<GxEPD2_290_C90c, GxEPD2_290_C90c::HEIGHT>
display(GxEPD2_290_C90c(5, 17, 16, 4));En un panel de tres colores el bloque sale rojo. Pero los paneles de tres colores son lentos: una actualización completa de este lleva unos 26 segundos y no tiene actualización parcial rápida, así que cada actualización redibuja toda la pantalla. Dibuja el cartel una vez y salta la cuenta atrás de minutos:
void setup() {
display.init();
display.setRotation(1);
drawBusy(); // full refresh, about 26 s on a real panel
display.hibernate();
}La imagen roja necesita un segundo búfer: el sketch ocupa 33.196 bytes de RAM (10%) en lugar de 28.460.

Paso 7: publícalo
Ahora publícalo. Haz clic en el ícono de engranaje junto al nombre del proyecto para abrir los ajustes del proyecto, activa Publish to Gallery y haz clic en Save.

Un proyecto publicado muestra sus pantallas solo cuando las haces públicas. Selecciona la pantalla Busy y haz clic en Make Public en el inspector. La pantalla recibe una insignia Public y el botón se convierte en Make Private.
Paso 8: comparte el enlace
Haz clic en Share en la barra superior y elige Copy public link. El enlace apunta a esta pantalla en la galería: https://lopaka.app/gallery/<project>/<screen>.

Envía el enlace a quien sea: un compañero, un cliente, un hilo de foro. Abren el diseño y su código directo en el navegador. Con Star lo guardan en sus favoritos y con Fork obtienen su propia copia en My Projects para construir sobre ella, mientras la tuya permanece intacta.

Antes de publicar: cinco errores
Antes de publicar cualquier pantalla, recorre esta lista. Estos son los cinco errores que más vemos en pantallas embebidas:
- Texto demasiado pequeño. Lee la pantalla desde donde estará la gente: para un cartel de sala, la puerta.
- Colores que el panel no puede mostrar. Diseña con la paleta real del panel: blanco y negro, más rojo o amarillo si tu panel lo tiene.
- Texto que no cabe. Prueba el nombre más largo que tendrás nunca, no el bonito y corto. «Design review» cabe junto al bloque; «Quarterly planning» se sale del borde del cartel.
- Actualizar demasiado a menudo. Redibuja solo lo que cambió: es más rápido y en papel electrónico ahorra batería.
- Un dispositivo muerto aún muestra su última imagen. Este es el traicionero en papel electrónico: un cartel con la batería agotada parece uno que funciona. Muestra siempre cuándo se actualizó, para que nadie confíe en un cartel desactualizado.

El cartel de este episodio cubre el quinto: «Updated 15:25» en la esquina le dice a todos qué antigüedad tiene la imagen.
Resumen
- El papel electrónico mantiene su imagen sin energía: las partículas se quedan donde el último voltaje las puso, así que el panel necesita energía solo para cambiar la imagen.
- Actualización completa, actualización parcial, dormir:
display()redibuja todo con un destello,displayWindow()cambia una zona sin él, yhibernate()apaga el panel mientras la imagen permanece. - Comprueba y después publica: recorre la lista de cinco puntos, publica el proyecto, haz pública la pantalla y comparte el enlace.
Dibújalo, consigue el código, míralo en la pantalla. Abre Lopaka y diseña tu propio cartel de papel electrónico.