Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
224ea93
use current desired curvature for error instead of plan indexed with …
felsager Sep 9, 2025
4fea166
fix typo
felsager Sep 9, 2025
cf0aa85
correct function call
felsager Sep 9, 2025
f55fb02
test should accomodate change
felsager Sep 9, 2025
8582b92
only give raw desired curvature to torque controlled platforms
felsager Sep 9, 2025
33f9aad
raw desired curvature as input to all controllers
felsager Sep 10, 2025
e841e9c
update ref
felsager Sep 10, 2025
57687aa
.
felsager Sep 10, 2025
faa3d02
keep it clean
felsager Sep 11, 2025
592fb97
keep it clean
felsager Sep 11, 2025
cc66562
keep it clean
felsager Sep 11, 2025
762f012
desired lat accel buffer
felsager Sep 11, 2025
44319b7
update ref
felsager Sep 11, 2025
cafa6b9
use deque and add type annotation for confusing variables
felsager Sep 12, 2025
9d9f94c
use deque and add type annotation for confusing variables
felsager Sep 12, 2025
d5a522b
don't use expected lat accel for friction
felsager Sep 12, 2025
639afc8
update ref
felsager Sep 12, 2025
acf3863
use current expected curvature for low speed factor
felsager Sep 12, 2025
106b3b6
type lat_delay
felsager Sep 12, 2025
f8c2a7d
types in different pr and index from end of array
felsager Sep 16, 2025
69b3ea1
try half delay
felsager Sep 17, 2025
997cfed
try zero delay
felsager Sep 17, 2025
2688663
full lag for testing
felsager Sep 17, 2025
843295d
tweak torqued
felsager Sep 18, 2025
fb93974
only use expected error for pid
felsager Sep 18, 2025
ffadc70
only use expected error for pid
felsager Sep 18, 2025
2fa7e51
only use expected error for pid
felsager Sep 18, 2025
e517c4d
correct low speed compensation
felsager Sep 18, 2025
d18423b
use expected error for the integrator
felsager Sep 18, 2025
176d5fa
activate derivative action
felsager Sep 26, 2025
c1cf773
derivative to compensate large changes
felsager Sep 30, 2025
603fb8a
no kd for now
felsager Sep 30, 2025
3541931
updated opendbc with branch
felsager Sep 30, 2025
32ea793
initialize pid with derivative coefficient
felsager Sep 30, 2025
fd14a2d
set k_d properly
felsager Sep 30, 2025
8cbb481
use expected error
felsager Oct 2, 2025
e00931a
reference derivative feedforward
felsager Oct 2, 2025
6f81ce7
improve pid naming convention and use dt for controls
felsager Oct 3, 2025
1ca982c
only use measurement derivative for derivative compensation
felsager Oct 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions common/pid.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def __init__(self, k_p, k_i, k_f=0., k_d=0., pos_limit=1e308, neg_limit=-1e308,

self.set_limits(pos_limit, neg_limit)

self.i_rate = 1.0 / rate
self.i_dt = 1.0 / rate
self.speed = 0.0

self.reset()
Expand Down Expand Up @@ -46,12 +46,12 @@ def set_limits(self, pos_limit, neg_limit):

def update(self, error, error_rate=0.0, speed=0.0, feedforward=0., freeze_integrator=False):
self.speed = speed
self.p = float(error) * self.k_p
self.f = feedforward * self.k_f
self.d = error_rate * self.k_d
self.p = self.k_p * float(error)
self.d = self.k_d * error_rate
self.f = self.k_f * feedforward

if not freeze_integrator:
i = self.i + error * self.k_i * self.i_rate
i = self.i + self.k_i * self.i_dt * error

# Don't allow windup if already clipping
test_control = self.p + i + self.d + self.f
Expand Down
2 changes: 1 addition & 1 deletion opendbc_repo
Submodule opendbc_repo updated 72 files
+3 −1 SConscript
+53 −0 SConstruct
+1 −4 docs/CARS.md
+1 −0 opendbc/car/car.capnp
+2 −3 opendbc/dbc/SConscript
+1 −1 opendbc/safety/__init__.py
+10 −0 opendbc/safety/board/can.h
+1 −5 opendbc/safety/board/can_declarations.h
+19 −0 opendbc/safety/board/drivers/can_common.h
+4 −0 opendbc/safety/board/drivers/can_common_declarations.h
+28 −0 opendbc/safety/board/fake_stm.h
+25 −0 opendbc/safety/board/faults.h
+18 −0 opendbc/safety/board/faults_declarations.h
+47 −0 opendbc/safety/board/utils.h
+5 −40 opendbc/safety/helpers.h
+52 −52 opendbc/safety/lateral.h
+4 −4 opendbc/safety/longitudinal.h
+2 −2 opendbc/safety/modes/body.h
+1 −1 opendbc/safety/modes/chrysler.h
+5 −5 opendbc/safety/modes/defaults.h
+1 −1 opendbc/safety/modes/elm327.h
+2 −2 opendbc/safety/modes/ford.h
+1 −1 opendbc/safety/modes/gm.h
+1 −1 opendbc/safety/modes/honda.h
+1 −1 opendbc/safety/modes/hyundai.h
+1 −1 opendbc/safety/modes/hyundai_canfd.h
+2 −2 opendbc/safety/modes/hyundai_common.h
+2 −2 opendbc/safety/modes/mazda.h
+1 −1 opendbc/safety/modes/nissan.h
+2 −2 opendbc/safety/modes/psa.h
+2 −2 opendbc/safety/modes/rivian.h
+1 −1 opendbc/safety/modes/subaru.h
+1 −1 opendbc/safety/modes/subaru_preglobal.h
+2 −2 opendbc/safety/modes/tesla.h
+3 −3 opendbc/safety/modes/toyota.h
+2 −2 opendbc/safety/modes/volkswagen_mqb.h
+2 −2 opendbc/safety/modes/volkswagen_pq.h
+10 −8 opendbc/safety/safety.h
+0 −0 opendbc/safety/safety_declarations.h
+13 −13 opendbc/safety/tests/common.py
+1 −1 opendbc/safety/tests/hyundai_common.py
+11 −0 opendbc/safety/tests/install_mull.sh
+31 −31 opendbc/safety/tests/libsafety/SConscript
+0 −12 opendbc/safety/tests/libsafety/fake_stm.h
+37 −53 opendbc/safety/tests/libsafety/libsafety_py.py
+9 −200 opendbc/safety/tests/libsafety/safety.c
+192 −0 opendbc/safety/tests/libsafety/safety_helpers.h
+104 −0 opendbc/safety/tests/libsafety/safety_helpers.py
+7 −7 opendbc/safety/tests/misra/main.c
+2 −2 opendbc/safety/tests/misra/test_misra.sh
+3 −3 opendbc/safety/tests/mutation.sh
+1 −1 opendbc/safety/tests/safety_replay/helpers.py
+6 −6 opendbc/safety/tests/test_body.py
+14 −14 opendbc/safety/tests/test_chrysler.py
+2 −2 opendbc/safety/tests/test_defaults.py
+20 −20 opendbc/safety/tests/test_ford.py
+23 −23 opendbc/safety/tests/test_gm.py
+21 −21 opendbc/safety/tests/test_honda.py
+27 −27 opendbc/safety/tests/test_hyundai.py
+20 −20 opendbc/safety/tests/test_hyundai_canfd.py
+11 −11 opendbc/safety/tests/test_mazda.py
+14 −14 opendbc/safety/tests/test_nissan.py
+10 −10 opendbc/safety/tests/test_psa.py
+13 −13 opendbc/safety/tests/test_rivian.py
+14 −14 opendbc/safety/tests/test_subaru.py
+9 −9 opendbc/safety/tests/test_subaru_preglobal.py
+13 −13 opendbc/safety/tests/test_tesla.py
+22 −22 opendbc/safety/tests/test_toyota.py
+14 −14 opendbc/safety/tests/test_volkswagen_mqb.py
+12 −12 opendbc/safety/tests/test_volkswagen_pq.py
+0 −1 pyproject.toml
+0 −9 setup.sh
2 changes: 1 addition & 1 deletion panda
6 changes: 4 additions & 2 deletions selfdrive/controls/controlsd.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def __init__(self) -> None:
self.CI = interfaces[self.CP.carFingerprint](self.CP)

self.sm = messaging.SubMaster(['liveParameters', 'liveTorqueParameters', 'modelV2', 'selfdriveState',
'liveCalibration', 'livePose', 'longitudinalPlan', 'carState', 'carOutput',
'liveCalibration', 'liveDelay', 'livePose', 'longitudinalPlan', 'carState', 'carOutput',
'driverMonitoringState', 'onroadEvents', 'driverAssistance'], poll='selfdriveState')
self.pm = messaging.PubMaster(['carControl', 'controlsState'])

Expand Down Expand Up @@ -118,10 +118,12 @@ def state_control(self):
new_desired_curvature = model_v2.action.desiredCurvature if CC.latActive else self.curvature
self.desired_curvature, curvature_limited = clip_curvature(CS.vEgo, self.desired_curvature, new_desired_curvature, lp.roll)

lat_delay = self.sm["liveDelay"].lateralDelay

actuators.curvature = self.desired_curvature
steer, steeringAngleDeg, lac_log = self.LaC.update(CC.latActive, CS, self.VM, lp,
self.steer_limited_by_safety, self.desired_curvature,
curvature_limited) # TODO what if not available
curvature_limited, lat_delay)
actuators.torque = float(steer)
actuators.steeringAngleDeg = float(steeringAngleDeg)
# Ensure no NaNs/Infs
Expand Down
2 changes: 1 addition & 1 deletion selfdrive/controls/lib/drive_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def smooth_value(val, prev_val, tau, dt=DT_MDL):
alpha = 1 - np.exp(-dt/tau) if tau > 0 else 1
return alpha * val + (1 - alpha) * prev_val

