Lopaka Basics 09: más allá de Arduino

C++. YAML. Python. Más Python. Y C para un Flipper Zero. Cinco programas distintos dibujan la misma pantalla y Lopaka escribió el código de dibujo para cada uno de ellos.
En este episodio descubrirás por qué las placas ejecutan código en lenguajes tan distintos, y qué separa el firmware compilado de un intérprete de Python. Después dibujarás un pequeño monitor de plantas en Lopaka para MicroPython, crearás la misma pantalla para CircuitPython, ESPHome y Flipper Zero, y ejecutarás tres de ellos en placas simuladas.
Lo que vas a crear: una pantalla de monitor de plantas de 128×64: una maceta con tres hojas, el nombre de la planta, una barra de humedad y un recordatorio para regarla, en cuatro plataformas. Lo que necesitas: un navegador y el simulador en línea del episodio 1. Las placas son opcionales: una Raspberry Pi Pico o un ESP32 con un OLED SSD1306 de 128×64, o un Flipper Zero.
Dos formas de ejecutar código
Compilado: todo el firmware, cada vez
El código Arduino es compilado. Un compilador convierte tu sketch en firmware, instrucciones de máquina en bruto para el chip, y flasheas todo el firmware en la placa. Cambias una línea y compilas y flasheas todo otra vez.
ESPHome funciona igual: tu archivo YAML se convierte en C++ y el C++ se convierte en firmware. Una app de Flipper Zero también es compilada.
Interpretado: un archivo de texto en la placa
MicroPython y CircuitPython le dan la vuelta. Flasheas un intérprete en la placa una vez. Después, tu programa es un archivo de texto simple en la placa, como main.py, y el intérprete lo lee línea a línea. Cambias una línea, guardas el archivo y se ejecuta otra vez, sin compilador entre medias.
El precio es la velocidad y la memoria: el propio intérprete se ejecuta en el chip y ocupa su parte. En placas pequeñas, mantén el dibujo sencillo.

Paso 1: elige una plataforma
En Lopaka, la plataforma es lo primero que eliges en un proyecto nuevo. Haz clic en + New Project y abre la lista Platform. Junto al nombre de cada plataforma hay una insignia con el lenguaje de su código: C++, YAML, Python o C.

Empieza con Micropython. Mantén el tamaño de pantalla en 128×64, dale al proyecto un título, «Plant Monitor», y haz clic en Save.
Paso 2: dibuja la pantalla
El diseño es un monitor de plantas diminuto. Usa solo rectángulos, círculos, líneas y texto, así que cada plataforma en este episodio puede dibujarlo.
Dibújalo con las herramientas Rectangle, ellipse, line y texto (Aa), y escribe los valores exactos en el inspector:
- La maceta: dos rectángulos llenos, el borde en X
8, Y42, W26, H5, y la maceta bajo él en X11, Y47, W20, H15. Activa Filled para ambos. - El tallo: una línea de
21, 24a21, 41. - Las hojas: tres elipses con Filled activado y ambos radios iguales: izquierda en X
9, Y26con radio5, derecha en X23, Y22con radio5, y superior en X15, Y11con radio6. - El nombre: el texto
BASILen X44, Y11, y una línea bajo él de44, 13a123, 13. - La barra de humedad: el texto
MOISTUREen X44, Y27, un rectángulo en X44, Y30, W80, H10, y un rectángulo lleno dentro de él en X46, Y32, W21, H6: el nivel de agua. - El recordatorio: el texto
WATER ME!en X44, Y57.
MicroPython tiene una fuente, 8×8 píxeles, así que las capas de texto no tienen lista de fuentes.
Por último, dale nombres a todo. Haz doble clic en Screen 1 en el panel de capas y renómbrala a Plant, después nombra las capas rim, pot, stem, leaf_left, leaf_right, leaf_top, title, rule, caption, bar, level y hint. El nombre de la pantalla se convierte en el nombre de la función y los nombres de capa se convierten en comentarios en el código.

