Project 6.10: "Explorer Robot"
What you’ll learn
- ✅ Mission orchestration: Build a clear state machine to run a full autonomous mission from start to finish.
- ✅ Checkpoints and timing: Detect checkpoint markers, track laps and time, and compute a simple score.
- ✅ Persistence and logging: Save mission logs to internal storage (text files) and reload to resume.
- ✅ Safety and recovery: Add an emergency stop button and robust resume rules after interruptions.
- ✅ Final integration: Combine sensing, movement, WiFi signals, and logging into a single reliable flow.
Blocks glossary (used in this project)
- State machine: Variables and if/elif branches to manage mission phases (IDLE → RUN → DELIVER → RETURN → DONE).
- Digital inputs/outputs: Button (stop/resume), LED/buzzer feedback, checkpoint IR input.
- Timers: time.ticks_ms for lap timing, cooldowns, and mission deadlines.
- File I/O: open, write, read to create and load mission logs (“mission.txt”).
- Serial println: Print “STATE:…”, “CHK:…”, “LAP:…”, “SCORE:…”, “LOG:…”, “SAFE:…”.
What you need
| Part | How many? | Pin connection / Notes |
|---|---|---|
| D1 R32 (ESP32) | 1 | USB cable (30 cm) |
| L298N + TT motors | 1 set | Left IN1→18, IN2→19; Right IN3→5, IN4→23 |
| IR checkpoint sensor | 1 | Digital input → Pin 33 (marker detection) |
| LED + buzzer (status) | 1 each | LED → Pin 13, Buzzer → Pin 23 |
| Emergency stop button | 1 | Button → Pin 26 (to GND) with internal pull‑up |
Notes
- Use internal pull‑up for the button; wire the button to short Pin 26 to GND when pressed.
- Place a simple marker (dark tape or reflective tag) on the track for “checkpoint” detection.
- Keep grounds shared across all modules.
Before you start
- Hardware wired and grounds shared
- Button on Pin 26 verified (press prints “SAFE:STOP”)
- Serial monitor open and shows:
print("Ready!") # Confirm serial is working so you can see messages
Microprojects 1–5
Microproject 6.10.1 – Mission state machine basics
Goal: Create mission states and move through them with prints only (no motors yet).
Blocks used:
- Variables: state, next transitions.
- Serial println: “STATE:IDLE/RUN/DELIVER/RETURN/DONE”.
MicroPython code:
import time # Import time for delays and timestamps
# Define mission states as simple strings
STATE_IDLE = "IDLE" # Idle state before mission starts
STATE_RUN = "RUN" # Running toward destination
STATE_DELIVER = "DELIVER" # Delivering at destination
STATE_RETURN = "RETURN" # Returning to home base
STATE_DONE = "DONE" # Mission complete
state = STATE_IDLE # Initialize current state to IDLE
print("STATE:", state) # Print initial state
def step_state(): # Define a simple transition step
global state # Use global state variable
if state == STATE_IDLE: # If currently IDLE
state = STATE_RUN # Transition to RUN
print("STATE:", state) # Print new state
elif state == STATE_RUN: # If currently RUN
state = STATE_DELIVER # Transition to DELIVER
print("STATE:", state) # Print new state
elif state == STATE_DELIVER: # If currently DELIVER
state = STATE_RETURN # Transition to RETURN
print("STATE:", state) # Print new state
elif state == STATE_RETURN: # If currently RETURN
state = STATE_DONE # Transition to DONE
print("STATE:", state) # Print new state
else: # For any other state
print("STATE:HALT") # Print halt (no transition)
# Demo: walk through the states
for i in range(5): # Loop five transitions
step_state() # Perform one transition
time.sleep(0.2) # Short delay for readability
Reflection: Naming states makes complex missions simple—you always know “where” you are.
Challenge:
- Easy: Add a “PAUSE” state in the chain.
- Harder: Add a function to jump back to “RUN” from “PAUSE”.
Microproject 6.10.2 – Checkpoint detection and lap timing
Goal: Detect a checkpoint marker and compute lap time and simple score.
Blocks used:
- Digital input: IR sensor as checkpoint.
- Timer math: ticks_ms for lap time.
MicroPython code:
import machine # Import machine for Pin input
import time # Import time for lap timing
chk = machine.Pin(33, machine.Pin.IN) # Create checkpoint input on Pin 33
print("CHK:READY PIN=33") # Confirm checkpoint pin
laps = 0 # Initialize laps counter
t_start = time.ticks_ms() # Record start time in milliseconds
def on_checkpoint(): # Define function to handle checkpoint detection
global laps # Use global laps counter
t_now = time.ticks_ms() # Read current time in ms
lap_ms = time.ticks_diff(t_now, t_start) # Compute lap duration in ms
laps += 1 # Increment lap counter
score = max(0, 10000 - lap_ms) // 100 # Compute simple score from time
print("CHK:DETECTED", laps) # Print checkpoint detection
print("LAP:MS", lap_ms, "SCORE:", score) # Print lap time and score
# Simulate polling for checkpoint
for i in range(20): # Poll for a short period
if chk.value() == 1: # If checkpoint sensor is active
on_checkpoint() # Handle checkpoint event
time.sleep(0.3) # Debounce delay
time.sleep(0.05) # Small polling delay
Reflection: Checkpoints turn space into progress—students see time and points immediately.
Challenge:
- Easy: Add “BEST_LAP” memory.
- Harder: Add “MISS” detection if no checkpoint within a deadline.
Microproject 6.10.3 – Safe stop and resume with a button
Goal: Add an emergency stop button and a resume press pattern.
Blocks used:
- Pull‑up button: Pin 26, pressed = 0.
- State flags: is_stopped.
MicroPython code:
import machine # Import machine for Pin input
import time # Import time for delays
btn = machine.Pin(26, machine.Pin.IN, machine.Pin.PULL_UP) # Create button input with pull-up
print("SAFE:BTN READY PIN=26") # Confirm button pin
is_stopped = False # Initialize stopped flag to False
def check_button(): # Define function to read button and update stop state
global is_stopped # Use global flag
if btn.value() == 0: # If button pressed (pull-up goes LOW)
is_stopped = True # Set stopped flag
print("SAFE:STOP") # Print stop event
else: # If button not pressed
if is_stopped: # If previously stopped
print("SAFE:RESUME_CANDIDATE") # Print resume candidate
# No state change unless resume logic elsewhere
return is_stopped # Return current stop flag
# Demo: poll button a few times
for i in range(20): # Loop 20 polls
check_button() # Check button state
time.sleep(0.1) # Small delay
Reflection: A single button can prevent chaos—students feel safe to experiment.
Challenge:
- Easy: Require double press to resume.
- Harder: Add “SAFE:LOCKOUT” for 2 s after any stop.
Microproject 6.10.4 – Mission logging to a file
Goal: Write events to a text file and read them back to resume.
Blocks used:
- File I/O: open, write, read.
- Events: STATE, CHK, SCORE, SAFE.
MicroPython code:
import time # Import time for timestamps
log_name = "mission.txt" # Define mission log filename
print("LOG:FILE", log_name) # Print filename
def ts(): # Create a timestamp in seconds since boot
return str(time.ticks_ms() // 1000) # Return integer seconds as string
def log_write(line): # Write a single line to the log
try: # Try writing
with open(log_name, "a") as f: # Open file in append mode
f.write(line + "\n") # Write line with newline
print("LOG:WRITE", line) # Print written line
except OSError: # If file write fails
print("LOG:ERR_WRITE") # Print error
def log_read_all(): # Read entire log file
try: # Try reading
with open(log_name, "r") as f: # Open file in read mode
data = f.read() # Read all text
print("LOG:READ_OK") # Print read success
return data # Return file contents
except OSError: # If file read fails
print("LOG:ERR_READ") # Print error
return "" # Return empty string on error
# Demo: write and read
log_write("STATE:RUN TS=" + ts()) # Write a state entry
log_write("CHK:DETECTED TS=" + ts()) # Write a checkpoint entry
log_text = log_read_all() # Read back log
print("LOG:DUMP\n" + log_text) # Print the whole log
Reflection: Logs tell the story—perfect for debugging and resuming a mission.
Challenge:
- Easy: Prepend dates if you have RTC.
- Harder: Save compact CSV lines and import into a spreadsheet.
Microproject 6.10.5 – Minimal movement integration (safe pulses)
Goal: Add motor helpers and integrate stop/resume logic before full mission.
Blocks used:
- Motor helpers: forward, left, right, stop.
- Stop flag: abort movement steps when is_stopped is True.
MicroPython code:
import machine # Import machine for motor pins
import time # Import time for pulse durations
# Motor pins for L298N driver
L_IN1 = machine.Pin(18, machine.Pin.OUT) # Left IN1
L_IN2 = machine.Pin(19, machine.Pin.OUT) # Left IN2
R_IN3 = machine.Pin(5, machine.Pin.OUT) # Right IN3
R_IN4 = machine.Pin(23, machine.Pin.OUT) # Right IN4
print("MOTORS:READY 18/19 5/23") # Confirm motor pins
is_stopped = False # Initialize stopped flag
def motors_stop(): # Stop both motors
L_IN1.value(0) # Left IN1 LOW
L_IN2.value(0) # Left IN2 LOW
R_IN3.value(0) # Right IN3 LOW
R_IN4.value(0) # Right IN4 LOW
print("MOVE:STOP") # Print stop
def forward(pulse=0.20): # Move forward if safe
if is_stopped: # If stopped flag is set
print("MOVE:ABORT_FWD") # Print aborted move
return # Exit without moving
L_IN1.value(1) # Left forward HIGH
L_IN2.value(0) # Left forward LOW
R_IN3.value(1) # Right forward HIGH
R_IN4.value(0) # Right forward LOW
print("MOVE:FWD", pulse) # Print forward pulse
time.sleep(pulse) # Run motors for pulse
motors_stop() # Stop motors
def left(pulse=0.16): # Turn left if safe
if is_stopped: # If stopped flag is set
print("MOVE:ABORT_LEFT") # Print aborted turn
return # Exit without turning
L_IN1.value(0) # Left backward LOW
L_IN2.value(1) # Left backward HIGH
R_IN3.value(1) # Right forward HIGH
R_IN4.value(0) # Right forward LOW
print("MOVE:LEFT", pulse) # Print left pulse
time.sleep(pulse) # Run motors for pulse
motors_stop() # Stop motors
def right(pulse=0.16): # Turn right if safe
if is_stopped: # If stopped flag is set
print("MOVE:ABORT_RIGHT") # Print aborted turn
return # Exit without turning
L_IN1.value(1) # Left forward HIGH
L_IN2.value(0) # Left forward LOW
R_IN3.value(0) # Right backward LOW
R_IN4.value(1) # Right backward HIGH
print("MOVE:RIGHT", pulse) # Print right pulse
time.sleep(pulse) # Run motors for pulse
motors_stop() # Stop motors
# Demo: safe movement sequence
forward(0.20) # Try forward move
left(0.16) # Try left turn
right(0.16) # Try right turn
Reflection: Movement tied to safety makes confidence grow—students see clear behavior.
Challenge:
- Easy: Add a “slow forward” pulse.
- Harder: Add a cooldown after any stop before allowing movement.
Main project – Integrated autonomous mission
Blocks steps (with glossary)
- State machine: IDLE → RUN → DELIVER → RETURN → DONE with safe stop/resume.
- Checkpoints: Detect markers and update lap time and score.
- Logging: Write key mission events and read back to resume.
- Movement: Use safe pulses for forward/turn steps per state.
- Finalization: Print clear summary and store the last mission result.
MicroPython code (mirroring blocks)
# Project 6.10 – Integrated Autonomous Mission (State machine + Checkpoints + Safety + Logging)
import machine # Import machine for pins (motors, inputs)
import time # Import time for ticks and delays
# ====== Safety button and checkpoint input ======
btn = machine.Pin(26, machine.Pin.IN, machine.Pin.PULL_UP) # Create emergency button with pull-up
chk = machine.Pin(33, machine.Pin.IN) # Create checkpoint sensor input
print("INIT:SAFE BTN=26 CHK=33") # Print input pins
# ====== Status outputs ======
led = machine.Pin(13, machine.Pin.OUT) # Create LED output for status
buzz = machine.Pin(23, machine.Pin.OUT) # Create buzzer output for status
print("INIT:STATUS LED=13 BUZ=23") # Print status pins
# ====== Motors ======
L_IN1 = machine.Pin(18, machine.Pin.OUT) # Left IN1
L_IN2 = machine.Pin(19, machine.Pin.OUT) # Left IN2
R_IN3 = machine.Pin(5, machine.Pin.OUT) # Right IN3
R_IN4 = machine.Pin(23, machine.Pin.OUT) # Right IN4
print("INIT:MOTORS 18/19 5/23") # Print motor pins
def motors_stop(): # Stop both motors
L_IN1.value(0) # Left IN1 LOW
L_IN2.value(0) # Left IN2 LOW
R_IN3.value(0) # Right IN3 LOW
R_IN4.value(0) # Right IN4 LOW
print("MOVE:STOP") # Print stop
def forward(pulse=0.20): # Safe forward pulse
if is_stopped: # If stopped
print("MOVE:ABORT_FWD") # Print abort
return # Do not move
L_IN1.value(1) # Left forward HIGH
L_IN2.value(0) # Left forward LOW
R_IN3.value(1) # Right forward HIGH
R_IN4.value(0) # Right forward LOW
print("MOVE:FWD", pulse) # Print action
time.sleep(pulse) # Run motors
motors_stop() # Stop
def left(pulse=0.16): # Safe left turn
if is_stopped: # If stopped
print("MOVE:ABORT_LEFT") # Print abort
return # Do not move
L_IN1.value(0) # Left backward LOW
L_IN2.value(1) # Left backward HIGH
R_IN3.value(1) # Right forward HIGH
R_IN4.value(0) # Right forward LOW
print("MOVE:LEFT", pulse) # Print action
time.sleep(pulse) # Run motors
motors_stop() # Stop
def right(pulse=0.16): # Safe right turn
if is_stopped: # If stopped
print("MOVE:ABORT_RIGHT") # Print abort
return # Do not move
L_IN1.value(1) # Left forward HIGH
L_IN2.value(0) # Left forward LOW
R_IN3.value(0) # Right backward LOW
R_IN4.value(1) # Right backward HIGH
print("MOVE:RIGHT", pulse) # Print action
time.sleep(pulse) # Run motors
motors_stop() # Stop
# ====== File logging ======
log_name = "mission.txt" # Log filename
print("LOG:FILE", log_name) # Print log file name
def ts(): # Timestamp helper (seconds since boot)
return str(time.ticks_ms() // 1000) # Return seconds as string
def log_write(line): # Write one line to log
try: # Try write
with open(log_name, "a") as f: # Open file append
f.write(line + "\n") # Write line with newline
print("LOG:WRITE", line) # Print confirmation
except OSError: # On error
print("LOG:ERR_WRITE") # Print error
def log_read_all(): # Read full log
try: # Try read
with open(log_name, "r") as f: # Open file read
data = f.read() # Read contents
print("LOG:READ_OK") # Print success
return data # Return text
except OSError: # On error
print("LOG:ERR_READ") # Print error
return "" # Return empty
# ====== Mission states ======
STATE_IDLE = "IDLE" # Idle state
STATE_RUN = "RUN" # Going to destination
STATE_DELIVER = "DELIVER" # Delivering at destination
STATE_RETURN = "RETURN" # Returning home
STATE_DONE = "DONE" # Mission complete
state = STATE_IDLE # Start in IDLE
is_stopped = False # Safety stop flag
laps = 0 # Lap counter (checkpoints)
best_lap_ms = None # Best lap time storage
mission_start = 0 # Mission start time
mission_deadline_ms = 60000 # Mission timeout (60 s)
print("INIT:STATE", state) # Print initial state
def check_button(): # Read button and update stop/resume
global is_stopped # Use global flag
if btn.value() == 0: # If button pressed (LOW)
is_stopped = True # Set stop
led.value(1) # LED ON
buzz.value(1) # Buzzer ON
print("SAFE:STOP") # Print stop
motors_stop() # Ensure motors are stopped
else: # If not pressed
buzz.value(0) # Buzzer OFF
# Keep LED state as a reminder when stopped
return is_stopped # Return current flag
def celebrate(): # Delivery celebration
for i in range(3): # Repeat three times
led.value(1) # LED ON
buzz.value(1) # Buzzer ON
time.sleep(0.1) # On time
led.value(0) # LED OFF
buzz.value(0) # Buzzer OFF
time.sleep(0.1) # Off time
print("DELIVER:CELEBRATE") # Print celebration
def handle_checkpoint(): # Process a checkpoint hit
global laps, best_lap_ms # Use global counters
t_now = time.ticks_ms() # Current ms
lap_ms = time.ticks_diff(t_now, mission_start) # Lap duration since mission start
laps += 1 # Increment laps
if (best_lap_ms is None) or (lap_ms < best_lap_ms): # If best lap improved
best_lap_ms = lap_ms # Update best lap
score = max(0, 100000 - lap_ms) // 1000 # Compute score from time
print("CHK:DETECTED LAPS=", laps) # Print checkpoint
print("LAP:MS", lap_ms, "BEST:", best_lap_ms, "SCORE:", score) # Print lap and score
log_write("CHK LAPS=" + str(laps) + " LAP_MS=" + str(lap_ms) + " TS=" + ts()) # Log checkpoint
def resume_if_double_press(): # Resume only after double press
global is_stopped # Use global flag
if not is_stopped: # If not stopped
return False # Nothing to do
# Wait for double press within 2 s
start = time.ticks_ms() # Start timing
presses = 0 # Press count
while time.ticks_diff(time.ticks_ms(), start) < 2000: # 2 s window
if btn.value() == 0: # If pressed
presses += 1 # Count press
print("SAFE:PRESS", presses) # Print press count
time.sleep(0.25) # Debounce
time.sleep(0.05) # Poll delay
if presses >= 2: # If two presses detected
is_stopped = False # Clear stop flag
led.value(0) # LED OFF
print("SAFE:RESUME") # Print resume
log_write("SAFE RESUME TS=" + ts()) # Log resume
return True # Resumed
return False # Did not resume
def drive_toward_checkpoint(): # Movement pattern in RUN state
forward(0.22) # Forward pulse
left(0.12) # Small left correction
forward(0.22) # Forward pulse
right(0.12) # Small right correction
print("NAV:STEP") # Print navigation step
print("RUN:MISSION") # Announce mission start
log_write("STATE IDLE TS=" + ts()) # Log initial state
mission_start = time.ticks_ms() # Record mission start ms
state = STATE_RUN # Enter RUN state
log_write("STATE RUN TS=" + ts()) # Log state change
while True: # Main mission loop
check_button() # Update safety flag from button
if is_stopped: # If stopped
resume_if_double_press() # Attempt resume on double press
time.sleep(0.05) # Small delay
continue # Skip mission actions while stopped
if state == STATE_RUN: # If running toward destination
drive_toward_checkpoint() # Perform a navigation step
if chk.value() == 1: # If checkpoint sensor active
handle_checkpoint() # Handle checkpoint
state = STATE_DELIVER # Change state to DELIVER
log_write("STATE DELIVER TS=" + ts()) # Log state change
# Check mission timeout
if time.ticks_diff(time.ticks_ms(), mission_start) > mission_deadline_ms: # If over deadline
print("SAFE:TIMEOUT") # Print timeout
log_write("SAFE TIMEOUT TS=" + ts()) # Log timeout
state = STATE_RETURN # Change to RETURN
log_write("STATE RETURN TS=" + ts()) # Log state change
elif state == STATE_DELIVER: # If delivering
celebrate() # Play celebration pattern
forward(0.18) # Small reposition forward
print("DELIVER:DONE") # Print deliver done
log_write("DELIVER DONE TS=" + ts()) # Log delivery
state = STATE_RETURN # Go to RETURN
log_write("STATE RETURN TS=" + ts()) # Log state
elif state == STATE_RETURN: # If returning to base
right(0.18) # Turn toward home direction
forward(0.25) # Move forward
forward(0.25) # Move forward again
print("RETURN:STEP") # Print return step
# Simple condition to finish: after two forward pulses
state = STATE_DONE # Enter DONE
log_write("STATE DONE TS=" + ts()) # Log completion
elif state == STATE_DONE: # If mission complete
motors_stop() # Ensure stopped
led.value(1) # LED ON to indicate success
print("MISSION:COMPLETE LAPS=", laps, "BEST_LAP_MS=", best_lap_ms) # Print summary
log_write("SUMMARY LAPS=" + str(laps) + " BEST=" + str(best_lap_ms) + " TS=" + ts()) # Log summary
break # Exit loop
time.sleep(0.05) # Small pacing delay
External explanation
- What it teaches: You designed and ran a full mission with a clear state machine, checkpoint timing and scoring, safe stop/resume, and persistent logging that makes debugging and resuming simple.
- Why it works: States keep logic tidy, timers give honest performance data, a single button provides safety, and logs preserve the mission’s story for learning and improvement.
- Key concept: “Plan → act → detect → log → finish.”
Story time
The robot sets off, checks in at the marker, celebrates, and heads home. A sudden stop—button pressed—and it waits. Two taps, and it resumes like nothing happened. Later, its log reads like a diary: time, laps, best run. Calm, competent, complete.
Debugging (2)
Debugging 6.10.1 – Mission never completes
Problem: Robot stays in RUN and never transitions.
Clues: CHK:DETECTED never prints; checkpoint logic not firing.
Broken code:
if chk.value() == 0: # Wrong polarity for sensor
handle_checkpoint()
Fixed code:
if chk.value() == 1: # Use the sensor’s active HIGH detection
handle_checkpoint()
state = STATE_DELIVER # Transition after checkpoint
Why it works: Correct polarity and explicit state change ensure progress.
Avoid next time: Test sensor polarity with manual prints before mission day.
Debugging 6.10.2 – Cannot resume after stop
Problem: SAFE:STOP prints, but movement never returns.
Clues: is_stopped stays True; resume condition not met.
Broken code:
# Resume on single press (too easy to trigger accidentally)
if btn.value() == 0:
is_stopped = False
Fixed code:
# Require double press within 2 s to resume
resumed = resume_if_double_press()
print("DEBUG:RESUMED", resumed) # Show resume attempt result
Why it works: A double‑press prevents accidental resumes and clarifies user intent.
Avoid next time: Use a clear, deliberate resume pattern and print results.
Final checklist
- State machine prints clean transitions: IDLE → RUN → DELIVER → RETURN → DONE
- Checkpoint detection updates laps, lap time, and score
- Emergency stop button halts motors instantly and logs the event
- Double‑press resume works reliably with clear prints
- Mission log file contains STATE/CHK/SUMMARY lines for review
- Final mission prints a clear summary and ends safely
Extras
- 🧠 Student tip: Sketch your mission flow as boxes and arrows; match each arrow to a print tag (STATE, CHK, SAFE, LOG).
- 🧑🏫 Instructor tip: Review mission logs in class—compare lap times and discuss improvements.
- 📖 Glossary:
- State machine: A set of named states and rules to move between them.
- Checkpoint: A physical marker the robot detects for progress tracking.
- Persistence: Saving data to storage so it survives resets.
- 💡 Mini tips:
- Debounce checkpoint and button events with short sleeps after detection.
- Keep logs small and structured for easy reading.
- Always test safety stop before every demo.