Files
StarPilot/panda/tests/hitl/base.py
T
firestar5683 22a8575199 Vendor dependencies and resources for standalone builds
Keep official openpilot ancestry and dependency licenses. Store resources directly in Git, remove LFS setup requirements, and leave the downloadable big model outside the source tree.
2026-10-03 21:01:18 +00:00

134 lines
4.0 KiB
Python

import atexit
import os
import signal
import unittest
from panda import Panda, PandaDFU, PandaJungle
from panda.tests.hitl.helpers import clear_can_buffers
SPEED_NORMAL = 500
BUS_SPEEDS = [(0, SPEED_NORMAL), (1, SPEED_NORMAL), (2, SPEED_NORMAL)]
# test options
NO_JUNGLE = os.environ.get("NO_JUNGLE", "0") == "1"
# Find all pandas connected
_panda_jungle = None
_panda_type = None
_panda_serial = None
def init_devices():
if not NO_JUNGLE:
global _panda_jungle
_panda_jungle = PandaJungle()
_panda_jungle.set_panda_power(True)
with Panda(serial=None, claim=False) as p:
global _panda_type
global _panda_serial
_panda_serial = p.get_usb_serial()
_panda_type = bytes(p.get_type())
assert _panda_serial is not None, "No panda found!"
def init_jungle():
if _panda_jungle is None:
return
clear_can_buffers(_panda_jungle)
_panda_jungle.set_panda_power(True)
_panda_jungle.set_can_loopback(False)
_panda_jungle.set_obd(False)
_panda_jungle.set_harness_orientation(PandaJungle.HARNESS_ORIENTATION_1)
for bus, speed in BUS_SPEEDS:
_panda_jungle.set_can_speed_kbps(bus, speed)
def panda_test(**options):
def decorate(test):
test.panda_options = {**getattr(test, "panda_options", {}), **options}
return test
return decorate
@unittest.skipUnless(os.environ.get("HITL") == "1", "hardware tests require HITL=1")
class PandaTestCase(unittest.TestCase):
panda_types = None
@classmethod
def setUpClass(cls):
if _panda_serial is None:
init_devices()
if _panda_jungle is not None:
atexit.register(_panda_jungle.close)
# init jungle
init_jungle()
# init panda
assert Panda.wait_for_panda(_panda_serial, timeout=10), "panda not found"
p = Panda(serial=_panda_serial)
cls.addClassCleanup(p.close)
cls.p = p
p.reset(reconnect=True)
p.set_can_loopback(False)
p.set_power_save(False)
for bus, speed in BUS_SPEEDS:
p.set_can_speed_kbps(bus, speed)
clear_can_buffers(p)
p.set_power_save(False)
def setUp(self):
self.options = getattr(getattr(self, self._testMethodName), "panda_options", {})
panda_types = self.options.get("panda_types", self.panda_types)
if panda_types is not None and _panda_type not in panda_types:
self.skipTest(f"Not applicable, {panda_types} pandas only")
if self.options.get("needs_jungle", False) and NO_JUNGLE:
self.skipTest("skipping tests that require a jungle")
previous_handler = signal.signal(signal.SIGALRM, self._timeout)
self.addCleanup(signal.signal, signal.SIGALRM, previous_handler)
self.addCleanup(signal.setitimer, signal.ITIMER_REAL, 0)
signal.setitimer(signal.ITIMER_REAL, self.options.get("timeout", 60))
if self.options.get("needs_jungle", False):
init_jungle()
self.panda_jungle = _panda_jungle
self.p.reset()
assert self.p.up_to_date()
def _timeout(self, signum, frame):
raise TimeoutError(f"{self.id()} exceeded its test timeout")
def tearDown(self):
p = self.p
# reconnect
if p.get_dfu_serial() in PandaDFU.list():
PandaDFU(p.get_dfu_serial()).reset()
p.reconnect()
if not p.connected:
p.reconnect()
if p.bootstub:
p.reset()
assert not p.bootstub
# Check for faults
assert p.health()['faults'] == 0
assert p.health()['fault_status'] == 0
# Check for SPI errors
#assert p.health()['spi_error_count'] == 0
# Check health of each CAN core after test, normal to fail for test_gen2_loopback on OBD bus, so skipping
if not self.options.get("expect_can_error", False):
for i in range(3):
can_health = p.can_health(i)
assert can_health['bus_off_cnt'] == 0
assert can_health['receive_error_cnt'] < 127
assert can_health['transmit_error_cnt'] < 255
assert can_health['error_passive'] == 0
assert can_health['error_warning'] == 0
assert can_health['total_rx_lost_cnt'] == 0
assert can_health['total_tx_lost_cnt'] == 0
assert can_health['total_error_cnt'] == 0
assert can_health['total_tx_checksum_error_cnt'] == 0