Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/commaai/openpilot/llms.txt

Use this file to discover all available pages before exploring further.

cereal is the inter-process communication backbone of openpilot. Every piece of data that crosses a process boundary — camera metadata, CAN frames, model predictions, control commands, driver monitoring state — is serialised as a cereal Event message and exchanged over named sockets. There is no shared global state between processes; all coordination is explicit and typed.

What is cereal?

cereal is a thin layer on top of two open standards:
  • Cap’n Proto — a schema language and zero-copy binary serialisation format. All message types are defined as Cap’n Proto structs in the cereal/ directory of the openpilot repository.
  • msgq — a shared-memory pub/sub transport developed by comma. Unlike ZeroMQ, msgq uses POSIX shared memory segments so subscribers can read the latest message without a system call in the common case.
The top-level message type is Event (defined in cereal/log.capnp). Every Event has a logMonoTime (nanoseconds since boot) and a valid flag. The payload is a union field — exactly one of the many named message types is set per event. Service names (e.g. modelV2, carState) map directly to fields in the Event union. The full list of services, their expected publishing frequency, and their queue sizes are defined in cereal/services.py.

Transport: msgq shared memory

msgq creates a named shared-memory segment for each service. The publisher writes serialised Cap’n Proto bytes into the segment using a ring buffer. Subscribers map the same segment and read directly from it. A Poller allows a subscriber to sleep until one of several sockets has a new message, avoiding busy-waiting. ZeroMQ bridging is also available (cereal/messaging/bridge_zmq.cc and msgq_to_zmq.cc) for tools that need to consume or inject messages over a network, but all on-device communication uses the native msgq backend. Queue sizes are set per service in cereal/services.py:
ConstantSizeUsed for
QueueSize.BIG10 MBVideo frames, raw model outputs, can bus
QueueSize.MEDIUM2 MBHigh-frequency streams: controlsState, sendcan
QueueSize.SMALL250 KBMost services

Schema files

FileContains
cereal/log.capnpThe main Event union and all driving/system message structs
cereal/car.capnpCar-specific types: CarState, CarControl, CarParams
cereal/custom.capnpReserved slots for fork-specific extensions without breaking compatibility
cereal/deprecated.capnpStructs retained only for log backwards-compatibility
All fields use SI units unless the field name says otherwise (e.g. steeringAngleDeg is explicitly in degrees).

Key message types

The table below describes the most commonly referenced services. Frequencies are nominal; see cereal/services.py for the authoritative list.
ServiceFrequencyDescription
carState100 HzVehicle state: speed, steering angle, gear, button events, CAN validity
carControl100 HzActuator commands: torque, steering angle, acceleration, HUD control
carParams0.02 Hz (once)Static car fingerprint: safety model, longitudinal/lateral capability flags
carOutput100 HzWhat the car interface actually sent to the car (mirrors applied actuators)
modelV220 HzDriving model output: predicted path, lane lines, lead vehicle, desired curvature/accel, meta
drivingModelData20 HzAlternative model data encoding (same neural network run as modelV2)
cameraOdometry20 HzVisual odometry pose estimate from the driving model
driverStateV220 HzRaw driver monitoring model output: face pose, eye/blink probabilities, phone prob
driverMonitoringState20 HzProcessed driver monitoring: alert level, lockout status, RHD detection
selfdriveState100 HzEngagement state (enabled, active, state enum), current alert text and sound
onroadEvents1 Hz / on changeList of active OnroadEvent entries with their type flags
controlsState100 HzControl loop internals: curvature, PID terms, long control state, lateral controller log
longitudinalPlan20 HzLongitudinal trajectory: target acceleration, shouldStop, hasLead, fcw
driverAssistance20 HzLane departure warning flags: leftLaneDeparture, rightLaneDeparture
liveCalibration4 HzDevice pitch/yaw calibration status and rpyCalib angles
liveParameters20 HzLearned vehicle parameters: steer ratio, tire stiffness, angle offset
livePose20 HzFused pose estimate from locationd
liveDelay4 HzEstimated lateral and longitudinal actuator delays
pandaStates10 HzPanda health: ignition state, safety model, fault flags, controlsAllowed
peripheralState2 HzPeripheral info: fan speed RPM, panda type
deviceState2 HzSystem health: thermal status, free space, memory usage, fan speed desired
managerState2 HzProcess liveness table: each registered process, whether it is running and should be
can100 HzRaw CAN frames from all buses
sendcan100 HzCAN frames to be sent to the vehicle via panda
radarState20 HzRadar lead vehicle tracks and error flags
accelerometer104 HzIMU accelerometer readings (m/s²)
gyroscope104 HzIMU gyroscope readings (rad/s)
gpsLocationExternal10 HzExternal GNSS fix (u-blox or Qualcomm)
roadCameraState20 HzRoad camera frame metadata: frame ID, gain, exposure
driverCameraState20 HzDriver camera frame metadata
wideRoadCameraState20 HzWide road camera frame metadata

