# How openpilot Handles CAN Communication: Architecture and Implementation

> Discover how openpilot handles CAN communication. Explore its layered architecture, Panda device I/O abstraction, DBC parsers, and CarInterface implementations for seamless vehicle integration.

- Repository: [comma.ai/openpilot](https://github.com/commaai/openpilot)
- Tags: architecture
- Published: 2026-03-05

---

**openpilot handles CAN communication through a layered architecture that abstracts hardware I/O via the Panda device, decodes messages using DBC-driven parsers, and routes commands through vehicle-specific CarInterface implementations.**

The commaai/openpilot repository implements a sophisticated vehicle communication system that translates raw CAN bus traffic into structured self-driving commands. This architecture separates hardware access from high-level logic, enabling support for hundreds of vehicle models through a unified interface. Understanding how openpilot manages **CAN (Controller Area Network)** messaging reveals the design patterns that make cross-platform autonomous driving feasible.

## The Five-Layer CAN Communication Stack

openpilot's CAN handling follows a strict separation of concerns across five architectural layers, each implemented in specific modules within the repository.

### Hardware Interface Layer

The lowest layer manages physical CAN bus access through the **Panda** device—a dedicated CAN router that bridges the vehicle's OBD-II port to the host computer via USB or Ethernet. The `pandad` daemon ([`selfdrive/pandad/pandad.py`](https://github.com/commaai/openpilot/blob/main/selfdrive/pandad/pandad.py)) initializes the Panda hardware, reads raw CAN frames, and publishes them to the internal messaging system.

```python

# Simplified excerpt from pandad.py

can_raw = panda.can_recv()
msg = messaging.new_message('can')
msg.can = can_raw
pm.send('can', msg)

```

### Message Bus Layer

Raw CAN frames travel through **Cereal**, openpilot's typed Pub/Sub messaging framework. This decouples hardware producers from consumers like the control logic and UI. The `can` topic carries raw frame data, while `pandaStates` carries device health information. Services are defined in [`cereal/services.py`](https://github.com/commaai/openpilot/blob/main/cereal/services.py), ensuring synchronized inter-process communication across the entire system.

### CAN Parsing and Packing Layer

The transition between raw bytes and Python objects happens in the `opendbc` module. **CANParser** ([`opendbc/can/parser.py`](https://github.com/commaai/openpilot/blob/main/opendbc/can/parser.py)) converts incoming hex frames into typed signals using vehicle-specific **DBC (Database CAN)** files. Conversely, **CANPacker** ([`opendbc/can/packer.py`](https://github.com/commaai/openpilot/blob/main/opendbc/can/packer.py)) assembles outgoing actuator commands into byte arrays formatted for the vehicle's ECU.

### Car Interface Abstraction

Vehicle-specific logic resides in [`selfdrive/car/interface.py`](https://github.com/commaai/openpilot/blob/main/selfdrive/car/interface.py) and its per-make implementations (e.g., [`honda_interface.py`](https://github.com/commaai/openpilot/blob/main/honda_interface.py)). Each `CarInterface` instantiates a `CANParser` for inbound signals and a `CANPacker` for outbound commands, providing a uniform API regardless of manufacturer. This abstraction allows the main control loop to call generic `update()` and `apply()` methods without knowing underlying CAN IDs or scaling factors.

### Control Loop Integration

The main `controlsd` process ([`selfdrive/control/controlsd.py`](https://github.com/commaai/openpilot/blob/main/selfdrive/control/controlsd.py)) closes the feedback loop. It subscribes to parsed `CarState` objects, runs longitudinal and lateral planners, and transmits steering torque and braking commands back through the `CANPacker` to the Panda hardware.

## Step-by-Step CAN Message Flow

Understanding the end-to-end pipeline requires tracing a single frame from the vehicle bus to the actuation command and back:

1. **Hardware Acquisition**: `pandad` polls the Panda device for new frames via `panda.can_recv()` and publishes them on the `can` topic.

2. **Subscription**: The car interface's `SubMaster` receives raw frames through `sm['can']`.

3. **Parsing Initialization**: During setup, `CarInterface.__init__` creates a `CANParser` loaded with the vehicle's DBC definition and required message checks:

   ```python
   self.parser = CANParser(dbc_name, checks, bus=0)
   ```

4. **Signal Extraction**: Each control iteration feeds raw messages to the parser, which updates a typed `CarState` object:

   ```python
   can_msgs = sm['can']
   self.parser.update(can_msgs)
   car_state = CarState.from_parser(self.parser)
   ```

5. **Decision Computation**: The planner consumes `CarState` to calculate desired steering angles and acceleration profiles.

6. **Command Packing**: The `CANPacker` constructs specific messages like `LKAS_COMMAND` or `ACC_COMMAND`:

   ```python
   packer = CANPacker(dbc_name)
   commands = packer.make_can_msg('STEERING_TORQUE', 0, [torque_l, torque_r])
   ```

7. **Transmission**: Packed frames return through the messaging bus to `pandad`, which writes them to the vehicle CAN bus via `panda.can_send()`.

## Practical Code Examples

### Instantiating Parser and Packer for a Specific Vehicle

When implementing support for a new model, developers instantiate the parsing stack with the appropriate DBC file:

```python
from opendbc.can.parser import CANParser
from opendbc.can.packer import CANPacker

DBC = "honda_civic_2022"
checks = [
    ("STEERING_TORQUE", 100),
    ("VEHICLE_SPEED", 50),
]

parser = CANParser(DBC, checks, bus=0)  # Inbound parser

packer = CANPacker(DBC)                 # Outbound packer

```

This pattern appears in [`tools/sim/lib/simulated_car.py`](https://github.com/commaai/openpilot/blob/main/tools/sim/lib/simulated_car.py), demonstrating how the same abstraction works in simulation environments.

### Subscribing to CAN Messages in Control Processes

Any process requiring vehicle state creates a `SubMaster` listening to the `can` topic:

```python
import messaging

sm = messaging.SubMaster(['can'], poll='can')
while True:
    sm.update()  # Blocks until new frames arrive

    can_raw = sm['can']
    parser.update(can_raw)
    speed = parser.vl["VEHICLE_SPEED"]  # Access decoded signal

```

This implementation mirrors the pattern found in [`selfdrive/control/controlsd.py`](https://github.com/commaai/openpilot/blob/main/selfdrive/control/controlsd.py), where the main control thread waits for fresh CAN data before executing planning logic.

### Sending Actuator Commands

To command steering torque, the controller packs values according to the DBC specification and publishes via the Panda driver:

```python
torque = int(0.2 * 0x1000)
msg = packer.make_can_msg("STEERING_TORQUE", bus=0,
                          values=[torque & 0xFF, (torque >> 8) & 0xFF])

pm = messaging.PubMaster(['can'])
pm.send('can', messaging.new_message('can', 0, [msg]))

```

This code structure, derived from [`selfdrive/car/honda/car_controller.py`](https://github.com/commaai/openpilot/blob/main/selfdrive/car/honda/car_controller.py), ensures that high-level torque requests translate to correctly formatted CAN frames on the appropriate bus.

## Summary

- **Hardware abstraction**: The Panda device and `pandad` daemon handle all physical CAN I/O, isolating the main system from USB and timing details.
- **DBC-driven parsing**: `CANParser` and `CANPacker` in `opendbc/` use Database CAN files to translate between raw bytes and semantic signals without vehicle-specific code changes.
- **Layered architecture**: Five distinct layers—hardware, messaging, parsing, interface, and control—ensure that adding new vehicles requires only DBC files and interface subclasses rather than core logic modifications.
- **Pub/Sub decoupling**: The Cereal messaging system allows asynchronous communication between `pandad`, `controlsd`, and car-specific interfaces through the `can` topic.

## Frequently Asked Questions

### How does openpilot support multiple vehicle manufacturers with one codebase?

openpilot uses **DBC (Database CAN)** files to describe each vehicle's unique CAN message layout. The generic `CANParser` and `CANPacker` classes read these definitions at runtime, while vehicle-specific `CarInterface` subclasses handle semantic mapping. This means the core control logic in `controlsd` remains manufacturer-agnostic, calling uniform methods like `update()` and `apply()` regardless of whether the vehicle is a Honda, Toyota, or Volkswagen.

### What is the role of the Panda device in CAN communication?

The **Panda** acts as a safety-focused CAN router and translator. It connects to the vehicle's OBD-II port and exposes a USB/Ethernet interface to the host computer. The `pandad` process manages this connection, reading raw frames via `panda.can_recv()` and writing commands via `panda.can_send()`. This hardware isolation ensures that the main openpilot processes never directly access CAN buses, providing a security and safety boundary.

### How are CAN message frequencies and timeouts enforced?

When initializing a `CANParser`, developers provide a `checks` list containing tuples of `(message_name, frequency_hz)`. The parser tracks arrival timestamps for each defined message and sets a `valid` flag to `False` if expected messages arrive late or stop appearing. This mechanism, visible in [`selfdrive/car/interface.py`](https://github.com/commaai/openpilot/blob/main/selfdrive/car/interface.py), allows safety-critical code to detect communication failures and enter a fallback state immediately.

### Where does the conversion from high-level steering commands to CAN bytes happen?

The translation occurs in two stages. First, the `CarController` class (e.g., [`selfdrive/car/honda/car_controller.py`](https://github.com/commaai/openpilot/blob/main/selfdrive/car/honda/car_controller.py)) calculates desired actuator values like steering torque. Then, it calls `CANPacker.make_can_msg()` with the signal name and physical values. The packer lookups the DBC definition to apply scaling factors and byte offsets, returning a raw CAN frame that `pandad` transmits to the vehicle.