đŸ€– Level 4 – Mobile Robotics

Project 4.6: "Bluetooth R32 to R32 Communication"

 

What you’ll learn

  • Goal 1: Pair and connect two R32 boards using official Bluetooth central/peripheral blocks.
  • Goal 2: Send simple commands as short text messages between boards.
  • Goal 3: Control actuators on the remote board (e.g., LED or motor pins) via received commands.
  • Goal 4: Build two‑way communication (request/response) with clear, minimal messages.
  • Goal 5: Add basic reliability (acknowledgements and retries) to reduce message loss.

Blocks glossary

  • ble_peripheral.BLESimplePeripheral(name): Crea un dispositivo BLE que puede enviar/recibir mensajes.
  • ble_central.BLESimpleCentral(): Crea un cliente BLE que escanea y se conecta a un perifĂ©rico.
  • ble_central.scan(): Inicia el escaneo para descubrir perifĂ©ricos BLE cercanos.
  • ble_central.connect(name=’…’): Conecta al perifĂ©rico con nombre especĂ­fico.
  • ble_handle.Handle(): Crea un manejador para registrar un callback de recepciĂłn.
  • handle.recv(callback): Registra la funciĂłn que procesa mensajes entrantes.
  • ble_p.send(text) / ble_c.send(text): EnvĂ­a un texto corto por BLE al dispositivo conectado.
  • machine.Pin(pin, mode), .value(v): Controla actuadores remotos como LED/motores.
  • print(…): Registra cada paso en el monitor serial para no perderse.

What you need

Part How many? Pin connection (R32)
D1 R32 (Peripheral board) 1 USB cable
D1 R32 (Central board) 1 USB cable
LED actuator (optional) 1 Peripheral LED → Pin 13
Motor driver (optional) 1 Peripheral IN1 → 21, IN2 → 13 (example)
  • Alimenta cada R32 por USB y colĂłcalos cerca para facilitar el escaneo.
  • Usa nombres BLE claros (por ejemplo, “R32‑P” para perifĂ©rico y “R32‑C” para central).

Before you start

  • Conecta ambas placas por USB y abre dos monitores serial (uno por placa).
  • Decide cuĂĄl serĂĄ “Peripheral” y cuĂĄl “Central” (nombres: “R32‑P” y “R32‑C”).
  • Quick test:
print("Ready!")  # Confirmar que el monitor serial funciona en cada placa

Microprojects 1–5

Microproject 4.6.1 – Pairing and connection

# PERIPHERAL BOARD (R32-P) – Advertising and RX setup
import ble_peripheral                              # Bloque: importar periférico BLE
import ble_handle                                  # Bloque: importar manejador BLE
import time                                        # Bloque: importar tiempo para pausas
ble_p = ble_peripheral.BLESimplePeripheral("R32-P")# Bloque: crear periférico con nombre R32-P
print("[P] BLE Peripheral 'R32-P' ready")          # Bloque: confirmar periférico en serial
handle_p = ble_handle.Handle()                     # Bloque: crear manejador de recepciĂłn
print("[P] RX handle ready")                       # Bloque: confirmar handle
def rx_p(msg):                                     # Bloque: funciĂłn callback de recepciĂłn
    s = str(msg)                                   # Bloque: asegurar que el mensaje es string
    print("[P] RX:", s)                            # Bloque: mostrar mensaje entrante
handle_p.recv(rx_p)                                # Bloque: registrar callback de recepciĂłn
print("[P] RX callback registered")                # Bloque: confirmar registro
while True:                                        # Bloque: mantener periférico activo
    time.sleep_ms(800)                             # Bloque: pausa suave para mantener publicidad
# CENTRAL BOARD (R32-C) – Scan and connect to R32-P
import ble_central                                 # Bloque: importar central BLE
import time                                        # Bloque: importar tiempo para pausas
ble_c = ble_central.BLESimpleCentral()             # Bloque: crear cliente central BLE
print("[C] BLE Central ready")                     # Bloque: confirmar central
ble_c.scan()                                       # Bloque: iniciar escaneo de periféricos
print("[C] Scanning...")                           # Bloque: log de escaneo
time.sleep_ms(1200)                                # Bloque: pequeña espera para descubrir
ble_c.connect(name="R32-P")                        # Bloque: conectar al periférico por nombre
print("[C] Connected to 'R32-P'")                  # Bloque: confirmar conexiĂłn

