food & stuff

This commit is contained in:
firestar5683
2026-07-15 11:20:09 -05:00
parent b0334c2ae5
commit d4cfd34291
46 changed files with 2280 additions and 257 deletions
Binary file not shown.
+1 -2
View File
@@ -349,8 +349,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"HigherBitrate", {PERSISTENT, BOOL, "0", "0", 2}},
{"HolidayThemes", {PERSISTENT, BOOL, "1", "0", 0}},
{"HumanAcceleration", {PERSISTENT, BOOL, "0", "0", 2}},
{"CoastUpToLeads", {PERSISTENT, BOOL, "1", "1", 2}},
{"PrioritizeSmoothFollowing", {PERSISTENT, BOOL, "0", "0", 2}},
{"HumanLaneChanges", {PERSISTENT, BOOL, "0", "0", 2}},
{"IconPack", {PERSISTENT, STRING, "stock", "stock", 0}},
{"IconToDownload", {CLEAR_ON_MANAGER_START, STRING, "", ""}},
@@ -610,6 +608,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"SubaruSNG", {PERSISTENT, BOOL, "1", "0", 2}},
{"SubaruSNGManualParkingBrake", {PERSISTENT, BOOL, "0", "0", 2}},
{"TacoTune", {PERSISTENT, BOOL, "0", "0", 2}},
{"TeslaCoopSteering", {PERSISTENT, BOOL, "0", "0", 2}},
{"TestAlert", {CLEAR_ON_MANAGER_START, STRING, "", ""}},
{"TetheringEnabled", {PERSISTENT, INT, "0", "0", 0}},
{"ThemeDownloadProgress", {CLEAR_ON_MANAGER_START, STRING, "", ""}},
Binary file not shown.
+2 -2
View File
@@ -66,9 +66,9 @@ TRUCK_LONG_SMOOTH_CARS = {
CAR.CHEVROLET_SILVERADO,
CAR.CHEVROLET_SILVERADO_CC,
}
TRUCK_FRICTION_BRAKE_ENGAGE = 25
TRUCK_FRICTION_BRAKE_ENGAGE = 40
TRUCK_FRICTION_BRAKE_RELEASE = 8
TRUCK_FRICTION_BRAKE_IMMEDIATE_ACCEL = -0.65
TRUCK_FRICTION_BRAKE_IMMEDIATE_ACCEL = -0.85
ACC_DASHBOARD_ZERO_RESERVED_CARS = {
CAR.CHEVROLET_BLAZER,
CAR.CHEVROLET_EQUINOX,
@@ -866,13 +866,14 @@ def test_shape_truck_friction_brake_suppresses_boundary_chatter():
def test_shape_truck_friction_brake_uses_hysteresis_once_engaged():
assert shape_truck_friction_brake(25, -0.3, False, False) == (25, True)
assert shape_truck_friction_brake(39, -0.3, False, False) == (0, False)
assert shape_truck_friction_brake(40, -0.3, False, False) == (40, True)
assert shape_truck_friction_brake(14, -0.3, False, True) == (14, True)
assert shape_truck_friction_brake(8, -0.3, False, True) == (0, False)
def test_shape_truck_friction_brake_never_delays_meaningful_braking():
assert shape_truck_friction_brake(5, -0.65, False, False) == (5, True)
assert shape_truck_friction_brake(5, -0.85, False, False) == (5, True)
assert shape_truck_friction_brake(5, -0.2, True, False) == (5, True)
@@ -176,6 +176,8 @@ class CarInterface(CarInterfaceBase):
if ret.flags & HyundaiFlags.CAMERA_SCC:
ret.safetyConfigs[0].safetyParam |= HyundaiSafetyFlags.CAMERA_SCC.value
if candidate in (CAR.HYUNDAI_ELANTRA_2024, CAR.HYUNDAI_ELANTRA_HEV_2024):
ret.safetyConfigs[-1].safetyParam |= HyundaiSafetyFlags.CAN_REFRESH_MSGS.value
# These cars expose an LKAS/LFA steering-wheel button that StarPilot can customize.
if 0x391 in fingerprint[0] or ret.flags & HyundaiFlags.CAN_CANFD_BLENDED:
@@ -368,6 +368,13 @@ class TestHyundaiFingerprint:
assert palisade_2023.safetyConfigs[-1].safetyParam & HyundaiSafetyFlags.CAN_CANFD_BLENDED
assert palisade_2023.safetyConfigs[-1].safetyParam & HyundaiSafetyFlags.CANCEL_BTN_ENABLE
@pytest.mark.parametrize("candidate", (CAR.HYUNDAI_ELANTRA_2024, CAR.HYUNDAI_ELANTRA_HEV_2024))
def test_hyundai_can_refresh_platforms_use_refresh_dbc_and_safety_param(self, candidate):
CP = CarInterface.get_params(candidate, gen_empty_fingerprint(), [], False, False, False, None)
assert DBC[CP.carFingerprint][Bus.pt] == "hyundai_can_refresh_generated"
assert CP.safetyConfigs[-1].safetyParam & HyundaiSafetyFlags.CAN_REFRESH_MSGS
def test_hyundai_lkas_button_sets_starpilot_safety_flag(self):
fingerprint = gen_empty_fingerprint()
fingerprint[0][0x391] = 8
+8 -2
View File
@@ -103,6 +103,7 @@ class HyundaiSafetyFlags(IntFlag):
NON_SCC = 4096
CAN_CANFD_BLENDED = 8192
CANCEL_BTN_ENABLE = 16384
CAN_REFRESH_MSGS = 32768
CCNC = 32768
@@ -218,6 +219,11 @@ class HyundaiPlatformConfig(PlatformConfig):
self.dbc_dict = {Bus.pt: "hyundai_palisade_2023_generated"}
@dataclass
class HyundaiRefreshPlatformConfig(HyundaiPlatformConfig):
dbc_dict: DbcDict = field(default_factory=lambda: {Bus.pt: "hyundai_can_refresh_generated"})
@dataclass
class HyundaiCanFDPlatformConfig(PlatformConfig):
dbc_dict: DbcDict = field(default_factory=lambda: {Bus.pt: "hyundai_canfd_generated"})
@@ -289,7 +295,7 @@ class CAR(Platforms):
CarSpecs(mass=2800 * CV.LB_TO_KG, wheelbase=2.72, steerRatio=12.9, tireStiffnessFactor=0.65),
flags=HyundaiFlags.CHECKSUM_CRC8,
)
HYUNDAI_ELANTRA_2024 = HyundaiPlatformConfig(
HYUNDAI_ELANTRA_2024 = HyundaiRefreshPlatformConfig(
[HyundaiCarDocs("Hyundai Elantra 2024-25", car_parts=CarParts.common([CarHarness.hyundai_k]))],
CarSpecs(mass=2797 * CV.LB_TO_KG, wheelbase=2.72, steerRatio=12.9, tireStiffnessFactor=0.65),
flags=HyundaiFlags.CHECKSUM_CRC8 | HyundaiFlags.CAMERA_SCC,
@@ -300,7 +306,7 @@ class CAR(Platforms):
CarSpecs(mass=3017 * CV.LB_TO_KG, wheelbase=2.72, steerRatio=12.9, tireStiffnessFactor=0.65),
flags=HyundaiFlags.CHECKSUM_CRC8 | HyundaiFlags.HYBRID,
)
HYUNDAI_ELANTRA_HEV_2024 = HyundaiPlatformConfig(
HYUNDAI_ELANTRA_HEV_2024 = HyundaiRefreshPlatformConfig(
[
HyundaiCarDocs("Hyundai Elantra Hybrid 2024-26", car_parts=CarParts.common([CarHarness.hyundai_k])),
HyundaiCarDocs("Hyundai i30 Hybrid 2024", car_parts=CarParts.common([CarHarness.hyundai_k])),
@@ -3,10 +3,11 @@ from opendbc.can import CANPacker
from opendbc.car import Bus
from opendbc.car.lateral import apply_steer_angle_limits_vm
from opendbc.car.interfaces import CarControllerBase
from opendbc.car.tesla.coop_steering import CooperativeSteeringController
from opendbc.car.tesla.teslacan import TeslaCAN
from opendbc.car.tesla.preap.carcontroller import PreAPLongController, init_preap_can
from opendbc.car.tesla.preap.stock_cc_spoofer import StockCCSpoofer
from opendbc.car.tesla.values import CANBUS, CAR, CarControllerParams
from opendbc.car.tesla.values import CANBUS, CAR, CarControllerParams, TeslaSafetyFlags
from opendbc.car.vehicle_model import VehicleModel
def get_safety_CP():
@@ -18,6 +19,11 @@ class CarController(CarControllerBase):
def __init__(self, dbc_names, CP):
super().__init__(dbc_names, CP)
self.apply_angle_last = 0
self.apply_angle_command_last = 0
self.coop_steer = CooperativeSteeringController()
self.coop_enabled = CP.carFingerprint == CAR.TESLA_MODEL_3 and any(
config.safetyParam & TeslaSafetyFlags.COOP_STEERING.value for config in CP.safetyConfigs
)
self.packer = CANPacker(dbc_names[Bus.party])
self.tesla_can = TeslaCAN(self.packer)
self.preap_long = None
@@ -40,17 +46,18 @@ class CarController(CarControllerBase):
actuators = CC.actuators
can_sends = []
# Tesla EPS enforces disabling steering on heavy lateral override force.
# When enabling in a tight curve, we wait until user reduces steering force to start steering.
# Canceling is done on rising edge and is handled generically with CC.cruiseControl.cancel
lat_active = CC.latActive and CS.hands_on_level < 3
# Preserve the stock controller path unless cooperative steering is explicitly enabled.
lat_active = CC.latActive and (not CS.out.steeringDisengage if self.coop_enabled else CS.hands_on_level < 3)
if self.frame % 2 == 0:
# Angular rate limit based on speed
self.apply_angle_last = apply_steer_angle_limits_vm(actuators.steeringAngleDeg, self.apply_angle_last, CS.out.vEgoRaw, CS.out.steeringAngleDeg,
lat_active, CarControllerParams, self.VM)
can_sends.append(self.tesla_can.create_steering_control(self.apply_angle_last, lat_active))
self.apply_angle_command_last, lat_active = self.coop_steer.update(
self.apply_angle_last, lat_active, self.coop_enabled, CS, self.VM,
)
can_sends.append(self.tesla_can.create_steering_control(self.apply_angle_command_last, lat_active))
if self.frame % 10 == 0:
can_sends.append(self.tesla_can.create_steering_allowed())
@@ -71,7 +78,7 @@ class CarController(CarControllerBase):
# TODO: HUD control
new_actuators = actuators.as_builder()
new_actuators.steeringAngleDeg = self.apply_angle_last
new_actuators.steeringAngleDeg = self.apply_angle_command_last
self.frame += 1
return new_actuators, can_sends
+9 -3
View File
@@ -4,7 +4,7 @@ from opendbc.can import CANDefine, CANParser
from opendbc.car import Bus, structs
from opendbc.car.common.conversions import Conversions as CV
from opendbc.car.interfaces import CarStateBase
from opendbc.car.tesla.values import DBC, CANBUS, GEAR_MAP, STEER_THRESHOLD, CAR
from opendbc.car.tesla.values import DBC, CANBUS, GEAR_MAP, STEER_DISENGAGE_THRESHOLD, STEER_THRESHOLD, TeslaSafetyFlags, CAR
from opendbc.car.tesla.preap.carstate import get_preap_can_parsers, update_preap
from opendbc.car.tesla.preap.engagement import PreAPEngagement
from opendbc.car.tesla.preap.nap_conf import nap_conf
@@ -30,6 +30,9 @@ class CarState(CarStateBase):
self.prev_cruise_buttons = 0
self.msg_stw_actn_req = None
self.speed_units = "MPH"
self.cooperative_steering = any(
config.safetyParam & TeslaSafetyFlags.COOP_STEERING.value for config in CP.safetyConfigs
)
if CP.carFingerprint == CAR.TESLA_MODEL_S_PREAP:
self.engagement = PreAPEngagement(nap_conf.double_pull_enabled, nap_conf.double_pull_window_ms)
@@ -95,8 +98,11 @@ class CarState(CarStateBase):
# FSD disengages using union of handsOnLevel (slow overrides) and high angle rate faults (fast overrides, high speed)
eac_error_code = self.can_define.dv["EPAS3S_sysStatus"]["EPAS3S_eacErrorCode"].get(int(epas_status["EPAS3S_eacErrorCode"]), None)
ret.steeringDisengage = self.hands_on_level >= 3 or (eac_status == "EAC_INHIBITED" and
eac_error_code == "EAC_ERROR_HIGH_ANGLE_RATE_SAFETY")
ret.steeringDisengage = (
self.hands_on_level >= 3 or
(eac_status == "EAC_INHIBITED" and eac_error_code == "EAC_ERROR_HIGH_ANGLE_RATE_SAFETY") or
(self.cooperative_steering and abs(ret.steeringTorque) > STEER_DISENGAGE_THRESHOLD)
)
# Cruise state
cruise_state = self.can_define.dv["DI_state"]["DI_cruiseState"].get(int(cp_party.vl["DI_state"]["DI_cruiseState"]), None)
@@ -0,0 +1,133 @@
import math
import numpy as np
from opendbc.car import DT_CTRL, rate_limit
from opendbc.car.lateral import apply_steer_angle_limits_vm
from opendbc.car.tesla.values import CarControllerParams
from opendbc.car.vehicle_model import VehicleModel
DT_LAT_CTRL = DT_CTRL * CarControllerParams.STEER_STEP
STEER_RESUME_RATE_LIMIT_RAMP_RATE = 300.0 # deg/s^2
STEER_OVERRIDE_MIN_TORQUE = 0.5 # Nm
STEER_OVERRIDE_MAX_TORQUE = 2.5 # Nm
STEER_OVERRIDE_TORQUE_RANGE = STEER_OVERRIDE_MAX_TORQUE - STEER_OVERRIDE_MIN_TORQUE
STEER_OVERRIDE_MAX_LAT_ACCEL = 2.0 # m/s^2
STEER_OVERRIDE_DELTA_GAIN_LIMIT = 125.0 # deg/s/Nm
def apply_bounds(signal: float, limit: float) -> float:
return float(np.clip(signal, -limit, limit))
def apply_deadzone(signal: float, deadzone: float) -> float:
return signal - apply_bounds(signal, deadzone)
def get_steer_from_lat_accel(lat_accel: float, v_ego: float, VM: VehicleModel) -> float:
curvature = lat_accel / max(1.0, v_ego) ** 2
return math.degrees(VM.get_steer_from_curvature(curvature, v_ego, 0.0))
def get_override_torque_to_angle(v_ego: float, VM: VehicleModel) -> float:
max_angle = CarControllerParams.ANGLE_LIMITS.STEER_ANGLE_MAX
steer_from_lat_accel = apply_bounds(get_steer_from_lat_accel(STEER_OVERRIDE_MAX_LAT_ACCEL, v_ego, VM), max_angle)
return steer_from_lat_accel / STEER_OVERRIDE_TORQUE_RANGE
def calc_override_angle_delta_limit(torque: float) -> float:
max_gain = CarControllerParams.ANGLE_LIMITS.MAX_ANGLE_RATE / DT_LAT_CTRL / STEER_OVERRIDE_TORQUE_RANGE
return torque * min(STEER_OVERRIDE_DELTA_GAIN_LIMIT, max_gain) * DT_LAT_CTRL
class SteerRateLimiter:
def __init__(self):
self.last = 0.0
def reset(self, angle: float) -> None:
self.last = angle
def update(self, angle: float, angle_delta_limit: float) -> float:
limited = rate_limit(angle, self.last, -angle_delta_limit, angle_delta_limit)
self.last = limited
return limited
class CooperativeSteeringController:
def __init__(self):
self.apply_angle_last = 0.0
self.coop_apply_angle_last = 0.0
self.angle_override = 0.0
self.resume_rate_limiter_delta = SteerRateLimiter()
self.resume_rate_limiter = SteerRateLimiter()
def reset_override_state(self, apply_angle: float) -> None:
self.apply_angle_last = apply_angle
self.angle_override = 0.0
self.coop_apply_angle_last = apply_angle
def reset_resume_state(self, apply_angle: float) -> None:
self.resume_rate_limiter_delta.reset(0.0)
self.resume_rate_limiter.reset(apply_angle)
def update_override_angle(self, apply_angle_delta: float, driver_torque: float, v_ego: float, VM: VehicleModel) -> float:
driver_torque = apply_deadzone(driver_torque, STEER_OVERRIDE_MIN_TORQUE)
torque_to_angle = get_override_torque_to_angle(v_ego, VM)
target_angle = driver_torque * torque_to_angle
holding_torque = self.angle_override / torque_to_angle if abs(v_ego) > 0.1 else 0.0
torque_delta = driver_torque - holding_torque
angle_delta_limit = calc_override_angle_delta_limit(abs(torque_delta))
angle_override_delta = float(np.clip(target_angle - self.angle_override, -angle_delta_limit, angle_delta_limit))
# Avoid counting model-requested motion and driver-requested motion twice.
if angle_override_delta * apply_angle_delta > 0.0:
angle_override_delta -= apply_bounds(apply_angle_delta, abs(angle_override_delta))
self.angle_override += angle_override_delta
return self.angle_override
def unwind_override_angle(self, saturation_error: float) -> None:
if self.angle_override * saturation_error > 0.0:
self.angle_override -= apply_bounds(saturation_error, abs(self.angle_override))
def apply_resume_rate_limit(self, lat_active: bool, apply_angle: float) -> float:
if not lat_active:
self.reset_resume_state(apply_angle)
return apply_angle
angle_rate_delta = self.resume_rate_limiter_delta.update(
CarControllerParams.ANGLE_LIMITS.MAX_ANGLE_RATE,
STEER_RESUME_RATE_LIMIT_RAMP_RATE * DT_LAT_CTRL ** 2,
)
return self.resume_rate_limiter.update(apply_angle, angle_rate_delta)
def update(self, apply_angle: float, lat_active: bool, enabled: bool, CS, VM: VehicleModel) -> tuple[float, bool]:
if not enabled:
self.reset_resume_state(apply_angle)
self.reset_override_state(apply_angle)
return apply_angle, lat_active
apply_angle = self.apply_resume_rate_limit(lat_active, apply_angle)
if not lat_active:
self.reset_override_state(apply_angle)
return apply_angle, False
apply_angle_delta = apply_angle - self.apply_angle_last
self.apply_angle_last = apply_angle
apply_angle += self.update_override_angle(apply_angle_delta, CS.out.steeringTorque, CS.out.vEgo, VM)
limited_angle = apply_steer_angle_limits_vm(
apply_angle,
self.coop_apply_angle_last,
CS.out.vEgoRaw,
CS.out.steeringAngleDeg,
True,
CarControllerParams,
VM,
)
self.coop_apply_angle_last = limited_angle
self.unwind_override_angle(apply_angle - limited_angle)
return limited_angle, True
@@ -9,6 +9,7 @@ FW_VERSIONS = {
(Ecu.eps, 0x730, None): [
b'TeM3_E014p10_0.0.0 (16),E014.17.00',
b'TeM3_E014p10_0.0.0 (16),EL014.17.00',
b'TeM3_E014p10_0.0.0 (24),E014.20.2',
b'TeM3_ES014p11_0.0.0 (25),ES014.19.0',
b'TeMYG4_DCS_Update_0.0.0 (13),E4014.28.1',
b'TeMYG4_DCS_Update_0.0.0 (9),E4014.26.0',
@@ -18,6 +18,13 @@ class CarInterface(CarInterfaceBase):
return get_preap_accel_limits(current_speed)
return CarInterfaceBase.get_pid_accel_limits(CP, current_speed, cruise_speed)
@classmethod
def get_params(cls, candidate, fingerprint, car_fw, alpha_long, is_release, docs, starpilot_toggles):
ret = super().get_params(candidate, fingerprint, car_fw, alpha_long, is_release, docs, starpilot_toggles)
if candidate == CAR.TESLA_MODEL_3 and getattr(starpilot_toggles, "tesla_cooperative_steering", False):
ret.safetyConfigs[0].safetyParam |= TeslaSafetyFlags.COOP_STEERING.value
return ret
@staticmethod
def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, alpha_long, is_release, docs) -> structs.CarParams:
ret.brand = "tesla"
@@ -0,0 +1,75 @@
from types import SimpleNamespace
import pytest
from opendbc.car import gen_empty_fingerprint
from opendbc.car.tesla.carcontroller import CarController, get_safety_CP
from opendbc.car.tesla.coop_steering import CooperativeSteeringController
from opendbc.car.tesla.interface import CarInterface
from opendbc.car.tesla.values import CAR, DBC, CarControllerParams, TeslaSafetyFlags
from opendbc.car.vehicle_model import VehicleModel
def make_car_state(torque=0.0, speed=15.0, angle=0.0):
return SimpleNamespace(out=SimpleNamespace(
steeringTorque=torque,
steeringAngleDeg=angle,
vEgo=speed,
vEgoRaw=speed,
))
@pytest.fixture
def vehicle_model():
return VehicleModel(get_safety_CP())
def test_disabled_preserves_angle_command(vehicle_model):
controller = CooperativeSteeringController()
angle, lat_active = controller.update(12.5, True, False, make_car_state(torque=2.0), vehicle_model)
assert angle == 12.5
assert lat_active
def test_light_driver_torque_adjusts_angle(vehicle_model):
controller = CooperativeSteeringController()
angle = 0.0
for _ in range(10):
angle, lat_active = controller.update(0.0, True, True, make_car_state(torque=1.5), vehicle_model)
assert lat_active
assert angle > 0.0
assert angle <= CarControllerParams.ANGLE_LIMITS.STEER_ANGLE_MAX
def test_inactive_lateral_resets_override(vehicle_model):
controller = CooperativeSteeringController()
for _ in range(10):
controller.update(0.0, True, True, make_car_state(torque=1.5), vehicle_model)
angle, lat_active = controller.update(8.0, False, True, make_car_state(torque=1.5, angle=8.0), vehicle_model)
assert angle == 8.0
assert not lat_active
angle, lat_active = controller.update(8.0, True, True, make_car_state(angle=8.0), vehicle_model)
assert angle == 8.0
assert lat_active
@pytest.mark.parametrize(("candidate", "enabled", "expected"), (
(CAR.TESLA_MODEL_3, False, False),
(CAR.TESLA_MODEL_3, True, True),
(CAR.TESLA_MODEL_Y, True, False),
(CAR.TESLA_MODEL_S_PREAP, True, False),
))
def test_safety_flag_is_model_3_only(candidate, enabled, expected):
toggles = SimpleNamespace(tesla_cooperative_steering=enabled, trailer_load_kg=0.0)
params = CarInterface.get_params(candidate, gen_empty_fingerprint(), [], False, False, False, toggles)
has_flag = any(config.safetyParam & TeslaSafetyFlags.COOP_STEERING.value for config in params.safetyConfigs)
assert has_flag is expected
if candidate != CAR.TESLA_MODEL_S_PREAP:
assert CarController(DBC[candidate], params).coop_enabled is expected
+2
View File
@@ -130,6 +130,7 @@ class CarControllerParams:
class TeslaSafetyFlags(IntFlag):
LONG_CONTROL = 1
COOP_STEERING = 256
class TeslaFlags(IntFlag):
@@ -158,3 +159,4 @@ class CruiseButtons:
DBC = CAR.create_dbc_map()
STEER_THRESHOLD = 1
STEER_DISENGAGE_THRESHOLD = 5.0
+1
View File
@@ -237,6 +237,7 @@ routes = [
CarTestRoute("c2fd040a5e34f3ad/00000013--9211a52a3d", HYUNDAI.HYUNDAI_ELANTRA_2024),
CarTestRoute("715ac05b594e9c59/2021-06-20--16-21-07", HYUNDAI.HYUNDAI_ELANTRA_HEV_2021),
CarTestRoute("65ef8b49f9b0dd24/00000141--5c8720a01c", HYUNDAI.HYUNDAI_ELANTRA_HEV_2024),
CarTestRoute("07a48901db7b2503/000001a1--ad07872c4f", HYUNDAI.HYUNDAI_ELANTRA_HEV_2024),
CarTestRoute("07a48901db7b2503/0000000f--697d5906e8", HYUNDAI.HYUNDAI_ELANTRA_HEV_2024), # Hyundai i30 Hybrid 2024
CarTestRoute("7120aa90bbc3add7/2021-08-02--07-12-31", HYUNDAI.HYUNDAI_SONATA_HYBRID),
CarTestRoute("bc40c72b728178f2/00000006--ee76ae8c42", HYUNDAI.HYUNDAI_SONATA_HEV_2024),
@@ -79,6 +79,7 @@ def get_test_starpilot_toggles() -> SimpleNamespace:
sng_hack=False,
subaru_sng=False,
subaru_sng_manual_parking_brake=False,
tesla_cooperative_steering=False,
unlock_doors=False,
vEgoStopping=0.5,
volt_sng=False,
File diff suppressed because it is too large Load Diff
@@ -1481,7 +1481,7 @@ BO_ 905 SCC14: 8 SCC
SG_ ACCMode : 32|3@1+ (1,0) [0|7] "" CLU,HUD,LDWS_LKAS,ESC
SG_ ObjGap : 56|8@1+ (1,0) [0|255] "" CLU,HUD,ESC
BO_ 1157 LFAHDA_MFC: 8 XXX
BO_ 1157 LFAHDA_MFC: 4 XXX
SG_ HDA_USM : 0|2@1+ (1,0) [0|3] "" XXX
SG_ HDA_Active : 2|1@1+ (1,0) [0|1] "" XXX
SG_ HDA_Icon_State : 3|2@1+ (1,0) [0|3] "" XXX
+52 -15
View File
@@ -25,13 +25,13 @@ const LongitudinalLimits HYUNDAI_LONG_LIMITS = {
.min_accel = -350, // 1/100 m/s2
};
#define HYUNDAI_COMMON_TX_MSGS(scc_bus) \
{0x340, 0, 8, .check_relay = true}, /* LKAS11 Bus 0 */ \
{0x4F1, scc_bus, 4, .check_relay = false}, /* CLU11 Bus 0 (radar-SCC) or 2 (camera-SCC) */ \
{0x485, 0, 8, .check_relay = true}, /* LFAHDA_MFC Bus 0 */ \
#define HYUNDAI_COMMON_TX_MSGS(scc_bus, can_refresh) \
{0x340, 0, 8, .check_relay = true}, /* LKAS11 Bus 0 */ \
{0x4F1, scc_bus, 4, .check_relay = false}, /* CLU11 Bus 0 (radar-SCC) or 2 (camera-SCC) */ \
{0x485, 0, (can_refresh) ? 8 : 4, .check_relay = true}, /* LFAHDA_MFC Bus 0 */ \
#define HYUNDAI_LONG_COMMON_TX_MSGS(scc_bus) \
HYUNDAI_COMMON_TX_MSGS(scc_bus) \
#define HYUNDAI_LONG_COMMON_TX_MSGS(scc_bus, can_refresh) \
HYUNDAI_COMMON_TX_MSGS(scc_bus, can_refresh) \
{0x420, 0, 8, .check_relay = true}, /* SCC11 Bus 0 */ \
{0x421, 0, 8, .check_relay = true}, /* SCC12 Bus 0 */ \
{0x50A, 0, 8, .check_relay = true}, /* SCC13 Bus 0 */ \
@@ -67,11 +67,22 @@ const LongitudinalLimits HYUNDAI_LONG_LIMITS = {
{.msg = {{0x592U, 0, 8, 10U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}}, \
static const CanMsg HYUNDAI_TX_MSGS[] = {
HYUNDAI_COMMON_TX_MSGS(0)
HYUNDAI_COMMON_TX_MSGS(0, false)
};
static const CanMsg HYUNDAI_REFRESH_TX_MSGS[] = {
HYUNDAI_COMMON_TX_MSGS(0, true)
};
static const CanMsg HYUNDAI_LONG_TX_MSGS[] = {
HYUNDAI_LONG_COMMON_TX_MSGS(0)
HYUNDAI_LONG_COMMON_TX_MSGS(0, false)
{0x38D, 0, 8, .check_relay = false}, // FCA11 Bus 0
{0x483, 0, 8, .check_relay = false}, // FCA12 Bus 0
{0x7D0, 0, 8, .check_relay = false}, // radar UDS TX addr Bus 0 (for radar disable)
};
static const CanMsg HYUNDAI_LONG_REFRESH_TX_MSGS[] = {
HYUNDAI_LONG_COMMON_TX_MSGS(0, true)
{0x38D, 0, 8, .check_relay = false}, // FCA11 Bus 0
{0x483, 0, 8, .check_relay = false}, // FCA12 Bus 0
{0x7D0, 0, 8, .check_relay = false}, // radar UDS TX addr Bus 0 (for radar disable)
@@ -330,11 +341,19 @@ static bool hyundai_tx_hook(const CANPacket_t *msg) {
static safety_config hyundai_init(uint16_t param) {
static const CanMsg HYUNDAI_CAMERA_SCC_TX_MSGS[] = {
HYUNDAI_COMMON_TX_MSGS(2)
HYUNDAI_COMMON_TX_MSGS(2, false)
};
static const CanMsg HYUNDAI_CAMERA_SCC_REFRESH_TX_MSGS[] = {
HYUNDAI_COMMON_TX_MSGS(2, true)
};
static const CanMsg HYUNDAI_CAMERA_SCC_LONG_TX_MSGS[] = {
HYUNDAI_LONG_COMMON_TX_MSGS(2)
HYUNDAI_LONG_COMMON_TX_MSGS(2, false)
};
static const CanMsg HYUNDAI_CAMERA_SCC_LONG_REFRESH_TX_MSGS[] = {
HYUNDAI_LONG_COMMON_TX_MSGS(2, true)
};
static const CanMsg HYUNDAI_CAN_CANFD_BLENDED_TX_MSGS[] = {
@@ -403,11 +422,19 @@ static safety_config hyundai_init(uint16_t param) {
}
}
if (hyundai_camera_scc) {
SET_TX_MSGS(HYUNDAI_CAMERA_SCC_LONG_TX_MSGS, ret);
if (hyundai_can_refresh_msgs) {
SET_TX_MSGS(HYUNDAI_CAMERA_SCC_LONG_REFRESH_TX_MSGS, ret);
} else {
SET_TX_MSGS(HYUNDAI_CAMERA_SCC_LONG_TX_MSGS, ret);
}
} else if (hyundai_can_canfd_blended) {
SET_TX_MSGS(HYUNDAI_CAN_CANFD_BLENDED_LONG_TX_MSGS, ret);
} else {
SET_TX_MSGS(HYUNDAI_LONG_TX_MSGS, ret);
if (hyundai_can_refresh_msgs) {
SET_TX_MSGS(HYUNDAI_LONG_REFRESH_TX_MSGS, ret);
} else {
SET_TX_MSGS(HYUNDAI_LONG_TX_MSGS, ret);
}
}
} else if (hyundai_camera_scc) {
@@ -425,9 +452,14 @@ static safety_config hyundai_init(uint16_t param) {
};
if (hyundai_has_lda_button) {
ret = BUILD_SAFETY_CFG(hyundai_cam_scc_rx_checks_lda, HYUNDAI_CAMERA_SCC_TX_MSGS);
SET_RX_CHECKS(hyundai_cam_scc_rx_checks_lda, ret);
} else {
ret = BUILD_SAFETY_CFG(hyundai_cam_scc_rx_checks, HYUNDAI_CAMERA_SCC_TX_MSGS);
SET_RX_CHECKS(hyundai_cam_scc_rx_checks, ret);
}
if (hyundai_can_refresh_msgs) {
SET_TX_MSGS(HYUNDAI_CAMERA_SCC_REFRESH_TX_MSGS, ret);
} else {
SET_TX_MSGS(HYUNDAI_CAMERA_SCC_TX_MSGS, ret);
}
} else if (hyundai_can_canfd_blended) {
static RxCheck hyundai_can_canfd_blended_rx_checks[] = {
@@ -509,7 +541,11 @@ static safety_config hyundai_init(uint16_t param) {
HYUNDAI_LDA_BUTTON_ADDR_CHECK
};
SET_TX_MSGS(HYUNDAI_TX_MSGS, ret);
if (hyundai_can_refresh_msgs) {
SET_TX_MSGS(HYUNDAI_REFRESH_TX_MSGS, ret);
} else {
SET_TX_MSGS(HYUNDAI_TX_MSGS, ret);
}
if (hyundai_fcev_gas_signal) {
if (hyundai_has_lda_button) {
SET_RX_CHECKS(hyundai_fcev_rx_checks_lda, ret);
@@ -557,6 +593,7 @@ static safety_config hyundai_legacy_init(uint16_t param) {
hyundai_common_init(param);
hyundai_legacy = true;
hyundai_camera_scc = false;
hyundai_can_refresh_msgs = false;
return hyundai_longitudinal ? BUILD_SAFETY_CFG(hyundai_legacy_rx_checks, HYUNDAI_LONG_TX_MSGS) :
BUILD_SAFETY_CFG(hyundai_legacy_rx_checks, HYUNDAI_TX_MSGS);
}
@@ -57,6 +57,9 @@ bool hyundai_non_scc = false;
extern bool hyundai_cancel_button_enable;
bool hyundai_cancel_button_enable = false;
extern bool hyundai_can_refresh_msgs;
bool hyundai_can_refresh_msgs = false;
static uint8_t hyundai_last_button_interaction; // button messages since the user pressed an enable button
static bool acc_main_on_prev;
static bool acc_main_on_tx;
@@ -76,6 +79,7 @@ void hyundai_common_init(uint16_t param) {
const uint16_t HYUNDAI_PARAM_NON_SCC = 4096;
const uint16_t HYUNDAI_PARAM_CAN_CANFD_BLENDED = 8192;
const uint16_t HYUNDAI_PARAM_CANCEL_BTN_ENABLE = 16384;
const uint16_t HYUNDAI_PARAM_CAN_REFRESH_MSGS = 32768;
hyundai_ev_gas_signal = GET_FLAG(param, HYUNDAI_PARAM_EV_GAS);
hyundai_hybrid_gas_signal = !hyundai_ev_gas_signal && GET_FLAG(param, HYUNDAI_PARAM_HYBRID_GAS);
@@ -90,6 +94,7 @@ void hyundai_common_init(uint16_t param) {
hyundai_aol_lkas_on_engage = GET_FLAG(param, HYUNDAI_PARAM_AOL_LKAS_ON_ENGAGE);
hyundai_non_scc = GET_FLAG(param, HYUNDAI_PARAM_NON_SCC);
hyundai_cancel_button_enable = GET_FLAG(param, HYUNDAI_PARAM_CANCEL_BTN_ENABLE);
hyundai_can_refresh_msgs = GET_FLAG(param, HYUNDAI_PARAM_CAN_REFRESH_MSGS);
hyundai_last_button_interaction = HYUNDAI_PREV_BUTTON_SAMPLES;
acc_main_on_prev = false;
+11 -1
View File
@@ -3,8 +3,11 @@
#include "opendbc/safety/declarations.h"
static bool tesla_longitudinal = false;
static bool tesla_coop_steering = false;
static bool tesla_stock_aeb = false;
#define TESLA_STEERING_DISENGAGE_TORQUE 500 // cNm
// Only rising edges while controls are not allowed are considered for these systems:
// TODO: Only LKAS (non-emergency) is currently supported since we've only seen it
static bool tesla_stock_lkas = false;
@@ -101,11 +104,14 @@ static void tesla_rx_hook(const CANPacket_t *msg) {
update_sample(&angle_meas, angle_meas_new);
const int hands_on_level = msg->data[4] >> 6; // EPAS3S_handsOnLevel
const int torsion_bar_torque = (((msg->data[2] & 0x0FU) << 8) | msg->data[3]) - 2050; // 0.01 Nm
const int eac_status = msg->data[6] >> 5; // EPAS3S_eacStatus
const int eac_error_code = msg->data[2] >> 4; // EPAS3S_eacErrorCode
// Disengage on normal user override, or if high angle rate fault from user overriding extremely quickly
steering_disengage = (hands_on_level >= 3) || ((eac_status == 0) && (eac_error_code == 9));
steering_disengage = (hands_on_level >= 3) ||
(tesla_coop_steering && (SAFETY_ABS(torsion_bar_torque) > TESLA_STEERING_DISENGAGE_TORQUE)) ||
((eac_status == 0) && (eac_error_code == 9));
}
// Vehicle speed (DI_speed)
@@ -333,7 +339,11 @@ static safety_config tesla_init(uint16_t param) {
SAFETY_UNUSED(param);
#ifdef ALLOW_DEBUG
const uint16_t TESLA_FLAG_LONGITUDINAL_CONTROL = 1;
const uint16_t TESLA_FLAG_COOP_STEERING = 256;
tesla_longitudinal = GET_FLAG(param, TESLA_FLAG_LONGITUDINAL_CONTROL);
tesla_coop_steering = GET_FLAG(param, TESLA_FLAG_COOP_STEERING);
#else
tesla_coop_steering = false;
#endif
tesla_stock_aeb = false;
@@ -984,6 +984,7 @@ class SafetyTest(SafetyTestBase):
continue
if {attr, current_test}.issubset({'TestHyundaiLongitudinalSafety', 'TestHyundaiLongitudinalSafetyCameraSCC',
'TestHyundaiSafetyFCEVLong', 'TestHyundaiLongitudinalAolLkasOnEngageSafety',
'TestHyundaiSafetyCanRefreshLong', 'TestHyundaiSafetyCanRefreshLongCameraSCC',
'TestHyundaiCanCanfdBlendedLongitudinalSafety',
'TestHyundaiLegacyLongitudinalSafetyHEV'}):
continue
@@ -50,6 +50,7 @@ class TestHyundaiSafety(HyundaiButtonBase, common.CarSafetyTest, common.DriverTo
STANDSTILL_THRESHOLD = 12 # 0.375 kph
RELAY_MALFUNCTION_ADDRS = {0: (0x340, 0x485)} # LKAS11
FWD_BLACKLISTED_ADDRS = {2: [0x340, 0x485]}
LFAHDA_MFC_LEN = 4
MAX_RATE_UP = 3
MAX_RATE_DOWN = 7
@@ -116,6 +117,13 @@ class TestHyundaiSafety(HyundaiButtonBase, common.CarSafetyTest, common.DriverTo
values = {"CR_Lkas_StrToqReq": torque, "CF_Lkas_ActToi": steer_req}
return self.packer.make_can_msg_safety("LKAS11", 0, values)
def test_lfahda_mfc_tx_length(self):
self.safety.set_controls_allowed(1)
self.assertTrue(self._tx(common.make_msg(0, 0x485, self.LFAHDA_MFC_LEN)))
wrong_len = 8 if self.LFAHDA_MFC_LEN == 4 else 4
self.assertFalse(self._tx(common.make_msg(0, 0x485, wrong_len)))
def test_pcm_main_cruise_state_availability(self):
if self.safety.get_current_safety_param() & HyundaiSafetyFlags.LONG:
raise unittest.SkipTest("Longitudinal mode does not learn ACC main state from SCC11 RX")
@@ -162,6 +170,16 @@ class TestHyundaiSafetyCameraSCC(TestHyundaiSafety):
self.safety.init_tests()
class TestHyundaiSafetyCanRefresh(TestHyundaiSafety):
LFAHDA_MFC_LEN = 8
def setUp(self):
self.packer = CANPackerSafety("hyundai_can_refresh_generated")
self.safety = libsafety_py.libsafety
self.safety.set_safety_hooks(CarParams.SafetyModel.hyundai, HyundaiSafetyFlags.CAN_REFRESH_MSGS)
self.safety.init_tests()
class TestHyundaiSafetyNonScc(TestHyundaiSafety):
def setUp(self):
self.packer = CANPackerSafety("hyundai_kia_generic")
@@ -211,6 +229,7 @@ class TestHyundaiCanCanfdBlendedSafety(TestHyundaiSafety):
TX_MSGS = [[0x340, 0], [0x4F1, 0], [0x485, 0], [0x364, 0]]
RELAY_MALFUNCTION_ADDRS = {0: (0x340, 0x485, 0x364)}
FWD_BLACKLISTED_ADDRS = {2: [0x340, 0x485, 0x364]}
LFAHDA_MFC_LEN = 8
MAX_RATE_UP = 2
MAX_RATE_DOWN = 3
MAX_TORQUE_LOOKUP = [0], [404]
@@ -408,6 +427,36 @@ class TestHyundaiLongitudinalSafetyCameraSCC(HyundaiLongitudinalBase, TestHyunda
pass
class TestHyundaiSafetyCanRefreshCameraSCC(TestHyundaiSafetyCameraSCC):
LFAHDA_MFC_LEN = 8
def setUp(self):
self.packer = CANPackerSafety("hyundai_can_refresh_generated")
self.safety = libsafety_py.libsafety
self.safety.set_safety_hooks(CarParams.SafetyModel.hyundai, HyundaiSafetyFlags.CAMERA_SCC | HyundaiSafetyFlags.CAN_REFRESH_MSGS)
self.safety.init_tests()
class TestHyundaiSafetyCanRefreshLong(TestHyundaiLongitudinalSafety):
LFAHDA_MFC_LEN = 8
def setUp(self):
self.packer = CANPackerSafety("hyundai_can_refresh_generated")
self.safety = libsafety_py.libsafety
self.safety.set_safety_hooks(CarParams.SafetyModel.hyundai, HyundaiSafetyFlags.LONG | HyundaiSafetyFlags.CAN_REFRESH_MSGS)
self.safety.init_tests()
class TestHyundaiSafetyCanRefreshLongCameraSCC(TestHyundaiLongitudinalSafetyCameraSCC):
LFAHDA_MFC_LEN = 8
def setUp(self):
self.packer = CANPackerSafety("hyundai_can_refresh_generated")
self.safety = libsafety_py.libsafety
self.safety.set_safety_hooks(CarParams.SafetyModel.hyundai, HyundaiSafetyFlags.LONG | HyundaiSafetyFlags.CAMERA_SCC | HyundaiSafetyFlags.CAN_REFRESH_MSGS)
self.safety.init_tests()
class TestHyundaiSafetyFCEVLong(TestHyundaiLongitudinalSafety, TestHyundaiSafetyFCEV):
def setUp(self):
self.packer = CANPackerSafety("hyundai_kia_generic")
@@ -4,7 +4,7 @@ import unittest
import numpy as np
from opendbc.car.lateral import get_max_angle_delta_vm, get_max_angle_vm
from opendbc.car.tesla.values import CarControllerParams, TeslaSafetyFlags
from opendbc.car.tesla.values import CarControllerParams, STEER_DISENGAGE_THRESHOLD, TeslaSafetyFlags
from opendbc.car.tesla.carcontroller import get_safety_CP
from opendbc.car.structs import CarParams
from opendbc.car.vehicle_model import VehicleModel
@@ -78,8 +78,10 @@ class TestTeslaSafetyBase(common.CarSafetyTest, common.AngleSteeringSafetyTest,
self.__class__.cnt_angle_cmd += 1
return self.packer.make_can_msg_safety("DAS_steeringControl", bus, values)
def _angle_meas_msg(self, angle: float, hands_on_level: int = 0, eac_status: int = 1, eac_error_code: int = 0):
def _angle_meas_msg(self, angle: float, hands_on_level: int = 0, eac_status: int = 1, eac_error_code: int = 0,
torsion_bar_torque: float = 0.0):
values = {"EPAS3S_internalSAS": angle, "EPAS3S_handsOnLevel": hands_on_level,
"EPAS3S_torsionBarTorque": torsion_bar_torque,
"EPAS3S_eacStatus": eac_status, "EPAS3S_eacErrorCode": eac_error_code,
"EPAS3S_sysStatusCounter": self.cnt_epas % 16}
self.__class__.cnt_epas += 1
@@ -378,6 +380,26 @@ class TestTeslaStockSafety(TestTeslaSafetyBase):
self.assertFalse(self._tx(self._long_control_msg(0, acc_state=acc_state, accel_limits=(self.MIN_ACCEL, self.MAX_ACCEL))))
self.assertEqual(should_tx, self._tx(self._long_control_msg(0, acc_state=acc_state)))
def test_cooperative_steering_torque_disengage_flag(self):
torque_cases = (
(-STEER_DISENGAGE_THRESHOLD - 0.01, True),
(-STEER_DISENGAGE_THRESHOLD, False),
(STEER_DISENGAGE_THRESHOLD, False),
(STEER_DISENGAGE_THRESHOLD + 0.01, True),
)
for param, cooperative_steering in ((0, False), (TeslaSafetyFlags.COOP_STEERING, True)):
self.safety.set_safety_hooks(CarParams.SafetyModel.tesla, param)
self.safety.init_tests()
for torsion_bar_torque, exceeds_threshold in torque_cases:
self.safety.set_controls_allowed(True)
should_disengage = cooperative_steering and exceeds_threshold
self.assertTrue(self._rx(self._angle_meas_msg(0, torsion_bar_torque=torsion_bar_torque)))
self.assertNotEqual(should_disengage, self.safety.get_controls_allowed())
self.assertEqual(should_disengage, self.safety.get_steering_disengage_prev())
def test_no_aeb(self):
for aeb_event in range(4):
should_tx = aeb_event == 0
@@ -73,16 +73,14 @@ def load_route_bookmarks(clip_root: Path, log_id: str) -> list[dict]:
route_start_monotime = None
raw_events: list[tuple[float, str]] = []
for segment_dir in segment_dirs:
rlog_path = segment_dir / "rlog.zst"
if not rlog_path.exists():
rlog_path = segment_dir / "rlog.bz2"
if not rlog_path.exists():
log_path = next((segment_dir / name for name in ("rlog.zst", "rlog.bz2", "qlog.zst", "qlog.bz2") if (segment_dir / name).exists()), None)
if log_path is None:
continue
try:
events = list(log.Event.read_multiple_bytes(read_log_bytes(rlog_path)))
events = list(log.Event.read_multiple_bytes(read_log_bytes(log_path)))
except Exception as exc:
print(f"{segment_dir.name}: skipping unreadable rlog {rlog_path.name}: {exc}")
print(f"{segment_dir.name}: skipping unreadable log {log_path.name}: {exc}")
continue
if not events:
continue
+79 -38
View File
@@ -177,6 +177,9 @@ VISION_UNTRACKED_APPROACH_LIFT_TRIGGER_TIME = 20.0
VISION_UNTRACKED_APPROACH_LIFT_FULL_TIME = 6.0
VISION_UNTRACKED_APPROACH_LIFT_MAX_ACCEL = 0.22
VISION_UNTRACKED_APPROACH_LIFT_CONFIRM_TIME = 0.30
VISION_UNTRACKED_APPROACH_LIFT_HOLD_TIME = 0.75
VISION_UNTRACKED_APPROACH_LIFT_RATE_DOWN = 0.35
VISION_UNTRACKED_APPROACH_LIFT_RATE_UP = 0.25
VISION_SLOW_LEAD_MAX_SPEED = 5.0
VISION_SLOW_LEAD_MIN_CLOSING_SPEED = 1.5
VISION_SLOW_LEAD_TRIGGER_TTC = 4.5
@@ -626,6 +629,9 @@ class LongitudinalPlanner:
self.vision_lead_approach_confirm_t = 0.0
self.untracked_slow_lead_confirm_t = 0.0
self.untracked_vision_approach_lift_confirm_t = 0.0
self.untracked_vision_approach_lift_cap = None
self.untracked_vision_approach_lift_target = None
self.untracked_vision_approach_lift_hold_until = 0.0
self.manual_stop_resume_override_until = 0.0
self.lead_depart_accel_hold_until = 0.0
self.lead_depart_accel_hold_floor = None
@@ -940,6 +946,45 @@ class LongitudinalPlanner:
[0.0, VISION_UNTRACKED_APPROACH_LIFT_MAX_ACCEL],
))
def update_vision_untracked_approach_lift_cap(self, raw_cap, output_a_target, prev_output_a_target,
now_t, untracked):
if untracked and raw_cap is not None:
if self.untracked_vision_approach_lift_cap is None:
self.untracked_vision_approach_lift_confirm_t = min(
self.untracked_vision_approach_lift_confirm_t + self.dt,
VISION_UNTRACKED_APPROACH_LIFT_CONFIRM_TIME,
)
if self.untracked_vision_approach_lift_confirm_t >= VISION_UNTRACKED_APPROACH_LIFT_CONFIRM_TIME:
self.untracked_vision_approach_lift_cap = float(prev_output_a_target)
if self.untracked_vision_approach_lift_cap is not None:
self.untracked_vision_approach_lift_target = float(raw_cap)
self.untracked_vision_approach_lift_hold_until = now_t + VISION_UNTRACKED_APPROACH_LIFT_HOLD_TIME
elif self.untracked_vision_approach_lift_cap is None:
self.untracked_vision_approach_lift_confirm_t = 0.0
active_cap = self.untracked_vision_approach_lift_cap
if active_cap is None:
return None
holding = untracked and now_t < self.untracked_vision_approach_lift_hold_until
target = self.untracked_vision_approach_lift_target if holding else float(output_a_target)
if target is None:
target = float(output_a_target)
lower = active_cap - VISION_UNTRACKED_APPROACH_LIFT_RATE_DOWN * self.dt
upper = active_cap + VISION_UNTRACKED_APPROACH_LIFT_RATE_UP * self.dt
active_cap = float(np.clip(target, lower, upper))
self.untracked_vision_approach_lift_cap = active_cap
if not holding and active_cap >= float(output_a_target) - 1e-6:
self.untracked_vision_approach_lift_confirm_t = 0.0
self.untracked_vision_approach_lift_cap = None
self.untracked_vision_approach_lift_target = None
self.untracked_vision_approach_lift_hold_until = 0.0
return None
return active_cap
def get_vision_slow_stopped_lead_cap(self, lead, v_ego, accel_min, t_follow):
if lead is None or not lead.status or bool(getattr(lead, "radar", False)):
return None
@@ -2606,11 +2651,8 @@ class LongitudinalPlanner:
uncert_slope > UNCERT_SLOPE_TRIG or uncertainty >= UNCERT_MAG_TRIG
)
prioritize_smooth_following = bool(getattr(starpilot_toggles, "prioritize_smooth_following", False))
allow_complex_follow_logic = not prioritize_smooth_following
steady_follow_filter_floor = 0.0
if allow_complex_follow_logic and lead_one_active and desired_gap is not None and not panic_bypass:
if lead_one_active and desired_gap is not None and not panic_bypass:
lead_brake = max(0.0, -float(getattr(self.lead_one, "aLeadK", 0.0)))
lead_radar = bool(getattr(self.lead_one, "radar", False))
lead_prob = float(getattr(self.lead_one, "modelProb", 1.0 if lead_radar else 0.0))
@@ -2671,7 +2713,7 @@ class LongitudinalPlanner:
self.mpc.update(sm['radarState'], v_cruise, x, v, a, j,
sm['starpilotPlan'].dangerFactor, effective_t_follow,
personality=personality, tracking_lead=lead_control_active,
optional_far_lead_comfort=allow_complex_follow_logic)
optional_far_lead_comfort=True)
self.a_desired_trajectory_full = np.interp(CONTROL_N_T_IDX, T_IDXS_MPC, self.mpc.a_solution)
self.v_desired_trajectory = np.interp(CONTROL_N_T_IDX, T_IDXS_MPC, self.mpc.v_solution)
@@ -2697,7 +2739,7 @@ class LongitudinalPlanner:
self.v_desired_filter.x = self.v_desired_filter.x + self.dt * (self.a_desired + a_prev) / 2.0
# Anticipatory pre-brake to avoid "coming in hot" when closing on a lead
if allow_complex_follow_logic and lead_one_active:
if lead_one_active:
rel_v = max(0.0, v_ego - self.lead_one.vLead)
# dynamic time headway adds a small buffer when uncertainty is elevated
base_th = max(1.6, effective_t_follow)
@@ -2748,6 +2790,7 @@ class LongitudinalPlanner:
output_accel_min = comfort_output_accel_min
model_desired_accel = float(sm['modelV2'].action.desiredAcceleration)
raw_approach_lift_cap = None
if not tracking_lead:
approach_lift_caps = [
cap for cap in (
@@ -2756,16 +2799,7 @@ class LongitudinalPlanner:
) if cap is not None
]
if approach_lift_caps:
self.untracked_vision_approach_lift_confirm_t = min(
self.untracked_vision_approach_lift_confirm_t + self.dt,
VISION_UNTRACKED_APPROACH_LIFT_CONFIRM_TIME,
)
if self.untracked_vision_approach_lift_confirm_t >= VISION_UNTRACKED_APPROACH_LIFT_CONFIRM_TIME:
approach_lift_cap = min(approach_lift_caps)
self.a_desired = min(self.a_desired, approach_lift_cap)
output_a_target = min(output_a_target, approach_lift_cap)
else:
self.untracked_vision_approach_lift_confirm_t = 0.0
raw_approach_lift_cap = min(approach_lift_caps)
pretracking_vision_caps = []
for lead in (self.lead_one, self.lead_two):
@@ -2798,9 +2832,19 @@ class LongitudinalPlanner:
else:
self.untracked_slow_lead_confirm_t = 0.0
else:
self.untracked_vision_approach_lift_confirm_t = 0.0
self.untracked_slow_lead_confirm_t = 0.0
approach_lift_cap = self.update_vision_untracked_approach_lift_cap(
raw_approach_lift_cap,
output_a_target,
prev_output_a_target,
now_t,
not tracking_lead,
)
if approach_lift_cap is not None:
self.a_desired = min(self.a_desired, approach_lift_cap)
output_a_target = min(output_a_target, approach_lift_cap)
close_lead_caps = []
tracked_vision_approach_caps = []
vision_low_speed_stop_active = False
@@ -3077,7 +3121,7 @@ class LongitudinalPlanner:
if close_final_guard_caps:
close_final_guard_cap = min(close_final_guard_caps)
if allow_complex_follow_logic and lead_one_active:
if lead_one_active:
lead_catchup_accel_cap = self.get_lead_catchup_accel_cap(
self.lead_one,
scene_v_ego,
@@ -3098,17 +3142,15 @@ class LongitudinalPlanner:
if vision_brake_cap_active:
output_accel_min = min(output_accel_min, vision_cap_accel_min)
follow_control_lead = None
if allow_complex_follow_logic:
follow_control_lead = self.get_follow_control_lead(
lead_control_active,
scene_v_ego,
effective_t_follow,
allow_optional_far_lead_logic=True,
)
duplicate_vision_comfort_lead = self.get_duplicate_vision_comfort_lead(scene_v_ego) if allow_complex_follow_logic else None
follow_control_lead = self.get_follow_control_lead(
lead_control_active,
scene_v_ego,
effective_t_follow,
allow_optional_far_lead_logic=True,
)
duplicate_vision_comfort_lead = self.get_duplicate_vision_comfort_lead(scene_v_ego)
comfort_follow_lead = duplicate_vision_comfort_lead if duplicate_vision_comfort_lead is not None and follow_control_lead is not None else follow_control_lead
if allow_complex_follow_logic and follow_control_lead is not None and not panic_bypass:
if follow_control_lead is not None and not panic_bypass:
if not output_should_stop and not vision_low_speed_stop_active:
tracked_vision_model_brake_floor = self.get_tracked_vision_model_brake_floor(
follow_control_lead,
@@ -3121,11 +3163,10 @@ class LongitudinalPlanner:
self.a_desired = min(self.a_desired, tracked_vision_model_brake_floor)
output_a_target = min(output_a_target, tracked_vision_model_brake_floor)
if allow_complex_follow_logic:
matched_follow_brake_cap = self.get_matched_follow_brake_cap(follow_control_lead, scene_v_ego, effective_t_follow)
if matched_follow_brake_cap is not None:
self.a_desired = max(self.a_desired, matched_follow_brake_cap)
output_a_target = max(output_a_target, matched_follow_brake_cap)
matched_follow_brake_cap = self.get_matched_follow_brake_cap(follow_control_lead, scene_v_ego, effective_t_follow)
if matched_follow_brake_cap is not None:
self.a_desired = max(self.a_desired, matched_follow_brake_cap)
output_a_target = max(output_a_target, matched_follow_brake_cap)
if not close_lead_caps and not output_should_stop and not vision_low_speed_stop_active:
low_speed_transition_brake_cap = self.get_low_speed_follow_transition_brake_cap(
@@ -3142,13 +3183,13 @@ class LongitudinalPlanner:
comfort_lead = duplicate_vision_comfort_lead if duplicate_vision_comfort_lead is not None else (
self.lead_two if self.mpc.source == 'lead1' and self.lead_two.status else self.lead_one
)
if allow_complex_follow_logic and comfort_lead is not None and not panic_bypass:
if comfort_lead is not None and not panic_bypass:
far_lead_brake_cap = self.get_far_lead_brake_cap(comfort_lead, scene_v_ego, effective_t_follow)
if far_lead_brake_cap is not None:
self.a_desired = max(self.a_desired, far_lead_brake_cap)
output_a_target = max(output_a_target, far_lead_brake_cap)
if allow_complex_follow_logic and follow_control_lead is not None and not panic_bypass and not output_should_stop and not vision_low_speed_stop_active:
if follow_control_lead is not None and not panic_bypass and not output_should_stop and not vision_low_speed_stop_active:
tracked_vision_model_brake_cap = self.get_tracked_vision_model_brake_cap(
follow_control_lead,
scene_v_ego,
@@ -3159,7 +3200,7 @@ class LongitudinalPlanner:
self.a_desired = max(self.a_desired, tracked_vision_model_brake_cap)
output_a_target = max(output_a_target, tracked_vision_model_brake_cap)
if allow_complex_follow_logic and comfort_follow_lead is not None and not panic_bypass and not output_should_stop and not vision_low_speed_stop_active:
if comfort_follow_lead is not None and not panic_bypass and not output_should_stop and not vision_low_speed_stop_active:
matched_follow_transition_target = self.get_matched_follow_transition_target(
comfort_follow_lead,
scene_v_ego,
@@ -3176,7 +3217,7 @@ class LongitudinalPlanner:
self.a_desired = max(self.a_desired, matched_follow_transition_target)
output_a_target = matched_follow_transition_target
if allow_complex_follow_logic and comfort_lead is not None and not panic_bypass and not output_should_stop and not vision_low_speed_stop_active:
if comfort_lead is not None and not panic_bypass and not output_should_stop and not vision_low_speed_stop_active:
near_duplicate_transition_target = self.get_near_duplicate_lead_transition_target(
comfort_lead,
scene_v_ego,
@@ -3209,7 +3250,7 @@ class LongitudinalPlanner:
self.a_desired = max(self.a_desired, duplicate_slow_lead_brake_hold_target)
output_a_target = duplicate_slow_lead_brake_hold_target
if allow_complex_follow_logic and comfort_follow_lead is not None and not panic_bypass and not output_should_stop and not vision_low_speed_stop_active:
if comfort_follow_lead is not None and not panic_bypass and not output_should_stop and not vision_low_speed_stop_active:
cruise_tracking_lead_accel_cap = self.get_cruise_tracking_lead_accel_cap(
comfort_follow_lead,
scene_v_ego,
@@ -122,7 +122,7 @@ def make_sm(v_ego: float, desired_accel: float, min_accel: float, *, experimenta
}
def make_toggles(model_version: str = "v11", radar_takeoffs: bool = False, prioritize_smooth_following: bool = False):
def make_toggles(model_version: str = "v11", radar_takeoffs: bool = False):
return SimpleNamespace(
taco_tune=False,
classic_model=False,
@@ -131,7 +131,6 @@ def make_toggles(model_version: str = "v11", radar_takeoffs: bool = False, prior
stop_distance=6.0,
vEgoStopping=0.5,
radar_takeoffs=radar_takeoffs,
prioritize_smooth_following=prioritize_smooth_following,
)
@@ -410,6 +409,41 @@ def test_vision_untracked_approach_lift_ignores_unqualified_leads(lead):
assert planner.get_vision_untracked_approach_lift_cap(lead, v_ego, 1.4) is None
def test_vision_untracked_approach_lift_is_rate_limited_and_held():
CP = CarInterface.get_non_essential_params(CAR.HONDA_CIVIC)
planner = LongitudinalPlanner(CP, init_v=25.0, init_a=0.5)
now = 0.0
cap = None
for _ in range(6):
now += planner.dt
cap = planner.update_vision_untracked_approach_lift_cap(0.0, 0.5, 0.5, now, True)
assert cap is not None
assert 0.45 < cap < 0.5
held_cap = planner.update_vision_untracked_approach_lift_cap(None, 0.5, 0.5, now + planner.dt, True)
assert held_cap is not None
assert held_cap < cap
def test_vision_untracked_approach_lift_releases_after_hold_or_tracking():
CP = CarInterface.get_non_essential_params(CAR.HONDA_CIVIC)
planner = LongitudinalPlanner(CP, init_v=25.0, init_a=0.5)
now = 0.0
for _ in range(6):
now += planner.dt
planner.update_vision_untracked_approach_lift_cap(0.0, 0.5, 0.5, now, True)
previous_cap = planner.untracked_vision_approach_lift_cap
now += planner.dt
releasing_cap = planner.update_vision_untracked_approach_lift_cap(None, 0.5, 0.5, now, False)
assert releasing_cap is not None
assert releasing_cap > previous_cap
def test_far_opening_radar_brake_guard_removes_only_harmless_pulse():
v_ego = 28.9
CP = CarInterface.get_non_essential_params(CAR.HONDA_CIVIC)
@@ -3193,44 +3227,39 @@ def test_mild_follow_zero_cross_guard_skips_urgent_close_follow():
assert guarded is None
def test_prioritize_smooth_following_skips_post_097_follow_nudges():
def test_post_097_follow_logic_is_always_active():
v_ego = 16.2
CP = CarInterface.get_non_essential_params(CAR.HONDA_CIVIC)
lead = make_lead(status=True, d_rel=33.4, v_lead=16.0, a_lead=0.0, radar=False, model_prob=0.99, y_rel=0.12)
def run_once(prioritize_smooth_following: bool) -> list[str]:
planner = LongitudinalPlanner(CP, init_v=v_ego)
sm = make_sm(
v_ego,
desired_accel=0.8,
min_accel=-0.5,
experimental_mode=False,
tracking_lead=True,
lead_one=lead,
)
sm["starpilotPlan"].vCruise = v_ego + 8.0
planner = LongitudinalPlanner(CP, init_v=v_ego)
sm = make_sm(
v_ego,
desired_accel=0.8,
min_accel=-0.5,
experimental_mode=False,
tracking_lead=True,
lead_one=lead,
)
sm["starpilotPlan"].vCruise = v_ego + 8.0
calls = []
calls = []
def record(name, return_value=None):
def _inner(self, *args, **kwargs):
calls.append(name)
return return_value
return types.MethodType(_inner, planner)
def record(name, return_value=None):
def _inner(self, *args, **kwargs):
calls.append(name)
return return_value
return types.MethodType(_inner, planner)
planner.get_follow_control_lead = record("get_follow_control_lead", lead)
planner.get_lead_catchup_accel_cap = record("get_lead_catchup_accel_cap", None)
planner.get_tracked_vision_model_brake_floor = record("get_tracked_vision_model_brake_floor", None)
planner.get_low_speed_follow_transition_brake_cap = record("get_low_speed_follow_transition_brake_cap", None)
planner.get_tracked_vision_model_brake_cap = record("get_tracked_vision_model_brake_cap", None)
planner.get_cruise_tracking_lead_accel_cap = record("get_cruise_tracking_lead_accel_cap", None)
planner.get_cruise_tracking_lead_accel_transition_target = record("get_cruise_tracking_lead_accel_transition_target", None)
planner.get_follow_control_lead = record("get_follow_control_lead", lead)
planner.get_lead_catchup_accel_cap = record("get_lead_catchup_accel_cap", None)
planner.get_tracked_vision_model_brake_floor = record("get_tracked_vision_model_brake_floor", None)
planner.get_low_speed_follow_transition_brake_cap = record("get_low_speed_follow_transition_brake_cap", None)
planner.get_tracked_vision_model_brake_cap = record("get_tracked_vision_model_brake_cap", None)
planner.get_cruise_tracking_lead_accel_cap = record("get_cruise_tracking_lead_accel_cap", None)
planner.get_cruise_tracking_lead_accel_transition_target = record("get_cruise_tracking_lead_accel_transition_target", None)
planner.update(sm, make_toggles(prioritize_smooth_following=prioritize_smooth_following))
return calls
calls_default = run_once(False)
calls_smooth = run_once(True)
planner.update(sm, make_toggles())
expected_calls = {
"get_lead_catchup_accel_cap",
@@ -3242,8 +3271,7 @@ def test_prioritize_smooth_following_skips_post_097_follow_nudges():
"get_cruise_tracking_lead_accel_transition_target",
}
assert expected_calls.issubset(set(calls_default))
assert not expected_calls.intersection(set(calls_smooth))
assert expected_calls.issubset(set(calls))
def test_near_duplicate_lead_source_hysteresis_prefers_previous_source():
@@ -119,7 +119,7 @@ def test_lateral_resume_delay_ignores_signal_cycles_that_never_slow_enough(monke
planner.shutdown()
def test_prioritize_smooth_following_skips_radarless_follow_hold(monkeypatch):
def test_radarless_follow_hold_applies_to_tracked_vision_lead(monkeypatch):
planner = StarPilotPlanner(Path("/tmp/nonexistent"), DummyThemeManager())
try:
@@ -136,28 +136,10 @@ def test_prioritize_smooth_following_skips_radarless_follow_hold(monkeypatch):
radar=False,
)
planner.update_lead_status(27.5, stop_distance=6.0, prioritize_smooth_following=False)
planner.update_lead_status(27.5, stop_distance=6.0)
assert planner.radarless_follow_hold_until > 100.0
planner_smooth = StarPilotPlanner(Path("/tmp/nonexistent"), DummyThemeManager())
planner_smooth.model_length = 30.0
planner_smooth.tracking_lead = True
planner_smooth.starpilot_following.t_follow = 1.45
planner_smooth.lead_one = SimpleNamespace(
status=True,
dRel=46.0,
vLead=27.0,
aLeadK=0.0,
modelProb=0.98,
radar=False,
)
planner_smooth.update_lead_status(27.5, stop_distance=6.0, prioritize_smooth_following=True)
assert planner_smooth.radarless_follow_hold_until == 0.0
finally:
planner.shutdown()
if 'planner_smooth' in locals():
planner_smooth.shutdown()
def test_tracked_vision_lead_uses_exit_hysteresis_at_mid_speed():
@@ -177,8 +159,7 @@ def test_tracked_vision_lead_uses_exit_hysteresis_at_mid_speed():
radar=False,
)
assert planner.update_lead_status(16.8, stop_distance=6.0, prioritize_smooth_following=False)
assert planner.update_lead_status(16.8, stop_distance=6.0, prioritize_smooth_following=True)
assert planner.update_lead_status(16.8, stop_distance=6.0)
finally:
planner.shutdown()
@@ -198,6 +179,6 @@ def test_untracked_vision_lead_still_uses_strict_entry_gate():
radar=False,
)
assert not planner.update_lead_status(16.8, stop_distance=6.0, prioritize_smooth_following=False)
assert not planner.update_lead_status(16.8, stop_distance=6.0)
finally:
planner.shutdown()
+1 -1
View File
@@ -26,7 +26,7 @@ MIN_VEGO = 15.0
MIN_ABS_YAW_RATE = 0.0
MAX_YAW_RATE_SANITY_CHECK = 1.0
MIN_NCC = 0.95
MAX_LAG = 1.0
MAX_LAG = 0.65
MAX_LAG_STD = 0.1
MAX_LAT_ACCEL = 2.0
MAX_LAT_ACCEL_DIFF = 0.6
+11 -6
View File
@@ -7,7 +7,7 @@ import pytest
from cereal import messaging, log, car
from openpilot.selfdrive.locationd.lagd import LateralLagEstimator, retrieve_initial_lag, masked_normalized_cross_correlation, \
BLOCK_NUM_NEEDED, BLOCK_SIZE, MIN_OKAY_WINDOW_SEC
BLOCK_NUM_NEEDED, BLOCK_SIZE, MIN_OKAY_WINDOW_SEC, MAX_LAG
from openpilot.selfdrive.test.process_replay.migration import migrate, migrate_carParams
from openpilot.selfdrive.locationd.test.test_locationd_scenarios import TEST_ROUTE
from openpilot.common.params import Params
@@ -16,6 +16,7 @@ from openpilot.system.hardware import PC
MAX_ERR_FRAMES = 1
DT = 0.05
LAGD_MAX_LAG_FRAMES = int(round(MAX_LAG / DT))
def process_messages(estimator, lag_frames, n_frames, vego=20.0, rejection_threshold=0.0):
@@ -101,8 +102,9 @@ class TestLagd:
assert np.argmax(corr) in range(lag_frames - MAX_ERR_FRAMES, lag_frames + MAX_ERR_FRAMES + 1)
def test_empty_estimator(self):
mocked_CP = car.CarParams(steerActuatorDelay=0.8)
mocked_CP = car.CarParams(steerActuatorDelay=0.5)
estimator = LateralLagEstimator(mocked_CP, DT)
estimator.starpilot_toggles = SimpleNamespace(use_custom_steerActuatorDelay=False)
msg = estimator.get_msg(True)
assert msg.liveDelay.status == 'unestimated'
assert np.allclose(msg.liveDelay.lateralDelay, estimator.initial_lag)
@@ -111,10 +113,11 @@ class TestLagd:
assert msg.liveDelay.calPerc == 0
def test_estimator_basics(self, subtests):
for lag_frames in range(5):
for lag_frames in range(LAGD_MAX_LAG_FRAMES - 1):
with subtests.test(msg=f"lag_frames={lag_frames}"):
mocked_CP = car.CarParams(steerActuatorDelay=0.8)
mocked_CP = car.CarParams(steerActuatorDelay=0.5)
estimator = LateralLagEstimator(mocked_CP, DT, min_recovery_buffer_sec=0.0, min_yr=0.0)
estimator.starpilot_toggles = SimpleNamespace(use_custom_steerActuatorDelay=False)
process_messages(estimator, lag_frames, int(MIN_OKAY_WINDOW_SEC / DT) + BLOCK_NUM_NEEDED * BLOCK_SIZE)
msg = estimator.get_msg(True)
assert msg.liveDelay.status == 'estimated'
@@ -125,8 +128,10 @@ class TestLagd:
assert msg.liveDelay.calPerc == 100
def test_estimator_masking(self):
mocked_CP, lag_frames = car.CarParams(steerActuatorDelay=0.8), random.randint(1, 19)
mocked_CP = car.CarParams(steerActuatorDelay=0.5)
lag_frames = random.randint(1, LAGD_MAX_LAG_FRAMES - 1)
estimator = LateralLagEstimator(mocked_CP, DT, min_recovery_buffer_sec=0.0, min_yr=0.0, min_valid_block_count=1)
estimator.starpilot_toggles = SimpleNamespace(use_custom_steerActuatorDelay=False)
process_messages(estimator, lag_frames, (int(MIN_OKAY_WINDOW_SEC / DT) + BLOCK_SIZE) * 2, rejection_threshold=0.4)
msg = estimator.get_msg(True)
assert np.allclose(msg.liveDelay.lateralDelayEstimate, lag_frames * DT, atol=0.01)
@@ -136,7 +141,7 @@ class TestLagd:
@pytest.mark.skipif(PC, reason="only on device")
@pytest.mark.timeout(60)
def test_estimator_performance(self):
mocked_CP = car.CarParams(steerActuatorDelay=0.8)
mocked_CP = car.CarParams(steerActuatorDelay=0.5)
estimator = LateralLagEstimator(mocked_CP, DT)
ds = []
@@ -25,7 +25,6 @@ class Maneuver:
self.e2e = kwargs.get("e2e", False)
self.personality = kwargs.get("personality", 0)
self.force_decel = kwargs.get("force_decel", False)
self.prioritize_smooth_following = kwargs.get("prioritize_smooth_following", False)
self.duration = duration
self.title = title
@@ -42,7 +41,6 @@ class Maneuver:
e2e=self.e2e,
personality=self.personality,
force_decel=self.force_decel,
prioritize_smooth_following=self.prioritize_smooth_following,
)
valid = True
@@ -56,8 +56,7 @@ class Plant:
def __init__(self, lead_relevancy=False, speed=0.0, distance_lead=2.0,
enabled=True, only_lead2=False, only_radar=False, track_lead_with_gate=False,
e2e=False, personality=0, force_decel=False,
prioritize_smooth_following=False):
e2e=False, personality=0, force_decel=False):
self.rate = 1. / DT_MDL
current_prefix = os.environ.get("OPENPILOT_PREFIX")
@@ -92,7 +91,6 @@ class Plant:
self.e2e = e2e
self.personality = personality
self.force_decel = force_decel
self.prioritize_smooth_following = prioritize_smooth_following
self.tracking_lead_filter = FirstOrderFilter(0.0, 0.5, DT_MDL)
self.rk = Ratekeeper(self.rate, print_delay_threshold=100.0)
@@ -113,7 +111,6 @@ class Plant:
stop_distance=6.0,
longitudinalActuatorDelay=0.2,
vEgoStopping=0.5,
prioritize_smooth_following=self.prioritize_smooth_following,
)
@property
@@ -138,7 +138,7 @@ class LongitudinalManagerView(AetherSettingsView):
hero_data = [
{
"title": tr("Longitudinal Tuning"),
"desc": tr("Configure acceleration profiles, smooth following, lane changes, and route speed control."),
"desc": tr("Configure acceleration profiles, lane changes, and route speed control."),
"icon": "steering",
"color": "#8B5CF6",
"on_click": lambda: self._controller._navigate_to("tune")
@@ -619,11 +619,6 @@ class StarPilotLongitudinalLayout(_SettingsPage):
get_value=self._get_deceleration_profile_label,
on_click=self._show_deceleration_profile_selector,
visible=self._longitudinal_enabled),
SettingRow("PrioritizeSmoothFollowing", "toggle", tr_noop("Prioritize Smooth Following"),
subtitle=tr_noop("Disables the newer far-lead follow logic on cars that show lead-follow stutter. Tradeoff: it may react later in some edge-case lead approaches."),
get_state=lambda: self._params.get_bool("PrioritizeSmoothFollowing"),
set_state=lambda s: self._params.put_bool("PrioritizeSmoothFollowing", s),
visible=self._longitudinal_enabled),
SettingRow("HumanLaneChanges", "toggle", tr_noop("Human-Like Lane Changes"),
subtitle=tr_noop("Radar-informed behavior during lane changes."),
get_state=lambda: self._params.get_bool("HumanLaneChanges"),
@@ -958,7 +953,7 @@ class StarPilotLongitudinalLayout(_SettingsPage):
self,
[SettingSection(title="", rows=self._tune_rows)],
header_title=tr_noop("Longitudinal Tuning"),
header_subtitle=tr_noop("Configure acceleration profiles, smooth following, lane changes, and route speed control."),
header_subtitle=tr_noop("Configure acceleration profiles, lane changes, and route speed control."),
parent_toggle=pt_tune,
panel_style=PANEL_STYLE,
)
BIN
View File
Binary file not shown.
-1
View File
@@ -73,7 +73,6 @@ SAFE_MODE_MANAGED_KEYS = (
"AccelerationProfile",
"DecelerationProfile",
"HumanAcceleration",
"PrioritizeSmoothFollowing",
"HumanLaneChanges",
"LeadDetectionThreshold",
"RecoveryPower",
+8 -7
View File
@@ -20,6 +20,7 @@ from opendbc.car.hyundai.values import CAR as HYUNDAI_CAR, EV_CAR as HYUNDAI_EV_
from opendbc.car.interfaces import TORQUE_SUBSTITUTE_PATH, CarInterfaceBase, GearShifter
from opendbc.car.mock.values import CAR as MOCK
from opendbc.car.subaru.values import SubaruFlags
from opendbc.car.tesla.values import CAR as TESLA_CAR
from opendbc.car.toyota.values import CAR as TOYOTA_CAR, ToyotaStarPilotFlags
from openpilot.common.basedir import BASEDIR
from openpilot.common.constants import CV
@@ -585,6 +586,8 @@ class StarPilotVariables:
def update(self, holiday_theme="stock", started=False):
toggle = self.starpilot_toggles
toggle.tuning_level = self.params.get("TuningLevel") if self.params.get_bool("TuningLevelConfirmed") else TUNING_LEVELS["ADVANCED"]
# CarParams uses this value to select the matching Panda safety configuration.
toggle.tesla_cooperative_steering = self.params.get_bool("TeslaCoopSteering")
fallback_platform = GM_CAR.CHEVROLET_BOLT_ACC_2022_2023 if HARDWARE.get_device_type() == "pc" else MOCK.MOCK
@@ -1170,13 +1173,6 @@ class StarPilotVariables:
else:
toggle.custom_accel_profile_values = [custom_accel_defaults[key] for key in CUSTOM_ACCEL_PROFILE_PARAM_KEYS]
toggle.human_acceleration = self.get_value("HumanAcceleration", condition=longitudinal_tuning)
toggle.prioritize_smooth_following = self.get_value("PrioritizeSmoothFollowing", condition=longitudinal_tuning)
if longitudinal_tuning and self.params_raw.get("PrioritizeSmoothFollowing") is None:
legacy_coast_value = self.params_raw.get("CoastUpToLeads")
if legacy_coast_value is not None:
toggle.prioritize_smooth_following = not self.params_raw.get_bool("CoastUpToLeads")
else:
toggle.prioritize_smooth_following = False
toggle.human_lane_changes = has_radar and self.get_value("HumanLaneChanges", condition=longitudinal_tuning)
toggle.nav_longitudinal_allowed = toggle.openpilot_longitudinal and self.get_value("NavLongitudinalAllowed", condition=longitudinal_tuning)
# Keep lead detection sensitivity normalized even when longitudinal tuning is disabled.
@@ -1414,6 +1410,11 @@ class StarPilotVariables:
toggle.subaru_sng = self.get_value("SubaruSNG", condition=toggle.car_make == "subaru" and not (CP.flags & SubaruFlags.GLOBAL_GEN2 or CP.flags & SubaruFlags.HYBRID))
toggle.subaru_sng_manual_parking_brake = self.get_value("SubaruSNGManualParkingBrake", condition=toggle.subaru_sng)
toggle.tesla_cooperative_steering = self.get_value(
"TeslaCoopSteering",
condition=toggle.car_make == "tesla" and toggle.car_model == TESLA_CAR.TESLA_MODEL_3,
)
toggle.tethering_config = self.get_value("TetheringEnabled", cast=float)
toyota_doors = self.get_value("ToyotaDoors", condition=toggle.car_make == "toyota")
@@ -84,9 +84,8 @@ class StarPilotFollowing:
if self.starpilot_planner.starpilot_weather.weather_id != 0:
self.t_follow = min(self.t_follow + self.starpilot_planner.starpilot_weather.increase_following_distance, MAX_T_FOLLOW)
prioritize_smooth_following = bool(getattr(starpilot_toggles, "prioritize_smooth_following", False))
self.disable_throttle = False
if not prioritize_smooth_following and self.starpilot_planner.tracking_lead and self.starpilot_planner.lead_one.status:
if self.starpilot_planner.tracking_lead and self.starpilot_planner.lead_one.status:
lead_distance = self.starpilot_planner.lead_one.dRel
v_lead = self.starpilot_planner.lead_one.vLead
closing_speed = max(0.0, v_ego - v_lead)
+5 -9
View File
@@ -204,11 +204,7 @@ class StarPilotPlanner:
self.road_curvature_detected = (1 / abs(self.road_curvature))**0.5 < v_ego > CRUISING_SPEED and not (sm["carState"].leftBlinker or sm["carState"].rightBlinker)
if not sm["carState"].standstill:
self.tracking_lead = self.update_lead_status(
v_ego,
starpilot_toggles.stop_distance,
prioritize_smooth_following=bool(getattr(starpilot_toggles, "prioritize_smooth_following", False)),
)
self.tracking_lead = self.update_lead_status(v_ego, starpilot_toggles.stop_distance)
self.starpilot_following.update(controls_enabled, v_ego, sm, starpilot_toggles)
@@ -235,7 +231,7 @@ class StarPilotPlanner:
else:
self.starpilot_weather.weather_id = 0
def update_lead_status(self, v_ego, stop_distance=STOP_DISTANCE, prioritize_smooth_following=False):
def update_lead_status(self, v_ego, stop_distance=STOP_DISTANCE):
following_lead = should_track_lead(
self.lead_one.status,
self.lead_one.dRel,
@@ -270,12 +266,12 @@ class StarPilotPlanner:
lead_brake=max(0.0, -float(getattr(self.lead_one, "aLeadK", 0.0))),
lead_prob=float(getattr(self.lead_one, "modelProb", 0.0)),
)
if not prioritize_smooth_following and matched_follow_window and (following_lead or self.tracking_lead or self.tracking_lead_filter.x >= THRESHOLD * 0.6):
if matched_follow_window and (following_lead or self.tracking_lead or self.tracking_lead_filter.x >= THRESHOLD * 0.6):
self.radarless_follow_hold_until = now_t + RADARLESS_TRACK_HOLD_TIME
elif prioritize_smooth_following or lead_radar or not self.lead_one.status:
elif lead_radar or not self.lead_one.status:
self.radarless_follow_hold_until = 0.0
if not prioritize_smooth_following and not following_lead and matched_follow_window and now_t < self.radarless_follow_hold_until:
if not following_lead and matched_follow_window and now_t < self.radarless_follow_hold_until:
following_lead = True
self.tracking_lead_filter.update(following_lead)
@@ -1031,14 +1031,6 @@
"ui_type": "toggle",
"parent_key": "LongitudinalTune"
},
{
"key": "PrioritizeSmoothFollowing",
"label": "Prioritize Smooth Following",
"description": "Disable the newer far-lead follow logic on cars that show lead-follow stutter or springy gas/brake behavior. Tradeoff: the car may react later and brake less proactively in some edge-case lead approaches.",
"data_type": "bool",
"ui_type": "toggle",
"parent_key": "LongitudinalTune"
},
{
"key": "HumanLaneChanges",
"label": "Human-Like Lane Changes",
@@ -2417,6 +2409,13 @@
"data_type": "bool",
"ui_type": "toggle"
},
{
"key": "TeslaCoopSteering",
"label": "Tesla Model 3 Cooperative Steering",
"description": "For Tesla Model 3 only. Converts light driver steering input into bounded steering-wheel rotation while lateral control remains active. Change while parked.",
"data_type": "bool",
"ui_type": "toggle"
},
{
"key": "NAPRadarEnabled",
"label": "Tesla Pre-AP Bosch Radar",
@@ -158,7 +158,6 @@ StarPilotLongitudinalPanel::StarPilotLongitudinalPanel(StarPilotSettingsWindow *
{"AccelerationProfile", tr("Acceleration Profile"), tr("<b>How quickly openpilot speeds up.</b> \"Eco\" is gentle and efficient, \"Sport\" is firmer and more responsive, and \"Sport+\" accelerates at the maximum rate allowed."), ""},
{"DecelerationProfile", tr("Deceleration Profile"), tr("<b>How firmly openpilot slows down.</b> \"Eco\" favors coasting, \"Sport\" applies stronger braking."), ""},
{"HumanAcceleration", tr("Human-Like Acceleration"), tr("<b>Acceleration that mimics human behavior</b> by easing the throttle at low speeds and adding extra power when taking off from a stop."), ""},
{"PrioritizeSmoothFollowing", tr("Prioritize Smooth Following"), tr("<b>Disable the newer far-lead follow logic that can cause springy gas/brake behavior on some cars.</b> Enable this if your vehicle shows lead-follow stutter or oscillation.<br><br><i>Tradeoff: the car may react later and brake less proactively in some edge-case lead approaches.</i>"), ""},
{"HumanLaneChanges", tr("Human-Like Lane Changes"), tr("<b>Lane-change behavior that mimics human drivers</b> by anticipating and tracking adjacent vehicles during lane changes."), ""},
{"LeadDetectionThreshold", tr("Lead Detection Sensitivity"), tr("<b>How sensitive openpilot is to detecting vehicles.</b> Higher sensitivity allows quicker detection at longer distances but may react to non-vehicle objects; lower sensitivity is more conservative and reduces false detections."), ""},
{"TacoTune", tr("\"Taco Bell Run\" Turn Speed Hack"), tr("<b>The turn-speed hack from comma's 2022 \"Taco Bell Run\".</b> Designed to slow down for left and right turns."), ""},
@@ -34,7 +34,7 @@ private:
QSet<QString> conditionalExperimentalKeys = {"PersistExperimentalState", "CESpeed", "CESpeedLead", "CECurves", "CELead", "CEModelStopTime", "CESignalSpeed", "CEStopLights", "ShowCEMStatus"};
QSet<QString> curveSpeedKeys = {"CalibratedLateralAcceleration", "CalibrationProgress", "ResetCurveData", "ShowCSCStatus"};
QSet<QString> customDrivingPersonalityKeys = {"AggressivePersonalityProfile", "RelaxedPersonalityProfile", "StandardPersonalityProfile", "TrafficPersonalityProfile"};
QSet<QString> longitudinalTuneKeys = {"AccelerationProfile", "DecelerationProfile", "HumanAcceleration", "PrioritizeSmoothFollowing", "HumanLaneChanges", "LeadDetectionThreshold", "TacoTune", "NavLongitudinalAllowed"};
QSet<QString> longitudinalTuneKeys = {"AccelerationProfile", "DecelerationProfile", "HumanAcceleration", "HumanLaneChanges", "LeadDetectionThreshold", "TacoTune", "NavLongitudinalAllowed"};
QSet<QString> qolKeys = {"CustomCruise", "CustomCruiseLong", "ForceStops", "ForceStopDistanceOffset", "ForceStandstill", "RadarTakeoffs", "IncreasedStoppedDistance", "MapGears", "ReverseCruise", "SetSpeedOffset", "WeatherPresets"};
QSet<QString> relaxedPersonalityKeys = {"RelaxedFollow", "RelaxedFollowHigh", "RelaxedJerkAcceleration", "RelaxedJerkDeceleration", "RelaxedJerkDanger", "RelaxedJerkSpeed", "RelaxedJerkSpeedDecrease", "ResetRelaxedPersonality"};
QSet<QString> speedLimitControllerKeys = {"SLCOffsets", "SLCFallback", "SLCOverride", "SLCPriority", "SLCQOL", "SLCVisuals"};
+1 -32
View File
@@ -61,13 +61,12 @@ LEGACY_BOLT_FP_MIGRATION_FLAG = Path("/data") / "legacy_bolt_fp_migration_v1"
STARPILOT_DEFAULTS_PARITY_MIGRATION_FLAG = Path("/data") / "starpilot_defaults_parity_v1"
STARPILOT_HUMANLIKE_DISABLE_MIGRATION_FLAG = Path("/data") / "starpilot_humanlike_disable_v1"
STARPILOT_CLUSTER_OFFSET_MIGRATION_FLAG = Path("/data") / "starpilot_cluster_offset_v1"
STARPILOT_PRIORITIZE_SMOOTH_FOLLOWING_MIGRATION_FLAG = Path("/data") / "starpilot_prioritize_smooth_following_v1"
STARPILOT_PARAM_RENAME_MIGRATION_FLAG = Path("/data") / "starpilot_param_rename_v1"
STARPILOT_PARAM_CANONICALIZATION_MIGRATION_FLAG = Path("/data") / "starpilot_param_canonicalization_v1"
STARPILOT_PC_ROOT_MIGRATION_FLAG = Path("/data") / "starpilot_pc_root_v1"
STARPILOT_PARAMS_CACHE_MIGRATION_FLAG = Path("/data") / "starpilot_params_cache_v1"
STARPILOT_LEGACY_CACHE_MARKER_KEYS = ("RemapCancelToDistance",)
STARPILOT_REMOVED_PARAM_KEYS = ("HumanFollowing",)
STARPILOT_REMOVED_PARAM_KEYS = ("CoastUpToLeads", "HumanFollowing", "PrioritizeSmoothFollowing")
LEGACY_CARMODEL_MIGRATIONS = {
"CHEVROLET_BOLT_CC_2019_2021": "CHEVROLET_BOLT_CC_2018_2021",
}
@@ -601,35 +600,6 @@ def migrate_cluster_offset_default(params: Params, params_cache: Params) -> None
cloudlog.exception(f"Failed to write migration flag: {STARPILOT_CLUSTER_OFFSET_MIGRATION_FLAG}")
def migrate_prioritize_smooth_following_default(params: Params, params_cache: Params) -> None:
if STARPILOT_PRIORITIZE_SMOOTH_FOLLOWING_MIGRATION_FLAG.exists():
return
if not (_has_persisted_param_file(params, "PrioritizeSmoothFollowing") or _has_persisted_param_file(params_cache, "PrioritizeSmoothFollowing")):
migrated_from_legacy = False
for params_obj in (params, params_cache):
if not _has_persisted_param_file(params_obj, "CoastUpToLeads"):
continue
prioritize_smooth_following = not params_obj.get_bool("CoastUpToLeads")
params.put_bool("PrioritizeSmoothFollowing", prioritize_smooth_following)
params_cache.put_bool("PrioritizeSmoothFollowing", prioritize_smooth_following)
cloudlog.warning(f"Migrated CoastUpToLeads to PrioritizeSmoothFollowing={int(prioritize_smooth_following)}")
migrated_from_legacy = True
break
if not migrated_from_legacy:
params.put_bool("PrioritizeSmoothFollowing", False)
params_cache.put_bool("PrioritizeSmoothFollowing", False)
cloudlog.warning("Seeded PrioritizeSmoothFollowing to default disabled")
try:
STARPILOT_PRIORITIZE_SMOOTH_FOLLOWING_MIGRATION_FLAG.parent.mkdir(parents=True, exist_ok=True)
STARPILOT_PRIORITIZE_SMOOTH_FOLLOWING_MIGRATION_FLAG.write_text(f"{datetime.datetime.now(datetime.UTC).isoformat()}\n")
except Exception:
cloudlog.exception(f"Failed to write migration flag: {STARPILOT_PRIORITIZE_SMOOTH_FOLLOWING_MIGRATION_FLAG}")
def _read_raw_param_bytes(params: Params, key: str | bytes):
try:
path = params.get_param_path(key)
@@ -849,7 +819,6 @@ def manager_init() -> None:
migrate_starpilot_default_parity(params, params_cache)
migrate_disable_humanlike_defaults(params, params_cache)
migrate_cluster_offset_default(params, params_cache)
migrate_prioritize_smooth_following_default(params, params_cache)
last_timing = _log_boot_timing("manager_init", "starpilot_migrations", manager_init_start, last_timing)
# set unset params to their default value
+4 -37
View File
@@ -250,16 +250,20 @@ class TestManager:
def test_cleanup_removed_starpilot_params(self, tmp_path):
params = FileBackedFakeParams(tmp_path / "params", {
"CoastUpToLeads": True,
"HumanFollowing": True,
})
params_cache = FileBackedFakeParams(tmp_path / "cache", {
"HumanFollowing": False,
"PrioritizeSmoothFollowing": True,
})
manager.cleanup_removed_starpilot_params(params, params_cache)
assert not Path(params.get_param_path("CoastUpToLeads")).exists()
assert not Path(params.get_param_path("HumanFollowing")).exists()
assert not Path(params_cache.get_param_path("HumanFollowing")).exists()
assert not Path(params_cache.get_param_path("PrioritizeSmoothFollowing")).exists()
def test_migrate_legacy_starpilot_params_cache_copies_marker_sources(self, tmp_path, monkeypatch):
monkeypatch.setattr(manager, "STARPILOT_PARAMS_CACHE_MIGRATION_FLAG", tmp_path / "starpilot_params_cache_v1")
@@ -339,43 +343,6 @@ class TestManager:
assert params.get("ClusterOffset") == "1.02"
assert params_cache.get("ClusterOffset") is None
def test_migrate_prioritize_smooth_following_default_seeds_disabled(self, tmp_path, monkeypatch):
monkeypatch.setattr(manager, "STARPILOT_PRIORITIZE_SMOOTH_FOLLOWING_MIGRATION_FLAG", tmp_path / "starpilot_prioritize_smooth_following_v1")
params = FileBackedFakeParams(tmp_path / "params", {})
params_cache = FileBackedFakeParams(tmp_path / "cache", {})
manager.migrate_prioritize_smooth_following_default(params, params_cache)
assert not params.get_bool("PrioritizeSmoothFollowing")
assert not params_cache.get_bool("PrioritizeSmoothFollowing")
def test_migrate_prioritize_smooth_following_default_inverts_legacy_coast_toggle(self, tmp_path, monkeypatch):
monkeypatch.setattr(manager, "STARPILOT_PRIORITIZE_SMOOTH_FOLLOWING_MIGRATION_FLAG", tmp_path / "starpilot_prioritize_smooth_following_v1")
params = FileBackedFakeParams(tmp_path / "params", {
"CoastUpToLeads": False,
})
params_cache = FileBackedFakeParams(tmp_path / "cache", {})
manager.migrate_prioritize_smooth_following_default(params, params_cache)
assert params.get_bool("PrioritizeSmoothFollowing")
assert params_cache.get_bool("PrioritizeSmoothFollowing")
def test_migrate_prioritize_smooth_following_default_preserves_existing_values(self, tmp_path, monkeypatch):
monkeypatch.setattr(manager, "STARPILOT_PRIORITIZE_SMOOTH_FOLLOWING_MIGRATION_FLAG", tmp_path / "starpilot_prioritize_smooth_following_v1")
params = FileBackedFakeParams(tmp_path / "params", {
"PrioritizeSmoothFollowing": True,
"CoastUpToLeads": True,
})
params_cache = FileBackedFakeParams(tmp_path / "cache", {})
manager.migrate_prioritize_smooth_following_default(params, params_cache)
assert params.get_bool("PrioritizeSmoothFollowing")
def test_cleanup_inaccessible_msgq_files_removes_only_blocked_files(self, tmp_path, monkeypatch):
healthy = tmp_path / "msgq_deviceState"
blocked = tmp_path / "msgq_gpsLocation"
-1
View File
@@ -61,7 +61,6 @@ ColorScheme
CommunityFavorites
ConditionalChill
ConditionalExperimental
PrioritizeSmoothFollowing
CurveSpeedController
CustomAlerts
CustomCruise