#!/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 CANPackerPanda 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.PandaCarSafetyTest): 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]} # Max allowed delta between car speeds MAX_SPEED_DELTA = 2.0 # m/s 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: CANPackerPanda safety: libsafety_py.Panda 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_panda("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_panda("BrakeSysFeatures", 0, values, fix_checksum=checksum) # PCM vehicle speed def _speed_msg_2(self, speed: float, quality_flag=True): 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_panda("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_panda("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_panda("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_panda("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_panda("EngBrakeData", 0, values) # LKAS command def _lkas_command_msg(self, action: int): values = { "LkaActvStats_D2_Req": action, } return self.packer.make_can_msg_panda("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_panda("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_panda("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_panda("Steering_Data_FD1", bus, values) def test_rx_hook(self): # checksum, counter, and quality flag checks for quality_flag in [True, False]: for msg in ["speed", "speed_2", "yaw"]: self.safety.set_controls_allowed(True) # send multiple times to verify counter checks for _ in range(10): if msg == "speed": to_push = self._speed_msg(0, quality_flag=quality_flag) elif msg == "speed_2": to_push = self._speed_msg_2(0, quality_flag=quality_flag) elif msg == "yaw": to_push = self._yaw_rate_msg(0, 0, quality_flag=quality_flag) self.assertEqual(quality_flag, self._rx(to_push)) self.assertEqual(quality_flag, self.safety.get_controls_allowed()) # Mess with checksum to make it fail, checksum is not checked for 2nd speed to_push[0].data[3] = 0 # Speed checksum & half of yaw signal should_rx = msg == "speed_2" and quality_flag self.assertEqual(should_rx, self._rx(to_push)) self.assertEqual(should_rx, self.safety.get_controls_allowed()) def test_rx_hook_speed_mismatch(self): # Ford relies on speed for driver curvature limiting, so it checks two sources for speed in np.arange(0, 40, 0.5): for speed_delta in np.arange(-5, 5, 0.1): speed_2 = round(max(speed + speed_delta, 0), 1) # Set controls allowed in between rx since first message can reset it self._rx(self._speed_msg(speed)) self.safety.set_controls_allowed(True) self._rx(self._speed_msg_2(speed_2)) within_delta = abs(speed - speed_2) <= self.MAX_SPEED_DELTA self.assertEqual(self.safety.get_controls_allowed(), within_delta) 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=path_offset, path_angle=path_angle, curvature_rate=curvature_rate, curvature=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 panda 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 = CANPackerPanda("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_panda("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 = CANPackerPanda("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 = CANPackerPanda("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()