Files
IQ.Pilot/iqpilot/selfdrive/controls/tests/test_latcontrol.py
2026-08-27 20:17:33 -05:00

141 lines
6.2 KiB
Python

from parameterized import parameterized
from iqpilot.cereal import car, log
from iqdbc.car.car_helpers import interfaces
from iqdbc.car.honda.values import CAR as HONDA
from iqdbc.car.toyota.values import CAR as TOYOTA
from iqdbc.car.nissan.values import CAR as NISSAN
from iqdbc.car.gm.values import CAR as GM
from iqdbc.car.volkswagen.values import CAR as VOLKSWAGEN
from iqdbc.car.vehicle_model import VehicleModel
from iqpilot.common.realtime import DT_CTRL
from iqpilot.selfdrive.car.helpers import convert_to_capnp
from iqpilot.selfdrive.controls.lib.latcontrol_pid import LatControlPID
from iqpilot.selfdrive.controls.lib.latcontrol_torque import LatControlTorque
from iqpilot.selfdrive.controls.lib.latcontrol_torque_pq import LatControlTorquePQ
from iqpilot.selfdrive.controls.lib.latcontrol_torque_v0 import LatControlTorqueV0, is_vw_mqb_torque
import iqpilot.selfdrive.controls.lib.latcontrol_torque_pq as latcontrol_torque_pq
from iqpilot.selfdrive.controls.lib.latcontrol_angle import LatControlAngle
from iqpilot.selfdrive.locationd.helpers import Pose
from iqpilot.common.mock.generators import generate_deviceMotion
from iqpilot.selfdrive.car import interfaces as iqpilot_interfaces
class TestLatControl:
@staticmethod
def build_pq_controller():
car_name = TOYOTA.TOYOTA_RAV4
CarInterface = interfaces[car_name]
CP = CarInterface.get_non_essential_params(car_name)
CP_IQ = CarInterface.get_non_essential_params_iq(CP, car_name)
CI = CarInterface(CP, CP_IQ)
iqpilot_interfaces.apply_iq_car_config(CI)
return CP, LatControlTorquePQ(CP.as_reader(), convert_to_capnp(CP_IQ).as_reader(), CI, DT_CTRL)
@staticmethod
def build_v0_controller():
car_name = TOYOTA.TOYOTA_RAV4
CarInterface = interfaces[car_name]
CP = CarInterface.get_non_essential_params(car_name)
CP_IQ = CarInterface.get_non_essential_params_iq(CP, car_name)
CI = CarInterface(CP, CP_IQ)
iqpilot_interfaces.apply_iq_car_config(CI)
return CP, LatControlTorqueV0(CP.as_reader(), convert_to_capnp(CP_IQ).as_reader(), CI, DT_CTRL)
@parameterized.expand([(HONDA.HONDA_CIVIC, LatControlPID), (TOYOTA.TOYOTA_RAV4, LatControlTorque), (TOYOTA.TOYOTA_RAV4, LatControlTorqueV0),
(NISSAN.NISSAN_LEAF, LatControlAngle), (GM.CHEVROLET_BOLT_EUV, LatControlTorque)])
def test_saturation(self, car_name, controller):
CarInterface = interfaces[car_name]
CP = CarInterface.get_non_essential_params(car_name)
CP_IQ = CarInterface.get_non_essential_params_iq(CP, car_name)
CI = CarInterface(CP, CP_IQ)
iqpilot_interfaces.apply_iq_car_config(CI)
CP_IQ = convert_to_capnp(CP_IQ)
VM = VehicleModel(CP)
controller = controller(CP.as_reader(), CP_IQ.as_reader(), CI, DT_CTRL)
CS = car.CarState.new_message()
CS.vEgo = 30
CS.steeringPressed = False
params = log.VehicleParameters.new_message()
lp = generate_deviceMotion()
pose = Pose.from_live_pose(lp.deviceMotion)
# Saturate for curvature limited and controller limited
for _ in range(1000):
_, _, lac_log = controller.update(True, CS, VM, params, False, 0, pose, True, 0.2)
assert lac_log.saturated
for _ in range(1000):
_, _, lac_log = controller.update(True, CS, VM, params, False, 0, pose, False, 0.2)
assert not lac_log.saturated
for _ in range(1000):
_, _, lac_log = controller.update(True, CS, VM, params, False, 1, pose, False, 0.2)
assert lac_log.saturated
def test_pq_controller_update(self):
CP, controller = self.build_pq_controller()
VM = VehicleModel(CP)
CS = car.CarState.new_message()
CS.vEgo = 30
params = log.VehicleParameters.new_message()
pose = Pose.from_live_pose(generate_deviceMotion().deviceMotion)
_, _, lac_log = controller.update(True, CS, VM, params, False, 0.001, pose, False, 0.2)
assert lac_log.active
def test_v0_uses_current_lateral_acceleration_setpoint(self):
CP, controller = self.build_v0_controller()
VM = VehicleModel(CP)
CS = car.CarState.new_message(vEgo=30)
params = log.VehicleParameters.new_message()
pose = Pose.from_live_pose(generate_deviceMotion().deviceMotion)
_, _, lac_log = controller.update(True, CS, VM, params, False, 0.001, pose, False, 0.3)
assert lac_log.version == 0
assert abs(lac_log.desiredLateralAccel - 0.9) < 1e-6
def test_v0_platform_selection(self):
mqb_interface = interfaces[VOLKSWAGEN.VOLKSWAGEN_PASSAT_MK8]
mqb_params = mqb_interface.get_non_essential_params(VOLKSWAGEN.VOLKSWAGEN_PASSAT_MK8)
assert is_vw_mqb_torque(mqb_params)
for car_name in (VOLKSWAGEN.VOLKSWAGEN_PASSAT_MK7, VOLKSWAGEN.VOLKSWAGEN_GOLF_MK8,
VOLKSWAGEN.VOLKSWAGEN_ID4_MK1, VOLKSWAGEN.AUDI_A4_MK4, TOYOTA.TOYOTA_RAV4):
CarInterface = interfaces[car_name]
assert not is_vw_mqb_torque(CarInterface.get_non_essential_params(car_name))
def test_pq_controller_inactive_lookahead_and_slew_reset(self):
CP, controller = self.build_pq_controller()
controller.curvature_lookahead_enabled = True
controller.lateral_acceleration_slew_limiter.enabled = True
VM = VehicleModel(CP)
CS = car.CarState.new_message(vEgo=30)
params = log.VehicleParameters.new_message()
pose = Pose.from_live_pose(generate_deviceMotion().deviceMotion)
torque, angle, lac_log = controller.update(False, CS, VM, params, False, 0.001, pose, False, 0.2, lookahead_curvature=0.002)
assert torque == 0.0
assert angle == 0.0
assert not lac_log.active
assert controller.lateral_acceleration_slew_limiter.a_lim == 1.8
def test_pq_live_torque_update_freeze_and_unfreeze(self, monkeypatch):
_, controller = self.build_pq_controller()
initial = (controller.torque_params.latAccelFactor, controller.torque_params.latAccelOffset, controller.torque_params.friction)
controller.update_live_torque_params(3.0, 0.2, 0.4)
assert (controller.torque_params.latAccelFactor, controller.torque_params.latAccelOffset, controller.torque_params.friction) == initial
monkeypatch.setattr(latcontrol_torque_pq, "FREEZE_LIVE_TORQUE_PARAMS", False)
controller.update_live_torque_params(3.0, 0.2, 0.4)
assert controller.torque_params.latAccelFactor == 3.0
assert abs(controller.torque_params.latAccelOffset - 0.2) < 1e-6
assert abs(controller.torque_params.friction - 0.4) < 1e-6