Reflection: Lograste que un R32 publicara su presencia y el otro se conectara por nombre.
Challenge: Cambia el nombre del periférico y comprueba que el central solo conecta si coincide.


Microproject 4.6.2 – Sending simple commands

# CENTRAL (R32-C) – Send a simple command to the peripheral
import ble_central                                 # Bloque: importar central BLE
import time                                        # Bloque: importar tiempo
ble_c = ble_central.BLESimpleCentral()             # Bloque: crear central BLE
print("[C] Central ready")                         # Bloque: confirmar
ble_c.scan()                                       # Bloque: escanear periféricos cercanos
time.sleep_ms(1000)                                # Bloque: esperar resultados
ble_c.connect(name="R32-P")                        # Bloque: conectar a R32-P
print("[C] Connected")                              # Bloque: confirmar conexiĂłn
ble_c.send("CMD:LED_ON")                           # Bloque: enviar comando para encender LED remoto
print("[C] TX: CMD:LED_ON")                        # Bloque: reflejar en serial
time.sleep_ms(500)                                 # Bloque: pequeña pausa
ble_c.send("CMD:LED_OFF")                          # Bloque: enviar comando para apagar LED
print("[C] TX: CMD:LED_OFF")                       # Bloque: reflejar en serial
# PERIPHERAL (R32-P) – Receive command and print it
import ble_peripheral                              # Bloque: importar periférico BLE
import ble_handle                                  # Bloque: importar manejador BLE
import time                                        # Bloque: importar tiempo
ble_p = ble_peripheral.BLESimplePeripheral("R32-P")# Bloque: crear periférico
print("[P] Peripheral ready")                      # Bloque: confirmar
h = ble_handle.Handle()                            # Bloque: crear handle
print("[P] Handle ready")                          # Bloque: confirmar
def rx(msg):                                       # Bloque: callback de recepciĂłn
    s = str(msg)                                   # Bloque: convertir a string
    print("[P] RX:", s)                            # Bloque: mostrar recibido
h.recv(rx)                                         # Bloque: registrar callback
print("[P] RX registered")                         # Bloque: confirmar
while True:                                        # Bloque: mantener periférico vivo
    time.sleep_ms(800)                             # Bloque: pausa

Reflection: El central pudo mandar textos cortos y el periférico los imprimió.
Challenge: Añade “CMD:BEEP” y prepara al perifĂ©rico para responder con un pitido.


Microproject 4.6.3 – Remote control of actuators

# PERIPHERAL (R32-P) – Control LED actuator from received commands
import ble_peripheral                              # Bloque: importar periférico BLE
import ble_handle                                  # Bloque: importar manejador BLE
import machine                                     # Bloque: importar pines
import time                                        # Bloque: importar tiempo
ble_p = ble_peripheral.BLESimplePeripheral("R32-P")# Bloque: crear periférico
print("[P] Peripheral ready")                      # Bloque: confirmar
led = machine.Pin(13, machine.Pin.OUT)             # Bloque: LED en pin 13 como salida
print("[P] LED pin=13 ready")                      # Bloque: confirmar LED
h = ble_handle.Handle()                            # Bloque: crear handle
print("[P] Handle ready")                          # Bloque: confirmar
def rx(msg):                                       # Bloque: callback para comandos
    s = str(msg)                                   # Bloque: convertir a string
    print("[P] RX:", s)                            # Bloque: log del comando
    if s == "CMD:LED_ON":                          # Bloque: si comando encender LED
        led.value(1)                               # Bloque: encender LED
        print("[P] LED ON")                        # Bloque: confirmar acciĂłn
    elif s == "CMD:LED_OFF":                       # Bloque: si comando apagar LED
        led.value(0)                               # Bloque: apagar LED
        print("[P] LED OFF")                       # Bloque: confirmar acciĂłn
    else:                                          # Bloque: comando desconocido
        print("[P] Unknown CMD")                   # Bloque: informar desconocido
