mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-09-09 01:23:43 +08:00
gmornin
This commit is contained in:
@@ -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,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,
|
||||
|
||||
@@ -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,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 @@
|
||||
DEV-46ae2472-DEBUG
|
||||
DEV-83cec26d-DEBUG
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -91,7 +91,6 @@ SAFE_MODE_MANAGED_KEYS = (
|
||||
"MapGears",
|
||||
"MapAcceleration",
|
||||
"MapDeceleration",
|
||||
"ReverseCruise",
|
||||
"SetSpeedOffset",
|
||||
"WeatherPresets",
|
||||
"IncreaseFollowingLowVisibility",
|
||||
|
||||
@@ -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, [
|
||||
|
||||
@@ -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(), {}))
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -266,7 +266,6 @@ RelaxedJerkSpeedDecrease
|
||||
RelaxedPersonalityProfile
|
||||
RemapCancelToDistance
|
||||
RemoteStartBootsComma
|
||||
ReverseCruise
|
||||
RoadEdgesWidth
|
||||
RoadNameUI
|
||||
RotatingWheel
|
||||
|
||||
Reference in New Issue
Block a user