# functions common among cars
import numpy as np
from dataclasses import dataclass , field
from enum import IntFlag , ReprEnum , StrEnum , EnumType , auto
from dataclasses import replace
from opendbc . car import structs , uds
from opendbc . car . can_definitions import CanData
from opendbc . car . docs_definitions import CarDocs , ExtraCarDocs
DT_CTRL = 0.01 # car state and control loop timestep (s)
# kg of standard extra cargo to count for drive, gas, etc...
STD_CARGO_KG = 136.
ACCELERATION_DUE_TO_GRAVITY = 9.81 # m/s^2
ButtonType = structs . CarState . ButtonEvent . Type
def apply_hysteresis ( val : float , val_steady : float , hyst_gap : float ) - > float :
if val > val_steady + hyst_gap :
val_steady = val - hyst_gap
elif val < val_steady - hyst_gap :
val_steady = val + hyst_gap
return val_steady
def create_button_events ( cur_btn : int , prev_btn : int , buttons_dict : dict [ int , structs . CarState . ButtonEvent . Type ] ,
unpressed_btn : int = 0 ) - > list [ structs . CarState . ButtonEvent ] :
events : list [ structs . CarState . ButtonEvent ] = [ ]
if cur_btn == prev_btn :
return events
# Add events for button presses, multiple when a button switches without going to unpressed
for pressed , btn in ( ( False , prev_btn ) , ( True , cur_btn ) ) :
if btn != unpressed_btn :
events . append ( structs . CarState . ButtonEvent ( pressed = pressed ,
type = buttons_dict . get ( btn , ButtonType . unknown ) ) )
return events
def gen_empty_fingerprint ( ) :
return { i : { } for i in range ( 8 ) }
# these params were derived for the Civic and used to calculate params for other cars
class VehicleDynamicsParams :
MASS = 1326. + STD_CARGO_KG
WHEELBASE = 2.70
CENTER_TO_FRONT = WHEELBASE * 0.4
CENTER_TO_REAR = WHEELBASE - CENTER_TO_FRONT
ROTATIONAL_INERTIA = 2500
TIRE_STIFFNESS_FRONT = 192150
TIRE_STIFFNESS_REAR = 202500
# TODO: get actual value, for now starting with reasonable value for
# civic and scaling by mass and wheelbase
def scale_rot_inertia ( mass , wheelbase ) :
return VehicleDynamicsParams . ROTATIONAL_INERTIA * mass * wheelbase * * 2 / ( VehicleDynamicsParams . MASS * VehicleDynamicsParams . WHEELBASE * * 2 )
# TODO: start from empirically derived lateral slip stiffness for the civic and scale by
# mass and CG position, so all cars will have approximately similar dyn behaviors
def scale_tire_stiffness ( mass , wheelbase , center_to_front , tire_stiffness_factor ) :
center_to_rear = wheelbase - center_to_front
tire_stiffness_front = ( VehicleDynamicsParams . TIRE_STIFFNESS_FRONT * tire_stiffness_factor ) * mass / VehicleDynamicsParams . MASS * \
( center_to_rear / wheelbase ) / ( VehicleDynamicsParams . CENTER_TO_REAR / VehicleDynamicsParams . WHEELBASE )
tire_stiffness_rear = ( VehicleDynamicsParams . TIRE_STIFFNESS_REAR * tire_stiffness_factor ) * mass / VehicleDynamicsParams . MASS * \
( center_to_front / wheelbase ) / ( VehicleDynamicsParams . CENTER_TO_FRONT / VehicleDynamicsParams . WHEELBASE )
return tire_stiffness_front , tire_stiffness_rear
DbcDict = dict [ StrEnum , str ]
class Bus ( StrEnum ) :
pt = auto ( )
cam = auto ( )
radar = auto ( )
adas = auto ( )
alt = auto ( )
body = auto ( )
chassis = auto ( )
loopback = auto ( )
main = auto ( )
party = auto ( )
ap_party = auto ( )
def rate_limit ( new_value , last_value , dw_step , up_step ) :
return float ( np . clip ( new_value , last_value + dw_step , last_value + up_step ) )
def make_tester_present_msg ( addr , bus , subaddr = None , suppress_response = False ) :
dat = [ 0x02 , uds . SERVICE_TYPE . TESTER_PRESENT ]
if subaddr is not None :
dat . insert ( 0 , subaddr )
dat . append ( 0x80 if suppress_response else 0x0 ) # sub-function
dat . extend ( [ 0x0 ] * ( 8 - len ( dat ) ) )
return CanData ( addr , bytes ( dat ) , bus )
def get_safety_config ( safety_model : structs . CarParams . SafetyModel , safety_param : int = None ) - > structs . CarParams . SafetyConfig :
ret = structs . CarParams . SafetyConfig ( )
ret . safetyModel = safety_model
if safety_param is not None :
ret . safetyParam = safety_param
return ret
class CanBusBase :
offset : int
def __init__ ( self , CP , fingerprint : dict [ int , dict [ int , int ] ] | None ) - > None :
if CP is None :
assert fingerprint is not None
num = max ( [ k for k , v in fingerprint . items ( ) if len ( v ) ] , default = 0 ) / / 4 + 1
else :
num = len ( CP . safetyConfigs )
self . offset = 4 * ( num - 1 )
class CanSignalRateCalculator :
"""
Calculates the instantaneous rate of a CAN signal by using the counter
variable and the known frequency of the CAN message that contains it .
"""
def __init__ ( self , frequency ) :
self . frequency = frequency
self . previous_counter = 0
self . previous_value = 0
self . rate = 0
def update ( self , current_value , current_counter ) :
if current_counter != self . previous_counter :
self . rate = ( current_value - self . previous_value ) * self . frequency
self . previous_counter = current_counter
self . previous_value = current_value
return self . rate
@dataclass ( frozen = True , kw_only = True )
class CarSpecs :
mass : float # kg, curb weight
wheelbase : float # meters
steerRatio : float
centerToFrontRatio : float = 0.5
minSteerSpeed : float = 0.0 # m/s
minEnableSpeed : float = - 1.0 # m/s
tireStiffnessFactor : float = 1.0
def override ( self , * * kwargs ) :
return replace ( self , * * kwargs )
class Freezable :
_frozen : bool = False
def freeze ( self ) :
if not self . _frozen :
self . _frozen = True
def __setattr__ ( self , * args , * * kwargs ) :
if self . _frozen :
raise Exception ( " cannot modify frozen object " )
super ( ) . __setattr__ ( * args , * * kwargs )
@dataclass ( order = True )
class PlatformConfigBase ( Freezable ) :
car_docs : list [ CarDocs ] | list [ ExtraCarDocs ]
specs : CarSpecs
dbc_dict : DbcDict
flags : int = 0
platform_str : str | None = None
def __hash__ ( self ) - > int :
return hash ( self . platform_str )
def override ( self , * * kwargs ) :
return replace ( self , * * kwargs )
def init ( self ) :
pass
def __post_init__ ( self ) :
self . init ( )
@dataclass ( order = True )
class PlatformConfig ( PlatformConfigBase ) :
car_docs : list [ CarDocs ]
specs : CarSpecs
dbc_dict : DbcDict
@dataclass ( order = True )
class ExtraPlatformConfig ( PlatformConfigBase ) :
car_docs : list [ ExtraCarDocs ]
specs : CarSpecs = CarSpecs ( mass = 0. , wheelbase = 0. , steerRatio = 0. )
dbc_dict : DbcDict = field ( default_factory = lambda : dict ( ) )
class PlatformsType ( EnumType ) :
def __new__ ( metacls , cls , bases , classdict , * , boundary = None , _simple = False , * * kwds ) :
for key in classdict . _member_names . keys ( ) :
cfg : PlatformConfig = classdict [ key ]
cfg . platform_str = key
cfg . freeze ( )
return super ( ) . __new__ ( metacls , cls , bases , classdict , boundary = boundary , _simple = _simple , * * kwds )
class Platforms ( str , ReprEnum , metaclass = PlatformsType ) :
config : PlatformConfigBase
def __new__ ( cls , platform_config : PlatformConfig ) :
member = str . __new__ ( cls , platform_config . platform_str )
member . config = platform_config
member . _value_ = platform_config . platform_str
return member
def __repr__ ( self ) :
return f " < { self . __class__ . __name__ } . { self . name } > "
@classmethod
def create_dbc_map ( cls ) - > dict [ str , DbcDict ] :
return { p : p . config . dbc_dict for p in cls }
@classmethod
def with_flags ( cls , flags : IntFlag ) - > set [ ' Platforms ' ] :
return { p for p in cls if p . config . flags & flags }