h.recv(rx)                                         # Bloque: registrar callback
print("[P] RX registered")                         # Bloque: confirmar registro
while True:                                        # Bloque: bucle del periférico
    time.sleep_ms(800)                             # Bloque: pausa para operar estable
# CENTRAL (R32-C) – Send LED control commands
import ble_central                                 # Bloque: importar central BLE
import time                                        # Bloque: importar tiempo
ble_c = ble_central.BLESimpleCentral()             # Bloque: crear central
print("[C] Central ready")                         # Bloque: confirmar
ble_c.scan()                                       # Bloque: escanear periféricos
time.sleep_ms(1000)                                # Bloque: esperar descubrimiento
ble_c.connect(name="R32-P")                        # Bloque: conectar a periférico R32-P
print("[C] Connected to R32-P")                    # Bloque: confirmar conexiĂłn
ble_c.send("CMD:LED_ON")                           # Bloque: encender LED remoto
print("[C] TX: CMD:LED_ON")                        # Bloque: reflejar envĂ­o
time.sleep_ms(800)                                 # Bloque: pausa entre comandos
ble_c.send("CMD:LED_OFF")                          # Bloque: apagar LED remoto
print("[C] TX: CMD:LED_OFF")                       # Bloque: reflejar envĂ­o

Reflection: Ya controlas un actuador remoto de forma limpia y visible.
Challenge: Añade “CMD:BLINK” y haz que el perifĂ©rico parpadee 3 veces con pausas cortas.


Microproject 4.6.4 – Two‑way communication

# PERIPHERAL (R32-P) – Receive command and send ACK replies
import ble_peripheral                              # Bloque: importar periférico BLE
import ble_handle                                  # Bloque: importar manejador BLE
import machine                                     # Bloque: importar pines
import time                                        # Bloque: importar tiempo
ble_p = ble_peripheral.BLESimplePeripheral("R32-P")# Bloque: crear periférico
print("[P] Peripheral ready")                      # Bloque: confirmar
led = machine.Pin(13, machine.Pin.OUT)             # Bloque: LED en pin 13
print("[P] LED pin=13 ready")                      # Bloque: confirmar LED
h = ble_handle.Handle()                            # Bloque: crear handle
print("[P] Handle ready")                          # Bloque: confirmar
def rx(msg):                                       # Bloque: callback RX
    s = str(msg)                                   # Bloque: convertir a string
    print("[P] RX:", s)                            # Bloque: mostrar recibido
    if s == "REQ:STATUS":                          # Bloque: solicitud de estado
        st = "LED=" + ("ON" if led.value() == 1 else "OFF")  # Bloque: componer estado simple
        ble_p.send("ACK:" + st)                    # Bloque: enviar respuesta ACK con estado
        print("[P] TX:", "ACK:" + st)              # Bloque: reflejar respuesta en serial
    elif s == "CMD:LED_ON":                        # Bloque: comando LED ON
        led.value(1)                               # Bloque: ejecutar acciĂłn
        ble_p.send("ACK:LED_ON")                   # Bloque: enviar ACK
        print("[P] TX: ACK:LED_ON")                # Bloque: reflejar ACK
    elif s == "CMD:LED_OFF":                       # Bloque: comando LED OFF
        led.value(0)                               # Bloque: ejecutar acciĂłn
        ble_p.send("ACK:LED_OFF")                  # Bloque: enviar ACK
        print("[P] TX: ACK:LED_OFF")               # Bloque: reflejar ACK
    else:                                          # Bloque: desconocido
        ble_p.send("ERR:UNKNOWN")                  # Bloque: enviar error
        print("[P] TX: ERR:UNKNOWN")               # Bloque: reflejar error
h.recv(rx)                                         # Bloque: registrar callback
print("[P] RX registered")                         # Bloque: confirmar
while True:                                        # Bloque: mantener servicio
    time.sleep_ms(800)                             # Bloque: pausa
