mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-07-17 07:12:08 +08:00
food & stuff
This commit is contained in:
Binary file not shown.
@@ -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.
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
Binary file not shown.
@@ -73,7 +73,6 @@ SAFE_MODE_MANAGED_KEYS = (
|
||||
"AccelerationProfile",
|
||||
"DecelerationProfile",
|
||||
"HumanAcceleration",
|
||||
"PrioritizeSmoothFollowing",
|
||||
"HumanLaneChanges",
|
||||
"LeadDetectionThreshold",
|
||||
"RecoveryPower",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -61,7 +61,6 @@ ColorScheme
|
||||
CommunityFavorites
|
||||
ConditionalChill
|
||||
ConditionalExperimental
|
||||
PrioritizeSmoothFollowing
|
||||
CurveSpeedController
|
||||
CustomAlerts
|
||||
CustomCruise
|
||||
|
||||
Reference in New Issue
Block a user