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.
		
		
		
		
			
				
					499 lines
				
				24 KiB
			
		
		
			
		
	
	
					499 lines
				
				24 KiB
			| 
								 
											3 weeks ago
										 
									 | 
							
								#!/usr/bin/env python3
							 | 
						||
| 
								 | 
							
								import numpy as np
							 | 
						||
| 
								 | 
							
								import random
							 | 
						||
| 
								 | 
							
								import unittest
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import opendbc.safety.tests.common as common
							 | 
						||
| 
								 | 
							
								from opendbc.car.ford.carcontroller import MAX_LATERAL_ACCEL
							 | 
						||
| 
								 | 
							
								from opendbc.car.ford.values import FordSafetyFlags
							 | 
						||
| 
								 | 
							
								from opendbc.car.structs import CarParams
							 | 
						||
| 
								 | 
							
								from opendbc.safety.tests.libsafety import libsafety_py
							 | 
						||
| 
								 | 
							
								from opendbc.safety.tests.common import CANPackerSafety
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								MSG_EngBrakeData = 0x165           # RX from PCM, for driver brake pedal and cruise state
							 | 
						||
| 
								 | 
							
								MSG_EngVehicleSpThrottle = 0x204   # RX from PCM, for driver throttle input
							 | 
						||
| 
								 | 
							
								MSG_BrakeSysFeatures = 0x415       # RX from ABS, for vehicle speed
							 | 
						||
| 
								 | 
							
								MSG_EngVehicleSpThrottle2 = 0x202  # RX from PCM, for second vehicle speed
							 | 
						||
| 
								 | 
							
								MSG_Yaw_Data_FD1 = 0x91            # RX from RCM, for yaw rate
							 | 
						||
| 
								 | 
							
								MSG_Steering_Data_FD1 = 0x083      # TX by OP, various driver switches and LKAS/CC buttons
							 | 
						||
| 
								 | 
							
								MSG_ACCDATA = 0x186                # TX by OP, ACC controls
							 | 
						||
| 
								 | 
							
								MSG_ACCDATA_3 = 0x18A              # TX by OP, ACC/TJA user interface
							 | 
						||
| 
								 | 
							
								MSG_Lane_Assist_Data1 = 0x3CA      # TX by OP, Lane Keep Assist
							 | 
						||
| 
								 | 
							
								MSG_LateralMotionControl = 0x3D3   # TX by OP, Lateral Control message
							 | 
						||
| 
								 | 
							
								MSG_LateralMotionControl2 = 0x3D6  # TX by OP, alternate Lateral Control message
							 | 
						||
| 
								 | 
							
								MSG_IPMA_Data = 0x3D8              # TX by OP, IPMA and LKAS user interface
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def checksum(msg):
							 | 
						||
| 
								 | 
							
								  addr, dat, bus = msg
							 | 
						||
| 
								 | 
							
								  ret = bytearray(dat)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if addr == MSG_Yaw_Data_FD1:
							 | 
						||
| 
								 | 
							
								    chksum = dat[0] + dat[1]  # VehRol_W_Actl
							 | 
						||
| 
								 | 
							
								    chksum += dat[2] + dat[3]  # VehYaw_W_Actl
							 | 
						||
| 
								 | 
							
								    chksum += dat[5]  # VehRollYaw_No_Cnt
							 | 
						||
| 
								 | 
							
								    chksum += dat[6] >> 6  # VehRolWActl_D_Qf
							 | 
						||
| 
								 | 
							
								    chksum += (dat[6] >> 4) & 0x3  # VehYawWActl_D_Qf
							 | 
						||
| 
								 | 
							
								    chksum = 0xff - (chksum & 0xff)
							 | 
						||
| 
								 | 
							
								    ret[4] = chksum
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  elif addr == MSG_BrakeSysFeatures:
							 | 
						||
| 
								 | 
							
								    chksum = dat[0] + dat[1]  # Veh_V_ActlBrk
							 | 
						||
| 
								 | 
							
								    chksum += (dat[2] >> 2) & 0xf  # VehVActlBrk_No_Cnt
							 | 
						||
| 
								 | 
							
								    chksum += dat[2] >> 6  # VehVActlBrk_D_Qf
							 | 
						||
| 
								 | 
							
								    chksum = 0xff - (chksum & 0xff)
							 | 
						||
| 
								 | 
							
								    ret[3] = chksum
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  elif addr == MSG_EngVehicleSpThrottle2:
							 | 
						||
| 
								 | 
							
								    chksum = (dat[2] >> 3) & 0xf  # VehVActlEng_No_Cnt
							 | 
						||
| 
								 | 
							
								    chksum += (dat[4] >> 5) & 0x3  # VehVActlEng_D_Qf
							 | 
						||
| 
								 | 
							
								    chksum += dat[6] + dat[7]  # Veh_V_ActlEng
							 | 
						||
| 
								 | 
							
								    chksum = 0xff - (chksum & 0xff)
							 | 
						||
| 
								 | 
							
								    ret[1] = chksum
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  return addr, ret, bus
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class Buttons:
							 | 
						||
| 
								 | 
							
								  CANCEL = 0
							 | 
						||
| 
								 | 
							
								  RESUME = 1
							 | 
						||
| 
								 | 
							
								  TJA_TOGGLE = 2
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# Ford safety has four different configurations tested here:
							 | 
						||
| 
								 | 
							
								#  * CAN with openpilot longitudinal
							 | 
						||
| 
								 | 
							
								#  * CAN FD with stock longitudinal
							 | 
						||
