Ir al contenido

Lopaka Basics 09: más allá de Arduino ​

Cinco dispositivos mostrando la misma pantalla de monitor de plantas con una maceta, BASIL, MOISTURE, una barra y WATER ME!, un Arduino Uno, un ESP32 con ESPHome, una Pico con MicroPython, una Pico con CircuitPython y un Flipper Zero

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.

Compilado frente a interpretado: arriba, un sketch pasa por un compilador a firmware que se flashea otra vez por cada cambio, para Arduino, ESPHome y apps de Flipper; abajo, un intérprete se flashea una vez y ejecuta main.py en la pantalla, guardado y ejecutado otra vez, a costa de velocidad y memoria

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.

La lista Platform en el diálogo Create New Project: ESPHome con la insignia YAML, Micropython y CircuitPython con la insignia Python y Flipper Zero, One con la insignia 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, Y 42, W 26, H 5, y la maceta bajo él en X 11, Y 47, W 20, H 15. Activa Filled para ambos.
  • El tallo: una línea de 21, 24 a 21, 41.
  • Las hojas: tres elipses con Filled activado y ambos radios iguales: izquierda en X 9, Y 26 con radio 5, derecha en X 23, Y 22 con radio 5, y superior en X 15, Y 11 con radio 6.
  • El nombre: el texto BASIL en X 44, Y 11, y una línea bajo él de 44, 13 a 123, 13.
  • La barra de humedad: el texto MOISTURE en X 44, Y 27, un rectángulo en X 44, Y 30, W 80, H 10, y un rectángulo lleno dentro de él en X 46, Y 32, W 21, H 6: el nivel de agua.
  • El recordatorio: el texto WATER ME! en X 44, Y 57.

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.

El monitor de plantas en Lopaka en un proyecto MicroPython: la capa bar está seleccionada con X 44, Y 30, W 80 y H 10 en el inspector, y el panel de código resalta display.rect(44, 30, 80, 10, 1)

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:

python
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, Y 26 y radio 5: la esquina superior izquierda de la caja que la rodea. ellipse() toma el centro, así que el código dice ellipse(14, 31, 5, 5, ...). El True al final la rellena.
  • El texto. Colocaste BASIL en Y 11 y el código dice text("BASIL", 44, 2, ...). El text() 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:

  1. Crea un proyecto Raspberry Pi Pico nuevo con MicroPython.
  2. Añade una pantalla SSD1306 OLED y conecta cuatro cables: GND a GND, VCC a 3V3, SCL a GP5 y SDA a GP4.
  3. 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 importa SSD1306_I2C de él. Sin este archivo el programa se detiene en import.
  4. Pega el código copiado en main.py y 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: 1 es el tamaño normal.

El monitor de plantas en Lopaka en un proyecto CircuitPython: la capa de título BASIL está seleccionada en X 44, Y 11 con Size 1, y el código muestra title = label.Label(terminalio.FONT, text="BASIL") con title.y = 6

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:

python
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:
    pass

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

Objetos CircuitPython en un grupo: Plant = Group() guarda objetos Rect para el borde, la maceta y la barra, objetos Circle para las hojas y objetos Label para BASIL y los demás textos, y display.root_group muestra el grupo en la pantalla

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_shapes

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

El monitor de plantas en Lopaka en un proyecto ESPHome: la capa de título BASIL en Roboto, Size 12, en X 44 e Y 14; el panel de código empieza con una sección font, file gfonts://Roboto, id Roboto, size 12

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:

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:

yaml
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 Lopaka

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

El YAML del dispositivo con la sección font y la lambda resaltadas, y una flecha al src/main.cpp que genera ESPHome, donde las mismas líneas filled_rectangle, line, filled_circle y print se sitúan dentro de set_writer

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

El monitor de plantas en Lopaka en un proyecto Flipper Zero, negro sobre naranja: la capa de título BASIL está seleccionada y la lista de fuentes muestra FontPrimary, FontSecondary, FontKeyboard y FontBigNumbers

Paso 11: C para el lienzo ​

Lopaka escribe C para el lienzo del Flipper:

c

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

El callback de dibujo de una app de Flipper llamando a drawPlant(canvas), la función de Lopaka, junto a un Flipper Zero mostrando el monitor de plantas negro sobre naranja; créala con el SDK de apps de 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.

La barra de humedad en cinco lenguajes: u8g2.drawFrame(44, 30, 80, 10) para Arduino con U8g2, it.rectangle(44, 30, 80, 10) para ESPHome, display.rect(44, 30, 80, 10, 1) para MicroPython, bar = Rect(44, 30, 80, 10, outline=0xFFFFFF) para CircuitPython y canvas_draw_frame(canvas, 44, 30, 80, 10) para Flipper Zero

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.

El mismo monitor de plantas con cinco fuentes: Petme 8×8 para MicroPython, Terminus para CircuitPython, Roboto 12 para ESPHome, FontPrimary para Flipper Zero y 6x13B para U8g2: mismos números, palabras distintas, las fuentes propias de cada plataforma

Resumen ​

  1. 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.
  2. Elige la plataforma cuando creas el proyecto: la plataforma decide el lenguaje del código y cada plataforma necesita su propio proyecto.
  3. 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.