This commit is contained in:
firestar5683
2026-10-05 22:50:38 -05:00
parent d9bd49fb8a
commit 34ecbf9204
9 changed files with 302 additions and 21 deletions
@@ -11,7 +11,7 @@ from opendbc.car.interfaces import CarControllerBase
from opendbc.car.toyota import toyotacan
from opendbc.car.toyota.values import CAR, MIN_ACC_SPEED, NO_STOP_TIMER_CAR, PEDAL_TRANSITION, TSS2_CAR, \
CarControllerParams, ToyotaFlags, ToyotaSafetyFlags, \
UNSUPPORTED_DSU_CAR, LEGACY_PRIUS_CAR, TOYOTA_AUTO_HOLD_CARS, TOYOTA_AUTO_HOLD_AEB_CARS
UNSUPPORTED_DSU_CAR, LEGACY_PRIUS_CAR, TOYOTA_AUTO_HOLD_CARS, uses_toyota_auto_hold_aeb
from opendbc.can import CANPacker
Ecu = structs.CarParams.Ecu
@@ -373,7 +373,7 @@ class CarController(CarControllerBase):
return []
def reset_auto_hold_state(self):
if self.brake_hold_active and self.CP.carFingerprint not in TOYOTA_AUTO_HOLD_AEB_CARS:
if self.brake_hold_active and not uses_toyota_auto_hold_aeb(self.CP):
self.standstill_req = False
self._brake_hold_counter = 0
self.brake_hold_active = False
@@ -475,7 +475,7 @@ class CarController(CarControllerBase):
self._update_standstill_request(CC, CS, actuators, starpilot_toggles)
if supports_toyota_auto_hold(self.CP, getattr(starpilot_toggles, "toyota_auto_hold", False)):
if self.CP.carFingerprint in TOYOTA_AUTO_HOLD_AEB_CARS:
if uses_toyota_auto_hold_aeb(self.CP):
can_sends.extend(self.create_auto_brake_hold_messages(CS))
else:
self.update_auto_hold_state(CS, pcm_cancel_cmd, long_active=CC.longActive, stopping=stopping)
@@ -590,7 +590,7 @@ class CarController(CarControllerBase):
pcm_accel_cmd = float(np.clip(pcm_accel_cmd, self.params.ACCEL_MIN, self.params.ACCEL_MAX))
if self.brake_hold_active and self.CP.carFingerprint not in TOYOTA_AUTO_HOLD_AEB_CARS:
if self.brake_hold_active and not uses_toyota_auto_hold_aeb(self.CP):
pcm_accel_cmd = TOYOTA_AUTO_HOLD_ACCEL
self.permit_braking = True
self.standstill_req = True
+2 -2
View File
@@ -5,7 +5,7 @@ from opendbc.car.toyota.radar_interface import RadarInterface
from opendbc.car.toyota.values import Ecu, CAR, DBC, ToyotaFlags, CarControllerParams, TSS2_CAR, RADAR_ACC_CAR, NO_DSU_CAR, \
MIN_ACC_SPEED, EPS_SCALE, NO_STOP_TIMER_CAR, ANGLE_CONTROL_CAR, \
ToyotaSafetyFlags, LEGACY_PRIUS_CAR, TOYOTA_AUTO_HOLD_CARS, \
TOYOTA_AUTO_HOLD_AEB_CARS
uses_toyota_auto_hold_aeb
from opendbc.car.disable_ecu import disable_ecu
from opendbc.car.interfaces import CarInterfaceBase
from opendbc.safety import ALTERNATIVE_EXPERIENCE
@@ -167,7 +167,7 @@ class CarInterface(CarInterfaceBase):
toyota_auto_hold = Params(return_defaults=True).get_bool("ToyotaAutoHold")
if toyota_auto_hold and ret.openpilotLongitudinalControl and candidate in TOYOTA_AUTO_HOLD_CARS:
ret.alternativeExperience |= (ALTERNATIVE_EXPERIENCE.ALLOW_AEB
if candidate in TOYOTA_AUTO_HOLD_AEB_CARS
if uses_toyota_auto_hold_aeb(ret)
else ALTERNATIVE_EXPERIENCE.TOYOTA_AUTO_HOLD)
ret.flags |= ToyotaFlags.AUTO_BRAKE_HOLD.value
@@ -25,7 +25,7 @@ from opendbc.car.toyota.radar_interface import RadarInterface, TSSP_RADAR_EGO_SP
from opendbc.car.toyota.values import CAR, DBC, MIN_ACC_SPEED, TSS2_CAR, ANGLE_CONTROL_CAR, RADAR_ACC_CAR, SECOC_CAR, \
FW_QUERY_CONFIG, PLATFORM_CODE_ECUS, FUZZY_EXCLUDED_PLATFORMS, \
ToyotaFlags, ToyotaSafetyFlags, ToyotaStarPilotFlags, TOYOTA_AUTO_HOLD_CARS, \
TOYOTA_AUTO_HOLD_AEB_CARS, \
uses_toyota_auto_hold_aeb, \
get_platform_codes
from opendbc.safety import ALTERNATIVE_EXPERIENCE
from openpilot.common.params import Params
@@ -193,8 +193,15 @@ class TestToyotaInterfaces:
if car_model in TSS2_CAR and car_model not in SECOC_CAR:
assert dbc[Bus.pt] == "toyota_nodsu_pt_generated"
@pytest.mark.parametrize("candidate", [CAR.TOYOTA_CAMRY_TSS2, CAR.TOYOTA_RAV4, CAR.TOYOTA_RAV4H])
def test_auto_hold_sets_flag_on_supported_toyota(self, candidate):
@pytest.mark.parametrize("candidate,hybrid", [
(CAR.TOYOTA_CAMRY_TSS2, False),
(CAR.TOYOTA_RAV4, False),
(CAR.TOYOTA_RAV4H, True),
(CAR.TOYOTA_RAV4_TSS2, False),
(CAR.TOYOTA_RAV4_TSS2, True),
(CAR.TOYOTA_COROLLA_TSS2, True),
])
def test_auto_hold_sets_flag_on_supported_toyota(self, candidate, hybrid):
params = Params()
try:
params.put_bool("ToyotaAutoHold", True)
@@ -202,7 +209,7 @@ class TestToyotaInterfaces:
candidate,
{bus: ({0x2FF: 8} if candidate in (CAR.TOYOTA_RAV4, CAR.TOYOTA_RAV4H) and bus == 0 else {})
for bus in range(8)},
[],
[CarParams.CarFw(ecu=Ecu.hybrid, address=0x7D2, fwVersion=b"test")] if hybrid else [],
alpha_long=False,
is_release=False,
docs=False,
@@ -212,7 +219,9 @@ class TestToyotaInterfaces:
params.remove("ToyotaAutoHold")
assert car_params.flags & ToyotaFlags.AUTO_BRAKE_HOLD.value
if candidate in TOYOTA_AUTO_HOLD_AEB_CARS:
legacy_hold = candidate == CAR.TOYOTA_CAMRY_TSS2 or (candidate == CAR.TOYOTA_RAV4_TSS2 and hybrid)
assert uses_toyota_auto_hold_aeb(car_params) == legacy_hold
if legacy_hold:
assert car_params.alternativeExperience & ALTERNATIVE_EXPERIENCE.ALLOW_AEB
assert not car_params.alternativeExperience & ALTERNATIVE_EXPERIENCE.TOYOTA_AUTO_HOLD
else:
@@ -222,16 +231,24 @@ class TestToyotaInterfaces:
can_parsers = CarState.get_can_parsers(car_params)
car_state = CarState(car_params, SimpleNamespace(flags=0))
car_state.update(can_parsers, SimpleNamespace(cluster_offset=1.0))
assert (0x344 in can_parsers[Bus.cam].vl) == (candidate in TOYOTA_AUTO_HOLD_AEB_CARS)
assert (0x344 in can_parsers[Bus.cam].vl) == legacy_hold
assert car_state.auto_brake_hold == legacy_hold
@pytest.mark.parametrize("candidate", [CAR.TOYOTA_CAMRY_TSS2, CAR.TOYOTA_RAV4, CAR.TOYOTA_RAV4H])
def test_auto_hold_is_disabled_by_default(self, candidate):
@pytest.mark.parametrize("candidate,hybrid", [
(CAR.TOYOTA_CAMRY_TSS2, False),
(CAR.TOYOTA_RAV4, False),
(CAR.TOYOTA_RAV4H, True),
(CAR.TOYOTA_RAV4_TSS2, False),
(CAR.TOYOTA_RAV4_TSS2, True),
(CAR.TOYOTA_RAV4_TSS2_2022, True),
])
def test_auto_hold_is_disabled_by_default(self, candidate, hybrid):
params = Params()
params.remove("ToyotaAutoHold")
car_params = CarInterface.get_params(
candidate,
{bus: {} for bus in range(8)},
[],
[CarParams.CarFw(ecu=Ecu.hybrid, address=0x7D2, fwVersion=b"test")] if hybrid else [],
alpha_long=False,
is_release=False,
docs=False,
@@ -240,6 +257,8 @@ class TestToyotaInterfaces:
assert not car_params.flags & ToyotaFlags.AUTO_BRAKE_HOLD.value
assert not car_params.alternativeExperience & ALTERNATIVE_EXPERIENCE.TOYOTA_AUTO_HOLD
assert not car_params.alternativeExperience & ALTERNATIVE_EXPERIENCE.ALLOW_AEB
assert 0x344 not in CarState.get_can_parsers(car_params)[Bus.cam].vl
def test_prius_openpilot_long_uses_hybrid_long_defaults(self):
car_params = CarInterface.get_params(
@@ -1239,11 +1258,13 @@ class TestToyotaCarController:
class TestToyotaAutoHoldCruise:
@staticmethod
def _make_car(*, candidate=CAR.TOYOTA_RAV4_TSS2, enabled=True, capability=True):
def _make_car(*, candidate=CAR.TOYOTA_RAV4_TSS2, hybrid=False, enabled=True, capability=True):
cp = CarInterface.get_non_essential_params(candidate)
cp.flags &= ~ToyotaFlags.AUTO_BRAKE_HOLD.value
cp.flags &= ~(ToyotaFlags.AUTO_BRAKE_HOLD.value | ToyotaFlags.HYBRID.value)
if capability:
cp.flags |= ToyotaFlags.AUTO_BRAKE_HOLD.value
if hybrid:
cp.flags |= ToyotaFlags.HYBRID.value
controller = CarController(DBC[candidate], cp)
cc = structs.CarControl(enabled=True, longActive=True)
cc.actuators.accel = -0.7
@@ -1263,16 +1284,18 @@ class TestToyotaAutoHoldCruise:
pre_collision_2={},
)
toggles = SimpleNamespace(toyota_auto_hold=enabled, sng_hack=False, lock_doors=False, unlock_doors=False)
parser = CANParser(DBC[candidate][Bus.pt], [("ACC_CONTROL", 0)], 0)
parser = CANParser(DBC[candidate][Bus.pt], [("ACC_CONTROL", 0), ("PRE_COLLISION_2", 0)], 0)
return SimpleNamespace(controller=controller, cc=cc, cs=cs, toggles=toggles, parser=parser)
@staticmethod
def _tick(car):
# Run through a complete ACC_CONTROL send interval and decode the actual
# controller output, including the ordinary standstill and PID paths.
car.can_sends = []
for _ in range(3):
now_nanos = car.controller.frame * 10_000_000
_, messages = car.controller.update(car.cc.as_reader(), car.cs, now_nanos, car.toggles)
car.can_sends.extend(messages)
car.parser.update([(now_nanos, messages)])
return car.parser.vl["ACC_CONTROL"]
@@ -1412,6 +1435,92 @@ class TestToyotaAutoHoldCruise:
assert not car.controller.brake_hold_active
class TestToyotaAutoHoldAeb:
_make_car = staticmethod(TestToyotaAutoHoldCruise._make_car)
_tick = staticmethod(TestToyotaAutoHoldCruise._tick)
@pytest.mark.parametrize("candidate", list(CAR))
@pytest.mark.parametrize("hybrid", [False, True])
def test_legacy_path_is_scoped_to_camry_and_early_rav4_hybrid(self, candidate, hybrid):
cp = SimpleNamespace(carFingerprint=candidate, flags=ToyotaFlags.HYBRID.value if hybrid else 0)
expected = candidate == CAR.TOYOTA_CAMRY_TSS2 or (candidate == CAR.TOYOTA_RAV4_TSS2 and hybrid)
assert uses_toyota_auto_hold_aeb(cp) == expected
@pytest.mark.parametrize("candidate,hybrid", [(CAR.TOYOTA_CAMRY_TSS2, False), (CAR.TOYOTA_RAV4_TSS2, True)])
def test_manual_stop_uses_legacy_hold_and_releases_on_gas(self, candidate, hybrid):
car = self._make_car(candidate=candidate, hybrid=hybrid)
car.cc.enabled = False
car.cc.longActive = False
car.cc.latActive = True
car.cc.actuators.accel = 0.0
car.cs.out.cruiseState.enabled = False
car.cs.out.cruiseState.standstill = False
car.cs.out.brakePressed = True
car.cs.pre_collision_2 = {"DSS1GDRV": -0.1, "PBRTRGR": 0}
# Pass through the camera message until the pedal has been held at a stop.
command = self._tick(car)
assert not car.controller.brake_hold_active
assert car.parser.vl["PRE_COLLISION_2"]["DSS1GDRV"] == -0.1
for _ in range(34):
command = self._tick(car)
assert car.controller.brake_hold_active
assert any(msg[0] == 0x344 for msg in car.can_sends)
assert car.parser.vl["PRE_COLLISION_2"]["DSS1GDRV"] == -1.0
assert car.parser.vl["PRE_COLLISION_2"]["PBRTRGR"] == 1
# Do not simultaneously inject an ACC_CONTROL hold on legacy-path cars.
assert command["ACCEL_CMD"] == 0.0
assert command["RELEASE_STANDSTILL"] == 1
car.cs.out.brakePressed = False
for _ in range(50):
self._tick(car)
assert car.controller.brake_hold_active
assert car.parser.vl["PRE_COLLISION_2"]["DSS1GDRV"] == -1.0
car.cs.out.gasPressed = True
self._tick(car)
assert not car.controller.brake_hold_active
assert car.parser.vl["PRE_COLLISION_2"]["DSS1GDRV"] == -0.1
assert car.parser.vl["PRE_COLLISION_2"]["PBRTRGR"] == 0
@pytest.mark.parametrize("enabled,capability", [(False, True), (True, False)])
def test_rav4_hybrid_does_not_inject_legacy_messages_without_opt_in(self, enabled, capability):
car = self._make_car(hybrid=True, enabled=enabled, capability=capability)
car.cc.longActive = False
car.cc.actuators.accel = 0.0
car.cs.out.cruiseState.enabled = False
car.cs.out.brakePressed = True
for _ in range(50):
self._tick(car)
assert not car.controller.brake_hold_active
assert all(msg[0] != 0x344 for msg in car.can_sends)
@pytest.mark.parametrize("release", ["toggle", "main", "park", "reverse", "moving", "cruise"])
def test_rav4_hybrid_legacy_hold_releases_when_conditions_change(self, release):
car = self._make_car(hybrid=True)
car.cc.longActive = False
car.cc.actuators.accel = 0.0
car.cs.out.cruiseState.enabled = False
car.cs.out.brakePressed = True
for _ in range(35):
self._tick(car)
assert car.controller.brake_hold_active
if release == "toggle":
car.toggles.toyota_auto_hold = False
elif release == "main":
car.cs.out.cruiseState.available = False
elif release in ("park", "reverse"):
car.cs.out.gearShifter = getattr(structs.CarState.GearShifter, release)
elif release == "moving":
car.cs.out.standstill = False
else:
car.cs.out.cruiseState.enabled = True
self._tick(car)
assert not car.controller.brake_hold_active
class TestToyotaCarState:
@pytest.mark.parametrize("candidate", [CAR.TOYOTA_PRIUS, CAR.TOYOTA_PRIUS_RETROFIT])
def test_legacy_prius_distance_button_generates_events(self, candidate):
+8 -2
View File
@@ -629,10 +629,16 @@ TOYOTA_AUTO_HOLD_CARS = (TSS2_CAR - RADAR_ACC_CAR - SECOC_CAR) | {
CAR.TOYOTA_RAV4H,
}
# The Camry uses the legacy camera AEB replacement for Auto Hold. Other
# supported Toyota models use the ACC_CONTROL hold request.
# The Camry uses the legacy camera AEB replacement for Auto Hold. The
# 2019-2021 RAV4 uses it only with the detected hybrid powertrain.
TOYOTA_AUTO_HOLD_AEB_CARS = {CAR.TOYOTA_CAMRY_TSS2}
def uses_toyota_auto_hold_aeb(CP: CarParams) -> bool:
return (CP.carFingerprint in TOYOTA_AUTO_HOLD_AEB_CARS or
(CP.carFingerprint == CAR.TOYOTA_RAV4_TSS2 and bool(CP.flags & ToyotaFlags.HYBRID.value)))
# no resume button press required
NO_STOP_TIMER_CAR = CAR.with_flags(ToyotaFlags.NO_STOP_TIMER)
@@ -13,6 +13,7 @@ from openpilot.common.pid import PIDController
from openpilot.selfdrive.controls.lib.drive_helpers import MIN_SPEED
from openpilot.selfdrive.controls.lib.latcontrol import LatControl
from openpilot.selfdrive.controls.lib.latcontrol_vehicle_tunes import * # noqa: F403
from openpilot.selfdrive.controls.lib.latcontrol_vehicle_tunes import get_genesis_g70_center_measurement_damping_gain
# At higher speeds (25+mph) we can assume:
# Lateral acceleration achieved by a specific car correlates to
@@ -561,6 +562,11 @@ class LatControlTorque(LatControl):
ff, self.gv70_previous_feedforward, setpoint, desired_lateral_jerk, CS.vEgo, self.dt,
)
self.gv70_previous_feedforward = ff
if self.is_genesis_g70:
damping_gain = 0.0 if CS.steeringPressed else get_genesis_g70_center_measurement_damping_gain(
CS.vEgo, setpoint, measurement, desired_lateral_jerk,
)
self.pid._k_d = [[0.0], [damping_gain]]
freeze_integrator = (steer_limited_by_safety or CS.steeringPressed or
CS.vEgo < self.low_speed_reset_threshold or unwind_detected)
error_rate = 0.0 if self.is_genesis_gv70 and CS.steeringPressed else -measurement_rate
@@ -366,6 +366,10 @@ GENESIS_G70_OUTPUT_SMOOTHING_CENTER_RC = 0.18
GENESIS_G70_OUTPUT_SMOOTHING_CURVE_RC = 0.10
GENESIS_G70_OUTPUT_SMOOTHING_RELEASE_RC = 0.03
GENESIS_G70_OUTPUT_SMOOTHING_OVERSHOOT = 0.08
GENESIS_G70_CENTER_MEASUREMENT_DAMPING_MAX = 0.06
GENESIS_G70_CENTER_MEASUREMENT_DAMPING_SPEED_BP = [50.0 * CV.MPH_TO_MS, 60.0 * CV.MPH_TO_MS]
GENESIS_G70_CENTER_MEASUREMENT_DAMPING_LAT_BP = [0.15, 0.35]
GENESIS_G70_CENTER_MEASUREMENT_DAMPING_JERK_BP = [0.20, 0.50]
GENESIS_G70_HIGHWAY_STABILIZER_CENTER_LAT_BP = [1.4, 1.8]
GENESIS_G70_HIGHWAY_STABILIZER_CURVE_EXIT_LAT = 0.15
GENESIS_G70_ANGLE_OUTPUT_TAPER_MIN = 0.45
@@ -3574,6 +3578,15 @@ def get_genesis_g70_highway_turn_in_output_scale(output_torque: float, setpoint:
return 1.0 - GENESIS_G70_HIGHWAY_TURN_IN_OUTPUT_REDUCTION * speed_weight * curve_weight * jerk_weight * tracking_weight
def get_genesis_g70_center_measurement_damping_gain(v_ego: float, desired_lateral_accel: float,
measured_lateral_accel: float, desired_lateral_jerk: float) -> float:
speed_weight = np.interp(v_ego, GENESIS_G70_CENTER_MEASUREMENT_DAMPING_SPEED_BP, [0.0, 1.0])
center_weight = np.interp(max(abs(desired_lateral_accel), abs(measured_lateral_accel)),
GENESIS_G70_CENTER_MEASUREMENT_DAMPING_LAT_BP, [1.0, 0.0])
jerk_weight = np.interp(abs(desired_lateral_jerk), GENESIS_G70_CENTER_MEASUREMENT_DAMPING_JERK_BP, [1.0, 0.0])
return float(GENESIS_G70_CENTER_MEASUREMENT_DAMPING_MAX * speed_weight * center_weight * jerk_weight)
def get_genesis_g70_stabilized_output(output_torque: float, prev_output_torque: float,
desired_lateral_accel: float, measured_lateral_accel: float,
desired_lateral_jerk: float, v_ego: float, dt: float) -> float:
@@ -61,6 +61,7 @@ from openpilot.selfdrive.controls.lib.latcontrol_vehicle_tunes import (
get_genesis_gv70_low_speed_center_overshoot_scale,
get_genesis_gv70_stabilized_output,
get_genesis_g70_stabilized_output,
get_genesis_g70_center_measurement_damping_gain,
normalize_flm_overrides,
set_flm_runtime_overrides,
)
@@ -1041,6 +1042,73 @@ class TestLatControl:
assert lac_log.active
assert output == pytest.approx(-0.123)
@pytest.mark.parametrize("mph,desired,measured,jerk,expected", [
(65.0, 0.0, 0.10, 0.0, 0.06),
(50.0, 0.0, 0.10, 0.0, 0.0),
(55.0, 0.0, 0.10, 0.0, 0.03),
(65.0, 0.25, 0.10, 0.0, 0.03),
(65.0, 0.0, 0.25, 0.0, 0.03),
(65.0, 0.35, 0.10, 0.0, 0.0),
(65.0, 0.0, 0.35, 0.0, 0.0),
(65.0, 0.0, 0.10, 0.35, 0.03),
(65.0, 0.0, 0.10, 0.50, 0.0),
])
def test_genesis_g70_center_measurement_damping_gates(self, mph, desired, measured, jerk, expected):
for direction in [-1.0, 1.0]:
gain = get_genesis_g70_center_measurement_damping_gain(
mph * 0.44704, desired * direction, measured * direction, jerk * direction,
)
assert gain == pytest.approx(expected)
@pytest.mark.parametrize("direction", [-1.0, 1.0])
def test_genesis_g70_center_measurement_damping_update_path(self, direction):
controller, VM, CS, params, toggles = self._build_torque_controller(HYUNDAI.GENESIS_G70_2020)
CS.vEgo = 65.0 * 0.44704
CS.steeringAngleDeg = 0.0
controller.update(True, CS, VM, params, False, 0.0, False, 0.2, None, None, toggles)
CS.steeringAngleDeg = -direction * 0.5
output, _, moving_log = controller.update(True, CS, VM, params, False, 0.0, False, 0.2, None, None, toggles)
assert moving_log.d * direction < 0.0
assert abs(moving_log.d) <= 0.15
assert abs(output) <= controller.steer_max
for _ in range(150):
_, _, steady_log = controller.update(True, CS, VM, params, False, 0.0, False, 0.2, None, None, toggles)
assert steady_log.d == pytest.approx(0.0, abs=1e-6)
CS.steeringPressed = True
CS.steeringAngleDeg = 0.0
_, _, driver_log = controller.update(True, CS, VM, params, False, 0.0, False, 0.2, None, None, toggles)
assert driver_log.d == 0.0
CS.steeringPressed = False
controller.update(False, CS, VM, params, False, 0.0, False, 0.2, None, None, toggles)
_, _, resumed_log = controller.update(True, CS, VM, params, False, 0.0, False, 0.2, None, None, toggles)
assert resumed_log.d == 0.0
CS.vEgo = 40.0 * 0.44704
CS.steeringAngleDeg = -direction * 0.5
_, _, low_speed_log = controller.update(True, CS, VM, params, False, 0.0, False, 0.2, None, None, toggles)
assert low_speed_log.d == 0.0
CS.vEgo = 65.0 * 0.44704
curvature = direction * 0.8 / CS.vEgo ** 2
controller.curvature_request_buffer = deque([curvature] * controller.request_buffer_len,
maxlen=controller.request_buffer_len)
_, _, curve_log = controller.update(True, CS, VM, params, False, curvature, False, 0.2, None, None, toggles)
assert curve_log.d == 0.0
@pytest.mark.parametrize("car_name", [HYUNDAI.GENESIS_GV70_1ST_GEN, HYUNDAI.KIA_EV6, HYUNDAI.HYUNDAI_IONIQ_6])
def test_genesis_g70_center_measurement_damping_does_not_change_other_cars(self, monkeypatch, car_name):
def unexpected_damping(*_args):
pytest.fail("G70 center damping reached another vehicle")
monkeypatch.setattr(latcontrol_torque, "get_genesis_g70_center_measurement_damping_gain", unexpected_damping)
controller, VM, CS, params, toggles = self._build_torque_controller(car_name)
CS.vEgo = 65.0 * 0.44704
controller.update(True, CS, VM, params, False, 0.0, False, 0.2, None, None, toggles)
assert controller.pid.d == 0.0
def test_sonata_hybrid_center_output_taper_is_mid_speed_and_center_gated(self):
low_speed = get_sonata_hybrid_center_output_scale(0.0, 8.0)
center = get_sonata_hybrid_center_output_scale(0.0, 13.4)
+11 -1
View File
@@ -264,7 +264,17 @@ class FordLateralController:
[MACH_E_DIRECTION_CHANGE_MIN_PREVIEW_CURVATURE, MACH_E_DIRECTION_CHANGE_FULL_PREVIEW_CURVATURE],
[0.0, 1.0],
))
return base + (MACH_E_CURVATURE_ERROR_MAX - base) * speed_weight * max(deficit_weight, reversal_weight)
unwind_weight = 0.0
if (self.model is not None and len(self.model.orientationRate.z) >= 17 and
requested * current >= 0.0 and desired * current >= 0.0 and abs(desired) < abs(current)):
unwind_deficit = np.sign(current) * (current - requested)
preview_deficit = np.sign(current) * (current - predicted)
unwind_weight = float(np.interp(
min(unwind_deficit, preview_deficit),
[MACH_E_UNDERSTEER_ERROR_MIN_DEFICIT, MACH_E_UNDERSTEER_ERROR_FULL_DEFICIT],
[0.0, 1.0],
))
return base + (MACH_E_CURVATURE_ERROR_MAX - base) * speed_weight * max(deficit_weight, reversal_weight, unwind_weight)
def _path_angle_assist(self, requested: float, desired: float, applied: float, current: float, v_ego: float,
steering_pressed: bool, lane_change: bool) -> float:
+69
View File
@@ -130,6 +130,75 @@ def test_understeer_error_preserves_other_fords(controller):
assert controller._curvature_error_limit(0.012, 0.012, 0.004, 12.0, False, False) == 0.002
@pytest.mark.parametrize("sign", (-1, 1))
@pytest.mark.parametrize("speed,expected", ((8.0, 0.002), (8.5, 0.004), (9.0, 0.006), (12.0, 0.006),
(14.0, 0.006), (15.0, 0.004), (16.0, 0.002)))
def test_mach_e_unwind_error_tracks_opening_path_before_direction_changes(controller, sign, speed, expected):
controller.CP.carFingerprint = CAR.FORD_MUSTANG_MACH_E_MK1
controller.CP.flags = FordFlags.CANFD
controller.model = SimpleNamespace(orientationRate=SimpleNamespace(z=[0.0] * 33))
assert controller._curvature_error_limit(
sign * 0.001, sign * 0.002, sign * 0.005, speed, False, False, sign * 0.001) == pytest.approx(expected)
@pytest.mark.parametrize("sign", (-1, 1))
@pytest.mark.parametrize("requested,preview,expected", ((0.003, 0.001, 0.002), (0.002, 0.001, 0.004),
(0.001, 0.001, 0.006), (0.001, 0.004, 0.002),
(0.001, 0.003, 0.002), (0.001, 0.002, 0.004),
(0.001, -0.0002, 0.006), (0.001, 0.0, 0.006),
(0.0, 0.0, 0.006)))
def test_mach_e_unwind_error_requires_measured_lag_and_opening_preview(controller, sign, requested, preview, expected):
controller.CP.carFingerprint = CAR.FORD_MUSTANG_MACH_E_MK1
controller.CP.flags = FordFlags.CANFD
controller.model = SimpleNamespace(orientationRate=SimpleNamespace(z=[0.0] * 33))
assert controller._curvature_error_limit(
sign * requested, sign * 0.002, sign * 0.005, 12.0, False, False, sign * preview) == pytest.approx(expected)
@pytest.mark.parametrize("fingerprint,flags,driver,lane_change", (
(CAR.FORD_MUSTANG_MACH_E_MK1, FordFlags.CANFD, True, False),
(CAR.FORD_MUSTANG_MACH_E_MK1, FordFlags.CANFD, False, True),
(CAR.FORD_MUSTANG_MACH_E_MK1, 0, False, False),
(CAR.FORD_EDGE_MK2, FordFlags.CANFD, False, False),
(CAR.FORD_EXPLORER_MK6, FordFlags.CANFD, False, False),
(CAR.FORD_F_150_MK14, FordFlags.CANFD, False, False),
))
def test_unwind_error_preserves_takeover_lane_changes_and_other_fords(controller, fingerprint, flags, driver, lane_change):
controller.CP.carFingerprint = fingerprint
controller.CP.flags = flags
controller.model = SimpleNamespace(orientationRate=SimpleNamespace(z=[0.0] * 33))
assert controller._curvature_error_limit(0.001, 0.002, 0.005, 12.0, driver, lane_change, 0.001) == 0.002
@pytest.mark.parametrize("model", (None, SimpleNamespace(orientationRate=SimpleNamespace(z=[0.0] * 16))))
def test_mach_e_unwind_error_requires_model_preview(controller, model):
controller.CP.carFingerprint = CAR.FORD_MUSTANG_MACH_E_MK1
controller.CP.flags = FordFlags.CANFD
controller.model = model
assert controller._curvature_error_limit(0.001, 0.002, 0.005, 12.0, False, False, 0.001) == 0.002
@pytest.mark.parametrize("sign", (-1, 1))
def test_mach_e_curve_exit_releases_without_waiting_for_left_right_reversal(controller, monkeypatch, sign):
controller.CP.carFingerprint = CAR.FORD_MUSTANG_MACH_E_MK1
controller.CP.flags = FordFlags.CANFD
controller.model = SimpleNamespace(orientationRate=SimpleNamespace(z=[0.0] * 33),
meta=SimpleNamespace(laneChangeState=0, laneChangeDirection=0))
controller.curvature_last = sign * 0.004
controller.desired_curvature_last = sign * 0.003
monkeypatch.setattr(controller, "_predicted_curvature", lambda *_: sign * 0.001)
commands = []
for _ in range(4):
result = controller.update(SimpleNamespace(latActive=True), car_state(speed=12.0, curvature=sign * 0.005),
SimpleNamespace(curvature=sign * 0.002))
assert result.active
assert result.path_angle == 0.0
commands.append(sign * result.curvature)
assert commands[0] == pytest.approx(0.004 - 0.0018)
assert commands[-1] == pytest.approx(0.0016)
assert commands[-1] < 0.005 - 0.002
@pytest.mark.parametrize("sign", (-1, 1))
@pytest.mark.parametrize("speed,expected", ((8.0, 0.002), (8.5, 0.004), (9.0, 0.006), (9.5, 0.006), (10.0, 0.006),
(12.0, 0.006), (15.0, 0.004), (16.0, 0.002)))