Skip to content

test_models: test carcontrollers generate sendable actuation messages #34720

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 13 commits into
base: master
Choose a base branch
from
176 changes: 140 additions & 36 deletions selfdrive/car/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,16 @@
from opendbc.car.can_definitions import CanData
from opendbc.car.car_helpers import FRAME_FINGERPRINT, interfaces
from opendbc.car.fingerprints import MIGRATION
from opendbc.car.toyota.values import CAR as TOYOTA
from opendbc.car.ford.values import CAR as FORD
from opendbc.car.honda.values import CAR as HONDA, HondaFlags
from opendbc.car.hyundai.values import CAR as HYUNDAI
from opendbc.car.subaru.values import CAR as SUBARU
from opendbc.car.structs import car
from opendbc.car.tests.routes import non_tested_cars, routes, CarTestRoute
from opendbc.car.values import Platform, PLATFORMS
from opendbc.safety.tests.libsafety import libsafety_py
from opendbc.safety.tests.safety_replay.helpers import package_can_msg, init_segment
from openpilot.common.basedir import BASEDIR
from openpilot.selfdrive.pandad import can_capnp_to_list
from openpilot.selfdrive.test.helpers import read_segment_list
Expand Down Expand Up @@ -80,7 +85,8 @@ def get_testing_data_from_logreader(cls, lr):
cls.car_safety_mode_frame = None
cls.fingerprint = gen_empty_fingerprint()
experimental_long = False
for msg in lr:
cls.msgs = list(lr)
for msg in cls.msgs:
if msg.which() == "can":
can_msgs.append(msg)
if len(can_msgs) <= FRAME_FINGERPRINT:
Expand Down Expand Up @@ -174,6 +180,7 @@ def setUp(self):
cfg = self.CP.safetyConfigs[-1]
set_status = self.safety.set_safety_hooks(cfg.safetyModel.raw, cfg.safetyParam)
self.assertEqual(0, set_status, f"failed to set safetyModel {cfg}")
self.safety.set_alternative_experience(self.CP.alternativeExperience)
self.safety.init_tests()

def test_car_params(self):
Expand Down Expand Up @@ -264,41 +271,42 @@ def test_panda_safety_rx_checks(self):
self.safety.safety_tick_current_safety_config()
self.assertFalse(self.safety.safety_config_valid())

def test_panda_safety_tx_cases(self, data=None):
"""Asserts we can tx common messages"""
if self.CP.notCar:
self.skipTest("Skipping test for notCar")

def test_car_controller(car_control):
now_nanos = 0
msgs_sent = 0
CI = self.CarInterface(self.CP, self.CarController, self.CarState)
for _ in range(round(10.0 / DT_CTRL)): # make sure we hit the slowest messages
CI.update([])
_, sendcan = CI.apply(car_control, now_nanos)

now_nanos += DT_CTRL * 1e9
msgs_sent += len(sendcan)
for addr, dat, bus in sendcan:
to_send = libsafety_py.make_CANPacket(addr, bus % 4, dat)
self.assertTrue(self.safety.safety_tx_hook(to_send), (addr, dat, bus))

# Make sure we attempted to send messages
self.assertGreater(msgs_sent, 50)

# Make sure we can send all messages while inactive
CC = structs.CarControl()
test_car_controller(CC.as_reader())

# Test cancel + general messages (controls_allowed=False & cruise_engaged=True)
self.safety.set_cruise_engaged_prev(True)
CC = structs.CarControl(cruiseControl=structs.CarControl.CruiseControl(cancel=True))
test_car_controller(CC.as_reader())

# Test resume + general messages (controls_allowed=True & cruise_engaged=True)
self.safety.set_controls_allowed(True)
CC = structs.CarControl(cruiseControl=structs.CarControl.CruiseControl(resume=True))
test_car_controller(CC.as_reader())
# def test_panda_safety_tx_cases(self, data=None):
# return
# """Asserts we can tx common messages"""
# if self.CP.notCar:
# self.skipTest("Skipping test for notCar")
#
# def test_car_controller(car_control):
# now_nanos = 0
# msgs_sent = 0
# CI = self.CarInterface(self.CP, self.CarController, self.CarState)
# for _ in range(round(10.0 / DT_CTRL)): # make sure we hit the slowest messages
# CI.update([])
# _, sendcan = CI.apply(car_control, now_nanos)
#
# now_nanos += DT_CTRL * 1e9
# msgs_sent += len(sendcan)
# for addr, dat, bus in sendcan:
# to_send = libsafety_py.make_CANPacket(addr, bus % 4, dat)
# self.assertTrue(self.safety.safety_tx_hook(to_send), (addr, dat, bus))
#
# # Make sure we attempted to send messages
# self.assertGreater(msgs_sent, 50)
#
# # Make sure we can send all messages while inactive
# CC = structs.CarControl()
# test_car_controller(CC.as_reader())
#
# # Test cancel + general messages (controls_allowed=False & cruise_engaged=True)
# self.safety.set_cruise_engaged_prev(True)
# CC = structs.CarControl(cruiseControl=structs.CarControl.CruiseControl(cancel=True))
# test_car_controller(CC.as_reader())
#
# # Test resume + general messages (controls_allowed=True & cruise_engaged=True)
# self.safety.set_controls_allowed(True)
# CC = structs.CarControl(cruiseControl=structs.CarControl.CruiseControl(resume=True))
# test_car_controller(CC.as_reader())

