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.