Ir al contenido

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

Un módulo de papel electrónico de 2,9 pulgadas mostrando un cartel de sala de reuniones con ROOM 3, Updated 15:25, un bloque negro BUSY, Design review, until 15:30 y 4 min left

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.

Dos pantallas una al lado de la otra con la energía cortada: el TFT retroiluminado está oscuro a 0 W, el módulo de papel electrónico aún muestra el cartel ROOM 3 BUSY a 0 W

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.

Cómo funciona la tinta electrónica: tres cápsulas entre un electrodo superior transparente y un electrodo inferior, con partículas negativas arriba y partículas blancas positivas abajo; el píxel que ves se mantiene oscuro a 0 W, 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.

Actualización completa frente a parcial: a la izquierda todo el cartel parpadea y lleva unos 4 segundos; a la derecha solo cambian los minutos en menos de un segundo, sin destello, y un fantasma tenue del dígito antiguo se ve tras el nuevo 1

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.

El diálogo Create New Project con Platform GxEPD2 (e-paper), el título Room Sign, el tamaño de pantalla 296×128 y el color de fondo #F8F8F8

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:

  1. El nombre de la sala. Elige la herramienta text (Aa), escribe ROOM 3 y elige FreeSansBold12pt7b. Ponlo en la esquina superior izquierda.
  2. La hora de actualización. Añade Updated 15:25 en FreeSans9pt7b, en la esquina superior derecha.
  3. Un divisor. Elige la herramienta line y dibuja una línea bajo el encabezado, de x = 10 a x = 285 en y = 32.
  4. El bloque de estado. Elige la herramienta rectangle, arrastra un rectángulo y pon X 10, Y 42, W 128, H 76, R 6, después activa Filled.
  5. El estado. Añade BUSY en FreeSansBold18pt7b sobre el bloque y pon su Fill color en #FFFFFF, blanco sobre negro.
  6. La reunión. A la derecha del bloque, añade tres líneas de texto: Design review en FreeSansBold9pt7b, until 15:30 en FreeSans9pt7b y 5 min left en FreeSansBold12pt7b.

La pantalla Busy en el editor de Lopaka: ROOM 3 y Updated 15:25 sobre una línea divisoria, un bloque redondeado negro con BUSY en blanco, y Design review, until 15:30 y 5 min left a la derecha; las capas string 1 a string 8, line 3 y rect 4 a la izquierda

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.

La paleta GxEPD2 de 9 colores: negro, blanco, gris claro, gris oscuro, rojo, amarillo, azul, verde y naranja; tu panel muestra blanco y negro, más rojo o amarillo

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:

  • BUSY se convierte en FREE, con el Fill color de vuelta a #000000.
  • Selecciona rect 4 y desactiva Filled: el bloque se convierte en un marco.
  • Updated 15:25 se convierte en Updated 15:30.
  • Las líneas de reunión se convierten en Next: Standup, at 16:00 y 30 min free.

Ambas pantallas una al lado de la otra en el lienzo: Busy con el bloque BUSY negro, y Free con FREE en un marco con contorno, Next: Standup, at 16:00 y 30 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:

cpp
// [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 es drawRoundRect, 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.

El panel de código con las pestañas Busy y Free: la función generada drawBusy empieza tras el marcador BEGIN, e Include fonts está desactivado en los interruptores de la derecha

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:

cpp
#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

GxEPD2_BW es la clase de pantalla blanco y negro. Dentro de los paréntesis angulares:

  • GxEPD2_290_T94_V2 es 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::HEIGHT es 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:

cpp
// 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 a displayWindow() 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 ​

cpp
#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.

El módulo de papel electrónico de 2,9 pulgadas tras la reunión, dormido: ROOM 3, Updated 15:30, FREE en un marco con contorno, Next: Standup, at 16:00 y 30 min free

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:

cpp
display.fillRoundRect(10, 42, 128, 76, 6, GxEPD_RED);

La pantalla Busy con un bloque rojo tras el BUSY blanco, el inspector mostrando rect 4 con Fill color #FF0000, y la línea fillRoundRect(10, 42, 128, 76, 6, GxEPD_RED) resaltada en el código

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:

cpp
#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:

cpp
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.

El mismo cartel en un módulo de papel electrónico de tres colores de 2,9 pulgadas: ROOM 3, Updated 15:25, un bloque BUSY rojo, Design review, until 15:30 y 5 min left

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.

El diálogo Edit Project para Room Sign con Platform GxEPD2 (e-paper), tamaño de pantalla 296 por 128 y Publish to Gallery marcado

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.

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>.

El menú Share abierto con Copy public link, Presentation y Save As; la pantalla Busy tiene una insignia Public, el proyecto está marcado Public project, y el inspector muestra Make Private

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.

La página de galería de Room Sign: la pantalla Busy con el bloque rojo, su código GxEPD2 generado abajo, y Star, Fork y Share arriba a la derecha

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:

  1. Texto demasiado pequeño. Lee la pantalla desde donde estará la gente: para un cartel de sala, la puerta.
  2. 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.
  3. 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.
  4. Actualizar demasiado a menudo. Redibuja solo lo que cambió: es más rápido y en papel electrónico ahorra batería.
  5. 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.

Antes de publicar, los cinco errores que más vemos: un cartel de sala mostrando Updated yesterday 15:25 con una batería al 0%, junto a una lista: texto lo bastante grande, solo colores del panel, texto que cabe, actualizar solo cambios y mostrar actualizado a, todo marcado

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 ​

  1. 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.
  2. Actualización completa, actualización parcial, dormir: display() redibuja todo con un destello, displayWindow() cambia una zona sin él, y hibernate() apaga el panel mientras la imagen permanece.
  3. 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.