| 
								 | 
							
								#  * CAN FD with openpilot longitudinal
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class TestFordSafetyBase(common.CarSafetyTest):
							 | 
						||
| 
								 | 
							
								  STANDSTILL_THRESHOLD = 1
							 | 
						||
| 
								 | 
							
								  RELAY_MALFUNCTION_ADDRS = {0: (MSG_ACCDATA_3, MSG_Lane_Assist_Data1, MSG_LateralMotionControl,
							 | 
						||
| 
								 | 
							
								                                 MSG_LateralMotionControl2, MSG_IPMA_Data)}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  FWD_BLACKLISTED_ADDRS = {2: [MSG_ACCDATA_3, MSG_Lane_Assist_Data1, MSG_LateralMotionControl,
							 | 
						||
| 
								 | 
							
								                               MSG_LateralMotionControl2, MSG_IPMA_Data]}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  STEER_MESSAGE = 0
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  # Curvature control limits
							 | 
						||
| 
								 | 
							
								  DEG_TO_CAN = 50000  # 1 / (2e-5) rad to can
							 | 
						||
| 
								 | 
							
								  MAX_CURVATURE = 0.02
							 | 
						||
| 
								 | 
							
								  MAX_CURVATURE_ERROR = 0.002
							 | 
						||
| 
								 | 
							
								  CURVATURE_ERROR_MIN_SPEED = 10.0  # m/s
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  ANGLE_RATE_BP = [5., 25., 25.]
							 | 
						||
| 
								 | 
							
								  ANGLE_RATE_UP = [0.00045, 0.0001, 0.0001]  # windup limit
							 | 
						||
| 
								 | 
							
								  ANGLE_RATE_DOWN = [0.00045, 0.00015, 0.00015]  # unwind limit
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  cnt_speed = 0
							 | 
						||
| 
								 | 
							
								  cnt_speed_2 = 0
							 | 
						||
| 
								 | 
							
								  cnt_yaw_rate = 0
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  packer: CANPackerSafety
							 | 
						||
| 
								 | 
							
								  safety: libsafety_py.LibSafety
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  def get_canfd_curvature_limits(self, speed):
							 | 
						||
| 
								 | 
							
								    # Round it in accordance with the safety
							 | 
						||
| 
								 | 
							
								    curvature_accel_limit = MAX_LATERAL_ACCEL / (max(speed, 1) ** 2)
							 | 
						||
| 
								 | 
							
								    curvature_accel_limit_lower = int(curvature_accel_limit * self.DEG_TO_CAN - 1) / self.DEG_TO_CAN
							 | 
						||
| 
								 | 
							
								    curvature_accel_limit_upper = int(curvature_accel_limit * self.DEG_TO_CAN + 1) / self.DEG_TO_CAN
							 | 
						||
| 
								 | 
							
								    return curvature_accel_limit_lower, curvature_accel_limit_upper
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  def _set_prev_desired_angle(self, t):
							 | 
						||
| 
								 | 
							
								    t = round(t * self.DEG_TO_CAN)
							 | 
						||
| 
								 | 
							
								    self.safety.set_desired_angle_last(t)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  def _reset_curvature_measurement(self, curvature, speed):
							 | 
						||
| 
								 | 
							
								    for _ in range(6):
							 | 
						||
| 
								 | 
							
								      self._rx(self._speed_msg(speed))
							 | 
						||
| 
								 | 
							
								      self._rx(self._yaw_rate_msg(curvature, speed))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  # Driver brake pedal
							 | 
						||
| 
								 | 
							
								  def _user_brake_msg(self, brake: bool):
							 | 
						||
| 
								 | 
							
								    # brake pedal and cruise state share same message, so we have to send
							 | 
						||
| 
								 | 
							
								    # the other signal too
							 | 
						||
| 
								 | 
							
								    enable = self.safety.get_controls_allowed()
							 | 
						||
