diff --git a/opendbc_repo/opendbc/car/chrysler/carcontroller.py b/opendbc_repo/opendbc/car/chrysler/carcontroller.py index 23209489a1..c322e9a4c8 100644 --- a/opendbc_repo/opendbc/car/chrysler/carcontroller.py +++ b/opendbc_repo/opendbc/car/chrysler/carcontroller.py @@ -2,7 +2,8 @@ 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 CUSW_CARS, RAM_CARS, CarControllerParams, ChryslerFlags +from opendbc.car.chrysler.values import CUSW_CARS, RAM_CARS, RAM_DT, CarControllerParams, ChryslerFlags +from opendbc.car.structs import CarState from opendbc.car.interfaces import CarControllerBase @@ -50,7 +51,15 @@ class CarController(CarControllerBase): # TODO: can we make this more sane? why is it different for all the cars? lkas_control_bit = self.lkas_control_bit_prev - if CS.out.vEgo > self.CP.minSteerSpeed: + if self.CP.flags & ChryslerFlags.STEERING_SPEED_BYPASS: + lkas_control_bit = CC.latActive + elif self.CP.carFingerprint in RAM_DT: + # Arm in the EPS speed window, then keep the request through lower speeds. + if self.CP.minEnableSpeed <= CS.out.vEgo <= self.CP.minEnableSpeed + 0.5: + lkas_control_bit = True + if self.CP.minEnableSpeed >= 14.5 and CS.out.gearShifter != CarState.GearShifter.drive: + lkas_control_bit = False + elif CS.out.vEgo > self.CP.minSteerSpeed: lkas_control_bit = True elif self.CP.flags & ChryslerFlags.HIGHER_MIN_STEERING_SPEED: if CS.out.vEgo < (self.CP.minSteerSpeed - 3.0): diff --git a/opendbc_repo/opendbc/car/chrysler/interface.py b/opendbc_repo/opendbc/car/chrysler/interface.py index 61b10810fa..4bf4be16ff 100755 --- a/opendbc_repo/opendbc/car/chrysler/interface.py +++ b/opendbc_repo/opendbc/car/chrysler/interface.py @@ -66,9 +66,8 @@ class CarInterface(CarInterfaceBase): # Ram elif candidate == CAR.RAM_1500_5TH_GEN: ret.steerActuatorDelay = 0.2 - # Older EPS FW allow steer to zero - if any(fw.ecu == 'eps' and b"68" < fw.fwVersion[:4] <= b"6831" for fw in car_fw): - ret.minSteerSpeed = 0. + ret.minEnableSpeed = 14.5 + ret.minSteerSpeed = 0.5 elif candidate == CAR.RAM_HD_5TH_GEN: ret.steerActuatorDelay = 0.2 @@ -81,6 +80,10 @@ class CarInterface(CarInterfaceBase): # TODO: Durango 2020 may be able to steer to zero once above 38 kph ret.minSteerSpeed = 17.5 # m/s 17 on the way up, 13 on the way down once engaged. + if candidate not in CUSW_CARS and 0x4FF in fingerprint[0]: + ret.flags |= ChryslerFlags.STEERING_SPEED_BYPASS.value + ret.minSteerSpeed = 0. + ret.centerToFront = ret.wheelbase * 0.44 if (0x62cc033 if candidate in CUSW_CARS else 0x2d0) in fingerprint[0]: ret.flags |= ChryslerFlags.HAS_BSM.value diff --git a/opendbc_repo/opendbc/car/chrysler/tests/__init__.py b/opendbc_repo/opendbc/car/chrysler/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/opendbc_repo/opendbc/car/chrysler/tests/test_ram_steering.py b/opendbc_repo/opendbc/car/chrysler/tests/test_ram_steering.py new file mode 100644 index 0000000000..21ff807cdf --- /dev/null +++ b/opendbc_repo/opendbc/car/chrysler/tests/test_ram_steering.py @@ -0,0 +1,84 @@ +import unittest + +from opendbc.can import CANParser +from opendbc.car import Bus, gen_empty_fingerprint, structs +from opendbc.car.chrysler.interface import CarInterface +from opendbc.car.chrysler.values import CAR, DBC + + +def params(alpha=False, release=False, firmware=b"68410000"): + fw = structs.CarParams.CarFw(ecu=structs.CarParams.Ecu.eps, fwVersion=firmware) + return CarInterface.get_params(CAR.RAM_1500_5TH_GEN, gen_empty_fingerprint(), [fw], alpha, release, False) + + +class TestRamSteering(unittest.TestCase): + def test_enable_window_is_separate_from_steering_minimum(self): + for firmware in (b"68310000", b"68410000"): + for alpha in (False, True): + for release in (False, True): + with self.subTest(firmware=firmware, alpha=alpha, release=release): + cp = params(alpha, release, firmware) + self.assertEqual(cp.minEnableSpeed, 14.5) + self.assertEqual(cp.minSteerSpeed, 0.5) + self.assertEqual(cp.safetyConfigs[0].safetyParam, 1) + self.assertTrue(cp.pcmCruise) + self.assertFalse(cp.openpilotLongitudinalControl) + + def test_window_latches_but_gear_change_and_cooldown_block_reentry(self): + ci = CarInterface(params()) + ci.update([]) + command = structs.CarControl(latActive=True) + command.actuators.torque = 0.1 + gear = structs.CarState.GearShifter + parser = CANParser(DBC[CAR.RAM_1500_5TH_GEN][Bus.pt], [("LKAS_COMMAND", 0)], 0) + cases = ( + (202, 15.1, gear.drive, False), + (204, 14.5, gear.drive, True), + (206, 25, gear.drive, True), + (208, 2, gear.drive, True), + (210, 14.75, gear.reverse, False), + (212, 14.75, gear.drive, False), + (410, 14.75, gear.drive, False), + (412, 14.75, gear.drive, True), + (414, 14.75, gear.neutral, False), + (616, 15, gear.drive, True), + ) + for frame, speed, selected_gear, expected in cases: + with self.subTest(frame=frame): + ci.CC.frame = frame + ci.CS.out.vEgo = speed + ci.CS.out.gearShifter = selected_gear + _, messages = ci.CC.update(command.as_reader(), ci.CS, frame * 10_000_000) + parser.update((frame * 10_000_000, messages)) + self.assertEqual(ci.CC.lkas_control_bit_prev, expected) + if not expected: + self.assertEqual(parser.vl["LKAS_COMMAND"]["STEERING_TORQUE"], 0) + + def test_full_host_torque_ramp_passes_existing_native_limits(self): + from opendbc.safety.tests.libsafety import libsafety_py + from opendbc.safety.tests.libsafety.libsafety_py import make_CANPacket + + native = libsafety_py.libsafety + self.assertEqual(native.set_safety_hooks(9, 1), 0) + native.init_tests() + native.set_controls_allowed(True) + ci = CarInterface(params()) + ci.update([]) + ci.CS.out.vEgo = 14.75 + ci.CS.out.gearShifter = structs.CarState.GearShifter.drive + command = structs.CarControl(latActive=True) + command.actuators.torque = 1 + for frame in range(202, 342, 2): + ci.CC.frame = frame + ci.CS.out.steeringTorqueEps = ci.CC.apply_torque_last + native.set_torque_meas(ci.CC.apply_torque_last, ci.CC.apply_torque_last) + native.set_timer(frame * 10_000) + actuators, messages = ci.CC.update(command.as_reader(), ci.CS, frame * 10_000_000) + for address, data, bus in messages: + self.assertTrue(native.safety_tx_hook(make_CANPacket(address, bus, data)), (frame, address, data)) + self.assertEqual(actuators.torqueOutputCan, 350) + self.assertEqual(actuators.torque, 1) + command.latActive = False + ci.CC.frame = 344 + actuators, _ = ci.CC.update(command.as_reader(), ci.CS, 3_440_000_000) + self.assertEqual(actuators.torqueOutputCan, 0) diff --git a/opendbc_repo/opendbc/car/chrysler/tests/test_steering_bypass.py b/opendbc_repo/opendbc/car/chrysler/tests/test_steering_bypass.py new file mode 100644 index 0000000000..ec0b1d4fd7 --- /dev/null +++ b/opendbc_repo/opendbc/car/chrysler/tests/test_steering_bypass.py @@ -0,0 +1,82 @@ +import unittest + +from opendbc.can import CANParser +from opendbc.car import Bus, gen_empty_fingerprint, structs +from opendbc.car.chrysler.interface import CarInterface +from opendbc.car.chrysler.values import CAR, CUSW_CARS, DBC, ChryslerFlags + + +def params(car, bus=None): + fingerprint = gen_empty_fingerprint() + if bus is not None: + fingerprint[bus][0x4FF] = 8 + return CarInterface.get_params(car, fingerprint, [], False, False, False) + + +class TestSteeringSpeedBypass(unittest.TestCase): + def test_blind_spot_detection_does_not_enable_speed_bypass(self): + car = CAR.CHRYSLER_PACIFICA_2020 + baseline = params(car) + for blind_spot in (False, True): + for bypass in (False, True): + for bus in (0, 1, 2): + with self.subTest(blind_spot=blind_spot, bypass=bypass, bus=bus): + fingerprint = gen_empty_fingerprint() + if blind_spot: + fingerprint[bus][0x2d0] = 8 + if bypass: + fingerprint[bus][0x4ff] = 8 + cp = CarInterface.get_params(car, fingerprint, [], False, False, False) + self.assertEqual(bool(cp.flags & ChryslerFlags.HAS_BSM), blind_spot and bus == 0) + self.assertEqual(bool(cp.flags & ChryslerFlags.STEERING_SPEED_BYPASS), bypass and bus == 0) + self.assertEqual(cp.minSteerSpeed, 0 if bypass and bus == 0 else baseline.minSteerSpeed) + self.assertEqual([(str(c.safetyModel), c.safetyParam) for c in cp.safetyConfigs], + [(str(c.safetyModel), c.safetyParam) for c in baseline.safetyConfigs]) + + def test_hardware_is_scoped_to_original_platforms_and_bus(self): + for car in CAR: + baseline = params(car) + for bus in (0, 1, 2): + with self.subTest(car=car, bus=bus): + cp = params(car, bus) + supported = bus == 0 and car not in CUSW_CARS + self.assertEqual(bool(cp.flags & ChryslerFlags.STEERING_SPEED_BYPASS), supported) + self.assertEqual(cp.minSteerSpeed, 0 if supported else baseline.minSteerSpeed) + self.assertEqual([(str(c.safetyModel), c.safetyParam) for c in cp.safetyConfigs], + [(str(c.safetyModel), c.safetyParam) for c in baseline.safetyConfigs]) + self.assertEqual(cp.dashcamOnly, baseline.dashcamOnly) + self.assertEqual(cp.pcmCruise, baseline.pcmCruise) + self.assertFalse(cp.openpilotLongitudinalControl) + + def test_standstill_engagement_and_reentry_keep_eps_cooldown(self): + for car in set(CAR) - CUSW_CARS: + with self.subTest(car=car): + ci = CarInterface(params(car, 0)) + ci.update([]) + ci.CS.out.vEgo = 0 + command = structs.CarControl() + command.actuators.torque = 0.1 + parser = CANParser(DBC[car][Bus.pt], [('LKAS_COMMAND', 0)], 0) + for frame, active, expected in ((200, True, False), (202, True, True), (204, True, True), + (206, False, False), (208, True, False), + (406, True, False), (408, True, True)): + ci.CC.frame = frame + command.latActive = active + _, sends = ci.CC.update(command.as_reader(), ci.CS, frame * 10_000_000) + parser.update((frame * 10_000_000, sends)) + self.assertEqual(ci.CC.lkas_control_bit_prev, expected) + if not expected: + self.assertEqual(parser.vl['LKAS_COMMAND']['STEERING_TORQUE'], 0) + + def test_without_hardware_standstill_does_not_enable_steering(self): + for car in set(CAR) - CUSW_CARS: + with self.subTest(car=car): + ci = CarInterface(params(car)) + ci.update([]) + ci.CC.frame = 202 + command = structs.CarControl() + command.latActive = True + command.actuators.torque = 0.1 + actuators, _ = ci.CC.update(command.as_reader(), ci.CS, 2_020_000_000) + self.assertFalse(ci.CC.lkas_control_bit_prev) + self.assertEqual(actuators.torqueOutputCan, 0) diff --git a/opendbc_repo/opendbc/car/chrysler/values.py b/opendbc_repo/opendbc/car/chrysler/values.py index fb54c865c2..44f1441230 100644 --- a/opendbc_repo/opendbc/car/chrysler/values.py +++ b/opendbc_repo/opendbc/car/chrysler/values.py @@ -18,6 +18,7 @@ class ChryslerFlags(IntFlag): # Detected flags HIGHER_MIN_STEERING_SPEED = 1 HAS_BSM = 2 # blind spot monitoring + STEERING_SPEED_BYPASS = 4 @dataclass @@ -110,7 +111,7 @@ class CarControllerParams: elif CP.carFingerprint in RAM_DT: self.STEER_DELTA_UP = 6 self.STEER_DELTA_DOWN = 6 - self.STEER_MAX = 261 # EPS allows more, up to 350? + self.STEER_MAX = 350 elif CP.carFingerprint in CUSW_CARS: self.STEER_STEP = 1 # 100 Hz self.STEER_DELTA_UP = 4