Skip to main content
The Vehicle class is the execution engine of Donkeycar. It runs a continuous loop that orchestrates all parts, manages data flow through Memory, and maintains consistent timing.

The Vehicle Class

Defined in donkeycar/vehicle.py:61, the Vehicle class is simple but powerful:

Adding Parts

Parts are registered with the add() method:
Example from the source code (complete.py:105):
Parts are executed in the order they are added. This execution order is important because later parts can use outputs from earlier parts in the same iteration.

The Drive Loop

The main loop is started with the start() method:

Loop Initialization

From vehicle.py:137-147:

Main Loop Execution

The core loop structure (vehicle.py:151-172):
The loop automatically sleeps between iterations to maintain the target rate_hz. If processing takes too long, you’ll see “jitter violation” warnings when running in verbose mode.

Update Parts Cycle

The update_parts() method executes all registered parts (vehicle.py:186-215):

Execution Steps

For each part, the vehicle:
  1. Check run condition - If a run_condition is specified, check if it’s True in Memory
  2. Get inputs - Retrieve all input values from Memory using mem.get()
  3. Execute part - Call either run() or run_threaded() with the inputs
  4. Store outputs - Save return values to Memory using mem.put()
  5. Profile - Track execution time for performance analysis

Rate Control

The rate_hz parameter controls loop frequency:

How It Works

  • Calculates how long the current iteration took
  • Sleeps for the remaining time to hit target rate
  • If processing takes longer than 1/rate_hz, no sleep (jitter violation)
A typical Donkeycar runs at 20 Hz, giving each iteration 50ms. If your parts take longer than this, consider:
  • Making slow parts threaded
  • Reducing camera resolution
  • Optimizing model inference
  • Lowering the rate_hz

Threading Model

Synchronous Parts

Default behavior - part runs in main loop:
The Vehicle calls:

Threaded Parts

For I/O-bound operations:

Thread Creation

From vehicle.py:102-105:

Thread Execution

The part must implement update() which runs continuously:
  • update() runs in background thread, continuously capturing frames
  • run_threaded() is called by main loop to get latest result
Example from camera.py:71-74:
Threaded parts should store their latest result in an instance variable (like self.frame). The main loop just reads this cached value, so slow I/O doesn’t block the drive loop.

Run Conditions

Parts can execute conditionally based on Memory values:
The part only executes when Memory['run_pilot'] == True.

Example from complete.py

This pattern enables:
  • User vs autopilot mode switching
  • Conditional recording
  • Button-triggered actions
  • Feature toggles

Performance Profiling

The PartProfiler tracks execution time for each part:

Profiler Report

When the vehicle stops, it prints a summary:
From vehicle.py:39-58:
Run with verbose=True to see profiling data every 200 iterations during execution, helping you identify bottlenecks in real-time.

Shutdown Sequence

When the vehicle stops (vehicle.py:217-228):
  • Calls shutdown() on all parts (if they have it)
  • Prints final profiling report
  • Gracefully handles missing shutdown methods

Example: Complete Drive Loop

Here’s a simplified example showing the entire lifecycle:

What Happens:

  1. Initialization: Vehicle creates Memory, registers 8 parts
  2. Startup: Starts camera and controller threads
  3. Loop (every 50ms at 20Hz):
    • Camera updates in background, run_threaded() returns latest frame → cam/image_array
    • Controller polls for input → user/angle, user/throttle, user/mode
    • PilotCondition checks mode → run_pilot
    • Model runs (if run_pilot=True) → pilot/angle, pilot/throttle
    • DriveMode selects user or pilot outputs → angle, throttle
    • Steering and throttle actuators execute
  4. Sleep: Calculate remaining time to hit 50ms, sleep
  5. Repeat until interrupted
  6. Shutdown: Call shutdown on all parts, print profiling report

Timing Considerations

Target Rate

Common rate_hz values:
  • 20 Hz (50ms): Standard for most cars
  • 10 Hz (100ms): For slower hardware or heavy processing
  • 30 Hz (33ms): For faster response times

Jitter Violations

If processing takes longer than 1/rate_hz:
This means the loop overran by 12ms. Causes:
  • Blocking parts taking too long
  • Camera capture delays
  • Model inference too slow
  • Too many synchronous parts

Solutions

  1. Make parts threaded: Move slow I/O to background
  2. Optimize processing: Reduce image resolution, use faster models
  3. Lower rate_hz: If 20Hz is too fast, try 15Hz or 10Hz
  4. Profile: Use verbose mode to identify bottlenecks

Best Practices

  1. Thread I/O operations: Cameras, network, sensors should be threaded
  2. Keep processing light: Main loop should be fast (< 50ms total)
  3. Order parts correctly: Later parts can use earlier outputs in same iteration
  4. Use run conditions: Avoid unnecessary work with conditional execution
  5. Profile regularly: Check execution times to catch performance regressions
  6. Handle shutdown: Implement cleanup in shutdown() method

Next Steps