# CENTRAL (R32-C) – Send REQ and read ACK prints on its serial
import ble_central                                 # Bloque: importar central BLE
import time                                        # Bloque: importar tiempo
ble_c = ble_central.BLESimpleCentral()             # Bloque: crear central
print("[C] Central ready")                         # Bloque: confirmar
ble_c.scan()                                       # Bloque: escanear
time.sleep_ms(1000)                                # Bloque: esperar
ble_c.connect(name="R32-P")                        # Bloque: conectar a R32-P
print("[C] Connected")                              # Bloque: confirmar
ble_c.send("REQ:STATUS")                           # Bloque: solicitar estado al periférico
print("[C] TX: REQ:STATUS")                        # Bloque: reflejar envĂ­o
time.sleep_ms(800)                                 # Bloque: pausa antes de nuevo envĂ­o
ble_c.send("CMD:LED_ON")                           # Bloque: encender LED remoto
print("[C] TX: CMD:LED_ON")                        # Bloque: reflejar envĂ­o

Reflection: Ahora tienes ida y vuelta: el periférico responde con ACK y estado.
Challenge: Agrega “REQ:PING” y responde “ACK:PONG” para validar conectividad rápida.


Microproject 4.6.5 – Reliable communication protocol (ACK + retry)

# CENTRAL (R32-C) – Simple reliability: send, wait, retry if no action observed locally
import ble_central                                 # Bloque: importar central BLE
import time                                        # Bloque: importar tiempo
ble_c = ble_central.BLESimpleCentral()             # Bloque: crear central
print("[C] Central ready")                         # Bloque: confirmar
ble_c.scan()                                       # Bloque: escanear
time.sleep_ms(1000)                                # Bloque: esperar resultados
ble_c.connect(name="R32-P")                        # Bloque: conectar por nombre
print("[C] Connected to R32-P")                    # Bloque: confirmar
ble_c.send("CMD:LED_ON")                           # Bloque: primer intento de encendido
print("[C] TX: CMD:LED_ON")                        # Bloque: reflejar envĂ­o
time.sleep_ms(600)                                 # Bloque: esperar una respuesta/efecto
# Nota: en esta plantilla, el central no recibe ACK directamente; confĂ­a en tiempos.
# Si no observas efecto remoto (por ejemplo, visual), reintenta:
ble_c.send("CMD:LED_ON")                           # Bloque: reintento
print("[C] TX: RETRY CMD:LED_ON")                  # Bloque: reflejar reintento
time.sleep_ms(600)                                 # Bloque: pausa tras reintento
ble_c.send("REQ:STATUS")                           # Bloque: pedir estado para confirmar
print("[C] TX: REQ:STATUS")                        # Bloque: reflejar solicitud
# PERIPHERAL (R32-P) – Always reply with ACK to improve reliability
import ble_peripheral                              # Bloque: importar periférico BLE
import ble_handle                                  # Bloque: importar manejador BLE
import machine                                     # Bloque: importar pines
import time                                        # Bloque: importar tiempo
ble_p = ble_peripheral.BLESimplePeripheral("R32-P")# Bloque: crear periférico
print("[P] Peripheral ready")                      # Bloque: confirmar
led = machine.Pin(13, machine.Pin.OUT)             # Bloque: LED remoto
print("[P] LED pin=13 ready")                      # Bloque: confirmar LED
h = ble_handle.Handle()                            # Bloque: crear handle
print("[P] Handle ready")                          # Bloque: confirmar
def rx(msg):                                       # Bloque: callback RX
    s = str(msg)                                   # Bloque: convertir a string
    print("[P] RX:", s)                            # Bloque: mostrar recibido
    if s == "CMD:LED_ON":                          # Bloque: comando ON
        led.value(1)                               # Bloque: encender LED
        ble_p.send("ACK:LED_ON")                   # Bloque: confirmar acciĂłn
        print("[P] TX: ACK:LED_ON")                # Bloque: reflejar ACK
    elif s == "CMD:LED_OFF":                       # Bloque: comando OFF
        led.value(0)                               # Bloque: apagar LED
        ble_p.send("ACK:LED_OFF")                  # Bloque: confirmar acciĂłn
        print("[P] TX: ACK:LED_OFF")               # Bloque: reflejar ACK
    elif s == "REQ:STATUS":                        # Bloque: solicitud de estado
        st = "LED=" + ("ON" if led.value()==1 else "OFF")  # Bloque: estado textual
        ble_p.send("ACK:" + st)                    # Bloque: enviar estado
        print("[P] TX:", "ACK:" + st)              # Bloque: reflejar estado
    else:                                          # Bloque: desconocido
        ble_p.send("ERR:UNKNOWN")                  # Bloque: enviar error
        print("[P] TX: ERR:UNKNOWN")               # Bloque: reflejar error
