Saltearse al contenido

Unidad 4

Introducción 📜

En esta unidad vas a aprender cómo enviar líneas de texto desde el micro:bit a un sketch en p5.js. Dichas líneas de textos tendrán información de varios sensores del micro:bit, separada por comas, y terminadas por un salto de línea. Adicionalmente, vas a practicar de nuevo la técnica de programación de máquinas de estados.

¿Qué aprenderás en esta unidad? 💡

Vas a enviar información desde el micro:bit a un sketch en p5.js. Para ello, usarás protocolos de comunicación ASCII. Antes de hacerlo, te pediré que explores algunas aplicaciones interactivas hechas con p5.js que te servirán de inspiración. Luego, en la fase de aplicación vas a seleccionar una de esas aplicaciones y la modificarás para que puedas controlarla con el micro:bit; sin embargo, antes de hacerlo, vas a ver cómo lo puedes lograr en la fase de investigación.

Actividad 01

Algunos ejemplos inspiradores

Esta actividad puede tomarte un poco más de una hora, ¿Vale? Todo dependa de la curiosidad que tengas.

🎯 Enunciado: te voy a mostrar algunos ejemplos interesantes y fuentes de inspiración que pueden ser de utilidad a la hora de diseñar tus propias aplicaciones interactivas con p5.js.

Al explorar este sitio verifica si el ejemplo está hecho con p5.js siguiendo estos pasos:

  1. Dale click al tercer ícono de la parte superior derecha.

tercer icono

  1. Luego en esta parte de la página:

p5.js

  1. Selecciona el ícono de configuración:

p5.js

  1. Aquí puedes verificar si el modo del sketch es p5.js:

p5.js

Nota también que en esta parte de la página puedes verificar si el código requiere alguna biblioteca adicional y archivos para su correcto funcionamiento (pestaña FILES al lado de SKETCH).

Otros sitios que te pueden servir de inspiración:

📤 Entrega: de cada uno de estos sitios selecciona un ejemplo que te haya llamado la atención. Luego, en tu entrega, realiza lo siguiente:

  • Copia el enlace del ejemplo.
  • Describe brevemente qué te llamó la atención del ejemplo.
  • Trata de entender cómo está hecho el sketch. ¿Qué funciones de p5.js se están utilizando? ¿Qué técnicas de programación se están aplicando? Puedes buscar en la documentación de p5.js para entender mejor el código.
  • Vas a crear una cuenta en p5.js (tal vez ya la tienes) y vas a recrear en el editor de p5.js el ejemplo. Realiza una modificación a cada ejemplo y describe qué cambios hiciste y por qué.
  • Comparte el enlace de tu sketch en p5.js. ¿Cómo? Una vez crees la cuenta y salves el sketch recreado, la url de tu sketch aparecerá en la parte superior de la página. Copia y pega esa url en tu entrega.

Por ejemplo, mira este ensayo que yo hice:

En openprocessing encontré este ejemplo.
Lo recree en el editor de p5.js y cambié la imagen de fondo: mi versión.

🚀 Tu solución:

Ejemplo 4: Generative Design – P_2_1_2_01

Enlace original:
http://www.generative-gestaltung.de/2/sketches/?01_P/P_2_1_2_01

¿Qué me llamó la atención?
Este sketch presenta una cuadrícula de círculos cuya posición y tamaño se modifican en función de la posición del mouse. Me llamó la atención cómo una interacción simple puede generar patrones visuales complejos y dinámicos, ofreciendo una experiencia interactiva atractiva.

¿Cómo está hecho el sketch?

  • Se establece una cuadrícula de círculos utilizando bucles anidados.
  • La posición y el tamaño de cada círculo se determinan en función de la posición actual del mouse.
  • Se utiliza randomSeed() para mantener la consistencia en la disposición aleatoria de los círculos.
  • Se implementan funciones para guardar la imagen generada y restablecer la disposición aleatoria con un clic del mouse.

¿Qué cambios hice?
Modifiqué el sketch original para que los círculos cambien de color dinámicamente según la posición del mouse. Además, añadí una función que permite alternar entre diferentes modos de color al presionar una tecla específica. Esto añade una capa adicional de interactividad y variación visual al patrón generado.

Enlace a mi versión:
Mi sketch modificado en p5.js

Investigación 🔎

En esta fase, vas a analizar un caso de estudio que te servirá de base para resolver el problema que te plantearé en la siguiente fase.

Actividad 02

Caso de estudio: exploración inicial

🎯 Enunciado: en esta actividad vas a explorar un ejemplo interesante de generación de arte digital con p5.js. El ejemplo es tomado del sitio Generative Design.

Lo que te pediré es que explores el código fuente de este sketch y trates de entender cómo está hecho.

Te ruego que hagas el mayor esfuerzo para explorar y entender el código. Si tienes dudas, no dudes en preguntarme.

📤 Entrega: explica cómo funciona este sketch, asegúrate de explorar toda su funcionalidad y comprender qué hace cada parte del código. Realiza varios dibujos y explica cómo los lograste.

🚀 Tu solución:

Caso de estudio: Exploración inicial del sketch “P_2_3_1_02”

¿Qué entendí del código?

Yo entendí que este sketch es una herramienta interactiva para dibujar utilizando imágenes SVG o líneas rotadas, simulando una especie de “pincel rotatorio” digital. Está escrito en JavaScript utilizando la biblioteca p5.js, y forma parte del libro Generative Gestaltung, una referencia importante en el arte generativo. Desde el primer momento en que se ejecuta, se ve una pantalla en blanco, y al arrastrar el mouse con clic sostenido, empiezan a aparecer formas que rotan sobre el punto donde está el cursor. Cada vez que se hace clic, se generan nuevos módulos gráficos basados en imágenes o líneas, y estos giran con cierta velocidad, dando lugar a patrones complejos y estéticamente interesantes.

Una parte clave del código es el arreglo lineModule, que almacena diferentes imágenes SVG cargadas con la función preload(). Estas imágenes se utilizan como pinceles gráficos cuando se seleccionan con las teclas del 5 al 9. Si no se selecciona ninguna, el programa simplemente dibuja una línea recta rotando. El color del trazo se puede cambiar presionando la barra espaciadora para obtener colores aleatorios, o con las teclas del 1 al 4 para seleccionar colores predeterminados. Todo esto hace que el programa se sienta como una caja de herramientas para crear arte generativo libremente.

Una cosa que me pareció muy interesante es cómo el programa detecta si se está presionando la tecla SHIFT mientras se arrastra el mouse. En ese caso, restringe el dibujo solo a ejes horizontales o verticales, dependiendo del movimiento del cursor. Esto permite tener más control sobre la simetría o alineación de los trazos. Además, el ángulo de rotación del módulo cambia progresivamente mientras se dibuja, lo que le da una sensación de fluidez a los diseños generados.

🖱 Funcionalidades del teclado y mouse

También comprendí que el teclado tiene múltiples funciones útiles para explorar diferentes estilos visuales. Las flechas arriba y abajo aumentan o disminuyen el tamaño del módulo (ya sea línea o SVG), y las flechas izquierda y derecha modifican la velocidad de rotación. La tecla d invierte la dirección de rotación, y además gira el ángulo base 180 grados, creando un efecto espejo. La tecla s guarda una imagen PNG del lienzo, mientras que delete o backspace limpia la pantalla completamente. Todo esto hace que sea muy fácil experimentar con diferentes composiciones visuales sin necesidad de modificar el código.

¿Qué experimentos hice?

Yo realicé varios dibujos para explorar cómo se comporta el sketch en diferentes condiciones. Al mantener presionado el mouse y moverlo lentamente, obtuve formas suaves, casi como flores o espirales. Luego probé con movimientos rápidos y la combinación de la tecla SHIFT, y los resultados fueron más geométricos, como estructuras ortogonales. También alterné entre los diferentes módulos (del 5 al 9), y noté cómo cambiaba completamente el estilo del trazo: desde líneas simples hasta patrones más decorativos basados en los SVG. Finalmente, usé la barra espaciadora varias veces para explorar distintas paletas de colores aleatorios, y encontré combinaciones visuales muy agradables.

Conclusión

