🌐 Level 6 – Wi-Fi Robotics

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

PartHow many?Pin connection / Notes
D1 R32 (ESP32)1USB cable (30 cm)
L298N + TT motors1 setLeft IN1→18, IN2→19; Right IN3→5, IN4→23
IR checkpoint sensor1Digital input → Pin 33 (marker detection)
LED + buzzer (status)1 eachLED → Pin 13, Buzzer → Pin 23
Emergency stop button1Button → 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.
On this page