Unidad 4
Introducción 📜
Sección titulada «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.
Set: ¿Qué aprenderás en esta unidad? 💡
Sección titulada «Set: ¿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.
Seek: Investigación 🔎
Sección titulada «Seek: Investigación 🔎»Vamos a analizar un caso de estudio que te servirá de base para resolver el problema que te plantearé en la fase de aplicación.
Actividad 1
Sección titulada «Actividad 1»Antes de comenzar con el caso de estudio te voy a pedir que explore este sitio:
Actividad 2
Sección titulada «Actividad 2»Caso de estudio: exploración inicial
Sección titulada «Caso de estudio: exploración inicial»Vamos a explorar uno de los ejemplos de diseño generativo tomado del sitio Generative Design.
En particular será este sketch que analizaremos juntos y luego modificaremos para controlarlo desde el micro:bit.
Actividad 3
Sección titulada «Actividad 3»Caso de estudio: micro:bit
Sección titulada «Caso de estudio: micro:bit»Una vez comprendido el funcionamiento del sketch, vamos a adaptarlo para que reciba información desde el micro:bit. Primero te mostraré cómo transmitir información desde el micro:bit.
Analicemos lentamente el siguiente código:
# Imports go at the topfrom 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
Probemos el programa del micro:bit con la aplicación SerialTerminal para ver los datos que se están enviando.
Analizaremos varios asuntos:
- ¿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
yyValue
a medida que el micro:bit se inclina hacia la izquierda, derecha, adelante y atrás. ¿Qué valores tomanxValue
yyValue
en cada caso? - ¿Qué valores toman
aState
ybState
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
.
Actividad 4
Sección titulada «Actividad 4»Caso de estudio: p5.js
Sección titulada «Caso de estudio: p5.js»Vamos a recorrer juntos la versión modificada. Para ello crearemos un nuevo proyecto p5.js.
- Modifica el archivo index.html así:
<!DOCTYPE html><html> <head> <script src="https://cdn.jsdelivr.net/npm/p5@1.11.10/lib/p5.js"></script> <script src="https://cdn.jsdelivr.net/npm/p5.sound@0.2.0/dist/p5.sound.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>
Necesitamos cargar, del ejemplo original, algunas imágenes:
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 seriallet 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 seriallet 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 connectionInitialized = false;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); connectionInitialized = false; } 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 connectionInitialized = false;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); connectionInitialized = false; } 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 connectionInitialized = false;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); connectionInitialized = false; } else { port.close(); }}
function draw() { //****************************************** if (!port.opened()) { connectBtn.html("Connect to micro:bit"); microBitConnected = false; } else { microBitConnected = true; connectBtn.html("Disconnect");
if (port.opened() && !connectionInitialized) { port.clear(); connectionInitialized = true; }
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 connectionInitialized = false;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); connectionInitialized = false; } 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.opened() && !connectionInitialized) { port.clear(); connectionInitialized = true; }
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 connectionInitialized = false;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); connectionInitialized = false; } 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.opened() && !connectionInitialized) { port.clear(); connectionInitialized = true; }
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
.
Apply: Aplicación 🛠
Sección titulada «Apply: 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 5
Sección titulada «Actividad 5»El momento de aplicar lo aprendido
Sección titulada «El momento de aplicar lo aprendido»Selecciona uno de los ejemplos que exploraste en la actividad 1 y realiza las modificaciones necesarias para que interactúe con el siguiente código corriendo en el micro:bit:
# Imports go at the topfrom 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é puedo hacer para trabajar en mi casa si no tengo un micro:bit?
Si no tienes un micro:bit puedes “emular” los datos que produce el micro:bit utilizando un emulador y conectando tanto la aplicación p5.js como el emulador a un pareja de puertos seriales virtuales.
La idea es esta:
Para lograr esto necesitas lo siguiente:
- Instalar este driver de virtual serial port.
- Configurar los puertos con el programa Setup for com0com que viene con la instalación.
- Instalar node.js.
- La aplicación que emulará al micro:bit. Te propongo esta. Ten cuidado al momento de seleccionar el puerto serial virtual que usarás para emular al micro:bit. Debe ser uno de los puertos seriales virtuales configurados en el paso 2.
- Necesitas realizar una modificación a la manera como te conectas desde p5.js al micro:bit así:
function setup() { port = createSerial(); connectBtn = createButton("Connect to micro:bit"); connectBtn.position(0, 0); connectBtn.mousePressed(() => connectBtnClick('emu'));}
function connectBtnClick(mode = 'emu') { if (!port.opened()) { if (mode === 'micro') { port.open('MicroPython', 115200); } else { port.open(115200); } connectionInitialized = false; } else { port.close(); }}
El código anterior te permitirá seleccionar alguno de los puertos seriales que tengas disponibles en tu sistema, incluyendo el virtual serial port y el que aparece cuando se conecta un micro:bit.
Ten en cuenta que debes seleccionar un puerto serial virtual diferente al seleccionado para el emulador, pero que esté conectado a este. Eso lo configuras con la aplicación Setup for com0com.
Este diagrama muestra cómo se relacionarían algunos de los programas anteriores:
Evidencias 🗂️
Sección titulada «Evidencias 🗂️»Reflect: Consolidación y metacognición 🤔
Sección titulada «Reflect: Consolidación y metacognición 🤔»Una vez termines esta unidad invierte en ti unos minutos para reflexionar sobre tu proceso de aprendizaje.
- ¿Qué es un protocolo de comunicación y por qué es importante en la comunicación serial?
- ¿Por qué se separan los datos con comas en el protocolo ASCII que exploramos?
- ¿Por qué es necesario terminar los datos con un carácter que marque el fin del mensaje?
- ¿Por qué fue necesario usar una máquina de estados en la aplicación modificada de p5.js?
- ¿Cómo se formatean los datos en el micro:bit para ser enviados por el puerto serial?
- ¿Qué significa que los datos enviados por el micro:bit están codificados en ASCII?
- ¿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é hiciste bien en esta unidad que debes continuar haciendo?
- ¿Qué deberías comenzar a hacer para mejorar tu proceso?