Paso 3: tu código
Una llamada por capa
El código es Python simple. Cada capa se convierte en una llamada en el objeto de pantalla: fill_rect, rect, line, ellipse y text. Al final, display.show() envía la imagen terminada a la pantalla.
El código generado de Lopaka se sitúa entre los marcadores BEGIN y END. Unas líneas tuyas van alrededor: cuatro sobre los marcadores configuran la pantalla y una bajo ellos llama a la función. Escríbelas directamente en el panel de código de Lopaka. Lopaka mantiene tus líneas fuera de los marcadores y Copy toma todo el archivo:
from machine import Pin, I2C
from ssd1306 import SSD1306_I2C
i2c = I2C(0, scl=Pin(5), sda=Pin(4))
display = SSD1306_I2C(128, 64, i2c)
# [BEGIN lopaka generated]
# SSD1306 OLED display library: https://github.com/PerfecXX/MicroPython-SSD1306/blob/main/ssd1306.py
def drawPlant(display):
# rim
display.fill_rect(8, 42, 26, 5, 1)
# pot
display.fill_rect(11, 47, 20, 15, 1)
# stem
display.line(21, 24, 21, 41, 1)
# leaf_left
display.ellipse(14, 31, 5, 5, 1, True)
# leaf_right
display.ellipse(28, 27, 5, 5, 1, True)
# leaf_top
display.ellipse(21, 17, 6, 6, 1, True)
# title
display.text("BASIL", 44, 2, 1)
# rule
display.line(44, 13, 123, 13, 1)
# caption
display.text("MOISTURE", 44, 18, 1)
# bar
display.rect(44, 30, 80, 10, 1)
# level
display.fill_rect(46, 32, 21, 6, 1)
# hint
display.text("WATER ME!", 44, 48, 1)
display.show()
# [END lopaka generated]
drawPlant(display)Tus cuatro líneas crean un bus I2C en los pines GP5 (SCL) y GP4 (SDA) de la Pico y un objeto de pantalla de 128×64 en él. El último argumento de la mayoría de las llamadas, 1, es el color: píxel encendido.
Los números que cambiaron
Dos tipos de números en este código difieren del inspector:
- Las hojas. En el inspector, la hoja izquierda tiene X
9, Y26y radio5: la esquina superior izquierda de la caja que la rodea.ellipse()toma el centro, así que el código diceellipse(14, 31, 5, 5, ...). ElTrueal final la rellena. - El texto. Colocaste
BASILen Y11y el código dicetext("BASIL", 44, 2, ...). Eltext()de MicroPython toma la esquina superior izquierda del texto, así que Lopaka convierte la posición por ti.
Paso 4: ejecútalo en el simulador
El simulador ejecuta MicroPython real en una Raspberry Pi Pico simulada:
- Crea un proyecto Raspberry Pi Pico nuevo con MicroPython.
- Añade una pantalla SSD1306 OLED y conecta cuatro cables: GND a GND, VCC a 3V3, SCL a GP5 y SDA a GP4.
- Añade un segundo archivo,
ssd1306.py, con el controlador de pantalla del enlace en el comentario del código generado. Tu primera línea importaSSD1306_I2Cde él. Sin este archivo el programa se detiene enimport. - Pega el código copiado en
main.pyy pulsa Play.
La planta aparece en la pantalla. No hay paso de compilación: el intérprete ejecuta el archivo tal cual.
En una Pico real, copia ambos archivos, main.py y ssd1306.py, en la placa. La placa ejecuta main.py cada vez que arranca.
Paso 5: un proyecto CircuitPython
Ahora la misma pantalla en CircuitPython. Un proyecto mantiene la plataforma que elegiste, así que la plataforma nueva necesita un proyecto nuevo: + New Project, plataforma CircuitPython, tamaño 128×64.
Coloca las mismas capas con los mismos números y dales a la pantalla y a las capas los mismos nombres. Dos cosas se ven distintas en el inspector:
- Un círculo tiene un radio, R, en lugar de dos.
- El texto usa Terminus, la fuente integrada en CircuitPython. Su campo Size multiplica la fuente:
1es el tamaño normal.

