From e5a826949b538a39ca90716368422fa61b81f35c Mon Sep 17 00:00:00 2001 From: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Fri, 13 Feb 2026 14:48:00 -0800 Subject: [PATCH 1/6] Hyundai Longitudinal: refactor tuning (#359) * hkg long: refactor long tune Refactor long tune to add dynamic comfort band logic, reduce reliance on radarUnavailable setters and clean up config. * test coverage for dynamic comfort band * show that negative list comprehension is the same as expected format * deprecate unittest * blank space * pytest.approx * Update test_tuning_controller.py --------- Co-authored-by: Jason Wen --- .../car/hyundai/longitudinal/config.py | 22 +- .../car/hyundai/longitudinal/controller.py | 31 +- .../hyundai/tests/test_tuning_controller.py | 293 ++++++++++-------- 3 files changed, 191 insertions(+), 155 deletions(-) diff --git a/opendbc/sunnypilot/car/hyundai/longitudinal/config.py b/opendbc/sunnypilot/car/hyundai/longitudinal/config.py index c685f5b5..dc810919 100644 --- a/opendbc/sunnypilot/car/hyundai/longitudinal/config.py +++ b/opendbc/sunnypilot/car/hyundai/longitudinal/config.py @@ -18,7 +18,7 @@ class CarTuningConfig: lookahead_jerk_bp: list[float] = field(default_factory=lambda: [5., 20.]) lookahead_jerk_upper_v: list[float] = field(default_factory=lambda: [0.25, 0.5]) lookahead_jerk_lower_v: list[float] = field(default_factory=lambda: [0.15, 0.3]) - longitudinal_actuator_delay: float = 0.45 + longitudinal_actuator_delay: float = 0.50 jerk_limits: float = 4.0 @@ -33,8 +33,10 @@ TUNING_CONFIGS = { "EV": CarTuningConfig( stopping_decel_rate=0.45, v_ego_stopping=0.35, - lookahead_jerk_upper_v=[0.3, 0.7], - lookahead_jerk_lower_v=[0.2, 0.4], + lookahead_jerk_bp=[2., 5., 20.], + lookahead_jerk_upper_v=[0.25, 0.5, 1.0], + lookahead_jerk_lower_v=[0.05, 0.10, 0.3], + longitudinal_actuator_delay=0.25 ), "HYBRID": CarTuningConfig( v_ego_starting=0.15, @@ -42,6 +44,7 @@ TUNING_CONFIGS = { v_ego_stopping=0.4, ), "DEFAULT": CarTuningConfig( + v_ego_stopping=0.3, lookahead_jerk_bp=[2., 5., 20.], lookahead_jerk_upper_v=[0.25, 0.5, 1.0], lookahead_jerk_lower_v=[0.05, 0.10, 0.3], @@ -51,18 +54,21 @@ TUNING_CONFIGS = { # Car-specific configs CAR_SPECIFIC_CONFIGS = { CAR.KIA_NIRO_EV: CarTuningConfig( - stopping_decel_rate=0.3, - lookahead_jerk_upper_v=[0.3, 1.0], - lookahead_jerk_lower_v=[0.2, 0.4], + v_ego_stopping=0.1, + stopping_decel_rate=0.1, + lookahead_jerk_bp=[2., 5., 20.], + lookahead_jerk_upper_v=[0.25, 0.5, 1.0], + lookahead_jerk_lower_v=[0.1, 0.15, 0.3], + longitudinal_actuator_delay=0.25, jerk_limits=2.5, ), CAR.KIA_NIRO_PHEV_2022: CarTuningConfig( stopping_decel_rate=0.3, lookahead_jerk_upper_v=[0.3, 1.0], lookahead_jerk_lower_v=[0.15, 0.3], - jerk_limits=4.0, + jerk_limits=5.0, ), CAR.HYUNDAI_IONIQ: CarTuningConfig( - jerk_limits=4.5, + jerk_limits=5.0, ) } diff --git a/opendbc/sunnypilot/car/hyundai/longitudinal/controller.py b/opendbc/sunnypilot/car/hyundai/longitudinal/controller.py index c15213a3..76e1f44d 100644 --- a/opendbc/sunnypilot/car/hyundai/longitudinal/controller.py +++ b/opendbc/sunnypilot/car/hyundai/longitudinal/controller.py @@ -17,7 +17,6 @@ from opendbc.sunnypilot.car.hyundai.values import HyundaiFlagsSP LongCtrlState = structs.CarControl.Actuators.LongControlState MIN_JERK = 0.5 -COMFORT_BAND_VAL = 0.01 DYNAMIC_LOWER_JERK_BP = [-2.0, -1.5, -1.0, -0.25, -0.1, -0.025, -0.01, -0.005] DYNAMIC_LOWER_JERK_V = [3.3, 2.5, 2.0, 1.9, 1.8, 1.65, 1.15, 0.5] @@ -101,12 +100,12 @@ class LongitudinalController: # Upper jerk limit varies based on speed and control state if long_control_state == LongCtrlState.pid: - upper_limit = float(np.interp(velocity, [0.0, 5.0, 20.0], [2.0, 3.0, 1.6])) + upper_limit = float(np.interp(velocity, [0.0, 5.0, 20.0], [2.0, 3.0, 2.0])) else: upper_limit = 0.5 # Default for non-PID states # Lower jerk limit varies based on speed - lower_limit = float(np.interp(velocity, [0.0, 5.0, 20.0], [5.0, 4.0, 2.5])) + lower_limit = float(np.interp(velocity, [0.0, 5.0, 20.0], [5.0, 3.5, 3.0])) return upper_limit, lower_limit @@ -144,9 +143,6 @@ class LongitudinalController: Dynamic lower jerk limit (m/s³) """ - if self.CP.radarUnavailable: - return 5.0 - if accel_error < 0: # Scale the brake jerk values based on car config lower_max = self.car_config.jerk_limits @@ -187,8 +183,6 @@ class LongitudinalController: # Calculate lower jerk limit lower_jerk = max(-j_ego_lower, MIN_JERK) - if self.CP.radarUnavailable: - lower_jerk = 5.0 # Final jerk limits with thresholds desired_jerk_upper = min(max(j_ego_upper, MIN_JERK), upper_speed_factor) @@ -200,6 +194,10 @@ class LongitudinalController: dynamic_lower_jerk = self._calculate_dynamic_lower_jerk(dynamic_accel_error, velocity) dynamic_desired_lower_jerk = min(dynamic_lower_jerk, lower_speed_factor) + if self.CP.radarUnavailable: + desired_jerk_lower = 5.0 + dynamic_desired_lower_jerk = 5.0 + # Apply jerk limits based on tuning approach self.jerk_upper = ramp_update(self.jerk_upper, desired_jerk_upper) @@ -223,7 +221,7 @@ class LongitudinalController: """ # Skip custom processing if tuning is disabled or radar unavailable - if not self.enabled or self.CP.radarUnavailable: + if not self.enabled: self.desired_accel = self.accel_cmd self.actual_accel = self.accel_cmd return @@ -246,14 +244,19 @@ class LongitudinalController: self.accel_last = self.actual_accel - def calculate_comfort_band(self, CC: structs.CarControl) -> None: - if not self.enabled or self.CP.radarUnavailable or not CC.longActive: + def calculate_comfort_band(self, CC: structs.CarControl, CS: CarStateBase) -> None: + if not self.enabled or not CC.longActive: self.comfort_band_upper = 0.0 self.comfort_band_lower = 0.0 return - self.comfort_band_upper = COMFORT_BAND_VAL - self.comfort_band_lower = COMFORT_BAND_VAL + accel = CS.out.aEgo + accel_vals = [0.0, 0.3, 0.6, 0.9, 1.2, 1.5] + decel_vals = [-3.0, -2.0, -1.5, -1.0, -0.5, -0.05] + comfort_band_vals = [0.0, 0.02, 0.04, 0.06, 0.08, 0.10] + + self.comfort_band_upper = float(np.interp(accel, accel_vals, comfort_band_vals)) + self.comfort_band_lower = float(np.interp(accel, decel_vals, comfort_band_vals[::-1])) def get_tuning_state(self) -> None: """Update the tuning state object with current control values. @@ -289,7 +292,7 @@ class LongitudinalController: self.get_stopping_state(actuators) self.calculate_jerk(CC, CS, long_control_state) self.calculate_accel(CC) - self.calculate_comfort_band(CC) + self.calculate_comfort_band(CC, CS) self.get_tuning_state() self.long_control_state_last = long_control_state diff --git a/opendbc/sunnypilot/car/hyundai/tests/test_tuning_controller.py b/opendbc/sunnypilot/car/hyundai/tests/test_tuning_controller.py index 6c085e76..0aa23a60 100644 --- a/opendbc/sunnypilot/car/hyundai/tests/test_tuning_controller.py +++ b/opendbc/sunnypilot/car/hyundai/tests/test_tuning_controller.py @@ -5,145 +5,172 @@ This file is part of sunnypilot and is licensed under the MIT License. See the LICENSE.md file in the root directory for more details. """ -import unittest import numpy as np -from unittest.mock import Mock +import pytest +from dataclasses import dataclass, field -from opendbc.sunnypilot.car.hyundai.longitudinal.controller import LongitudinalController, LongitudinalState +from opendbc.car import structs +from opendbc.sunnypilot.car.hyundai.longitudinal.controller import LongitudinalController from opendbc.sunnypilot.car.hyundai.values import HyundaiFlagsSP -from opendbc.car import DT_CTRL, structs -from opendbc.car.interfaces import CarStateBase -from opendbc.car.hyundai.values import HyundaiFlags LongCtrlState = structs.CarControl.Actuators.LongControlState -class TestLongitudinalTuningController(unittest.TestCase): - def setUp(self): - self.mock_CP = Mock(carFingerprint="KIA_NIRO_EV", flags=0) - self.mock_CP.radarUnavailable = False # ensure tuning branch - self.mock_CP_SP = Mock(flags=0) - self.controller = LongitudinalController(self.mock_CP, self.mock_CP_SP) - - def test_init(self): - """Test controller initialization""" - self.assertIsInstance(self.controller.tuning, LongitudinalState) - self.assertEqual(self.controller.desired_accel, 0.0) - self.assertEqual(self.controller.actual_accel, 0.0) - self.assertEqual(self.controller.jerk_upper, 0.0) - self.assertEqual(self.controller.jerk_lower, 0.0) - self.assertEqual(self.controller.comfort_band_upper, 0.0) - self.assertEqual(self.controller.comfort_band_lower, 0.0) - - def test_make_jerk_flag_off(self): - """Test when LONG_TUNING_DYNAMIC flag is off""" - mock_CC, mock_CS = Mock(spec=structs.CarControl), Mock(spec=CarStateBase) - mock_CS.out = Mock() - mock_CS.out.vEgo = 0.0 - mock_CS.out.aEgo = 0.0 - mock_CS.aBasis = 0.0 - - # Test with PID state - self.controller.calculate_jerk(mock_CC, mock_CS, LongCtrlState.pid) - print(f"[PID state] jerk_upper={self.controller.jerk_upper:.2f}, jerk_lower={self.controller.jerk_lower:.2f}") - self.assertEqual(self.controller.jerk_upper, 3.0) - self.assertEqual(self.controller.jerk_lower, 5.0) - - # Test with non-PID state - self.controller.calculate_jerk(mock_CC, mock_CS, LongCtrlState.stopping) - print(f"[Non-PID state] jerk_upper={self.controller.jerk_upper:.2f}, jerk_lower={self.controller.jerk_lower:.2f}") - self.assertEqual(self.controller.jerk_upper, 1.0) - self.assertEqual(self.controller.jerk_lower, 5.0) - - def test_make_jerk_flag_on(self): - """Only verify that limits update when flags are on.""" - self.controller.CP_SP.flags = HyundaiFlagsSP.LONG_TUNING_DYNAMIC - self.controller.CP.flags = HyundaiFlags.CANFD - mock_CC = Mock() - mock_CC.actuators = Mock(accel=1.0) - mock_CC.longActive = True - self.controller.stopping = False - mock_CS = Mock() - mock_CS.out = Mock(aEgo=0.8, vEgo=3.0) - mock_CS.aBasis = 0.8 - - self.controller.calculate_jerk(mock_CC, mock_CS, LongCtrlState.pid) - print(f"[FlagOn] jerk_upper={self.controller.jerk_upper:.3f}, jerk_lower={self.controller.jerk_lower:.3f}") - self.assertGreater(self.controller.jerk_upper, 0.0) - self.assertGreater(self.controller.jerk_lower, 0.0) - - def test_a_value_jerk_scaling(self): - """Test a_value jerk scaling under tuning branch.""" - self.controller.CP_SP.flags = HyundaiFlagsSP.LONG_TUNING_DYNAMIC - self.controller.CP.radarUnavailable = False - mock_CC = Mock() - mock_CC.actuators = Mock(accel=1.0) - mock_CC.longActive = True - print("[a_value] starting accel_last:", self.controller.tuning.accel_last) - # first pass: limit to jerk_upper * DT_CTRL * 2 = 0.1 - self.controller.jerk_upper = 0.1 / (DT_CTRL * 2) - self.controller.accel_cmd = 1.0 # ensure accel_cmd is set - self.controller.calculate_accel(mock_CC) - print(f"[a_value] pass1 actual_accel={self.controller.actual_accel:.5f}") - self.assertAlmostEqual(self.controller.actual_accel, 0.1, places=5) - - # second pass: limit increment by new jerk_upper - mock_CC.actuators.accel = 0.7 - self.controller.jerk_upper = 0.2 / (DT_CTRL * 2) - self.controller.accel_cmd = 0.7 # update accel_cmd - self.controller.calculate_accel(mock_CC) - print(f"[a_value] pass2 actual_accel={self.controller.actual_accel:.5f}") - self.assertAlmostEqual(self.controller.actual_accel, 0.3, places=5) - - def test_make_jerk_realistic_profile(self): - """Test make_jerk with realistic velocity and acceleration profile""" - np.random.seed(42) - num_points = 30 - segments = [ - np.random.uniform(0.3, 0.8, num_points//4), - np.random.uniform(0.8, 1.6, num_points//4), - np.random.uniform(-0.2, 0.2, num_points//4), - np.random.uniform(-1.2, -0.5, num_points//8), - np.random.uniform(-2.2, -1.2, num_points//8) - ] - accels = np.concatenate(segments)[:num_points] - vels = np.zeros_like(accels) - vels[0] = 5.0 - for i in range(1, len(accels)): - vels[i] = max(0.0, min(30.0, vels[i-1] + accels[i-1] * (DT_CTRL*2))) - mock_CC, mock_CS = Mock(), Mock() - mock_CC.actuators, mock_CS.out = Mock(), Mock() - mock_CC.longActive = True - self.controller.stopping = False - - # Test with LONG_TUNING_DYNAMIC only - self.controller.CP_SP.flags = HyundaiFlagsSP.LONG_TUNING_DYNAMIC - for v, a in zip(vels, accels, strict=True): - mock_CS.out.vEgo = float(v) - mock_CS.out.aEgo = float(a) - mock_CS.aBasis = float(a) - mock_CC.actuators.accel = float(a) - self.controller.calculate_jerk(mock_CC, mock_CS, LongCtrlState.pid) - print(f"[realistic][LONG_TUNING_DYNAMIC] v={v:.2f}, a={a:.2f}, jerk_upper={self.controller.jerk_upper:.2f}, jerk_lower={self.controller.jerk_lower:.2f}") - self.assertGreater(self.controller.jerk_upper, 0.0) - - # Reset controller before next test - self.controller.tuning = LongitudinalState() - self.controller.jerk_upper = 0.5 - self.controller.jerk_lower = 0.5 - - # Test with LONG_TUNING_DYNAMIC and LONG_TUNING_PREDICTIVE - self.controller.CP_SP.flags = HyundaiFlagsSP.LONG_TUNING_DYNAMIC | HyundaiFlagsSP.LONG_TUNING_PREDICTIVE - for v, a in zip(vels, accels, strict=True): - mock_CS.out.vEgo = float(v) - mock_CS.out.aEgo = float(a) - mock_CS.aBasis = float(a) - mock_CC.actuators.accel = float(a) - self.controller.calculate_jerk(mock_CC, mock_CS, LongCtrlState.pid) - print(f"[realistic][LONG_TUNING_PREDICTIVE] v={v:.2f}, a={a:.2f}, " + - f"jerk_upper={self.controller.jerk_upper:.2f}, jerk_lower={self.controller.jerk_lower:.2f}") - self.assertGreater(self.controller.jerk_upper, 0.0) +@dataclass +class CP: + carFingerprint: str = "KIA_NIRO_EV" + flags: int = 0 + radarUnavailable: bool = False -if __name__ == "__main__": - unittest.main() +@dataclass +class Actuators: + accel: float = 0.0 + longControlState = LongCtrlState + + +@dataclass +class CC: + actuators: Actuators = field(default_factory=lambda: Actuators()) + longActive: bool = True + + +@dataclass +class Out: + vEgo: float = 0.0 + aEgo: float = 0.0 + + +@dataclass +class CS: + out: Out = field(default_factory=lambda: Out()) + aBasis: float = 0.0 + + +class TestLongitudinalTuningController: + def setup_method(self): + self.CP = CP(flags=0) + self.CP_SP = CP(flags=0) + self.CP_SP.flags = HyundaiFlagsSP.LONG_TUNING_DYNAMIC + self.CS = CS() + self.CC = CC() + self.controller = LongitudinalController(self.CP, self.CP_SP) + + def test_enabled_and_disabled(self): + assert self.controller.enabled + self.CP_SP.flags = 0 + assert not self.controller.enabled + + def test_stopping_state(self): + self.CC.actuators.longControlState = LongCtrlState.stopping + self.controller.get_stopping_state(self.CC.actuators) + assert self.controller.stopping + assert self.controller.stopping_count == 0 + self.CC.actuators.longControlState = LongCtrlState.pid + self.controller.get_stopping_state(self.CC.actuators) + assert not self.controller.stopping + + def test_calc_speed_based_jerk(self): + assert (0.5, 5.0) == self.controller._calculate_speed_based_jerk_limits(0.0, LongCtrlState.stopping) + velocities: list = [0.0, 2.0, 5.0, 7.0, 10.0, 15.0, 20.0, 25.0, 30.0] + + for velocity in velocities: + upper_limit = float(np.interp(velocity, [0.0, 5.0, 20.0], [2.0, 3.0, 2.0])) + lower_limit = float(np.interp(velocity, [0.0, 5.0, 20.0], [5.0, 3.5, 3.0])) + expected: tuple = (upper_limit, lower_limit) + actual: tuple = self.controller._calculate_speed_based_jerk_limits(velocity, LongCtrlState.pid) + assert expected == actual + + def test_calc_lookahead_jerk(self): + assert pytest.approx((-1.0, -3.3), abs=0.1) == self.controller._calculate_lookahead_jerk(-0.5, 4.9) + assert pytest.approx((1.0, 3.3), abs=0.1) == self.controller._calculate_lookahead_jerk(0.5, 4.9) + + def test_calc_dynamic_low_jerk(self): + self.controller.car_config.jerk_limits = 3.3 + assert 0.5 == self.controller._calculate_dynamic_lower_jerk(0.0, 10.0) + assert 3.3 == self.controller._calculate_dynamic_lower_jerk(-2.0, 10.0) + + def test_calc_jerk(self): + self.CP_SP.flags = 0 + self.controller.calculate_jerk(self.CC, self.CS, LongCtrlState.pid) + assert self.controller.jerk_upper == 3.0 + assert self.controller.jerk_lower == 5.0 + self.controller.calculate_jerk(self.CC, self.CS, LongCtrlState.off) + assert self.controller.jerk_upper == 1.0 + + self.CP_SP.flags = HyundaiFlagsSP.LONG_TUNING_PREDICTIVE + self.controller.__init__(self.CP, self.CP_SP) + self.CS.out.vEgo = 10.0 + self.controller.accel_cmd = -3.5 + self.controller.accel_last = -1.0 + self.controller.calculate_jerk(self.CC, self.CS, LongCtrlState.pid) + assert self.controller.jerk_upper == 0.1 # ramp update first pass + assert self.controller.jerk_lower == pytest.approx(3.33, abs=0.01) + + self.CP_SP.flags = HyundaiFlagsSP.LONG_TUNING_DYNAMIC + self.controller.__init__(self.CP, self.CP_SP) + self.controller.car_config.jerk_limits = 3.3 + self.CS.out.vEgo = 10.0 + self.CS.aBasis = -3.3 + self.CS.out.aEgo = -3.5 + self.controller.accel_cmd = -3.5 + self.controller.accel_last = -1.0 + for _ in range(50): + self.controller.calculate_jerk(self.CC, self.CS, LongCtrlState.pid) + assert self.controller.jerk_upper == 0.5 + assert self.controller.jerk_lower == 3.3 + + def test_calc_accel(self): + self.CP_SP.flags = 0 + self.controller.accel_cmd = 1.5 + self.controller.calculate_accel(self.CC) + assert self.controller.desired_accel == self.controller.accel_cmd + + self.CP_SP.flags = HyundaiFlagsSP.LONG_TUNING_DYNAMIC + self.CC.longActive = False + self.controller.calculate_accel(self.CC) + assert self.controller.desired_accel == 0.0 + + self.CC.longActive = True + self.controller.stopping = True + self.controller.calculate_accel(self.CC) + assert self.controller.desired_accel == 0.0 + + def test_calc_comfort_band(self): + stock_decels_list: list = [-3.0, -2.0, -1.5, -1.0, -0.5, -0.05] + stock_accels_list: list = [0.0, 0.3, 0.6, 0.9, 1.2, 1.5] + stock_comfort_band_vals: list = [0.0, 0.02, 0.04, 0.06, 0.08, 0.10] + + decels_list: list = [-3.5, -3.1, -2.245, -1.853, -1.234, -0.64352, -0.06432, -0.00005] + accels_list: list = [0.0, 0.23345, 0.456, 0.5677, 0.6788, 0.834, 1.0, 1.3456, 1.8] + + for decel in decels_list: + self.CS.out.aEgo = decel + self.controller.calculate_comfort_band(self.CC, self.CS) + actual = self.controller.comfort_band_lower + expected = float(np.interp(decel, stock_decels_list, [0.1, 0.08, 0.06, 0.04, 0.02, 0.0])) + assert actual == expected + assert self.controller.comfort_band_upper == 0.0 + + for accel in accels_list: + self.CS.out.aEgo = accel + self.controller.calculate_comfort_band(self.CC, self.CS) + actual = self.controller.comfort_band_upper + expected = float(np.interp(accel, stock_accels_list, stock_comfort_band_vals)) + assert actual == expected + assert self.controller.comfort_band_lower == 0.0 + + def test_update(self): + self.CC.actuators.accel = 2.0 + self.CC.actuators.longControlState = LongCtrlState.pid + self.CS.aBasis = 1.75 + self.CS.out.aEgo = 2.0 + self.CS.out.vEgo = 5.0 + for _ in range(50): + self.controller.update(self.CC, self.CS) + + assert self.controller.jerk_lower == 0.5 + assert self.controller.jerk_upper == pytest.approx(1.01, abs=0.01) + assert self.controller.comfort_band_lower == 0.0 + assert self.controller.comfort_band_upper == 0.10 + assert self.controller.desired_accel == 2.0 From a45a2861dd568837e8ee7283defc55d0d8c43aee Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 13 Feb 2026 18:03:38 -0500 Subject: [PATCH 2/6] Tesla: add fingerprint for Model 3 Performance HW4 (#407) * Fingerprint Tesla Model 3 Performance HW4 Added Tesla Model 3 Performance HW4 (Highland) Fingerprint * Tesla Model 3 Performance Fingerprint Added Fingerprint via fingerprints_ext * Apply suggestions from code review * not hyundai * wrong dir --------- Co-authored-by: flberger1987 --- opendbc/car/tesla/fingerprints.py | 4 ++++ opendbc/sunnypilot/car/tesla/fingerprints_ext.py | 12 ++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 opendbc/sunnypilot/car/tesla/fingerprints_ext.py diff --git a/opendbc/car/tesla/fingerprints.py b/opendbc/car/tesla/fingerprints.py index 8d4b185e..5fe292bc 100644 --- a/opendbc/car/tesla/fingerprints.py +++ b/opendbc/car/tesla/fingerprints.py @@ -1,6 +1,8 @@ """ AUTO-FORMATTED USING opendbc/car/debug/format_fingerprints.py, EDIT STRUCTURE THERE.""" from opendbc.car.structs import CarParams from opendbc.car.tesla.values import CAR +from opendbc.sunnypilot.car.fingerprints_ext import merge_fw_versions +from opendbc.sunnypilot.car.tesla.fingerprints_ext import FW_VERSIONS_EXT Ecu = CarParams.Ecu @@ -48,3 +50,5 @@ FW_VERSIONS = { ], }, } + +FW_VERSIONS = merge_fw_versions(FW_VERSIONS, FW_VERSIONS_EXT) diff --git a/opendbc/sunnypilot/car/tesla/fingerprints_ext.py b/opendbc/sunnypilot/car/tesla/fingerprints_ext.py new file mode 100644 index 00000000..f0009db0 --- /dev/null +++ b/opendbc/sunnypilot/car/tesla/fingerprints_ext.py @@ -0,0 +1,12 @@ +from opendbc.car.structs import CarParams +from opendbc.car.tesla.values import CAR + +Ecu = CarParams.Ecu + +FW_VERSIONS_EXT = { + CAR.TESLA_MODEL_3: { + (Ecu.eps, 0x730, None): [ + b'TeMYG4_Main_0.0.0 (67),E4HP015.02.1', + ], + }, +} From e47b4d3c8ee7b164252b659b33ab9314af8178b5 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 13 Feb 2026 21:59:11 -0500 Subject: [PATCH 3/6] Toyota: do not disable radar when smartDSU or CAN Filter detected (#399) * Toyota: do not disable radar when smartDSU or CAN Filter detected * gate it * Revert "gate it" This reverts commit de21aa77e5d306018841102a0ebca71f2d405187. * need this --- opendbc/car/toyota/carstate.py | 3 ++- opendbc/car/toyota/interface.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/opendbc/car/toyota/carstate.py b/opendbc/car/toyota/carstate.py index fc5e41c8..4986a6ff 100644 --- a/opendbc/car/toyota/carstate.py +++ b/opendbc/car/toyota/carstate.py @@ -152,7 +152,8 @@ class CarState(CarStateBase, CarStateExt): ret.cruiseState.speedCluster = cluster_set_speed * conversion_factor if self.CP.carFingerprint in TSS2_CAR and not self.CP.flags & ToyotaFlags.DISABLE_RADAR.value: - self.acc_type = cp_acc.vl["ACC_CONTROL"]["ACC_TYPE"] + if not (self.CP_SP.flags & ToyotaFlagsSP.SMART_DSU.value): + self.acc_type = cp_acc.vl["ACC_CONTROL"]["ACC_TYPE"] ret.stockFcw = bool(cp_acc.vl["PCS_HUD"]["FCW"]) # some TSS2 cars have low speed lockout permanently set, so ignore on those cars diff --git a/opendbc/car/toyota/interface.py b/opendbc/car/toyota/interface.py index 537485b7..d2aeabfb 100644 --- a/opendbc/car/toyota/interface.py +++ b/opendbc/car/toyota/interface.py @@ -176,6 +176,7 @@ class CarInterface(CarInterfaceBase): stock_cp.flags |= ToyotaFlags.DISABLE_RADAR.value else: use_sdsu = use_sdsu and alpha_long + stock_cp.flags &= ~ToyotaFlags.DISABLE_RADAR.value # openpilot longitudinal enabled by default: # - non-(TSS2 radar ACC cars) w/ smartDSU installed From b39eb5ab918ad53e78708976148120df16334d48 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Mon, 16 Feb 2026 01:46:29 -0500 Subject: [PATCH 4/6] Toyota: move Enforce Factory Longitudinal Control checks to sunnypilot interface (#411) * Toyota: move Enforce Factory Longitudinal Control checks to sunnypilot interface * hmm * back --- opendbc/car/toyota/interface.py | 4 ---- opendbc/sunnypilot/car/interfaces.py | 4 ++++ 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/opendbc/car/toyota/interface.py b/opendbc/car/toyota/interface.py index d2aeabfb..26bd1ce2 100644 --- a/opendbc/car/toyota/interface.py +++ b/opendbc/car/toyota/interface.py @@ -196,10 +196,6 @@ class CarInterface(CarInterfaceBase): ret.safetyParam |= ToyotaSafetyFlagsSP.GAS_INTERCEPTOR stock_cp.minEnableSpeed = -1. - if ret.flags & ToyotaFlagsSP.STOCK_LONGITUDINAL: - stock_cp.alphaLongitudinalAvailable = False - stock_cp.openpilotLongitudinalControl = False - if not stock_cp.openpilotLongitudinalControl: stock_cp.safetyConfigs[0].safetyParam |= ToyotaSafetyFlags.STOCK_LONGITUDINAL.value else: diff --git a/opendbc/sunnypilot/car/interfaces.py b/opendbc/sunnypilot/car/interfaces.py index 36fe1bf8..c0e1f4a3 100644 --- a/opendbc/sunnypilot/car/interfaces.py +++ b/opendbc/sunnypilot/car/interfaces.py @@ -13,6 +13,7 @@ from opendbc.car import structs from opendbc.car.can_definitions import CanRecvCallable, CanSendCallable from opendbc.car.hyundai.values import HyundaiFlags from opendbc.car.subaru.values import SubaruFlags +from opendbc.car.toyota.values import ToyotaSafetyFlags from opendbc.sunnypilot.car.hyundai.enable_radar_tracks import enable_radar_tracks as hyundai_enable_radar_tracks from opendbc.sunnypilot.car.hyundai.longitudinal.helpers import LongitudinalTuningType from opendbc.sunnypilot.car.hyundai.values import HyundaiFlagsSP @@ -138,3 +139,6 @@ def _initialize_toyota(CP: structs.CarParams, CP_SP: structs.CarParamsSP, params if toyota_stock_long: CP_SP.flags |= ToyotaFlagsSP.STOCK_LONGITUDINAL.value + CP.alphaLongitudinalAvailable = False + CP.openpilotLongitudinalControl = False + CP.safetyConfigs[0].safetyParam |= ToyotaSafetyFlags.STOCK_LONGITUDINAL.value From 32f841cd5608196ab6353efee618637063df35b3 Mon Sep 17 00:00:00 2001 From: MVL Date: Mon, 16 Feb 2026 02:10:25 -0500 Subject: [PATCH 5/6] Honda: add missing `GasInterceptor` messages to Taiwan Odyssey DBC (#409) Co-authored-by: Jason Wen --- opendbc/dbc/generator/honda/honda_odyssey_twn_2018.dbc | 1 + 1 file changed, 1 insertion(+) diff --git a/opendbc/dbc/generator/honda/honda_odyssey_twn_2018.dbc b/opendbc/dbc/generator/honda/honda_odyssey_twn_2018.dbc index 4dc4ea5c..b382ce5b 100644 --- a/opendbc/dbc/generator/honda/honda_odyssey_twn_2018.dbc +++ b/opendbc/dbc/generator/honda/honda_odyssey_twn_2018.dbc @@ -1,3 +1,4 @@ +CM_ "IMPORT _community.dbc"; CM_ "IMPORT _honda_common.dbc"; CM_ "IMPORT _nidec_common.dbc"; CM_ "IMPORT _lkas_hud_5byte.dbc"; From 8289577096cf303e3f8c53a86d0e7355651b53b5 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Mon, 16 Feb 2026 03:01:30 -0500 Subject: [PATCH 6/6] GM: remove `CHEVROLET_EQUINOX_NON_ACC_3RD_GEN` from `dashcamOnly` (#412) * GM: remove `CHEVROLET_EQUINOX_NON_ACC_3RD_GEN` from `dashcamOnly` * oops * docs --- docs/CARS.md | 2 +- opendbc/car/gm/interface.py | 5 ++--- opendbc/sunnypilot/car/car_list.json | 13 +++++++++++++ 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/docs/CARS.md b/docs/CARS.md index b3be58c4..3e196ced 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -41,7 +41,7 @@ |Chevrolet|Bolt EV Non-ACC 2017|No Adaptive Cruise Control (Non-ACC)|[Community](community)| |Chevrolet|Bolt EV Non-ACC 2018-21|No Adaptive Cruise Control (Non-ACC)|[Community](community)| |Chevrolet|Equinox 2019-22|Adaptive Cruise Control (ACC)|[Upstream](#upstream)| -|Chevrolet|Equinox Non-ACC 2019-22|No Adaptive Cruise Control (Non-ACC)|[Dashcam mode](#dashcam)| +|Chevrolet|Equinox Non-ACC 2019-22|No Adaptive Cruise Control (Non-ACC)|[Community](community)| |Chevrolet|Malibu Non-ACC 2016-23|No Adaptive Cruise Control (Non-ACC)|[Community](community)| |Chevrolet|Silverado 1500 2020-21|Safety Package II|[Upstream](#upstream)| |Chevrolet|Suburban Non-ACC 2016-20|No Adaptive Cruise Control (Non-ACC)|[Dashcam mode](#dashcam)| diff --git a/opendbc/car/gm/interface.py b/opendbc/car/gm/interface.py index 4e64ca20..85eec033 100755 --- a/opendbc/car/gm/interface.py +++ b/opendbc/car/gm/interface.py @@ -260,9 +260,8 @@ class CarInterface(CarInterfaceBase, CarInterfaceExt): stock_cp.minSteerSpeed = 3.0 # ~6 mph # dashcamOnly platforms: untested platforms, need user validations - if candidate in (CAR.CHEVROLET_BOLT_NON_ACC_2ND_GEN, CAR.CHEVROLET_EQUINOX_NON_ACC_3RD_GEN, - CAR.CHEVROLET_SUBURBAN_NON_ACC_11TH_GEN, CAR.CADILLAC_CT6_NON_ACC_1ST_GEN, CAR.CHEVROLET_TRAILBLAZER_NON_ACC_2ND_GEN, - CAR.CADILLAC_XT5_NON_ACC_1ST_GEN): + if candidate in (CAR.CHEVROLET_BOLT_NON_ACC_2ND_GEN, CAR.CHEVROLET_SUBURBAN_NON_ACC_11TH_GEN, CAR.CADILLAC_CT6_NON_ACC_1ST_GEN, + CAR.CHEVROLET_TRAILBLAZER_NON_ACC_2ND_GEN, CAR.CADILLAC_XT5_NON_ACC_1ST_GEN): stock_cp.dashcamOnly = True return ret diff --git a/opendbc/sunnypilot/car/car_list.json b/opendbc/sunnypilot/car/car_list.json index f065b96a..8d788e15 100644 --- a/opendbc/sunnypilot/car/car_list.json +++ b/opendbc/sunnypilot/car/car_list.json @@ -259,6 +259,19 @@ ], "package": "Adaptive Cruise Control (ACC)" }, + "Chevrolet Equinox Non-ACC 2019-22": { + "platform": "CHEVROLET_EQUINOX_NON_ACC_3RD_GEN", + "make": "Chevrolet", + "brand": "gm", + "model": "Equinox Non-ACC", + "year": [ + "2019", + "2020", + "2021", + "2022" + ], + "package": "No Adaptive Cruise Control (Non-ACC)" + }, "Chevrolet Malibu Non-ACC 2016-23": { "platform": "CHEVROLET_MALIBU_NON_ACC_9TH_GEN", "make": "Chevrolet",