pull/26226/head
Shane Smiskol 2 years ago
parent aeaafabc21
commit 3b7f740dd4
  1. 7
      selfdrive/car/car_helpers.py
  2. 358
      selfdrive/car/tests/test_models.py
  3. 5
      tools/lib/logreader.py

@ -13,6 +13,8 @@ from system.swaglog import cloudlog
import cereal.messaging as messaging import cereal.messaging as messaging
from selfdrive.car import gen_empty_fingerprint from selfdrive.car import gen_empty_fingerprint
FRAME_FINGERPRINT = 100 # 1s
EventName = car.CarEvent.EventName EventName = car.CarEvent.EventName
@ -126,7 +128,6 @@ def fingerprint(logcan, sendcan, num_pandas):
finger = gen_empty_fingerprint() finger = gen_empty_fingerprint()
candidate_cars = {i: all_legacy_fingerprint_cars() for i in [0, 1]} # attempt fingerprint on both bus 0 and 1 candidate_cars = {i: all_legacy_fingerprint_cars() for i in [0, 1]} # attempt fingerprint on both bus 0 and 1
frame = 0 frame = 0
frame_fingerprint = 100 # 1s
car_fingerprint = None car_fingerprint = None
done = False done = False
@ -152,12 +153,12 @@ def fingerprint(logcan, sendcan, num_pandas):
# if we only have one car choice and the time since we got our first # if we only have one car choice and the time since we got our first
# message has elapsed, exit # message has elapsed, exit
for b in candidate_cars: for b in candidate_cars:
if len(candidate_cars[b]) == 1 and frame > frame_fingerprint: if len(candidate_cars[b]) == 1 and frame > FRAME_FINGERPRINT:
# fingerprint done # fingerprint done
car_fingerprint = candidate_cars[b][0] car_fingerprint = candidate_cars[b][0]
# bail if no cars left or we've been waiting for more than 2s # bail if no cars left or we've been waiting for more than 2s
failed = (all(len(cc) == 0 for cc in candidate_cars.values()) and frame > frame_fingerprint) or frame > 200 failed = (all(len(cc) == 0 for cc in candidate_cars.values()) and frame > FRAME_FINGERPRINT) or frame > 200
succeeded = car_fingerprint is not None succeeded = car_fingerprint is not None
done = failed or succeeded done = failed or succeeded

