Files
StarPilot/opendbc_repo/opendbc/safety/tests/test_ford.py
T
firestar5683 22a8575199 Vendor dependencies and resources for standalone builds
Keep official openpilot ancestry and dependency licenses. Store resources directly in Git, remove LFS setup requirements, and leave the downloadable big model outside the source tree.
2026-10-03 21:01:18 +00:00

530 lines
26 KiB
Python
Executable File

#!/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
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
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):
DBC = "ford_lincoln_base_pt"
SAFETY_MODEL = CarParams.SafetyModel.ford
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
lat_ctl_frame = 0
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 _get_max_curvature_delta_relaxed_can(self, speed):
# flipped fudge, this is the least movement toward the error bounds safety requires
fudged_speed = speed + 1.0
return int(MAX_LATERAL_JERK / (fudged_speed * fudged_speed) / self.LATERAL_FREQUENCY * self.DEG_TO_CAN) - 1
def _get_max_curvature_relaxed_can(self, speed):
# flipped fudge, safety never requires commanding more curvature than openpilot can send
fudged_speed = speed + 1.0
max_curvature_accel_can = int(MAX_LATERAL_ACCEL / (fudged_speed * fudged_speed) * self.DEG_TO_CAN) - 1
return min(max_curvature_accel_can, round(self.MAX_CURVATURE * self.DEG_TO_CAN))
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}
return self.packer.make_can_msg_safety("BrakeSysFeatures", 0, values)
# 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}
return self.packer.make_can_msg_safety("EngVehicleSpThrottle2", 0, values)
# 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}
return self.packer.make_can_msg_safety("Yaw_Data_FD1", 0, values)
# 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)
def test_enable_control_allowed_from_queue_assist(self):
msg = self.packer.make_can_msg_safety("EngBrakeData", 0, {"BpedDrvAppl_D_Actl": 1, "CcStat_D_Actl": 4})
self.assertTrue(self._rx(msg))
self.assertTrue(self.safety.get_controls_allowed())
# 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.lat_ctl_frame * int(1e6 / self.LATERAL_FREQUENCY))
self.__class__.lat_ctl_frame += 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):
"""
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 = self._get_max_curvature_can(speed) / self.DEG_TO_CAN
limit_command = speed > self.CURVATURE_ERROR_MIN_SPEED
# ensure our limits match the safety's rounded limits
# lateral jerk is symmetric, so the wind up and wind down limits are the same
max_delta = self._get_max_curvature_delta_can(speed) / self.DEG_TO_CAN
max_delta_relaxed = self._get_max_curvature_delta_relaxed_can(speed) / self.DEG_TO_CAN
up_cases = (self.MAX_CURVATURE_ERROR * 2, [
(not limit_command, 0, 0),
(not limit_command, 0, max_delta_relaxed - small_curvature),
(True, 0, max_delta_relaxed),
(True, 0, max_delta),
(False, 0, max_delta + 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_relaxed + small_curvature),
(True, self.MAX_CURVATURE, self.MAX_CURVATURE - max_delta_relaxed),
(True, self.MAX_CURVATURE, self.MAX_CURVATURE - max_delta),
(False, self.MAX_CURVATURE, self.MAX_CURVATURE - max_delta - small_curvature),
])
# the driver can hold a curvature openpilot may not command, safety must never require moving past
# the most it can send: the lower of the lateral acceleration limit and what the EPS accepts
max_curvature_relaxed = self._get_max_curvature_relaxed_can(speed) / self.DEG_TO_CAN
# safety fudges the speed down for the accel check and up for the cap, so the last command can sit above the cap
max_curvature_allowed_can = min(self._get_max_curvature_can(speed), round(self.MAX_CURVATURE * self.DEG_TO_CAN))
winds_down_within_jerk = (max_curvature_allowed_can - self._get_max_curvature_relaxed_can(speed) <=
self._get_max_curvature_delta_can(speed))
relaxed_cases = (self.MAX_CURVATURE * 2, [
(True, max_curvature_relaxed, max_curvature_relaxed),
(not limit_command, max_curvature_relaxed, max_curvature_relaxed - small_curvature),
# no longer requiring the command to wind towards meas doesn't stop rate limiting it winding away
(winds_down_within_jerk, max_curvature_allowed_can / self.DEG_TO_CAN, max_curvature_relaxed),
])
for sign in (-1, 1):
for angle_meas, cases in (up_cases, down_cases, relaxed_cases):
self._reset_curvature_measurement(sign * angle_meas, speed)
for should_tx, initial_curvature, desired_curvature in cases:
# at low speeds one frame of jerk exceeds the curvature signal, so the should_tx=False cases will rightly not fail.
# assert we never drop a case at a speed where the curvature error is enforced
if abs(desired_curvature) > self.MAX_CURVATURE:
self.assertLess(speed, self.CURVATURE_ERROR_MIN_SPEED)
continue
# can not send if the curvature is above the max lateral acceleration
should_tx = should_tx and abs(desired_curvature) <= curvature_accel_limit
self._set_prev_desired_angle(sign * initial_curvature)
self.assertEqual(should_tx, self._tx(self._lat_ctl_msg(True, 0, 0, sign * desired_curvature, 0)))
# the newest speed sample gates the check, not the whole sample window
max_error = self.MAX_CURVATURE_ERROR + small_curvature * 2
for sign in (-1, 1):
self._reset_curvature_measurement(0, self.CURVATURE_ERROR_MIN_SPEED - 1)
self._rx(self._speed_msg(self.CURVATURE_ERROR_MIN_SPEED + 1))
self._set_prev_desired_angle(sign * self.MAX_CURVATURE_ERROR)
self.assertFalse(self._tx(self._lat_ctl_msg(True, 0, 0, sign * max_error, 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)))
class TestFordCANFDStockSafety(TestFordSafetyBase):
SAFETY_PARAM = FordSafetyFlags.CANFD
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]}
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):
# Make sure we enforce long safety even without long flag for CAN
SAFETY_PARAM = 0
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]}
class TestFordCANFDLongitudinalSafety(TestFordLongitudinalSafetyBase):
SAFETY_PARAM = FordSafetyFlags.LONG_CONTROL | FordSafetyFlags.CANFD
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]}
if __name__ == "__main__":
unittest.main()