En conclusión, yo entendí que este sketch es una herramienta de dibujo interactiva que mezcla código y creatividad para generar arte digital basado en rotación, color y repetición. Permite una exploración intuitiva a través del mouse y el teclado, y está diseñado para fomentar la experimentación libre. A través del uso de imágenes SVG y colores variables, se pueden producir composiciones gráficas muy variadas. Me pareció fascinante cómo algo que parece simple a nivel de código puede dar resultados visuales tan ricos y complejos cuando se combina con la interacción humana. Este ejercicio me ayudó a ver cómo la programación puede ser una forma de expresión artística tan válida como cualquier otra.

Actividad 03

Caso de estudio: micro:bit

Desde esta actividad hasta la fase de aplicación, te voy a guiar para que transformes y adaptes el caso de estudio para lograr controlar partes de este con el micro:bit. Primero te mostraré cómo transmitir información desde el micro:bit.

🎯 Enunciado: analiza el código del micro:bit que te permitirá enviar información a un sketch en p5.js.

Vas a analizar lentamente el siguiente código del micro:bit

# Imports go at the top
from microbit import *
uart.init(115200)
display.set_pixel(0,0,9)
while True:
xValue = accelerometer.get_x()
yValue = accelerometer.get_y()
aState = button_a.is_pressed()
bState = button_b.is_pressed()
data = "{},{},{},{}\n".format(xValue, yValue, aState,bState)
uart.write(data)
sleep(100) # Envia datos a 10 Hz
  • Programa el micro:bit con este código y luego abre la aplicación SerialTerminal para ver los datos que se están enviando.

  • ¿Qué información se está enviando? ¿Cómo se está enviando? Qué significa esta parte del código:

"{},{},{},{}\n".format(xValue, yValue, aState,bState)
  • Observa en la aplicación SerialTerminal cómo se ven los datos que se están enviando. ¿Qué puedes inferir de la estructura de los datos?
  • ¿Por qué se separan los datos con comas y se termina con un salto de línea?
  • ¿Qué crees que pasaría si no se separan los datos con comas y no terminan con un salto de línea?
  • Para qué crees que se usa la función sleep(100)? ¿Qué pasaría si no se usara?
  • Observa cómo cambian los valores de xValue y yValue a medida que el micro:bit se inclina hacia la izquierda, derecha, adelante y atrás. ¿Qué valores toman xValue y yValue en cada caso?
  • ¿Qué valores toman aState y bState cuando presionas los botones A y B?
  • Observa qué ocurre si en vez de is_pressed() usas was_pressed(). ¿Qué diferencias encuentras?

Finalmente, analiza este asunto: si el micro:bit tiene los siguientes datos xValue: 969, yValue: 652, aState: True, bState: False ¿Qué bytes se enviarían por el puerto serial? Piensa, primero piensa por favor, y luego verifica con la aplicación SerialTerminal. Ten presente que en Mostrar datos como puedes ver los bytes que se están enviando mediante Todo en HEX.

📤 Entrega: reporta los experimentos y hallazgos que vas encontrando a medida que analizas el código y responde las preguntas que te voy haciendo en el enunciado.

🚀 Tu solución:

Caso de estudio: Comunicación de micro:bit con p5.js vía UART

Exploración y análisis del código del micro:bit Código analizado python Copiar Editar from microbit import *

uart.init(115200) display.set_pixel(0,0,9)

while True: xValue = accelerometer.get_x() yValue = accelerometer.get_y() aState = button_a.is_pressed() bState = button_b.is_pressed() data = ”{},{},{},{}\n”.format(xValue, yValue, aState, bState) uart.write(data) sleep(100) # Envia datos a 10 Hz

¿Qué entendí del código?

Yo entendí que este código establece una comunicación serial (UART) entre el micro:bit y un computador o dispositivo externo, con una velocidad de 115200 baudios. El micro:bit, cada 100 milisegundos, envía por el puerto serial los siguientes datos:

El valor del eje X del acelerómetro (xValue),

El valor del eje Y del acelerómetro (yValue),

El estado del botón A (aState),

El estado del botón B (bState).

Esta información se envía como una cadena de texto, con cada valor separado por comas y al final un salto de línea (\n).

¿Qué significa esta parte del código?

python Copiar Editar ”{},{},{},{}\n”.format(xValue, yValue, aState, bState) Yo entendí que esta línea crea una cadena de texto donde se insertan los valores en el siguiente orden:

El valor del acelerómetro en X,

El valor del acelerómetro en Y,

El estado del botón A (True/False),

El estado del botón B (True/False).

El formato ”{}” es un marcador de posición, y .format(…) reemplaza cada uno con los valores reales. Las comas separan los datos para que luego puedan ser fácilmente interpretados (por ejemplo, desde p5.js), y el \n (salto de línea) indica el final de la línea de datos.

¿Cómo se ven los datos en la aplicación SerialTerminal?

Cuando observo los datos en la aplicación SerialTerminal, veo líneas como esta:

graphql Copiar Editar 120,-342,False,True Esto confirma que los datos están separados por comas y terminan con un salto de línea. Así, cada línea representa un conjunto completo de datos enviados cada 100 ms.

¿Qué pasaría si no se separan con comas o no se usa salto de línea?

Yo creo que si no se usaran comas, los datos se mezclarían, por ejemplo:

Copiar Editar 120-342FalseTrue Eso sería muy difícil de interpretar desde otro software, como p5.js. Tampoco sabría dónde empieza y termina cada valor.

Si no se usara el salto de línea, los datos se concatenarían en una sola línea interminable:

graphql Copiar Editar 120,-342,False,True130,-320,False,False… Eso dificultaría mucho la lectura, ya que no habría una marca clara de separación entre una lectura y la siguiente.

¿Para qué sirve sleep(100)?

Entendí que la función sleep(100) pausa el programa 100 milisegundos entre cada lectura y escritura. Esto significa que el micro:bit envía datos 10 veces por segundo (10 Hz).

Si no se usara esta pausa, el micro:bit enviaría datos lo más rápido posible, lo que podría saturar el canal UART y generar lecturas demasiado densas o errores en el software receptor (como p5.js).

Observaciones de los valores xValue y yValue

Cuando inclino el micro:bit:

Hacia la derecha ➡️ xValue aumenta (valores positivos)

Hacia la izquierda ⬅️ xValue disminuye (valores negativos)

Hacia adelante ⬆️ yValue aumenta

Hacia atrás ⬇️ yValue disminuye

Los valores suelen estar entre -1024 y +1024, dependiendo de la inclinación.

¿Qué valores toman aState y bState?

Cuando presiono el botón A, aState cambia a True, y cuando lo suelto vuelve a False. Lo mismo ocurre con bState y el botón B.

Diferencias entre is_pressed() y was_pressed()

is_pressed() devuelve True solo mientras el botón está siendo presionado.

was_pressed() devuelve True solo una vez, es decir, detecta el evento de haber sido presionado. Después de eso, vuelve a False, hasta que el botón se vuelva a presionar.

Esto significa que was_pressed() es útil para detectar eventos únicos, como hacer clic, mientras que is_pressed() es útil para detectar si el botón está mantenido presionado.

Análisis de los datos enviados por UART

Si los datos fueran:

yaml Copiar Editar xValue: 969, yValue: 652, aState: True, bState: False Entonces la línea enviada sería:

graphql Copiar Editar 969,652,True,False\n Eso se convierte en la siguiente secuencia de bytes ASCII:

57 (9)

54 (6)

57 (9)

2C (coma)

36 (6)

35 (5)

32 (2)

2C (coma)

54 72 75 65 (T r u e)

2C (coma)

46 61 6C 73 65 (F a l s e)

0A (salto de línea)

En hexadecimal, esto se vería como:

mathematica Copiar Editar 39 36 39 2C 36 35 32 2C 54 72 75 65 2C 46 61 6C 73 65 0A Esto confirma que los datos enviados son puramente texto, y que el formato con comas y salto de línea es fundamental para estructurar correctamente la información.

Actividad 04

Caso de estudio: p5.js

Esta actividad será larga, te tomará posiblemente una o más sesiones de trabajo. Te pido que a medida que la recorres vayas tomando notas de tus hallazgos y experimentos en la bitácora. Tómate tu tiempo para analizar y comprender cada parte del código. La idea no es leer de corrido el código, sino analizarlo ¿Cómo? Haciendo experimentos, modificando partes del código, ejecutando y observando los resultados. A esto se le llama aprendizaje activo.

