Ir al contenido

Unidad 5

En esta unidad vas a explorar un protocolo de comunicación serial binario, continuando con el mismo proyecto de la unidad anterior. Vas a ver por qué los protocolos binarios son relevantes: son más compactos y más eficientes, pero también más complejos de implementar. La idea es que apliques lo que aprendiste sobre la arquitectura desacoplada (patrón Adapter) para crear un nuevo adapter que soporte el protocolo binario, sin tocar ninguna otra capa del sistema.


Criterio (peso)Cumple plenamente (5.0)Se cumple medianamente (4.0)Problemas importantes (3.0)Falta comprensión básica (2.0)No hay evidencia (0.0)
1. Aplicación + bitácora (40%)La app se ejecuta sin fallos en el entorno acordado. Evidencia completa y verificable en bitácora. Todo consistente con lo mostrado en la demo.La app funciona y cumple lo esencial. La bitácora permite verificar, pero hay 1–2 vacíos menoresLa app funciona parcialmente o depende de condiciones no declaradas. Bitácora con vacíos importantes o incompleta.La app no corre o no demuestra lo requerido. La bitácora no permite verificación de la app.No se entregaron evidencias o no se puede acceder a ellas
Evaluación
2. Sustentación (60%)Responde a las preguntas con precisión, conectando: (a) lo que se ve, (b) cómo está hecho, y (c) por qué. Usa su bitácora para justificar decisiones. Reconoce límites/errores y propone cómo probar/mejorar.Respuestas correctas pero con imprecisiones menores o justificación superficial. Usa parcialmente la bitácora para sustentar.Responde solo “qué hizo” pero le cuesta explicar “cómo” o “por qué”. Necesita guía para conectar con su propia evidencia/bitácora.No logra responder de forma coherente o responde sin relación con lo presentado/documentado. Evidencia falta de comprensión básica del trabajo entregado.No se entregaron evidencias o no se puede acceder a ellas
Evaluación


En esta actividad voy a guiar el análisis. Tu rol es observar, participar en la discusión y registrar tus observaciones en la bitácora.

Paso 1: Del ASCII al binario — ¿Qué cambia?

Sección titulada «Paso 1: Del ASCII al binario — ¿Qué cambia?»

Recuerda el protocolo ASCII del micro:bit que estudiamos en la unidad anterior:

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)

Ahora se reemplaza únicamente la línea de empaquetado por:

import struct
data = struct.pack('>2h2B', xValue, yValue, int(aState), int(bState))

¿Qué significa esto?

  • struct.pack empaqueta datos en formato binario compacto.
  • '>2h2B': > = big-endian, 2h = 2 enteros con signo de 2 bytes cada uno (xValue, yValue), 2B = 2 bytes sin signo (aState, bState).
  • El paquete resultante tiene tamaño fijo: 2+2+1+1 = 6 bytes.
  • Comparación: para enviar xValue=500, yValue=524, aState=True, bState=False, el protocolo ASCII produce la cadena "500,524,True,False\n" (~19 bytes), mientras que el protocolo binario siempre envía exactamente 6 bytes.

El código completo del micro:bit con el nuevo formato (solo datos, sin framing todavía):

from microbit import *
import struct
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 = struct.pack('>2h2B', xValue, yValue, int(aState), int(bState))
uart.write(data)
sleep(100)

Paso 2: El problema de sincronización — ¿Por qué necesitamos framing?

Sección titulada «Paso 2: El problema de sincronización — ¿Por qué necesitamos framing?»

Si leemos 6 bytes directamente sin ningún mecanismo de sincronización, el receptor puede empezar a leer a mitad de un paquete. Mira este código de lectura “sin framing”:

if (port.availableBytes() >= 6) {
let data = port.readBytes(6);
if (data) {
const buffer = new Uint8Array(data).buffer;
const view = new DataView(buffer);
microBitX = view.getInt16(0);
microBitY = view.getInt16(2);
microBitAState = view.getUint8(4) === 1;
microBitBState = view.getUint8(5) === 1;
updateButtonStates(microBitAState, microBitBState);
print(`microBitX: ${microBitX} microBitY: ${microBitY} microBitAState: ${microBitAState} microBitBState: ${microBitBState} \n`);
}
}

Este código puede producir salidas como esta:

Connected to serial port
A pressed
microBitX: 500 microBitY: 524 microBitAState: true microBitBState: false
Microbit ready to draw
92 microBitX: 500 microBitY: 524 microBitAState: true microBitBState: false
microBitX: 500 microBitY: 513 microBitAState: false microBitBState: false
222 microBitX: 3073 microBitY: 1 microBitAState: false microBitBState: false

¿Por qué ocurre esto?

  • La comunicación serial es un flujo continuo de bytes, sin fronteras entre paquetes.
  • Sin delimitadores, si se pierde o llega un byte extra al conectarse, la lectura se desalinea.
  • Los bytes de un paquete se mezclan con los del siguiente.
  • Valores absurdos como microBitX: 3073 o microBitY: 513 son el resultado de interpretar bytes de dos paquetes distintos como si fueran uno solo.

