diff --git a/common/params_keys.h b/common/params_keys.h index 1bd4be9b6..30b094fd8 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -640,6 +640,7 @@ inline static std::unordered_map keys = { {"VeryLongModeButtonControl", {PERSISTENT, INT, "0", "0", 2}}, {"VeryLongStarButtonControl", {PERSISTENT, INT, "0", "0", 2}}, {"VoltSNG", {PERSISTENT, BOOL, "0", "0", 2}}, + {"JeepBrakeHold", {PERSISTENT, BOOL, "0", "0", 2}}, {"GMAutoHold", {PERSISTENT, BOOL, "0", "0", 2}}, {"VoltOnePedalMode", {PERSISTENT, BOOL, "0", "0", 2}}, {"ToyotaAutoHold", {PERSISTENT, BOOL, "0", "0", 2}}, diff --git a/opendbc_repo/opendbc/car/chrysler/carcontroller.py b/opendbc_repo/opendbc/car/chrysler/carcontroller.py index e7df5068b..b1d19ffd5 100644 --- a/opendbc_repo/opendbc/car/chrysler/carcontroller.py +++ b/opendbc_repo/opendbc/car/chrysler/carcontroller.py @@ -2,9 +2,28 @@ from opendbc.can import CANPacker from opendbc.car import Bus, DT_CTRL from opendbc.car.lateral import apply_meas_steer_torque_limits from opendbc.car.chrysler import chryslercan -from opendbc.car.chrysler.values import RAM_CARS, RAM_DT, CarControllerParams, ChryslerFlags, ChryslerStarPilotFlags +from opendbc.car.chrysler.values import JEEPS, RAM_CARS, RAM_DT, CarControllerParams, ChryslerFlags, ChryslerSafetyFlags, ChryslerStarPilotFlags from opendbc.car.interfaces import CarControllerBase +JEEP_BRAKE_HOLD_DEFAULT_DECEL = -2.0 +JEEP_BRAKE_HOLD_MIN_DECEL = -0.5 +JEEP_BRAKE_HOLD_MAX_DECEL = -3.0 + + +def clip_jeep_brake_hold_decel(decel: float) -> float: + return max(JEEP_BRAKE_HOLD_MAX_DECEL, min(JEEP_BRAKE_HOLD_MIN_DECEL, float(decel))) + + +def supports_jeep_brake_hold(CP, brake_hold_enabled: bool) -> bool: + safety_cfg = getattr(CP, "safetyConfigs", ()) + safety_param = safety_cfg[0].safetyParam if safety_cfg else 0 + return ( + brake_hold_enabled and + getattr(CP, "pcmCruise", False) and + CP.carFingerprint in JEEPS and + bool(safety_param & ChryslerSafetyFlags.JEEP_BRAKE_HOLD.value) + ) + class CarController(CarControllerBase): def __init__(self, dbc_names, CP): @@ -18,6 +37,8 @@ class CarController(CarControllerBase): self.packer = CANPacker(dbc_names[Bus.pt]) self.params = CarControllerParams(CP) + self.jeep_brake_hold_decel = JEEP_BRAKE_HOLD_DEFAULT_DECEL + self.last_das_3_counter = -1 def update(self, CC, CS, now_nanos, starpilot_toggles): can_sends = [] @@ -82,6 +103,11 @@ class CarController(CarControllerBase): can_sends.append(chryslercan.create_lkas_command(self.packer, self.CP, int(apply_torque), lkas_control_bit)) + if supports_jeep_brake_hold(self.CP, getattr(starpilot_toggles, "jeep_brake_hold", False)): + self.update_jeep_brake_hold(CC, CS, can_sends) + elif getattr(CS, "brake_hold", False): + CS.brake_hold = False + self.frame += 1 new_actuators = CC.actuators.as_builder() @@ -89,3 +115,43 @@ class CarController(CarControllerBase): new_actuators.torqueOutputCan = self.apply_torque_last return new_actuators, can_sends + + def update_jeep_brake_hold(self, CC, CS, can_sends): + if not getattr(CS, "das_3", None): + CS.brake_hold = False + return + + counter_changed = CS.das_3.get("COUNTER") != self.last_das_3_counter + self.last_das_3_counter = CS.das_3.get("COUNTER") + + if not CS.brake_hold and CS.cruise_active_actual and CS.acc_decelerating and CS.out.standstill: + CS.brake_hold = True + self.jeep_brake_hold_decel = JEEP_BRAKE_HOLD_DEFAULT_DECEL + + driver_intervened = ( + CC.cruiseControl.cancel or + CS.out.gasPressed or + CS.out.brakePressed or + not CS.forward_gear or + not CS.out.standstill + ) + if CS.brake_hold and driver_intervened: + CS.brake_hold = False + return + + if not CS.brake_hold: + return + + if CS.cruise_active_actual: + self.jeep_brake_hold_decel = clip_jeep_brake_hold_decel( + min(self.jeep_brake_hold_decel, CS.das_3.get("ACC_DECEL", JEEP_BRAKE_HOLD_DEFAULT_DECEL)) + ) + return + + counter_offset = 2 if counter_changed else 3 + can_sends.append(chryslercan.create_das_3_command(self.packer, counter_offset, self.jeep_brake_hold_decel, CS.das_3)) + + if self.frame % 10 == 0: + can_sends.append(chryslercan.create_cruise_buttons( + self.packer, CS.button_counter + 1, 0, CS.button_message, resume=True + )) diff --git a/opendbc_repo/opendbc/car/chrysler/carstate.py b/opendbc_repo/opendbc/car/chrysler/carstate.py index 198503261..1a77987d5 100644 --- a/opendbc_repo/opendbc/car/chrysler/carstate.py +++ b/opendbc_repo/opendbc/car/chrysler/carstate.py @@ -1,7 +1,7 @@ from cereal import custom from opendbc.can import CANDefine, CANParser from opendbc.car import Bus, create_button_events, structs -from opendbc.car.chrysler.values import DBC, STEER_THRESHOLD, RAM_CARS, ChryslerStarPilotFlags +from opendbc.car.chrysler.values import DBC, JEEPS, STEER_THRESHOLD, RAM_CARS, ChryslerStarPilotFlags from opendbc.car.common.conversions import Conversions as CV from opendbc.car.interfaces import CarStateBase @@ -29,6 +29,11 @@ class CarState(CarStateBase): self.button_message = "CRUISE_BUTTONS_ALT" if FPCP.flags & ChryslerStarPilotFlags.RAM_HD_ALT_BUTTONS else "CRUISE_BUTTONS" self.lkas_button = 0 + self.brake_hold = False + self.cruise_active_actual = False + self.forward_gear = False + self.acc_decelerating = False + self.das_3 = {} @staticmethod def get_lkas_button(pt_signals, is_ram: bool) -> bool: @@ -93,6 +98,13 @@ class CarState(CarStateBase): ret.cruiseState.standstill = cp_cruise.vl["DAS_3"]["ACC_STANDSTILL"] == 1 ret.accFaulted = cp_cruise.vl["DAS_3"]["ACC_FAULTED"] != 0 + if self.CP.carFingerprint in JEEPS: + self.forward_gear = ret.gearShifter == structs.CarState.GearShifter.drive + self.cruise_active_actual = ret.cruiseState.enabled + self.acc_decelerating = cp_cruise.vl["DAS_3"]["ACC_DECEL"] < -0.5 + self.das_3 = dict(cp_cruise.vl["DAS_3"]) + ret.brakeHoldActive = self.brake_hold + if self.CP.carFingerprint in RAM_CARS: # Auto High Beam isn't Located in this message on chrysler or jeep currently located in 729 message self.auto_high_beam = cp_cam.vl["DAS_6"]['AUTO_HIGH_BEAM_ON'] diff --git a/opendbc_repo/opendbc/car/chrysler/chryslercan.py b/opendbc_repo/opendbc/car/chrysler/chryslercan.py index 1fbd84c83..dad8d11fb 100644 --- a/opendbc_repo/opendbc/car/chrysler/chryslercan.py +++ b/opendbc_repo/opendbc/car/chrysler/chryslercan.py @@ -73,6 +73,21 @@ def create_cruise_buttons(packer, frame, bus, button_message, cancel=False, resu return packer.make_can_msg(button_message, bus, values) +def create_das_3_command(packer, counter_offset, brake_decel, das_3): + values = das_3.copy() + values["ACC_AVAILABLE"] = 1 + values["ACC_ACTIVE"] = 1 + values["ACC_GO"] = 0 + values["ACC_STANDSTILL"] = 0 + values["ACC_DECEL_REQ"] = 1 + values["ACC_DECEL"] = brake_decel + values["ACC_BRK_PREP"] = 0 + values["ENGINE_TORQUE_REQUEST_MAX"] = 0 + values["GR_MAX_REQ"] = 2 + values["COUNTER"] = (das_3["COUNTER"] + counter_offset) % 0x10 + return packer.make_can_msg("DAS_3", 0, values) + + def chrysler_checksum(address: int, sig, d: bytearray) -> int: checksum = 0xFF for j in range(len(d) - 1): diff --git a/opendbc_repo/opendbc/car/chrysler/interface.py b/opendbc_repo/opendbc/car/chrysler/interface.py index b131e1b6e..ff6612b57 100755 --- a/opendbc_repo/opendbc/car/chrysler/interface.py +++ b/opendbc_repo/opendbc/car/chrysler/interface.py @@ -3,8 +3,9 @@ from opendbc.car import get_safety_config, structs from opendbc.car.chrysler.carcontroller import CarController from opendbc.car.chrysler.carstate import CarState from opendbc.car.chrysler.radar_interface import RadarInterface -from opendbc.car.chrysler.values import CAR, RAM_HD, RAM_DT, RAM_CARS, ChryslerFlags, ChryslerSafetyFlags +from opendbc.car.chrysler.values import CAR, JEEPS, RAM_HD, RAM_DT, RAM_CARS, ChryslerFlags, ChryslerSafetyFlags from opendbc.car.interfaces import CarInterfaceBase +from openpilot.common.params import Params, UnknownKeyName class CarInterface(CarInterfaceBase): @@ -14,6 +15,12 @@ class CarInterface(CarInterfaceBase): @staticmethod def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, alpha_long, is_release, docs) -> structs.CarParams: + params = Params() + try: + jeep_brake_hold = params.get_bool("JeepBrakeHold") + except UnknownKeyName: + jeep_brake_hold = False + ret.brand = "chrysler" ret.dashcamOnly = candidate in RAM_HD @@ -28,6 +35,8 @@ class CarInterface(CarInterfaceBase): ret.safetyConfigs[0].safetyParam |= ChryslerSafetyFlags.RAM_HD.value elif candidate in RAM_DT: ret.safetyConfigs[0].safetyParam |= ChryslerSafetyFlags.RAM_DT.value + elif candidate in JEEPS and jeep_brake_hold: + ret.safetyConfigs[0].safetyParam |= ChryslerSafetyFlags.JEEP_BRAKE_HOLD.value CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) if candidate not in RAM_CARS: diff --git a/opendbc_repo/opendbc/car/chrysler/values.py b/opendbc_repo/opendbc/car/chrysler/values.py index 087f1c342..c05ab073a 100644 --- a/opendbc_repo/opendbc/car/chrysler/values.py +++ b/opendbc_repo/opendbc/car/chrysler/values.py @@ -12,6 +12,7 @@ Ecu = CarParams.Ecu class ChryslerSafetyFlags(IntFlag): RAM_DT = 1 RAM_HD = 2 + JEEP_BRAKE_HOLD = 4 class ChryslerFlags(IntFlag): @@ -140,6 +141,7 @@ STEER_THRESHOLD = 120 RAM_DT = {CAR.RAM_1500_5TH_GEN, } RAM_HD = {CAR.RAM_HD_5TH_GEN, } RAM_CARS = RAM_DT | RAM_HD +JEEPS = {CAR.JEEP_GRAND_CHEROKEE, CAR.JEEP_GRAND_CHEROKEE_2019} CHRYSLER_VERSION_REQUEST = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \ diff --git a/opendbc_repo/opendbc/car/hyundai/carcontroller.py b/opendbc_repo/opendbc/car/hyundai/carcontroller.py index de9481dad..8d87a7bae 100644 --- a/opendbc_repo/opendbc/car/hyundai/carcontroller.py +++ b/opendbc_repo/opendbc/car/hyundai/carcontroller.py @@ -652,6 +652,8 @@ class CarController(CarControllerBase): # prevent LFA from activating on LKA steering cars by sending "no lane lines detected" to ADAS ECU suppress_lfa = bool(lka_steering) + if self.CP.carFingerprint == CAR.KIA_EV9 and self.CP.flags & HyundaiFlags.CANFD_ANGLE_STEERING: + suppress_lfa = bool(lka_steering and CC.latActive) if self.frame % 5 == 0 and suppress_lfa: can_sends.append(hyundaicanfd.create_suppress_lfa(self.packer, self.CAN, CS.lfa_block_msg, self.CP.flags & HyundaiFlags.CANFD_LKA_STEERING_ALT)) diff --git a/opendbc_repo/opendbc/car/hyundai/tests/test_hyundai.py b/opendbc_repo/opendbc/car/hyundai/tests/test_hyundai.py index 0c3477df5..be19fceb6 100644 --- a/opendbc_repo/opendbc/car/hyundai/tests/test_hyundai.py +++ b/opendbc_repo/opendbc/car/hyundai/tests/test_hyundai.py @@ -1460,7 +1460,28 @@ class TestHyundaiFingerprint: assert parser.vl["LKAS_ALT"]["STEER_MODE"] == 2 assert parser.vl["LKAS_ALT"]["NEW_SIGNAL_2"] == 3 - def test_ev9_inactive_angle_steering_still_suppresses_stock_lfa(self): + def test_ev9_inactive_angle_steering_does_not_suppress_stock_lfa(self): + CP = CarParams.new_message() + CP.carFingerprint = CAR.KIA_EV9 + CP.flags = int(HyundaiFlags.CANFD | HyundaiFlags.EV | HyundaiFlags.CANFD_ANGLE_STEERING | + HyundaiFlags.CANFD_LKA_STEERING | HyundaiFlags.CANFD_LKA_STEERING_ALT) + CP.openpilotLongitudinalControl = False + + controller = CarController(DBC[CP.carFingerprint], CP) + controller.frame = 5 + cc = SimpleNamespace(enabled=False, latActive=False, actuators=SimpleNamespace(longControlState=LongCtrlState.off), + leftBlinker=False, rightBlinker=False, hudControl=SimpleNamespace()) + lfa_block_msg = {f"BYTE{i}": 0 for i in range(3, 32) if i != 7} + lfa_block_msg["COUNTER"] = 0 + cs = SimpleNamespace(stock_lfa_msg=None, stock_lkas_msg={}, lfa_block_msg=lfa_block_msg, + out=SimpleNamespace(steeringAngleDeg=0.0)) + + 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) + suppress_msgs = [msg for msg in msgs if msg[0] == 0x362] + assert not suppress_msgs + + def test_ev9_active_angle_steering_still_suppresses_stock_lfa(self): CP = CarParams.new_message() CP.carFingerprint = CAR.KIA_EV9 CP.flags = int(HyundaiFlags.CANFD | HyundaiFlags.EV | HyundaiFlags.CANFD_ANGLE_STEERING | @@ -1471,7 +1492,7 @@ class TestHyundaiFingerprint: controller.frame = 5 can_bus = CanBus(CP) parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("CAM_0x362", 0)], can_bus.ECAN) - cc = SimpleNamespace(enabled=False, latActive=False, actuators=SimpleNamespace(longControlState=LongCtrlState.off), + cc = SimpleNamespace(enabled=False, latActive=True, actuators=SimpleNamespace(longControlState=LongCtrlState.off), leftBlinker=False, rightBlinker=False, hudControl=SimpleNamespace()) lfa_block_msg = {f"BYTE{i}": 0 for i in range(3, 32) if i != 7} lfa_block_msg["COUNTER"] = 0 @@ -1479,7 +1500,7 @@ class TestHyundaiFingerprint: out=SimpleNamespace(steeringAngleDeg=0.0)) 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) + get_test_toggles(), lka_icon=2, lfa_icon=2) suppress_msgs = [msg for msg in msgs if msg[0] == 0x362] assert len(suppress_msgs) == 1 diff --git a/opendbc_repo/opendbc/car/tests/test_chrysler_starpilot_helpers.py b/opendbc_repo/opendbc/car/tests/test_chrysler_starpilot_helpers.py new file mode 100644 index 000000000..c94fa6d0e --- /dev/null +++ b/opendbc_repo/opendbc/car/tests/test_chrysler_starpilot_helpers.py @@ -0,0 +1,49 @@ +from types import SimpleNamespace + +from opendbc.car.chrysler.carcontroller import ( + JEEP_BRAKE_HOLD_DEFAULT_DECEL, + clip_jeep_brake_hold_decel, + supports_jeep_brake_hold, +) +from opendbc.car.chrysler.values import CAR, ChryslerSafetyFlags, JEEPS + + +def test_jeep_group_is_grand_cherokee_only(): + assert JEEPS == {CAR.JEEP_GRAND_CHEROKEE, CAR.JEEP_GRAND_CHEROKEE_2019} + + +def test_jeep_brake_hold_support_requires_toggle_jeep_pcm_and_safety_flag(): + safety = [SimpleNamespace(safetyParam=ChryslerSafetyFlags.JEEP_BRAKE_HOLD.value)] + no_safety = [SimpleNamespace(safetyParam=0)] + + assert supports_jeep_brake_hold(SimpleNamespace( + carFingerprint=CAR.JEEP_GRAND_CHEROKEE, + pcmCruise=True, + safetyConfigs=safety, + ), True) + assert not supports_jeep_brake_hold(SimpleNamespace( + carFingerprint=CAR.JEEP_GRAND_CHEROKEE, + pcmCruise=True, + safetyConfigs=no_safety, + ), True) + assert not supports_jeep_brake_hold(SimpleNamespace( + carFingerprint=CAR.JEEP_GRAND_CHEROKEE, + pcmCruise=False, + safetyConfigs=safety, + ), True) + assert not supports_jeep_brake_hold(SimpleNamespace( + carFingerprint=CAR.CHRYSLER_PACIFICA_2018, + pcmCruise=True, + safetyConfigs=safety, + ), True) + assert not supports_jeep_brake_hold(SimpleNamespace( + carFingerprint=CAR.JEEP_GRAND_CHEROKEE, + pcmCruise=True, + safetyConfigs=safety, + ), False) + + +def test_jeep_brake_hold_decel_is_bounded(): + assert clip_jeep_brake_hold_decel(JEEP_BRAKE_HOLD_DEFAULT_DECEL) == JEEP_BRAKE_HOLD_DEFAULT_DECEL + assert clip_jeep_brake_hold_decel(-8.0) == -3.0 + assert clip_jeep_brake_hold_decel(0.5) == -0.5 diff --git a/opendbc_repo/opendbc/safety/modes/chrysler.h b/opendbc_repo/opendbc/safety/modes/chrysler.h index 0e210eb07..74d667e80 100644 --- a/opendbc_repo/opendbc/safety/modes/chrysler.h +++ b/opendbc_repo/opendbc/safety/modes/chrysler.h @@ -23,6 +23,7 @@ typedef enum { } ChryslerPlatform; static ChryslerPlatform chrysler_platform; static const ChryslerAddrs *chrysler_addrs; +static bool chrysler_jeep_brake_hold; static uint32_t chrysler_get_checksum(const CANPacket_t *msg) { int checksum_byte = GET_LEN(msg) - 1U; @@ -153,18 +154,27 @@ static bool chrysler_tx_hook(const CANPacket_t *msg) { if (msg->addr == chrysler_addrs->CRUISE_BUTTONS || msg->addr == chrysler_addrs->CRUISE_BUTTONS_ALT) { const bool is_cancel = msg->data[0] == 1U; const bool is_resume = msg->data[0] == 0x10U; - const bool allowed = is_cancel || (is_resume && controls_allowed); + const bool allow_resume_standstill = chrysler_jeep_brake_hold && is_resume && acc_main_on && + !vehicle_moving && !gas_pressed && !brake_pressed; + const bool allowed = is_cancel || (is_resume && (controls_allowed || allow_resume_standstill)); if (!allowed) { tx = false; } } + if (msg->addr == chrysler_addrs->DAS_3) { + if (!chrysler_jeep_brake_hold || !acc_main_on || vehicle_moving || gas_pressed || brake_pressed) { + tx = false; + } + } + return tx; } static safety_config chrysler_init(uint16_t param) { const uint32_t CHRYSLER_PARAM_RAM_DT = 1U; // set for Ram DT platform + const uint32_t CHRYSLER_PARAM_JEEP_BRAKE_HOLD = 4U; // CAN messages for Chrysler/Jeep platforms static const ChryslerAddrs CHRYSLER_ADDRS = { @@ -217,6 +227,7 @@ static safety_config chrysler_init(uint16_t param) { {CHRYSLER_ADDRS.CRUISE_BUTTONS, 0, 3, .check_relay = false}, {CHRYSLER_ADDRS.LKAS_COMMAND, 0, 6, .check_relay = true}, {CHRYSLER_ADDRS.DAS_6, 0, 8, .check_relay = true}, + {CHRYSLER_ADDRS.DAS_3, 0, 8, .check_relay = false}, // RealFast variables {CHRYSLER_ADDRS.CRUISE_BUTTONS_ALT, 2, 3, .check_relay = false}, @@ -287,6 +298,7 @@ static safety_config chrysler_init(uint16_t param) { chrysler_addrs = &CHRYSLER_ADDRS; ret = BUILD_SAFETY_CFG(chrysler_rx_checks, CHRYSLER_TX_MSGS); } + chrysler_jeep_brake_hold = GET_FLAG(param, CHRYSLER_PARAM_JEEP_BRAKE_HOLD) && (chrysler_platform == CHRYSLER_PACIFICA); return ret; } diff --git a/opendbc_repo/opendbc/safety/tests/test_chrysler.py b/opendbc_repo/opendbc/safety/tests/test_chrysler.py index f7f3a111c..adfe254ed 100755 --- a/opendbc_repo/opendbc/safety/tests/test_chrysler.py +++ b/opendbc_repo/opendbc/safety/tests/test_chrysler.py @@ -9,7 +9,7 @@ from opendbc.safety.tests.common import CANPackerSafety class TestChryslerSafety(common.CarSafetyTest, common.MotorTorqueSteeringSafetyTest): - TX_MSGS = [[0x23B, 0], [0x292, 0], [0x2A6, 0]] + TX_MSGS = [[0x23B, 0], [0x292, 0], [0x2A6, 0], [0x1F4, 0]] RELAY_MALFUNCTION_ADDRS = {0: (0x292, 0x2A6)} FWD_BLACKLISTED_ADDRS = {2: [0x292, 0x2A6]} @@ -37,6 +37,14 @@ class TestChryslerSafety(common.CarSafetyTest, common.MotorTorqueSteeringSafetyT values = {"ACC_ACTIVE": enable} return self.packer.make_can_msg_safety("DAS_3", self.DAS_BUS, values) + def _main_on_msg(self, available=True, active=False): + values = {"ACC_AVAILABLE": available, "ACC_ACTIVE": active} + return self.packer.make_can_msg_panda("DAS_3", self.DAS_BUS, values) + + def _das_3_msg(self): + values = {"ACC_AVAILABLE": 1, "ACC_ACTIVE": 1, "ACC_DECEL_REQ": 1, "ACC_DECEL": -2.0} + return self.packer.make_can_msg_safety("DAS_3", self.DAS_BUS, values) + def _speed_msg(self, speed): values = {"SPEED_LEFT": speed, "SPEED_RIGHT": speed} return self.packer.make_can_msg_safety("SPEED_1", 0, values) @@ -71,6 +79,58 @@ class TestChryslerSafety(common.CarSafetyTest, common.MotorTorqueSteeringSafetyT self.assertFalse(self._tx(self._button_msg(cancel=True, resume=True))) self.assertFalse(self._tx(self._button_msg(cancel=False, resume=False))) + def test_jeep_brake_hold_resume_at_standstill_requires_safety_flag_and_main_on(self): + if self.__class__ is not TestChryslerSafety: + self.skipTest("Jeep brake hold applies to base Chrysler/Jeep safety only") + + self._rx(self._speed_msg(0)) + self._rx(self._main_on_msg(True)) + self.safety.set_controls_allowed(False) + + self.assertFalse(self._tx(self._button_msg(resume=True))) + + self.safety.set_safety_hooks(CarParams.SafetyModel.chrysler, ChryslerSafetyFlags.JEEP_BRAKE_HOLD) + self.safety.init_tests() + self._rx(self._speed_msg(0)) + self._rx(self._main_on_msg(True)) + self.assertTrue(self._tx(self._button_msg(resume=True))) + + self._rx(self._user_gas_msg(1)) + self.assertFalse(self._tx(self._button_msg(resume=True))) + self._rx(self._user_gas_msg(0)) + + self._rx(self._user_brake_msg(True)) + self.assertFalse(self._tx(self._button_msg(resume=True))) + self._rx(self._user_brake_msg(False)) + + self._rx(self._speed_msg(1)) + self.assertFalse(self._tx(self._button_msg(resume=True))) + + def test_jeep_brake_hold_das_3_requires_safety_flag_main_on_and_standstill(self): + if self.__class__ is not TestChryslerSafety: + self.skipTest("Jeep brake hold applies to base Chrysler/Jeep safety only") + + self.assertFalse(self._tx(self._das_3_msg())) + + self.safety.set_safety_hooks(CarParams.SafetyModel.chrysler, ChryslerSafetyFlags.JEEP_BRAKE_HOLD) + self.safety.init_tests() + self._rx(self._speed_msg(0)) + self.assertFalse(self._tx(self._das_3_msg())) + + self._rx(self._main_on_msg(True)) + self.assertTrue(self._tx(self._das_3_msg())) + + self._rx(self._user_gas_msg(1)) + self.assertFalse(self._tx(self._das_3_msg())) + self._rx(self._user_gas_msg(0)) + + self._rx(self._user_brake_msg(True)) + self.assertFalse(self._tx(self._das_3_msg())) + self._rx(self._user_brake_msg(False)) + + self._rx(self._speed_msg(1)) + self.assertFalse(self._tx(self._das_3_msg())) + def _toggle_aol(self, toggle_on): # DAS_3, bit 20 is ACC_AVAILABLE values = {"ACC_AVAILABLE": 1 if toggle_on else 0} diff --git a/selfdrive/selfdrived/selfdrived.py b/selfdrive/selfdrived/selfdrived.py index f2b6d8c3a..284f831d3 100644 --- a/selfdrive/selfdrived/selfdrived.py +++ b/selfdrive/selfdrived/selfdrived.py @@ -589,8 +589,9 @@ class SelfdriveD: self.CP.openpilotLongitudinalControl and not self.CP.pcmCruise ) effective_pcm_cruise = self.CP.pcmCruise or preap_software_cruise + jeep_brake_hold = self.CP.brand == "chrysler" and getattr(CS, "brakeHoldActive", False) cruise_mismatch = should_flag_cruise_mismatch(self.CP, CS.cruiseState.enabled, self.enabled, - effective_pcm_cruise) and not pacifica_hybrid_aol + effective_pcm_cruise) and not pacifica_hybrid_aol and not jeep_brake_hold self.cruise_mismatch_counter = self.cruise_mismatch_counter + 1 if cruise_mismatch else 0 if self.cruise_mismatch_counter > int(6. / DT_CTRL): self.events.add(EventName.cruiseMismatch) diff --git a/selfdrive/ui/layouts/settings/starpilot/vehicle.py b/selfdrive/ui/layouts/settings/starpilot/vehicle.py index 09a7e41e6..0ea7dd339 100644 --- a/selfdrive/ui/layouts/settings/starpilot/vehicle.py +++ b/selfdrive/ui/layouts/settings/starpilot/vehicle.py @@ -162,6 +162,13 @@ class VehicleSettingsManagerView(PanelManagerView): "get_state": lambda: self._controller._params.get_bool("VoltSNG"), "set_state": lambda s: self._controller._on_toggle("VoltSNG"), }) + if cs.isJeep: + toggles.append({ + "title": tr("Jeep Brake Hold"), + "subtitle": tr("Hold after ACC times out at a stop and resume when traffic moves."), + "get_state": lambda: self._controller._params.get_bool("JeepBrakeHold"), + "set_state": lambda s: self._controller._on_toggle("JeepBrakeHold"), + }) if cs.isSubaru: toggles.append({ diff --git a/selfdrive/ui/lib/starpilot_state.py b/selfdrive/ui/lib/starpilot_state.py index 6022187ce..28c7d8230 100644 --- a/selfdrive/ui/lib/starpilot_state.py +++ b/selfdrive/ui/lib/starpilot_state.py @@ -14,6 +14,7 @@ class StarPilotCarState: # ========== Car Type Detection ========== isGM: bool = False isHKG: bool = False + isJeep: bool = False isToyota: bool = False isSubaru: bool = False isVolt: bool = False @@ -86,8 +87,10 @@ class StarPilotState: from openpilot.selfdrive.ui.lib.fingerprint_catalog import FINGERPRINT_MAKE_TO_VALUES_DIR fallback_make_lower = fallback_make.lower() brand = FINGERPRINT_MAKE_TO_VALUES_DIR.get(fallback_make_lower, fallback_make_lower) + fallback_model_str = fallback_model or "" self.car_state.isGM = brand == "gm" self.car_state.isHKG = brand == "hyundai" + self.car_state.isJeep = brand == "chrysler" and fallback_model_str.startswith("JEEP_") self.car_state.isSubaru = brand == "subaru" self.car_state.isToyota = brand == "toyota" self.car_state.isHKGCanFd = False @@ -98,6 +101,7 @@ class StarPilotState: if fallback_model: self.params.put("CarModel", fallback_model) + self.car_state.isJeep = fallback_model.startswith("JEEP_") if not starpilot_toggles: self.car_state.hasOpenpilotLongitudinal = True @@ -161,6 +165,7 @@ class StarPilotState: self.car_state.isGM = car_make == "gm" self.car_state.isHKG = car_make == "hyundai" self.car_state.isHKGCanFd = self.car_state.isHKG and safety_model == car.CarParams.SafetyModel.hyundaiCanfd + self.car_state.isJeep = car_make == "chrysler" and car_fingerprint.startswith("JEEP_") self.car_state.isSubaru = car_make == "subaru" self.car_state.isToyota = car_make == "toyota" self.car_state.isTSK = bool(self._safe_get(CP, "secOcRequired", False)) diff --git a/starpilot/common/safe_mode.py b/starpilot/common/safe_mode.py index caeaf40ba..7e2c81567 100644 --- a/starpilot/common/safe_mode.py +++ b/starpilot/common/safe_mode.py @@ -185,6 +185,7 @@ SAFE_MODE_MANAGED_KEYS = ( "SubaruSNG", "SubaruSNGManualParkingBrake", "VoltSNG", + "JeepBrakeHold", "GMAutoHold", "VoltOnePedalMode", "GMPedalLongitudinal", diff --git a/starpilot/common/starpilot_variables.py b/starpilot/common/starpilot_variables.py index 7e14c4cae..530c3225d 100644 --- a/starpilot/common/starpilot_variables.py +++ b/starpilot/common/starpilot_variables.py @@ -15,6 +15,7 @@ import numpy as np from cereal import car, custom, log from opendbc.car import gen_empty_fingerprint from opendbc.car.car_helpers import interfaces +from opendbc.car.chrysler.values import JEEPS as CHRYSLER_JEEPS from opendbc.car.gm.values import CAR as GM_CAR, EV_CAR as GM_EV_CAR, GMFlags from opendbc.car.hyundai.values import CAR as HYUNDAI_CAR, EV_CAR as HYUNDAI_EV_CAR, HyundaiFlags, HyundaiStarPilotSafetyFlags from opendbc.car.interfaces import TORQUE_SUBSTITUTE_PATH, CarInterfaceBase, GearShifter @@ -1412,6 +1413,7 @@ class StarPilotVariables: toggle.volt_one_pedal_mode = self.get_value("VoltOnePedalMode", condition=gm_auto_hold_supported) toggle.volt_sng = self.get_value("VoltSNG", condition=toggle.car_model in LEGACY_VOLT_STOCK_ACC_CARS) + toggle.jeep_brake_hold = self.get_value("JeepBrakeHold", condition=toggle.car_model in CHRYSLER_JEEPS) process_starpilot_toggles.cache_clear() self.params_memory.remove("StarPilotTogglesUpdated") diff --git a/starpilot/system/galaxy/galaxy.py b/starpilot/system/galaxy/galaxy.py index 817db7f1c..cb900fcac 100644 --- a/starpilot/system/galaxy/galaxy.py +++ b/starpilot/system/galaxy/galaxy.py @@ -4,6 +4,7 @@ import json import platform import shutil import signal +import socket import subprocess import tarfile import time @@ -23,6 +24,8 @@ else: FRPC_VERSION = "0.67.0" FRPC_LOG = GALAXY_DIR / "frpc.log" AUTH_PORT = 8083 +GALAXY_WEB_HOST = "127.0.0.1" +GALAXY_WEB_PORT = 8082 BUNDLED_FRPC_DIR = Path(__file__).resolve().parent / "bin" process = None @@ -92,6 +95,11 @@ def cleanup_frpc(*_): process = None +def shutdown(*_): + cleanup_frpc() + raise SystemExit + + def get_arch_url(): arch = platform.machine() system = platform.system() @@ -135,6 +143,14 @@ def get_param_str(params, key): return str(value or "") +def local_galaxy_ready(): + try: + with socket.create_connection((GALAXY_WEB_HOST, GALAXY_WEB_PORT), timeout=1.0): + return True + except OSError: + return False + + def setup_frpc(): GALAXY_DIR.mkdir(parents=True, exist_ok=True) frpc_bin = GALAXY_DIR / "frpc" @@ -182,8 +198,8 @@ def main(): global process params = Params() - signal.signal(signal.SIGTERM, cleanup_frpc) - signal.signal(signal.SIGINT, cleanup_frpc) + signal.signal(signal.SIGTERM, shutdown) + signal.signal(signal.SIGINT, shutdown) dongle_id = get_param_str(params, "DongleId") while not dongle_id: @@ -205,11 +221,21 @@ def main(): is_paired = galaxy_password_hash and len(galaxy_password_hash) == 64 and slug if is_paired: + if process is not None and process.poll() is None and not local_galaxy_ready(): + print(f"Galaxy: Web app is not ready on {GALAXY_WEB_HOST}:{GALAXY_WEB_PORT}. Stopping frpc tunnel until it recovers...") + cleanup_frpc() + last_slug = None + if process is None or process.poll() is not None or slug != last_slug: cleanup_frpc() if process is not None and slug == last_slug: print(f"Galaxy: frpc exited with code {process.returncode}. Restarting...") + if not local_galaxy_ready(): + print(f"Galaxy: Waiting for web app on {GALAXY_WEB_HOST}:{GALAXY_WEB_PORT} before starting frpc tunnel...") + time.sleep(3) + continue + print("Galaxy: Password set. Preparing frpc tunnel...") if not setup_frpc(): print("Galaxy: FRPC setup failed. Retrying later...") diff --git a/starpilot/ui/qt/offroad/starpilot_settings.cc b/starpilot/ui/qt/offroad/starpilot_settings.cc index 63935ee4b..119a4df09 100644 --- a/starpilot/ui/qt/offroad/starpilot_settings.cc +++ b/starpilot/ui/qt/offroad/starpilot_settings.cc @@ -344,6 +344,7 @@ void StarPilotSettingsWindow::updateVariables() { isGM = carMake == "gm"; isHKG = carMake == "hyundai"; isHKGCanFd = isHKG && hasModeStarButtons; + isJeep = fallbackFingerprint.rfind("JEEP_", 0) == 0; isSubaru = carMake == "subaru"; isToyota = carMake == "toyota"; isVolt = fallbackFingerprint.rfind("CHEVROLET_VOLT", 0) == 0; @@ -380,6 +381,7 @@ void StarPilotSettingsWindow::updateVariables() { isGM = carMake == "gm"; isHKG = carMake == "hyundai"; isHKGCanFd = isHKG && safetyModel == cereal::CarParams::SafetyModel::HYUNDAI_CANFD; + isJeep = carMake == "chrysler" && carFingerprint.rfind("JEEP_", 0) == 0; isSubaru = carMake == "subaru"; isTorqueCar = CP.getLateralTuning().which() == cereal::CarParams::LateralTuning::TORQUE; isToyota = carMake == "toyota"; diff --git a/starpilot/ui/qt/offroad/starpilot_settings.h b/starpilot/ui/qt/offroad/starpilot_settings.h index 3ae3a6831..a7e27109b 100644 --- a/starpilot/ui/qt/offroad/starpilot_settings.h +++ b/starpilot/ui/qt/offroad/starpilot_settings.h @@ -39,6 +39,7 @@ public: bool isGM = true; bool isHKG = true; bool isHKGCanFd = true; + bool isJeep = false; bool isSubaru = false; bool isTorqueCar = false; bool isToyota = true; diff --git a/starpilot/ui/qt/offroad/vehicle_settings.cc b/starpilot/ui/qt/offroad/vehicle_settings.cc index 676ec4b99..8bb3db851 100644 --- a/starpilot/ui/qt/offroad/vehicle_settings.cc +++ b/starpilot/ui/qt/offroad/vehicle_settings.cc @@ -154,18 +154,21 @@ StarPilotVehiclesPanel::StarPilotVehiclesPanel(StarPilotSettingsWindow *parent, }); settingsList->addItem(disableOpenpilotLong); + StarPilotListWidget *chryslerList = new StarPilotListWidget(this); StarPilotListWidget *gmList = new StarPilotListWidget(this); StarPilotListWidget *hkgList = new StarPilotListWidget(this); StarPilotListWidget *subaruList = new StarPilotListWidget(this); StarPilotListWidget *toyotaList = new StarPilotListWidget(this); StarPilotListWidget *vehicleInfoList = new StarPilotListWidget(this); + ScrollView *chryslerPanel = new ScrollView(chryslerList, this); ScrollView *gmPanel = new ScrollView(gmList, this); ScrollView *hkgPanel = new ScrollView(hkgList, this); ScrollView *subaruPanel = new ScrollView(subaruList, this); ScrollView *toyotaPanel = new ScrollView(toyotaList, this); ScrollView *vehicleInfoPanel = new ScrollView(vehicleInfoList, this); + vehiclesLayout->addWidget(chryslerPanel); vehiclesLayout->addWidget(gmPanel); vehiclesLayout->addWidget(hkgPanel); vehiclesLayout->addWidget(subaruPanel); @@ -173,6 +176,9 @@ StarPilotVehiclesPanel::StarPilotVehiclesPanel(StarPilotSettingsWindow *parent, vehiclesLayout->addWidget(vehicleInfoPanel); std::vector> vehicleToggles { + {"ChryslerToggles", tr("Chrysler/Jeep Settings"), tr("StarPilot features for Chrysler and Jeep vehicles."), ""}, + {"JeepBrakeHold", tr("Brake Hold"), tr("Hold the brakes after Jeep ACC times out at a stop, then send resume when traffic moves again."), ""}, + {"GMToggles", tr("General Motors Settings"), tr("StarPilot features for General Motors vehicles."), ""}, {"GMPedalLongitudinal", tr("Use Pedal For Longitudinal"), tr("Use the pedal interceptor for full longitudinal control on supported GM vehicles."), ""}, {"GMDashSpoofOffsets", tr("Apply Offsets To Dash Spoof"), tr("On GM pedal-long cars, add the configured set-speed offset to the spoofed dash set speed so it matches the on-screen set speed."), ""}, @@ -209,7 +215,15 @@ StarPilotVehiclesPanel::StarPilotVehiclesPanel(StarPilotSettingsWindow *parent, for (const auto &[param, title, desc, icon] : vehicleToggles) { AbstractControl *vehicleToggle; - if (param == "GMToggles") { + if (param == "ChryslerToggles") { + ButtonControl *chryslerButton = new ButtonControl(title, tr("MANAGE"), desc); + QObject::connect(chryslerButton, &ButtonControl::clicked, [vehiclesLayout, chryslerPanel, this]() { + openDescriptions(forceOpenDescriptions, toggles); + vehiclesLayout->setCurrentWidget(chryslerPanel); + }); + vehicleToggle = chryslerButton; + + } else if (param == "GMToggles") { ButtonControl *gmButton = new ButtonControl(title, tr("MANAGE"), desc); QObject::connect(gmButton, &ButtonControl::clicked, [vehiclesLayout, gmPanel, this]() { openDescriptions(forceOpenDescriptions, toggles); @@ -275,7 +289,9 @@ StarPilotVehiclesPanel::StarPilotVehiclesPanel(StarPilotSettingsWindow *parent, toggles[param] = vehicleToggle; - if (gmKeys.contains(param)) { + if (chryslerKeys.contains(param)) { + chryslerList->addItem(vehicleToggle); + } else if (gmKeys.contains(param)) { gmList->addItem(vehicleToggle); } else if (hkgKeys.contains(param)) { hkgList->addItem(vehicleToggle); @@ -407,7 +423,9 @@ void StarPilotVehiclesPanel::updateToggles() { bool setVisible = showAllToggles || parent->tuningLevel >= parent->starpilotToggleLevels[key].toDouble(); if (!showAllToggles) { - if (gmKeys.contains(key)) { + if (chryslerKeys.contains(key)) { + setVisible &= parent->isJeep; + } else if (gmKeys.contains(key)) { setVisible &= parent->isGM; } else if (hkgKeys.contains(key)) { setVisible &= parent->isHKGCanFd && parent->hasOpenpilotLongitudinal; @@ -455,7 +473,9 @@ void StarPilotVehiclesPanel::updateToggles() { toggle->setVisible(setVisible); if (setVisible) { - if (gmKeys.contains(key)) { + if (chryslerKeys.contains(key)) { + toggles["ChryslerToggles"]->setVisible(true); + } else if (gmKeys.contains(key)) { toggles["GMToggles"]->setVisible(true); } else if (hkgKeys.contains(key)) { toggles["HKGToggles"]->setVisible(true); diff --git a/starpilot/ui/qt/offroad/vehicle_settings.h b/starpilot/ui/qt/offroad/vehicle_settings.h index 2b4ead133..7142121fa 100644 --- a/starpilot/ui/qt/offroad/vehicle_settings.h +++ b/starpilot/ui/qt/offroad/vehicle_settings.h @@ -23,6 +23,7 @@ private: std::map toggles; + QSet chryslerKeys = {"JeepBrakeHold"}; QSet gmKeys = {"GMPedalLongitudinal", "GMDashSpoofOffsets", "IgnoreIgnitionLine", "LongPitch", "RemoteStartBootsComma", "RemapCancelToDistance", "VoltSNG"}; QSet hkgKeys = {"HKGRemoteStartBootsComma"}; QSet longitudinalKeys = {"FrogsGoMoosTweak", "GMDashSpoofOffsets", "LongPitch", "RemapCancelToDistance", "SNGHack", "VoltSNG"};