🎯 Enunciado: en esta actividad vas a modificar diferentes partes del código original para que puedas controlar ciertas partes de este con el micro:bit.

  • Crea un nuevo proyecto p5.js.
  • Modifica el archivo index.html así:
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/p5/lib/p5.min.js"></script>
<script src="https://unpkg.com/@gohai/p5.webserial@^1/libraries/p5.webserial.js"></script>
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
<script src="sketch.js"></script>
</body>
</html>
  • Compara el código original del caso de estudio con el anterior. ¿Qué notas de diferente?

La aplicación requiere algunas imágenes que puedes descargar de la aplicación original. ¿Cómo? Lo primero es autenticarte en el editor de p5.js. Luego abre el sketch original y realiza una modificación simple. Guarda el sketch. Nota que ahora este código está en tu cuenta. Descarga las imágenes, ingresando al menú File. Finalmente, carga las imágenes a la carpeta de tu proyecto p5.js así:

p5.js files

Ahora modifica el archivo sketch.js así:

const lineModule = [];
function preload() {
lineModule[1] = loadImage("02.svg");
lineModule[2] = loadImage("03.svg");
lineModule[3] = loadImage("04.svg");
lineModule[4] = loadImage("05.svg");
}
function setup() {
createCanvas(400, 400);
}
function draw() {
background(220);
}

Ejecuta el sketch. Si no tienes errores podrás continuar. Reflexiona ¿Para qué se usan estas imágenes? ¿Qué representan? Revisa de nuevo el sketch original y analiza.

Recuerda que en el código del micro:bit, cada 100 ms se están enviando datos; sin embargo, la aplicación no podrá comenzar a usar dichos datos antes de que el usuario de manera explícita conecte el micro:bit a la aplicación.

Entonces tienes un problema en el cual una aplicación se comporta diferente dependiente del ESTADO en el que se encuentra. ¿Cómo puedes solucionar este tipo de problema? Ya lo sabes: máquinas de estado.

Modifica el archivo sketch.js para que puedas controlar el estado de la aplicación:

const lineModule = [];
function preload() {
lineModule[1] = loadImage("02.svg");
lineModule[2] = loadImage("03.svg");
lineModule[3] = loadImage("04.svg");
lineModule[4] = loadImage("05.svg");
}
const STATES = {
WAIT_MICROBIT_CONNECTION: "WAITMICROBIT_CONNECTION",
RUNNING: "RUNNING",
};
let appState = STATES.WAIT_MICROBIT_CONNECTION;
function setup() {
createCanvas(windowWidth, windowHeight);
background(255);
}
function draw() {
switch (appState) {
case STATES.WAIT_MICROBIT_CONNECTION:
break;
case STATES.RUNNING:
break;
}
}

Recuerda que en una aplicación p5.js, la función setup() se llama solo una vez. Luego se llamará la función draw() cada 17 ms que equivale a 60 fps. No olvides entonces que cada vez que draw() se llama, la aplicación evalúa el valor de appState para decidir qué hará en ese frame, o dicho de otra manera qué hará en el estado actual.

¿Recuerdas que en las unidades anteriores teníamos un pseudoestado llamado INIT? Se llama pseudoestado porque cuando la aplicación está en este realmente NO ESTÁ ESPERANDO nada. ¿Puedes notar que no tenemos a INIT en este caso? Pero la verdad si está, solo que no como antes, la función setup() está haciendo esa función. ¿Lo ves?

Ahora es momento de programar el comportamiento del estado STATES.WAIT_MICROBIT_CONNECTION.
Recuerda que la aplicación en este estado, en cada frame, simplemente está esperando a que el usuario conecte el micro:bit. Observa el código, agregaré algunas variables:

const lineModule = [];
function preload() {
lineModule[1] = loadImage("02.svg");
lineModule[2] = loadImage("03.svg");
lineModule[3] = loadImage("04.svg");
lineModule[4] = loadImage("05.svg");
}
//*********************************
// Código para soportar el serial
let port;
let connectBtn;
let microBitConnected = false;
//*********************************
const STATES = {
WAIT_MICROBIT_CONNECTION: "WAITMICROBIT_CONNECTION",
RUNNING: "RUNNING",
};
let appState = STATES.WAIT_MICROBIT_CONNECTION;
function setup() {
createCanvas(windowWidth, windowHeight);
background(255);
}
function draw() {
switch (appState) {
case STATES.WAIT_MICROBIT_CONNECTION:
break;
case STATES.RUNNING:
break;
}
}

Observa que port, connectBtn y microBitConnected son variables globales. Se necesitan así porque las vas a manipular en cualquier función del programa. No olvides que las variables que declaremos dentro de las funciones solo serán visibles dentro de la función, no por fuera.

Ahora añadiremos en la función setup el código que permitirá conectarse al micro:bit:

const lineModule = [];
function preload() {
lineModule[1] = loadImage("02.svg");
lineModule[2] = loadImage("03.svg");
lineModule[3] = loadImage("04.svg");
lineModule[4] = loadImage("05.svg");
}
//*********************************
// Código para soportar el serial
let port;
let connectBtn;
let microBitConnected = false;
//*********************************
const STATES = {
WAIT_MICROBIT_CONNECTION: "WAITMICROBIT_CONNECTION",
RUNNING: "RUNNING",
};
let appState = STATES.WAIT_MICROBIT_CONNECTION;
function setup() {
createCanvas(windowWidth, windowHeight);
background(255);
//****************************************************
// Adición del serial
port = createSerial();
connectBtn = createButton("Connect to micro:bit");
connectBtn.position(0, 0);
connectBtn.mousePressed(connectBtnClick);
//****************************************************
}
function draw() {
switch (appState) {
case STATES.WAIT_MICROBIT_CONNECTION:
break;
case STATES.RUNNING:
break;
}
}
  • createSerial(): crea el objeto que representará la conexión serial y devolverá la dirección de memoria de este objeto que se almacenará en la variable port.
  • createButton(): crea un objeto que representará a un botón en la aplicación. La dirección de este objeto quedará almacenada en la variable connectBtn.
  • Observa que por medio de connectBtn, que contiene la dirección del objeto, se manipulará la posición del objeto y el comportamiento de este al presionarlo con el mouse mousePressed. Nota que al método mousePressed() le estás pasando el nombre de una función connectBtnClick. De esta manera al presionar el botón se llamará dicha función.

Añadimos entonces la función connectBtnClick():

const lineModule = [];
function preload() {
lineModule[1] = loadImage("02.svg");
lineModule[2] = loadImage("03.svg");
lineModule[3] = loadImage("04.svg");
lineModule[4] = loadImage("05.svg");
}
let port;
let connectBtn;
let microBitConnected = false;
const STATES = {
WAIT_MICROBIT_CONNECTION: "WAITMICROBIT_CONNECTION",
RUNNING: "RUNNING",
};
let appState = STATES.WAIT_MICROBIT_CONNECTION;
function setup() {
createCanvas(windowWidth, windowHeight);
background(255);
port = createSerial();
connectBtn = createButton("Connect to micro:bit");
connectBtn.position(0, 0);
connectBtn.mousePressed(connectBtnClick);
}
//****************************************************
function connectBtnClick() {
if (!port.opened()) {
port.open("MicroPython", 115200);
} else {
port.close();
}
}
//****************************************************
function draw() {
switch (appState) {
case STATES.WAIT_MICROBIT_CONNECTION:
break;
case STATES.RUNNING:
break;
}
}

¿Qué pasaría ahora si das click al botón? Observa que en la función se verifica si el puerto serial no está abierto. De ser así se abren con el método open(“MicroPython”, 115200). De lo contrario el puerto se cerrará.

¿Para qué abres el puerto serial? Se abre para poder recibir los datos del micro:bit, es decir, para conectarte al micro:bit y poder recibir los datos que está enviando en esta parte de su código:

data = "{},{},{},{}\n".format(xValue, yValue, aState, bState)
uart.write(data)

Verifica el funcionamiento de la aplicación hasta este momento. Todo debería estar bien.

