IQ.Pilot Release Commit @ 1399ceb
This commit is contained in:
@@ -22,6 +22,7 @@ from iqpilot.selfdrive.controls.lib.latcontrol_pid import LatControlPID
|
||||
from iqpilot.selfdrive.controls.lib.latcontrol_angle import LatControlAngle, STEER_ANGLE_SATURATION_THRESHOLD
|
||||
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
|
||||
from iqpilot.selfdrive.controls.lib.longcontrol import LongControl
|
||||
from iqpilot.system.proprietary_runtime._verified_import import import_verified_module
|
||||
from iqpilot.selfdrive.locationd.helpers import PoseCalibrator, Pose
|
||||
@@ -111,12 +112,20 @@ class Controls(IQControlsLayer):
|
||||
elif self.CP.lateralTuning.which() == 'pid':
|
||||
self.LaC = LatControlPID(self.CP, self.CP_IQ, self.CI, DT_CTRL)
|
||||
elif self.CP.lateralTuning.which() == 'torque':
|
||||
self.LaC = LatControlTorque(self.CP, self.CP_IQ, self.CI, DT_CTRL)
|
||||
if self._use_pq_torque():
|
||||
try:
|
||||
self.LaC = LatControlTorquePQ(self.CP, self.CP_IQ, self.CI, DT_CTRL)
|
||||
except Exception:
|
||||
cloudlog.exception("LatControlTorquePQ init failed; using generic torque")
|
||||
self.LaC = LatControlTorque(self.CP, self.CP_IQ, self.CI, DT_CTRL)
|
||||
elif self._use_mqb_torque_v0():
|
||||
try:
|
||||
self.LaC = LatControlTorqueV0(self.CP, self.CP_IQ, self.CI, DT_CTRL)
|
||||
except Exception:
|
||||
cloudlog.exception("LatControlTorqueV0 init failed; using generic torque")
|
||||
self.LaC = LatControlTorque(self.CP, self.CP_IQ, self.CI, DT_CTRL)
|
||||
else:
|
||||
self.LaC = LatControlTorque(self.CP, self.CP_IQ, self.CI, DT_CTRL)
|
||||
|
||||
def _use_pq_torque(self) -> bool:
|
||||
try:
|
||||
@@ -128,6 +137,13 @@ class Controls(IQControlsLayer):
|
||||
cloudlog.exception("pq torque selection failed; using generic torque")
|
||||
return False
|
||||
|
||||
def _use_mqb_torque_v0(self) -> bool:
|
||||
try:
|
||||
return is_vw_mqb_torque(self.CP)
|
||||
except Exception:
|
||||
cloudlog.exception("mqb torque v0 selection failed; using generic torque")
|
||||
return False
|
||||
|
||||
def _update_params(self) -> None:
|
||||
self.enable_curvature_controller = self.params.get_bool("EnableCurvatureController")
|
||||
self.enable_smooth_steer = self.params.get_bool("EnableSmoothSteer")
|
||||
@@ -320,7 +336,7 @@ class Controls(IQControlsLayer):
|
||||
hudControl.leftLaneDepart = self.sm['driverAssistance'].leftLaneDeparture
|
||||
hudControl.rightLaneDepart = self.sm['driverAssistance'].rightLaneDeparture
|
||||
|
||||
if self.sm['selfdriveState'].active:
|
||||
if CC.latActive:
|
||||
CO = self.sm['carOutput']
|
||||
if self.CP.steerControlType in (car.CarParams.SteerControlType.angle, car.CarParams.SteerControlType.curvatureDEPRECATED):
|
||||
self.steer_limited_by_safety = abs(CC.actuators.steeringAngleDeg - CO.actuatorsOutput.steeringAngleDeg) > \
|
||||
|
||||
118
iqpilot/selfdrive/controls/lib/latcontrol_torque_v0.py
Normal file
118
iqpilot/selfdrive/controls/lib/latcontrol_torque_v0.py
Normal file
@@ -0,0 +1,118 @@
|
||||
# Copyright (c) 2018, Comma.ai, Inc.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
|
||||
# to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
# and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
import math
|
||||
from collections import deque
|
||||
|
||||
import numpy as np
|
||||
|
||||
from iqpilot.cereal import log
|
||||
from iqdbc.car.lateral import FRICTION_THRESHOLD, get_friction
|
||||
from iqpilot.common.constants import ACCELERATION_DUE_TO_GRAVITY
|
||||
from iqpilot.common.filter_simple import FirstOrderFilter
|
||||
from iqpilot.common.pid import PIDController
|
||||
from iqpilot.selfdrive.controls.lib.latcontrol import LatControl
|
||||
|
||||
|
||||
KP = 1.0
|
||||
KI = 0.3
|
||||
KD = 0.0
|
||||
INTERP_SPEEDS = [1, 1.5, 2.0, 3.0, 5, 7.5, 10, 15, 30]
|
||||
KP_INTERP = [250, 120, 65, 30, 11.5, 5.5, 3.5, 2.0, KP]
|
||||
|
||||
LP_FILTER_CUTOFF_HZ = 1.2
|
||||
LAT_ACCEL_REQUEST_BUFFER_SECONDS = 1.0
|
||||
VERSION = 0
|
||||
|
||||
|
||||
def is_vw_mqb_torque(CP):
|
||||
if CP.brand != 'volkswagen':
|
||||
return False
|
||||
from iqdbc.car.volkswagen.values import VolkswagenFlags
|
||||
excluded_platforms = VolkswagenFlags.PQ | VolkswagenFlags.MLB | VolkswagenFlags.MEB | VolkswagenFlags.MQB_EVO
|
||||
return not bool(CP.flags & excluded_platforms)
|
||||
|
||||
|
||||
class LatControlTorqueV0(LatControl):
|
||||
def __init__(self, CP, CP_IQ, CI, dt):
|
||||
super().__init__(CP, CP_IQ, CI, dt)
|
||||
self.torque_params = CP.lateralTuning.torque.as_builder()
|
||||
self.torque_from_lateral_accel = CI.torque_from_lateral_accel()
|
||||
self.lateral_accel_from_torque = CI.lateral_accel_from_torque()
|
||||
self.pid = PIDController([INTERP_SPEEDS, KP_INTERP], KI, KD, rate=1 / self.dt)
|
||||
self.update_limits()
|
||||
self.steering_angle_deadzone_deg = self.torque_params.steeringAngleDeadzoneDeg
|
||||
self.lat_accel_request_buffer_len = int(LAT_ACCEL_REQUEST_BUFFER_SECONDS / self.dt)
|
||||
self.lat_accel_request_buffer = deque([0.] * self.lat_accel_request_buffer_len, maxlen=self.lat_accel_request_buffer_len)
|
||||
self.previous_measurement = 0.0
|
||||
self.measurement_rate_filter = FirstOrderFilter(0.0, 1 / (2 * np.pi * LP_FILTER_CUTOFF_HZ), self.dt)
|
||||
|
||||
def update_live_torque_params(self, latAccelFactor, latAccelOffset, friction):
|
||||
self.torque_params.latAccelFactor = latAccelFactor
|
||||
self.torque_params.latAccelOffset = latAccelOffset
|
||||
self.torque_params.friction = friction
|
||||
self.update_limits()
|
||||
|
||||
def update_limits(self):
|
||||
self.pid.set_limits(self.lateral_accel_from_torque(self.steer_max, self.torque_params),
|
||||
self.lateral_accel_from_torque(-self.steer_max, self.torque_params))
|
||||
|
||||
def update(self, active, CS, VM, params, steer_limited_by_safety, desired_curvature, calibrated_pose, curvature_limited, lat_delay,
|
||||
lookahead_curvature=None):
|
||||
pid_log = log.ControlsState.LateralTorqueState.new_message()
|
||||
pid_log.version = VERSION
|
||||
if not active:
|
||||
output_torque = 0.0
|
||||
pid_log.active = False
|
||||
else:
|
||||
measured_curvature = -VM.calc_curvature(math.radians(CS.steeringAngleDeg - params.angleOffsetDeg), CS.vEgo, params.roll)
|
||||
roll_compensation = params.roll * ACCELERATION_DUE_TO_GRAVITY
|
||||
curvature_deadzone = abs(VM.calc_curvature(math.radians(self.steering_angle_deadzone_deg), CS.vEgo, 0.0))
|
||||
lateral_accel_deadzone = curvature_deadzone * CS.vEgo ** 2
|
||||
|
||||
delay_frames = int(np.clip(lat_delay / self.dt, 1, self.lat_accel_request_buffer_len))
|
||||
expected_lateral_accel = self.lat_accel_request_buffer[-delay_frames]
|
||||
future_desired_lateral_accel = desired_curvature * CS.vEgo ** 2
|
||||
self.lat_accel_request_buffer.append(future_desired_lateral_accel)
|
||||
gravity_adjusted_future_lateral_accel = future_desired_lateral_accel - roll_compensation
|
||||
desired_lateral_jerk = (future_desired_lateral_accel - expected_lateral_accel) / lat_delay
|
||||
|
||||
measurement = measured_curvature * CS.vEgo ** 2
|
||||
measurement_rate = self.measurement_rate_filter.update((measurement - self.previous_measurement) / self.dt)
|
||||
self.previous_measurement = measurement
|
||||
|
||||
setpoint = lat_delay * desired_lateral_jerk + expected_lateral_accel
|
||||
error = setpoint - measurement
|
||||
|
||||
pid_log.error = float(error)
|
||||
feedforward = gravity_adjusted_future_lateral_accel
|
||||
feedforward -= self.torque_params.latAccelOffset
|
||||
feedforward += get_friction(error, lateral_accel_deadzone, FRICTION_THRESHOLD, self.torque_params)
|
||||
|
||||
freeze_integrator = steer_limited_by_safety or CS.steeringPressed or CS.vEgo < 5
|
||||
output_lataccel = self.pid.update(pid_log.error, -measurement_rate, feedforward=feedforward, speed=CS.vEgo,
|
||||
freeze_integrator=freeze_integrator)
|
||||
output_torque = self.torque_from_lateral_accel(output_lataccel, self.torque_params)
|
||||
|
||||
pid_log.active = True
|
||||
pid_log.p = float(self.pid.p)
|
||||
pid_log.i = float(self.pid.i)
|
||||
pid_log.d = float(self.pid.d)
|
||||
pid_log.f = float(self.pid.f)
|
||||
pid_log.output = float(-output_torque)
|
||||
pid_log.actualLateralAccel = float(measurement)
|
||||
pid_log.desiredLateralAccel = float(setpoint)
|
||||
pid_log.desiredLateralJerk = float(desired_lateral_jerk)
|
||||
pid_log.saturated = bool(self._check_saturation(self.steer_max - abs(output_torque) < 1e-3, CS, steer_limited_by_safety, curvature_limited))
|
||||
|
||||
return -output_torque, 0.0, pid_log
|
||||
@@ -6,12 +6,14 @@ 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
|
||||
@@ -31,7 +33,17 @@ class TestLatControl:
|
||||
iqpilot_interfaces.apply_iq_car_config(CI)
|
||||
return CP, LatControlTorquePQ(CP.as_reader(), convert_to_capnp(CP_IQ).as_reader(), CI, DT_CTRL)
|
||||
|
||||
@parameterized.expand([(HONDA.HONDA_CIVIC, LatControlPID), (TOYOTA.TOYOTA_RAV4, LatControlTorque),
|
||||
@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]
|
||||
@@ -78,6 +90,28 @@ class TestLatControl:
|
||||
_, _, 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
|
||||
|
||||
@@ -21,7 +21,7 @@ MIN_VEGO = 50.0 * CV.MPH_TO_MS
|
||||
MIN_ABS_YAW_RATE = 0.0
|
||||
MAX_YAW_RATE_SANITY_CHECK = 1.0
|
||||
MIN_NCC = 0.95
|
||||
MAX_LAG = 1.0
|
||||
MAX_LAG = 0.65
|
||||
MIN_LAG = 0.15
|
||||
MAX_LAG_STD = 0.1
|
||||
MAX_LAT_ACCEL = 2.0
|
||||
|
||||
@@ -88,7 +88,7 @@ class TestLagd:
|
||||
assert np.argmax(corr) in range(lag_frames - MAX_ERR_FRAMES, lag_frames + MAX_ERR_FRAMES + 1)
|
||||
|
||||
def test_empty_estimator(self):
|
||||
mocked_CP = car.CarParams(steerActuatorDelay=0.8)
|
||||
mocked_CP = car.CarParams(steerActuatorDelay=0.5)
|
||||
estimator = LateralLagEstimator(mocked_CP, DT)
|
||||
msg = estimator.get_msg(True)
|
||||
assert msg.lateralDelay.status == 'unestimated'
|
||||
@@ -100,7 +100,7 @@ class TestLagd:
|
||||
def test_estimator_basics(self, subtests):
|
||||
for lag_frames in range(LAGD_MIN_LAG_FRAMES, LAGD_MAX_LAG_FRAMES - 1):
|
||||
with subtests.test(msg=f"lag_frames={lag_frames}"):
|
||||
mocked_CP = car.CarParams(steerActuatorDelay=0.8)
|
||||
mocked_CP = car.CarParams(steerActuatorDelay=0.5)
|
||||
estimator = LateralLagEstimator(mocked_CP, DT, min_recovery_buffer_sec=0.0, min_yr=0.0)
|
||||
process_messages(estimator, lag_frames, int(MIN_OKAY_WINDOW_SEC / DT) + BLOCK_NUM_NEEDED * BLOCK_SIZE)
|
||||
msg = estimator.get_msg(True)
|
||||
@@ -112,7 +112,7 @@ class TestLagd:
|
||||
assert msg.lateralDelay.calPerc == 100
|
||||
|
||||
def test_estimator_masking(self):
|
||||
mocked_CP, lag_frames = car.CarParams(steerActuatorDelay=0.8), random.randint(LAGD_MIN_LAG_FRAMES, LAGD_MAX_LAG_FRAMES - 1)
|
||||
mocked_CP, lag_frames = car.CarParams(steerActuatorDelay=0.5), random.randint(LAGD_MIN_LAG_FRAMES, LAGD_MAX_LAG_FRAMES - 1)
|
||||
estimator = LateralLagEstimator(mocked_CP, DT, min_recovery_buffer_sec=0.0, min_yr=0.0, min_valid_block_count=1)
|
||||
process_messages(estimator, lag_frames, (int(MIN_OKAY_WINDOW_SEC / DT) + BLOCK_SIZE) * 2, rejection_threshold=0.4)
|
||||
msg = estimator.get_msg(True)
|
||||
@@ -122,7 +122,7 @@ class TestLagd:
|
||||
|
||||
@pytest.mark.timeout(60)
|
||||
def test_estimator_performance(self):
|
||||
mocked_CP = car.CarParams(steerActuatorDelay=0.8)
|
||||
mocked_CP = car.CarParams(steerActuatorDelay=0.5)
|
||||
estimator = LateralLagEstimator(mocked_CP, DT)
|
||||
|
||||
ds = []
|
||||
|
||||
Reference in New Issue
Block a user