h.recv(rx)                                         # Bloque: registrar callback
print("[P] RX registered")                         # Bloque: confirmar registro
while True:                                        # Bloque: mantener servicio
    time.sleep_ms(800)                             # Bloque: pausa

Reflection: Con ACK y reintentos, la comunicaciĂłn se vuelve mĂĄs confiable y clara.
Challenge: Añade un “REQ:PING” en central y responde con “ACK:PONG” para pruebas de latencia.


Main project

Bluetooth R32 to R32 communication with commands, actuators, two‑way messages, and simple reliability

  • Pair/connect: Central escanea y conecta al perifĂ©rico por nombre.
  • Commands: Mensajes cortos como “CMD:LED_ON/OFF” controlan actuadores remotos.
  • Two‑way: El perifĂ©rico responde con ACK y “REQ:STATUS” entrega estado.
  • Reliability: Reintento del central y respuestas ACK del perifĂ©rico mejoran la robustez.
# PERIPHERAL (R32-P) – Full service with LED control and ACK replies
import ble_peripheral                              # Bloque: importar periférico BLE
import ble_handle                                  # Bloque: importar manejador BLE
import machine                                     # Bloque: importar pines
import time                                        # Bloque: importar tiempo
ble_p = ble_peripheral.BLESimplePeripheral("R32-P")# Bloque: crear periférico R32-P
print("[P] Peripheral R32-P ready")                # Bloque: confirmar periférico
led = machine.Pin(13, machine.Pin.OUT)             # Bloque: LED remoto en pin 13
print("[P] LED pin=13 ready")                      # Bloque: confirmar LED
h = ble_handle.Handle()                            # Bloque: crear handle RX
print("[P] Handle ready")                          # Bloque: confirmar handle
def rx(msg):                                       # Bloque: callback RX
    s = str(msg)                                   # Bloque: convertir a string
    print("[P] RX:", s)                            # Bloque: mostrar recibido
    if s == "CMD:LED_ON":                          # Bloque: encender LED
        led.value(1)                               # Bloque: aplicar acciĂłn
        ble_p.send("ACK:LED_ON")                   # Bloque: enviar ACK
        print("[P] TX: ACK:LED_ON")                # Bloque: confirmar
    elif s == "CMD:LED_OFF":                       # Bloque: apagar LED
        led.value(0)                               # Bloque: aplicar acciĂłn
        ble_p.send("ACK:LED_OFF")                  # Bloque: enviar ACK
        print("[P] TX: ACK:LED_OFF")               # Bloque: confirmar
    elif s == "REQ:STATUS":                        # Bloque: solicitud estado
        st = "LED=" + ("ON" if led.value()==1 else "OFF")  # Bloque: formar estado
        ble_p.send("ACK:" + st)                    # Bloque: enviar estado
        print("[P] TX:", "ACK:" + st)              # Bloque: confirmar envĂ­o
    elif s == "REQ:PING":                          # Bloque: ping prueba
        ble_p.send("ACK:PONG")                     # Bloque: respuesta inmediata
        print("[P] TX: ACK:PONG")                  # Bloque: confirmar
    else:                                          # Bloque: comando desconocido
        ble_p.send("ERR:UNKNOWN")                  # Bloque: error
        print("[P] TX: ERR:UNKNOWN")               # Bloque: confirmar
h.recv(rx)                                         # Bloque: registrar callback RX
print("[P] RX registered")                         # Bloque: confirmar
while True:                                        # Bloque: mantener servicio activo
    time.sleep_ms(800)                             # Bloque: pausa de servicio