Python API

The cereal.messaging Python module wraps msgq and Cap’n Proto into a convenient high-level API. The two primary classes are SubMaster (subscribe to one or more services) and PubMaster (publish to one or more services).

Subscribing to messages

SubMaster manages a set of subscriber sockets, handles frequency tracking, and exposes a simple dict-like interface. The update() call blocks until any of the polled sockets has a message (or the timeout fires), then updates all non-polled sockets non-blocking.
import cereal.messaging as messaging

sm = messaging.SubMaster(['carState', 'modelV2'])

while True:
    sm.update()  # blocks up to 100 ms by default

    if sm.updated['carState']:
        speed = sm['carState'].vEgo          # m/s
        steer = sm['carState'].steeringAngleDeg

    if sm.updated['modelV2']:
        curvature = sm['modelV2'].action.desiredCurvature
SubMaster also tracks liveness. You can check whether all subscribed services are publishing at the expected rate:
if sm.all_checks():
    # all services alive, freq OK, and last message valid
    pass

if not sm.alive['carState']:
    # carState has not been received within 10x its expected interval
    pass

Polling on a specific service

When one service drives your loop rate (e.g. modelV2 at 20 Hz), pass it as the poll argument. Other services are read non-blocking on each iteration.
sm = messaging.SubMaster(['modelV2', 'carState', 'liveCalibration'], poll='modelV2')

while True:
    sm.update()
    if not sm.updated['modelV2']:
        continue
    # process model output here

Publishing messages

PubMaster opens a publisher socket for each named service. Use messaging.new_message to create a typed message builder, fill in the fields, and call pm.send.
import cereal.messaging as messaging

pm = messaging.PubMaster(['driverMonitoringState'])

while True:
    msg = messaging.new_message('driverMonitoringState')
    msg.valid = True
    msg.driverMonitoringState.alertLevel = 0   # no alert
    pm.send('driverMonitoringState', msg)

Reading raw socket messages

For one-shot reads without SubMaster, use the low-level socket functions:
import cereal.messaging as messaging

sock = messaging.sub_sock('selfdriveState', timeout=1000)

msg = messaging.recv_one(sock)
if msg is not None:
    state = msg.selfdriveState
    print(state.enabled, state.active, state.state)

Creating a message

messaging.new_message allocates a new log.Event builder with logMonoTime set to the current monotonic clock and valid = False by default.
# message without a list payload
msg = messaging.new_message('controlsState')
msg.valid = True
msg.controlsState.curvature = 0.003

# message with a variable-length list (e.g. onroadEvents)
msg = messaging.new_message('onroadEvents', 2)   # size=2
msg.onroadEvents[0].name = ...

Cap’n Proto schema conventions

Every message in log.capnp follows these conventions:
  • All physical quantities use SI units unless the field name includes a unit suffix (e.g. steeringAngleDeg, vCruiseKph).
  • All numeric identifiers (@0, @1, …) are permanent — fields are never renumbered. This ensures old log files remain parseable.
  • Adding new fields to existing structs is safe. Removing or reordering fields breaks compatibility.
  • Fork-specific additions belong in cereal/custom.capnp using the reserved struct slots to avoid conflicting with upstream schema changes.
The logMonoTime field on every Event is nanoseconds since boot (CLOCK_MONOTONIC). It is set by the publisher at the time of message creation and is used by loggerd to order log entries and by SubMaster to check service liveness.

System overview

Architecture, hardware, data flow, and design principles for the full openpilot system.

Process reference

Per-process documentation with pub/sub topic lists for every daemon.

Build docs developers (and LLMs) love