# How Openpilot Performs Real-Time Car Parameter Estimation Using liveParameters

> Discover how openpilot estimates car parameters in real-time using liveParameters. Learn how the Kalman filter refines steer ratio, stiffness, and offsets for precise lateral control.

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

---

**Openpilot continuously refines steer ratio, stiffness factor, and angle offsets through a Kalman-filter-based learner running in the locationd daemon, publishing validated estimates as liveParameters messages for lateral control.**

The commaai/openpilot repository implements real-time vehicle dynamics estimation through the **locationd** (Location Daemon) service. This system fuses live sensor streams to adapt to changing vehicle characteristics—such as tire wear or load variations—without requiring manual recalibration, storing persistent estimates in the **LiveParametersV2** parameter key.

## Core Architecture Components

The parameter estimation pipeline centers on three primary components orchestrated within [`selfdrive/locationd/paramsd.py`](https://github.com/commaai/openpilot/blob/main/selfdrive/locationd/paramsd.py):

- **VehicleParamsLearner** – Implements the Extended Kalman Filter (EKF) logic and manages the state vector
- **CarKalman** – Generates the filter equations and observation models defined in [`selfdrive/locationd/models/car_kf.py`](https://github.com/commaai/openpilot/blob/main/selfdrive/locationd/models/car_kf.py)
- **PoseCalibrator** – Aligns raw pose data with the vehicle reference frame using utilities in [`selfdrive/locationd/helpers.py`](https://github.com/commaai/openpilot/blob/main/selfdrive/locationd/helpers.py)

The system persists estimates using the **Params** database ([`common/params.py`](https://github.com/commaai/openpilot/blob/main/common/params.py)) under the binary key **`LiveParametersV2`**, enabling warm-start initialization across ignition cycles.

## Data Flow and Estimation Pipeline

### Input Sensor Fusion

The daemon subscribes to three critical message streams via `SubMaster`:

1. `livePose` – Raw GNSS and vision-based pose estimates (position, orientation, angular velocity)
2. `liveCalibration` – Camera-to-vehicle calibration offsets and status
3. `carState` – Steering angle, vehicle speed, and pose validity flags

Preprocessing occurs through `Pose.from_live_pose` and `PoseCalibrator.feed_live_calib` to transform measurements into the vehicle coordinate system before filtering.

### Kalman Filter Update Cycle

The **CarKalman** EKF processes observations at approximately 20 Hz, synchronized with `livePose` updates. The filter ingests:

- `ROAD_FRAME_YAW_RATE` and `ROAD_ROLL` extracted from calibrated pose data
- `STEER_ANGLE` and `ROAD_FRAME_X_SPEED` derived from vehicle CAN signals
- High-noise observations of the current `STEER_RATIO` and `STIFFNESS` states to bound estimation variance

The state vector `x` maintains estimates for `STEER_RATIO`, `STIFFNESS`, `ANGLE_OFFSET`, `ANGLE_OFFSET_FAST`, and `ROAD_ROLL`, while the covariance matrix `P` provides real-time uncertainty quantification for each parameter.

### Validation and Persistence

Hysteresis-based checks in `check_valid_with_hysteresis` enforce safety constraints before publishing:

- Angle offset limits (`OFFSET_MAX`, `OFFSET_LOWERED_MAX`)
- Roll boundaries (`ROLL_MAX`, `ROLL_LOWERED_MAX`)
- Sensor consistency validation for yaw-rate magnitude and lateral acceleration

Every iteration constructs a `liveParameters` protobuf via `get_msg()`, publishing immediately through `PubMaster`. Once per minute (every 1200 frames), the daemon serializes the message to persistent storage using `params.put_nonblocking("LiveParametersV2", msg.to_bytes())`.

## Real-Time Operational Safeguards

The implementation includes specific mechanisms to prevent estimator divergence during non-representative driving conditions:

**Active-Mode Gating** restricts filter updates to periods when vehicle speed exceeds `MIN_ACTIVE_SPEED` (1 m/s) and absolute steering angle remains below 45 degrees. This prevents parameter drift during parking maneuvers or standstill.

**Adaptive Noise Modeling** applies sensor-specific observation noise caps—yaw-rate standard deviation limited to 10 rad/s and roll standard deviation to 1 rad—maintaining filter stability under noisy GNSS or vision conditions.

**State Recovery** on startup invokes `retrieve_initial_vehicle_params()` to load cached values from `LiveParametersV2`, validate them against the current `CarParams` configuration, and seed the learner. Corrupted or vehicle-mismatched data triggers automatic fallback to default specifications.

## Implementation Example

The following excerpt from [`selfdrive/locationd/paramsd.py`](https://github.com/commaai/openpilot/blob/main/selfdrive/locationd/paramsd.py) illustrates the main daemon loop initializing the learner and managing the estimation cycle:

```python
pm = messaging.PubMaster(['liveParameters'])
sm = messaging.SubMaster(['livePose', 'liveCalibration', 'carState'], poll='livePose')

params = Params()
CP = messaging.log_from_bytes(params.get("CarParams", block=True), car.CarParams)

# Load previous estimates if available

steer_ratio, stiffness, angle_offset_deg, p_initial = retrieve_initial_vehicle_params(
    params, CP, REPLAY=False, DEBUG=False)

learner = VehicleParamsLearner(
    CP,
    steer_ratio,
    stiffness,
    np.radians(angle_offset_deg),
    p_initial)

while True:
    sm.update()
    if sm.all_checks():
        for which in sorted(sm.updated.keys(), key=lambda x: sm.logMonoTime[x]):
            if sm.updated[which]:
                t = sm.logMonoTime[which] * 1e-9
                learner.handle_log(t, which, sm[which])

    if sm.updated['livePose']:
        msg = learner.get_msg(sm.all_checks())
        # Persist once per minute (1200 frames at 20 Hz)

        if sm.frame % 1200 == 0:
            params.put_nonblocking("LiveParametersV2", msg.to_bytes())
        pm.send('liveParameters', msg.to_bytes())

```

## Summary

- **VehicleParamsLearner** in [`selfdrive/locationd/paramsd.py`](https://github.com/commaai/openpilot/blob/main/selfdrive/locationd/paramsd.py) orchestrates real-time estimation using an EKF defined in [`selfdrive/locationd/models/car_kf.py`](https://github.com/commaai/openpilot/blob/main/selfdrive/locationd/models/car_kf.py)
- The system fuses `livePose`, `liveCalibration`, and `carState` at 20 Hz to estimate steer ratio, stiffness factor, angle offsets, and road roll
- Estimates undergo hysteresis-based validity checks before publishing and persist to `LiveParametersV2` for cross-drive initialization
- Active-mode gating prevents parameter drift during low-speed or high-steering-angle maneuvers

## Frequently Asked Questions

### What specific vehicle parameters does liveParameters estimate?

The system estimates **steer ratio** (steering wheel to road wheel angle ratio), **stiffness factor** (tire compliance coefficient), **angle offset** (slow-varying steering bias), **angle offset fast** (rapid calibration changes), and **road roll** (lateral road inclination). These values feed directly into lateral control algorithms to compensate for vehicle-specific dynamics and road geometry.

### How does openpilot prevent liveParameters estimates from diverging?

The implementation employs **active-mode gating** that requires vehicle speed above 1 m/s and steering magnitude below 45 degrees before processing updates. Additionally, the filter receives high-noise observations of steer ratio and stiffness to artificially bound variance, while `check_valid_with_hysteresis` enforces maximum limits on angle offsets and roll before inclusion in the published message.

### Where does openpilot store estimated parameters between drives?

Parameters serialize to the **Params** key-value store ([`common/params.py`](https://github.com/commaai/openpilot/blob/main/common/params.py)) under the binary key **`LiveParametersV2`** once per minute. During startup, `retrieve_initial_vehicle_params()` validates cached values against the current `CarParams` vehicle configuration before seeding the Kalman filter, ensuring estimates remain valid across ignition cycles unless the vehicle hardware changes.

### Which source files contain the liveParameters implementation?

The core logic resides in [`selfdrive/locationd/paramsd.py`](https://github.com/commaai/openpilot/blob/main/selfdrive/locationd/paramsd.py) (daemon orchestration and learner), [`selfdrive/locationd/models/car_kf.py`](https://github.com/commaai/openpilot/blob/main/selfdrive/locationd/models/car_kf.py) (EKF equations and state definitions), and [`selfdrive/locationd/helpers.py`](https://github.com/commaai/openpilot/blob/main/selfdrive/locationd/helpers.py) (pose calibration utilities). The persistence layer uses [`common/params.py`](https://github.com/commaai/openpilot/blob/main/common/params.py) for the `LiveParametersV2` storage interface.