# Skip stdout/stderr capture with pytest, causes elevated memory usage
@pytest.mark.nocapture
Expand Down Expand Up @@ -377,6 +385,102 @@ def test_panda_safety_carstate_fuzzy(self, data):
if self.safety.get_acc_main_on() != prev_panda_acc_main_on:
self.assertEqual(CS.cruiseState.available, self.safety.get_acc_main_on())

def test_panda_safety_tx(self):
"""
Assert that panda safety can send all messages, need to re-generate the messages using carcontroller
"""

if self.CP.dashcamOnly:
self.skipTest("no need to check panda safety for dashcamOnly")

if self.CP.carFingerprint in (
TOYOTA.TOYOTA_RAV4_PRIME, # what is going on
TOYOTA.TOYOTA_SIENNA_4TH_GEN, # what is going on
TOYOTA.TOYOTA_AVALON_2019, # real relay malfunction
TOYOTA.TOYOTA_RAV4, # real relay malfunction
HONDA.HONDA_ACCORD, # real relay malfunction
HONDA.ACURA_RDX_3G, # real relay malfunction
FORD.FORD_EXPLORER_MK6, # cameron is sending path offset/angle/etc
HYUNDAI.KIA_OPTIMA_G4_FL, # real relay malfunction
SUBARU.SUBARU_IMPREZA, # real relay malfunction
HYUNDAI.HYUNDAI_ELANTRA_GT_I30, # real relay malfunction
TOYOTA.TOYOTA_CAMRY, # no alternative experience in logs
):
self.skipTest("Skipping for known failures/bad routes")

# for idx, can in enumerate(self.can_msgs):
# CS = self.CI.update(can_capnp_to_list((can.as_builder().to_bytes(), ))).as_reader()

# # TODO: use the CarState to get the vehicle speed to explicitly set
# i = 0
# for msg in self.msgs:
# if msg.which() == 'can':
# i += 1
# for can in msg.can:
# self.safety.safety_rx_hook(package_can_msg(can))
# if i > 20:
# break

# TODO: we should init on the generated sendcan messages
init_segment(self.safety, self.msgs, self.CP.safetyConfigs[-1].safetyModel, self.CP.safetyConfigs[-1].safetyParam)

print('long', self.CP.openpilotLongitudinalControl)

seen_can = False
t = None
CS = None
blocked = Counter()
blocked_frame = 0
# FIXME: online there is usually a 1 frame delay from carState to carControl
# so messages will be blocked when gas overriding, disabling, etc.

# TODO: use safety replay
for idx, msg in enumerate(self.msgs):
msg.which()
if msg.which() == 'can':
if t is None:
t = msg.logMonoTime
self.safety.set_timer((msg.logMonoTime // 1000) % 0xFFFFFFFF)
seen_can = True
to_push = can_capnp_to_list([msg.as_builder().to_bytes()])
CS = self.CI.update(to_push)
for canmsg in filter(lambda m: m.src < 128, msg.can):
to_send = libsafety_py.make_CANPacket(canmsg.address, canmsg.src % 4, canmsg.dat)
self.assertTrue(self.safety.safety_rx_hook(to_send))

elif msg.which() == 'carControl':
# TODO: remove, these are good failures
if not seen_can:
continue

last_actuators_output, can_sends = self.CI.apply(msg.carControl, msg.logMonoTime)
print('carControl latActive', msg.carControl.latActive, 'longActive', msg.carControl.longActive, 'enabled', msg.carControl.enabled,
'cancel', msg.carControl.cruiseControl.cancel, 'resume', msg.carControl.cruiseControl.resume)
print('carOutput torque', last_actuators_output.torque, 'accel', last_actuators_output.accel)
print('driver torque', CS.steeringTorque, 'gasPressed', CS.gasPressed)
print('safety brakePressed', self.safety.get_brake_pressed_prev(), 'gasPressed', self.safety.get_gas_pressed_prev(), 'cruiseEngaged', self.safety.get_cruise_engaged_prev())
print('safety controls_allowed', self.safety.get_controls_allowed())
print()
print(can_sends)
for can_send in can_sends:
# TODO: remove set_controls_allowed to test more than actuation limits
self.safety.set_controls_allowed(True)
to_send = libsafety_py.make_CANPacket(can_send[0], can_send[2] % 4, can_send[1])
tx = self.safety.safety_tx_hook(to_send)
if not tx:
print('BLOCKED!', can_send[0])
blocked[can_send[0]] += 1
raise Exception(can_send[0])
blocked_frame = idx
# if blocked > 0 and idx != blocked_frame:
# print('WOOOOOOOOO')
# raise Exception
# self.assertTrue(tx, (f"failed to send at {(msg.logMonoTime - t) / 1e9:.2f}s", can_send[0], can_send[1], can_send[2]))

# TODO: assert no more blocked messages than the number of cancels or overrides
print('total blocked', blocked)
assert not any(b > 5 for b in blocked.values()), f"blocked messages: {blocked}"

def test_panda_safety_carstate(self):
"""
Assert that panda safety matches openpilot's carState
Expand Down
Loading