@ -2,6 +2,7 @@
# pylint: disable=E1101 # pylint: disable=E1101
import os import os
import importlib import importlib
import time
import unittest import unittest
from collections import defaultdict, Counter from collections import defaultdict, Counter
from typing import List, Optional, Tuple from typing import List, Optional, Tuple
@ -11,7 +12,7 @@ from cereal import log, car
from common.basedir import BASEDIR from common.basedir import BASEDIR
from common.realtime import DT_CTRL from common.realtime import DT_CTRL
from selfdrive.car.fingerprints import all_known_cars from selfdrive.car.fingerprints import all_known_cars
from selfdrive.car.car_helpers import interfaces from selfdrive.car.car_helpers import FRAME_FINGERPRINT, interfaces
from selfdrive.car.gm.values import CAR as GM from selfdrive.car.gm.values import CAR as GM
from selfdrive.car.honda.values import CAR as HONDA, HONDA_BOSCH from selfdrive.car.honda.values import CAR as HONDA, HONDA_BOSCH
from selfdrive.car.hyundai.values import CAR as HYUNDAI from selfdrive.car.hyundai.values import CAR as HYUNDAI
@ -19,6 +20,7 @@ from selfdrive.car.tests.routes import non_tested_cars, routes, CarTestRoute
from selfdrive.test.openpilotci import get_url from selfdrive.test.openpilotci import get_url
from tools.lib.logreader import LogReader from tools.lib.logreader import LogReader
from tools.lib.route import Route, SegmentName, RouteName from tools.lib.route import Route, SegmentName, RouteName
from selfdrive.car import gen_empty_fingerprint
from panda.tests.libpanda import libpanda_py from panda.tests.libpanda import libpanda_py
@ -101,15 +103,22 @@ class TestCarModelBase(unittest.TestCase):
except Exception: except Exception:
continue continue
t = time.perf_counter()
car_fw = [] car_fw = []
can_msgs = [] can_msgs = []
fingerprint = defaultdict(dict) fingerprint = defaultdict(dict)
for msg in lr: start_time = None
# fingerprint = gen_empty_fingerprint()
for idx, msg in enumerate(lr):
if msg.which() == "can": if msg.which() == "can":
# gather fingerprint for 2s
can_msgs.append(msg)
if idx > FRAME_FINGERPRINT * 2:
continue
for m in msg.can: for m in msg.can:
if m.src < 64: if m.src < 64:
fingerprint[m.src][m.address] = len(m.dat) fingerprint[m.src][m.address] = len(m.dat)
can_msgs.append(msg)
elif msg.which() == "carParams": elif msg.which() == "carParams":
car_fw = msg.carParams.carFw car_fw = msg.carParams.carFw
if msg.carParams.openpilotLongitudinalControl: if msg.carParams.openpilotLongitudinalControl:
@ -118,6 +127,7 @@ class TestCarModelBase(unittest.TestCase):
cls.car_model = msg.carParams.carFingerprint cls.car_model = msg.carParams.carFingerprint
if len(can_msgs) > int(50 / DT_CTRL): if len(can_msgs) > int(50 / DT_CTRL):
print('can_msgs', time.perf_counter() - t)
break break
else: else:
raise Exception(f"Route: {repr(cls.test_route.route)} with segments: {test_segs} not found or no CAN msgs found. Is it uploaded?") raise Exception(f"Route: {repr(cls.test_route.route)} with segments: {test_segs} not found or no CAN msgs found. Is it uploaded?")
@ -163,177 +173,177 @@ class TestCarModelBase(unittest.TestCase):
else: else:
raise Exception("unknown tuning") raise Exception("unknown tuning")
def test_car_interface(self): # def test_car_interface(self):
# TODO: also check for checksum violations from can parser # # TODO: also check for checksum violations from can parser
can_invalid_cnt = 0 # can_invalid_cnt = 0
can_valid = False # can_valid = False
CC = car.CarControl.new_message() # CC = car.CarControl.new_message()
#
for i, msg in enumerate(self.can_msgs): # for i, msg in enumerate(self.can_msgs):
CS = self.CI.update(CC, (msg.as_builder().to_bytes(),)) # CS = self.CI.update(CC, (msg.as_builder().to_bytes(),))
self.CI.apply(CC, msg.logMonoTime) # self.CI.apply(CC, msg.logMonoTime)
#
if CS.canValid: # if CS.canValid:
can_valid = True # can_valid = True
#
# wait max of 2s for low frequency msgs to be seen # # wait max of 2s for low frequency msgs to be seen
if i > 200 or can_valid: # if i > 200 or can_valid:
can_invalid_cnt += not CS.canValid # can_invalid_cnt += not CS.canValid
#
self.assertEqual(can_invalid_cnt, 0) # self.assertEqual(can_invalid_cnt, 0)
#
def test_radar_interface(self): # def test_radar_interface(self):
os.environ['NO_RADAR_SLEEP'] = "1" # os.environ['NO_RADAR_SLEEP'] = "1"
RadarInterface = importlib.import_module(f'selfdrive.car.{self.CP.carName}.radar_interface').RadarInterface # RadarInterface = importlib.import_module(f'selfdrive.car.{self.CP.carName}.radar_interface').RadarInterface
RI = RadarInterface(self.CP) # RI = RadarInterface(self.CP)
assert RI # assert RI
#
error_cnt = 0 # error_cnt = 0
for i, msg in enumerate(self.can_msgs): # for i, msg in enumerate(self.can_msgs):
rr = RI.update((msg.as_builder().to_bytes(),)) # rr = RI.update((msg.as_builder().to_bytes(),))
if rr is not None and i > 50: # if rr is not None and i > 50:
error_cnt += car.RadarData.Error.canError in rr.errors # error_cnt += car.RadarData.Error.canError in rr.errors
self.assertEqual(error_cnt, 0) # self.assertEqual(error_cnt, 0)
#
def test_panda_safety_rx_valid(self): # def test_panda_safety_rx_valid(self):
if self.CP.dashcamOnly: # if self.CP.dashcamOnly:
self.skipTest("no need to check panda safety for dashcamOnly") # self.skipTest("no need to check panda safety for dashcamOnly")
#
start_ts = self.can_msgs[0].logMonoTime # start_ts = self.can_msgs[0].logMonoTime
#
failed_addrs = Counter() # failed_addrs = Counter()
for can in self.can_msgs: # for can in self.can_msgs:
# update panda timer # # update panda timer
t = (can.logMonoTime - start_ts) / 1e3 # t = (can.logMonoTime - start_ts) / 1e3
self.safety.set_timer(int(t)) # self.safety.set_timer(int(t))
#
# run all msgs through the safety RX hook # # run all msgs through the safety RX hook
for msg in can.can: # for msg in can.can:
if msg.src >= 64: # if msg.src >= 64:
continue # continue
#
to_send = libpanda_py.make_CANPacket(msg.address, msg.src % 4, msg.dat) # to_send = libpanda_py.make_CANPacket(msg.address, msg.src % 4, msg.dat)
if self.safety.safety_rx_hook(to_send) != 1: # if self.safety.safety_rx_hook(to_send) != 1:
failed_addrs[hex(msg.address)] += 1 # failed_addrs[hex(msg.address)] += 1
#
# ensure all msgs defined in the addr checks are valid # # ensure all msgs defined in the addr checks are valid
if self.car_model not in ignore_addr_checks_valid: # if self.car_model not in ignore_addr_checks_valid:
self.safety.safety_tick_current_rx_checks() # self.safety.safety_tick_current_rx_checks()
if t > 1e6: # if t > 1e6:
self.assertTrue(self.safety.addr_checks_valid()) # self.assertTrue(self.safety.addr_checks_valid())
self.assertFalse(len(failed_addrs), f"panda safety RX check failed: {failed_addrs}") # self.assertFalse(len(failed_addrs), f"panda safety RX check failed: {failed_addrs}")
def test_panda_safety_tx_cases(self, data=None): # def test_panda_safety_tx_cases(self, data=None):
"""Asserts we can tx common messages""" # """Asserts we can tx common messages"""
if self.CP.notCar: # if self.CP.notCar:
self.skipTest("Skipping test for notCar") # self.skipTest("Skipping test for notCar")
#
def test_car_controller(car_control): # def test_car_controller(car_control):
now_nanos = 0 # now_nanos = 0
msgs_sent = 0 # msgs_sent = 0
CI = self.CarInterface(self.CP, self.CarController, self.CarState) # CI = self.CarInterface(self.CP, self.CarController, self.CarState)
for _ in range(round(10.0 / DT_CTRL)): # make sure we hit the slowest messages # for _ in range(round(10.0 / DT_CTRL)): # make sure we hit the slowest messages
CI.update(car_control, []) # CI.update(car_control, [])
_, sendcan = CI.apply(car_control, now_nanos) # _, sendcan = CI.apply(car_control, now_nanos)
#
now_nanos += DT_CTRL * 1e9 # now_nanos += DT_CTRL * 1e9
msgs_sent += len(sendcan) # msgs_sent += len(sendcan)
for addr, _, dat, bus in sendcan: # for addr, _, dat, bus in sendcan:
to_send = libpanda_py.make_CANPacket(addr, bus % 4, dat) # to_send = libpanda_py.make_CANPacket(addr, bus % 4, dat)
self.assertTrue(self.safety.safety_tx_hook(to_send), (addr, dat, bus)) # self.assertTrue(self.safety.safety_tx_hook(to_send), (addr, dat, bus))
#
# Make sure we attempted to send messages # # Make sure we attempted to send messages
self.assertGreater(msgs_sent, 50) # self.assertGreater(msgs_sent, 50)
#
# Make sure we can send all messages while inactive # # Make sure we can send all messages while inactive
CC = car.CarControl.new_message() # CC = car.CarControl.new_message()
test_car_controller(CC) # test_car_controller(CC)
#
# Test cancel + general messages (controls_allowed=False & cruise_engaged=True) # # Test cancel + general messages (controls_allowed=False & cruise_engaged=True)
self.safety.set_cruise_engaged_prev(True) # self.safety.set_cruise_engaged_prev(True)
CC = car.CarControl.new_message(cruiseControl={'cancel': True}) # CC = car.CarControl.new_message(cruiseControl={'cancel': True})
test_car_controller(CC) # test_car_controller(CC)
#
# Test resume + general messages (controls_allowed=True & cruise_engaged=True) # # Test resume + general messages (controls_allowed=True & cruise_engaged=True)
self.safety.set_controls_allowed(True) # self.safety.set_controls_allowed(True)
CC = car.CarControl.new_message(cruiseControl={'resume': True}) # CC = car.CarControl.new_message(cruiseControl={'resume': True})
test_car_controller(CC) # test_car_controller(CC)
def test_panda_safety_carstate(self): # def test_panda_safety_carstate(self):
""" # """
Assert that panda safety matches openpilot's carState # Assert that panda safety matches openpilot's carState
""" # """
if self.CP.dashcamOnly: # if self.CP.dashcamOnly:
self.skipTest("no need to check panda safety for dashcamOnly") # self.skipTest("no need to check panda safety for dashcamOnly")
#
CC = car.CarControl.new_message() # CC = car.CarControl.new_message()
#
# warm up pass, as initial states may be different # # warm up pass, as initial states may be different
for can in self.can_msgs[:300]: # for can in self.can_msgs[:300]:
self.CI.update(CC, (can.as_builder().to_bytes(), )) # self.CI.update(CC, (can.as_builder().to_bytes(), ))
for msg in filter(lambda m: m.src in range(64), can.can): # for msg in filter(lambda m: m.src in range(64), can.can):
to_send = libpanda_py.make_CANPacket(msg.address, msg.src % 4, msg.dat) # to_send = libpanda_py.make_CANPacket(msg.address, msg.src % 4, msg.dat)
self.safety.safety_rx_hook(to_send) # self.safety.safety_rx_hook(to_send)
#
controls_allowed_prev = False # controls_allowed_prev = False
CS_prev = car.CarState.new_message() # CS_prev = car.CarState.new_message()
checks = defaultdict(lambda: 0) # checks = defaultdict(lambda: 0)
for idx, can in enumerate(self.can_msgs): # for idx, can in enumerate(self.can_msgs):
CS = self.CI.update(CC, (can.as_builder().to_bytes(), )) # CS = self.CI.update(CC, (can.as_builder().to_bytes(), ))
for msg in filter(lambda m: m.src in range(64), can.can): # for msg in filter(lambda m: m.src in range(64), can.can):
to_send = libpanda_py.make_CANPacket(msg.address, msg.src % 4, msg.dat) # to_send = libpanda_py.make_CANPacket(msg.address, msg.src % 4, msg.dat)
ret = self.safety.safety_rx_hook(to_send) # ret = self.safety.safety_rx_hook(to_send)
self.assertEqual(1, ret, f"safety rx failed ({ret=}): {to_send}") # self.assertEqual(1, ret, f"safety rx failed ({ret=}): {to_send}")
#
# Skip first frame so CS_prev is properly initialized # # Skip first frame so CS_prev is properly initialized
if idx == 0: # if idx == 0:
CS_prev = CS # CS_prev = CS
# Button may be left pressed in warm up period # # Button may be left pressed in warm up period
if not self.CP.pcmCruise: # if not self.CP.pcmCruise:
self.safety.set_controls_allowed(0) # self.safety.set_controls_allowed(0)
continue # continue
#
# TODO: check rest of panda's carstate (steering, ACC main on, etc.) # # TODO: check rest of panda's carstate (steering, ACC main on, etc.)
#
checks['gasPressed'] += CS.gasPressed != self.safety.get_gas_pressed_prev() # checks['gasPressed'] += CS.gasPressed != self.safety.get_gas_pressed_prev()
if self.CP.carName not in ("hyundai", "body"): # if self.CP.carName not in ("hyundai", "body"):
# TODO: fix standstill mismatches for other makes # # TODO: fix standstill mismatches for other makes
checks['standstill'] += CS.standstill == self.safety.get_vehicle_moving() # checks['standstill'] += CS.standstill == self.safety.get_vehicle_moving()
#
# TODO: remove this exception once this mismatch is resolved # # TODO: remove this exception once this mismatch is resolved
brake_pressed = CS.brakePressed # brake_pressed = CS.brakePressed
if CS.brakePressed and not self.safety.get_brake_pressed_prev(): # if CS.brakePressed and not self.safety.get_brake_pressed_prev():
if self.CP.carFingerprint in (HONDA.PILOT, HONDA.RIDGELINE) and CS.brake > 0.05: # if self.CP.carFingerprint in (HONDA.PILOT, HONDA.RIDGELINE) and CS.brake > 0.05:
brake_pressed = False # brake_pressed = False
checks['brakePressed'] += brake_pressed != self.safety.get_brake_pressed_prev() # checks['brakePressed'] += brake_pressed != self.safety.get_brake_pressed_prev()
checks['regenBraking'] += CS.regenBraking != self.safety.get_regen_braking_prev() # checks['regenBraking'] += CS.regenBraking != self.safety.get_regen_braking_prev()
#
if self.CP.pcmCruise: # if self.CP.pcmCruise:
# On most pcmCruise cars, openpilot's state is always tied to the PCM's cruise state. # # On most pcmCruise cars, openpilot's state is always tied to the PCM's cruise state.
# On Honda Nidec, we always engage on the rising edge of the PCM cruise state, but # # On Honda Nidec, we always engage on the rising edge of the PCM cruise state, but
# openpilot brakes to zero even if the min ACC speed is non-zero (i.e. the PCM disengages). # # openpilot brakes to zero even if the min ACC speed is non-zero (i.e. the PCM disengages).
if self.CP.carName == "honda" and self.CP.carFingerprint not in HONDA_BOSCH: # if self.CP.carName == "honda" and self.CP.carFingerprint not in HONDA_BOSCH:
# only the rising edges are expected to match # # only the rising edges are expected to match
if CS.cruiseState.enabled and not CS_prev.cruiseState.enabled: # if CS.cruiseState.enabled and not CS_prev.cruiseState.enabled:
checks['controlsAllowed'] += not self.safety.get_controls_allowed() # checks['controlsAllowed'] += not self.safety.get_controls_allowed()
else: # else:
checks['controlsAllowed'] += not CS.cruiseState.enabled and self.safety.get_controls_allowed() # checks['controlsAllowed'] += not CS.cruiseState.enabled and self.safety.get_controls_allowed()
else: # else:
# Check for enable events on rising edge of controls allowed # # Check for enable events on rising edge of controls allowed
button_enable = any(evt.enable for evt in CS.events) # button_enable = any(evt.enable for evt in CS.events)
mismatch = button_enable != (self.safety.get_controls_allowed() and not controls_allowed_prev) # mismatch = button_enable != (self.safety.get_controls_allowed() and not controls_allowed_prev)
checks['controlsAllowed'] += mismatch # checks['controlsAllowed'] += mismatch
controls_allowed_prev = self.safety.get_controls_allowed() # controls_allowed_prev = self.safety.get_controls_allowed()
if button_enable and not mismatch: # if button_enable and not mismatch:
self.safety.set_controls_allowed(False) # self.safety.set_controls_allowed(False)
#
if self.CP.carName == "honda": # if self.CP.carName == "honda":
checks['mainOn'] += CS.cruiseState.available != self.safety.get_acc_main_on() # checks['mainOn'] += CS.cruiseState.available != self.safety.get_acc_main_on()
#
CS_prev = CS # CS_prev = CS
#
failed_checks = {k: v for k, v in checks.items() if v > 0} # failed_checks = {k: v for k, v in checks.items() if v > 0}
self.assertFalse(len(failed_checks), f"panda safety doesn't agree with openpilot: {failed_checks}") # self.assertFalse(len(failed_checks), f"panda safety doesn't agree with openpilot: {failed_checks}")
@parameterized_class(('car_model', 'test_route'), test_cases) @parameterized_class(('car_model', 'test_route'), test_cases)

@ -2,6 +2,7 @@
import os import os
import sys import sys
import bz2 import bz2
import time
import urllib.parse import urllib.parse
import capnp import capnp
import warnings import warnings
@ -85,11 +86,15 @@ class LogReader:
# old rlogs weren't bz2 compressed # old rlogs weren't bz2 compressed
raise Exception(f"unknown extension {ext}") raise Exception(f"unknown extension {ext}")
t = time.perf_counter()
with FileReader(fn) as f: with FileReader(fn) as f:
dat = f.read() dat = f.read()
print('download time', time.perf_counter() - t)
t = time.perf_counter()
if ext == ".bz2" or dat.startswith(b'BZh9'): if ext == ".bz2" or dat.startswith(b'BZh9'):
dat = bz2.decompress(dat) dat = bz2.decompress(dat)
print('decomp time', time.perf_counter() - t)
ents = capnp_log.Event.read_multiple_bytes(dat) ents = capnp_log.Event.read_multiple_bytes(dat)

Loading…
Cancel
Save