You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							102 lines
						
					
					
						
							3.8 KiB
						
					
					
				
			
		
		
	
	
							102 lines
						
					
					
						
							3.8 KiB
						
					
					
				#!/usr/bin/env python3
 | 
						|
from collections import defaultdict
 | 
						|
import importlib
 | 
						|
from parameterized import parameterized_class
 | 
						|
import sys
 | 
						|
from typing import DefaultDict, Dict
 | 
						|
import unittest
 | 
						|
 | 
						|
from openpilot.common.realtime import DT_CTRL
 | 
						|
from openpilot.selfdrive.car.car_helpers import interfaces
 | 
						|
from openpilot.selfdrive.car.fingerprints import all_known_cars
 | 
						|
from openpilot.selfdrive.car.interfaces import get_torque_params
 | 
						|
from openpilot.selfdrive.car.subaru.values import CAR as SUBARU
 | 
						|
 | 
						|
CAR_MODELS = all_known_cars()
 | 
						|
 | 
						|
# ISO 11270 - allowed up jerk is strictly lower than recommended limits
 | 
						|
MAX_LAT_ACCEL = 3.0              # m/s^2
 | 
						|
MAX_LAT_JERK_UP = 2.5            # m/s^3
 | 
						|
MAX_LAT_JERK_DOWN = 5.0          # m/s^3
 | 
						|
MAX_LAT_JERK_UP_TOLERANCE = 0.5  # m/s^3
 | 
						|
 | 
						|
# jerk is measured over half a second
 | 
						|
JERK_MEAS_T = 0.5
 | 
						|
 | 
						|
# TODO: put these cars within limits
 | 
						|
ABOVE_LIMITS_CARS = [
 | 
						|
  SUBARU.LEGACY,
 | 
						|
  SUBARU.OUTBACK,
 | 
						|
]
 | 
						|
 | 
						|
car_model_jerks: DefaultDict[str, Dict[str, float]] = defaultdict(dict)
 | 
						|
 | 
						|
 | 
						|
@parameterized_class('car_model', [(c,) for c in sorted(CAR_MODELS)])
 | 
						|
class TestLateralLimits(unittest.TestCase):
 | 
						|
  car_model: str
 | 
						|
 | 
						|
  @classmethod
 | 
						|
  def setUpClass(cls):
 | 
						|
    CarInterface, _, _ = interfaces[cls.car_model]
 | 
						|
    CP = CarInterface.get_non_essential_params(cls.car_model)
 | 
						|
 | 
						|
    if CP.dashcamOnly:
 | 
						|
      raise unittest.SkipTest("Platform is behind dashcamOnly")
 | 
						|
 | 
						|
    # TODO: test all platforms
 | 
						|
    if CP.lateralTuning.which() != 'torque':
 | 
						|
      raise unittest.SkipTest
 | 
						|
 | 
						|
    if CP.notCar:
 | 
						|
      raise unittest.SkipTest
 | 
						|
 | 
						|
    if CP.carFingerprint in ABOVE_LIMITS_CARS:
 | 
						|
      raise unittest.SkipTest
 | 
						|
 | 
						|
    CarControllerParams = importlib.import_module(f'selfdrive.car.{CP.carName}.values').CarControllerParams
 | 
						|
    cls.control_params = CarControllerParams(CP)
 | 
						|
    cls.torque_params = get_torque_params(cls.car_model)
 | 
						|
 | 
						|
  @staticmethod
 | 
						|
  def calculate_0_5s_jerk(control_params, torque_params):
 | 
						|
    steer_step = control_params.STEER_STEP
 | 
						|
    max_lat_accel = torque_params['MAX_LAT_ACCEL_MEASURED']
 | 
						|
 | 
						|
    # Steer up/down delta per 10ms frame, in percentage of max torque
 | 
						|
    steer_up_per_frame = control_params.STEER_DELTA_UP / control_params.STEER_MAX / steer_step
 | 
						|
    steer_down_per_frame = control_params.STEER_DELTA_DOWN / control_params.STEER_MAX / steer_step
 | 
						|
 | 
						|
    # Lateral acceleration reached in 0.5 seconds, clipping to max torque
 | 
						|
    accel_up_0_5_sec = min(steer_up_per_frame * JERK_MEAS_T / DT_CTRL, 1.0) * max_lat_accel
 | 
						|
    accel_down_0_5_sec = min(steer_down_per_frame * JERK_MEAS_T / DT_CTRL, 1.0) * max_lat_accel
 | 
						|
 | 
						|
    # Convert to m/s^3
 | 
						|
    return accel_up_0_5_sec / JERK_MEAS_T, accel_down_0_5_sec / JERK_MEAS_T
 | 
						|
 | 
						|
  def test_jerk_limits(self):
 | 
						|
    up_jerk, down_jerk = self.calculate_0_5s_jerk(self.control_params, self.torque_params)
 | 
						|
    car_model_jerks[self.car_model] = {"up_jerk": up_jerk, "down_jerk": down_jerk}
 | 
						|
    self.assertLessEqual(up_jerk, MAX_LAT_JERK_UP + MAX_LAT_JERK_UP_TOLERANCE)
 | 
						|
    self.assertLessEqual(down_jerk, MAX_LAT_JERK_DOWN)
 | 
						|
 | 
						|
  def test_max_lateral_accel(self):
 | 
						|
    self.assertLessEqual(self.torque_params["MAX_LAT_ACCEL_MEASURED"], MAX_LAT_ACCEL)
 | 
						|
 | 
						|
 | 
						|
if __name__ == "__main__":
 | 
						|
  result = unittest.main(exit=False)
 | 
						|
 | 
						|
  print(f"\n\n---- Lateral limit report ({len(CAR_MODELS)} cars) ----\n")
 | 
						|
 | 
						|
  max_car_model_len = max([len(car_model) for car_model in car_model_jerks])
 | 
						|
  for car_model, _jerks in sorted(car_model_jerks.items(), key=lambda i: i[1]['up_jerk'], reverse=True):
 | 
						|
    violation = _jerks["up_jerk"] > MAX_LAT_JERK_UP + MAX_LAT_JERK_UP_TOLERANCE or \
 | 
						|
                _jerks["down_jerk"] > MAX_LAT_JERK_DOWN
 | 
						|
    violation_str = " - VIOLATION" if violation else ""
 | 
						|
 | 
						|
    print(f"{car_model:{max_car_model_len}} - up jerk: {round(_jerks['up_jerk'], 2):5} \
 | 
						|
              m/s^3, down jerk: {round(_jerks['down_jerk'], 2):5} m/s^3{violation_str}")
 | 
						|
 | 
						|
  # exit with test result
 | 
						|
  sys.exit(not result.result.wasSuccessful())
 | 
						|
 |