Paso 6: objetos en un grupo
El código
El código se ve distinto. Cada forma es un objeto: un Rect, un Circle, una Line o un Label para texto. Cada objeto se añade a un grupo y la pantalla muestra ese grupo:
import board, busio, displayio, terminalio
import adafruit_displayio_ssd1306
from adafruit_display_text import label
from adafruit_display_shapes.rect import Rect
from adafruit_display_shapes.circle import Circle
from adafruit_display_shapes.line import Line
displayio.release_displays()
i2c = busio.I2C(board.GP5, board.GP4)
bus = displayio.I2CDisplay(i2c, device_address=0x3C)
display = adafruit_displayio_ssd1306.SSD1306(bus, width=128, height=64)
# [BEGIN lopaka generated]
# Init display
Plant = displayio.Group()
display.root_group = Plant
def drawPlant():
# rim
rim = Rect(8, 42, 26, 5, fill=0xFFFFFF, outline=0xFFFFFF)
Plant.append(rim)
# pot
pot = Rect(11, 47, 20, 15, fill=0xFFFFFF, outline=0xFFFFFF)
Plant.append(pot)
# stem
stem = Line(21, 24, 21, 41, color=0xFFFFFF)
Plant.append(stem)
# leaf_left
leaf_left = Circle(14, 31, 5, fill=0xFFFFFF, outline=0xFFFFFF)
Plant.append(leaf_left)
# leaf_right
leaf_right = Circle(28, 27, 5, fill=0xFFFFFF, outline=0xFFFFFF)
Plant.append(leaf_right)
# leaf_top
leaf_top = Circle(21, 17, 6, fill=0xFFFFFF, outline=0xFFFFFF)
Plant.append(leaf_top)
# title
title = label.Label(terminalio.FONT, text="BASIL", color=0xFFFFFF)
title.x = 44
title.y = 6
Plant.append(title)
# rule
rule = Line(44, 13, 123, 13, color=0xFFFFFF)
Plant.append(rule)
# caption
caption = label.Label(terminalio.FONT, text="MOISTURE", color=0xFFFFFF)
caption.x = 44
caption.y = 22
Plant.append(caption)
# bar
bar = Rect(44, 30, 80, 10, outline=0xFFFFFF)
Plant.append(bar)
# level
level = Rect(46, 32, 21, 6, fill=0xFFFFFF, outline=0xFFFFFF)
Plant.append(level)
# hint
hint = label.Label(terminalio.FONT, text="WATER ME!", color=0xFFFFFF)
hint.x = 44
hint.y = 52
Plant.append(hint)
# [END lopaka generated]
drawPlant()
while True:
passLas once líneas sobre los marcadores son tuyas: los imports para las formas y la etiqueta de texto, y la configuración de pantalla en los mismos pines, GP5 y GP4, en la dirección I2C habitual de estos módulos, 0x3C. displayio.release_displays() libera la pantalla en caso de que una ejecución anterior aún la tenga. Bajo los marcadores, drawPlant() crea el grupo y while True: pass mantiene el programa ejecutándose. Cuando un programa CircuitPython termina, la placa recupera la pantalla para su propia salida de texto y la planta desaparecería.
La pantalla sigue mostrando el grupo
display.root_group = Plant le dice a la pantalla qué grupo mostrar. Desde entonces la pantalla redibuja ese grupo por sí sola. Tu código nunca envía un fotograma: si mueves un objeto o cambias el texto de una etiqueta, la pantalla sigue.

Los colores son valores hexadecimales de 24 bits: 0xFFFFFF es blanco, un píxel encendido en esta pantalla. Y title.y = 6 es el centro de la línea de texto: un Label se coloca por su borde izquierdo y su centro vertical, así que Lopaka convierte la Y 11 del inspector.
Ejecútalo en el simulador
Crea un proyecto Raspberry Pi Pico nuevo con CircuitPython y cablea la pantalla como en el paso 4. Añade un archivo requirements.txt con las tres bibliotecas de pantalla de Adafruit que importa el código:
adafruit_displayio_ssd1306
adafruit_display_text
adafruit_display_shapesPega el código en code.py y pulsa Play. Las bibliotecas se cargan cuando la simulación empieza y aparece la misma planta. En una placa real, copia estas tres bibliotecas en la carpeta lib de la unidad CIRCUITPY.
Paso 7: un proyecto ESPHome
ESPHome está hecho para dispositivos de hogar inteligente: describes el dispositivo en un archivo YAML y aparece en Home Assistant. Crea un proyecto nuevo con la plataforma ESPHome, tamaño 128×64, y coloca las mismas capas con los mismos números.
El texto funciona distinto aquí. ESPHome usa fuentes TrueType en cualquier tamaño y la lista Font ofrece Courier Prime, Fira Code, Merriweather, Montserrat, Roboto, Roboto Mono y Ubuntu. Selecciona BASIL, elige Roboto y pon Size en 12. El texto siguiente que añadas mantiene Roboto pero empieza en tamaño 14, así que pon 12 también en MOISTURE y WATER ME!. Cada fuente y tamaño que uses se convierte en una entrada aparte en el código y el dispositivo guarda cada una.
En ESPHome, la Y de un texto es la parte inferior de su caja de texto, y Roboto 12 hace una caja de 14 píxeles de alto. En Y 11, la parte superior de BASIL sobresaldría sobre la pantalla. Mueve los tres textos un poco más abajo: Y 14, 30 y 59.