def clip_curvature(v_ego, prev_curvature, new_curvature, roll):
def clip_curvature(v_ego, prev_curvature, new_curvature, roll) -> tuple[float, bool]:
# This function respects ISO lateral jerk and acceleration limits + a max curvature
v_ego = max(v_ego, MIN_SPEED)
max_curvature_rate = MAX_LATERAL_JERK / (v_ego ** 2) # inexact calculation, check https://github.com/commaai/openpilot/pull/24755
Expand Down
19 changes: 10 additions & 9 deletions selfdrive/controls/lib/latcontrol.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,28 @@


class LatControl(ABC):
def __init__(self, CP, CI):
self.sat_count_rate = 1.0 * DT_CTRL
def __init__(self, CP, CI, dt=DT_CTRL):
self.dt = dt
self.sat_time_dt = 1.0 * dt
self.sat_limit = CP.steerLimitTimer
self.sat_count = 0.
self.sat_time = 0.
self.sat_check_min_speed = 10.

# we define the steer torque scale as [-1.0...1.0]
self.steer_max = 1.0

@abstractmethod
def update(self, active, CS, VM, params, steer_limited_by_safety, desired_curvature, calibrated_pose, curvature_limited):
def update(self, active, CS, VM, params, steer_limited_by_safety, desired_curvature, curvature_limited, lat_delay: float):
pass