# CENTRAL (R32-C) – Full client with connect, commands, status requests, and simple retries
import ble_central                                 # Bloque: importar central BLE
import time                                        # Bloque: importar tiempo
ble_c = ble_central.BLESimpleCentral()             # Bloque: crear central
print("[C] Central ready")                         # Bloque: confirmar
ble_c.scan()                                       # Bloque: escanear periféricos cercanos
print("[C] Scanning...")                           # Bloque: log de escaneo
time.sleep_ms(1200)                                # Bloque: esperar descubrimiento
ble_c.connect(name="R32-P")                        # Bloque: conectar al periférico R32-P
print("[C] Connected to R32-P")                    # Bloque: confirmar conexiĂłn
ble_c.send("REQ:PING")                             # Bloque: enviar ping
print("[C] TX: REQ:PING")                          # Bloque: confirmar envĂ­o
time.sleep_ms(500)                                 # Bloque: pausa
ble_c.send("CMD:LED_ON")                           # Bloque: encender LED remoto
print("[C] TX: CMD:LED_ON")                        # Bloque: confirmar envĂ­o
time.sleep_ms(800)                                 # Bloque: pausa para acciĂłn
ble_c.send("REQ:STATUS")                           # Bloque: pedir estado remoto
print("[C] TX: REQ:STATUS")                        # Bloque: confirmar envĂ­o
time.sleep_ms(800)                                 # Bloque: pausa
ble_c.send("CMD:LED_OFF")                          # Bloque: apagar LED remoto
print("[C] TX: CMD:LED_OFF")                       # Bloque: confirmar envĂ­o
time.sleep_ms(800)                                 # Bloque: pausa final

External explanation

Usamos Ășnicamente bloques BLE oficiales: un R32 actĂșa como perifĂ©rico y el otro como central. Los mensajes de texto cortos (CMD/REQ/ACK/ERR) hacen la comunicaciĂłn clara y estable. Con ACK y reintentos simples, el alumno entiende cĂłmo mejorar la confiabilidad sin estructuras complicadas.


Story time

Dos robots se hablan: uno pide, el otro responde. Enciende la luz, apĂĄgala, dime tu estado. Es un diĂĄlogo breve, educado y eficaz, como pasar notas entre amigos que cooperan.


Debugging (2)

Debugging 4.6.A – Unexpected disconnection

# Mantén los dispositivos cerca y reduce pausas excesivas
print("[Debug] If disconnects, re-scan and reconnect")  # Bloque: consejo en serial
# Procedimiento:
# ble_c.scan()       # Bloque: volver a escanear
# ble_c.connect(...) # Bloque: reconectar por nombre
# Evita mover las placas durante la sesiĂłn

Debugging 4.6.B – Unreceived commands

# Verifica que el periférico tenga el callback RX registrado
print("[Debug] Ensure handle.recv(callback) is set")  # Bloque: recordar registro
# Asegura mensajes cortos y claros, por ejemplo "CMD:LED_ON"
# Añade pequeñas pausas (500–800 ms) entre comandos para evitar saturaciĂłn

Final checklist

  • El central escanea y conecta al perifĂ©rico por nombre.
  • Se envĂ­an y reciben mensajes cortos (CMD/REQ/ACK/ERR).
  • El perifĂ©rico controla actuadores (LED) segĂșn comandos.
  • Hay respuestas ACK y solicitudes REQ:STATUS.
  • Pequeñas pausas y reintentos mejoran la confiabilidad.

Extras

  • Student tip: Diseña tus propios comandos (“CMD:BEEP”, “REQ:TEMP”) y documenta quĂ© hacen.
  • Instructor tip: Pide a los equipos que definan un mini protocolo y lo prueben con otro grupo.
  • Glossary:
    • Central/Peripheral: Roles en BLE; el central inicia conexiones, el perifĂ©rico las acepta.
    • ACK: ConfirmaciĂłn de recepciĂłn/acciĂłn.
    • Retry: Segundo intento cuando la acciĂłn no se percibe.
  • Mini tips:
    • MantĂ©n mensajes muy cortos para evitar problemas.
    • Usa nombres Ășnicos de dispositivos para facilitar la conexiĂłn.
    • Añade un “PING/PONG” para comprobar la comunicaciĂłn rĂĄpidamente.
On this page