Paso 8: una fuente y una lambda
Lo que escribe Lopaka
En ESPHome, el diseño se convierte en dos piezas de YAML: una sección font: que trae la fuente de Google Fonts y una lambda, unas líneas de C++ que viven dentro del archivo YAML:
# [BEGIN lopaka generated]
font:
- file: 'gfonts://Roboto'
id: Roboto
size: 12
display:
lambda: |-
// rim
it.filled_rectangle(8, 42, 26, 5);
// pot
it.filled_rectangle(11, 47, 20, 15);
// stem
it.line(21, 24, 21, 41);
// leaf_left
it.filled_circle(14, 31, 5);
// leaf_right
it.filled_circle(28, 27, 5);
// leaf_top
it.filled_circle(21, 17, 6);
// title
it.print(44, 0, id(Roboto), "BASIL");
// rule
it.line(44, 13, 123, 13);
// caption
it.print(44, 16, id(Roboto), "MOISTURE");
// bar
it.rectangle(44, 30, 80, 10);
// level
it.filled_rectangle(46, 32, 21, 6);
// hint
it.print(44, 45, id(Roboto), "WATER ME!");
# [END lopaka generated]it es la pantalla. filled_circle toma el centro y el radio, como el ellipse de MicroPython. print(44, 0, ...) toma la esquina superior izquierda de la caja de texto: la caja de BASIL va de la fila 0 a la fila 14 y el inspector muestra su parte inferior, 14.
Ponlo en la configuración de tu dispositivo
El resto del archivo YAML describe tu dispositivo y lo escribes tú: la placa, los pines I2C y la pantalla. Pon la sección font: de Lopaka en el nivel superior y su lambda: bajo tu pantalla, con la sangría tal cual:
esphome:
name: plant-monitor
esp32:
board: esp32dev
framework:
type: esp-idf
i2c:
sda: 21
scl: 22
font:
- file: 'gfonts://Roboto'
id: Roboto
size: 12
display:
- platform: ssd1306_i2c
model: 'SSD1306 128x64'
address: 0x3C
lambda: |-
// rim
it.filled_rectangle(8, 42, 26, 5);
// ...the rest of the lambda from LopakaCopia toda la lambda, de // rim al último it.print. La única línea que Lopaka no te da bajo display: es - platform: ssd1306_i2c con su modelo y dirección: esa parte le dice a ESPHome qué pantalla tienes.
Paso 9: compila el firmware y ejecútalo
ESPHome es compilado. Guarda el archivo como plant.yaml y ejecuta esphome compile plant.yaml. ESPHome convierte todo el archivo en C++, con tu lambda dentro línea a línea, y compila firmware para el ESP32.

En el simulador, crea un proyecto ESP32 y conecta la pantalla: SDA al pin 21, SCL al pin 22, GND a GND y VCC a 3V3. Carga el firmware.factory.bin compilado con el comando Upload Firmware and Start Simulation del simulador. La planta está en la pantalla.
En un ESP32 real, esphome run plant.yaml compila el mismo firmware y lo flashea por USB. Como el dibujo vive en una lambda, las mismas líneas pueden imprimir valores de sensor en vivo más tarde, por ejemplo la humedad real en lugar de una barra fija.
Paso 10: un proyecto Flipper Zero
El Flipper Zero tiene su propia pantalla, también de 128×64 píxeles, negro sobre naranja. Crea un proyecto nuevo con la plataforma Flipper Zero, One. Esta plataforma no tiene un tamaño predeterminado: abre Screen Size y elige Zero Horizontal (128×64).
Coloca las mismas capas con los mismos números. Lopaka conoce las cuatro fuentes integradas del Flipper: FontPrimary, FontSecondary, FontKeyboard y FontBigNumbers. El texto nuevo empieza en FontPrimary, que es la fuente para los tres textos aquí.