def reset(self):
self.sat_count = 0.
self.sat_time = 0.

def _check_saturation(self, saturated, CS, steer_limited_by_safety, curvature_limited):
# Saturated only if control output is not being limited by car torque/angle rate limits
if (saturated or curvature_limited) and CS.vEgo > self.sat_check_min_speed and not steer_limited_by_safety and not CS.steeringPressed:
self.sat_count += self.sat_count_rate
self.sat_time += self.sat_time_dt
else:
self.sat_count -= self.sat_count_rate
self.sat_count = np.clip(self.sat_count, 0.0, self.sat_limit)
return self.sat_count > (self.sat_limit - 1e-3)
self.sat_time -= self.sat_time_dt
self.sat_time = np.clip(self.sat_time, 0.0, self.sat_limit)
return self.sat_time > (self.sat_limit - 1e-3)
2 changes: 1 addition & 1 deletion selfdrive/controls/lib/latcontrol_angle.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def __init__(self, CP, CI):
self.sat_check_min_speed = 5.
self.use_steer_limited_by_safety = CP.brand == "tesla"

def update(self, active, CS, VM, params, steer_limited_by_safety, desired_curvature, curvature_limited):
def update(self, active, CS, VM, params, steer_limited_by_safety, desired_curvature, curvature_limited, lat_delay: float):
angle_log = log.ControlsState.LateralAngleState.new_message()

if not active:
Expand Down
2 changes: 1 addition & 1 deletion selfdrive/controls/lib/latcontrol_pid.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def __init__(self, CP, CI):
k_f=CP.lateralTuning.pid.kf, pos_limit=self.steer_max, neg_limit=-self.steer_max)
self.get_steer_feedforward = CI.get_steer_feedforward_function()