¿Qué es framing y por qué lo necesitamos?

  • Sincronización: un byte de inicio (header) marca dónde comienza cada paquete.
  • Integridad: un checksum verifica que los datos no están corruptos.
  • Robustez: si el receptor recibe datos residuales al conectarse, puede descartar bytes hasta encontrar un paquete válido.

Paso 3: El protocolo binario final con framing

Sección titulada «Paso 3: El protocolo binario final con framing»

El protocolo final usa un paquete de 8 bytes con la siguiente estructura:

ByteContenidoDescripción
00xAAByte de sincronización (header)
1–2int16 BEValor del acelerómetro X (big-endian, con signo)
3–4int16 BEValor del acelerómetro Y (big-endian, con signo)
5uint8Estado del botón A (1 = presionado, 0 = liberado)
6uint8Estado del botón B (1 = presionado, 0 = liberado)
7uint8Checksum: (suma de bytes 1 a 6) % 256

El código final del micro:bit:

from microbit import *
import struct
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 = struct.pack('>2h2B', xValue, yValue, int(aState), int(bState))
checksum = sum(data) % 256
packet = b'\xAA' + data + bytes([checksum])
uart.write(packet)
sleep(100)

¿Qué hace cada línea del framing?

  • checksum = sum(data) % 256: suma todos los bytes de datos y lo ajusta a 1 byte (0–255).
  • packet = b'\xAA' + data + bytes([checksum]): concatena header + datos + checksum.

La lógica del receptor debe:

  1. Acumular bytes en un buffer.
  2. Buscar el byte 0xAA al inicio del buffer.
  3. Si el primer byte NO es 0xAA, descartarlo y seguir buscando.
  4. Si hay al menos 8 bytes y el primero es 0xAA: extraer el paquete.
  5. Calcular el checksum sobre los bytes 1–6 y comparar con el byte 7.
  6. Si coincide: extraer los valores con DataView o Buffer.readInt16BE.
  7. Si no coincide: descartar el byte 0xAA y seguir buscando (era un falso positivo).

El proveedor de hardware ha actualizado el firmware del dispositivo para enviar datos usando el protocolo serial binario que analizamos en la actividad anterior. Como en la unidad anterior, no puedes cambiar el firmware. Tu tarea es crear un nuevo adaptador que soporte este protocolo sin modificar ninguna otra parte del sistema.

La documentación del dispositivo de hardware es esta:

  • El sensor envía paquetes binarios a 115200 baudios a una frecuencia de 10 Hz.

  • Estructura del paquete (8 bytes):

    ByteContenidoDescripción
    00xAAByte de sincronización (header)
    1–2int16 BEValor del acelerómetro X (-2048 a 2047)
    3–4int16 BEValor del acelerómetro Y (-2048 a 2047)
    5uint8Estado del botón A (1 = presionado, 0 = liberado)
    6uint8Estado del botón B (1 = presionado, 0 = liberado)
    7uint8Checksum: (suma de bytes 1 a 6) % 256
  • Si el checksum calculado no coincide con el recibido, la trama está corrupta y debe descartarse. Se debe registrar un mensaje de advertencia en la consola.

  • Ejemplo de paquete válido (en hexadecimal):
    AA 01 F4 02 0C 01 00 FE

    Donde: xValue=500 (01 F4), yValue=524 (02 0C), btnA=true (01), btnB=false (00),
    checksum = (0x01+0xF4+0x02+0x0C+0x01+0x00) % 256 = 0xFE (254).

Qué debes implementar:

  1. Crear el archivo adapters/MicrobitBinaryAdapter.js en el repositorio del caso de estudio que:

    • Herede de BaseAdapter.
    • Abra el puerto serial.
    • Acumule bytes entrantes en un buffer.
    • Implemente la lógica de framing: buscar el header 0xAA y verificar el checksum.
    • Emita this.onData?.({ x, y, btnA, btnB }) cuando reciba un paquete válido.
    • El contrato de salida es idéntico al del MicrobitASCIIAdapter.js.
  2. Registrar el adapter en bridgeServer.js:

    • Descomentar/añadir la línea de importación del nuevo adapter.
    • Añadir un caso "microbitBinary" en la función createAdapter() que instancie tu nuevo adapter.
  3. Probar el sistema completo:

    • Flashear el micro:bit con el firmware binario (proporcionado por el profesor).
    • Ejecutar: node bridgeServer.js --device microbitBinary --wsPort 8081
    • Verificar que el sketch funciona correctamente con los datos del micro:bit.

Pista para la implementación:

Observa cómo MicrobitASCIIAdapter.js acumula texto en un string buffer y busca el carácter \n para delimitar líneas. Tu adapter debe hacer lo análogo pero con bytes: acumular en un Buffer de Node.js, buscar el byte 0xAA, y cuando tengas al menos 8 bytes verificar el checksum antes de parsear.


Reflect: Consolidación y metacognición 🤔

Sección titulada «Reflect: Consolidación y metacognición 🤔»