This commit is contained in:
firestar5683
2026-08-25 10:00:34 -05:00
parent 83cec26dea
commit 2ef545b987
44 changed files with 977 additions and 277 deletions
-1
View File
@@ -549,7 +549,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"RelaxedJerkDeceleration", {PERSISTENT, FLOAT, "100.0", "100.0", 3}},
{"RelaxedJerkSpeed", {PERSISTENT, FLOAT, "100.0", "100.0", 3}},
{"RelaxedJerkSpeedDecrease", {PERSISTENT, FLOAT, "100.0", "100.0", 3}},
{"ReverseCruise", {PERSISTENT, BOOL, "0", "0", 1}},
{"RivianAngleControl", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
{"RivianAngleSaturated", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BOOL, "0", "0"}},
{"RivianToiRecoveryFailed", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BOOL, "0", "0"}},
@@ -9,7 +9,7 @@ from opendbc.car.common.conversions import Conversions as CV
from opendbc.car.hyundai import hyundaicanfd, hyundaican
from opendbc.car.hyundai.hyundaicanfd import CanBus
from opendbc.car.hyundai.values import HyundaiFlags, Buttons, CarControllerParams, CAR, CANFD_ANGLE_LONGITUDINAL_CAR, \
CANFD_RADAR_LIVE_LONGITUDINAL_CAR, kia_ev6_gt_line_longitudinal_tuning, \
CANFD_RADAR_LIVE_LONGITUDINAL_CAR, CANFD_ALT_BUTTONS_RESUME_CAR, kia_ev6_gt_line_longitudinal_tuning, \
KIA_EV6_GT_LINE_LONG_TUNING_TESTING_GROUND_ID
from opendbc.car.interfaces import CarControllerBase
from opendbc.car.vehicle_model import VehicleModel
@@ -789,6 +789,7 @@ class CarController(CarControllerBase):
# TODO: unclear if this is needed
jerk = 3.0 if actuators.longControlState == LongCtrlState.pid else 1.0
use_fca = self.CP.flags & HyundaiFlags.USE_FCA.value
main_cruise_enabled = getattr(CS, "main_cruise_on", False) if getattr(CS, "main_cruise_tracking", False) else True
if blended_hda2:
stopping = stopping and CS.out.vEgoRaw < 0.1
can_sends.extend(hyundaican.create_acc_commands_can_canfd_blended_hda2(
@@ -804,7 +805,8 @@ class CarController(CarControllerBase):
else:
can_sends.extend(hyundaican.create_acc_commands(self.packer, CC.enabled, accel, jerk, int(self.frame / 2),
hud_control, set_speed_in_units, stopping,
CC.cruiseControl.override, use_fca, self.CP))
CC.cruiseControl.override, use_fca, self.CP,
main_cruise_enabled))
# 20 Hz LFA MFA message
if self.frame % 5 == 0 and (self.CP.flags & HyundaiFlags.SEND_LFA.value or (self.long_active_ecu and blended_hda2)):
@@ -866,14 +868,7 @@ class CarController(CarControllerBase):
steering_msg_active, apply_torque, apply_angle,
CS.stock_lfa_msg if preserve_stock_lfa_status else None,
CS.stock_lkas_msg if preserve_stock_lkas else None,
lka_icon=lka_icon,
send_lfa_status=self.ecu_disable_failed and
self.CP.carFingerprint == CAR.KIA_EV9))
elif self.ecu_disable_failed and self.CP.carFingerprint == CAR.KIA_EV9:
can_sends.extend(hyundaicanfd.create_steering_messages(
self.packer, self.CP, self.CAN, CC.enabled, False, 0.0, 0.0,
CS.stock_lfa_msg, lka_icon=lka_icon, send_lfa_status=True, lfa_only=True,
))
lka_icon=lka_icon))
direct_steering_active = ccnc_angle_long and drive_gear and CC.latActive and self.direct_angle_request_allowed and not CS.angle_steering_fault
inactive_steering_angle = float(np.clip(CS.angle_steering_angle,
-self.params.ANGLE_LIMITS.STEER_ANGLE_MAX,
@@ -1047,9 +1042,13 @@ class CarController(CarControllerBase):
# cruise standstill resume
elif CC.cruiseControl.resume:
if self.CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS:
# TODO: resume for alt button cars
pass
if self.CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS and self.CP.carFingerprint in CANFD_ALT_BUTTONS_RESUME_CAR:
for _ in range(20):
can_sends.append(hyundaicanfd.create_buttons(
self.packer, self.CP, self.CAN, (CS.buttons_counter + 1) % 0x100,
Buttons.RES_ACCEL, base_values=CS.cruise_buttons_msg,
))
self.last_button_frame = self.frame
else:
for _ in range(20):
can_sends.append(hyundaicanfd.create_buttons(self.packer, self.CP, self.CAN, CS.buttons_counter + 1, Buttons.RES_ACCEL))
+9 -3
View File
@@ -9,6 +9,7 @@ from opendbc.car.common.conversions import Conversions as CV
from opendbc.car.hyundai.hyundaicanfd import CanBus
from opendbc.car.hyundai.values import HyundaiFlags, HyundaiStarPilotFlags, HyundaiStarPilotSafetyFlags, CAR, DBC, Buttons, CarControllerParams, \
CANFD_ANGLE_LONGITUDINAL_CAR, CANFD_CORNER_RADAR_BSM_CAR, \
CANFD_ALT_BUTTONS_RESUME_CAR, \
hyundai_cancel_button_enables_cruise, ALT_BUS_LDA_BUTTON_CARS, ALT_BUS_LDA_BUTTON_SWL_STAT_CARS
from opendbc.car.interfaces import CarStateBase
@@ -132,6 +133,7 @@ class CarState(CarStateBase):
self.is_metric = False
self.buttons_counter = 0
self.main_cruise_on = False
self.main_cruise_tracking = bool(getattr(FPCP, "flags", 0) & HyundaiStarPilotFlags.MAIN_CRUISE_STATE_TRACKING)
self.cruise_info = {}
self.msg_161 = {}
@@ -558,7 +560,9 @@ class CarState(CarStateBase):
self.main_buttons.extend(cp.vl_all[self.cruise_btns_msg_canfd]["ADAPTIVE_CRUISE_MAIN_BTN"])
self.lda_button = cp.vl[self.cruise_btns_msg_canfd]["LDA_BTN"]
self.left_paddle = 0
if self.CP.carFingerprint == CAR.HYUNDAI_IONIQ_6:
if self.CP.carFingerprint in CANFD_ALT_BUTTONS_RESUME_CAR:
self.cruise_buttons_msg = copy.copy(cp.vl[self.cruise_btns_msg_canfd])
elif self.CP.carFingerprint == CAR.HYUNDAI_IONIQ_6:
self.cruise_buttons_msg = copy.copy(cp.vl["CRUISE_BUTTONS"])
self.left_paddle = cp.vl["CRUISE_BUTTONS"]["LEFT_PADDLE"]
self.buttons_counter = cp.vl[self.cruise_btns_msg_canfd]["COUNTER"]
@@ -591,7 +595,7 @@ class CarState(CarStateBase):
*create_button_events(self.main_buttons[-1], prev_main_buttons, {1: ButtonType.mainCruise}),
*create_button_events(self.lda_button, prev_lda_button, {1: ButtonType.lkas}),
*create_button_events(self.left_paddle, prev_left_paddle, {1: ButtonType.altButton2})]
if self.CP.openpilotLongitudinalControl and self.CP.carFingerprint == CAR.KIA_EV9:
if self.CP.openpilotLongitudinalControl and (self.CP.carFingerprint == CAR.KIA_EV9 or self.main_cruise_tracking):
ret.cruiseState.available = self.update_main_cruise(ret)
ret.blockPcmEnable = not self.recent_button_interaction()
@@ -618,7 +622,9 @@ class CarState(CarStateBase):
def get_can_parsers_canfd(self, CP):
msgs = []
cam_msgs = []
if not (CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS):
if CP.carFingerprint in CANFD_ALT_BUTTONS_RESUME_CAR:
msgs.append(("CRUISE_BUTTONS_ALT", 50))
elif not (CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS):
# The EV9 can stop publishing this during the non-ECU-disabled startup
# state. Keep decoding it when present without making CAN invalid.
msgs += [
@@ -284,11 +284,12 @@ def create_acc_commands_can_canfd_blended_hda2(packer, enabled, accel, accel_las
return commands
def create_acc_commands(packer, enabled, accel, upper_jerk, idx, hud_control, set_speed, stopping, long_override, use_fca, CP):
def create_acc_commands(packer, enabled, accel, upper_jerk, idx, hud_control, set_speed, stopping, long_override, use_fca, CP,
main_cruise_enabled=True):
commands = []
scc11_values = {
"MainMode_ACC": 1,
"MainMode_ACC": int(bool(main_cruise_enabled)),
"TauGapSet": hud_control.leadDistanceBars,
"VSetDis": set_speed if enabled else 0,
"AliveCounterACC": idx % 0x10,
@@ -3,7 +3,7 @@ import numpy as np
from opendbc.car import CanBusBase, CanData
from opendbc.car.common.conversions import Conversions as CV
from opendbc.car.crc import CRC16_XMODEM
from opendbc.car.hyundai.values import HyundaiFlags, CAR
from opendbc.car.hyundai.values import HyundaiFlags, CAR, CANFD_ALT_BUTTONS_RESUME_CAR
def _set_value(msg: bytearray, sig, ival: int) -> None:
@@ -152,17 +152,14 @@ def create_angle_adas_cmd(packer, CAN, apply_angle: float, lat_active: bool, tor
def create_steering_messages(packer, CP, CAN, enabled, lat_active, apply_torque, apply_angle,
lfa_base_values=None, lkas_base_values=None, lka_icon=None,
send_lfa_status=False, lfa_only=False):
lfa_base_values=None, lkas_base_values=None, lka_icon=None):
if lka_icon is None:
lka_icon = 2 if enabled else 1
if CP.carFingerprint == CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN and CP.flags & HyundaiFlags.CANFD_LKA_STEERING:
ret = []
if CP.openpilotLongitudinalControl or send_lfa_status:
if CP.openpilotLongitudinalControl:
ret.append(_create_gv70_lka_status_msg(packer, CAN, "LFA", CAN.ECAN, enabled, lat_active, apply_torque))
if lfa_only:
return ret
ret.append(_create_gv70_lka_status_msg(packer, CAN, "LKAS", CAN.ACAN, enabled, lat_active, apply_torque))
return ret
@@ -258,10 +255,8 @@ def create_steering_messages(packer, CP, CAN, enabled, lat_active, apply_torque,
ret = []
if CP.flags & HyundaiFlags.CANFD_LKA_STEERING:
lkas_msg = "LKAS_ALT" if CP.flags & HyundaiFlags.CANFD_LKA_STEERING_ALT else "LKAS"
if (CP.openpilotLongitudinalControl and not CP.flags & HyundaiFlags.CAN_CANFD_BLENDED) or send_lfa_status:
if CP.openpilotLongitudinalControl and not CP.flags & HyundaiFlags.CAN_CANFD_BLENDED:
ret.append(packer.make_can_msg("LFA", CAN.ECAN, lfa_values))
if lfa_only:
return ret
ret.append(packer.make_can_msg(lkas_msg, CAN.ACAN, lkas_values))
else:
if CP.flags & HyundaiFlags.CANFD_ANGLE_STEERING:
@@ -311,16 +306,27 @@ def create_suppress_lfa(packer, CAN, lfa_block_msg, lka_steering_alt):
def create_buttons(packer, CP, CAN, cnt, btn=0, base_values=None, left_paddle=False, right_paddle=False):
values = {k: v for k, v in base_values.items() if k not in ("_CHECKSUM", "COUNTER")} if base_values else {}
values = {k: v for k, v in base_values.items() if k not in ("CHECKSUM", "_CHECKSUM", "COUNTER")} if base_values else {}
values.update({
"COUNTER": cnt,
"SET_ME_1": 1,
"CRUISE_BUTTONS": btn,
"LEFT_PADDLE": int(left_paddle),
"RIGHT_PADDLE": int(right_paddle),
})
if not (CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS and CP.carFingerprint in CANFD_ALT_BUTTONS_RESUME_CAR):
values.update({
"LEFT_PADDLE": int(left_paddle),
"RIGHT_PADDLE": int(right_paddle),
})
bus = CAN.ECAN if CP.flags & HyundaiFlags.CANFD_LKA_STEERING else CAN.CAM
if CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS and CP.carFingerprint in CANFD_ALT_BUTTONS_RESUME_CAR:
address, dat, bus = packer.make_can_msg("CRUISE_BUTTONS_ALT", bus, values)
dat = bytearray(dat)
checksum = hkg_can_fd_checksum(address, None, dat)
dat[0] = checksum & 0xFF
dat[1] = (checksum >> 8) & 0xFF
return address, bytes(dat), bus
return packer.make_can_msg("CRUISE_BUTTONS", bus, values)
@@ -25,7 +25,7 @@ from opendbc.car.hyundai.carstate import CarState, decode_canfd_camera_lead, dec
get_canfd_cruise_available
from opendbc.car.hyundai.interface import CarInterface, KIA_EV9_ACCEL_MAX
from opendbc.car.hyundai import hyundaican, hyundaicanfd
from opendbc.car.hyundai.hyundaicanfd import CanBus
from opendbc.car.hyundai.hyundaicanfd import CanBus, hkg_can_fd_checksum
from opendbc.car.hyundai.radar_interface import MRREVO14F_RADAR_START_ADDR, MRR30_RADAR_START_ADDR, MRR35_RADAR_START_ADDR, \
RADAR_START_ADDR, get_radar_track_config
from opendbc.car.hyundai.values import CAMERA_SCC_CAR, CANFD_CAR, CAN_GEARS, CAR, CHECKSUM, DATE_FW_ECUS, DATELESS_FUZZY_CARS, \
@@ -522,6 +522,27 @@ class TestHyundaiFingerprint:
k4_cp = CarInterface.get_params(CAR.KIA_K4_2025, fingerprint, [], False, False, False, None)
assert not (k4_cp.flags & HyundaiFlags.CANFD_ALT_BUTTONS)
@pytest.mark.parametrize("candidate", (CAR.KIA_CARNIVAL_2025, CAR.KIA_CARNIVAL_HEV_4TH_GEN))
def test_carnival_hda1_resume_uses_alternate_button_frame(self, candidate):
fingerprint = gen_empty_fingerprint()
fingerprint[0] = {0x1AA: 16}
CP = CarInterface.get_params(candidate, fingerprint, [], False, False, False, None)
assert CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS
assert not CP.openpilotLongitudinalControl
assert "CRUISE_BUTTONS_ALT" in {
state.name for state in CarState(CP, None).get_can_parsers(CP)[Bus.pt].message_states.values()
}
packer = CANPacker(DBC[CP.carFingerprint][Bus.pt])
address, dat, bus = hyundaicanfd.create_buttons(
packer, CP, CanBus(CP), 0x41, Buttons.RES_ACCEL,
base_values={"SET_ME_1": 1, "DISTANCE_UNIT": 0},
)
assert (address, bus) == (0x1AA, CanBus(CP).CAM)
assert dat[2] == 0x41
assert (dat[4] >> 4) & 0x7 == Buttons.RES_ACCEL
assert int.from_bytes(dat[:2], "little") == hkg_can_fd_checksum(address, None, bytearray(dat))
def test_ioniq_6_hda1_layout_stays_non_lka(self):
fingerprint = gen_empty_fingerprint()
fingerprint[1] = {0x100: 8, 0x110: 8}
@@ -706,14 +727,18 @@ class TestHyundaiFingerprint:
assert combined_safety_param & HyundaiSafetyFlags.LONG
assert combined_safety_param & HyundaiStarPilotSafetyFlags.AOL_LKAS_ON_ENGAGE
@pytest.mark.parametrize("candidate", (CAR.HYUNDAI_ELANTRA_2021, CAR.HYUNDAI_SONATA_HYBRID))
def test_legacy_hyundai_long_does_not_gate_availability_on_main_cruise(self, candidate):
@pytest.mark.parametrize("candidate, tracks_main_cruise", (
(CAR.HYUNDAI_ELANTRA_2021, False),
(CAR.HYUNDAI_ELANTRA_HEV_2024, True),
(CAR.HYUNDAI_SONATA_HYBRID, True),
))
def test_legacy_hyundai_long_main_cruise_tracking_is_vehicle_specific(self, candidate, tracks_main_cruise):
toggles = get_test_toggles()
CP = CarInterface.get_params(candidate, gen_empty_fingerprint(), [], True, False, False, toggles)
FPCP = CarInterface.get_starpilot_params(
candidate, gen_empty_fingerprint(), [], CP, toggles,
)
assert not (FPCP.flags & HyundaiStarPilotFlags.MAIN_CRUISE_STATE_TRACKING)
assert bool(FPCP.flags & HyundaiStarPilotFlags.MAIN_CRUISE_STATE_TRACKING) is tracks_main_cruise
ioniq_cp = CarInterface.get_params(CAR.HYUNDAI_IONIQ_6, gen_empty_fingerprint(), [], True, False, False, toggles)
ioniq_fpcp = CarInterface.get_starpilot_params(
@@ -2452,6 +2477,22 @@ class TestHyundaiFingerprint:
assert parser.vl["SCC14"]["ComfortBandLower"] == pytest.approx(0.0)
assert parser.vl["SCC14"]["JerkLowerLimit"] == pytest.approx(5.0)
def test_can_acc_commands_follow_sonata_main_cruise_state(self):
CP = CarParams.new_message()
CP.carFingerprint = CAR.HYUNDAI_SONATA_HYBRID
packer = CANPacker(DBC[CP.carFingerprint][Bus.pt])
parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("SCC11", 0)], 0)
msgs = hyundaican.create_acc_commands(packer, enabled=False, accel=0.0, upper_jerk=1.0, idx=3,
hud_control=SimpleNamespace(leadDistanceBars=3, leadVisible=False), set_speed=42,
stopping=False, long_override=False, use_fca=False, CP=CP,
main_cruise_enabled=False)
parser.update([(1, msgs)])
assert parser.can_valid
assert parser.vl["SCC11"]["MainMode_ACC"] == 0
def test_can_acc_commands_use_enabled_fca_status(self):
CP = CarParams.new_message()
CP.carFingerprint = CAR.GENESIS_G90
@@ -2670,26 +2711,47 @@ class TestHyundaiFingerprint:
("LKAS", can_bus.ACAN),
]
def test_ev9_fallback_keeps_lfa_status_without_longitudinal_control(self):
def test_ev9_fallback_active_lateral_uses_lkas_without_injecting_lfa(self):
CP = CarParams.new_message()
CP.carFingerprint = CAR.KIA_EV9
CP.flags = int(HyundaiFlags.CANFD | HyundaiFlags.EV | HyundaiFlags.CCNC |
HyundaiFlags.CANFD_ANGLE_STEERING | HyundaiFlags.CANFD_LKA_STEERING |
HyundaiFlags.CANFD_LKA_STEERING_ALT)
CP.openpilotLongitudinalControl = False
CP.openpilotLongitudinalControl = True
controller = CarController(DBC[CP.carFingerprint], CP)
controller.frame = 1
controller.ecu_disable_failed = True
controller.long_active_ecu = False
CP.openpilotLongitudinalControl = False
packer = CANPacker(DBC[CP.carFingerprint][Bus.pt])
can_bus = CanBus(CP)
msgs = hyundaicanfd.create_steering_messages(
packer, CP, can_bus, True, True, 0.44, -31.5, send_lfa_status=True,
cc = SimpleNamespace(
enabled=True,
latActive=True,
actuators=SimpleNamespace(longControlState=LongCtrlState.off),
cruiseControl=SimpleNamespace(cancel=False, resume=False),
leftBlinker=False,
rightBlinker=False,
hudControl=SimpleNamespace(),
)
cs = SimpleNamespace(
stock_lfa_msg={},
stock_lkas_msg={},
out=SimpleNamespace(
standstill=False,
steeringAngleDeg=-31.5,
gearShifter=structs.CarState.GearShifter.drive,
),
)
msgs = controller.create_canfd_msgs(0, True, 0.44, -31.5, 0.0, 0.0, False,
cc.hudControl, cs, cc, get_test_toggles(), lka_icon=2, lfa_icon=2)
assert [(packer.dbc.addr_to_msg[addr].name, bus) for addr, _, bus in msgs] == [
("LFA", can_bus.ECAN),
("LKAS_ALT", can_bus.ACAN),
]
def test_ev9_fallback_lfa_only_does_not_send_lkas_at_standstill(self):
def test_ev9_fallback_does_not_inject_lfa_while_parked(self):
CP = CarParams.new_message()
CP.carFingerprint = CAR.KIA_EV9
CP.flags = int(HyundaiFlags.CANFD | HyundaiFlags.EV | HyundaiFlags.CCNC |
@@ -2697,15 +2759,31 @@ class TestHyundaiFingerprint:
HyundaiFlags.CANFD_LKA_STEERING_ALT)
CP.openpilotLongitudinalControl = False
packer = CANPacker(DBC[CP.carFingerprint][Bus.pt])
can_bus = CanBus(CP)
msgs = hyundaicanfd.create_steering_messages(
packer, CP, can_bus, True, False, 0.0, 0.0, send_lfa_status=True, lfa_only=True,
controller = CarController(DBC[CP.carFingerprint], CP)
controller.ecu_disable_failed = True
cc = SimpleNamespace(
enabled=False,
latActive=False,
actuators=SimpleNamespace(longControlState=LongCtrlState.off),
cruiseControl=SimpleNamespace(cancel=False, resume=False),
leftBlinker=False,
rightBlinker=False,
hudControl=SimpleNamespace(),
)
cs = SimpleNamespace(
stock_lfa_msg={},
stock_lkas_msg={},
out=SimpleNamespace(
standstill=True,
steeringAngleDeg=0.0,
gearShifter=structs.CarState.GearShifter.park,
),
)
assert [(packer.dbc.addr_to_msg[addr].name, bus) for addr, _, bus in msgs] == [
("LFA", can_bus.ECAN),
]
msgs = controller.create_canfd_msgs(0, False, 0.0, 0.0, 0.0, 0.0, False,
cc.hudControl, cs, cc, get_test_toggles(), lka_icon=1, lfa_icon=1)
assert not [msg for msg in msgs if msg[0] in (0x110, 0x12A)]
def test_kia_ev6_lkas_helper_preserves_stock_camera_fields_with_stock_long(self):
CP = CarParams.new_message()
@@ -1183,6 +1183,7 @@ CANFD_SECURITYACCESS_CAR = {
}
CANFD_UNSUPPORTED_LONGITUDINAL_CAR = CAR.with_flags(HyundaiFlags.CANFD_NO_RADAR_DISABLE) - CANFD_SECURITYACCESS_CAR # TODO: merge with UNSUPPORTED_LONGITUDINAL_CAR
CANFD_ANGLE_LONGITUDINAL_CAR = {CAR.KIA_EV9, CAR.HYUNDAI_IONIQ_5_PE}
CANFD_ALT_BUTTONS_RESUME_CAR = {CAR.KIA_CARNIVAL_2025, CAR.KIA_CARNIVAL_HEV_4TH_GEN}
CANFD_CORNER_RADAR_BSM_CAR = {CAR.HYUNDAI_IONIQ_6, CAR.HYUNDAI_IONIQ_5_PE, CAR.KIA_EV9}
CANFD_RADAR_LIVE_LONGITUDINAL_CAR = {
CAR.HYUNDAI_IONIQ_5, CAR.HYUNDAI_IONIQ_5_PE, CAR.HYUNDAI_IONIQ_6, CAR.KIA_EV6, CAR.KIA_EV9, CAR.GENESIS_GV60_EV_1ST_GEN,
+7
View File
@@ -245,6 +245,13 @@ class CarInterfaceBase(ABC):
fp_ret.pcmCruiseSpeed = False
CP.openpilotLongitudinalControl = True
# These classic Hyundai hybrids need their stock ACC main state tracked while
# using OP long. Their cluster/EPS state becomes inconsistent when AOL remains
# active after the physical ACC main state changes.
if candidate in (HYUNDAI.HYUNDAI_SONATA_HYBRID, HYUNDAI.HYUNDAI_ELANTRA_HEV_2024) and \
CP.openpilotLongitudinalControl:
fp_ret.flags |= HyundaiStarPilotFlags.MAIN_CRUISE_STATE_TRACKING.value
hyundai_has_lda_button = not (CP.flags & HyundaiFlags.CANFD) and (
0x391 in fingerprint[0] or
0x50C in fingerprint[0] or
@@ -429,14 +429,24 @@ def test_angle_controller_tracks_driver_override():
CP = CarInterface.get_non_essential_params(CAR.SUBARU_CROSSTREK_2025)
controller = CarController({}, CP)
CC = SimpleNamespace(latActive=True, actuators=SimpleNamespace(steeringAngleDeg=15.0))
CS = SimpleNamespace(out=SimpleNamespace(vEgoRaw=15.0, steeringAngleDeg=2.0, steeringTorque=250.0))
CS = SimpleNamespace(out=SimpleNamespace(vEgoRaw=15.0, steeringAngleDeg=2.0, steeringTorque=175.0))
msg = controller.lateral_angle(CC, CS)
assert controller.driver_override
assert controller.p.STEER_OVERRIDE_TORQUE_HIGH == 150
assert controller.p.STEER_OVERRIDE_TORQUE_LOW == 100
assert controller.apply_steer_last == CS.out.steeringAngleDeg
assert msg[0] == 0x124
CS.out.steeringTorque = 125.0
controller.lateral_angle(CC, CS)
assert controller.driver_override
CS.out.steeringTorque = 75.0
controller.lateral_angle(CC, CS)
assert not controller.driver_override
def test_ascent_angle_controller_uses_fixed_angle_rate_limits():
CP = CarInterface.get_non_essential_params(CAR.SUBARU_ASCENT_2023)
@@ -37,6 +37,11 @@ class CarControllerParams:
self.STEER_OVERRIDE_TORQUE_HIGH = 200
self.STEER_OVERRIDE_TORQUE_LOW = 150
# Crosstrek 2025 reports manual parking-lot inputs below the generic handoff threshold.
if CP.carFingerprint == CAR.SUBARU_CROSSTREK_2025:
self.STEER_OVERRIDE_TORQUE_HIGH = 150
self.STEER_OVERRIDE_TORQUE_LOW = 100
if CP.flags & SubaruFlags.GLOBAL_GEN2:
# TODO: lower rate limits, this reaches min/max in 0.5s which negatively affects tuning
self.STEER_MAX = 1500
@@ -74,7 +74,6 @@ def get_test_starpilot_toggles() -> SimpleNamespace:
disable_openpilot_long=False,
force_fingerprint=False,
lock_doors=False,
reverse_cruise_increase=False,
sng_hack=False,
subaru_sng=False,
subaru_sng_manual_parking_brake=False,
@@ -6,6 +6,9 @@
#define HYUNDAI_CANFD_CRUISE_BUTTON_TX_MSGS(bus) \
{0x1CF, bus, 8, .check_relay = false}, /* CRUISE_BUTTON */ \
#define HYUNDAI_CANFD_ALT_CRUISE_BUTTON_TX_MSGS(bus) \
{0x1AA, bus, 16, .check_relay = false}, /* CRUISE_BUTTONS_ALT */ \
#define HYUNDAI_CANFD_LKA_STEERING_COMMON_TX_MSGS(a_can, e_can) \
HYUNDAI_CANFD_CRUISE_BUTTON_TX_MSGS(e_can) \
{0x50, a_can, 16, .check_relay = (a_can) == 0}, /* LKAS */ \
@@ -291,8 +294,8 @@ static bool hyundai_canfd_tx_hook(const CANPacket_t *msg) {
}
// cruise buttons check
if (msg->addr == 0x1cfU) {
int button = msg->data[2] & 0x7U;
if ((msg->addr == 0x1cfU) || (hyundai_canfd_alt_buttons && (msg->addr == 0x1aaU))) {
int button = (msg->addr == 0x1aaU) ? ((msg->data[4] >> 4U) & 0x7U) : (msg->data[2] & 0x7U);
bool is_cancel = (button == HYUNDAI_BTN_CANCEL);
bool is_resume = (button == HYUNDAI_BTN_RESUME);
bool is_set = (button == HYUNDAI_BTN_SET);
@@ -427,11 +430,6 @@ static safety_config hyundai_canfd_init(uint16_t param) {
{0x1DA, 1, 32, .check_relay = false}, // ADRV_0x1da
};
static const CanMsg HYUNDAI_CANFD_CCNC_ANGLE_FALLBACK_TX_MSGS[] = {
HYUNDAI_CANFD_LKA_STEERING_ALT_COMMON_TX_MSGS(0, 1)
{0x12A, 1, 16, .check_relay = false}, // LFA status
};
static const CanMsg HYUNDAI_CANFD_LFA_STEERING_TX_MSGS[] = {
HYUNDAI_CANFD_CRUISE_BUTTON_TX_MSGS(2)
HYUNDAI_CANFD_LFA_STEERING_COMMON_TX_MSGS(0)
@@ -455,6 +453,12 @@ static safety_config hyundai_canfd_init(uint16_t param) {
HYUNDAI_CANFD_SCC_CONTROL_COMMON_TX_MSGS(0, (longitudinal)) \
{0x160, 0, 16, .check_relay = (longitudinal)}, /* ADRV_0x160 */ \
#define HYUNDAI_CANFD_LFA_STEERING_CAMERA_SCC_ALT_BUTTONS_TX_MSGS(longitudinal) \
HYUNDAI_CANFD_ALT_CRUISE_BUTTON_TX_MSGS(2) \
HYUNDAI_CANFD_LFA_STEERING_COMMON_TX_MSGS(0) \
HYUNDAI_CANFD_SCC_CONTROL_COMMON_TX_MSGS(0, (longitudinal)) \
{0x160, 0, 16, .check_relay = (longitudinal)}, /* ADRV_0x160 */ \
#define HYUNDAI_CANFD_LFA_STEERING_CAMERA_SCC_CCNC_TX_MSGS(longitudinal) \
HYUNDAI_CANFD_CRUISE_BUTTON_TX_MSGS(2) \
HYUNDAI_CANFD_LFA_STEERING_COMMON_TX_MSGS(0) \
@@ -464,6 +468,15 @@ static safety_config hyundai_canfd_init(uint16_t param) {
{0x7C4, 2, 8, .check_relay = true}, /* camera support frame */ \
{0xEA, 2, 24, .check_relay = true}, /* MDPS support frame */ \
#define HYUNDAI_CANFD_LFA_STEERING_CAMERA_SCC_CCNC_ALT_BUTTONS_TX_MSGS(longitudinal) \
HYUNDAI_CANFD_ALT_CRUISE_BUTTON_TX_MSGS(2) \
HYUNDAI_CANFD_LFA_STEERING_COMMON_TX_MSGS(0) \
HYUNDAI_CANFD_SCC_CONTROL_COMMON_TX_MSGS(0, (longitudinal)) \
{0x161, 0, 32, .check_relay = true}, /* CCNC_0x161 */ \
{0x162, 0, 32, .check_relay = true}, /* CCNC_0x162 */ \
{0x7C4, 2, 8, .check_relay = true}, /* camera support frame */ \
{0xEA, 2, 24, .check_relay = true}, /* MDPS support frame */ \
hyundai_common_init(param);
gen_crc_lookup_table_16(0x1021, hyundai_canfd_crc_lut);
@@ -527,7 +540,19 @@ static safety_config hyundai_canfd_init(uint16_t param) {
if (hyundai_camera_scc) {
if (hyundai_ccnc) {
SET_TX_MSGS(hyundai_canfd_lfa_steering_camera_scc_ccnc_tx_msgs, ret);
if (hyundai_canfd_alt_buttons) {
static CanMsg hyundai_canfd_lfa_steering_camera_scc_ccnc_alt_buttons_tx_msgs[] = {
HYUNDAI_CANFD_LFA_STEERING_CAMERA_SCC_CCNC_ALT_BUTTONS_TX_MSGS(true)
};
SET_TX_MSGS(hyundai_canfd_lfa_steering_camera_scc_ccnc_alt_buttons_tx_msgs, ret);
} else {
SET_TX_MSGS(hyundai_canfd_lfa_steering_camera_scc_ccnc_tx_msgs, ret);
}
} else if (hyundai_canfd_alt_buttons) {
static CanMsg hyundai_canfd_lfa_steering_camera_scc_alt_buttons_tx_msgs[] = {
HYUNDAI_CANFD_LFA_STEERING_CAMERA_SCC_ALT_BUTTONS_TX_MSGS(true)
};
SET_TX_MSGS(hyundai_canfd_lfa_steering_camera_scc_alt_buttons_tx_msgs, ret);
} else {
SET_TX_MSGS(hyundai_canfd_lfa_steering_camera_scc_tx_msgs, ret);
}
@@ -555,9 +580,7 @@ static safety_config hyundai_canfd_init(uint16_t param) {
} else {
SET_RX_CHECKS(hyundai_canfd_lka_steering_rx_checks, ret);
}
if (hyundai_ccnc && hyundai_canfd_angle_steering && hyundai_canfd_lka_steering_alt) {
SET_TX_MSGS(HYUNDAI_CANFD_CCNC_ANGLE_FALLBACK_TX_MSGS, ret);
} else if (hyundai_canfd_lka_steering_alt) {
if (hyundai_canfd_lka_steering_alt) {
if (hyundai_canfd_alt_buttons) {
SET_TX_MSGS(HYUNDAI_CANFD_LKA_STEERING_ALT_ALT_BUTTONS_TX_MSGS, ret);
} else {
@@ -613,8 +636,22 @@ static safety_config hyundai_canfd_init(uint16_t param) {
HYUNDAI_CANFD_LFA_STEERING_CAMERA_SCC_CCNC_TX_MSGS(false)
};
static CanMsg hyundai_canfd_lfa_steering_camera_scc_alt_buttons_tx_msgs[] = {
HYUNDAI_CANFD_LFA_STEERING_CAMERA_SCC_ALT_BUTTONS_TX_MSGS(false)
};
static CanMsg hyundai_canfd_lfa_steering_camera_scc_ccnc_alt_buttons_tx_msgs[] = {
HYUNDAI_CANFD_LFA_STEERING_CAMERA_SCC_CCNC_ALT_BUTTONS_TX_MSGS(false)
};
if (hyundai_ccnc) {
SET_TX_MSGS(hyundai_canfd_lfa_steering_camera_scc_ccnc_tx_msgs, ret);
if (hyundai_canfd_alt_buttons) {
SET_TX_MSGS(hyundai_canfd_lfa_steering_camera_scc_ccnc_alt_buttons_tx_msgs, ret);
} else {
SET_TX_MSGS(hyundai_canfd_lfa_steering_camera_scc_ccnc_tx_msgs, ret);
}
} else if (hyundai_canfd_alt_buttons) {
SET_TX_MSGS(hyundai_canfd_lfa_steering_camera_scc_alt_buttons_tx_msgs, ret);
} else {
SET_TX_MSGS(hyundai_canfd_lfa_steering_camera_scc_tx_msgs, ret);
}
@@ -459,12 +459,45 @@ class TestHyundaiCanfdAltButtonFlagIsolation(unittest.TestCase):
self.safety.safety_rx_hook(self._button_msg(lka=True))
self.safety.safety_rx_hook(self._button_msg())
self.assertTrue(self.safety.get_lkas_on())
self.safety.safety_rx_hook(self._button_msg(main=True))
self.safety.safety_rx_hook(self._button_msg())
self.assertTrue(self.safety.get_lkas_on())
class TestHyundaiCanfdCcncAltButtonResume(unittest.TestCase):
TX_MSGS = [[0x1AA, 2]]
def setUp(self):
self.packer = CANPackerSafety("hyundai_canfd_generated")
self.safety = libsafety_py.libsafety
self.safety.set_safety_hooks(
CarParams.SafetyModel.hyundaiCanfd,
HyundaiSafetyFlags.CCNC | HyundaiSafetyFlags.CAMERA_SCC | HyundaiSafetyFlags.CANFD_ALT_BUTTONS,
)
self.safety.init_tests()
def _resume_msg(self):
return self.packer.make_can_msg_safety(
"CRUISE_BUTTONS_ALT", 2, {"CRUISE_BUTTONS": Buttons.RESUME},
)
def test_resume_allowed_only_when_controls_are_allowed(self):
self.safety.set_controls_allowed(True)
self.assertTrue(self.safety.safety_tx_hook(self._resume_msg()))
self.safety.set_controls_allowed(False)
self.assertFalse(self.safety.safety_tx_hook(self._resume_msg()))
def test_alternate_button_frame_is_blocked_without_flag(self):
self.safety.set_safety_hooks(
CarParams.SafetyModel.hyundaiCanfd,
HyundaiSafetyFlags.CCNC | HyundaiSafetyFlags.CAMERA_SCC,
)
self.safety.init_tests()
self.safety.set_controls_allowed(True)
self.assertFalse(self.safety.safety_tx_hook(self._resume_msg()))
class TestHyundaiCanfdCCNCSupportFrames(common.SafetyTestBase):
TX_MSGS = [[0x161, 0], [0x162, 0], [0x7C4, 2], [0xEA, 2]]
@@ -793,12 +826,19 @@ class TestHyundaiCanfdLKASteeringAltAngleLongEV(HyundaiLongitudinalBase, TestHyu
with self.subTest(address=address):
self.assertFalse(self._tx(common.make_msg(1 if address != 0x51 else 0, address, length)))
def test_ccnc_angle_fallback_allows_lfa_status_without_longitudinal_control(self):
def test_ccnc_angle_fallback_allows_lateral_only(self):
fallback_param = (self.SAFETY_PARAM & ~HyundaiSafetyFlags.LONG) | HyundaiSafetyFlags.CCNC
self.safety.set_safety_hooks(CarParams.SafetyModel.hyundaiCanfd, fallback_param)
self.safety.init_tests()
self.assertTrue(self._tx(common.make_msg(1, 0x12A, 16)))
self._rx(self._gear_msg(5))
self._reset_speed_measurement(self.STANDSTILL_THRESHOLD + 1)
self._reset_angle_measurement(0)
self._set_prev_desired_angle(0)
self.safety.set_controls_allowed(True)
self.assertTrue(self._tx(self._angle_cmd_msg(0, enabled=True)))
self.assertFalse(self._tx(common.make_msg(1, 0x12A, 16)))
self.assertFalse(self._tx(common.make_msg(1, 0x1A0, 32)))
def test_ccnc_angle_long_uses_second_mdps_angle(self):
+1 -1
View File
@@ -1,2 +1,2 @@
extern const uint8_t gitversion[19];
const uint8_t gitversion[19] = "DEV-46ae2472-DEBUG";
const uint8_t gitversion[19] = "DEV-83cec26d-DEBUG";
+1 -1
View File
@@ -1 +1 @@
DEV-46ae2472-DEBUG
DEV-83cec26d-DEBUG
-4
View File
@@ -69,10 +69,6 @@ class VCruiseHelper:
def _get_cruise_delta_intervals(self, starpilot_toggles: SimpleNamespace) -> tuple[float, float]:
short_interval = self._get_cruise_delta_interval(getattr(starpilot_toggles, "cruise_increase", None))
long_interval = self._get_cruise_delta_interval(getattr(starpilot_toggles, "cruise_increase_long", None))
if getattr(starpilot_toggles, "reverse_cruise_increase", False):
return long_interval, short_interval
return short_interval, long_interval
@property
-33
View File
@@ -73,7 +73,6 @@ class TestVCruiseHelper:
cruise_increase=1,
cruise_increase_long=5,
is_metric=False,
reverse_cruise_increase=False,
set_speed_limit=False,
)
self.reset_cruise_speed_state()
@@ -493,7 +492,6 @@ class TestVCruiseHelperRedneck:
cruise_increase=1,
cruise_increase_long=5,
is_metric=False,
reverse_cruise_increase=False,
set_speed_limit=False,
)
@@ -555,34 +553,3 @@ class TestVCruiseHelperRedneck:
assert self.v_cruise_helper.v_cruise_kph == pytest.approx(75 * CV.MPH_TO_KPH)
assert self.v_cruise_helper.v_cruise_cluster_kph == pytest.approx(75 * CV.MPH_TO_KPH)
def test_reverse_cruise_increase_swaps_short_and_long_press_intervals(self):
self.enable(55 * CV.MPH_TO_MS, experimental_mode=False)
initial_v_cruise_kph = self.v_cruise_helper.v_cruise_kph
self.starpilot_toggles.cruise_increase = 1
self.starpilot_toggles.cruise_increase_long = 5
self.starpilot_toggles.reverse_cruise_increase = True
pressed_cs = car.CarState(cruiseState={"available": True})
pressed_cs.buttonEvents = [ButtonEvent(type=ButtonType.accelCruise, pressed=True)]
self.v_cruise_helper.update_v_cruise(
pressed_cs,
enabled=True,
is_metric=False,
speed_limit_changed=False,
starpilot_toggles=self.starpilot_toggles,
)
released_cs = car.CarState(cruiseState={"available": True})
released_cs.buttonEvents = [ButtonEvent(type=ButtonType.accelCruise, pressed=False)]
self.v_cruise_helper.update_v_cruise(
released_cs,
enabled=True,
is_metric=False,
speed_limit_changed=False,
starpilot_toggles=self.starpilot_toggles,
)
reversed_interval = 5 * IMPERIAL_INCREMENT
expected_kph = math.ceil(initial_v_cruise_kph / reversed_interval) * reversed_interval
assert self.v_cruise_helper.v_cruise_kph == pytest.approx(expected_kph)
-1
View File
@@ -44,7 +44,6 @@ def get_test_starpilot_toggles() -> SimpleNamespace:
disable_openpilot_long=False,
force_fingerprint=False,
lock_doors=False,
reverse_cruise_increase=False,
sng_hack=False,
subaru_sng=False,
subaru_sng_manual_parking_brake=False,
@@ -93,6 +93,7 @@ class LatControlTorque(LatControl):
self.low_speed_reset_threshold = max(CP.minSteerSpeed, MIN_LATERAL_CONTROL_SPEED)
self.steer_release_i_decay = 0.8
self.prev_steering_pressed = False
self.prev_output_torque = 0.0
self.debug_counter = 0
self.prev_desired_lateral_accel = 0.0
self.starpilot_lateral_state = custom.StarPilotLateralState.new_message()
@@ -231,6 +232,7 @@ class LatControlTorque(LatControl):
future_desired_lateral_accel = desired_curvature * CS.vEgo ** 2
if not active:
output_torque = 0.0
self.prev_output_torque = 0.0
pid_log.active = False
self._clear_starpilot_lateral_state()
self.pid.reset()
@@ -541,6 +543,9 @@ class LatControlTorque(LatControl):
-low_speed_center_output_limit,
low_speed_center_output_limit,
))
output_torque = get_bolt_2022_2023_low_speed_center_output(
output_torque, self.prev_output_torque, setpoint, CS.vEgo,
)
elif self.is_bolt_2017:
output_torque *= get_bolt_2017_torque_scale(setpoint, desired_lateral_jerk, CS.vEgo)
elif bolt_2018_2021_tuned_path_active:
@@ -658,6 +663,7 @@ class LatControlTorque(LatControl):
self.starpilot_lateral_state.lowSpeedFactor = float(low_speed_factor)
self.starpilot_lateral_state.unwindDetected = bool(unwind_detected)
pid_log.saturated = bool(self._check_saturation(self.steer_max - abs(output_torque) < 1e-3, CS, steer_limited_by_safety, curvature_limited))
self.prev_output_torque = float(output_torque)
if DEBUG_TORQUE_TUNE and self.is_bolt:
self.debug_counter += 1
@@ -376,6 +376,8 @@ BOLT_2022_2023_LOW_SPEED_CENTER_OUTPUT_SPEED = 2.5
BOLT_2022_2023_LOW_SPEED_CENTER_OUTPUT_SPEED_WIDTH = 0.7
BOLT_2022_2023_LOW_SPEED_CENTER_OUTPUT_SPEED_MAX = 7.2
BOLT_2022_2023_LOW_SPEED_CENTER_OUTPUT_SPEED_MAX_WIDTH = 0.5
BOLT_2022_2023_LOW_SPEED_CENTER_OUTPUT_SCALE_MIN = 0.62
BOLT_2022_2023_LOW_SPEED_CENTER_OUTPUT_ALPHA_MIN = 0.28
BOLT_2022_2023_CENTER_FRICTION_THRESHOLD_BUMP = 0.080
BOLT_2022_2023_CENTER_FRICTION_THRESHOLD_LAT = 0.18
BOLT_2022_2023_CENTER_FRICTION_THRESHOLD_LAT_WIDTH = 0.06
@@ -457,7 +459,7 @@ SONATA_HYBRID_LOW_SPEED_CENTER_TAPER_LAT = 0.10
SONATA_HYBRID_LOW_SPEED_CENTER_TAPER_LAT_WIDTH = 0.02
SONATA_HYBRID_LOW_SPEED_CENTER_TAPER_SPEED_MAX = 7.5
SONATA_HYBRID_LOW_SPEED_CENTER_TAPER_SPEED_WIDTH = 1.0
SONATA_HYBRID_CENTER_OUTPUT_TAPER_MAX = 0.08
SONATA_HYBRID_CENTER_OUTPUT_TAPER_MAX = 0.14
SONATA_HYBRID_CENTER_OUTPUT_TAPER_LAT = 0.18
SONATA_HYBRID_CENTER_OUTPUT_TAPER_LAT_WIDTH = 0.05
SONATA_HYBRID_CENTER_OUTPUT_TAPER_SPEED = 12.5
@@ -2267,6 +2269,27 @@ def get_bolt_2022_2023_low_speed_center_output_limit(desired_lateral_accel: floa
return 1.0 - reduction
def get_bolt_2022_2023_low_speed_center_output(output_torque: float, prev_output_torque: float,
desired_lateral_accel: float, v_ego: float) -> float:
"""Damp low-speed center reversals without reducing real turn authority."""
speed_weight = _bolt_2022_2023_sigmoid(
(v_ego - BOLT_2022_2023_LOW_SPEED_CENTER_OUTPUT_SPEED) /
BOLT_2022_2023_LOW_SPEED_CENTER_OUTPUT_SPEED_WIDTH
) * _bolt_2022_2023_sigmoid(
(BOLT_2022_2023_LOW_SPEED_CENTER_OUTPUT_SPEED_MAX - v_ego) /
BOLT_2022_2023_LOW_SPEED_CENTER_OUTPUT_SPEED_MAX_WIDTH
)
center_weight = _bolt_2022_2023_sigmoid(
(BOLT_2022_2023_LOW_SPEED_CENTER_OUTPUT_LAT - abs(desired_lateral_accel)) /
BOLT_2022_2023_LOW_SPEED_CENTER_OUTPUT_LAT_WIDTH
)
envelope = speed_weight * center_weight
output_scale = 1.0 - ((1.0 - BOLT_2022_2023_LOW_SPEED_CENTER_OUTPUT_SCALE_MIN) * envelope)
output_alpha = 1.0 - ((1.0 - BOLT_2022_2023_LOW_SPEED_CENTER_OUTPUT_ALPHA_MIN) * envelope)
limited_output = output_torque * output_scale
return float(prev_output_torque + output_alpha * (limited_output - prev_output_torque))
def get_bolt_2022_2023_friction_threshold(v_ego: float, desired_lateral_accel: float = 0.0, desired_lateral_jerk: float = 0.0) -> float:
base_threshold = get_gm_base_friction_threshold(v_ego)
center_weight = _bolt_2022_2023_sigmoid(
@@ -123,6 +123,13 @@ class LongControlVehicleTuning:
getattr(CP, "carFingerprint", None) in (CAR.CHEVROLET_SILVERADO, CAR.CHEVROLET_SILVERADO_CC) and
not CP.enableGasInterceptorDEPRECATED
)
self.is_toyota_sienna = bool(
CP.brand == "toyota" and
str(getattr(CP, "carFingerprint", "")) in (
str(TOYOTA_CAR.TOYOTA_SIENNA),
str(TOYOTA_CAR.TOYOTA_SIENNA_4TH_GEN),
)
)
self.is_toyota_sienna_4g = bool(
CP.brand == "toyota" and
str(getattr(CP, "carFingerprint", "")) == str(TOYOTA_CAR.TOYOTA_SIENNA_4TH_GEN)
@@ -268,7 +275,7 @@ class LongControlVehicleTuning:
def shape_toyota_sienna_accel_target(self, a_target, v_ego, should_stop, leads=None):
"""Smooth Sienna lead braking only while there is still comfortable stopping room."""
if not self.is_toyota_sienna_4g or should_stop:
if not self.is_toyota_sienna or should_stop:
self.toyota_sienna_target_filter_initialized = False
return a_target
+38 -12
View File
@@ -33,6 +33,11 @@ from openpilot.selfdrive.controls.lib.longitudinal_vehicle_tunes import (
get_toyota_sienna_post_departure_restop_cap,
get_untracked_slow_lead_decel_scale,
get_toyota_prius_stopped_lead_obstacle_bias,
get_honda_crv_5g_stopped_lead_obstacle_bias,
get_honda_crv_5g_low_speed_stopped_lead_cap,
allow_honda_crv_5g_vision_gap_settle,
get_standstill_gap_settle_max_extra_gap,
get_standstill_stopped_lead_guard_distance_margin,
)
from openpilot.selfdrive.controls.lib.drive_helpers import CONTROL_N
from openpilot.selfdrive.car.cruise import V_CRUISE_UNSET
@@ -1365,8 +1370,9 @@ class LongitudinalPlanner:
))
@staticmethod
def is_radar_standstill_gap_settle_candidate(lead, v_ego, target_gap, active=False):
if lead is None or not lead.status or not bool(getattr(lead, "radar", False)):
def is_radar_standstill_gap_settle_candidate(lead, v_ego, target_gap, active=False,
allow_vision=False, max_extra_gap=RADAR_STANDSTILL_GAP_SETTLE_MAX_EXTRA_GAP):
if lead is None or not lead.status or (not bool(getattr(lead, "radar", False)) and not allow_vision):
return False
if float(v_ego) > RADAR_STANDSTILL_GAP_SETTLE_MAX_EGO_SPEED:
return False
@@ -1374,15 +1380,20 @@ class LongitudinalPlanner:
return False
if abs(float(getattr(lead, "vLead", 0.0))) > RADAR_STANDSTILL_GAP_SETTLE_MAX_LEAD_SPEED:
return False
if allow_vision and (
bool(getattr(lead, "radar", False)) or
float(getattr(lead, "modelProb", 0.0)) < 0.99
):
return False
lead_gap = float(getattr(lead, "dRel", 0.0))
min_margin = RADAR_STANDSTILL_GAP_SETTLE_EXIT_MARGIN if active else RADAR_STANDSTILL_GAP_SETTLE_ENTRY_MARGIN
return bool(
lead_gap > target_gap + min_margin and
lead_gap <= target_gap + RADAR_STANDSTILL_GAP_SETTLE_MAX_EXTRA_GAP
lead_gap <= target_gap + float(max_extra_gap)
)
def update_radar_standstill_gap_settle(self, sm, target_gap):
def update_radar_standstill_gap_settle(self, sm, target_gap, allow_vision=False, max_extra_gap=None):
vetoed = bool(
getattr(sm["carState"], "brakePressed", False) or
getattr(sm["carState"], "gasPressed", False) or
@@ -1396,6 +1407,8 @@ class LongitudinalPlanner:
float(sm["carState"].vEgo),
target_gap,
active=self.radar_standstill_gap_settle_active,
allow_vision=allow_vision,
max_extra_gap=(RADAR_STANDSTILL_GAP_SETTLE_MAX_EXTRA_GAP if max_extra_gap is None else max_extra_gap),
)
]
@@ -1621,7 +1634,7 @@ class LongitudinalPlanner:
lead_delta = lead_speed - float(v_ego)
max_distance = max(
STANDSTILL_STOPPED_LEAD_GUARD_MIN_DISTANCE,
float(stop_distance) + STANDSTILL_STOPPED_LEAD_GUARD_DISTANCE_MARGIN,
float(stop_distance) + get_standstill_stopped_lead_guard_distance_margin(self.CP),
)
if (
float(getattr(lead, "dRel", float("inf"))) > max_distance or
@@ -2209,7 +2222,7 @@ class LongitudinalPlanner:
get_force_stop_distance_bias(self.CP.carFingerprint)
)
prius_lead_obstacle_bias = (0.0, 0.0)
stopped_lead_obstacle_bias = (0.0, 0.0)
if (
self.mode == 'acc' and
not bool(getattr(sm['modelV2'].action, 'shouldStop', False)) and
@@ -2217,9 +2230,12 @@ class LongitudinalPlanner:
not bool(getattr(sm['starpilotPlan'], 'forcingStop', False)) and
not bool(getattr(sm['carState'], 'standstill', False))
):
prius_lead_obstacle_bias = (
get_toyota_prius_stopped_lead_obstacle_bias(self.CP, self.lead_one, scene_v_ego),
get_toyota_prius_stopped_lead_obstacle_bias(self.CP, self.lead_two, scene_v_ego),
stopped_lead_obstacle_bias = tuple(
max(
get_toyota_prius_stopped_lead_obstacle_bias(self.CP, lead, scene_v_ego),
get_honda_crv_5g_stopped_lead_obstacle_bias(self.CP, lead, scene_v_ego),
)
for lead in (self.lead_one, self.lead_two)
)
self.mpc.update(sm['radarState'], v_cruise, x, v, a, j,
@@ -2230,7 +2246,7 @@ class LongitudinalPlanner:
stop_x=force_stop_x,
silverado_early_follow=early_truck_follow,
modelV2=sm['modelV2'],
lead_obstacle_bias=prius_lead_obstacle_bias)
lead_obstacle_bias=stopped_lead_obstacle_bias)
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)
@@ -2377,6 +2393,11 @@ class LongitudinalPlanner:
if rav4_early_lead_cap is not None:
rav4_early_lead_caps.append(rav4_early_lead_cap)
cap = self.get_close_lead_brake_cap(lead, v_ego, output_accel_min)
if cap is not None:
close_lead_caps.append(cap)
cap = get_honda_crv_5g_low_speed_stopped_lead_cap(
self.CP, lead, v_ego, vision_cap_accel_min,
)
if cap is not None:
close_lead_caps.append(cap)
slow_stop_cap = self.get_vision_slow_stopped_lead_cap(lead, v_ego, vision_cap_accel_min, effective_t_follow)
@@ -2498,8 +2519,13 @@ class LongitudinalPlanner:
self.slow_creep_lead_depart_elapsed >= STANDSTILL_LEAD_CREEP_RELEASE_CONFIRM_TIME
)
radar_gap_settle_active = False
if allow_radar_standstill_gap_settle(self.CP):
radar_gap_settle_active = self.update_radar_standstill_gap_settle(sm, standstill_nudge_gap)
if allow_radar_standstill_gap_settle(self.CP) or allow_honda_crv_5g_vision_gap_settle(self.CP):
radar_gap_settle_active = self.update_radar_standstill_gap_settle(
sm,
standstill_nudge_gap,
allow_vision=allow_honda_crv_5g_vision_gap_settle(self.CP),
max_extra_gap=get_standstill_gap_settle_max_extra_gap(self.CP),
)
else:
self.radar_standstill_gap_settle_elapsed = 0.0
self.radar_standstill_gap_settle_active = False
@@ -47,12 +47,32 @@ TOYOTA_PRIUS_STOPPED_LEAD_MIN_CLOSING_SPEED = 0.15
TOYOTA_PRIUS_STOPPED_LEAD_MAX_DISTANCE = 80.0
TOYOTA_PRIUS_STOPPED_LEAD_RAMP_DISTANCE = 10.0
TOYOTA_PRIUS_STOPPED_LEAD_MAX_LATERAL_OFFSET = 1.75
HONDA_CRV_5G_STOPPED_LEAD_OBSTACLE_BIAS_M = 1.0
HONDA_CRV_5G_STOPPED_LEAD_MAX_EGO_SPEED = 22.0
HONDA_CRV_5G_STOPPED_LEAD_MAX_SPEED = 1.0
HONDA_CRV_5G_STOPPED_LEAD_MIN_CLOSING_SPEED = 0.15
HONDA_CRV_5G_STOPPED_LEAD_MAX_DISTANCE = 80.0
HONDA_CRV_5G_STOPPED_LEAD_RAMP_DISTANCE = 10.0
HONDA_CRV_5G_STOPPED_LEAD_MAX_LATERAL_OFFSET = 1.75
HONDA_CRV_5G_LOW_SPEED_STOP_MAX_EGO_SPEED = 4.5
HONDA_CRV_5G_LOW_SPEED_STOP_MAX_LEAD_SPEED = 0.5
HONDA_CRV_5G_LOW_SPEED_STOP_MIN_MODEL_PROB = 0.99
HONDA_CRV_5G_LOW_SPEED_STOP_MAX_DISTANCE = 12.0
HONDA_CRV_5G_LOW_SPEED_STOP_MIN_DISTANCE = 6.5
HONDA_CRV_5G_LOW_SPEED_STOP_MIN_CLOSING_SPEED = 0.15
HONDA_CRV_5G_LOW_SPEED_STOP_MAX_LEAD_ACCEL = 0.25
HONDA_CRV_5G_LOW_SPEED_STOP_MAX_DECEL = 0.45
HONDA_CRV_5G_LOW_SPEED_STOP_MIN_DECEL = 0.12
HONDA_CRV_5G_GAP_SETTLE_MAX_EXTRA_GAP = 7.0
HONDA_CRV_5G_GUARD_DISTANCE_MARGIN = 1.5
TOYOTA_CAMRY_TSS2_FORCE_STOP_HANDOFF_M = 4.5
# The Camry's force-stop path otherwise consumes the model endpoint before the
# normal MPC stop-distance margin can be applied. Keep it within the forward
# offset range exposed by the Force Stop setting.
TOYOTA_CAMRY_TSS2_FORCE_STOP_DISTANCE_BIAS_M = 6.0
DEFAULT_FORCE_STOP_HANDOFF_M = 6.0
HYUNDAI_SANTA_FE_2022_FORCE_STOP_REANCHOR_SPEED_TOLERANCE = 0.25
HYUNDAI_SANTA_FE_2022_FORCE_STOP_LOW_SPEED_HOLD = 2.5
def get_toyota_prius_stopped_lead_obstacle_bias(CP, lead, v_ego):
@@ -84,6 +104,92 @@ def get_toyota_prius_stopped_lead_obstacle_bias(CP, lead, v_ego):
return float(min(bias, max(distance - 0.5, 0.0)))
def is_honda_crv_5g(CP):
return (
getattr(CP, "brand", "") == "honda" and
str(getattr(CP, "carFingerprint", "")) == "HONDA_CRV_5G"
)
def get_honda_crv_5g_stopped_lead_obstacle_bias(CP, lead, v_ego):
"""Bring the CR-V's vision stopped-lead target in without changing stops."""
if (
not is_honda_crv_5g(CP) or
lead is None or not bool(getattr(lead, "status", False)) or
float(v_ego) <= 0.0 or float(v_ego) > HONDA_CRV_5G_STOPPED_LEAD_MAX_EGO_SPEED or
float(getattr(lead, "vLead", 0.0)) > HONDA_CRV_5G_STOPPED_LEAD_MAX_SPEED or
bool(getattr(lead, "radar", False)) or
float(getattr(lead, "modelProb", 0.0)) < 0.95 or
abs(float(getattr(lead, "yRel", 0.0))) > HONDA_CRV_5G_STOPPED_LEAD_MAX_LATERAL_OFFSET
):
return 0.0
distance = float(getattr(lead, "dRel", float("inf")))
closing_speed = float(v_ego) - float(getattr(lead, "vLead", 0.0))
if (
distance <= 0.0 or distance > HONDA_CRV_5G_STOPPED_LEAD_MAX_DISTANCE or
closing_speed < HONDA_CRV_5G_STOPPED_LEAD_MIN_CLOSING_SPEED
):
return 0.0
strength = np.clip(
(HONDA_CRV_5G_STOPPED_LEAD_MAX_DISTANCE - distance) /
(HONDA_CRV_5G_STOPPED_LEAD_MAX_DISTANCE - HONDA_CRV_5G_STOPPED_LEAD_RAMP_DISTANCE),
0.0, 1.0,
)
bias = HONDA_CRV_5G_STOPPED_LEAD_OBSTACLE_BIAS_M * strength
return float(min(bias, max(distance - 0.5, 0.0)))
def get_honda_crv_5g_low_speed_stopped_lead_cap(CP, lead, v_ego, accel_min):
"""Bleed a CR-V crawl into the normal standstill gap without a hard jab."""
if (
not is_honda_crv_5g(CP) or
lead is None or not bool(getattr(lead, "status", False)) or
bool(getattr(lead, "radar", False)) or
float(getattr(lead, "modelProb", 0.0)) < HONDA_CRV_5G_LOW_SPEED_STOP_MIN_MODEL_PROB or
float(v_ego) <= 0.0 or float(v_ego) > HONDA_CRV_5G_LOW_SPEED_STOP_MAX_EGO_SPEED
):
return None
lead_speed = max(float(getattr(lead, "vLead", 0.0)), 0.0)
distance = float(getattr(lead, "dRel", float("inf")))
if (
lead_speed > HONDA_CRV_5G_LOW_SPEED_STOP_MAX_LEAD_SPEED or
float(getattr(lead, "aLeadK", 0.0)) > HONDA_CRV_5G_LOW_SPEED_STOP_MAX_LEAD_ACCEL or
distance < HONDA_CRV_5G_LOW_SPEED_STOP_MIN_DISTANCE or
distance > HONDA_CRV_5G_LOW_SPEED_STOP_MAX_DISTANCE or
float(v_ego) - lead_speed < HONDA_CRV_5G_LOW_SPEED_STOP_MIN_CLOSING_SPEED or
abs(float(getattr(lead, "yRel", 0.0))) > HONDA_CRV_5G_STOPPED_LEAD_MAX_LATERAL_OFFSET
):
return None
available_gap = max(distance - 6.0, 1.0)
required_decel = float(v_ego) ** 2 / (2.0 * available_gap)
decel = float(np.clip(
required_decel * 0.85,
HONDA_CRV_5G_LOW_SPEED_STOP_MIN_DECEL,
HONDA_CRV_5G_LOW_SPEED_STOP_MAX_DECEL,
))
return max(float(accel_min), -decel)
def allow_honda_crv_5g_vision_gap_settle(CP):
return is_honda_crv_5g(CP)
def get_standstill_gap_settle_max_extra_gap(CP):
if is_honda_crv_5g(CP):
return HONDA_CRV_5G_GAP_SETTLE_MAX_EXTRA_GAP
return 1.5
def get_standstill_stopped_lead_guard_distance_margin(CP):
if is_honda_crv_5g(CP):
return HONDA_CRV_5G_GUARD_DISTANCE_MARGIN
return 3.0
def is_toyota_rav4_tss2_post_departure_tune(CP):
"""Identify RAV4 TSS2 variants that need normal catch-up caps after departure."""
return (
@@ -285,3 +391,17 @@ def get_force_stop_distance_bias(car_fingerprint):
if str(car_fingerprint) == "TOYOTA_CAMRY_TSS2":
return TOYOTA_CAMRY_TSS2_FORCE_STOP_DISTANCE_BIAS_M
return 0.0
def get_force_stop_reanchor_speed_tolerance(car_params):
"""Keep the Santa Fe stop distance from reopening after braking begins."""
if str(getattr(car_params, "carFingerprint", car_params)) == "HYUNDAI_SANTA_FE_2022":
return HYUNDAI_SANTA_FE_2022_FORCE_STOP_REANCHOR_SPEED_TOLERANCE
return None
def get_force_stop_low_speed_hold(car_params):
"""Keep a committed Santa Fe stop from releasing while it is still rolling."""
if str(getattr(car_params, "carFingerprint", car_params)) == "HYUNDAI_SANTA_FE_2022":
return HYUNDAI_SANTA_FE_2022_FORCE_STOP_LOW_SPEED_HOLD
return None
@@ -65,6 +65,7 @@ from openpilot.selfdrive.controls.lib.latcontrol_torque import (
get_bolt_2022_2023_ff_scale,
get_bolt_2022_2023_center_output_scale,
get_bolt_2022_2023_low_speed_center_output_limit,
get_bolt_2022_2023_low_speed_center_output,
get_bolt_2022_2023_friction_scale,
get_bolt_2022_2023_friction_threshold,
get_trailer_lateral_ff_scale,
@@ -333,6 +334,15 @@ class TestLatControl:
assert low_speed_turn > 0.98
assert normal_speed_center > 0.98
def test_bolt_2022_2023_low_speed_center_output_damps_reversals(self):
low_speed = get_bolt_2022_2023_low_speed_center_output(1.0, -1.0, 0.05, 4.2)
large_turn = get_bolt_2022_2023_low_speed_center_output(1.0, -1.0, 0.40, 4.2)
highway = get_bolt_2022_2023_low_speed_center_output(1.0, -1.0, 0.05, 9.0)
assert abs(low_speed) < 0.50
assert abs(large_turn) > abs(low_speed)
assert highway > low_speed
def test_bolt_2022_2023_friction_threshold_curve(self):
base = get_gm_base_friction_threshold(6.0)
left_turn_in = get_bolt_2022_2023_friction_threshold(6.0, 0.7, 0.8)
@@ -906,6 +916,7 @@ class TestLatControl:
assert low_speed > center
assert turn > center
assert turn > 0.99
assert center > 0.85
def test_ioniq_5_ff_scale_curve(self):
assert get_ioniq_5_ff_scale(0.0, 0.0, 20.0) == 1.0
@@ -1488,6 +1499,26 @@ class TestLatControl:
assert lac_log.active
def test_bolt_2022_2023_low_speed_center_output_update_path(self, monkeypatch):
calls = []
def record_call(output_torque, prev_output_torque, desired_lateral_accel, v_ego):
calls.append((output_torque, prev_output_torque, desired_lateral_accel, v_ego))
return 0.0
monkeypatch.setattr(latcontrol_torque, "get_bolt_2022_2023_low_speed_center_output", record_call)
controller, VM, CS, params, starpilot_toggles = self._build_torque_controller(GM.CHEVROLET_BOLT_ACC_2022_2023)
CS.vEgo = 4.0
output, _, lac_log = controller.update(
True, CS, VM, params, False, 0.0025, False, 0.2, None, None, starpilot_toggles,
)
assert lac_log.active
assert output == 0.0
assert calls
assert calls[0][3] == pytest.approx(4.0)
def test_volt_standard_testing_ground_update_path(self, monkeypatch):
controller, VM, CS, params, starpilot_toggles = self._build_torque_controller(GM.CHEVROLET_VOLT_ASCM)
monkeypatch.setattr(latcontrol_torque, "volt_standard_lateral_testing_ground_active", lambda: True)
@@ -1235,6 +1235,17 @@ def test_toyota_sienna_target_filter_smooths_mild_high_speed_handoffs():
assert -0.20 < filtered < 0.30
def test_toyota_sienna_2019_target_filter_smooths_mild_high_speed_handoffs():
CP = make_longcontrol_cp(brand="toyota", carFingerprint="TOYOTA_SIENNA")
tuning = vehicle_tunes.LongControlVehicleTuning(CP)
assert tuning.shape_toyota_sienna_accel_target(0.30, 20.0, False) == pytest.approx(0.30)
filtered = tuning.shape_toyota_sienna_accel_target(-0.20, 20.0, False)
assert -0.20 < filtered < 0.30
def test_toyota_sienna_target_filter_smooths_nonurgent_low_speed_lead_braking():
CP = make_longcontrol_cp(brand="toyota", carFingerprint=TOYOTA_CAR.TOYOTA_SIENNA_4TH_GEN)
tuning = vehicle_tunes.LongControlVehicleTuning(CP)
@@ -28,6 +28,10 @@ from openpilot.selfdrive.controls.lib.longitudinal_vehicle_tunes import (
get_far_follow_output_slew_rates,
get_follow_prebrake_min_headway,
get_honda_accord_lead_departure_tune,
get_honda_crv_5g_stopped_lead_obstacle_bias,
get_honda_crv_5g_low_speed_stopped_lead_cap,
allow_honda_crv_5g_vision_gap_settle,
get_standstill_gap_settle_max_extra_gap,
get_toyota_prius_stopped_lead_obstacle_bias,
get_toyota_rav4_tss2_lead_departure_tune,
get_toyota_rav4_tss2_early_lead_cap,
@@ -116,6 +120,50 @@ def test_prius_stopped_lead_obstacle_bias_does_not_apply_at_standstill_or_to_dep
assert get_toyota_prius_stopped_lead_obstacle_bias(prius, departing_lead, v_ego=8.0) == pytest.approx(0.0)
def test_honda_crv_5g_stopped_lead_tune_is_vehicle_specific():
crv = CarInterface.get_non_essential_params(CAR.HONDA_CRV_5G)
civic = CarInterface.get_non_essential_params(CAR.HONDA_CIVIC)
stopped_lead = make_lead(status=True, d_rel=18.0, v_lead=0.2, model_prob=0.99)
bias = get_honda_crv_5g_stopped_lead_obstacle_bias(crv, stopped_lead, v_ego=8.0)
assert 0.0 < bias < 1.0
assert get_honda_crv_5g_stopped_lead_obstacle_bias(civic, stopped_lead, v_ego=8.0) == pytest.approx(0.0)
assert get_honda_crv_5g_low_speed_stopped_lead_cap(
crv, make_lead(status=True, d_rel=10.0, v_lead=0.1, model_prob=0.99), v_ego=1.6, accel_min=-0.5,
) == pytest.approx(-0.272)
assert get_honda_crv_5g_low_speed_stopped_lead_cap(
civic, make_lead(status=True, d_rel=10.0, v_lead=0.1, model_prob=0.99), v_ego=1.6, accel_min=-0.5,
) is None
assert allow_honda_crv_5g_vision_gap_settle(crv)
assert not allow_honda_crv_5g_vision_gap_settle(civic)
assert get_standstill_gap_settle_max_extra_gap(crv) > get_standstill_gap_settle_max_extra_gap(civic)
@pytest.mark.parametrize("model_version", ["v11", "v12", "v13", "v14", "v15"])
def test_honda_crv_5g_vision_lead_gap_settle_is_bounded(model_version):
CP = CarInterface.get_non_essential_params(CAR.HONDA_CRV_5G)
planner = LongitudinalPlanner(CP, init_v=0.0)
sm = make_sm(
0.0,
desired_accel=-0.12,
min_accel=-0.5,
experimental_mode=True,
tracking_lead=False,
lead_one=make_lead(status=True, d_rel=11.0, v_lead=0.0, a_lead=0.0, model_prob=0.99),
)
sm["carState"].standstill = True
sm["controlsState"].longControlState = LongCtrlState.stopping
sm["modelV2"].action.shouldStop = True
frames = int(round(longitudinal_planner_module.RADAR_STANDSTILL_GAP_SETTLE_CONFIRM_TIME / planner.dt)) + 2
for _ in range(frames):
planner.update(sm, make_toggles(model_version))
assert planner.radar_standstill_gap_settle_active
assert not planner.output_should_stop
assert planner.output_a_target == pytest.approx(longitudinal_planner_module.RADAR_STANDSTILL_GAP_SETTLE_ACCEL)
def test_mpc_duplicate_vision_filter_smooths_distance_jumps_per_track():
mpc = LongitudinalMpc()
mpc.set_cur_state(27.0, 0.0)
@@ -16,6 +16,8 @@ from openpilot.starpilot.controls.lib.starpilot_vcruise import (
from openpilot.selfdrive.controls.lib.longitudinal_vehicle_tunes import (
get_force_stop_distance_bias,
get_force_stop_handoff_distance,
get_force_stop_low_speed_hold,
get_force_stop_reanchor_speed_tolerance,
)
from types import SimpleNamespace
@@ -58,7 +60,7 @@ def make_vcruise(*, red_light=False, raw_model_stopped=False, forcing_stop=False
return planner, vcruise
def make_sm(*, standstill=True, min_steer_speed=0.0):
def make_sm(*, standstill=True, min_steer_speed=0.0, car_fingerprint=""):
return {
"carControl": SimpleNamespace(longActive=True),
"carState": SimpleNamespace(
@@ -71,7 +73,7 @@ def make_sm(*, standstill=True, min_steer_speed=0.0):
rightBlinker=False,
steeringAngleDeg=0.0,
),
"carParams": SimpleNamespace(minSteerSpeed=min_steer_speed),
"carParams": SimpleNamespace(minSteerSpeed=min_steer_speed, carFingerprint=car_fingerprint),
"starpilotCarState": SimpleNamespace(accelPressed=False, dashboardStopSign=0, dashboardSpeedLimit=0),
"onroadEvents": [],
}
@@ -130,6 +132,16 @@ def test_camry_tss2_gets_forward_force_stop_bias_only():
assert get_force_stop_distance_bias("TOYOTA_RAV4_TSS2") == pytest.approx(0.0)
def test_santa_fe_force_stop_tune_only_applies_to_that_car():
santa_fe = SimpleNamespace(carFingerprint="HYUNDAI_SANTA_FE_2022")
other = SimpleNamespace(carFingerprint="HYUNDAI_SANTA_FE_2021")
assert get_force_stop_reanchor_speed_tolerance(santa_fe) == pytest.approx(0.25)
assert get_force_stop_low_speed_hold(santa_fe) == pytest.approx(2.5)
assert get_force_stop_reanchor_speed_tolerance(other) is None
assert get_force_stop_low_speed_hold(other) is None
def test_curve_speed_controller_holds_target_through_brief_detector_dropout():
planner, vcruise = make_vcruise()
sm = make_sm(standstill=False)
@@ -520,6 +532,34 @@ def test_force_stop_reanchors_when_model_reopens_path_without_stop_action():
assert result > 5.0
def test_santa_fe_force_stop_does_not_reanchor_after_braking():
planner, vcruise = make_vcruise(red_light=False, raw_model_stopped=False, forcing_stop=True)
planner.model_length = 40.0
vcruise.tracked_model_length = 10.0
vcruise.force_stop_entry_speed = 12.0
sm = make_sm(standstill=False, car_fingerprint="HYUNDAI_SANTA_FE_2022")
sm["modelV2"] = SimpleNamespace(action=SimpleNamespace(shouldStop=False))
result = update_vcruise(vcruise, sm, make_toggles(), now=0.0, v_ego=5.0)
assert vcruise.tracked_model_length < 10.0
assert result < 5.0
def test_santa_fe_force_stop_holds_through_low_speed_detector_dropout():
planner, vcruise = make_vcruise(red_light=True, raw_model_stopped=False, forcing_stop=True)
vcruise.force_stop_entry_speed = 12.0
sm = make_sm(standstill=False, car_fingerprint="HYUNDAI_SANTA_FE_2022")
toggles = make_toggles()
update_vcruise(vcruise, sm, toggles, now=0.0, v_ego=2.0)
planner.starpilot_cem.stop_light_detected = False
result = update_vcruise(vcruise, sm, toggles, now=0.75, v_ego=2.0)
assert vcruise.forcing_stop
assert result == pytest.approx(0.0)
def test_force_stop_does_not_reanchor_committed_model_stop():
planner, vcruise = make_vcruise(red_light=False, raw_model_stopped=False, forcing_stop=True)
planner.model_length = 40.0
@@ -5,7 +5,6 @@ from openpilot.selfdrive.ui.lib.starpilot_state import starpilot_state
from openpilot.system.ui.lib.application import gui_app
from openpilot.system.ui.lib.multilang import tr, tr_noop
from openpilot.system.ui.widgets import DialogResult
from openpilot.system.ui.widgets.option_dialog import MultiOptionDialog
from openpilot.selfdrive.ui.layouts.settings.starpilot.panel import _SettingsPage
from openpilot.selfdrive.ui.layouts.settings.starpilot.aethergrid import (
@@ -73,13 +72,6 @@ class SteeringManagerView(CardHubManagerView):
"on_click": lambda: self._controller._navigate_to("advanced"),
},
]
if starpilot_state.car_state.isFord:
cards.append({
"title": tr("Ford Lateral Tuning"),
"desc": tr("Select the Ford steering strategy and tune prediction, lane-change, and speed response."),
"icon": "steering",
"on_click": lambda: self._controller._navigate_to("ford"),
})
return cards
@@ -330,95 +322,6 @@ class StarPilotLateralLayout(_SettingsPage):
),
]
# ── 4. Ford Lateral Tuning ──
def ford_curvature_mode():
return p.get_int("FordLateralMode") == 1
def ford_angle_mode():
return p.get_int("FordLateralMode") == 2
def ford_enhanced_mode():
return p.get_int("FordLateralMode") != 0
self._ford_rows = [
SettingRow(
"FordLateralMode", "value", tr_noop("Steering Strategy"),
subtitle=tr_noop("Curvature is the tuned default. Angle is available for comparison; Native preserves the original controls."),
get_value=self._get_ford_lateral_mode,
on_click=self._show_ford_lateral_mode,
),
SettingRow(
"FordHumanTurnDetection", "toggle", tr_noop("Manual Turn Release"),
subtitle=tr_noop("Yield during an intentional manual turn while keeping the Ford steering session ready."),
get_state=lambda: p.get_bool("FordHumanTurnDetection"),
set_state=lambda s: p.put_bool("FordHumanTurnDetection", s),
visible=ford_enhanced_mode,
),
SettingRow(
"FordHandsFreeCluster", "toggle", tr_noop("Hands-Free Cluster Display"),
subtitle=tr_noop("Show the vehicle's hands-free assistance graphic while lateral control is active. Driver monitoring requirements do not change."),
get_state=lambda: p.get_bool("FordHandsFreeCluster"),
set_state=lambda s: p.put_bool("FordHandsFreeCluster", s),
visible=ford_enhanced_mode,
),
SettingRow(
"FordCurvatureBlendLow", "value", tr_noop("Small-Curve Prediction"),
subtitle=tr_noop("Blend model-predicted curvature into gentle turns."),
get_value=lambda: f"{p.get_float('FordCurvatureBlendLow') * 100:.0f}%",
on_click=lambda: self._show_slider("FordCurvatureBlendLow", 0.0, 1.0, step=0.05, unit="", value_type="float"),
visible=ford_curvature_mode,
),
SettingRow(
"FordCurvatureBlendHigh", "value", tr_noop("Large-Curve Prediction"),
subtitle=tr_noop("Blend model-predicted curvature into tighter turns."),
get_value=lambda: f"{p.get_float('FordCurvatureBlendHigh') * 100:.0f}%",
on_click=lambda: self._show_slider("FordCurvatureBlendHigh", 0.0, 1.0, step=0.05, unit="", value_type="float"),
visible=ford_curvature_mode,
),
SettingRow(
"FordCurvatureLaneChangeFactor", "value", tr_noop("Curvature Lane-Change Factor"),
subtitle=tr_noop("Scale steering during high-speed lane changes in Curvature mode."),
get_value=lambda: f"{p.get_float('FordCurvatureLaneChangeFactor'):.2f}x",
on_click=lambda: self._show_slider("FordCurvatureLaneChangeFactor", 0.5, 1.25, step=0.05, unit="x", value_type="float"),
visible=ford_curvature_mode,
),
SettingRow(
"FordAngleBlend", "value", tr_noop("Angle Prediction Blend"),
subtitle=tr_noop("Blend model prediction into the path-angle command."),
get_value=lambda: f"{p.get_float('FordAngleBlend') * 100:.0f}%",
on_click=lambda: self._show_slider("FordAngleBlend", 0.0, 1.0, step=0.05, unit="", value_type="float"),
visible=ford_angle_mode,
),
SettingRow(
"FordAngleLowSpeedFactor", "value", tr_noop("Low-Speed Angle Response"),
subtitle=tr_noop("Adjust path-angle strength at lower speeds and higher curvature."),
get_value=lambda: f"{p.get_float('FordAngleLowSpeedFactor'):.2f}x",
on_click=lambda: self._show_slider("FordAngleLowSpeedFactor", 0.5, 1.5, step=0.05, unit="x", value_type="float"),
visible=ford_angle_mode,
),
SettingRow(
"FordAngleHighSpeedFactor", "value", tr_noop("High-Speed Angle Response"),
subtitle=tr_noop("Adjust path-angle strength through larger highway curves."),
get_value=lambda: f"{p.get_float('FordAngleHighSpeedFactor'):.2f}x",
on_click=lambda: self._show_slider("FordAngleHighSpeedFactor", 0.5, 1.5, step=0.05, unit="x", value_type="float"),
visible=ford_angle_mode,
),
SettingRow(
"FordAngleHighSpeedDamping", "value", tr_noop("High-Speed Damping"),
subtitle=tr_noop("Dampen small steering corrections at highway speed."),
get_value=lambda: f"{p.get_float('FordAngleHighSpeedDamping'):.2f}x",
on_click=lambda: self._show_slider("FordAngleHighSpeedDamping", 0.25, 1.25, step=0.05, unit="x", value_type="float"),
visible=ford_angle_mode,
),
SettingRow(
"FordAngleLaneChangeFactor", "value", tr_noop("Angle Lane-Change Factor"),
subtitle=tr_noop("Scale steering during high-speed lane changes in Angle mode."),
get_value=lambda: f"{p.get_float('FordAngleLaneChangeFactor'):.2f}x",
on_click=lambda: self._show_slider("FordAngleLaneChangeFactor", 0.5, 1.5, step=0.05, unit="x", value_type="float"),
visible=ford_angle_mode,
),
]
self._manager_view = SteeringManagerView(
self,
header_title=tr_noop("Steering"),
@@ -462,13 +365,6 @@ class StarPilotLateralLayout(_SettingsPage):
parent_toggle=pt_advanced,
panel_style=PANEL_STYLE,
)
self._sub_panels["ford"] = AetherSettingsView(
self,
[SettingSection(title="", rows=self._ford_rows)],
header_title=tr_noop("Ford Lateral Tuning"),
header_subtitle=tr_noop("Tune Ford-specific polynomial steering while retaining the native strategy as a fallback."),
panel_style=PANEL_STYLE,
)
self._wire_sub_panels()
def _on_pause_lateral_speed_clicked(self):
@@ -509,17 +405,3 @@ class StarPilotLateralLayout(_SettingsPage):
current = self._params.get_int("LaneChangeSmoothing") if self._params.get_int("LaneChangeSmoothing") > 0 else 5
gui_app.push_widget(AetherSliderDialog(tr("Lane Change Smoothing"), 1, 10, 1, current, on_close,
color=self.SLIDER_COLOR))
def _get_ford_lateral_mode(self) -> str:
return tr(("Native", "Curvature", "Angle")[max(0, min(2, self._params.get_int("FordLateralMode")))])
def _show_ford_lateral_mode(self):
options = [tr("Native"), tr("Curvature"), tr("Angle")]
current = options[max(0, min(2, self._params.get_int("FordLateralMode")))]
def on_select(res):
if res == DialogResult.CONFIRM and dialog.selection in options:
self._params.put_int("FordLateralMode", options.index(dialog.selection))
dialog = MultiOptionDialog(tr("Ford Steering Strategy"), options, current, callback=on_select)
gui_app.push_widget(dialog)
@@ -56,9 +56,6 @@ class DeveloperLayoutMici(NavScroller):
self._long_maneuver_toggle = BigToggle("longitudinal maneuver mode",
initial_state=ui_state.params.get_bool("LongitudinalManeuverMode"),
toggle_callback=self._on_long_maneuver_mode)
self._lat_maneuver_toggle = BigToggle("lateral maneuver mode",
initial_state=ui_state.params.get_bool("LateralManeuverMode"),
toggle_callback=self._on_lat_maneuver_mode)
self._alpha_long_toggle = BigToggle("alpha longitudinal",
initial_state=ui_state.params.get_bool("AlphaLongitudinalEnabled"),
toggle_callback=self._on_alpha_long_enabled)
@@ -73,7 +70,6 @@ class DeveloperLayoutMici(NavScroller):
self._disable_wide_road_toggle,
self._joystick_toggle,
self._long_maneuver_toggle,
self._lat_maneuver_toggle,
self._alpha_long_toggle,
self._debug_mode_toggle,
])
@@ -85,7 +81,6 @@ class DeveloperLayoutMici(NavScroller):
("DisableWideRoad", self._disable_wide_road_toggle),
("JoystickDebugMode", self._joystick_toggle),
("LongitudinalManeuverMode", self._long_maneuver_toggle),
("LateralManeuverMode", self._lat_maneuver_toggle),
("AlphaLongitudinalEnabled", self._alpha_long_toggle),
("ShowDebugInfo", self._debug_mode_toggle),
)
@@ -94,7 +89,7 @@ class DeveloperLayoutMici(NavScroller):
self._disable_wide_road_toggle,
self._joystick_toggle,
)
engaged_blocked_toggles = (self._long_maneuver_toggle, self._lat_maneuver_toggle, self._alpha_long_toggle)
engaged_blocked_toggles = (self._long_maneuver_toggle, self._alpha_long_toggle)
# Disable toggles that require offroad
for item in onroad_blocked_toggles:
@@ -139,12 +134,8 @@ class DeveloperLayoutMici(NavScroller):
if not long_man_enabled:
self._long_maneuver_toggle.set_checked(False)
ui_state.params.put_bool("LongitudinalManeuverMode", False)
lat_man_enabled = ui_state.is_offroad()
self._lat_maneuver_toggle.set_enabled(lat_man_enabled)
else:
self._long_maneuver_toggle.set_enabled(False)
self._lat_maneuver_toggle.set_enabled(False)
self._alpha_long_toggle.set_visible(False)
# Refresh toggles from params to mirror external changes
@@ -156,23 +147,12 @@ class DeveloperLayoutMici(NavScroller):
ui_state.params.put_bool("LongitudinalManeuverMode", False)
self._long_maneuver_toggle.set_checked(False)
ui_state.params.put_bool("LateralManeuverMode", False)
self._lat_maneuver_toggle.set_checked(False)
def _on_long_maneuver_mode(self, state: bool):
ui_state.params.put_bool("LongitudinalManeuverMode", state)
ui_state.params.put_bool("JoystickDebugMode", False)
self._joystick_toggle.set_checked(False)
ui_state.params.put_bool("LateralManeuverMode", False)
self._lat_maneuver_toggle.set_checked(False)
restart_needed_callback(state)
def _on_lat_maneuver_mode(self, state: bool):
ui_state.params.put_bool("LateralManeuverMode", state)
ui_state.params.put_bool("ExperimentalMode", False)
ui_state.params.put_bool("JoystickDebugMode", False)
self._joystick_toggle.set_checked(False)
ui_state.params.put_bool("LongitudinalManeuverMode", False)
self._long_maneuver_toggle.set_checked(False)
restart_needed_callback(state)
def _on_alpha_long_enabled(self, state: bool):
-4
View File
@@ -1666,10 +1666,6 @@
<source>Map Accel/Decel to Gears</source>
<translation>Прив'язати прискорення/сповільнення до передач</translation>
</message>
<message>
<source>Reverse Cruise Increase</source>
<translation>Змінити довге натискання</translation>
</message>
<message>
<source>Speed Limit Controller</source>
<translation>Контролер лімітів швидк.</translation>
@@ -364,6 +364,238 @@
"parent_key": "QOLLateral",
"settings_tier": "simple"
},
{
"key": "FordLateralMode",
"label": "Ford Steering Strategy",
"description": "Choose the Ford lateral controller. Curvature is the tuned default, Angle uses path-angle control, and Native preserves the original Ford controls.",
"picker_description": "Chooses Native, Curvature, or Angle steering on Ford vehicles.",
"data_type": "int",
"ui_type": "dropdown",
"options": [
{
"value": 0,
"label": "Native"
},
{
"value": 1,
"label": "Curvature"
},
{
"value": 2,
"label": "Angle"
}
],
"is_parent_toggle": true,
"galaxy_only": true,
"vehicle_makes": [
"Ford"
],
"settings_tier": "simple"
},
{
"key": "FordHumanTurnDetection",
"label": "Manual Turn Release",
"description": "Yield during an intentional manual turn while keeping the Ford steering session ready.",
"data_type": "bool",
"ui_type": "toggle",
"parent_key": "FordLateralMode",
"visible_when_key": "FordLateralMode",
"visible_when_values": [
1,
2
],
"galaxy_only": true,
"vehicle_makes": [
"Ford"
],
"settings_tier": "simple"
},
{
"key": "FordHandsFreeCluster",
"label": "Hands-Free Cluster Display",
"description": "Show the hands-free assistance graphic on supported CAN-FD Ford clusters while lateral control is active. Driver monitoring requirements do not change.",
"data_type": "bool",
"ui_type": "toggle",
"parent_key": "FordLateralMode",
"visible_when_key": "FordLateralMode",
"visible_when_values": [
1,
2
],
"galaxy_only": true,
"vehicle_makes": [
"Ford"
],
"settings_tier": "simple"
},
{
"key": "FordCurvatureBlendLow",
"label": "Small-Curve Prediction Blend",
"description": "Blend model-predicted curvature into gentle turns. 0 uses planner curvature only; 1 uses model prediction only.",
"data_type": "float",
"ui_type": "numeric",
"min": 0.0,
"max": 1.0,
"step": 0.05,
"precision": 2,
"parent_key": "FordLateralMode",
"visible_when_key": "FordLateralMode",
"visible_when_values": [
1
],
"galaxy_only": true,
"vehicle_makes": [
"Ford"
],
"settings_tier": "simple"
},
{
"key": "FordCurvatureBlendHigh",
"label": "Large-Curve Prediction Blend",
"description": "Blend model-predicted curvature into tighter turns. 0 uses planner curvature only; 1 uses model prediction only.",
"data_type": "float",
"ui_type": "numeric",
"min": 0.0,
"max": 1.0,
"step": 0.05,
"precision": 2,
"parent_key": "FordLateralMode",
"visible_when_key": "FordLateralMode",
"visible_when_values": [
1
],
"galaxy_only": true,
"vehicle_makes": [
"Ford"
],
"settings_tier": "simple"
},
{
"key": "FordCurvatureLaneChangeFactor",
"label": "Curvature Lane-Change Factor",
"description": "Scale steering during high-speed lane changes in Curvature mode.",
"data_type": "float",
"ui_type": "numeric",
"min": 0.5,
"max": 1.25,
"step": 0.05,
"precision": 2,
"parent_key": "FordLateralMode",
"visible_when_key": "FordLateralMode",
"visible_when_values": [
1
],
"galaxy_only": true,
"vehicle_makes": [
"Ford"
],
"settings_tier": "simple"
},
{
"key": "FordAngleBlend",
"label": "Angle Prediction Blend",
"description": "Blend model prediction into the Ford path-angle command. 0 uses planner curvature only; 1 uses model prediction only.",
"data_type": "float",
"ui_type": "numeric",
"min": 0.0,
"max": 1.0,
"step": 0.05,
"precision": 2,
"parent_key": "FordLateralMode",
"visible_when_key": "FordLateralMode",
"visible_when_values": [
2
],
"galaxy_only": true,
"vehicle_makes": [
"Ford"
],
"settings_tier": "simple"
},
{
"key": "FordAngleLowSpeedFactor",
"label": "Low-Speed Angle Response",
"description": "Adjust path-angle strength at lower speeds and higher curvature.",
"data_type": "float",
"ui_type": "numeric",
"min": 0.5,
"max": 1.5,
"step": 0.05,
"precision": 2,
"parent_key": "FordLateralMode",
"visible_when_key": "FordLateralMode",
"visible_when_values": [
2
],
"galaxy_only": true,
"vehicle_makes": [
"Ford"
],
"settings_tier": "simple"
},
{
"key": "FordAngleHighSpeedFactor",
"label": "High-Speed Angle Response",
"description": "Adjust path-angle strength through larger highway curves.",
"data_type": "float",
"ui_type": "numeric",
"min": 0.5,
"max": 1.5,
"step": 0.05,
"precision": 2,
"parent_key": "FordLateralMode",
"visible_when_key": "FordLateralMode",
"visible_when_values": [
2
],
"galaxy_only": true,
"vehicle_makes": [
"Ford"
],
"settings_tier": "simple"
},
{
"key": "FordAngleHighSpeedDamping",
"label": "High-Speed Angle Damping",
"description": "Dampen small steering corrections at highway speed.",
"data_type": "float",
"ui_type": "numeric",
"min": 0.25,
"max": 1.25,
"step": 0.05,
"precision": 2,
"parent_key": "FordLateralMode",
"visible_when_key": "FordLateralMode",
"visible_when_values": [
2
],
"galaxy_only": true,
"vehicle_makes": [
"Ford"
],
"settings_tier": "simple"
},
{
"key": "FordAngleLaneChangeFactor",
"label": "Angle Lane-Change Factor",
"description": "Scale steering during high-speed lane changes in Angle mode.",
"data_type": "float",
"ui_type": "numeric",
"min": 0.5,
"max": 1.5,
"step": 0.05,
"precision": 2,
"parent_key": "FordLateralMode",
"visible_when_key": "FordLateralMode",
"visible_when_values": [
2
],
"galaxy_only": true,
"vehicle_makes": [
"Ford"
],
"settings_tier": "simple"
},
{
"key": "VASMEnabled",
"label": "Enable V-ASM",
+3
View File
@@ -128,6 +128,9 @@ def build_favorite_slot_options(is_eligible_param: Callable[[str], bool], *,
options = [dict(option) for option in FAVORITE_ACTION_OPTIONS]
for key, param_data in catalog_map.items():
if param_data.get("galaxy_only"):
continue
ui_type = str(param_data.get("ui_type") or "")
data_type = str(param_data.get("data_type") or "")
raw_options = param_data.get("options")
-1
View File
@@ -91,7 +91,6 @@ SAFE_MODE_MANAGED_KEYS = (
"MapGears",
"MapAcceleration",
"MapDeceleration",
"ReverseCruise",
"SetSpeedOffset",
"WeatherPresets",
"IncreaseFollowingLowVisibility",
-1
View File
@@ -1278,7 +1278,6 @@ class StarPilotVariables:
map_gears = self.get_value("MapGears", condition=quality_of_life_longitudinal)
toggle.map_acceleration = self.get_value("MapAcceleration", condition=map_gears)
toggle.map_deceleration = self.get_value("MapDeceleration", condition=map_gears)
toggle.reverse_cruise_increase = self.get_value("ReverseCruise", condition=quality_of_life_cruise)
toggle.set_speed_offset = self.get_value("SetSpeedOffset", cast=float, condition=(quality_of_life_longitudinal and not pcm_cruise), conversion=(1 if toggle.is_metric else CV.MPH_TO_KPH))
toggle.weather_presets = self.get_value("WeatherPresets", condition=quality_of_life_longitudinal)
toggle.increase_following_distance_low_visibility = self.get_value("IncreaseFollowingLowVisibility", cast=float, condition=toggle.weather_presets)
@@ -88,6 +88,18 @@ def test_shared_settings_catalog_is_common_and_well_formed():
assert len(keys) == len(set(keys))
def test_galaxy_only_ford_controls_are_not_available_to_device_favorites():
ford_keys = {
"FordLateralMode",
"FordHumanTurnDetection",
"FordHandsFreeCluster",
}
options = build_favorite_slot_options(lambda _key: True, alpha_longitudinal_available=True)
assert ford_keys.isdisjoint({option["key"] for option in options})
def test_load_favorite_slots_filters_non_bool_keys():
params = FakeParams()
params.put(FAVORITE_SLOTS_PARAM, [
+25 -1
View File
@@ -11,6 +11,8 @@ from openpilot.starpilot.controls.lib.speed_limit_controller import SpeedLimitCo
from openpilot.selfdrive.controls.lib.longitudinal_vehicle_tunes import (
get_force_stop_distance_bias,
get_force_stop_handoff_distance,
get_force_stop_low_speed_hold,
get_force_stop_reanchor_speed_tolerance,
)
CSC_MIN_SPEED = CITY_SPEED_LIMIT * CV.MPH_TO_MS
@@ -166,6 +168,7 @@ class StarPilotVCruise:
self.override_force_stop_timer = 0
self.force_stop_timer = 0.0
self.force_stop_entry_speed = None
self.activation_gate_active = False
self.standstill_force_stop_hold = False
self.standstill_force_stop_clear_since = 0.0
@@ -358,6 +361,8 @@ class StarPilotVCruise:
car_params = sm["carParams"]
except (KeyError, IndexError, TypeError, AttributeError):
car_params = None
force_stop_reanchor_speed_tolerance = get_force_stop_reanchor_speed_tolerance(car_params)
force_stop_low_speed_hold = get_force_stop_low_speed_hold(car_params)
lead_veto_m = get_lead_veto_distance(car_params)
lead_present = (bool(getattr(lead, "status", False))
and float(getattr(lead, "dRel", float("inf"))) < lead_veto_m
@@ -490,6 +495,17 @@ class StarPilotVCruise:
not stop_light_detected and
not dash_active
)
low_speed_stop_commit = bool(
light_stop_cleared and
force_stop_low_speed_hold is not None and
self.force_stop_entry_speed is not None and
v_ego <= force_stop_low_speed_hold and
v_ego < self.force_stop_entry_speed - 0.25
)
# The Santa Fe's model stop signal can blink off after the car has already
# committed to the stop. Do not turn that late dropout into a throttle
# release while the vehicle is still rolling through the sign.
light_stop_cleared &= not low_speed_stop_commit
if light_stop_cleared:
if self.force_stop_light_clear_since is None:
self.force_stop_light_clear_since = now
@@ -593,6 +609,8 @@ class StarPilotVCruise:
v_cruise = 0.0
elif force_stop_enabled and not self.override_force_stop:
if self.force_stop_entry_speed is None and not sm["carState"].standstill:
self.force_stop_entry_speed = v_ego
self.forcing_stop |= not sm["carState"].standstill or self.standstill_force_stop_hold
if self.standstill_force_stop_hold:
@@ -614,7 +632,12 @@ class StarPilotVCruise:
not dash_active and
self.tracked_model_length > force_stop_handoff_m and
not model_wants_stop and
model_length > self.tracked_model_length + FORCE_STOP_DISTANCE_REANCHOR_MIN_GAP
model_length > self.tracked_model_length + FORCE_STOP_DISTANCE_REANCHOR_MIN_GAP and
(
force_stop_reanchor_speed_tolerance is None or
self.force_stop_entry_speed is None or
v_ego >= self.force_stop_entry_speed - force_stop_reanchor_speed_tolerance
)
):
self.tracked_model_length = model_length
else:
@@ -646,6 +669,7 @@ class StarPilotVCruise:
else:
self.forcing_stop = False
self.force_stop_entry_speed = None
self._clear_standstill_force_stop_hold()
# Latch is only meaningful during an active force-stop cycle
self.stop_sign_confirmed = False
@@ -11,7 +11,7 @@ const FAVORITE_OPTION_COLLATOR = new Intl.Collator(undefined, { numeric: true, s
const FAVORITE_ACTION_PREFIX = "__starpilot_favorite_action__:"
const GALAXY_DEVELOPER_MODE_KEY = "GalaxyDeveloperMode"
const HIDDEN_SECTION_NAMES = new Set(["Model & Customization"])
const HIDDEN_SETTING_KEYS = new Set(["HumanAcceleration", "ReverseCruise"])
const HIDDEN_SETTING_KEYS = new Set(["HumanAcceleration"])
const GM_MAKES = ["Buick", "Cadillac", "Chevrolet", "GMC", "Holden"]
const HKG_MAKES = ["Genesis", "Hyundai", "Kia"]
const VEHICLE_SETTING_MAKES = {
@@ -96,16 +96,22 @@ function normalizeVehicleMake(value) {
}
function isVehicleSettingVisible(section, param) {
if (section.name !== "Vehicle") return true
const allowedMakes = VEHICLE_SETTING_MAKES[param.key]
const allowedMakes = param.vehicle_makes || (section.name === "Vehicle" ? VEHICLE_SETTING_MAKES[param.key] : null)
if (!allowedMakes) return true
const selectedMake = normalizeVehicleMake(state.values.CarMake)
return allowedMakes.some(make => normalizeVehicleMake(make) === selectedMake)
}
function matchesSettingValueCondition(param) {
if (!param.visible_when_key) return true
const allowedValues = Array.isArray(param.visible_when_values) ? param.visible_when_values : []
const currentValue = toSelectValue(state.values[param.visible_when_key])
return allowedValues.some(value => toSelectValue(value) === currentValue)
}
function isSettingVisible(section, param) {
// This policy controls Galaxy rendering only; hidden params retain their stored values.
if (HIDDEN_SETTING_KEYS.has(param.key) || !isVehicleSettingVisible(section, param)) return false
if (HIDDEN_SETTING_KEYS.has(param.key) || !isVehicleSettingVisible(section, param) || !matchesSettingValueCondition(param)) return false
if (param.requires_capability && !state.values[param.requires_capability]) return false
if (RADAR_REQUIRED_KEYS.has(param.key) && !state.values.HasRadar) return false
if (param.key === "AlphaLongitudinalEnabled" && !state.values.AlphaLongitudinalAvailable) return false
@@ -35,7 +35,7 @@ def test_galaxy_layout_removes_obsolete_and_duplicate_controls():
all_keys = {key for params in sections.values() for key in params}
assert "Model & Customization" not in sections
assert {"HumanAcceleration", "ReverseCruise"}.isdisjoint(all_keys)
assert "HumanAcceleration" not in all_keys
assert "DisableWideRoad" in sections["Visual (Display & UI)"]
assert sum(
param.get("key") == "DisableWideRoad"
@@ -66,6 +66,58 @@ def test_galaxy_layout_contains_basic_mode_controls():
assert {"AlphaLongitudinalEnabled", "ForceOffroad", "GalaxyDeveloperMode"} <= sections["Developer"].keys()
def test_ford_lateral_controls_are_ford_only_and_galaxy_only():
lateral = _params_by_section(_layout())["Lateral (Steering)"]
ford_keys = {
"FordLateralMode",
"FordHumanTurnDetection",
"FordHandsFreeCluster",
"FordCurvatureBlendLow",
"FordCurvatureBlendHigh",
"FordCurvatureLaneChangeFactor",
"FordAngleBlend",
"FordAngleLowSpeedFactor",
"FordAngleHighSpeedFactor",
"FordAngleHighSpeedDamping",
"FordAngleLaneChangeFactor",
}
assert ford_keys <= lateral.keys()
assert all(lateral[key]["galaxy_only"] is True for key in ford_keys)
assert all(lateral[key]["vehicle_makes"] == ["Ford"] for key in ford_keys)
assert all(lateral[key]["settings_tier"] == "simple" for key in ford_keys)
mode = lateral["FordLateralMode"]
assert mode["ui_type"] == "dropdown"
assert mode["data_type"] == "int"
assert mode["is_parent_toggle"] is True
assert {option["label"]: option["value"] for option in mode["options"]} == {
"Native": 0,
"Curvature": 1,
"Angle": 2,
}
assert _declared_default("FordLateralMode") == "1"
common_keys = {"FordHumanTurnDetection", "FordHandsFreeCluster"}
curvature_keys = {"FordCurvatureBlendLow", "FordCurvatureBlendHigh", "FordCurvatureLaneChangeFactor"}
angle_keys = {
"FordAngleBlend",
"FordAngleLowSpeedFactor",
"FordAngleHighSpeedFactor",
"FordAngleHighSpeedDamping",
"FordAngleLaneChangeFactor",
}
assert all(lateral[key]["visible_when_values"] == [1, 2] for key in common_keys)
assert all(lateral[key]["visible_when_values"] == [1] for key in curvature_keys)
assert all(lateral[key]["visible_when_values"] == [2] for key in angle_keys)
assert all(lateral[key]["parent_key"] == "FordLateralMode" for key in ford_keys - {"FordLateralMode"})
device_ui_root = REPO_ROOT / "selfdrive/ui"
for path in device_ui_root.rglob("*.py"):
source = path.read_text(encoding="utf-8")
assert all(key not in source for key in ford_keys)
def test_device_shutdown_uses_literal_hours():
device_shutdown = _params_by_section(_layout())["Device & Data"]["DeviceShutdown"]
@@ -290,7 +342,11 @@ def test_pip_preview_is_under_driving_screen_widgets_and_configured_only_in_gala
assert _declared_default("PIPPreviewEnabled") == "0"
assert _declared_default("PIPPreviewShowOnBlinker") == "0"
assert _declared_default("PIPPreviewShowOnBSM") == "0"
assert '"{\\"width\\":1928,\\"height\\":1208,\\"center_left\\":[315,548],\\"center_right\\":[1571,539],\\"crop_size\\":580}"' in PARAM_KEYS_PATH.read_text(encoding="utf-8")
annotation_default = (
'"{\\"width\\":1928,\\"height\\":1208,\\"center_left\\":[315,548],' +
'\\"center_right\\":[1571,539],\\"crop_size\\":580}"'
)
assert annotation_default in PARAM_KEYS_PATH.read_text(encoding="utf-8")
physical_settings = (
REPO_ROOT / "selfdrive/ui/layouts/settings/starpilot/aethergrid.py",
@@ -95,10 +95,11 @@ def _params_client(monkeypatch, values, device_type):
the_galaxy,
"_get_param_type_info",
lambda: (
{"AlphaLongitudinalEnabled", "ForceOffroad"},
{"AlphaLongitudinalEnabled", "ForceOffroad", "FordLateralMode"},
{
"AlphaLongitudinalEnabled": bool,
"ForceOffroad": bool,
"FordLateralMode": int,
},
),
)
@@ -255,6 +256,19 @@ def test_device_settings_layout_asset_is_served_from_common_catalog(monkeypatch)
assert response.get_json() == the_galaxy.load_settings_catalog()
def test_ford_lateral_mode_is_editable_through_galaxy(monkeypatch):
client, fake_params = _params_client(monkeypatch, {
"CarMake": "Ford",
"FordLateralMode": 1,
}, "mici")
response = client.put("/api/params", json={"key": "FordLateralMode", "value": 2, "label": "Angle"})
assert response.status_code == 200
assert fake_params.values["FordLateralMode"] == "2"
assert ("FordLateralMode", "2") in fake_params.writes
def test_favorite_slot_options_include_virtual_cruise_actions(monkeypatch):
monkeypatch.setattr(the_galaxy, "_favorite_slot_options", None)
monkeypatch.setattr(the_galaxy, "_get_param_type_info", lambda: (set(), {}))
+20 -1
View File
@@ -22,6 +22,7 @@ DEVELOPER_METRIC_DISPLAY_KEYS = (
)
DEVICE_SHUTDOWN_KEY = "DeviceShutdown"
CAMERA_VIEW_KEY = "CameraView"
REVERSE_CRUISE_KEY = "ReverseCruise"
DEFAULT_STEER_KP = 0.6
LEGACY_STEER_KP = 0.7
@@ -38,6 +39,7 @@ LANE_CHANGE_SMOOTHING_MIGRATION_MARKER = ".starpilot_lane_change_smoothing_defau
SPEED_LIMIT_VISIBILITY_MIGRATION_MARKER = ".starpilot_speed_limit_visibility_v1"
DEVICE_SHUTDOWN_HOURS_MIGRATION_MARKER = ".starpilot_device_shutdown_hours_v1"
CAMERA_VIEW_DEFAULT_MIGRATION_MARKER = ".starpilot_camera_view_default_v1"
REVERSE_CRUISE_REMOVAL_MIGRATION_MARKER = ".starpilot_remove_reverse_cruise_v1"
MARKER_DIRNAME = ".starpilot_param_migrations"
LATERAL_METHOD_PARAM_SUFFIXES = (
@@ -145,6 +147,10 @@ def _camera_view_default_marker_path(params: ParamsLike) -> Path:
return _marker_dir_path(params) / CAMERA_VIEW_DEFAULT_MIGRATION_MARKER
def _reverse_cruise_removal_marker_path(params: ParamsLike) -> Path:
return _marker_dir_path(params) / REVERSE_CRUISE_REMOVAL_MIGRATION_MARKER
def _marker_dir_path(params: ParamsLike) -> Path:
params_path = Path(params.get_param_path())
# Params.clear_all() removes unknown files inside the params directory, so
@@ -326,6 +332,15 @@ def _apply_camera_view_default_migration(params: ParamsLike, marker: Path) -> No
marker.touch()
def _remove_reverse_cruise_param(params: ParamsLike, marker: Path) -> None:
if marker.exists():
return
marker.parent.mkdir(parents=True, exist_ok=True)
Path(params.get_param_path(REVERSE_CRUISE_KEY)).unlink(missing_ok=True)
marker.touch()
def apply_launch_param_migrations(params: ParamsLike, marker_path: Path | None = None,
branch_defaults_marker_path: Path | None = None,
acceleration_profile_marker_path: Path | None = None,
@@ -336,7 +351,8 @@ def apply_launch_param_migrations(params: ParamsLike, marker_path: Path | None =
lane_change_smoothing_marker_path: Path | None = None,
speed_limit_visibility_marker_path: Path | None = None,
device_shutdown_hours_marker_path: Path | None = None,
camera_view_default_marker_path: Path | None = None) -> None:
camera_view_default_marker_path: Path | None = None,
reverse_cruise_removal_marker_path: Path | None = None) -> None:
_apply_legacy_launch_param_migrations(params, marker_path or _default_marker_path(params))
# Keep branch-default rollout on its own marker so older installs that already
# have the legacy marker still receive this one-time param reset.
@@ -368,6 +384,9 @@ def apply_launch_param_migrations(params: ParamsLike, marker_path: Path | None =
_apply_camera_view_default_migration(
params, camera_view_default_marker_path or _camera_view_default_marker_path(params)
)
_remove_reverse_cruise_param(
params, reverse_cruise_removal_marker_path or _reverse_cruise_removal_marker_path(params)
)
def main() -> int:
+3 -1
View File
@@ -70,7 +70,9 @@ STARPILOT_PARAMS_CACHE_MIGRATION_FLAG = Path("/data") / "starpilot_params_cache_
STARPILOT_DEFAULT_MODEL_MIGRATION_FLAG = Path("/data") / "starpilot_default_model_rdf_v4"
STARPILOT_CE_MODEL_STOP_TIME_MIGRATION_FLAG = Path("/data") / "starpilot_ce_model_stop_time_v2"
STARPILOT_LEGACY_CACHE_MARKER_KEYS = ("RemapCancelToDistance",)
STARPILOT_REMOVED_PARAM_KEYS = ("CoastUpToLeads", "HumanAcceleration", "HumanFollowing", "PrioritizeSmoothFollowing")
STARPILOT_REMOVED_PARAM_KEYS = (
"CoastUpToLeads", "HumanAcceleration", "HumanFollowing", "PrioritizeSmoothFollowing", "ReverseCruise",
)
LEGACY_CARMODEL_MIGRATIONS = {
"CHEVROLET_BOLT_CC_2019_2021": "CHEVROLET_BOLT_CC_2018_2021",
}
@@ -14,6 +14,7 @@ from openpilot.system.manager.launch_param_migrations import (
LAUNCH_PARAM_MIGRATION_MARKER,
LATERAL_METHOD_REBRAND_MIGRATION_MARKER,
MARKER_DIRNAME,
REVERSE_CRUISE_REMOVAL_MIGRATION_MARKER,
STANDARD_ACCELERATION_PROFILE,
SPEED_LIMIT_VISIBILITY_MIGRATION_MARKER,
LEGACY_UI_SELECTION_MIGRATION_MARKER,
@@ -177,6 +178,16 @@ def test_apply_launch_param_migrations_preserves_custom_camera_view(tmp_path):
assert params.get_int("CameraView") == 0
def test_apply_launch_param_migrations_removes_reverse_cruise_param(tmp_path):
params = FileBackedFakeParams(tmp_path / "params")
params.put_bool("ReverseCruise", True)
apply_launch_param_migrations(params)
assert not Path(params.get_param_path("ReverseCruise")).exists()
assert marker_path(tmp_path, REVERSE_CRUISE_REMOVAL_MIGRATION_MARKER).is_file()
def test_apply_launch_param_migrations_applies_branch_defaults_for_existing_installs(tmp_path):
params = FileBackedFakeParams(tmp_path / "params")
+4
View File
@@ -392,10 +392,12 @@ class TestManager:
"CoastUpToLeads": True,
"HumanAcceleration": True,
"HumanFollowing": True,
"ReverseCruise": True,
})
params_cache = FileBackedFakeParams(tmp_path / "cache", {
"HumanFollowing": False,
"PrioritizeSmoothFollowing": True,
"ReverseCruise": True,
})
manager.cleanup_removed_starpilot_params(params, params_cache)
@@ -403,8 +405,10 @@ class TestManager:
assert not Path(params.get_param_path("CoastUpToLeads")).exists()
assert not Path(params.get_param_path("HumanAcceleration")).exists()
assert not Path(params.get_param_path("HumanFollowing")).exists()
assert not Path(params.get_param_path("ReverseCruise")).exists()
assert not Path(params_cache.get_param_path("HumanFollowing")).exists()
assert not Path(params_cache.get_param_path("PrioritizeSmoothFollowing")).exists()
assert not Path(params_cache.get_param_path("ReverseCruise")).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")
-1
View File
@@ -266,7 +266,6 @@ RelaxedJerkSpeedDecrease
RelaxedPersonalityProfile
RemapCancelToDistance
RemoteStartBootsComma
ReverseCruise
RoadEdgesWidth
RoadNameUI
RotatingWheel