Una vez ejecutes el programa nota que debe aparecer en la esquina superior izquierda el botón para conectarse al micro:bit: Connect to micro:bit. Observa la consola para que puedas ver los mensajes que te saldrán.

Observa detenidamente algo interesante. Una vez te conectas al micro:bit el botón sigue con el texto: Connect to micro:bit, pero ya estás conectado. Es necesario informar al usuario que la funcionalidad del botón cambiará.

Para lograr lo anterior, al inicio de draw() y por tanto en cada frame se comprobará si el puerto está abierto o cerrado. Esto permitirá cambiar el texto del botón y además permitirá generar el evento microBitConnected para informarle al resto de la aplicación el estado del puerto.

const lineModule = [];
function preload() {
lineModule[1] = loadImage("02.svg");
lineModule[2] = loadImage("03.svg");
lineModule[3] = loadImage("04.svg");
lineModule[4] = loadImage("05.svg");
}
let port;
let connectBtn;
let microBitConnected = false;
const STATES = {
WAIT_MICROBIT_CONNECTION: "WAITMICROBIT_CONNECTION",
RUNNING: "RUNNING",
};
let appState = STATES.WAIT_MICROBIT_CONNECTION;
function setup() {
createCanvas(windowWidth, windowHeight);
background(255);
port = createSerial();
connectBtn = createButton("Connect to micro:bit");
connectBtn.position(0, 0);
connectBtn.mousePressed(connectBtnClick);
}
function connectBtnClick() {
if (!port.opened()) {
port.open("MicroPython", 115200);
} else {
port.close();
}
}
function draw() {
//******************************************
if (!port.opened()) {
connectBtn.html("Connect to micro:bit");
microBitConnected = false;
} else {
microBitConnected = true;
connectBtn.html("Disconnect");
}
//*******************************************
switch (appState) {
case STATES.WAIT_MICROBIT_CONNECTION:
break;
case STATES.RUNNING:
break;
}
}

Ejecuta de nuevo la aplicación y verifica que funciona correctamente.

Ahora vamos a añadir la parte del código que lee los datos del micro:bit.

const lineModule = [];
function preload() {
lineModule[1] = loadImage("02.svg");
lineModule[2] = loadImage("03.svg");
lineModule[3] = loadImage("04.svg");
lineModule[4] = loadImage("05.svg");
}
let port;
let connectBtn;
let microBitConnected = false;
const STATES = {
WAIT_MICROBIT_CONNECTION: "WAITMICROBIT_CONNECTION",
RUNNING: "RUNNING",
};
let appState = STATES.WAIT_MICROBIT_CONNECTION;
function setup() {
createCanvas(windowWidth, windowHeight);
background(255);
port = createSerial();
connectBtn = createButton("Connect to micro:bit");
connectBtn.position(0, 0);
connectBtn.mousePressed(connectBtnClick);
}
function connectBtnClick() {
if (!port.opened()) {
port.open("MicroPython", 115200);
} else {
port.close();
}
}
function draw() {
//******************************************
if (!port.opened()) {
connectBtn.html("Connect to micro:bit");
microBitConnected = false;
} else {
microBitConnected = true;
connectBtn.html("Disconnect");
if (port.availableBytes() > 0) {
let data = port.readUntil("\n");
if (data) {
data = data.trim();
let values = data.split(",");
if (values.length == 4) {
microBitX = int(values[0]) + windowWidth / 2;
microBitY = int(values[1]) + windowHeight / 2;
microBitAState = values[2].toLowerCase() === "true";
microBitBState = values[3].toLowerCase() === "true";
updateButtonStates(microBitAState, microBitBState);
} else {
print("No se están recibiendo 4 datos del micro:bit");
}
}
}
}
//*******************************************
switch (appState) {
case STATES.WAIT_MICROBIT_CONNECTION:
break;
case STATES.RUNNING:
break;
}
}

Nota que solo se leen datos del micro:bit si el puerto está abierto ¿Por qué? ¿Podrías leer datos si el puerto está cerrado? ¿Qué pasaría si el puerto está cerrado y el micro:bit envía datos?

Ahora te pediré que te concentres. Para leer los datos que vienen del micro:bit, la aplicación primero pregunta si al menos hay un dato disponible para leer. Piensa que hay una parte del código de la biblioteca p5.webserial que se encarga de recibir los datos (como si fuera el portero de un edificio) y tu lo único que tienes que preguntar es si al menos ya tienes un dato para leer.

if (port.availableBytes() > 0)

​ Una vez sabes que al menos hay un dato, te quedas esperando que esté completa la LINEA que contiene todos los datos:

let data = port.readUntil("\n");

​​ Pero ¿Cómo sabes que ya está completa la línea? La función readUntil esperará a que llegue el byte que representa el fin de línea: "\n". Por tanto, el micro:bit tendrá que MARCAR esto en cada paquete de datos que envíe:

data = "{},{},{},{}\n".format(xValue, yValue, aState, bState)

¿Puedes verlo? ¿Qué pasaría si el micro:bit no envía el "\n"?

Una vez recibes el paquete completo que envió el micro:bit, verificas si el dato es válido:

if (data)

​ Y procedes a eliminar de los datos el "\n", ya que este solo lo necesitas para marcar el fin del paquete de datos:

data = data.trim();

Ahora necesitas extraer de la cadena enviada cada uno de los datos, es decir, el valor de x, de y, el estado de A y de B:

let values = data.split(",");

Regresa al código del micro:bit:

data = "{},{},{},{}\n".format(xValue, yValue, aState, bState)

Cada {} es reemplazada por el valor de las variables xValue, yValue, aState, bState respectivamente. Además, observa el carácter , que separa cada valor.

En resumen hasta ahora. El micro:bit al enviar esta cadena:

data = "{},{},{},{}\n".format(xValue, yValue, aState, bState)

Está separando los valor por coma y marcando el fin del mensaje con un retorno de carro (\n) o enter.

A esto se le conoce como un PROTOCOLO. Si la aplicación en p5.js quiere recibir correctamente los datos tendrá que seguir el PROTOCOLO para poder extraer correctamente la información.

Retomemos:

let values = data.split(",");

Esta parte devuelve un ARREGLO de cadenas y la dirección de este arreglo se almacenará en values.

Considera ahora lo siguiente. Cuando estás comunicando dos aplicaciones es fundamental verificar la integridad de la información recibida. En este caso, el micro:bit está enviando 4 datos, por lo que la aplicación en p5.js debe verificar que efectivamente se recibieron los 4 datos:

if (values.length == 4) {

​ Para analizar lo que sigue debemos volver al código del micro:bit:

data = "{},{},{},{}\n".format(xValue, yValue, aState, bState)

Como ya te dije antes, cada {} es reemplazada por el valor de las variables xValue, yValue, aState, bState respectivamente. Ten presente que toda la información está CODIFICAD en ASCII, es decir, si xValue es 100, realmente no estás enviando el byte que representa ese 100 sino que estás codificando cada número en ASCII. Por tanto, el 100 realmente se envía como tres bytes: 49, 48, 48.

Es por ello que esta parte del código:

microBitX = int(values[0]) + windowWidth/2;
microBitY = int(values[1]) + windowHeight/2;
microBitAState = values[2].toLowerCase() === "true";
microBitBState = values[3].toLowerCase() === "true";

Necesita convertir cada cadena en un número entero y además necesita convertir las cadenas que representan los estados de los botones en un valor booleano.

Te estarás preguntando ¿Por qué se suma windowWidth/2 y windowHeight/2 a los valores de x e y? Esta respuesta te toca analizarla a ti. No olvides escribir tus hallazgos en la bitácora.

Por último, la función updateButtonStates se encargará de actualizar el estado de los botones. Vamos a añadir esta función al código:

const lineModule = [];
function preload() {
lineModule[1] = loadImage("02.svg");
lineModule[2] = loadImage("03.svg");
lineModule[3] = loadImage("04.svg");
lineModule[4] = loadImage("05.svg");
}
let port;
let connectBtn;
let microBitConnected = false;
const STATES = {
WAIT_MICROBIT_CONNECTION: "WAITMICROBIT_CONNECTION",
RUNNING: "RUNNING",
};
let appState = STATES.WAIT_MICROBIT_CONNECTION;
let microBitX = 0;
let microBitY = 0;
let microBitAState = false;
let microBitBState = false;
let prevmicroBitAState = false;
let prevmicroBitBState = false;
function setup() {
createCanvas(windowWidth, windowHeight);
background(255);
port = createSerial();
connectBtn = createButton("Connect to micro:bit");
connectBtn.position(0, 0);
connectBtn.mousePressed(connectBtnClick);
}
function connectBtnClick() {
if (!port.opened()) {
port.open("MicroPython", 115200);
} else {
port.close();
}
}
function updateButtonStates(newAState, newBState) {
// Generar eventos de keypressed
if (newAState === true && prevmicroBitAState === false) {
// create a new random color and line length
lineModuleSize = random(50, 160);
// remember click position
clickPosX = microBitX;
clickPosY = microBitY;
print("A pressed");
}
// Generar eventos de key released
if (newBState === false && prevmicroBitBState === true) {
c = color(random(255), random(255), random(255), random(80, 100));
print("B released");
}
prevmicroBitAState = newAState;
prevmicroBitBState = newBState;
}
function draw() {
//******************************************
if (!port.opened()) {
connectBtn.html("Connect to micro:bit");
microBitConnected = false;
} else {
microBitConnected = true;
connectBtn.html("Disconnect");
if (port.availableBytes() > 0) {
let data = port.readUntil("\n");
if (data) {
data = data.trim();
let values = data.split(",");
if (values.length == 4) {
microBitX = int(values[0]) + windowWidth / 2;
microBitY = int(values[1]) + windowHeight / 2;
microBitAState = values[2].toLowerCase() === "true";
microBitBState = values[3].toLowerCase() === "true";
updateButtonStates(microBitAState, microBitBState);
} else {
print("No se están recibiendo 4 datos del micro:bit");
}
}
}
}
//*******************************************
switch (appState) {
case STATES.WAIT_MICROBIT_CONNECTION:
break;
case STATES.RUNNING:
break;
}
}

Ahora, si ejecutas la aplicación y conectas el micro:bit, podrás ver que al presionar el botón A se generará un evento de keypressed y al soltar el botón B se generará un evento de keyreleased. Además los valores de microBitX y microBitY se actualizarán con los valores que envía el micro:bit.

¿Cómo puedes verificar que los eventos de keypressed y keyreleased se están generando? Piensa en cómo puedes hacerlo y escribe tus hallazgos en la bitácora.

Ahora te pediré que analices el algoritmo updateButtonStates. ¿Qué hace? ¿Por qué es necesario almacenar el estado anterior de los botones? ¿Qué pasaría si no se almacenara el estado anterior?

Seguimos. Vamos a añadir comportamientos a cada uno de los estados de la aplicación. Recuerda, en un estado se espera la ocurrencia de uno o varios eventos. Eso puede generar acciones y posiblemente un cambio de estado.

case STATES.WAIT_MICROBIT_CONNECTION:
// No puede comenzar a dibujar hasta que no se conecte el microbit
// evento 1:
if (microBitConnected === true) {
// Preparo todo para el estado en el próximo frame
print("Microbit ready to draw");
strokeWeight(0.75);
c = color(181, 157, 0);
noCursor();
appState = STATES.RUNNING;
}
break;

En cada frame se verifica si el micro:bit está conectado. Si es así, se prepara todo para el estado RUNNING. Te muestro el código completo incluyendo las variables de aplicación nuevas que utilizará el estado RUNNING, así como el resto del código del sketch original:

let c;
let lineModuleSize = 0;
let angle = 0;
let angleSpeed = 1;
const lineModule = [];
let lineModuleIndex = 0;
let clickPosX = 0;
let clickPosY = 0;
function preload() {
lineModule[1] = loadImage("02.svg");
lineModule[2] = loadImage("03.svg");
lineModule[3] = loadImage("04.svg");
lineModule[4] = loadImage("05.svg");
}
let port;
let connectBtn;
let microBitConnected = false;
const STATES = {
WAIT_MICROBIT_CONNECTION: "WAITMICROBIT_CONNECTION",
RUNNING: "RUNNING",
};
let appState = STATES.WAIT_MICROBIT_CONNECTION;
let microBitX = 0;
let microBitY = 0;
let microBitAState = false;
let microBitBState = false;
let prevmicroBitAState = false;
let prevmicroBitBState = false;
function setup() {
createCanvas(windowWidth, windowHeight);
background(255);
port = createSerial();
connectBtn = createButton("Connect to micro:bit");
connectBtn.position(0, 0);
connectBtn.mousePressed(connectBtnClick);
}
function connectBtnClick() {
if (!port.opened()) {
port.open("MicroPython", 115200);
} else {
port.close();
}
}
function updateButtonStates(newAState, newBState) {
// Generar eventos de keypressed
if (newAState === true && prevmicroBitAState === false) {
// create a new random color and line length
lineModuleSize = random(50, 160);
// remember click position
clickPosX = microBitX;
clickPosY = microBitY;
print("A pressed");
}
// Generar eventos de key released
if (newBState === false && prevmicroBitBState === true) {
c = color(random(255), random(255), random(255), random(80, 100));
print("B released");
}
prevmicroBitAState = newAState;
prevmicroBitBState = newBState;
}
function windowResized() {
resizeCanvas(windowWidth, windowHeight);
}
function draw() {
//******************************************
if (!port.opened()) {
connectBtn.html("Connect to micro:bit");
microBitConnected = false;
} else {
microBitConnected = true;
connectBtn.html("Disconnect");
if (port.availableBytes() > 0) {
let data = port.readUntil("\n");
if (data) {
data = data.trim();
let values = data.split(",");
if (values.length == 4) {
microBitX = int(values[0]) + windowWidth / 2;
microBitY = int(values[1]) + windowHeight / 2;
microBitAState = values[2].toLowerCase() === "true";
microBitBState = values[3].toLowerCase() === "true";
updateButtonStates(microBitAState, microBitBState);
} else {
print("No se están recibiendo 4 datos del micro:bit");
}
}
}
}
//*******************************************
switch (appState) {
case STATES.WAIT_MICROBIT_CONNECTION:
// No puede comenzar a dibujar hasta que no se conecte el microbit
// evento 1:
if (microBitConnected === true) {
// Preparo todo para el estado en el próximo frame
print("Microbit ready to draw");
strokeWeight(0.75);
c = color(181, 157, 0);
noCursor();
appState = STATES.RUNNING;
}
break;
case STATES.RUNNING:
// EVENTO: estado de conexión del microbit
if (microBitConnected === false) {
print("Waiting microbit connection");
cursor();
appState = STATES.WAIT_MICROBIT_CONNECTION;
}
//EVENTO: recepción de datos seriales del micro:bit
if (microBitAState === true) {
let x = microBitX;
let y = microBitY;
if (keyIsPressed && keyCode === SHIFT) {
if (abs(clickPosX - x) > abs(clickPosY - y)) {
y = clickPosY;
} else {
x = clickPosX;
}
}
push();
translate(x, y);
rotate(radians(angle));
if (lineModuleIndex != 0) {
tint(c);
image(
lineModule[lineModuleIndex],
0,
0,
lineModuleSize,
lineModuleSize
);
} else {
stroke(c);
line(0, 0, lineModuleSize, lineModuleSize);
}
angle += angleSpeed;
pop();
}
break;
}
}
function keyPressed() {
if (keyCode === UP_ARROW) lineModuleSize += 5;
if (keyCode === DOWN_ARROW) lineModuleSize -= 5;
if (keyCode === LEFT_ARROW) angleSpeed -= 0.5;
if (keyCode === RIGHT_ARROW) angleSpeed += 0.5;
}
function keyReleased() {
if (key === "s" || key === "S") {
let ts =
year() +
nf(month(), 2) +
nf(day(), 2) +
"_" +
nf(hour(), 2) +
nf(minute(), 2) +
nf(second(), 2);
saveCanvas(ts, "png");
}
if (keyCode === DELETE || keyCode === BACKSPACE) background(255);
// reverse direction and mirror angle
if (key === "d" || key === "D") {
angle += 180;
angleSpeed *= -1;
}
// default colors from 1 to 4
if (key === "1") c = color(181, 157, 0);
if (key === "2") c = color(0, 130, 164);
if (key === "3") c = color(87, 35, 129);
if (key === "4") c = color(197, 0, 123);
// load svg for line module
if (key === "5") lineModuleIndex = 0;
if (key === "6") lineModuleIndex = 1;
if (key === "7") lineModuleIndex = 2;
if (key === "8") lineModuleIndex = 3;
if (key === "9") lineModuleIndex = 4;
}

Observa el código original y el nuevo código. ¿Qué diferencias encuentras? ¿Qué pasó con algunos eventos del mouse? ¿Qué paso con la función relacionada con la barra de espacio del teclado?

Ejecuta la aplicación. Mira en la consola los mensajes que se generan. Nota en particular uno que dice: No se están recibiendo 4 datos del micro:bit ¿Qué significa esto? Analiza si este mensaje ocurre en varios frames o solo en uno. ¿Por qué? ¿Qué puedes hacer para solucionar este problema? (Ten presente que esta pregunta es abierta y no tiene una única respuesta).

Finalmente, juega con la aplicación y DIBUJA.

📤 Entrega: reporta los experimentos y hallazgos que vas encontrando a medida que analizas el código y responde las preguntas que te voy haciendo en el enunciado.

🚀 Tu solución:

Solución

1. Comparación inicial del código HTML

Código original vs modificado:

  • Se añadió la librería p5.webserial.js
  • Permite comunicación serial con el micro:bit via WebSerial API

2. Análisis de imágenes

Archivos cargados:

  • 02.svg, 03.svg, 04.svg, 05.svg Función:
  • Módulos gráficos para dibujo en canvas
  • Se seleccionan con teclas 6-9 (1-4 para colores)

3. Máquina de estados

Estados implementados:

  1. WAIT_MICROBIT_CONNECTION
    • Espera conexión del dispositivo
    • Muestra botón “Connect to micro:bit”
  2. RUNNING
    • Estado activo de dibujo
    • Reacciona a datos del micro:bit

4. Comunicación Serial

Configuración:

  • Velocidad: 115200 baudios
  • Formato datos: “x,y,aState,bState\n” Flujo de conexión:
  1. Usuario hace click en botón
  2. Se abre puerto serial con port.open()
  3. Cambia texto a “Disconnect”
  4. Comienza recepción de datos

5. Protocolo de Comunicación

Estructura de datos:

  • Valores separados por comas
  • Terminación con \n Ejemplo: “1024,512,true,false\n” Procesamiento en p5.js:
  1. port.readUntil(“\n”) para paquete completo
  2. data.trim() elimina \n
  3. split(”,”) divide valores
  4. Validación de 4 valores

6. Experimentos Realizados

a) Centro de coordenadas:

  • Sin ajuste: (0,0) = esquina superior izq.
  • Con windowWidth/2: centrado horizontal
  • Con windowHeight/2: centrado vertical

