🤖 Level 4 – Mobile Robotics

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

PartHow many?Pin connection (R32)
D1 R321USB cable
Caterpillar chassis + motor driver1IN1 → 21, IN2 → 13, IN3 → 27, IN4 → 26
Ultrasonic HC‑SR041TRIG → 26, ECHO → 5
PIR motion sensor1Signal → 23
Small servo or gripper (optional)1Signal → 14 (example)
Bluetooth (built‑in)1Internal
  • 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.
On this page