Project 4.10: "Rescue Robot"
What you’ll learn
- Goal 1: Detect a person by motion using PIR and move toward safe locations.
- Goal 2: Navigate environments with obstacles using ultrasonic distance checks.
- Goal 3: Communicate status to the operator via Bluetooth and Serial.
- Goal 4: Simulate a rescue by carrying a small object with clear actuator control.
- Goal 5: Switch between autonomous and remote‑controlled modes cleanly.
Blocks glossary
- machine.Pin(pin, machine.Pin.IN/OUT).value(v): Digital input/output for PIR and motors.
- sonar.Sonar(TRIG, ECHO).checkdist(): Ultrasonic distance in cm to avoid obstacles.
- ble_peripheral.BLESimplePeripheral(name): Send short status messages to operator.
- ble_handle.Handle().recv(callback): Receive remote commands (mode switch, actions).
- print(…): Serial logs for clear operator visibility.
- time.sleep_ms(ms): Cadence control for smooth movement and decisions.
What you need
| Part | How many? | Pin connection (R32) |
|---|---|---|
| D1 R32 | 1 | USB cable |
| Caterpillar chassis + motor driver | 1 | IN1 → 21, IN2 → 13, IN3 → 27, IN4 → 26 |
| Ultrasonic HC‑SR04 | 1 | TRIG → 26, ECHO → 5 |
| PIR motion sensor | 1 | Signal → 23 |
| Small servo or gripper (optional) | 1 | Signal → 14 (example) |
| Bluetooth (built‑in) | 1 | Internal |
- Share GND across R32, motor driver, ultrasonic, PIR, and gripper.
- Keep ultrasonic centered in front; mount PIR high with clear line of sight.
Before you start
- Wire motors to the driver and connect control pins (21,13,27,26).
- Connect ultrasonic TRIG to 26 and ECHO to 5; PIR signal to 23; optional gripper to 14.
- Open the Serial monitor for live status.
- Quick test:
print("Ready!") # Serial: confirm setup works before starting
Microprojects 1–5
Microproject 4.10.1 – Search for movement (person)
import machine # Load pins library
import time # Load time for delays
pir = machine.Pin(23, machine.Pin.IN) # PIR motion sensor on pin 23 as input
print("[Search] PIR ready on 23") # Serial: confirm PIR setup
m = pir.value() # Read PIR (0=no motion, 1=motion)
print("[Search] Motion=", m) # Serial: show current motion state
if m == 1: # If motion detected
print("EVENT:PERSON_DETECTED") # Serial: person detection event
else: # If no motion detected
print("EVENT:NO_MOTION") # Serial: no motion event
time.sleep_ms(400) # Small delay for stability
Reflection: You verified PIR detects motion and emits clear events.
Challenge: Add a second read after 300 ms to confirm and reduce false positives.
Microproject 4.10.2 – Navigation in an environment with obstacles
import machine # Load pins for motors
import sonar # Load ultrasonic module
import time # Load time for pacing
# Caterpillar motors (L298N style)
in1 = machine.Pin(21, machine.Pin.OUT) # Left motor IN1
in2 = machine.Pin(13, machine.Pin.OUT) # Left motor IN2
in3 = machine.Pin(27, machine.Pin.OUT) # Right motor IN3
in4 = machine.Pin(26, machine.Pin.OUT) # Right motor IN4
print("[Nav] Motors ready 21/13/27/26") # Serial: confirm motor pins
us = sonar.Sonar(26, 5) # Ultrasonic TRIG=26, ECHO=5
print("[Nav] Sonar ready") # Serial: confirm sonar
SAFE_CM = 25 # Safe distance threshold in cm
def forward(): # Helper: move forward
in1.value(1); in2.value(0) # Left forward
in3.value(1); in4.value(0) # Right forward
print("[Nav] FORWARD") # Serial: action log
def right(): # Helper: turn right
in1.value(1); in2.value(0) # Left forward
in3.value(0); in4.value(1) # Right backward
print("[Nav] RIGHT") # Serial: action log
def stop(): # Helper: stop movement
in1.value(0); in2.value(0) # Left stop
in3.value(0); in4.value(0) # Right stop
print("[Nav] STOP") # Serial: action log
d = us.checkdist() # Read distance in cm
print("[Nav] d=", d) # Serial: show distance
if (d is None) or (d <= 0): # Invalid distance
stop() # Stop for safety
elif d < SAFE_CM: # Obstacle too close
right() # Turn to avoid obstacle
time.sleep_ms(700) # Turn briefly
stop() # Stop to re-evaluate
else: # Path is clear
forward() # Move forward
time.sleep_ms(700) # Advance briefly
stop() # Stop to check again
Reflection: Simple rules keep navigation safe and readable.
Challenge: Raise SAFE_CM to 35 cm and compare how cautious the robot becomes.
Microproject 4.10.3 – Status communication to the operator
import ble_peripheral # Load Bluetooth peripheral
import ble_handle # Load Bluetooth handle for RX (optional)
import time # Load time for pacing
ble_p = ble_peripheral.BLESimplePeripheral("Rescue-R32") # Create BLE peripheral
print("[Comm] BLE 'Rescue-R32' ready") # Serial: confirm BLE
h = ble_handle.Handle() # Create RX handle (for future commands)
print("[Comm] RX handle ready") # Serial: confirm handle
def rx_method(msg): # Optional RX handler for operator commands
s = str(msg) # Ensure string
print("[Comm] RX:", s) # Serial: show incoming command
h.recv(rx_method) # Register RX callback
print("[Comm] RX callback registered") # Serial: confirm registration
ble_p.send("STATUS:SEARCHING") # Send current status to operator
print("[Comm] TX STATUS:SEARCHING") # Serial: mirror BLE message
time.sleep_ms(500) # Brief pause
Reflection: Clear status messages keep the operator informed without noise.
Challenge: Send “STATUS:OBSTACLE” when sonar reports distance below SAFE_CM.
Microproject 4.10.4 – Rescue simulation (carrying object)
import machine # Load pins for actuator control
import time # Load time for pacing
grip = machine.Pin(14, machine.Pin.OUT) # Simple gripper control on pin 14 (ON/OFF)
print("[Rescue] Gripper pin=14 ready") # Serial: confirm gripper
# Simulate pick up (ON) and drop (OFF)
grip.value(1) # Activate gripper to "carry" object
print("[Rescue] PICKUP") # Serial: pickup event
time.sleep_ms(800) # Hold for a moment
grip.value(0) # Release gripper to "drop" object
print("[Rescue] DROP") # Serial: drop event
time.sleep_ms(400) # Short pause
Reflection: Simple ON/OFF control demonstrates carrying behavior clearly.
Challenge: Add a second pin for a “lift” motor and coordinate PICKUP then LIFT for realism.
Microproject 4.10.5 – Autonomous and remote‑controlled mode
import ble_peripheral # Load Bluetooth peripheral for commands
import ble_handle # Load callback handle
import machine # Load pins for motors
import time # Load time for pacing
# Motors
in1 = machine.Pin(21, machine.Pin.OUT) # Left IN1
in2 = machine.Pin(13, machine.Pin.OUT) # Left IN2
in3 = machine.Pin(27, machine.Pin.OUT) # Right IN3
in4 = machine.Pin(26, machine.Pin.OUT) # Right IN4
print("[Mode] Motors ready") # Serial: confirm motors
# BLE
ble_p = ble_peripheral.BLESimplePeripheral("Rescue-R32") # BLE peripheral name
h = ble_handle.Handle() # RX handle
print("[Mode] BLE ready") # Serial: confirm BLE
mode_auto = True # Start in autonomous mode
print("[Mode] AUTO=", mode_auto) # Serial: show current mode
def forward(): # Helper: forward
in1.value(1); in2.value(0) # Left forward
in3.value(1); in4.value(0) # Right forward
print("[Mode] FORWARD") # Serial: action
def stop(): # Helper: stop
in1.value(0); in2.value(0) # Left stop
in3.value(0); in4.value(0) # Right stop
print("[Mode] STOP") # Serial: action
def rx_method(msg): # RX: switch modes or move
global mode_auto # Use global mode flag
s = str(msg) # Ensure string
print("[Mode] RX:", s) # Serial: show command
if s == "CMD:MODE_AUTO": # Command: switch to autonomous
mode_auto = True # Set auto mode
ble_p.send("ACK:MODE_AUTO") # Send ack
print("[Mode] TX ACK:MODE_AUTO") # Serial: mirror ack
elif s == "CMD:MODE_REMOTE": # Command: switch to remote
mode_auto = False # Set remote mode
ble_p.send("ACK:MODE_REMOTE") # Send ack
print("[Mode] TX ACK:MODE_REMOTE") # Serial: mirror ack
elif (not mode_auto) and (s == "CMD:FORWARD"): # If remote mode, forward
forward() # Execute forward
ble_p.send("ACK:FORWARD") # Send ack
print("[Mode] TX ACK:FORWARD") # Serial: mirror ack
elif (not mode_auto) and (s == "CMD:STOP"): # If remote mode, stop
stop() # Execute stop
ble_p.send("ACK:STOP") # Send ack
print("[Mode] TX ACK:STOP") # Serial: mirror ack
else: # Unknown or blocked command
ble_p.send("ERR:UNKNOWN") # Send error
print("[Mode] TX ERR:UNKNOWN") # Serial: mirror error
h.recv(rx_method) # Register RX callback
print("[Mode] RX registered") # Serial: confirm
# Minimal loop to illustrate mode behavior
if mode_auto: # If autonomous (at start)
forward() # Move briefly
time.sleep_ms(500) # Short autonomous action
stop() # Stop again
else: # If remote mode
print("[Mode] Awaiting remote commands") # Serial: idle in remote
time.sleep_ms(500) # Brief wait
Reflection: Mode switching keeps control flexible and safe for beginners.
Challenge: Add “CMD:RIGHT” and “CMD:LEFT” in remote mode for basic steering.
Main project
Rescue robot combining search, obstacle navigation, status communication, carrying simulation, and dual control modes
- Search: PIR detects a person.
- Navigate: Ultrasonic distance prevents collisions.
- Communicate: BLE + Serial report status clearly.
- Rescue: Gripper simulates pick‑up/drop.
- Control: Switch between autonomous and remote with short text commands.
import machine # Load pins for sensors and motors
import sonar # Load ultrasonic module
import ble_peripheral # Load BLE peripheral
import ble_handle # Load BLE RX handle
import time # Load time library
# --- Sensors ---
pir = machine.Pin(23, machine.Pin.IN) # PIR on pin 23
us = sonar.Sonar(26, 5) # Ultrasonic TRIG=26 ECHO=5
print("[Main] Sensors ready PIR=23 SONAR 26/5") # Serial: confirm sensors
# --- Motors ---
in1 = machine.Pin(21, machine.Pin.OUT) # Left IN1
in2 = machine.Pin(13, machine.Pin.OUT) # Left IN2
in3 = machine.Pin(27, machine.Pin.OUT) # Right IN3
in4 = machine.Pin(26, machine.Pin.OUT) # Right IN4
print("[Main] Motors ready 21/13/27/26") # Serial: confirm motors
# --- Gripper (optional) ---
grip = machine.Pin(14, machine.Pin.OUT) # Gripper control on pin 14
print("[Main] Gripper ready pin=14") # Serial: confirm gripper
# --- Communication ---
ble_p = ble_peripheral.BLESimplePeripheral("Rescue-R32") # BLE peripheral name
h = ble_handle.Handle() # RX handle
print("[Main] BLE 'Rescue-R32' ready") # Serial: confirm BLE
# --- Params ---
SAFE_CM = 25 # Safe distance threshold
mode_auto = True # Start in autonomous mode
print("[Main] SAFE_CM=", SAFE_CM, "AUTO=", mode_auto) # Serial: show params
# --- Helpers ---
def forward(): # Move forward
in1.value(1); in2.value(0) # Left forward
in3.value(1); in4.value(0) # Right forward
print("[Main] FORWARD") # Serial: action
def right(): # Turn right
in1.value(1); in2.value(0) # Left forward
in3.value(0); in4.value(1) # Right backward
print("[Main] RIGHT") # Serial: action
def stop(): # Stop
in1.value(0); in2.value(0) # Left stop
in3.value(0); in4.value(0) # Right stop
print("[Main] STOP") # Serial: action
def pickup_drop(): # Simulate rescue pick/drop
grip.value(1) # Activate gripper
print("[Main] PICKUP") # Serial: pickup event
time.sleep_ms(700) # Hold briefly
grip.value(0) # Release gripper
print("[Main] DROP") # Serial: drop event
def rx_method(msg): # Handle remote commands
global mode_auto # Use global mode flag
s = str(msg) # Ensure string
print("[Main] RX:", s) # Serial: show command
if s == "CMD:MODE_AUTO": # Switch to autonomous
mode_auto = True # Set flag
ble_p.send("ACK:MODE_AUTO") # Ack
print("[Main] TX ACK:MODE_AUTO") # Serial: mirror
elif s == "CMD:MODE_REMOTE": # Switch to remote
mode_auto = False # Set flag
ble_p.send("ACK:MODE_REMOTE") # Ack
print("[Main] TX ACK:MODE_REMOTE") # Serial: mirror
elif (not mode_auto) and (s == "CMD:FORWARD"): # Remote forward
forward() # Action
ble_p.send("ACK:FORWARD") # Ack
print("[Main] TX ACK:FORWARD") # Serial: mirror
elif (not mode_auto) and (s == "CMD:STOP"): # Remote stop
stop() # Action
ble_p.send("ACK:STOP") # Ack
print("[Main] TX ACK:STOP") # Serial: mirror
elif (not mode_auto) and (s == "CMD:RIGHT"): # Remote right
right() # Action
ble_p.send("ACK:RIGHT") # Ack
print("[Main] TX ACK:RIGHT") # Serial: mirror
elif (not mode_auto) and (s == "CMD:PICKUP"): # Remote pickup/drop
pickup_drop() # Action
ble_p.send("ACK:PICKUP") # Ack
print("[Main] TX ACK:PICKUP") # Serial: mirror
else: # Unknown
ble_p.send("ERR:UNKNOWN") # Error
print("[Main] TX ERR:UNKNOWN") # Serial: mirror
h.recv(rx_method) # Register RX callback
print("[Main] RX registered") # Serial: confirm
# --- Autonomous loop (simple) ---
for _ in range(0, 3, 1): # Run a few autonomous cycles
if not mode_auto: # If switched to remote, break
print("[Main] Remote mode engaged") # Serial: note change
break # Exit autonomous loop
d = us.checkdist() # Read distance cm
m = pir.value() # Read motion (0/1)
print("[Main] d=", d, " m=", m) # Serial: show sensors
if (d is None) or (d <= 0): # Invalid read
stop() # Stop safely
ble_p.send("STATUS:SENSOR_ERROR") # Notify operator
print("[Main] TX STATUS:SENSOR_ERROR") # Serial: mirror
time.sleep_ms(500) # Brief pause
continue # Next cycle
if d < SAFE_CM: # Obstacle close
ble_p.send("STATUS:OBSTACLE") # Notify obstacle
print("[Main] TX STATUS:OBSTACLE") # Serial: mirror
right() # Turn away
time.sleep_ms(700) # Turn briefly
stop() # Stop to recheck
else: # Path clear
if m == 1: # Person detected
ble_p.send("STATUS:PERSON") # Notify person
print("[Main] TX STATUS:PERSON") # Serial: mirror
forward() # Approach (simple)
time.sleep_ms(700) # Move briefly
stop() # Stop to simulate rescue
pickup_drop() # Simulate pick/drop
else: # No person detected
forward() # Patrol forward
time.sleep_ms(600) # Move a bit
stop() # Stop to check again
time.sleep_ms(300) # Calm cadence
print("[Main] Loop end or remote mode") # Serial: final note
External explanation
The rescue robot blends PIR motion detection with ultrasonic obstacle avoidance. It reports status using short BLE messages and Serial logs, simulates carrying via a simple output pin, and supports mode switching so an operator can intervene. The pacing and helpers keep behavior readable and friendly.
Story time
Your robot scans quietly. It spots movement, steers around furniture, and lets you know what it’s doing. When it reaches the target, it “rescues” with a gentle pick‑and‑drop, then waits for your next command.
Debugging (2)
Debugging 4.10.A – Failure to detect the target
# Ensure PIR has a clear view and reduce false negatives by confirming twice
print("[Debug] Confirm PIR with a short second read") # Serial: tip
# Example approach:
# if pir.value()==1:
# time.sleep_ms(200)
# if pir.value()==1: print("EVENT:PERSON_CONFIRMED")
Debugging 4.10.B – Blockage in narrow spaces
# Increase SAFE_CM and turn longer to escape tight spots
print("[Debug] Raise SAFE_CM and extend turn duration") # Serial: tip
# Example:
# SAFE_CM = 35
# right(); time.sleep_ms(900); stop()
Final checklist
- PIR detects motion and emits clear person events.
- Ultrasonic keeps navigation safe with obstacle checks.
- BLE and Serial provide concise status messages.
- Gripper simulates pick‑up and drop reliably.
- Mode switching between autonomous and remote works.
Extras
- Student tip: Design your own status codes (“STATUS:PICKUP_DONE”, “STATUS:RETURNING”).
- Instructor tip: Have teams define rescue scenarios (pickup point, return path).
- Glossary:
- PIR: Passive Infrared motion sensor (0/1 detection).
- Sonar: Ultrasonic distance in cm.
- Mode switch: Operator toggles autonomous vs remote via BLE.
- Mini tips:
- Keep cadence gentle to avoid jerky motion.
- Use short BLE strings so logs stay readable.
- Confirm PIR readings to reduce false triggers.