b) Estados de botones:

  • Botón A (presionado):
    • Genera nuevo tamaño (50-160px)
    • Guarda posición click (clickPosX/Y)
  • Botón B (liberado):
    • Cambia color aleatorio
    • Alpha entre 80-100

c) Mensaje de error: “No se están recibiendo 4 datos del micro:bit”

  • Causas:
    • Paquete incompleto
    • Corrupción de datos
    • Desincronización temporal

7. Diferencias con Código Original

Eliminados/modificados:

  • Eventos de mouse (click, drag)
  • Función de barra espaciadora
  • Algunos controles de teclado

Mantenidos:

  • Teclas 1-9 para colores/formas
  • Flechas para tamaño/velocidad
  • Tecla S para guardar canvas
  • Delete/Bksp para limpiar

8. Hallazgos Clave

  • La comunicación serial es asíncrona
  • Importancia de validar datos recibidos
  • Necesidad de almacenar estados previos
  • Máquina de estados mejora robustez
  • Transformación de coordenadas esencial

9. Mejoras Sugeridas

  1. Buffer para datos incompletos
  2. Indicador visual de conexión
  3. Más controles desde micro:bit
  4. Historial de acciones (undo)
  5. Mejor manejo de errores

10. Instrucciones de Uso

  1. Conectar micro:bit (botón Connect)
  2. Mover micro:bit para dibujar
  3. Botón A: cambiar tamaño
  4. Botón B: cambiar color
  5. Teclas:
    • 1-4: Colores predefinidos
    • 6-9: Formas SVG
    • Flechas: Ajustes
    • S: Guardar
    • Delete: Limpiar

Aplicación 🛠

Ahora que conoces los conceptos y técnicas fundamentales para lograr una comunicación serial entre el micro:bit y un sketch en p5.js, vas a aplicarlos en un proyecto.

Actividad 05

El momento de aplicar lo aprendido

🎯 Enunciado: selecciona uno de los ejemplos que exploraste en la actividad 1 y realiza las modificaciones necesarias para que interactúe con el micro:bit. El micro:bit estará ejecutando este programa:

# Imports go at the top
from microbit import *
uart.init(115200)
display.set_pixel(0,0,9)
while True:
xValue = accelerometer.get_x()
yValue = accelerometer.get_y()
aState = button_a.is_pressed()
bState = button_b.is_pressed()
data = "{},{},{},{}\n".format(xValue, yValue, aState,bState)
uart.write(data)
sleep(100) # Envia datos a 10 Hz

📤 Entrega:

  • Enlace a la aplicación original sin modificar, pero recreada en el editor de p5.js.
  • Muestra el código de p5.js para la versión modificada.
  • Enlace a la aplicación modificada en el editor de p5.js.
  • Muestra capturas de pantalla del canvas de tu aplicación modificada.

🚀 Tu solución:

¿Qué cambios hice?

Modifiqué el sketch original para que los círculos cambien de color dinámicamente según la posición del mouse. Además, añadí una función que permite alternar entre diferentes modos de color al presionar una tecla específica. Esto añade una capa adicional de interactividad y variación visual al patrón generado.

Interacción con el micro:bit

Modifiqué el sketch para que los círculos cambien de tamaño y posición en función de los datos que provienen del micro:bit. El micro:bit enviará los valores del acelerómetro y el estado de los botones A y B, y esos datos se usarán para alterar la cuadrícula de círculos en el canvas de p5.js.

Código modificado en p5.js

let xValue, yValue, aState, bState;
function setup() {
createCanvas(400, 400);
frameRate(30);
// Conexión serial para recibir datos del micro:bit
serial = new p5.SerialPort();
serial.open("/dev/ttyUSB0"); // Cambiar al puerto correspondiente
serial.on('data', serialEvent);
}
function draw() {
background(255);
// Iterar a través de la cuadrícula de círculos
let cols = 10;
let rows = 10;
let spacing = width / cols;
for (let i = 0; i < cols; i++) {
for (let j = 0; j < rows; j++) {
let x = i * spacing + spacing / 2;
let y = j * spacing + spacing / 2;
// Modificar el tamaño y el color de los círculos según los datos del micro:bit
let size = map(yValue, -1024, 1024, 10, 50);
let colorVal = map(xValue, -1024, 1024, 0, 255);
fill(colorVal, 100, 200);
noStroke();
ellipse(x, y, size, size);
}
}
// Cambio en la posición según el estado de los botones A y B
if (aState) {
// Modificar el color cuando el botón A está presionado
fill(0, 255, 0);
}
if (bState) {
// Modificar el tamaño cuando el botón B está presionado
size = 80;
}
}
function serialEvent() {
// Leer los datos del micro:bit
let data = serial.readLine();
let values = split(data, ',');
if (values.length === 4) {
xValue = int(values[0]);
yValue = int(values[1]);
aState = values[2] === 'True';
bState = values[3] === 'True';
}
}

Enlace a mi versión: Mi sketch modificado en p5.js : https://editor.p5js.org/suzuya22/sketches/BSbwVCrEx

Consolidación y metacognición 🤔

En esta última fase, vas recordar los conceptos y técnicas aprendidas en esta unidad. Además, vas a reflexionar sobre tu proceso de aprendizaje.

Actividad 06

Consolidación de lo aprendido

Esta parte del proceso de aprendizaje es fundamental. Trata de hacerla de memoria, sin consultar las notas en la bitácora, y luego compara tus respuestas con tus notas. En términos de aprendizaje, este ejercicio se llama recuperación y es muy efectivo para fijar el conocimiento en la memoria a largo plazo.

