#!/usr/bin/env python3 import numpy as np import random import unittest import opendbc.safety.tests.common as common from opendbc.car.lateral import MAX_LATERAL_ACCEL, MAX_LATERAL_JERK 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 # CAN units per rad/m MAX_CURVATURE = 0.02 # rad/m, 1000 CAN units MAX_CURVATURE_ERROR = 0.002 # rad/m, 100 CAN units CURVATURE_ERROR_MIN_SPEED = 10.0 # m/s LATERAL_FREQUENCY = 20 # Hz, for per-frame jerk limit cnt_speed = 0 cnt_speed_2 = 0 cnt_yaw_rate = 0 cnt_lat_ctl = 0 packer: CANPackerSafety safety: libsafety_py.LibSafety def _get_max_curvature_can(self, speed): fudged_speed = max(speed - 1.0, 1.0) return int(MAX_LATERAL_ACCEL / (fudged_speed * fudged_speed) * self.DEG_TO_CAN) + 1 def _get_max_curvature_delta_can(self, speed): fudged_speed = max(speed - 1.0, 1.0) return int(MAX_LATERAL_JERK / (fudged_speed * fudged_speed) / self.LATERAL_FREQUENCY * self.DEG_TO_CAN) + 1 def _set_prev_desired_angle(self, t): t = round(t * self.DEG_TO_CAN) self.safety.set_desired_curvature_last(t) def _reset_curvature_measurement(self, curvature, speed): for _ in range(6): self._rx(self._speed_msg(speed)) self._rx(self._speed_msg_2(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, increment_timer: bool = True): if increment_timer: self.safety.set_timer(self.cnt_lat_ctl * int(1e6 / self.LATERAL_FREQUENCY)) self.__class__.cnt_lat_ctl += 1 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_speed_mismatch(self): 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) self._rx(self._speed_msg(speed)) self._rx(self._speed_msg_2(speed_2)) self.safety.set_controls_allowed(True) self._tx(self._lat_ctl_msg(True, 0, 0, 0, 0)) within_delta = abs(speed - speed_2) <= common.MAX_SPEED_DELTA self.assertEqual(self.safety.get_controls_allowed(), within_delta) 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_curvature_meas_min(), round(-curvature * self.DEG_TO_CAN)) self.assertEqual(self.safety.get_curvature_meas_max(), round(curvature * self.DEG_TO_CAN)) self._rx(self._yaw_rate_msg(0, speed)) self.assertEqual(self.safety.get_curvature_meas_min(), round(-curvature * self.DEG_TO_CAN)) self.assertEqual(self.safety.get_curvature_meas_max(), 0) self._rx(self._yaw_rate_msg(0, speed)) self.assertEqual(self.safety.get_curvature_meas_min(), 0) self.assertEqual(self.safety.get_curvature_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 max_curvature_can = round(self.MAX_CURVATURE * self.DEG_TO_CAN) for speed in np.arange(0, 40, 0.5): max_can = min(self._get_max_curvature_can(speed), max_curvature_can) for offset in (-5, -1, 0, 1, 5): curvature_can = max_can + offset curvature = curvature_can / self.DEG_TO_CAN for sign in (-1, 1): signed_curvature = sign * curvature self.safety.set_controls_allowed(True) self._set_prev_desired_angle(signed_curvature) self._reset_curvature_measurement(signed_curvature, speed) should_tx = abs(curvature_can) <= max_can self.assertEqual(should_tx, self._tx(self._lat_ctl_msg(True, 0, 0, signed_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): max_curvature_can = self._get_max_curvature_can(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) should_tx = should_tx and abs(round(curvature * self.DEG_TO_CAN)) <= max_curvature_can 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): """Curvature command must satisfy the ISO 11270 lateral jerk limit per frame.""" self.safety.set_controls_allowed(True) max_encoded_can = int(self.MAX_CURVATURE * self.DEG_TO_CAN) for speed in np.arange(0, 40, 0.5): max_can = self._get_max_curvature_can(speed) max_delta_can = self._get_max_curvature_delta_can(speed) base_can = max_delta_can * 2 if base_can + max_delta_can + 5 > min(max_can, max_encoded_can): continue # (should_tx, desired_curvature_can) relative to base_can cases = [ (True, max_delta_can), # at jerk limit (False, max_delta_can + 5), # over jerk limit ] for sign in (-1, 1): self._reset_curvature_measurement(sign * base_can / self.DEG_TO_CAN, speed) for should_tx, delta_can in cases: self.safety.set_desired_curvature_last(sign * base_can) self.assertEqual(should_tx, self._tx(self._lat_ctl_msg(True, 0, 0, sign * (base_can + delta_can) / self.DEG_TO_CAN, 0))) def test_curvature_error_limits(self): # above CURVATURE_ERROR_MIN_SPEED, command must be within max_curvature_error of measured, below the check is skipped self.safety.set_controls_allowed(True) max_error_can = round(self.MAX_CURVATURE_ERROR * self.DEG_TO_CAN) for speed in (self.CURVATURE_ERROR_MIN_SPEED - 1, self.CURVATURE_ERROR_MIN_SPEED + 1): limit_command = speed > self.CURVATURE_ERROR_MIN_SPEED self._reset_curvature_measurement(0, speed) for sign in (-1, 1): self.safety.set_desired_curvature_last(sign * max_error_can) self.assertTrue(self._tx(self._lat_ctl_msg(True, 0, 0, sign * (max_error_can + 1) / self.DEG_TO_CAN, 0))) self.safety.set_desired_curvature_last(sign * max_error_can) self.assertEqual(not limit_command, self._tx(self._lat_ctl_msg(True, 0, 0, sign * (max_error_can + 2) / self.DEG_TO_CAN, 0))) # newest speed sample gates the check self._reset_curvature_measurement(0, self.CURVATURE_ERROR_MIN_SPEED - 1) self._rx(self._speed_msg(self.CURVATURE_ERROR_MIN_SPEED + 1)) self.safety.set_desired_curvature_last(max_error_can) self.assertFalse(self._tx(self._lat_ctl_msg(True, 0, 0, (max_error_can + 2) / self.DEG_TO_CAN, 0))) def test_curvature_violation(self): # If violation occurs, curvature cmd is blocked until reset to 0 self.safety.set_controls_allowed(True) speed = 25. max_delta_can = self._get_max_curvature_delta_can(speed) self._reset_curvature_measurement(0, speed) self._tx(self._lat_ctl_msg(True, 0, 0, 0, 0)) over_curvature = (max_delta_can + 5) / self.DEG_TO_CAN for _ in range(20): self.assertFalse(self._tx(self._lat_ctl_msg(True, 0, 0, over_curvature, 0))) self.assertTrue(self._tx(self._lat_ctl_msg(True, 0, 0, 0, 0))) # prev tracks the commanded curvature on a passing tx (not reset to 0 every frame) self.assertTrue(self._tx(self._lat_ctl_msg(True, 0, 0, max_delta_can / self.DEG_TO_CAN, 0))) self.assertTrue(self._tx(self._lat_ctl_msg(True, 0, 0, 2 * max_delta_can / self.DEG_TO_CAN, 0))) def test_rt_limits(self): # send rate is limited over a rolling 250ms window split into two half-interval buckets self.safety.set_controls_allowed(True) self._reset_curvature_measurement(0, 0) max_rt_msgs = int(self.LATERAL_FREQUENCY * common.RT_INTERVAL / 1e6 * 1.2 + 1) half = common.RT_INTERVAL // 2 # too many messages within one window is blocked self.safety.set_timer(0) for i in range(max_rt_msgs * 2): self.assertEqual(i <= max_rt_msgs, self._tx(self._lat_ctl_msg(True, 0, 0, 0, 0, increment_timer=False))) # shift the overflow into the previous bucket self.safety.set_timer(half) self.assertFalse(self._tx(self._lat_ctl_msg(True, 0, 0, 0, 0, increment_timer=False))) # previous bucket still counts within the half interval self.safety.set_timer(half + 2 * common.RT_INTERVAL // 5) self.assertFalse(self._tx(self._lat_ctl_msg(True, 0, 0, 0, 0, increment_timer=False))) self.assertFalse(self._tx(self._lat_ctl_msg(True, 0, 0, 0, 0, increment_timer=False))) # both buckets clear after a full interval self.safety.set_timer(half + common.RT_INTERVAL) self.assertFalse(self._tx(self._lat_ctl_msg(True, 0, 0, 0, 0, increment_timer=False))) self.safety.set_timer(half + 2 * common.RT_INTERVAL) for _ in range(max_rt_msgs): self.assertTrue(self._tx(self._lat_ctl_msg(True, 0, 0, 0, 0, increment_timer=False))) 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))) def test_enable_control_allowed_from_acc_main_on(self): for enable_mads in (True, False): with self.subTest("enable_mads", mads_enabled=enable_mads): for main_button_msg_valid in (True, False): with self.subTest("main_button_msg_valid", state_valid=main_button_msg_valid): self.safety.set_mads_params(enable_mads, False, False) self._rx(self._pcm_status_msg(main_button_msg_valid)) self.assertEqual(enable_mads and main_button_msg_valid, self.safety.get_controls_allowed_lateral()) 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): brake_values = self._boundary_values([self.MIN_ACCEL, self.MAX_ACCEL, self.INACTIVE_ACCEL], self.MIN_ACCEL - 2, self.MAX_ACCEL + 2, 0.05) for controls_allowed in (True, False): self.safety.set_controls_allowed(controls_allowed) for brake_actuation in (True, False): for brake in brake_values: 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() 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()