def update(self, active, CS, VM, params, steer_limited_by_safety, desired_curvature, curvature_limited):
def update(self, active, CS, VM, params, steer_limited_by_safety, desired_curvature, curvature_limited, lat_delay: float):
pid_log = log.ControlsState.LateralPIDState.new_message()
pid_log.steeringAngleDeg = float(CS.steeringAngleDeg)
pid_log.steeringRateDeg = float(CS.steeringRateDeg)
Expand Down
56 changes: 40 additions & 16 deletions selfdrive/controls/lib/latcontrol_torque.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
from collections import deque
import math
import numpy as np

from cereal import log
from opendbc.car.lateral import FRICTION_THRESHOLD, get_friction
from opendbc.car.tests.test_lateral_limits import MAX_LAT_JERK_UP
from openpilot.common.constants import ACCELERATION_DUE_TO_GRAVITY
from openpilot.selfdrive.controls.lib.latcontrol import LatControl
from openpilot.common.filter_simple import FirstOrderFilter
from openpilot.common.pid import PIDController

# At higher speeds (25+mph) we can assume:
Expand All @@ -21,17 +24,23 @@
LOW_SPEED_X = [0, 10, 20, 30]
LOW_SPEED_Y = [15, 13, 10, 5]


class LatControlTorque(LatControl):
def __init__(self, CP, CI):
super().__init__(CP, CI)
def __init__(self, CP, CI, dt):
super().__init__(CP, CI, dt)
self.torque_params = CP.lateralTuning.torque.as_builder()
self.torque_from_lateral_accel = CI.torque_from_lateral_accel()
self.lateral_accel_from_torque = CI.lateral_accel_from_torque()
self.pid = PIDController(self.torque_params.kp, self.torque_params.ki,
k_f=self.torque_params.kf)
self.pid = PIDController(self.torque_params.kp, self.torque_params.ki, k_d=self.torque_params.kd, k_f=self.torque_params.kf, rate=1/self.dt)
self.update_limits()
self.steering_angle_deadzone_deg = self.torque_params.steeringAngleDeadzoneDeg
lataccel_request_buffer_size = int(1 / self.dt)
self.requested_lateral_accel_buffer = deque([0.] * lataccel_request_buffer_size , maxlen=lataccel_request_buffer_size)
self.error_pre = 0.0
self.measurement_pre = 0.0
self.gravity_adjusted_lateral_accel_pre = 0.0
self.error_rate_filter = FirstOrderFilter(0.0, 1 / (2 * np.pi * MAX_LAT_JERK_UP), self.dt)
self.ff_filter = FirstOrderFilter(0.0, 1 / (2 * np.pi * MAX_LAT_JERK_UP), self.dt)
self.jerk_ff_filter = FirstOrderFilter(0.0, 1 / (2 * np.pi * MAX_LAT_JERK_UP), self.dt)

def update_live_torque_params(self, latAccelFactor, latAccelOffset, friction):
self.torque_params.latAccelFactor = latAccelFactor
Expand All @@ -43,7 +52,7 @@ def update_limits(self):
self.pid.set_limits(self.lateral_accel_from_torque(self.steer_max, self.torque_params),
self.lateral_accel_from_torque(-self.steer_max, self.torque_params))

