Missions
A mission is a self-contained task your robot performs — “drive to the cone and pick it up”, “follow the line to the basket”, “go home”. Missions are composed of steps arranged in sequences.
Writing a Mission
Every mission extends the Mission base class and implements a sequence() method that returns a Sequential of steps:
from libstp import *
from src.hardware.defs import Defs
class M01DriveToConeMission(Mission):
def sequence(self) -> Sequential:
return seq([
drive_forward(25),
turn_right(90),
drive_forward(15),
Defs.claw.open(),
])
That’s it. The framework handles execution, timing, error handling, and hardware cleanup.
Real-World Example: ConeBot
Here’s the actual mission from the Ecer2026 ConeBot that drives to a cone on the game table:
from libstp import *
from src.hardware.defs import Defs
class M01DriveToConeMission(Mission):
def sequence(self) -> Sequential:
return seq([
# Start position: against the wall
Defs.cone_arm_servo.up(),
drive_backward(3),
turn_right(55),
# Align against the back wall
wall_align_backward(accel_threshold=0.3),
drive_forward(2),
# Sweep to check for cone
Defs.cone_arm_servo.down(),
turn_left(25),
turn_right(25),
Defs.cone_arm_servo.up(),
# Drive to the line and follow it
Defs.front.drive_until_black(),
forward_single_lineup(
Defs.front.right,
entry_threshold=0.9,
exit_threshold=0.7,
correction_side=CorrectionSide.RIGHT,
forward_speed=1.0,
),
# Drive along the line to the cone
drive_forward(27),
turn_right(90),
Defs.front.drive_until_black(),
])
Notice how the mission reads almost like natural language: “raise arm, back up, turn, align against wall, find the line, follow it, drive to the cone.”
Composing Steps
Sequential Execution
seq([...]) runs steps one after another. Each step must finish before the next one starts:
seq([
drive_forward(25), # Step 1: drive 25 cm
turn_right(90), # Step 2: turn 90 degrees (after step 1 finishes)
drive_forward(25), # Step 3: drive 25 cm (after step 2 finishes)
])
Parallel Execution
parallel(...) runs multiple things at the same time. Each argument is an independent track:
parallel(
drive_forward(50), # Track 1: drive forward
seq([ # Track 2: arm movement
wait_until_distance(20), # wait until 20cm traveled
Defs.arm.down(), # then lower the arm
]),
)
Parallel finishes when all tracks complete. You can pass individual steps, lists of steps (implicitly sequential), or explicit seq([...]) blocks.
Resource safety: Parallel validates that no two tracks use the same hardware resource. You can’t drive forward and turn at the same time, because both need the drive system. The framework will raise an error before execution if it detects a conflict.
Real Parallel Example
From the PackingBot — driving forward while operating a grabber:
parallel(
# Track 1: Follow the line edge
Defs.front.follow_right_edge(999).until(
after_cm(125) & on_black(Defs.front.right)
),
# Track 2: Grab POMs during the drive
seq([
wait_until_distance(35), # Wait until we've traveled 35cm
Defs.pom_grab.closed(), # Close grabber
Defs.pom_arm.up(), # Lift arm
]),
# Track 3: Put arm back down after passing edge
seq([
wait_until_distance(45),
Defs.pom_arm.down(),
]),
# Track 4: Pre-adjust grabber width
Defs.pom_grab.slightly_open(),
)
Four things happening simultaneously: line following, grabbing objects at the right moment, repositioning the arm, and adjusting the grabber width.
Stop Conditions
Many steps accept a .until(condition) clause that controls when the step finishes:
drive_forward(speed=0.8).until(on_black(Defs.front.right))
drive_forward(speed=1.0).until(on_black(Defs.front.right) | after_cm(50))
drive_forward(speed=1.0).until(after_cm(10) + on_black(Defs.front.right))
Conditions can be combined with | (OR), & (AND), + (THEN), and grouped with parentheses for complex logic. See Stop Conditions for the full reference.
Control Flow
Looping
# Repeat a step 5 times
loop_for(drive_forward(10), iterations=5)
# Repeat forever (use inside do_while_active to stop it)
loop_forever(seq([
drive_forward(10),
turn_right(90),
]))
Do While Active
Run a task that stops when a reference step finishes:
do_while_active(
reference_step=drive_forward(100), # This controls the lifetime
task=loop_forever( # This runs alongside and gets cancelled
seq([
Defs.arm.up(),
wait_for_seconds(0.5),
Defs.arm.down(),
wait_for_seconds(0.5),
])
),
)
Inline Code with run()
Execute arbitrary Python code as a step:
seq([
drive_forward(25),
run(lambda robot: print("Reached the cone!")),
Defs.claw.open(),
])
run() accepts sync or async callables:
async def check_sensor(robot):
value = robot.defs.front_right_ir.read()
if value > 2000:
print("On black line")
seq([
run(check_sensor),
drive_forward(10),
])
Deferred Steps
Build a step at runtime based on robot state:
def choose_direction(robot):
if robot.defs.front_right_ir.read() > 2000:
return turn_left(90)
else:
return turn_right(90)
seq([
drive_forward(25),
defer(choose_direction), # Decides at runtime
drive_forward(25),
])
Setup Mission Pattern
The setup mission runs before the match starts. Common pattern:
class M00SetupMission(Mission):
def sequence(self) -> Sequential:
return seq([
# Home all servos to known positions
motor_off(Defs.cone_container_motor),
Defs.claw.closed(),
Defs.arm.up(),
# Run distance calibration
calibrate(distance_cm=50),
])
Mission Registration
Missions are registered in the Robot class. This is salso automatically generated for you. Don’t edit this! To change it, edit the raccoon.project.yaml file:
class Robot(GenericRobot):
# ...
setup_mission = M00SetupMission() # Runs before start signal
missions = [ # Run in order after start
M01DriveToConeMission(),
M02CollectConeMission(),
M03CollectBotguyMission(),
]
shutdown_mission = M99ShutdownMission() # Runs when timer expires
Missions execute in list order. If mission 1 finishes, mission 2 starts immediately. If the shutdown_in timer fires during any mission, that mission is cancelled and the shutdown mission runs.