Project Structure
Every robot project follows the same file layout. This page explains what each file does and how the pieces fit together.
See It First
Before reading the rest of this page, it helps to have a real project open in front of you.
raccoon-example is a clean reference robot built specifically for documentation purposes — no competition pressure, no half-finished experiments. It demonstrates every concept on this page in a single, readable project:
git clone https://github.com/htl-stp-ecer/raccoon-example.git
Open it alongside this page. The file layout, naming conventions, and patterns described below all map directly to files in that repository.
Creating a Project
Use the Raccoon CLI to scaffold a new project:
raccoon create project MyRobot
This generates the full directory layout, YAML configuration, and starter Python files. See Raccoon CLI for details on the CLI commands.
Note: You can also create the project files manually, but the CLI scaffold saves significant setup time and ensures the correct structure.
Directory Layout
A typical project generated by the Raccoon CLI looks like this:
my-robot/
├── raccoon.project.yml # Project configuration (hardware, drive, missions)
├── racoon.calibration.yml # Sensor and motor calibration data (note: single 'c')
├── config/ # Split config files (included by project.yml)
│ ├── robot.yml
│ ├── hardware.yml
│ ├── missions.yml
│ └── connection.yml
└── src/
├── __init__.py # Required for Python imports
├── main.py # Entry point — creates Robot, calls start()
├── hardware/
│ ├── __init__.py # Required for Python imports
│ ├── defs.py # Hardware definitions (motors, servos, sensors)
│ └── robot.py # Robot class (kinematics, drive, odometry, missions)
├── missions/
│ ├── __init__.py # Required for Python imports
│ ├── m00_setup_mission.py # Runs before the match (calibration, homing)
│ ├── m01_first_task.py # First autonomous mission
│ ├── m02_second_task.py # Second autonomous mission
│ └── m99_shutdown.py # Cleanup after timeout
├── service/
│ ├── __init__.py # Required for Python imports
│ └── my_service.py # Stateful logic shared across missions
└── steps/
├── __init__.py # Required for Python imports
└── my_custom_steps.py # Reusable custom step functions
Important: Every directory under
src/needs an__init__.pyfile (can be empty) for Python imports to work. The Raccoon CLI creates these automatically, but if you add directories manually, don’t forget them or you’ll getModuleNotFoundError.
Key Files Explained
raccoon.project.yml — Project Configuration
This is the central configuration file. It defines your robot’s hardware, drive parameters, physical dimensions, and mission list. The Raccoon CLI and BotUI both read this file.
name: ConeBot
uuid: a1b2c3d4-e5f6-7890-abcd-ef1234567890
robot: !include 'config/robot.yml'
missions: !include 'config/missions.yml'
definitions: !include 'config/hardware.yml'
connection: !include 'config/connection.yml'
The !include tags split configuration across multiple files to keep things manageable. You can also put everything in a single file — here’s what the expanded version looks like (from a real project):
name: PackingBot
uuid: 322a6cb2-b54d-4ad2-bb55-14d157403ae7
robot:
shutdown_in: 120 # Emergency stop after 120 seconds
drive:
kinematics:
type: mecanum # or "differential"
wheel_radius: 0.0375
track_width: 0.2
wheelbase: 0.125
front_left_motor: front_left_motor
front_right_motor: front_right_motor
back_left_motor: rear_left_motor
back_right_motor: rear_right_motor
# ... PID and velocity config ...
physical:
width_cm: 23.5
length_cm: 29.6
rotation_center:
x_cm: 11.75
y_cm: 18.5
definitions:
front_left_motor:
type: Motor
port: 1
inverted: false
imu:
type: IMU
front_right_light_sensor:
type: IRSensor
port: 4
# ... more hardware ...
missions:
- M01SetupMission: setup
- M99ShutdownMission: shutdown
connection:
pi_address: 192.168.100.237
pi_port: 8421
pi_user: pi
racoon.calibration.yml — Calibration Data
Stores calibration values measured on the actual robot. This file is updated automatically when you run calibration steps. Don’t edit it by hand — use the calibration workflow instead (see Calibration).
root:
ir-calibration:
default:
white_tresh: 1469.84
black_tresh: 2490.58
default_port4:
white_tresh: 543.45
black_tresh: 3647.12
src/main.py — Entry Point
The simplest file in the project. Creates the robot and starts execution:
from src.hardware.robot import Robot
robot = Robot()
if __name__ == "__main__":
robot.start()
robot.start() handles everything: initializing hardware, running the setup mission, waiting for the start signal, executing main missions in sequence, and running the shutdown mission when time expires.
src/hardware/defs.py — Hardware Definitions (Generated)
This file is auto-generated from the
definitions:section ofraccoon.project.ymlby the Raccoon CLI. Never edit this file by hand — it gets overwritten every time code generation runs. Always make changes in the YAML file instead.
Here’s what the generated code looks like:
from libstp import (
DigitalSensor, IRSensor, Motor, MotorCalibration,
SensorGroup, Servo, ServoPreset,
)
from libstp import IMU as Imu
class Defs:
# IMU and start button
imu = Imu()
button = DigitalSensor(port=10)
# Drive motors
front_left_motor = Motor(
port=0, inverted=False,
calibration=MotorCalibration(
ticks_to_rad=1.947e-05, vel_lpf_alpha=1.0
),
)
front_right_motor = Motor(
port=1, inverted=False,
calibration=MotorCalibration(
ticks_to_rad=1.689e-05, vel_lpf_alpha=1.0
),
)
# Sensors
front_right_ir = IRSensor(port=0)
front = SensorGroup(right=front_right_ir)
# Servos with named positions
claw = ServoPreset(Servo(port=2), positions={"closed": 135, "open": 30})
arm = ServoPreset(Servo(port=1), positions={"up": 32, "down": 160})
# List of analog sensors for calibration
analog_sensors = [front_right_ir]
See Robot Definition for details on each component type.
src/hardware/robot.py — Robot Class (Generated)
This file is also auto-generated from the
robot:section ofraccoon.project.yml. The kinematics type, wheel dimensions, PID gains, axis constraints, physical dimensions, and sensor positions all come from the YAML. Never edit this file by hand — it gets overwritten every time code generation runs.
Here’s what the generated code looks like:
from libstp import (
DifferentialKinematics, Drive, FusedOdometry,
GenericRobot, PidGains, Feedforward,
# ... more imports ...
)
from src.hardware.defs import Defs
from src.missions.m01_first_mission import M01FirstMission
class Robot(GenericRobot):
defs = Defs()
kinematics = DifferentialKinematics(
left_motor=defs.front_left_motor,
right_motor=defs.front_right_motor,
wheel_radius=0.0345,
wheelbase=0.16,
)
drive = Drive(kinematics=kinematics, vel_config=..., imu=defs.imu)
odometry = FusedOdometry(imu=defs.imu, kinematics=kinematics, ...)
shutdown_in = 120 # seconds
missions = [M01FirstMission()]
setup_mission = M00SetupMission()
shutdown_mission = None
See Robot Definition for the full breakdown.
How It All Connects
graph LR
YAML["raccoon.project.yml"] -->|read by| CLI["Raccoon CLI"]
YAML -->|read by| UI["BotUI"]
CLI -->|generates| DEFS["defs.py"]
CLI -->|generates| ROBOT["robot.py"]
DEFS -->|imported by| ROBOT
ROBOT -->|imported by| MAIN["main.py"]
ROBOT -->|references| MISSIONS["missions"]
MISSIONS -->|may use| STEPS["steps"]
MAIN -->|"robot.start()"| RUN["Runtime"]
style YAML fill:#FFA726,color:#fff
style MAIN fill:#4CAF50,color:#fff
style MISSIONS fill:#66BB6A,color:#fff
style STEPS fill:#66BB6A,color:#fff
The YAML configuration is the single source of truth for your robot’s hardware and drive setup. The Raccoon CLI generates defs.py and robot.py from it automatically — these files are overwritten on every code generation run. Never edit defs.py or robot.py by hand. If you need to change motors, servos, kinematics, or PID config, edit raccoon.project.yml and let the CLI regenerate the Python files.
Mission files (missions/*.py) and custom step files (steps/*.py) are yours to write and edit freely — code generation does not touch them.
Naming Conventions
Classes — PascalCase (Java-style)
All class names use PascalCase with no underscores:
| What | Example | Notes |
|---|---|---|
| Mission classes | M01DriveToConeMission | Prefix matches file number |
| Robot class | Robot | Always Robot |
| Hardware defs | Defs | Always Defs |
| Custom step classes | WaitForAnalogRange | Descriptive, verb-first |
| Custom screens | CalibrationScreen | Suffix with Screen |
| Services | HeadingReferenceService | Suffix with Service |
| Result types | CalibrationResult | Suffix with Result |
Functions and Variables — snake_case (Python-style)
All function names, variable names, and hardware definition attributes use lowercase with underscores:
| What | Example | Notes |
|---|---|---|
| Step DSL functions | drive_forward(), turn_right() | Verb-first, describes the action |
| Custom step functions | down_cone_container() | Same pattern as built-in steps |
| Hardware attributes | front_left_motor | Position + side + component type |
| Sensor groups | front, rear | Short — they’re used constantly |
| Servo presets | claw, pom_arm, shiethe ld | Named by the mechanism, not the port |
| Servo positions | "open", "closed", "above_pom" | Descriptive of the physical state |
| Calibration sets | "default", "upper" | Named by the surface/context |
File Names — snake_case
All Python files use lowercase with underscores. No hyphens, no spaces:
| What | Pattern | Example |
|---|---|---|
| Mission files | m{NN}_{description}_mission.py | m01_drive_to_cone_mission.py |
| Setup mission | m00_setup_mission.py | Always m00 |
| Shutdown mission | m99_shutdown_mission.py | Always m99 |
| Custom step files | {description}_steps.py | cone_container_steps.py |
| Hardware defs | defs.py | Always defs.py (generated) |
| Robot class | robot.py | Always robot.py (generated) |
| Entry point | main.py | Always main.py |
Mission Numbering
Missions use a two-digit prefix (m00–m99) as a naming convention for readability:
m00— Setup mission (calibration, servo homing) by convention.m01–m98— Main missions.m99— Shutdown mission by convention.
The numbering is purely for human readability and file sorting — it does not control execution order. Execution order is determined by the missions: list in raccoon.project.yml:
missions:
- M01SetupMission: setup # Runs as setup mission
- M02GrabPomsMission # First main mission
- M03DriveToBasketMission # Second main mission
- M99ShutdownMission: shutdown # Runs as shutdown mission
The YAML list order defines the sequence. You could name a mission m42_foo.py and put it first in the list — it would run first regardless of the number.
Hardware Naming Pattern
Hardware attributes in Defs follow a consistent {position}_{side}_{type} pattern:
front_left_motor # position: front, side: left, type: motor
front_right_ir_sensor # position: front, side: right, type: ir_sensor
rear_right_light_sensor # position: rear, side: right, type: light_sensor
cone_arm_servo # mechanism: cone_arm, type: servo
cone_arm_down_button # mechanism: cone_arm, state: down, type: button
wait_for_light_sensor # purpose: wait_for_light, type: sensor
Be descriptive — when you’re debugging at 2am before competition, front_left_motor is much clearer than motor0.
YAML Keys
YAML configuration keys use snake_case to match the Python attributes they generate:
definitions:
front_left_motor: # becomes Defs.front_left_motor
type: Motor
port: 1
front_right_light_sensor: # becomes Defs.front_right_light_sensor
type: IRSensor
port: 4