mirror of
https://github.com/infiniteCable2/panda.git
synced 2026-02-18 17:23:52 +08:00
* improvements on the state machine for exiting controls and improvements on replay drive, and optimizations * New tests on hyundai for now to see how they behave with lkas and main button pressed interchangeably * cleaning up hyundai tests and ensuring we always cleanup mads states at the end of the tests * Adjusting tests * Adjusting the tests a bit more to ensure clean states * Cleaning up and simplifying logic * ensuring all tests always cleanup * improving the state * make static happy * Refactor safety replay script for better debugging and update Honda safety code The commit performs a comprehensive revision of the safety replay script, specifically focusing on introducing debug variables and enhancing the logging capabilities for improved debugging. Furthermore, changes were made to the Honda safety code. The test helpers within libpanda were also expanded for inclusion of additional test conditions. * Introduce 'ACC_MAIN_OFF' as a new disengagement reason in Sunnypilot's 'safety_mads.h' The Sunnypilot's 'safety_mads.h' file has been updated to include 'ACC_MAIN_OFF' as a new cause for disconnection in the 'DisengageReason' enumeration. If an 'acc_main_off' signal is received, the 'mads_exit_controls' function halts all requests for lateral control engagement. Additionally, the status of 'controls_requested_lat' now mirrors 'controls_allowed_lat' after a button press. * simpler logic cleaner * reorder code for readability * tmp * Refactor state transitions and add event handlers Renamed StateTransition to EdgeTransition for clarity and updated related logic. Introduced event handlers for button presses and ACC state changes, reducing duplicated control flow code. Improved encapsulation and maintainability by restructuring state update functions. * reorder * Refactor MADS state handling logic Removed redundant event handler functions and unnecessary timestamp fields to streamline the code. Simplified button and binary state updates by integrating logic directly into transition checks. Commented out unused fields * adding some more tests * split init * format * update naming * Refine lateral control request logic in safety_mads.h The logic for setting the `controls_requested_lat` variable in safety_mads.h has been refined. Previously, it switched state based on the current value of `controls_allowed_lat`. Now, it also takes into account the current state of `acc_main`, ensuring a more nuanced control request mechanism that accounts for different operational scenarios. * Fix button state handling in mads_exit_controls logic. Refactor button state transitions to better handle lateral control requests when ACC is active. Ensure controls are correctly disengaged under specific conditions, by setting `controls_requested_lat` more reliably during state transitions. This change improves safety by preventing inadvertent disengagement when ACC is not active. * Add test for LKAS button press with ACC main on This commit introduces a new test to ensure that controls remain enabled when the LKAS/LFA button is pressed while ACC main is on. It checks that LKAS button operations don't interfere with control permissions in this specific configuration, improving test coverage and preventing potential safety issues. * Add mismatch detection and change mads_acc_main to bool Enhanced mismatch detection logic by tracking cases where 'controls_allowed' is true while 'controls_allowed_lat' is false, updating the script to print relevant debug information. Additionally, changed the data type of 'mads_acc_main' and 'mads_acc_main_prev' from int to bool for improved type accuracy and consistency. * update controls_allowed_lat_pkt on health pkt to actually follow is_lat_active() which has the final word on whether we can allow lat or not. * Can't perform this test on toyota as we never really process a button disengagement for toyota * wow, we forgot about pcm hyundai can-fd * nuke nuke nuke * Revert "nuke nuke nuke" This reverts commit 9bf0de640a3439ac43c27bcbc6568853966d370b. * update name * event driven update states * add get_pcm_main_cruise_available * split PCM and non-PCM main cruise tets * fix some * pcm main cruise availability mutation * toyota pass fake lkas btn pressed * more * make pcm acc main rising edge on init * only falling edge when actually 0 (need test for mutation) * misra * remove state flags, main button related * skip lkas related tests with toyota and subaru fake button * need for honda * static * mutation * misra * skip nidec pcm alt * engage mads if controls allowed rising * static * remove non pcm properties * fixup! engage mads if controls allowed rising * move back * fix static * move around * Hyundai openpilot longitudinal main cruise button state handling * main button unit test * acc_main_on mismatch unit tests * clean up old main cruise button unit tests * add more reasons * cleanup * rename * rearrange * Revert "rearrange" This reverts commit f07caaa5b98b74c23667b387430ac48ba95bf21c. * more rearrange * rename * more * too slow * Revert "too slow" This reverts commit 31a249aebfa9c985e37be050e525b6924ca9e83d. * too slow v2 * cleanup * rename * more cleanup * Parse more flags from alt exp, more tests, hyundai main cruise allowed * missed * mutation for controls allowed rising edge * ford mutation * Update tests/safety/test.sh Co-authored-by: DevTekVE <devtekve@gmail.com> * license * unused * remove * comment * Apply suggestions from code review Co-authored-by: DevTekVE <devtekve@gmail.com> * comment * refactor alternative experience handling with helper function * use always allowed mads button alt exp * rename * parenthesis * use alternative experience for unit tests inits * cleanup * rename * mutation tests for alternative experience flags * bump timer * test for disengage and no disengage lateral on brake * test allow MADS engage with brake pressed * rename * move around * button combo test * use acc_main_on directly from global * fix caught failures from last commit's fix * Revert "use acc_main_on directly from global" This reverts commit 346964f55d020a287a1a679e22691ad8873e2a64. * Properly fix lmao * Add support for LKAS button handling across Chrysler platforms Introduced LKAS button message parsing for multiple Chrysler platforms, including specific handling for center stack button messages. Updated tests and safety configurations to reflect these changes, ensuring compatibility with different vehicle variants. This enhances modularity and improves safety feature integration. * Dockerfile: point to sunnypilot/opendbc * Happy days :) * clean * testx * Revert "Happy days :)" This reverts commit 7ea27b53c8f3b1e37677c1ce9498229fceac9de6. * symlink prior building * comment * only parse mads lateral, not stock op's lateral * do not allow controls allowed if acc_main_on is off * expose system_enabled, do not allow controls allowed to steer if system_mads is off * fix hyundai tests with acc_main_on requirement * fix test with new controls allowed with system_mads off * fix replay drive * Change 'DISABLE_DISENGAGE_LATERAL_ON_BRAKE' to 'DISENGAGE_LATERAL_ON_BRAKE' The commit modifies the usage of the 'DISABLE_DISENGAGE_LATERAL_ON_BRAKE' variable globally and replaces it with 'DISENGAGE_LATERAL_ON_BRAKE'. This change promotes correct and clear semantics, since the variable now indicates a state rather than the negation of a state. * Adding some more debug printouts on replay drive * remove unified engagement mode in panda * treat MADS button as user entry * controls allow should be allowed at all times * squash! treat MADS button as user entry * heartbeat for mads * heartbeat mismatch exit control * remove always allow mads button from alt * move to safety_mads * check heartbeat directly in main * remove main cruise allowed from alt * uint * squash! check heartbeat directly in main * update tests * not needed * fix mads_exit_controls sometimes not assigning disengage reason * more disengage lateral on brake tests * extern * missesd * honda mutation test * again * rename * more dlob test * update name * fix tests * fix panda tests * Refactor MADS state management to simplify pointer usage. This change replaces many pointer-based state variables with direct ones, improving code readability and reducing complexity. It also standardizes the use of `const` for parameters and updates function implementations accordingly. These improvements enhance maintainability and reduce potential for pointer-related errors. * Simplify braking logic in m_mads_check_braking function Removed redundant conditions to streamline braking logic. This change maintains functionality while improving code readability and maintainability. Only necessary checks are now performed to determine disengagement. * Prevent lateral control engagement during braking Added a condition to disable lateral control engagement when braking with disengage-on-brake enabled. This change is marked as a demonstration and is not final for merging. Moved the disengage_reason to be set only when an actual disengagement occurred. * Refactor MADS state handling and fix type consistency Remove redundant `get_mads_state` inline definition and migrate it to a static function. Fix return type syntax in `get_mads_pending_disengage_reason`. Minor formatting adjustments improve readability and code clarity. Refactor disengagement logic with enhanced reason tracking Added distinction between active and pending disengagement reasons to improve system state tracking. Updated related enums, structs, and logic to ensure proper handling during control transitions. Added new safety tests to verify behavior under braking and ACC conditions. Refactoring lateral control permissions and brake checks in MADS This revision refactors the MADS safety code. The aim is to simplify and improve readability. Operations and checks for brake states and lateral control permissions have been consolidated into fewer methods. In addition, unused 'previous_disengage' state tracking has been removed from MADSState structure to avoid unnecessary state tracking. Moreover, the 'can_allow_controls_lat' function has been removed entirely and its functionality has been incorporated into other functions, reducing the function count and complexity of the code. The braking status is now tracked with BinaryStateTracking for consistency. These changes maintain the system's functionality while optimizing the code and improving maintainability. * Why MISRA, why!? WHY!???? I DIDNT EVEN TOUCH THIS FILE OR NOWHERE NEAR! * Some format * no more messing with misra * const * more generic names * revert to validate * are you srs * make gpio.h stock again and add to supression lists the check on gpio.h since we are not even touching it and we don't plan on ever doing so * hard code to skip heartbeat check * update comment * cleanup * Update tests/safety/test_honda.py --------- Co-authored-by: DevTekVE <devtekve@gmail.com>
494 lines
21 KiB
Python
Executable File
494 lines
21 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
import numpy as np
|
|
import random
|
|
import unittest
|
|
|
|
import panda.tests.safety.common as common
|
|
|
|
from panda import Panda
|
|
from panda.tests.libpanda import libpanda_py
|
|
from panda.tests.safety.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 stock longitudinal
|
|
# * 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]}
|
|
FWD_BUS_LOOKUP = {0: 2, 2: 0}
|
|
|
|
# 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: libpanda_py.Panda
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
if cls.__name__ == "TestFordSafetyBase":
|
|
raise unittest.SkipTest
|
|
|
|
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_steer_allowed(self):
|
|
path_offsets = np.arange(-5.12, 5.11, 1).round()
|
|
path_angles = np.arange(-0.5, 0.5235, 0.1).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):
|
|
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)
|
|
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_limit_up(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)
|
|
small_curvature = 2 / self.DEG_TO_CAN # significant small amount of curvature to cross boundary
|
|
|
|
for speed in np.arange(0, 40, 0.5):
|
|
limit_command = speed > self.CURVATURE_ERROR_MIN_SPEED
|
|
max_delta_up = np.interp(speed - 1, self.ANGLE_RATE_BP, self.ANGLE_RATE_UP)
|
|
max_delta_up_lower = np.interp(speed + 1, self.ANGLE_RATE_BP, self.ANGLE_RATE_UP)
|
|
|
|
cases = [
|
|
(not limit_command, 0),
|
|
(not limit_command, max_delta_up_lower - small_curvature),
|
|
(True, max_delta_up_lower),
|
|
(True, max_delta_up),
|
|
(False, max_delta_up + small_curvature),
|
|
]
|
|
|
|
for sign in (-1, 1):
|
|
self._reset_curvature_measurement(sign * (self.MAX_CURVATURE_ERROR + 1e-3), speed)
|
|
for should_tx, curvature in cases:
|
|
self._set_prev_desired_angle(sign * small_curvature)
|
|
self.assertEqual(should_tx, self._tx(self._lat_ctl_msg(True, 0, 0, sign * (small_curvature + curvature), 0)))
|
|
|
|
def test_curvature_rate_limit_down(self):
|
|
self.safety.set_controls_allowed(True)
|
|
small_curvature = 2 / self.DEG_TO_CAN # significant small amount of curvature to cross boundary
|
|
|
|
for speed in np.arange(0, 40, 0.5):
|
|
limit_command = speed > self.CURVATURE_ERROR_MIN_SPEED
|
|
max_delta_down = np.interp(speed - 1, self.ANGLE_RATE_BP, self.ANGLE_RATE_DOWN)
|
|
max_delta_down_lower = np.interp(speed + 1, self.ANGLE_RATE_BP, self.ANGLE_RATE_DOWN)
|
|
|
|
cases = [
|
|
(not limit_command, self.MAX_CURVATURE),
|
|
(not limit_command, self.MAX_CURVATURE - max_delta_down_lower + small_curvature),
|
|
(True, self.MAX_CURVATURE - max_delta_down_lower),
|
|
(True, self.MAX_CURVATURE - max_delta_down),
|
|
(False, self.MAX_CURVATURE - max_delta_down - small_curvature),
|
|
]
|
|
|
|
for sign in (-1, 1):
|
|
self._reset_curvature_measurement(sign * (self.MAX_CURVATURE - self.MAX_CURVATURE_ERROR - 1e-3), speed)
|
|
for should_tx, curvature in cases:
|
|
self._set_prev_desired_angle(sign * self.MAX_CURVATURE)
|
|
self.assertEqual(should_tx, self._tx(self._lat_ctl_msg(True, 0, 0, sign * 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)))
|
|
|
|
def test_enable_control_allowed_from_acc_main_on(self):
|
|
try:
|
|
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._mads_states_cleanup()
|
|
self.safety.set_mads_params(enable_mads, 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_lat())
|
|
finally:
|
|
self._mads_states_cleanup()
|
|
|
|
|
|
class TestFordStockSafety(TestFordSafetyBase):
|
|
STEER_MESSAGE = MSG_LateralMotionControl
|
|
|
|
TX_MSGS = [
|
|
[MSG_Steering_Data_FD1, 0], [MSG_Steering_Data_FD1, 2], [MSG_ACCDATA_3, 0], [MSG_Lane_Assist_Data1, 0],
|
|
[MSG_LateralMotionControl, 0], [MSG_IPMA_Data, 0],
|
|
]
|
|
|
|
def setUp(self):
|
|
self.packer = CANPackerPanda("ford_lincoln_base_pt")
|
|
self.safety = libpanda_py.libpanda
|
|
self.safety.set_safety_hooks(Panda.SAFETY_FORD, 0)
|
|
self.safety.init_tests()
|
|
|
|
|
|
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],
|
|
]
|
|
|
|
def setUp(self):
|
|
self.packer = CANPackerPanda("ford_lincoln_base_pt")
|
|
self.safety = libpanda_py.libpanda
|
|
self.safety.set_safety_hooks(Panda.SAFETY_FORD, Panda.FLAG_FORD_CANFD)
|
|
self.safety.init_tests()
|
|
|
|
|
|
class TestFordLongitudinalSafetyBase(TestFordSafetyBase):
|
|
RELAY_MALFUNCTION_ADDRS = {0: (MSG_ACCDATA, MSG_ACCDATA_3, MSG_Lane_Assist_Data1, MSG_LateralMotionControl,
|
|
MSG_LateralMotionControl2, MSG_IPMA_Data)}
|
|
|
|
FWD_BLACKLISTED_ADDRS = {2: [MSG_ACCDATA, MSG_ACCDATA_3, MSG_Lane_Assist_Data1, MSG_LateralMotionControl,
|
|
MSG_LateralMotionControl2, MSG_IPMA_Data]}
|
|
|
|
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
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
if cls.__name__ == "TestFordLongitudinalSafetyBase":
|
|
raise unittest.SkipTest
|
|
|
|
# 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],
|
|
]
|
|
|
|
def setUp(self):
|
|
self.packer = CANPackerPanda("ford_lincoln_base_pt")
|
|
self.safety = libpanda_py.libpanda
|
|
self.safety.set_safety_hooks(Panda.SAFETY_FORD, Panda.FLAG_FORD_LONG_CONTROL)
|
|
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],
|
|
]
|
|
|
|
def setUp(self):
|
|
self.packer = CANPackerPanda("ford_lincoln_base_pt")
|
|
self.safety = libpanda_py.libpanda
|
|
self.safety.set_safety_hooks(Panda.SAFETY_FORD, Panda.FLAG_FORD_LONG_CONTROL | Panda.FLAG_FORD_CANFD)
|
|
self.safety.init_tests()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|