def update(self, active, CS, VM, params, steer_limited_by_safety, desired_curvature, curvature_limited):
def update(self, active, CS, VM, params, steer_limited_by_safety, desired_curvature, curvature_limited, lat_delay: float):
pid_log = log.ControlsState.LateralTorqueState.new_message()
if not active:
output_torque = 0.0
Expand All @@ -53,27 +62,42 @@ def update(self, active, CS, VM, params, steer_limited_by_safety, desired_curvat
roll_compensation = params.roll * ACCELERATION_DUE_TO_GRAVITY
curvature_deadzone = abs(VM.calc_curvature(math.radians(self.steering_angle_deadzone_deg), CS.vEgo, 0.0))

desired_lateral_accel = desired_curvature * CS.vEgo ** 2
delay_frames = int(lat_delay / self.dt)
plan_future_desired_lateral_accel = desired_curvature * CS.vEgo ** 2
self.requested_lateral_accel_buffer.append(plan_future_desired_lateral_accel)
current_expected_lateral_accel = self.requested_lateral_accel_buffer[-delay_frames]
current_expected_curvature = current_expected_lateral_accel / (CS.vEgo ** 2)
actual_lateral_accel = actual_curvature * CS.vEgo ** 2
lateral_accel_deadzone = curvature_deadzone * CS.vEgo ** 2

low_speed_factor = np.interp(CS.vEgo, LOW_SPEED_X, LOW_SPEED_Y)**2
setpoint = desired_lateral_accel + low_speed_factor * desired_curvature
# pid error calculated as difference between expected and measured lateral acceleration
setpoint_expected = current_expected_lateral_accel + low_speed_factor * current_expected_curvature
measurement = actual_lateral_accel + low_speed_factor * actual_curvature
gravity_adjusted_lateral_accel = desired_lateral_accel - roll_compensation

gravity_adjusted_lateral_accel = plan_future_desired_lateral_accel - roll_compensation
error = float(setpoint_expected - measurement)
meas_rate = (measurement - self.measurement_pre) / self.dt
meas_rate_filtered = self.error_rate_filter.update(meas_rate)
jerk_ff = (gravity_adjusted_lateral_accel - self.gravity_adjusted_lateral_accel_pre) / self.dt
jerk_ff_filtered = self.jerk_ff_filter.update(jerk_ff)
self.error_pre = error
self.measurement_pre = measurement
self.gravity_adjusted_lateral_accel_pre = gravity_adjusted_lateral_accel
# do error correction in lateral acceleration space, convert at end to handle non-linear torque responses correctly
pid_log.error = float(setpoint - measurement)
pid_log.error = float(error)
ff = gravity_adjusted_lateral_accel
ff += 0.0 * jerk_ff_filtered
# latAccelOffset corrects roll compensation bias from device roll misalignment relative to car roll
ff -= self.torque_params.latAccelOffset
ff += get_friction(desired_lateral_accel - actual_lateral_accel, lateral_accel_deadzone, FRICTION_THRESHOLD, self.torque_params)
ff += get_friction(plan_future_desired_lateral_accel - actual_lateral_accel, lateral_accel_deadzone, FRICTION_THRESHOLD, self.torque_params)

freeze_integrator = steer_limited_by_safety or CS.steeringPressed or CS.vEgo < 5
output_lataccel = self.pid.update(pid_log.error,
feedforward=ff,
speed=CS.vEgo,
freeze_integrator=freeze_integrator)
meas_rate_filtered,
feedforward=ff,
speed=CS.vEgo,
freeze_integrator=freeze_integrator,)
#error_expected=error_expected)
output_torque = self.torque_from_lateral_accel(output_lataccel, self.torque_params)

pid_log.active = True
Expand All @@ -83,7 +107,7 @@ def update(self, active, CS, VM, params, steer_limited_by_safety, desired_curvat
pid_log.f = float(self.pid.f)
pid_log.output = float(-output_torque) # TODO: log lat accel?
pid_log.actualLateralAccel = float(actual_lateral_accel)
pid_log.desiredLateralAccel = float(desired_lateral_accel)
pid_log.desiredLateralAccel = float(plan_future_desired_lateral_accel)
pid_log.saturated = bool(self._check_saturation(self.steer_max - abs(output_torque) < 1e-3, CS, steer_limited_by_safety, curvature_limited))

# TODO left is positive in this convention
Expand Down
6 changes: 3 additions & 3 deletions selfdrive/controls/tests/test_latcontrol.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,13 @@ def test_saturation(self, car_name, controller):

# Saturate for curvature limited and controller limited
for _ in range(1000):
_, _, lac_log = controller.update(True, CS, VM, params, False, 0, True)
_, _, lac_log = controller.update(True, CS, VM, params, False, 0, True, 0)
assert lac_log.saturated

for _ in range(1000):
_, _, lac_log = controller.update(True, CS, VM, params, False, 0, False)
_, _, lac_log = controller.update(True, CS, VM, params, False, 0, False, 0)
assert not lac_log.saturated

for _ in range(1000):
_, _, lac_log = controller.update(True, CS, VM, params, False, 1, False)
_, _, lac_log = controller.update(True, CS, VM, params, False, 1, False, 0)
assert lac_log.saturated
2 changes: 1 addition & 1 deletion selfdrive/test/process_replay/ref_commit
Original file line number Diff line number Diff line change
@@ -1 +1 @@
afcab1abb62b9d5678342956cced4712f44e909e
4e2926cc457a057980633163eabe062e421e9ff6
Loading