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__.py file (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 get ModuleNotFoundError.

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 of raccoon.project.yml by 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 of raccoon.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:

WhatExampleNotes
Mission classesM01DriveToConeMissionPrefix matches file number
Robot classRobotAlways Robot
Hardware defsDefsAlways Defs
Custom step classesWaitForAnalogRangeDescriptive, verb-first
Custom screensCalibrationScreenSuffix with Screen
ServicesHeadingReferenceServiceSuffix with Service
Result typesCalibrationResultSuffix with Result

Functions and Variables — snake_case (Python-style)

All function names, variable names, and hardware definition attributes use lowercase with underscores:

WhatExampleNotes
Step DSL functionsdrive_forward(), turn_right()Verb-first, describes the action
Custom step functionsdown_cone_container()Same pattern as built-in steps
Hardware attributesfront_left_motorPosition + side + component type
Sensor groupsfront, rearShort — they’re used constantly
Servo presetsclaw, pom_arm, shiethe ldNamed 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:

WhatPatternExample
Mission filesm{NN}_{description}_mission.pym01_drive_to_cone_mission.py
Setup missionm00_setup_mission.pyAlways m00
Shutdown missionm99_shutdown_mission.pyAlways m99
Custom step files{description}_steps.pycone_container_steps.py
Hardware defsdefs.pyAlways defs.py (generated)
Robot classrobot.pyAlways robot.py (generated)
Entry pointmain.pyAlways main.py

Mission Numbering

Missions use a two-digit prefix (m00m99) as a naming convention for readability:

  • m00 — Setup mission (calibration, servo homing) by convention.
  • m01m98 — 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