| 
								 | 
							
								    values = {
							 | 
						||
| 
								 | 
							
								      "BpedDrvAppl_D_Actl": 2 if brake else 1,
							 | 
						||
| 
								 | 
							
								      "CcStat_D_Actl": 5 if enable else 0,
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    return self.packer.make_can_msg_safety("EngBrakeData", 0, values)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  # ABS vehicle speed
							 | 
						||
| 
								 | 
							
								  def _speed_msg(self, speed: float, quality_flag=True):
							 | 
						||
| 
								 | 
							
								    values = {"Veh_V_ActlBrk": speed * 3.6, "VehVActlBrk_D_Qf": 3 if quality_flag else 0, "VehVActlBrk_No_Cnt": self.cnt_speed % 16}
							 | 
						||
| 
								 | 
							
								    self.__class__.cnt_speed += 1
							 | 
						||
| 
								 | 
							
								    return self.packer.make_can_msg_safety("BrakeSysFeatures", 0, values, fix_checksum=checksum)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  # PCM vehicle speed
							 | 
						||
| 
								 | 
							
								  def _speed_msg_2(self, speed: float, quality_flag=True):
							 | 
						||
| 
								 | 
							
								    # Ford relies on speed for driver curvature limiting, so it checks two sources
							 | 
						||
| 
								 | 
							
								    values = {"Veh_V_ActlEng": speed * 3.6, "VehVActlEng_D_Qf": 3 if quality_flag else 0, "VehVActlEng_No_Cnt": self.cnt_speed_2 % 16}
							 | 
						||
| 
								 | 
							
								    self.__class__.cnt_speed_2 += 1
							 | 
						||
| 
								 | 
							
								    return self.packer.make_can_msg_safety("EngVehicleSpThrottle2", 0, values, fix_checksum=checksum)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  # Standstill state
							 | 
						||
| 
								 | 
							
								  def _vehicle_moving_msg(self, speed: float):
							 | 
						||
| 
								 | 
							
								    values = {"VehStop_D_Stat": 1 if speed <= self.STANDSTILL_THRESHOLD else random.choice((0, 2, 3))}
							 | 
						||
| 
								 | 
							
								    return self.packer.make_can_msg_safety("DesiredTorqBrk", 0, values)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  # Current curvature
							 | 
						||
| 
								 | 
							
								  def _yaw_rate_msg(self, curvature: float, speed: float, quality_flag=True):
							 | 
						||
| 
								 | 
							
								    values = {"VehYaw_W_Actl": curvature * speed, "VehYawWActl_D_Qf": 3 if quality_flag else 0,
							 | 
						||
| 
								 | 
							
								              "VehRollYaw_No_Cnt": self.cnt_yaw_rate % 256}
							 | 
						||
| 
								 | 
							
								    self.__class__.cnt_yaw_rate += 1
							 | 
						||
| 
								 | 
							
								    return self.packer.make_can_msg_safety("Yaw_Data_FD1", 0, values, fix_checksum=checksum)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  # Drive throttle input
							 | 
						||
| 
								 | 
							
								  def _user_gas_msg(self, gas: float):
							 | 
						||
| 
								 | 
							
								    values = {"ApedPos_Pc_ActlArb": gas}
							 | 
						||
| 
								 | 
							
								    return self.packer.make_can_msg_safety("EngVehicleSpThrottle", 0, values)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  # Cruise status
							 | 
						||
| 
								 | 
							
								  def _pcm_status_msg(self, enable: bool):
							 | 
						||
| 
								 | 
							
								    # brake pedal and cruise state share same message, so we have to send
							 | 
						||
| 
								 | 
							
								    # the other signal too
							 | 
						||
| 
								 | 
							
								    brake = self.safety.get_brake_pressed_prev()
							 | 
						||
| 
								 | 
							
								    values = {
							 | 
						||
| 
								 | 
							
								      "BpedDrvAppl_D_Actl": 2 if brake else 1,
							 | 
						||
| 
								 | 
							
								      "CcStat_D_Actl": 5 if enable else 0,
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    return self.packer.make_can_msg_safety("EngBrakeData", 0, values)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  # LKAS command
							 | 
						||
| 
								 | 
							
								  def _lkas_command_msg(self, action: int):
							 | 
						||
| 
								 | 
							
								    values = {
							 | 
						||
| 
								 | 
							
								      "LkaActvStats_D2_Req": action,
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    return self.packer.make_can_msg_safety("Lane_Assist_Data1", 0, values)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  # LCA command
							 | 
						||
| 
								 | 
							
								  def _lat_ctl_msg(self, enabled: bool, path_offset: float, path_angle: float, curvature: float, curvature_rate: float):
							 | 
						||
| 
								 | 
							
								    if self.STEER_MESSAGE == MSG_LateralMotionControl:
							 | 
						||
| 
								 | 
							
								      values = {
							 | 
						||
| 
								 | 
							
								        "LatCtl_D_Rq": 1 if enabled else 0,
							 | 
						||
| 
								 | 
							
								        "LatCtlPathOffst_L_Actl": path_offset,     # Path offset [-5.12|5.11] meter
							 | 
						||
| 
								 | 
							
								        "LatCtlPath_An_Actl": path_angle,          # Path angle [-0.5|0.5235] radians
							 | 
						||
| 
								 | 
							
								        "LatCtlCurv_NoRate_Actl": curvature_rate,  # Curvature rate [-0.001024|0.00102375] 1/meter^2
							 | 
						||
| 
								 | 
							
								        "LatCtlCurv_No_Actl": curvature,           # Curvature [-0.02|0.02094] 1/meter
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      return self.packer.make_can_msg_safety("LateralMotionControl", 0, values)
							 | 
						||
| 
								 | 
							
								    elif self.STEER_MESSAGE == MSG_LateralMotionControl2:
							 | 
						||
| 
								 | 
							
								      values = {
							 | 
						||
| 
								 | 
							
								        "LatCtl_D2_Rq": 1 if enabled else 0,
							 | 
						||
| 
								 | 
							
								        "LatCtlPathOffst_L_Actl": path_offset,     # Path offset [-5.12|5.11] meter
							 | 
						||
| 
								 | 
							
								        "LatCtlPath_An_Actl": path_angle,          # Path angle [-0.5|0.5235] radians
							 | 
						||
| 
								 | 
							
								        "LatCtlCrv_NoRate2_Actl": curvature_rate,  # Curvature rate [-0.001024|0.001023] 1/meter^2
							 | 
						||
| 
								 | 
							
								        "LatCtlCurv_No_Actl": curvature,           # Curvature [-0.02|0.02094] 1/meter
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      return self.packer.make_can_msg_safety("LateralMotionControl2", 0, values)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  # Cruise control buttons
							 | 
						||
| 
								 | 
							
								  def _acc_button_msg(self, button: int, bus: int):
							 | 
						||
| 
								 | 
							
								    values = {
							 | 
						||
| 
								 | 
							
								      "CcAslButtnCnclPress": 1 if button == Buttons.CANCEL else 0,
							 | 
						||
| 
								 | 
							
								      "CcAsllButtnResPress": 1 if button == Buttons.RESUME else 0,
							 | 
						||
| 
								 | 
							
								      "TjaButtnOnOffPress": 1 if button == Buttons.TJA_TOGGLE else 0,
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    return self.packer.make_can_msg_safety("Steering_Data_FD1", bus, values)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  def test_rx_hook(self):
							 | 
						||
| 
								 | 
							
								    # checksum, counter, and quality flag checks
							 | 
						||
| 
								 | 
							
								    for quality_flag in [True, False]:
							 | 
						||
| 
								 | 
							
								      for msg_type in ["speed", "speed_2", "yaw"]:
							 | 
						||
| 
								 | 
							
								        self.safety.set_controls_allowed(True)
							 | 
						||
| 
								 | 
							
								        # send multiple times to verify counter checks
							 | 
						||
| 
								 | 
							
								        for _ in range(10):
							 | 
						||
| 
								 | 
							
								          if msg_type == "speed":
							 | 
						||
| 
								 | 
							
								            msg = self._speed_msg(0, quality_flag=quality_flag)
							 | 
						||
| 
								 | 
							
								          elif msg_type == "speed_2":
							 | 
						||
| 
								 | 
							
								            msg = self._speed_msg_2(0, quality_flag=quality_flag)
							 | 
						||
| 
								 | 
							
								          elif msg_type == "yaw":
							 | 
						||
| 
								 | 
							
								            msg = self._yaw_rate_msg(0, 0, quality_flag=quality_flag)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								          self.assertEqual(quality_flag, self._rx(msg))
							 | 
						||
| 
								 | 
							
								          self.assertEqual(quality_flag, self.safety.get_controls_allowed())
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Mess with checksum to make it fail, checksum is not checked for 2nd speed
							 | 
						||
| 
								 | 
							
								        msg[0].data[3] = 0  # Speed checksum & half of yaw signal
							 | 
						||
| 
								 | 
							
								        should_rx = msg_type == "speed_2" and quality_flag
							 | 
						||
| 
								 | 
							
								        self.assertEqual(should_rx, self._rx(msg))
							 | 
						||
| 
								 | 
							
								        self.assertEqual(should_rx, self.safety.get_controls_allowed())
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  def test_angle_measurements(self):
							 | 
						||
| 
								 | 
							
								    """Tests rx hook correctly parses the curvature measurement from the vehicle speed and yaw rate"""
							 | 
						||
| 
								 | 
							
								    for speed in np.arange(0.5, 40, 0.5):
							 | 
						||
| 
								 | 
							
								      for curvature in np.arange(0, self.MAX_CURVATURE * 2, 2e-3):
							 | 
						||
| 
								 | 
							
								        self._rx(self._speed_msg(speed))
							 | 
						||
| 
								 | 
							
								        for c in (curvature, -curvature, 0, 0, 0, 0):
							 | 
						||
| 
								 | 
							
								          self._rx(self._yaw_rate_msg(c, speed))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        self.assertEqual(self.safety.get_angle_meas_min(), round(-curvature * self.DEG_TO_CAN))
							 | 
						||
| 
								 | 
							
								        self.assertEqual(self.safety.get_angle_meas_max(), round(curvature * self.DEG_TO_CAN))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        self._rx(self._yaw_rate_msg(0, speed))
							 | 
						||
| 
								 | 
							
								        self.assertEqual(self.safety.get_angle_meas_min(), round(-curvature * self.DEG_TO_CAN))
							 | 
						||
| 
								 | 
							
								        self.assertEqual(self.safety.get_angle_meas_max(), 0)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        self._rx(self._yaw_rate_msg(0, speed))
							 | 
						||
| 
								 | 
							
								        self.assertEqual(self.safety.get_angle_meas_min(), 0)
							 | 
						||
| 
								 | 
							
								        self.assertEqual(self.safety.get_angle_meas_max(), 0)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  def test_max_lateral_acceleration(self):
							 | 
						||
| 
								 | 
							
								    # Ford CAN FD can achieve a higher max lateral acceleration than CAN so we limit curvature based on speed
							 | 
						||
| 
								 | 
							
								    for speed in np.arange(0, 40, 0.5):
							 | 
						||
| 
								 | 
							
								      # Clip so we test curvature limiting at low speed due to low max curvature
							 | 
						||
| 
								 | 
							
								      _, curvature_accel_limit_upper = self.get_canfd_curvature_limits(speed)
							 | 
						||
| 
								 | 
							
								      curvature_accel_limit_upper = np.clip(curvature_accel_limit_upper, -self.MAX_CURVATURE, self.MAX_CURVATURE)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      for sign in (-1, 1):
							 | 
						||
| 
								 | 
							
								        # Test above and below the lateral by 20%, max is clipped since
							 | 
						||
| 
								 | 
							
								        # max curvature at low speed is higher than the signal max
							 | 
						||
| 
								 | 
							
								        for curvature in np.arange(curvature_accel_limit_upper * 0.8, min(curvature_accel_limit_upper * 1.2, self.MAX_CURVATURE), 1 / self.DEG_TO_CAN):
							 | 
						||
| 
								 | 
							
								          curvature = sign * round(curvature * self.DEG_TO_CAN) / self.DEG_TO_CAN  # fix np rounding errors
							 | 
						||
| 
								 | 
							
								          self.safety.set_controls_allowed(True)
							 | 
						||
| 
								 | 
							
								          self._set_prev_desired_angle(curvature)
							 | 
						||
| 
								 | 
							
								          self._reset_curvature_measurement(curvature, speed)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								          should_tx = abs(curvature) <= curvature_accel_limit_upper
							 | 
						||
| 
								 | 
							
								          self.assertEqual(should_tx, self._tx(self._lat_ctl_msg(True, 0, 0, curvature, 0)))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  def test_steer_allowed(self):
							 | 
						||
| 
								 | 
							
								    path_offsets = np.arange(-5.12, 5.11, 2.5).round()
							 | 
						||
| 
								 | 
							
								    path_angles = np.arange(-0.5, 0.5235, 0.25).round(1)
							 | 
						||
| 
								 | 
							
								    curvature_rates = np.arange(-0.001024, 0.00102375, 0.001).round(3)
							 | 
						||
| 
								 | 
							
								    curvatures = np.arange(-0.02, 0.02094, 0.01).round(2)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    for speed in (self.CURVATURE_ERROR_MIN_SPEED - 1,
							 | 
						||
| 
								 | 
							
								                  self.CURVATURE_ERROR_MIN_SPEED + 1):
							 | 
						||
| 
								 | 
							
								      _, curvature_accel_limit_upper = self.get_canfd_curvature_limits(speed)
							 | 
						||
| 
								 | 
							
								      for controls_allowed in (True, False):
							 | 
						||
| 
								 | 
							
								        for steer_control_enabled in (True, False):
							 | 
						||
| 
								 | 
							
								          for path_offset in path_offsets:
							 | 
						||
| 
								 | 
							
								            for path_angle in path_angles:
							 | 
						||
| 
								 | 
							
								              for curvature_rate in curvature_rates:
							 | 
						||
| 
								 | 
							
								                for curvature in curvatures:
							 | 
						||
| 
								 | 
							
								                  self.safety.set_controls_allowed(controls_allowed)
							 | 
						||
| 
								 | 
							
								                  self._set_prev_desired_angle(curvature)
							 | 
						||
| 
								 | 
							
								                  self._reset_curvature_measurement(curvature, speed)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                  should_tx = path_offset == 0 and path_angle == 0 and curvature_rate == 0
							 | 
						||
| 
								 | 
							
								                  # when request bit is 0, only allow curvature of 0 since the signal range
							 | 
						||
| 
								 | 
							
								                  # is not large enough to enforce it tracking measured
							 | 
						||
| 
								 | 
							
								                  should_tx = should_tx and (controls_allowed if steer_control_enabled else curvature == 0)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                  # Only CAN FD has the max lateral acceleration limit
							 | 
						||
| 
								 | 
							
								                  if self.STEER_MESSAGE == MSG_LateralMotionControl2:
							 | 
						||
| 
								 | 
							
								                    should_tx = should_tx and abs(curvature) <= curvature_accel_limit_upper
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                  with self.subTest(controls_allowed=controls_allowed, steer_control_enabled=steer_control_enabled,
							 | 
						||
| 
								 | 
							
								                                    path_offset=float(path_offset), path_angle=float(path_angle), curvature_rate=float(curvature_rate),
							 | 
						||
| 
								 | 
							
								                                    curvature=float(curvature)):
							 | 
						||
| 
								 | 
							
								                    self.assertEqual(should_tx, self._tx(self._lat_ctl_msg(steer_control_enabled, path_offset, path_angle, curvature, curvature_rate)))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  def test_curvature_rate_limits(self):
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    When the curvature error is exceeded, commanded curvature must start moving towards meas respecting rate limits.
							 | 
						||
| 
								 | 
							
								    Since safety allows higher rate limits to avoid false positives, we need to allow a lower rate to move towards meas.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    self.safety.set_controls_allowed(True)
							 | 
						||
| 
								 | 
							
								    # safety fudges the speed (1 m/s) and rate limits (1 CAN unit) to avoid false positives
							 | 
						||
| 
								 | 
							
								    small_curvature = 1 / self.DEG_TO_CAN  # significant small amount of curvature to cross boundary
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    for speed in np.arange(0, 40, 0.5):
							 | 
						||
| 
								 | 
							
								      curvature_accel_limit_lower, curvature_accel_limit_upper = self.get_canfd_curvature_limits(speed)
							 | 
						||
| 
								 | 
							
								      limit_command = speed > self.CURVATURE_ERROR_MIN_SPEED
							 | 
						||
| 
								 | 
							
								      # ensure our limits match the safety's rounded limits
							 | 
						||
| 
								 | 
							
								      max_delta_up = int(np.interp(speed - 1, self.ANGLE_RATE_BP, self.ANGLE_RATE_UP) * self.DEG_TO_CAN + 1) / self.DEG_TO_CAN
							 | 
						||
| 
								 | 
							
								      max_delta_up_lower = int(np.interp(speed + 1, self.ANGLE_RATE_BP, self.ANGLE_RATE_UP) * self.DEG_TO_CAN - 1) / self.DEG_TO_CAN
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      max_delta_down = int(np.interp(speed - 1, self.ANGLE_RATE_BP, self.ANGLE_RATE_DOWN) * self.DEG_TO_CAN + 1 + 1e-3) / self.DEG_TO_CAN
							 | 
						||
| 
								 | 
							
								      max_delta_down_lower = int(np.interp(speed + 1, self.ANGLE_RATE_BP, self.ANGLE_RATE_DOWN) * self.DEG_TO_CAN - 1 + 1e-3) / self.DEG_TO_CAN
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      up_cases = (self.MAX_CURVATURE_ERROR * 2, [
							 | 
						||
| 
								 | 
							
								        (not limit_command, 0, 0),
							 | 
						||
| 
								 | 
							
								        (not limit_command, 0, max_delta_up_lower - small_curvature),
							 | 
						||
| 
								 | 
							
								        (True, 1e-9, max_delta_down),  # TODO: safety should not allow down limits at 0
							 | 
						||
| 
								 | 
							
								        (not limit_command, 1e-9, max_delta_up_lower),  # TODO: safety should not allow down limits at 0
							 | 
						||
| 
								 | 
							
								        (True, 0, max_delta_up_lower),
							 | 
						||
| 
								 | 
							
								        (True, 0, max_delta_up),
							 | 
						||
| 
								 | 
							
								        (False, 0, max_delta_up + small_curvature),
							 | 
						||
| 
								 | 
							
								        # stay at boundary limit
							 | 
						||
| 
								 | 
							
								        (True, self.MAX_CURVATURE_ERROR - small_curvature, self.MAX_CURVATURE_ERROR - small_curvature),
							 | 
						||
| 
								 | 
							
								        # 1 unit below boundary limit
							 | 
						||
| 
								 | 
							
								        (not limit_command, self.MAX_CURVATURE_ERROR - small_curvature * 2, self.MAX_CURVATURE_ERROR - small_curvature * 2),
							 | 
						||
| 
								 | 
							
								        # shouldn't allow command to move outside the boundary limit if last was inside
							 | 
						||
| 
								 | 
							
								        (not limit_command, self.MAX_CURVATURE_ERROR - small_curvature, self.MAX_CURVATURE_ERROR - small_curvature * 2),
							 | 
						||
| 
								 | 
							
								      ])
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      down_cases = (self.MAX_CURVATURE - self.MAX_CURVATURE_ERROR * 2, [
							 | 
						||
| 
								 | 
							
								        (not limit_command, self.MAX_CURVATURE, self.MAX_CURVATURE),
							 | 
						||
| 
								 | 
							
								        (not limit_command, self.MAX_CURVATURE, self.MAX_CURVATURE - max_delta_down_lower + small_curvature),
							 | 
						||
| 
								 | 
							
								        (True, self.MAX_CURVATURE, self.MAX_CURVATURE - max_delta_down_lower),
							 | 
						||
| 
								 | 
							
								        (True, self.MAX_CURVATURE, self.MAX_CURVATURE - max_delta_down),
							 | 
						||
| 
								 | 
							
								        (False, self.MAX_CURVATURE, self.MAX_CURVATURE - max_delta_down - small_curvature),
							 | 
						||
| 
								 | 
							
								      ])
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      for sign in (-1, 1):
							 | 
						||
| 
								 | 
							
								        for angle_meas, cases in (up_cases, down_cases):
							 | 
						||
| 
								 | 
							
								          self._reset_curvature_measurement(sign * angle_meas, speed)
							 | 
						||
| 
								 | 
							
								          for should_tx, initial_curvature, desired_curvature in cases:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            # Only CAN FD has the max lateral acceleration limit
							 | 
						||
| 
								 | 
							
								            if self.STEER_MESSAGE == MSG_LateralMotionControl2:
							 | 
						||
| 
								 | 
							
								              if should_tx:
							 | 
						||
| 
								 | 
							
								                # can not send if the curvature is above the max lateral acceleration
							 | 
						||
| 
								 | 
							
								                should_tx = should_tx and abs(desired_curvature) <= curvature_accel_limit_upper
							 | 
						||
| 
								 | 
							
								              else:
							 | 
						||
| 
								 | 
							
								                # if desired curvature violates driver curvature error, it can only send if
							 | 
						||
| 
								 | 
							
								                # the curvature is being limited by max lateral acceleration
							 | 
						||
| 
								 | 
							
								                should_tx = should_tx or curvature_accel_limit_lower <= abs(desired_curvature) <= curvature_accel_limit_upper
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            # small curvature ensures we're using up limits. at 0, safety allows down limits to allow to account for rounding errors
							 | 
						||
| 
								 | 
							
								            curvature_offset = small_curvature if initial_curvature == 0 else 0
							 | 
						||
| 
								 | 
							
								            self._set_prev_desired_angle(sign * (curvature_offset + initial_curvature))
							 | 
						||
| 
								 | 
							
								            self.assertEqual(should_tx, self._tx(self._lat_ctl_msg(True, 0, 0, sign * (curvature_offset + desired_curvature), 0)))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  def test_prevent_lkas_action(self):
							 | 
						||
| 
								 | 
							
								    self.safety.set_controls_allowed(1)
							 | 
						||
| 
								 | 
							
								    self.assertFalse(self._tx(self._lkas_command_msg(1)))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    self.safety.set_controls_allowed(0)
							 | 
						||
| 
								 | 
							
								    self.assertFalse(self._tx(self._lkas_command_msg(1)))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  def test_acc_buttons(self):
							 | 
						||
| 
								 | 
							
								    for allowed in (0, 1):
							 | 
						||
| 
								 | 
							
								      self.safety.set_controls_allowed(allowed)
							 | 
						||
| 
								 | 
							
								      for enabled in (True, False):
							 | 
						||
| 
								 | 
							
								        self._rx(self._pcm_status_msg(enabled))
							 | 
						||
| 
								 | 
							
								        self.assertTrue(self._tx(self._acc_button_msg(Buttons.TJA_TOGGLE, 2)))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    for allowed in (0, 1):
							 | 
						||
| 
								 | 
							
								      self.safety.set_controls_allowed(allowed)
							 | 
						||
| 
								 | 
							
								      for bus in (0, 2):
							 | 
						||
| 
								 | 
							
								        self.assertEqual(allowed, self._tx(self._acc_button_msg(Buttons.RESUME, bus)))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    for enabled in (True, False):
							 | 
						||
| 
								 | 
							
								      self._rx(self._pcm_status_msg(enabled))
							 | 
						||
| 
								 | 
							
								      for bus in (0, 2):
							 | 
						||
| 
								 | 
							
								        self.assertEqual(enabled, self._tx(self._acc_button_msg(Buttons.CANCEL, bus)))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class TestFordCANFDStockSafety(TestFordSafetyBase):
							 | 
						||
| 
								 | 
							
								  STEER_MESSAGE = MSG_LateralMotionControl2
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  TX_MSGS = [
							 | 
						||
| 
								 | 
							
								    [MSG_Steering_Data_FD1, 0], [MSG_Steering_Data_FD1, 2], [MSG_ACCDATA_3, 0], [MSG_Lane_Assist_Data1, 0],
							 | 
						||
| 
								 | 
							
								    [MSG_LateralMotionControl2, 0], [MSG_IPMA_Data, 0],
							 | 
						||
| 
								 | 
							
								  ]
							 | 
						||
| 
								 | 
							
								  RELAY_MALFUNCTION_ADDRS = {0: (MSG_ACCDATA_3, MSG_Lane_Assist_Data1, MSG_LateralMotionControl2,
							 | 
						||
| 
								 | 
							
								                                 MSG_IPMA_Data)}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  FWD_BLACKLISTED_ADDRS = {2: [MSG_ACCDATA_3, MSG_Lane_Assist_Data1, MSG_LateralMotionControl2,
							 | 
						||
| 
								 | 
							
								                               MSG_IPMA_Data]}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  def setUp(self):
							 | 
						||
| 
								 | 
							
								    self.packer = CANPackerSafety("ford_lincoln_base_pt")
							 | 
						||
| 
								 | 
							
								    self.safety = libsafety_py.libsafety
							 | 
						||
| 
								 | 
							
								    self.safety.set_safety_hooks(CarParams.SafetyModel.ford, FordSafetyFlags.CANFD)
							 | 
						||
| 
								 | 
							
								    self.safety.init_tests()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class TestFordLongitudinalSafetyBase(TestFordSafetyBase):
							 | 
						||
| 
								 | 
							
								  MAX_ACCEL = 2.0  # accel is used for brakes, but openpilot can set positive values
							 | 
						||
| 
								 | 
							
								  MIN_ACCEL = -3.5
							 | 
						||
| 
								 | 
							
								  INACTIVE_ACCEL = 0.0
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  MAX_GAS = 2.0
							 | 
						||
| 
								 | 
							
								  MIN_GAS = -0.5
							 | 
						||
| 
								 | 
							
								  INACTIVE_GAS = -5.0
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  # ACC command
							 | 
						||
| 
								 | 
							
								  def _acc_command_msg(self, gas: float, brake: float, brake_actuation: bool, cmbb_deny: bool = False):
							 | 
						||
| 
								 | 
							
								    values = {
							 | 
						||
| 
								 | 
							
								      "AccPrpl_A_Rq": gas,                              # [-5|5.23] m/s^2
							 | 
						||
| 
								 | 
							
								      "AccPrpl_A_Pred": gas,                            # [-5|5.23] m/s^2
							 | 
						||
| 
								 | 
							
								      "AccBrkTot_A_Rq": brake,                          # [-20|11.9449] m/s^2
							 | 
						||
| 
								 | 
							
								      "AccBrkPrchg_B_Rq": 1 if brake_actuation else 0,  # Pre-charge brake request: 0=No, 1=Yes
							 | 
						||
| 
								 | 
							
								      "AccBrkDecel_B_Rq": 1 if brake_actuation else 0,  # Deceleration request: 0=Inactive, 1=Active
							 | 
						||
| 
								 | 
							
								      "CmbbDeny_B_Actl": 1 if cmbb_deny else 0,         # [0|1] deny AEB actuation
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    return self.packer.make_can_msg_safety("ACCDATA", 0, values)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  def test_stock_aeb(self):
							 | 
						||
| 
								 | 
							
								    # Test that CmbbDeny_B_Actl is never 1, it prevents the ABS module from actuating AEB requests from ACCDATA_2
							 | 
						||
| 
								 | 
							
								    for controls_allowed in (True, False):
							 | 
						||
| 
								 | 
							
								      self.safety.set_controls_allowed(controls_allowed)
							 | 
						||
| 
								 | 
							
								      for cmbb_deny in (True, False):
							 | 
						||
| 
								 | 
							
								        should_tx = not cmbb_deny
							 | 
						||
| 
								 | 
							
								        self.assertEqual(should_tx, self._tx(self._acc_command_msg(self.INACTIVE_GAS, self.INACTIVE_ACCEL, controls_allowed, cmbb_deny)))
							 | 
						||
| 
								 | 
							
								        should_tx = controls_allowed and not cmbb_deny
							 | 
						||
| 
								 | 
							
								        self.assertEqual(should_tx, self._tx(self._acc_command_msg(self.MAX_GAS, self.MAX_ACCEL, controls_allowed, cmbb_deny)))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  def test_gas_safety_check(self):
							 | 
						||
| 
								 | 
							
								    for controls_allowed in (True, False):
							 | 
						||
| 
								 | 
							
								      self.safety.set_controls_allowed(controls_allowed)
							 | 
						||
| 
								 | 
							
								      for gas in np.concatenate((np.arange(self.MIN_GAS - 2, self.MAX_GAS + 2, 0.05), [self.INACTIVE_GAS])):
							 | 
						||
| 
								 | 
							
								        gas = round(gas, 2)  # floats might not hit exact boundary conditions without rounding
							 | 
						||
| 
								 | 
							
								        should_tx = (controls_allowed and self.MIN_GAS <= gas <= self.MAX_GAS) or gas == self.INACTIVE_GAS
							 | 
						||
| 
								 | 
							
								        self.assertEqual(should_tx, self._tx(self._acc_command_msg(gas, self.INACTIVE_ACCEL, controls_allowed)))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  def test_brake_safety_check(self):
							 | 
						||
| 
								 | 
							
								    for controls_allowed in (True, False):
							 | 
						||
| 
								 | 
							
								      self.safety.set_controls_allowed(controls_allowed)
							 | 
						||
| 
								 | 
							
								      for brake_actuation in (True, False):
							 | 
						||
| 
								 | 
							
								        for brake in np.arange(self.MIN_ACCEL - 2, self.MAX_ACCEL + 2, 0.05):
							 | 
						||
| 
								 | 
							
								          brake = round(brake, 2)  # floats might not hit exact boundary conditions without rounding
							 | 
						||
| 
								 | 
							
								          should_tx = (controls_allowed and self.MIN_ACCEL <= brake <= self.MAX_ACCEL) or brake == self.INACTIVE_ACCEL
							 | 
						||
| 
								 | 
							
								          should_tx = should_tx and (controls_allowed or not brake_actuation)
							 | 
						||
| 
								 | 
							
								          self.assertEqual(should_tx, self._tx(self._acc_command_msg(self.INACTIVE_GAS, brake, brake_actuation)))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class TestFordLongitudinalSafety(TestFordLongitudinalSafetyBase):
							 | 
						||
| 
								 | 
							
								  STEER_MESSAGE = MSG_LateralMotionControl
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  TX_MSGS = [
							 | 
						||
| 
								 | 
							
								    [MSG_Steering_Data_FD1, 0], [MSG_Steering_Data_FD1, 2], [MSG_ACCDATA, 0], [MSG_ACCDATA_3, 0], [MSG_Lane_Assist_Data1, 0],
							 | 
						||
| 
								 | 
							
								    [MSG_LateralMotionControl, 0], [MSG_IPMA_Data, 0],
							 | 
						||
| 
								 | 
							
								  ]
							 | 
						||
| 
								 | 
							
								  RELAY_MALFUNCTION_ADDRS = {0: (MSG_ACCDATA, MSG_ACCDATA_3, MSG_Lane_Assist_Data1, MSG_LateralMotionControl,
							 | 
						||
| 
								 | 
							
								                                 MSG_IPMA_Data)}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  FWD_BLACKLISTED_ADDRS = {2: [MSG_ACCDATA, MSG_ACCDATA_3, MSG_Lane_Assist_Data1, MSG_LateralMotionControl,
							 | 
						||
| 
								 | 
							
								                               MSG_IPMA_Data]}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  def setUp(self):
							 | 
						||
| 
								 | 
							
								    self.packer = CANPackerSafety("ford_lincoln_base_pt")
							 | 
						||
| 
								 | 
							
								    self.safety = libsafety_py.libsafety
							 | 
						||
| 
								 | 
							
								    # Make sure we enforce long safety even without long flag for CAN
							 | 
						||
| 
								 | 
							
								    self.safety.set_safety_hooks(CarParams.SafetyModel.ford, 0)
							 | 
						||
| 
								 | 
							
								    self.safety.init_tests()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  def test_max_lateral_acceleration(self):
							 | 
						||
| 
								 | 
							
								    # CAN does not limit curvature from lateral acceleration
							 | 
						||
| 
								 | 
							
								    pass
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class TestFordCANFDLongitudinalSafety(TestFordLongitudinalSafetyBase):
							 | 
						||
| 
								 | 
							
								  STEER_MESSAGE = MSG_LateralMotionControl2
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  TX_MSGS = [
							 | 
						||
| 
								 | 
							
								    [MSG_Steering_Data_FD1, 0], [MSG_Steering_Data_FD1, 2], [MSG_ACCDATA, 0], [MSG_ACCDATA_3, 0], [MSG_Lane_Assist_Data1, 0],
							 | 
						||
| 
								 | 
							
								    [MSG_LateralMotionControl2, 0], [MSG_IPMA_Data, 0],
							 | 
						||
| 
								 | 
							
								  ]
							 | 
						||
| 
								 | 
							
								  RELAY_MALFUNCTION_ADDRS = {0: (MSG_ACCDATA, MSG_ACCDATA_3, MSG_Lane_Assist_Data1, MSG_LateralMotionControl2,
							 | 
						||
| 
								 | 
							
								                                 MSG_IPMA_Data)}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  FWD_BLACKLISTED_ADDRS = {2: [MSG_ACCDATA, MSG_ACCDATA_3, MSG_Lane_Assist_Data1, MSG_LateralMotionControl2,
							 | 
						||
| 
								 | 
							
								                               MSG_IPMA_Data]}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  def setUp(self):
							 | 
						||
| 
								 | 
							
								    self.packer = CANPackerSafety("ford_lincoln_base_pt")
							 | 
						||
| 
								 | 
							
								    self.safety = libsafety_py.libsafety
							 | 
						||
| 
								 | 
							
								    self.safety.set_safety_hooks(CarParams.SafetyModel.ford, FordSafetyFlags.LONG_CONTROL | FordSafetyFlags.CANFD)
							 | 
						||
| 
								 | 
							
								    self.safety.init_tests()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								if __name__ == "__main__":
							 | 
						||
| 
								 | 
							
								  unittest.main()
							 |