Skip to main content
The Memory class is the communication backbone of Donkeycar. It provides a shared key-value store that enables parts to exchange data without directly coupling to each other.

What is Memory?

Memory is a simple dictionary-based storage system where:
  • Parts write outputs to named channels (keys)
  • Other parts read from those same channels (inputs)
  • The Vehicle orchestrates reading and writing
  • All data persists for one loop iteration

The Memory Class

Defined in donkeycar/memory.py:9-61:
Memory is essentially a Python dictionary with helper methods for batch operations. The Vehicle class uses put() and get() to manage data flow.

How Data Flows

Writing to Memory

Parts return values that the Vehicle stores in Memory:
The Vehicle calls Memory.put() (memory.py:35-45):

Reading from Memory

The Vehicle retrieves inputs before calling a part:
The Vehicle calls Memory.get() (memory.py:49-51):

Vehicle Orchestration

From vehicle.py:186-215, the Vehicle manages the flow:

Channel Naming

Channels use a hierarchical naming convention:

Sensor Channels

User Input Channels

Autopilot Channels

Control Channels

State Channels

Recording Channels

Use the pattern category/name for channel names. This makes it easy to understand where data comes from and prevents naming conflicts.

Data Flow Example

A complete data flow through Memory:

Memory State After One Iteration

Memory values persist across iterations. If a part doesn’t run (e.g., due to run_condition), its outputs from previous iterations remain in Memory.

Multiple Outputs

Parts can return multiple values:
From memory.py:35-42:

No Outputs

Parts that perform actions but don’t return data:
From vehicle.py:212-213:
If outputs is None, nothing is stored.

Accessing Memory Directly

While parts normally don’t access Memory directly, sometimes it’s useful:

Use Cases

  1. Debugging: Inspect Memory state
  2. Initialization: Set initial values
  3. Special parts: Parts that modify Memory directly
Example from complete.py:143:
This part takes web/buttons (a dict) and expands it into separate Memory keys.

Memory Lifecycle

1. Initialization

2. First Iteration

All inputs are None because Memory is empty:

3. Subsequent Iterations

Memory contains values from previous parts:

4. Run Conditions

If a part doesn’t run, its outputs aren’t updated:
Always check for None values in your parts, especially on the first loop iteration or when using run conditions.

Common Patterns

Pattern 1: Pipeline

Data flows sequentially through parts:

Pattern 2: Fan-Out

One part’s output feeds multiple parts:

Pattern 3: Merge

Multiple inputs combined:

Pattern 4: Conditional Branch

Memory and Threading

Threaded parts access Memory through the main loop:
Flow:
  1. Background thread updates self.frame continuously
  2. Main loop calls run_threaded() to get latest frame
  3. Vehicle stores return value in Memory
  4. Other parts read from Memory
Threaded parts should store data in instance variables, not in Memory directly. The Vehicle handles transferring data to Memory.

Debugging Memory

To see what’s in Memory:
Or create a debug part:

Best Practices

  1. Use descriptive channel names: cam/image_array not img
  2. Follow naming conventions: category/name pattern
  3. Handle None values: Check inputs on first iteration
  4. Don’t access Memory directly in parts: Use inputs/outputs
  5. Keep Memory clean: Don’t store unnecessary data
  6. Document channels: Comment what each channel contains
  7. Avoid name collisions: Use unique, specific names

Next Steps