Paso 11: C para el lienzo
Lopaka escribe C para el lienzo del Flipper:
// [BEGIN lopaka generated]
void drawPlant(Canvas* canvas) {
canvas_set_bitmap_mode(canvas, true);
// rim
canvas_draw_box(canvas, 8, 42, 26, 5);
// pot
canvas_draw_box(canvas, 11, 47, 20, 15);
// stem
canvas_draw_line(canvas, 21, 24, 21, 41);
// leaf_left
canvas_draw_disc(canvas, 14, 31, 5);
// leaf_right
canvas_draw_disc(canvas, 28, 27, 5);
// leaf_top
canvas_draw_disc(canvas, 21, 17, 6);
// title
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 44, 11, "BASIL");
// rule
canvas_draw_line(canvas, 44, 13, 123, 13);
// caption
canvas_draw_str(canvas, 44, 27, "MOISTURE");
// bar
canvas_draw_frame(canvas, 44, 30, 80, 10);
// level
canvas_draw_box(canvas, 46, 32, 21, 6);
// hint
canvas_draw_str(canvas, 44, 57, "WATER ME!");
}
// [END lopaka generated]canvas_draw_frame dibuja un contorno, canvas_draw_box un rectángulo lleno y canvas_draw_disc un círculo lleno alrededor de su centro. La y de canvas_draw_str es la línea base, el mismo número que en el inspector, así que los textos mantienen 11, 27 y 57. Los tres textos usan FontPrimary, así que canvas_set_font aparece una vez.
Esta función va en el callback de dibujo de una app de Flipper: la función que el Flipper llama cada vez que redibuja la pantalla de tu app. Creas la app con las herramientas propias del Flipper.

Cinco plataformas una al lado de la otra
Una al lado de la otra: un Arduino Uno, un ESP32 ejecutando ESPHome, una Pico con MicroPython, una Pico con CircuitPython y la versión de Flipper Zero, lista para su app. El Arduino Uno ejecuta el mismo diseño en un proyecto U8g2, configurado como en el episodio 1, con la fuente 6x13B.
Qué cambia entre plataformas
Mismos números, palabras distintas
Mira la barra de humedad en las cinco plataformas. Cuarenta y cuatro, treinta, ochenta, diez: los números son los mismos en todas partes. Solo cambian las palabras alrededor: el nombre de la llamada, el 1 para el color en MicroPython, el outline= en CircuitPython, el canvas delante en el Flipper.

Cada plataforma tiene sus propias fuentes
Las fuentes también cambian, porque cada plataforma tiene su propio conjunto: Petme 8×8 en MicroPython, Terminus en CircuitPython, Roboto 12 en ESPHome, FontPrimary en el Flipper Zero y 6x13B en U8g2. Por eso los textos necesitaron una Y distinta en ESPHome. Lopaka dibuja cada proyecto con las fuentes propias de la plataforma, así que el lienzo muestra el texto que obtendrás en el dispositivo.

Resumen
- Compilado o interpretado, los píxeles se mantienen iguales: Arduino, ESPHome y las apps de Flipper se compilan en firmware; MicroPython y CircuitPython ejecutan un archivo de texto mediante un intérprete.
- Elige la plataforma cuando creas el proyecto: la plataforma decide el lenguaje del código y cada plataforma necesita su propio proyecto.
- Mismas capas, lenguaje nuevo: coloca las mismas capas con los mismos números y Lopaka escribe el código en el lenguaje de esa plataforma.
Próximo episodio: E-paper & Shipping. Pantallas que mantienen su imagen sin energía. Lee el episodio 10 o míralo en YouTube.
¿Listo para probar? Abre Lopaka y lleva tu pantalla a una plataforma nueva.