#!/usr/bin/env python3
import unittest
import numpy as np
from opendbc . car . tesla . values import TeslaSafetyFlags
from opendbc . car . tesla . carcontroller import get_max_angle_delta , get_max_angle , get_safety_CP
from opendbc . car . structs import CarParams
from opendbc . car . vehicle_model import VehicleModel
from opendbc . can . can_define import CANDefine
from opendbc . safety . tests . libsafety import libsafety_py
import opendbc . safety . tests . common as common
from opendbc . safety . tests . common import CANPackerPanda , MAX_WRONG_COUNTERS , away_round , round_speed
MSG_DAS_steeringControl = 0x488
MSG_APS_eacMonitor = 0x27d
MSG_DAS_Control = 0x2b9
def round_angle ( apply_angle , can_offset = 0 ) :
apply_angle_can = ( apply_angle + 1638.35 ) / 0.1 + can_offset
# 0.49999_ == 0.5
rnd_offset = 1e-5 if apply_angle > = 0 else - 1e-5
return away_round ( apply_angle_can + rnd_offset ) * 0.1 - 1638.35
class TestTeslaSafetyBase ( common . PandaCarSafetyTest , common . AngleSteeringSafetyTest , common . LongitudinalAccelSafetyTest ) :
RELAY_MALFUNCTION_ADDRS = { 0 : ( MSG_DAS_steeringControl , MSG_APS_eacMonitor ) }
FWD_BLACKLISTED_ADDRS = { 2 : [ MSG_DAS_steeringControl , MSG_APS_eacMonitor ] }
TX_MSGS = [ [ MSG_DAS_steeringControl , 0 ] , [ MSG_APS_eacMonitor , 0 ] , [ MSG_DAS_Control , 0 ] ]
STANDSTILL_THRESHOLD = 0.1
GAS_PRESSED_THRESHOLD = 3
# Angle control limits
STEER_ANGLE_MAX = 360 # deg
DEG_TO_CAN = 10
# Tesla uses get_max_angle_delta and get_max_angle for real lateral accel and jerk limits
# TODO: integrate this into AngleSteeringSafetyTest
ANGLE_RATE_BP = None
ANGLE_RATE_UP = None
ANGLE_RATE_DOWN = None
# Long control limits
MAX_ACCEL = 2.0
MIN_ACCEL = - 3.48
INACTIVE_ACCEL = 0.0
# Max allowed delta between car speeds
MAX_SPEED_DELTA = 2.0 # m/s
cnt_epas = 0
packer : CANPackerPanda
def setUp ( self ) :
self . VM = VehicleModel ( get_safety_CP ( ) )
self . packer = CANPackerPanda ( " tesla_model3_party " )
self . define = CANDefine ( " tesla_model3_party " )
self . acc_states = { d : v for v , d in self . define . dv [ " DAS_control " ] [ " DAS_accState " ] . items ( ) }
self . autopark_states = { d : v for v , d in self . define . dv [ " DI_state " ] [ " DI_autoparkState " ] . items ( ) }
self . active_autopark_states = [ self . autopark_states [ s ] for s in ( ' ACTIVE ' , ' COMPLETE ' , ' SELFPARK_STARTED ' ) ]
self . steer_control_types = { d : v for v , d in self . define . dv [ " DAS_steeringControl " ] [ " DAS_steeringControlType " ] . items ( ) }
def _angle_cmd_msg ( self , angle : float , state : bool | int , bus : int = 0 ) :
values = { " DAS_steeringAngleRequest " : angle , " DAS_steeringControlType " : state }
return self . packer . make_can_msg_panda ( " DAS_steeringControl " , bus , values )
def _angle_meas_msg ( self , angle : float , hands_on_level : int = 0 , eac_status : int = 1 , eac_error_code : int = 0 ) :
values = { " EPAS3S_internalSAS " : angle , " EPAS3S_handsOnLevel " : hands_on_level ,
" EPAS3S_eacStatus " : eac_status , " EPAS3S_eacErrorCode " : eac_error_code ,
" EPAS3S_sysStatusCounter " : self . cnt_epas % 16 }
self . __class__ . cnt_epas + = 1
return self . packer . make_can_msg_panda ( " EPAS3S_sysStatus " , 0 , values )
def _user_brake_msg ( self , brake ) :
values = { " IBST_driverBrakeApply " : 2 if brake else 1 }
return self . packer . make_can_msg_panda ( " IBST_status " , 0 , values )
def _speed_msg ( self , speed ) :
values = { " DI_vehicleSpeed " : speed * 3.6 }
return self . packer . make_can_msg_panda ( " DI_speed " , 0 , values )
def _speed_msg_2 ( self , speed , quality_flag = True ) :
values = { " ESP_vehicleSpeed " : speed * 3.6 , " ESP_wheelSpeedsQF " : quality_flag }
return self . packer . make_can_msg_panda ( " ESP_B " , 0 , values )
def _vehicle_moving_msg ( self , speed : float ) :
values = { " DI_cruiseState " : 3 if speed < = self . STANDSTILL_THRESHOLD else 2 }
return self . packer . make_can_msg_panda ( " DI_state " , 0 , values )
def _user_gas_msg ( self , gas ) :
values = { " DI_accelPedalPos " : gas }
return self . packer . make_can_msg_panda ( " DI_systemStatus " , 0 , values )
def _pcm_status_msg ( self , enable , autopark_state = 0 ) :
values = {
" DI_cruiseState " : 2 if enable else 0 ,
" DI_autoparkState " : autopark_state ,
}
return self . packer . make_can_msg_panda ( " DI_state " , 0 , values )
def _long_control_msg ( self , set_speed , acc_state = 0 , jerk_limits = ( 0 , 0 ) , accel_limits = ( 0 , 0 ) , aeb_event = 0 , bus = 0 ) :
values = {
" DAS_setSpeed " : set_speed ,
" DAS_accState " : acc_state ,
" DAS_aebEvent " : aeb_event ,
" DAS_jerkMin " : jerk_limits [ 0 ] ,
" DAS_jerkMax " : jerk_limits [ 1 ] ,
" DAS_accelMin " : accel_limits [ 0 ] ,
" DAS_accelMax " : accel_limits [ 1 ] ,
}
return self . packer . make_can_msg_panda ( " DAS_control " , bus , values )
def _accel_msg ( self , accel : float ) :
# For common.LongitudinalAccelSafetyTest
return self . _long_control_msg ( 10 , accel_limits = ( accel , max ( accel , 0 ) ) )
def test_rx_hook ( self ) :
# counter check
for msg in ( " angle " , " long " , " speed " , " speed_2 " ) :
# send multiple times to verify counter checks
for i in range ( 10 ) :
if msg == " angle " :
to_push = self . _angle_cmd_msg ( 0 , True , bus = 2 )
elif msg == " long " :
to_push = self . _long_control_msg ( 0 , bus = 2 )
elif msg == " speed " :
to_push = self . _speed_msg ( 0 )
elif msg == " speed_2 " :
to_push = self . _speed_msg_2 ( 0 )
should_rx = i > = 5
if not should_rx :
# mess with checksums
if msg == " angle " :
to_push [ 0 ] . data [ 3 ] = 0
elif msg == " long " :
to_push [ 0 ] . data [ 7 ] = 0
elif msg == " speed " :
to_push [ 0 ] . data [ 0 ] = 0
elif msg == " speed_2 " :
to_push [ 0 ] . data [ 7 ] = 0
self . safety . set_controls_allowed ( True )
self . assertEqual ( should_rx , self . _rx ( to_push ) )
self . assertEqual ( should_rx , self . safety . get_controls_allowed ( ) )
# Send static counters
for i in range ( MAX_WRONG_COUNTERS + 1 ) :
should_rx = i + 1 < MAX_WRONG_COUNTERS
self . assertEqual ( should_rx , self . _rx ( to_push ) )
self . assertEqual ( should_rx , self . safety . get_controls_allowed ( ) )
def test_vehicle_speed_measurements ( self ) :
# OVERRIDDEN: 79.1667 is the max speed in m/s
self . _common_measurement_test ( self . _speed_msg , 0 , 285 / 3.6 , 1 ,
self . safety . get_vehicle_speed_min , self . safety . get_vehicle_speed_max )
def test_rx_hook_speed_mismatch ( self ) :
# TODO: this can be a common test w/ Ford
# Tesla relies on speed for lateral limits close to ISO 11270, so it checks two sources
for speed in np . arange ( 0 , 40 , 0.5 ) :
# match signal rounding on CAN
speed = away_round ( speed / 0.08 * 3.6 ) * 0.08 / 3.6
for speed_delta in np . arange ( - 5 , 5 , 0.1 ) :
speed_2 = max ( speed + speed_delta , 0 )
speed_2 = away_round ( speed_2 * 2 * 3.6 ) / 2 / 3.6
# Set controls allowed in between rx since first message can reset it
self . assertTrue ( self . _rx ( self . _speed_msg ( speed ) ) )
self . safety . set_controls_allowed ( True )
self . assertTrue ( 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 )
# Test ESP_B quality flag
for quality_flag in ( True , False ) :
self . safety . set_controls_allowed ( True )
self . assertTrue ( self . _rx ( self . _speed_msg ( 0 ) ) )
self . assertEqual ( quality_flag , self . _rx ( self . _speed_msg_2 ( 0 , quality_flag = quality_flag ) ) )
self . assertEqual ( quality_flag , self . safety . get_controls_allowed ( ) )
def test_steering_wheel_disengage ( self ) :
# Tesla disengages when the user forcibly overrides the locked-in angle steering control
# Either when the hands on level is high, or if there is a high angle rate fault
for hands_on_level in range ( 4 ) :
for eac_status in range ( 8 ) :
for eac_error_code in range ( 16 ) :
self . safety . set_controls_allowed ( True )
should_disengage = hands_on_level > = 3 or ( eac_status == 0 and eac_error_code == 9 )
self . assertTrue ( self . _rx ( self . _angle_meas_msg ( 0 , hands_on_level = hands_on_level , eac_status = eac_status ,
eac_error_code = eac_error_code ) ) )
self . assertNotEqual ( should_disengage , self . safety . get_controls_allowed ( ) )
self . assertEqual ( should_disengage , self . safety . get_steering_disengage_prev ( ) )
# Should not recover
self . assertTrue ( self . _rx ( self . _angle_meas_msg ( 0 , hands_on_level = 0 , eac_status = 1 , eac_error_code = 0 ) ) )
self . assertNotEqual ( should_disengage , self . safety . get_controls_allowed ( ) )
self . assertFalse ( self . safety . get_steering_disengage_prev ( ) )
def test_autopark_summon_while_enabled ( self ) :
# We should not respect Autopark that activates while controls are allowed
self . _rx ( self . _pcm_status_msg ( True , 0 ) )
self . _rx ( self . _pcm_status_msg ( True , self . autopark_states [ " SELFPARK_STARTED " ] ) )
self . assertTrue ( self . safety . get_controls_allowed ( ) )
self . assertTrue ( self . _tx ( self . _angle_cmd_msg ( 0 , True ) ) )
self . assertTrue ( self . _tx ( self . _long_control_msg ( 0 , acc_state = self . acc_states [ " ACC_CANCEL_GENERIC_SILENT " ] ) ) )
# We should still not respect Autopark if we disengage cruise
self . _rx ( self . _pcm_status_msg ( False , self . autopark_states [ " SELFPARK_STARTED " ] ) )
self . assertFalse ( self . safety . get_controls_allowed ( ) )
self . assertTrue ( self . _tx ( self . _angle_cmd_msg ( 0 , False ) ) )
self . assertTrue ( self . _tx ( self . _long_control_msg ( 0 , acc_state = self . acc_states [ " ACC_CANCEL_GENERIC_SILENT " ] ) ) )
def test_autopark_summon_behavior ( self ) :
for autopark_state in range ( 16 ) :
self . _rx ( self . _pcm_status_msg ( False , 0 ) )
# We shouldn't allow controls if Autopark is an active state
autopark_active = autopark_state in self . active_autopark_states
self . _rx ( self . _pcm_status_msg ( False , autopark_state ) )
self . _rx ( self . _pcm_status_msg ( True , autopark_state ) )
self . assertNotEqual ( autopark_active , self . safety . get_controls_allowed ( ) )
# We should also start blocking all inactive/active openpilot msgs
self . assertNotEqual ( autopark_active , self . _tx ( self . _angle_cmd_msg ( 0 , False ) ) )
self . assertNotEqual ( autopark_active , self . _tx ( self . _angle_cmd_msg ( 0 , True ) ) )
self . assertNotEqual ( autopark_active , self . _tx ( self . _long_control_msg ( 0 , acc_state = self . acc_states [ " ACC_CANCEL_GENERIC_SILENT " ] ) ) )
self . assertNotEqual ( autopark_active or not self . LONGITUDINAL , self . _tx ( self . _long_control_msg ( 0 , acc_state = self . acc_states [ " ACC_ON " ] ) ) )
# Regain controls when Autopark disables
self . _rx ( self . _pcm_status_msg ( True , 0 ) )
self . assertTrue ( self . safety . get_controls_allowed ( ) )
self . assertTrue ( self . _tx ( self . _angle_cmd_msg ( 0 , False ) ) )
self . assertTrue ( self . _tx ( self . _angle_cmd_msg ( 0 , True ) ) )
self . assertTrue ( self . _tx ( self . _long_control_msg ( 0 , acc_state = self . acc_states [ " ACC_CANCEL_GENERIC_SILENT " ] ) ) )
self . assertEqual ( self . LONGITUDINAL , self . _tx ( self . _long_control_msg ( 0 , acc_state = self . acc_states [ " ACC_ON " ] ) ) )
def test_steering_control_type ( self ) :
# Only angle control is allowed (no LANE_KEEP_ASSIST or EMERGENCY_LANE_KEEP)
self . safety . set_controls_allowed ( True )
for steer_control_type in range ( 4 ) :
should_tx = steer_control_type in ( self . steer_control_types [ " NONE " ] ,
self . steer_control_types [ " ANGLE_CONTROL " ] )
self . assertEqual ( should_tx , self . _tx ( self . _angle_cmd_msg ( 0 , state = steer_control_type ) ) )
def test_stock_lkas_passthrough ( self ) :
# TODO: make these generic passthrough tests
no_lkas_msg = self . _angle_cmd_msg ( 0 , state = False )
no_lkas_msg_cam = self . _angle_cmd_msg ( 0 , state = True , bus = 2 )
lkas_msg_cam = self . _angle_cmd_msg ( 0 , state = self . steer_control_types [ ' LANE_KEEP_ASSIST ' ] , bus = 2 )
# stock system sends no LKAS -> no forwarding, and OP is allowed to TX
self . assertEqual ( 1 , self . _rx ( no_lkas_msg_cam ) )
self . assertEqual ( - 1 , self . safety . safety_fwd_hook ( 2 , no_lkas_msg_cam . addr ) )
self . assertTrue ( self . _tx ( no_lkas_msg ) )
# stock system sends LKAS -> forwarding, and OP is not allowed to TX
self . assertEqual ( 1 , self . _rx ( lkas_msg_cam ) )
self . assertEqual ( 0 , self . safety . safety_fwd_hook ( 2 , lkas_msg_cam . addr ) )
self . assertFalse ( self . _tx ( no_lkas_msg ) )
def test_angle_cmd_when_enabled ( self ) :
# We properly test lateral acceleration and jerk below
pass
def test_lateral_accel_limit ( self ) :
for speed in np . linspace ( 0 , 40 , 100 ) :
# match DI_vehicleSpeed rounding on CAN
speed = round_speed ( away_round ( speed / 0.08 * 3.6 ) * 0.08 / 3.6 )
for sign in ( - 1 , 1 ) :
self . safety . set_controls_allowed ( True )
self . _reset_speed_measurement ( speed + 1 ) # safety fudges the speed
# angle signal can't represent 0, so it biases one unit down
angle_unit_offset = - 1 if sign == - 1 else 0
# at limit (safety tolerance adds 1)
max_angle = round_angle ( get_max_angle ( speed , self . VM ) , angle_unit_offset + 1 ) * sign
max_angle = np . clip ( max_angle , - self . STEER_ANGLE_MAX , self . STEER_ANGLE_MAX )
self . _tx ( self . _angle_cmd_msg ( max_angle , True ) )
self . assertTrue ( self . _tx ( self . _angle_cmd_msg ( max_angle , True ) ) )
# 1 unit above limit
max_angle_raw = round_angle ( get_max_angle ( speed , self . VM ) , angle_unit_offset + 2 ) * sign
max_angle = np . clip ( max_angle_raw , - self . STEER_ANGLE_MAX , self . STEER_ANGLE_MAX )
self . _tx ( self . _angle_cmd_msg ( max_angle , True ) )
# at low speeds max angle is above 360, so adding 1 has no effect
should_tx = abs ( max_angle_raw ) > = self . STEER_ANGLE_MAX
self . assertEqual ( should_tx , self . _tx ( self . _angle_cmd_msg ( max_angle , True ) ) )
def test_lateral_jerk_limit ( self ) :
for speed in np . linspace ( 0 , 40 , 100 ) :
# match DI_vehicleSpeed rounding on CAN
speed = round_speed ( away_round ( speed / 0.08 * 3.6 ) * 0.08 / 3.6 )
for sign in ( - 1 , 1 ) : # (-1, 1):
self . safety . set_controls_allowed ( True )
self . _reset_speed_measurement ( speed + 1 ) # safety fudges the speed
self . _tx ( self . _angle_cmd_msg ( 0 , True ) )
# angle signal can't represent 0, so it biases one unit down
angle_unit_offset = 1 if sign == - 1 else 0
# Stay within limits
# Up
max_angle_delta = round_angle ( get_max_angle_delta ( speed , self . VM ) , angle_unit_offset ) * sign
self . assertTrue ( self . _tx ( self . _angle_cmd_msg ( max_angle_delta , True ) ) )
# Don't change
self . assertTrue ( self . _tx ( self . _angle_cmd_msg ( max_angle_delta , True ) ) )
# Down
self . assertTrue ( self . _tx ( self . _angle_cmd_msg ( 0 , True ) ) )
# Inject too high rates
# Up
max_angle_delta = round_angle ( get_max_angle_delta ( speed , self . VM ) , angle_unit_offset + 1 ) * sign
self . assertFalse ( self . _tx ( self . _angle_cmd_msg ( max_angle_delta , True ) ) )
# Don't change
self . assertTrue ( self . _tx ( self . _angle_cmd_msg ( max_angle_delta , True ) ) )
# Down
self . assertFalse ( self . _tx ( self . _angle_cmd_msg ( 0 , True ) ) )
# Recover
self . assertTrue ( self . _tx ( self . _angle_cmd_msg ( 0 , True ) ) )
class TestTeslaStockSafety ( TestTeslaSafetyBase ) :
LONGITUDINAL = False
def setUp ( self ) :
super ( ) . setUp ( )
self . safety = libsafety_py . libsafety
self . safety . set_safety_hooks ( CarParams . SafetyModel . tesla , 0 )
self . safety . init_tests ( )
def test_cancel ( self ) :
for acc_state in range ( 16 ) :
self . safety . set_controls_allowed ( True )
should_tx = acc_state == self . acc_states [ " ACC_CANCEL_GENERIC_SILENT " ]
self . assertFalse ( self . _tx ( self . _long_control_msg ( 0 , acc_state = acc_state , accel_limits = ( self . MIN_ACCEL , self . MAX_ACCEL ) ) ) )
self . assertEqual ( should_tx , self . _tx ( self . _long_control_msg ( 0 , acc_state = acc_state ) ) )
def test_no_aeb ( self ) :
for aeb_event in range ( 4 ) :
should_tx = aeb_event == 0
ret = self . _tx ( self . _long_control_msg ( 10 , acc_state = self . acc_states [ " ACC_CANCEL_GENERIC_SILENT " ] , aeb_event = aeb_event ) )
self . assertEqual ( ret , should_tx )
def test_stock_aeb_no_cancel ( self ) :
# No passthrough logic since we always forward DAS_control,
# but ensure we can't send cancel cmd while stock AEB is active
no_aeb_msg = self . _long_control_msg ( 10 , acc_state = self . acc_states [ " ACC_CANCEL_GENERIC_SILENT " ] , aeb_event = 0 )
no_aeb_msg_cam = self . _long_control_msg ( 10 , aeb_event = 0 , bus = 2 )
aeb_msg_cam = self . _long_control_msg ( 10 , aeb_event = 1 , bus = 2 )
# stock system sends no AEB -> no forwarding, and OP is allowed to TX
self . assertEqual ( 1 , self . _rx ( no_aeb_msg_cam ) )
self . assertEqual ( 0 , self . safety . safety_fwd_hook ( 2 , no_aeb_msg_cam . addr ) )
self . assertTrue ( self . _tx ( no_aeb_msg ) )
# stock system sends AEB -> forwarding, and OP is not allowed to TX
self . assertEqual ( 1 , self . _rx ( aeb_msg_cam ) )
self . assertEqual ( 0 , self . safety . safety_fwd_hook ( 2 , aeb_msg_cam . addr ) )
self . assertFalse ( self . _tx ( no_aeb_msg ) )
class TestTeslaLongitudinalSafety ( TestTeslaSafetyBase ) :
RELAY_MALFUNCTION_ADDRS = { 0 : ( MSG_DAS_steeringControl , MSG_APS_eacMonitor , MSG_DAS_Control ) }
FWD_BLACKLISTED_ADDRS = { 2 : [ MSG_DAS_steeringControl , MSG_APS_eacMonitor , MSG_DAS_Control ] }
def setUp ( self ) :
super ( ) . setUp ( )
self . safety = libsafety_py . libsafety
self . safety . set_safety_hooks ( CarParams . SafetyModel . tesla , TeslaSafetyFlags . LONG_CONTROL )
self . safety . init_tests ( )
def test_no_aeb ( self ) :
for aeb_event in range ( 4 ) :
self . assertEqual ( self . _tx ( self . _long_control_msg ( 10 , aeb_event = aeb_event ) ) , aeb_event == 0 )
def test_stock_aeb_passthrough ( self ) :
no_aeb_msg = self . _long_control_msg ( 10 , aeb_event = 0 )
no_aeb_msg_cam = self . _long_control_msg ( 10 , aeb_event = 0 , bus = 2 )
aeb_msg_cam = self . _long_control_msg ( 10 , aeb_event = 1 , bus = 2 )
# stock system sends no AEB -> no forwarding, and OP is allowed to TX
self . assertEqual ( 1 , self . _rx ( no_aeb_msg_cam ) )
self . assertEqual ( - 1 , self . safety . safety_fwd_hook ( 2 , no_aeb_msg_cam . addr ) )
self . assertTrue ( self . _tx ( no_aeb_msg ) )
# stock system sends AEB -> forwarding, and OP is not allowed to TX
self . assertEqual ( 1 , self . _rx ( aeb_msg_cam ) )
self . assertEqual ( 0 , self . safety . safety_fwd_hook ( 2 , aeb_msg_cam . addr ) )
self . assertFalse ( self . _tx ( no_aeb_msg ) )
def test_prevent_reverse ( self ) :
# Note: Tesla can reverse while at a standstill if both accel_min and accel_max are negative.
self . safety . set_controls_allowed ( True )
# accel_min and accel_max are positive
self . assertTrue ( self . _tx ( self . _long_control_msg ( set_speed = 10 , accel_limits = ( 1.1 , 0.8 ) ) ) )
self . assertTrue ( self . _tx ( self . _long_control_msg ( set_speed = 0 , accel_limits = ( 1.1 , 0.8 ) ) ) )
# accel_min and accel_max are both zero
self . assertTrue ( self . _tx ( self . _long_control_msg ( set_speed = 10 , accel_limits = ( 0 , 0 ) ) ) )
self . assertTrue ( self . _tx ( self . _long_control_msg ( set_speed = 0 , accel_limits = ( 0 , 0 ) ) ) )
# accel_min and accel_max have opposing signs
self . assertTrue ( self . _tx ( self . _long_control_msg ( set_speed = 10 , accel_limits = ( - 0.8 , 1.3 ) ) ) )
self . assertTrue ( self . _tx ( self . _long_control_msg ( set_speed = 0 , accel_limits = ( 0.8 , - 1.3 ) ) ) )
self . assertTrue ( self . _tx ( self . _long_control_msg ( set_speed = 0 , accel_limits = ( 0 , - 1.3 ) ) ) )
# accel_min and accel_max are negative
self . assertFalse ( self . _tx ( self . _long_control_msg ( set_speed = 10 , accel_limits = ( - 1.1 , - 0.6 ) ) ) )
self . assertFalse ( self . _tx ( self . _long_control_msg ( set_speed = 0 , accel_limits = ( - 0.6 , - 1.1 ) ) ) )
self . assertFalse ( self . _tx ( self . _long_control_msg ( set_speed = 0 , accel_limits = ( - 0.1 , - 0.1 ) ) ) )
if __name__ == " __main__ " :
unittest . main ( )