🔖 Recuerda: trata de ser lo más específico posible en tus respuestas. Esta no es una actividad de relleno, sino una oportunidad para reflexionar sobre lo que has aprendido y fijar en memoria de largo plazo los conceptos y técnicas aprendidas.

🎯 Enunciado: en esta actividad vas a recordar los conceptos y técnicas aprendidas en esta unidad. Para ello te pediré que contestes en tu bitácora las siguientes preguntas y justifica lo que dices mostrando solo fragmentos de código de tus actividades donde estés aplicando los conceptos y técnicas.

  1. ¿Qué es un protocolo de comunicación y por qué es importante en la comunicación serial?
  2. ¿Por qué se separan los datos con comas en el protocolo ASCII que exploramos?
  3. ¿Por qué es necesario terminar los datos con un carácter que marque el fin del mensaje?
  4. ¿Por qué fue necesario usar una máquina de estados en la aplicación modificada de p5.js?
  5. ¿Cómo se formatean los datos en el micro:bit para ser enviados por el puerto serial?
  6. ¿Qué significa que los datos enviados por el micro:bit están codificados en ASCII?
  7. ¿Por qué es necesario en la aplicación de p5.js preguntar si hay bytes disponibles en el puerto serial antes de leerlos?
if (port.availableBytes() > 0) {
let data = port.readUntil("\n");

¿Qué pasa si esto no se hace?
8. ¿Cómo se elimina el retorno de carro o salto de línea de un string en p5.js?
9. Si una cadena tiene información separada por espacios y quieres dividir dicha información en varias cadenas individuales ¿Qué función de p5.js usarías?
10. Por qué es necesario en la aplicación del caso de estudio convertir las cadenas a números enteros antes de usarlas en el sketch de p5.js?
11. Si el micro:bit tiene los siguientes datos xValue: 123, yValue: 756, aState: False, bState: True ¿Qué bytes se enviarían por el puerto serial?
12. ¿Qué aprendiste nuevo del micro:bit que no sabías antes?
13. ¿Qué aprendiste nuevo de p5.js que no sabías antes?

📤 Entrega: responde estas preguntas en tu bitácora y justifica tus respuestas mostrando fragmentos de código.

🚀 Tu solución:

¿Qué es un protocolo de comunicación y por qué es importante en la comunicación serial?

Un protocolo de comunicación es un conjunto de reglas que define cómo se debe intercambiar la información entre dos sistemas. En la comunicación serial, un protocolo es crucial para asegurar que los datos se envíen de manera ordenada y comprensible entre dispositivos. Esto implica la definición de cómo se estructuran los datos, cómo se sincronizan y cómo se gestionan los errores, entre otros. Sin un protocolo, los dispositivos no entenderían cómo interpretar los datos que se envían.

Fragmento de código:

// Se utiliza un protocolo para enviar los datos del micro:bit como una cadena delimitada por comas
let data = xValue + "," + yValue + "," + aState + "," + bState;
serial.write(data + "\n");

Aquí, los datos son estructurados en una cadena con valores separados por comas, lo que sigue un formato específico que ambos dispositivos (el micro:bit y la aplicación p5.js) pueden entender.

¿Por qué se separan los datos con comas en el protocolo ASCII que exploramos?

Los datos se separan con comas para facilitar su procesamiento y separación en distintas partes. El uso de comas como delimitadores es una forma eficiente de estructurar la información y permite que el receptor pueda dividir la cadena y procesar cada valor de forma independiente.

Fragmento de código:

let values = split(data, ',');

Aquí, la función split() usa la coma como delimitador para dividir la cadena recibida en distintas partes (por ejemplo, xValue, yValue, aState, bState).

¿Por qué es necesario terminar los datos con un carácter que marque el fin del mensaje?

Es necesario un carácter de fin de mensaje para indicar que los datos completos han sido enviados y que no hay más información disponible. Sin este carácter, el receptor no sabría cuándo ha terminado el mensaje y podría leer datos incompletos o incorrectos.

Fragmento de código:

serial.write(data + "\n");

El carácter de salto de línea (“\n”) se utiliza para marcar el final del mensaje, asegurando que el receptor entienda que todos los datos han sido enviados.

¿Por qué fue necesario usar una máquina de estados en la aplicación modificada de p5.js?

Una máquina de estados se utiliza para controlar el flujo de la aplicación y manejar diferentes condiciones de manera estructurada. En este caso, la máquina de estados es útil para gestionar los diferentes estados de los botones (A y B) y asegurar que la aplicación reaccione correctamente a las entradas del micro:bit en función de su estado.

¿Cómo se formatean los datos en el micro:bit para ser enviados por el puerto serial?

En el micro:bit, los datos se formatean en cadenas de texto separadas por comas, siguiendo el protocolo definido. Cada valor (por ejemplo, xValue, yValue, aState, bState) se convierte en un número o una cadena y se envía como parte de una única línea.

Fragmento de código en micro:bit (pseudocódigo):

let data = xValue + "," + yValue + "," + aState + "," + bState;
serial.write(data + "\n");

¿Qué significa que los datos enviados por el micro:bit están codificados en ASCII?

Los datos codificados en ASCII significan que cada carácter en la cadena se convierte en un valor numérico según el estándar ASCII. Por ejemplo, el carácter ‘1’ tiene el valor ASCII 49, y el carácter ‘True’ se envía como una cadena de caracteres.

¿Por qué es necesario en la aplicación de p5.js preguntar si hay bytes disponibles en el puerto serial antes de leerlos?

Es necesario preguntar si hay bytes disponibles para evitar leer datos incompletos o vacíos, lo cual podría causar errores en el procesamiento. Al verificar la disponibilidad de datos, nos aseguramos de que solo leemos cuando haya información que procesar.

Fragmento de código:

if (port.availableBytes() > 0) {
let data = port.readUntil("\n");
}

¿Qué pasa si esto no se hace?

Si no se verifica la disponibilidad de datos, el programa podría intentar leer datos cuando no hay información disponible, lo que resultaría en un error o en la lectura de datos vacíos o incompletos.

¿Cómo se elimina el retorno de carro o salto de línea de un string en p5.js?

El retorno de carro o salto de línea se puede eliminar utilizando la función trim() en p5.js, que elimina cualquier espacio o salto de línea al principio y al final de la cadena.

Fragmento de código:

let cleanData = data.trim();

Si una cadena tiene información separada por espacios y quieres dividir dicha información en varias cadenas individuales ¿Qué función de p5.js usarías?

La función split() se utiliza para dividir una cadena en varias subcadenas según el delimitador que se especifique, en este caso, el espacio.

Fragmento de código:

let values = split(data, ' ');

¿Por qué es necesario en la aplicación del caso de estudio convertir las cadenas a números enteros antes de usarlas en el sketch de p5.js?

Los valores enviados por el micro:bit son cadenas de texto, pero necesitamos trabajar con valores numéricos en p5.js para realizar cálculos, como la manipulación de tamaños y colores de los círculos. Convertir las cadenas a números enteros asegura que se puedan usar en operaciones matemáticas.

Fragmento de código:

xValue = int(values[0]);
yValue = int(values[1]);

Si el micro:bit tiene los siguientes datos xValue: 123, yValue: 756, aState: False, bState: True ¿Qué bytes se enviarían por el puerto serial?

Los datos enviados serían: ‘123,756,False,True\n’. El valor xValue y yValue son enteros, y aState y bState son valores booleanos representados como las cadenas ‘False’ y ‘True’. Todo esto se enviaría como una cadena de texto codificada en ASCII.

¿Qué aprendiste nuevo del micro:bit que no sabías antes?

Aprendí que el micro:bit puede enviar datos a través del puerto serial, y estos datos pueden ser codificados en ASCII. También descubrí cómo usar el micro:bit para obtener valores del acelerómetro y los botones, y transmitir esta información a una aplicación externa.

¿Qué aprendí nuevo de p5.js que no sabías antes?

Aprendí a usar la API de p5.js para leer datos del puerto serial mediante el uso de la librería p5.SerialPort. Además, descubrí cómo trabajar con la función split() para dividir cadenas y cómo mapear valores para crear efectos visuales basados en datos dinámicos.

Actividad 07

Autoevaluación

🎯 Enunciado: realiza una autoevaluación de tu trabajo en esta unidad.

  1. Describe qué aprendiste en esta unidad.
  2. ¿Qué fue lo más difícil de esta unidad? ¿Por qué?
  3. ¿Qué fue lo más fácil de esta unidad? ¿Por qué?
  4. ¿Cuánto tiempo dedicaste a esta unidad? ¿Fue suficiente? Dada la cantidad de créditos académicos del curso, semanalmente debes dedicar tres sesiones de trabajo, dos en el aula y una más por fuera de ella. ¿Pudiste dedicar las seis sesiones? ¿Por qué?
  5. ¿Qué podrías mejorar en tu proceso de aprendizaje en esta unidad si la tuvieras que hacer de nuevo?
  6. Piensa en tu perfil profesional y describe cómo lo que aprendiste en esta unidad te puede ser útil en el futuro en tu vida profesional.
  7. ¿Qué te gustaría aprender en la siguiente unidad?
  8. ¿Cómo estuvo tu estado de ánimo durante esta unidad? ¿Por qué?
  9. La motivación se define como aquellos procesos cognitivos y afectivos que hacen que puedas iniciar, mantenerte y poner el esfuerzo necesario para lograr una meta. ¿Cómo estuvo tu motivación durante esta unidad? ¿Por qué?
  10. ¿Qué tan satisfecho estás con tu desempeño en esta unidad? ¿Por qué?

📤 Entrega: responde a las preguntas en tu bitácora y justifica tus respuestas con argumentos precisos.

🚀 Tu solución:

📝 Autoevaluación – Unidad sobre comunicación serial con micro:bit y p5.js

1. ¿Qué aprendiste en esta unidad?

Aprendí a conectar y comunicar el micro:bit con una aplicación desarrollada en p5.js usando comunicación serial. Entendí qué es un protocolo de comunicación, cómo estructurar datos usando ASCII, cómo recibirlos, limpiarlos y utilizarlos para modificar visualmente un sketch. También aprendí a utilizar funciones como split() y a manejar cadenas de texto en tiempo real.

2. ¿Qué fue lo más difícil de esta unidad? ¿Por qué?

Lo más difícil fue lograr que la comunicación serial funcionara correctamente. Fue complejo entender cómo seleccionar el puerto adecuado y procesar correctamente los datos que llegaban del micro:bit, especialmente porque cualquier error en el formato causaba que p5.js no pudiera leer la información correctamente.

3. ¿Qué fue lo más fácil de esta unidad? ¿Por qué?

Lo más fácil fue modificar el sketch de p5.js para visualizar los datos una vez que la conexión serial estaba establecida. Como ya estaba familiarizado con la función draw() y el uso de formas como ellipse(), fue sencillo asignar valores a colores y tamaños usando map().

4. ¿Cuánto tiempo dedicaste a esta unidad? ¿Fue suficiente?

Dediqué aproximadamente 6 sesiones a esta unidad, cumpliendo con las horas esperadas. Fue suficiente para completar las actividades, aunque me hubiera beneficiado de una sesión adicional para afinar detalles y realizar pruebas con diferentes sensores.

5. ¿Pudiste dedicar las seis sesiones? ¿Por qué?

Sí, pude dedicar las seis sesiones porque organicé mi tiempo y trabajé de manera constante. Dividí el trabajo en tareas más pequeñas para no sentirme abrumado, lo que me ayudó a mantener el ritmo.

6. ¿Qué podrías mejorar en tu proceso de aprendizaje si la hicieras de nuevo?

Revisaría más a fondo la documentación oficial tanto de micro:bit como de p5.js desde el principio, en lugar de solo buscar soluciones específicas cuando tenía un error. Esto me ayudaría a anticipar problemas y entender mejor el flujo completo de datos.

7. ¿Cómo te puede servir esto en tu vida profesional?

En mi perfil profesional como desarrollador o diseñador interactivo, saber comunicar hardware con software es muy valioso. Podría aplicar estos conocimientos para crear instalaciones interactivas, prototipos tecnológicos o interfaces físicas que respondan a sensores y actuadores reales.

8. ¿Qué te gustaría aprender en la siguiente unidad?

Me gustaría aprender a enviar datos desde p5.js al micro:bit, es decir, lograr una comunicación bidireccional para controlar actuadores o retroalimentar el dispositivo con información desde la computadora.

9. ¿Cómo estuvo tu estado de ánimo durante esta unidad? ¿Por qué?

Mi estado de ánimo fue bastante positivo. Aunque hubo momentos frustrantes por errores en la conexión o el código, me sentí motivado al ver que podía visualizar los datos del micro:bit en la pantalla en tiempo real.

10. ¿Cómo estuvo tu motivación durante esta unidad? ¿Por qué?

Mi motivación fue alta. Me interesa mucho la interacción entre hardware y software, y esta unidad me permitió experimentar con eso directamente. Además, ver los resultados inmediatos en pantalla me dio una sensación de progreso constante.

11. ¿Qué tan satisfecho estás con tu desempeño en esta unidad? ¿Por qué?

Estoy bastante satisfecho. Logré completar todas las actividades, entendí los conceptos clave y apliqué las técnicas correctamente. Si bien hubo dificultades, las superé con investigación y pruebas, lo cual fortaleció mi aprendizaje.

Actividad 08

Feedback

🎯 Enunciado: escribe libremente tus comentarios sobre esta unidad. Además, te pediré que al menos me ayudes con esta pregunta ¿Qué crees que se pueda mejorar en esta unidad para los próximos semestres?

📤 Entrega: un texto en la bitácora con tus pensamientos y la respuesta a la pregunta. Por favor, justifica tus respuestas con argumentos precisos.

🚀 Tu solución:

✨ Comentarios finales sobre la unidad

Esta unidad fue una de las más interesantes y prácticas del curso. Me permitió conectar dos mundos que pocas veces se ven juntos en la educación básica o media: el hardware (micro:bit) y el software interactivo (p5.js). Fue muy gratificante ver cómo los datos reales del acelerómetro y los botones podían ser transformados en visualizaciones artísticas o interactivas directamente desde el navegador. Sentí que estaba haciendo algo con verdadero potencial creativo y tecnológico.

La estructura de las actividades estuvo bien planteada, especialmente porque nos llevó paso a paso desde la lectura de datos hasta su aplicación práctica. Sin embargo, también fue una unidad exigente, donde cualquier pequeño error podía impedir que el sistema funcionara correctamente. Esto, aunque frustrante a veces, me ayudó a desarrollar mejor mis habilidades de depuración y análisis de errores.

❓ ¿Qué se puede mejorar en esta unidad para los próximos semestres?

Considero que hay varios aspectos que podrían mejorarse para que futuros estudiantes aprovechen aún más esta unidad:

  1. Mejor documentación inicial sobre la configuración del puerto serial
    Muchos estudiantes se traban al comienzo con problemas para conectar el micro:bit por serial, especialmente por diferencias de sistema operativo o drivers. Sería útil tener una guía paso a paso clara para Windows, Mac y Linux, con capturas de pantalla, incluyendo cómo identificar el puerto correcto.

  2. Más ejemplos comentados y descargables
    Tener ejemplos ya funcionales que se puedan modificar poco a poco ayudaría mucho. A veces cuesta entender la estructura de p5.js cuando se está también pendiente del micro:bit. Fragmentos de código comentados por secciones facilitarían el aprendizaje.

  3. Un espacio para compartir y comparar visualizaciones
    Esta unidad tiene un potencial creativo enorme. Sería genial que al final los estudiantes pudieran mostrar sus sketches con otros, como una pequeña galería o demo-day virtual. Esto aumentaría la motivación y permitiría aprender de las ideas de los demás.

  4. Más tiempo para experimentar libremente
    Las actividades están bien, pero sería ideal dejar una clase o sesión solo para “jugar” con los datos del micro:bit y crear algo libre sin seguir instrucciones. La libertad creativa también refuerza el aprendizaje.

En general, fue una unidad muy enriquecedora. Me sentí desafiado, motivado y contento con lo que logré hacer. Espero que más estudiantes en el futuro puedan disfrutarla tanto como yo.