#!/usr/bin/env python3
import unittest
import numpy as np
from opendbc . car . structs import CarParams
from opendbc . safety . tests . libsafety import libsafety_py
import opendbc . safety . tests . common as common
from opendbc . safety . tests . common import CANPackerPanda
from opendbc . car . rivian . values import RivianSafetyFlags
from opendbc . car . rivian . riviancan import checksum as _checksum
def checksum ( msg ) :
addr , dat , bus = msg
ret = bytearray ( dat )
# ESP_Status
if addr == 0x208 :
ret [ 0 ] = _checksum ( ret [ 1 : ] , 0x1D , 0xB1 )
elif addr == 0x150 :
ret [ 0 ] = _checksum ( ret [ 1 : ] , 0x1D , 0x9A )
return addr , ret , bus
class TestRivianSafetyBase ( common . PandaCarSafetyTest , common . DriverTorqueSteeringSafetyTest , common . LongitudinalAccelSafetyTest ,
common . VehicleSpeedSafetyTest ) :
TX_MSGS = [ [ 0x120 , 0 ] , [ 0x321 , 2 ] , [ 0x162 , 2 ] ]
RELAY_MALFUNCTION_ADDRS = { 0 : ( 0x120 , ) , 2 : ( 0x321 , 0x162 ) }
FWD_BLACKLISTED_ADDRS = { 0 : [ 0x321 , 0x162 ] , 2 : [ 0x120 ] }
MAX_TORQUE_LOOKUP = [ 9 , 17 ] , [ 350 , 250 ]
DYNAMIC_MAX_TORQUE = True
MAX_RATE_UP = 3
MAX_RATE_DOWN = 5
MAX_RT_DELTA = 125
DRIVER_TORQUE_ALLOWANCE = 100
DRIVER_TORQUE_FACTOR = 2
# Max allowed delta between car speeds
MAX_SPEED_DELTA = 2.0 # m/s
cnt_speed = 0
cnt_speed_2 = 0
def _torque_driver_msg ( self , torque ) :
values = { " EPAS_TorsionBarTorque " : torque / 100.0 }
return self . packer . make_can_msg_panda ( " EPAS_SystemStatus " , 0 , values )
def _torque_cmd_msg ( self , torque , steer_req = 1 ) :
values = { " ACM_lkaStrToqReq " : torque , " ACM_lkaActToi " : steer_req }
return self . packer . make_can_msg_panda ( " ACM_lkaHbaCmd " , 0 , values )
def _speed_msg ( self , speed , quality_flag = True ) :
values = { " ESP_Vehicle_Speed " : speed * 3.6 , " ESP_Status_Counter " : self . cnt_speed % 15 ,
" ESP_Vehicle_Speed_Q " : 1 if quality_flag else 0 }
self . __class__ . cnt_speed + = 1
return self . packer . make_can_msg_panda ( " ESP_Status " , 0 , values , fix_checksum = checksum )
def _speed_msg_2 ( self , speed , quality_flag = True ) :
return self . _user_gas_msg ( 0 , speed , quality_flag )
def _user_brake_msg ( self , brake ) :
values = { " iBESP2_BrakePedalApplied " : brake }
return self . packer . make_can_msg_panda ( " iBESP2 " , 0 , values )
def _user_gas_msg ( self , gas , speed = 0 , quality_flag = True ) :
values = { " VDM_AcceleratorPedalPosition " : gas , " VDM_VehicleSpeed " : speed * 3.6 ,
" VDM_PropStatus_Counter " : self . cnt_speed_2 % 15 , " VDM_VehicleSpeedQ " : 1 if quality_flag else 0 }
self . __class__ . cnt_speed_2 + = 1
return self . packer . make_can_msg_panda ( " VDM_PropStatus " , 0 , values , fix_checksum = checksum )
def _pcm_status_msg ( self , enable ) :
values = { " ACM_FeatureStatus " : enable , " ACM_Unkown1 " : 1 }
return self . packer . make_can_msg_panda ( " ACM_Status " , 2 , values )
def _accel_msg ( self , accel : float ) :
values = { " ACM_AccelerationRequest " : accel }
return self . packer . make_can_msg_panda ( " ACM_longitudinalRequest " , 0 , values )
def test_wheel_touch ( self ) :
# For hiding hold wheel alert on engage
for controls_allowed in ( True , False ) :
self . safety . set_controls_allowed ( controls_allowed )
values = {
" SCCM_WheelTouch_HandsOn " : 1 if controls_allowed else 0 ,
" SCCM_WheelTouch_CapacitiveValue " : 100 if controls_allowed else 0 ,
" SETME_X52 " : 100 ,
}
self . assertTrue ( self . _tx ( self . packer . make_can_msg_panda ( " SCCM_WheelTouch " , 2 , values ) ) )
def test_rx_hook ( self ) :
# checksum, counter, and quality flag checks
for quality_flag in ( True , False ) :
for msg in ( " speed " , " speed_2 " ) :
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 )
self . assertEqual ( quality_flag , self . _rx ( to_push ) )
self . assertEqual ( quality_flag , self . safety . get_controls_allowed ( ) )
# Mess with checksum to make it fail
to_push [ 0 ] . data [ 0 ] = 0xff
self . assertFalse ( self . _rx ( to_push ) )
self . assertFalse ( self . safety . get_controls_allowed ( ) )
def test_rx_hook_speed_mismatch ( self ) :
# TODO: this can be a common test w/ Ford
# Rivian has a dynamic max torque limit based on speed, 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 )
class TestRivianStockSafety ( TestRivianSafetyBase ) :
LONGITUDINAL = False
def setUp ( self ) :
self . packer = CANPackerPanda ( " rivian_primary_actuator " )
self . safety = libsafety_py . libsafety
self . safety . set_safety_hooks ( CarParams . SafetyModel . rivian , 0 )
self . safety . init_tests ( )
def test_adas_status ( self ) :
# For canceling stock ACC
for controls_allowed in ( True , False ) :
self . safety . set_controls_allowed ( controls_allowed )
for interface_status in range ( 4 ) :
values = { " VDM_AdasInterfaceStatus " : interface_status }
self . assertTrue ( self . _tx ( self . packer . make_can_msg_panda ( " VDM_AdasSts " , 2 , values ) ) )
class TestRivianLongitudinalSafety ( TestRivianSafetyBase ) :
TX_MSGS = [ [ 0x120 , 0 ] , [ 0x321 , 2 ] , [ 0x160 , 0 ] ]
RELAY_MALFUNCTION_ADDRS = { 0 : ( 0x120 , 0x160 ) , 2 : ( 0x321 , ) }
FWD_BLACKLISTED_ADDRS = { 0 : [ 0x321 ] , 2 : [ 0x120 , 0x160 ] }
def setUp ( self ) :
self . packer = CANPackerPanda ( " rivian_primary_actuator " )
self . safety = libsafety_py . libsafety
self . safety . set_safety_hooks ( CarParams . SafetyModel . rivian , RivianSafetyFlags . LONG_CONTROL )
self . safety . init_tests ( )
if __name__ == " __main__ " :
unittest . main ( )