speedup test_pandad.py (#38009)

* speedup slowest

* less setup

* rm som reset logging

* simpliy a lil more

* lil more

* down to 36s

* sleeeeeeep

* finishing touches

* oopsie

* ty fix

---------

Co-authored-by: Comma Device <device@comma.ai>
This commit is contained in:
Adeeb Shihadeh
2026-05-11 19:40:20 -07:00
committed by GitHub
parent bdee8731b7
commit 11c14a138f
5 changed files with 46 additions and 113 deletions
-2
View File
@@ -103,8 +103,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"OnroadCycleRequested", {CLEAR_ON_MANAGER_START, BOOL}},
{"OpenpilotEnabledToggle", {PERSISTENT, BOOL, "1"}},
{"PandaHeartbeatLost", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}},
{"PandaSomResetTriggered", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}},
{"PandaSignatures", {CLEAR_ON_MANAGER_START, BYTES}},
{"PrimeType", {PERSISTENT, INT}},
{"RecordAudio", {PERSISTENT, BOOL}},
{"RecordAudioFeedback", {PERSISTENT, BOOL, "0"}},
+32 -80
View File
@@ -14,21 +14,11 @@ from openpilot.common.swaglog import cloudlog
def get_expected_signature() -> bytes:
try:
fn = os.path.join(FW_PATH, McuType.H7.config.app_fn)
return Panda.get_signature_from_firmware(fn)
except Exception:
cloudlog.exception("Error computing expected signature")
return b""
def flash_panda(panda_serial: str) -> Panda:
try:
panda = Panda(panda_serial)
except PandaProtocolMismatch:
cloudlog.warning("detected protocol mismatch, reflashing panda")
HARDWARE.recover_internal_panda()
raise
fn = os.path.join(FW_PATH, McuType.H7.config.app_fn)
return Panda.get_signature_from_firmware(fn)
def flash_panda(panda_serial: str):
panda = Panda(panda_serial)
fw_signature = get_expected_signature()
internal_panda = panda.is_internal()
@@ -58,7 +48,7 @@ def flash_panda(panda_serial: str) -> Panda:
cloudlog.info("Version mismatch after flashing, exiting")
raise AssertionError
return panda
panda.close()
def main() -> None:
@@ -74,89 +64,51 @@ def main() -> None:
do_exit = False
signal.signal(signal.SIGINT, signal_handler)
count = 0
first_run = True
params = Params()
no_internal_panda_count = 0
# check health for lost heartbeat
try:
for s in Panda.list():
with Panda(s) as p:
health = p.health()
if p.is_internal() and health["heartbeat_lost"]:
Params().put_bool("PandaHeartbeatLost", True)
cloudlog.event("heartbeat lost", deviceState=health)
except Exception:
cloudlog.exception("pandad.uncaught_exception")
count = 0
while not do_exit:
try:
count += 1
cloudlog.event("pandad.flash_and_connect", count=count)
params.remove("PandaSignatures")
# Handle missing internal panda
if no_internal_panda_count > 0:
if no_internal_panda_count == 3:
cloudlog.info("No pandas found, putting internal panda into DFU")
HARDWARE.recover_internal_panda()
else:
cloudlog.info("No pandas found, resetting internal panda")
HARDWARE.reset_internal_panda()
time.sleep(3) # wait to come back up
if (count % 2) == 0:
HARDWARE.reset_internal_panda()
else:
HARDWARE.recover_internal_panda()
count += 1
# Flash all Pandas in DFU mode
dfu_serials = PandaDFU.list()
if len(dfu_serials) > 0:
for serial in dfu_serials:
cloudlog.info(f"Panda in DFU mode found, flashing recovery {serial}")
PandaDFU(serial).recover()
for serial in PandaDFU.list():
cloudlog.info(f"Panda in DFU mode found, flashing recovery {serial}")
PandaDFU(serial).recover()
time.sleep(1)
panda_serials = Panda.list()
if len(panda_serials) == 0:
no_internal_panda_count += 1
continue
if len(panda_serials):
assert len(panda_serials) == 1
cloudlog.info(f"{len(panda_serials)} panda found, connecting - {panda_serials}")
flash_panda(panda_serials[0])
cloudlog.info(f"{len(panda_serials)} panda(s) found, connecting - {panda_serials}")
# Flash the first panda
panda_serial = panda_serials[0]
panda = flash_panda(panda_serial)
# Ensure internal panda is present if expected
if HARDWARE.has_internal_panda() and not panda.is_internal():
cloudlog.error("Internal panda is missing, trying again")
no_internal_panda_count += 1
continue
no_internal_panda_count = 0
# log panda fw version
params.put("PandaSignatures", panda.get_signature())
# check health for lost heartbeat
health = panda.health()
if health["heartbeat_lost"]:
params.put_bool("PandaHeartbeatLost", True)
cloudlog.event("heartbeat lost", deviceState=health, serial=panda.get_usb_serial())
if health["som_reset_triggered"]:
params.put_bool("PandaSomResetTriggered", True)
cloudlog.event("panda.som_reset_triggered", health=health, serial=panda.get_usb_serial())
if first_run:
# reset panda to ensure we're in a good state
cloudlog.info(f"Resetting panda {panda.get_usb_serial()}")
panda.reset(reconnect=True)
panda.close()
# run real pandad
os.environ['MANAGER_DAEMON'] = 'pandad'
process = subprocess.Popen(["./pandad"], cwd=os.path.join(BASEDIR, "selfdrive/pandad"))
process.wait()
# TODO: wrap all panda exceptions in a base panda exception
except (usb1.USBErrorNoDevice, usb1.USBErrorPipe):
# a panda was disconnected while setting everything up. let's try again
cloudlog.exception("Panda USB exception while setting up")
continue
except PandaProtocolMismatch:
cloudlog.exception("pandad.protocol_mismatch")
continue
except Exception:
cloudlog.exception("pandad.uncaught_exception")
continue
first_run = False
# run pandad with all connected serials as arguments
os.environ['MANAGER_DAEMON'] = 'pandad'
process = subprocess.Popen(["./pandad", panda_serial], cwd=os.path.join(BASEDIR, "selfdrive/pandad"))
process.wait()
if __name__ == "__main__":
Binary file not shown.
+11 -28
View File
@@ -15,12 +15,6 @@ HERE = os.path.dirname(os.path.realpath(__file__))
@pytest.mark.tici
class TestPandad:
def setup_method(self):
# ensure panda is up
if len(Panda.list()) == 0:
self._run_test(60)
def teardown_method(self):
managed_processes['pandad'].stop()
@@ -30,7 +24,7 @@ class TestPandad:
managed_processes['pandad'].start()
while (time.monotonic() - st) < timeout:
sm.update(100)
sm.update(10)
if len(sm['pandaStates']) and sm['pandaStates'][0].pandaType != log.PandaState.PandaType.unknown:
break
dt = time.monotonic() - st
@@ -45,10 +39,6 @@ class TestPandad:
HARDWARE.recover_internal_panda()
assert Panda.wait_for_dfu(None, 10)
def _assert_no_panda(self):
assert not Panda.wait_for_dfu(None, 3)
assert not Panda.wait_for_panda(None, 3)
def _flash_bootstub(self, fn):
self._go_to_dfu()
pd = PandaDFU(None)
@@ -56,12 +46,11 @@ class TestPandad:
fn = os.path.join(HERE, pd.get_mcu_type().config.bootstub_fn)
with open(fn, "rb") as f:
pd.program_bootstub(f.read())
pd.reset()
HARDWARE.reset_internal_panda()
def test_in_dfu(self):
HARDWARE.recover_internal_panda()
self._run_test(60)
self._run_test()
def test_in_bootstub(self):
with Panda() as p:
@@ -69,30 +58,24 @@ class TestPandad:
assert p.bootstub
self._run_test()
def test_internal_panda_reset(self):
def test_in_reset(self):
gpio_init(GPIO.STM_RST_N, True)
gpio_set(GPIO.STM_RST_N, 1)
time.sleep(0.5)
assert all(not Panda(s).is_internal() for s in Panda.list())
assert not Panda.list()
self._run_test()
assert any(Panda(s).is_internal() for s in Panda.list())
def test_old_spi_protocol(self):
# flash firmware with old SPI protocol
self._flash_bootstub(os.path.join(HERE, "bootstub.panda_h7_spiv0.bin"))
self._run_test(45)
def test_release_to_devel_bootstub(self):
st = time.monotonic()
self._flash_bootstub(None)
self._run_test(45)
print("flash done", time.monotonic() - st)
self._run_test()
def test_recover_from_bad_bootstub(self):
self._go_to_dfu()
with PandaDFU(None) as pd:
pd.program_bootstub(b"\x00"*1024)
pd.reset()
pd._handle.program(pd.get_mcu_type().config.bootstub_address, b"\x00"*100)
HARDWARE.reset_internal_panda()
self._assert_no_panda()
assert not Panda.list()
assert not PandaDFU.list()
self._run_test(60)
self._run_test()
+3 -3
View File
@@ -414,7 +414,7 @@ class Tici(HardwareBase):
gpio_set(GPIO.STM_RST_N, 1)
gpio_set(GPIO.STM_BOOT0, 0)
time.sleep(1)
time.sleep(0.01)
gpio_set(GPIO.STM_RST_N, 0)
def recover_internal_panda(self):
@@ -423,9 +423,9 @@ class Tici(HardwareBase):
gpio_set(GPIO.STM_RST_N, 1)
gpio_set(GPIO.STM_BOOT0, 1)
time.sleep(0.5)
time.sleep(0.01)
gpio_set(GPIO.STM_RST_N, 0)
time.sleep(0.5)
time.sleep(0.01)
gpio_set(GPIO.STM_BOOT0, 0)
def booted(self):