đŸ€– 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

PartHow many?Pin connection (R32)
D1 R32 (Peripheral board)1USB cable
D1 R32 (Central board)1USB cable
LED actuator (optional)1Peripheral LED → Pin 13
Motor driver (optional)1Peripheral 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