import time import pytest from opendbc.car.structs import CarParams from panda import Panda, PandaJungle PANDA_SERIAL = "28002d000451323431333839" JUNGLE_SERIAL = "26001c001451313236343430" OBDC_PORT = 1 @pytest.fixture(autouse=True, scope="function") def pj(): jungle = PandaJungle(JUNGLE_SERIAL) jungle.flash() jungle.reset() jungle.set_ignition(False) yield jungle #jungle.set_panda_power(False) jungle.close() @pytest.fixture(scope="function") def p(pj): # note that the 3X's panda lib isn't updated, which # shold be fine since it only uses stable APIs pj.set_panda_power(True) assert Panda.wait_for_panda(PANDA_SERIAL, 10) p = Panda(PANDA_SERIAL) p.flash() p.reset() yield p p.close() def setup_state(panda, jungle, state): jungle.set_panda_power(0) if state == "off": wait_for_full_poweroff(jungle) elif state == "normal boot": jungle.set_panda_individual_power(OBDC_PORT, 1) elif state == "QDL": time.sleep(0.5) jungle.set_panda_individual_power(OBDC_PORT, 1) elif state == "ready to bootkick": wait_for_full_poweroff(jungle) jungle.set_panda_individual_power(OBDC_PORT, 1) wait_for_boot(panda, jungle) set_som_shutdown_flag(panda) panda.set_safety_mode(CarParams.SafetyModel.silent) panda.send_heartbeat() wait_for_som_shutdown(panda, jungle) else: raise ValueError(f"unkown state: {state}") def wait_for_som_shutdown(panda, jungle): st = time.monotonic() while panda.read_som_gpio(): # can take a while for the SOM to fully shutdown if time.monotonic() - st > 120: raise Exception("SOM didn't shutdown in time") if check_som_boot_flag(panda): raise Exception(f"SOM rebooted instead of shutdown: {time.monotonic() - st}s") time.sleep(0.5) dt = time.monotonic() - st print("waiting for shutdown", round(dt)) dt = time.monotonic() - st print(f"took {dt:.2f}s for SOM to shutdown") def wait_for_full_poweroff(jungle, timeout=30): st = time.monotonic() time.sleep(15) while PANDA_SERIAL in Panda.list(): if time.monotonic() - st > timeout: raise Exception("took too long for device to turn off") health = jungle.health() assert all(health[f"ch{i}_power"] < 0.1 for i in range(1, 7)) def check_som_boot_flag(panda): h = panda.health() return h['safety_mode'] == CarParams.SafetyModel.elm327 and h['safety_param'] == 30 def set_som_shutdown_flag(panda): panda.set_can_data_speed_kbps(0, 1000) def wait_for_boot(panda, jungle, reset_expected=False, bootkick=False, timeout=120): st = time.monotonic() Panda.wait_for_panda(PANDA_SERIAL, timeout) panda.reconnect() if bootkick: assert panda.health()['uptime'] > 20 else: assert panda.health()['uptime'] < 3 for i in range(3): assert not check_som_boot_flag(panda) time.sleep(1) # wait for SOM to bootup while not check_som_boot_flag(panda): if time.monotonic() - st > timeout: raise Exception("SOM didn't boot in time") time.sleep(1.0) assert panda.health()['som_reset_triggered'] == reset_expected def test_cold_boot(p, pj): setup_state(p, pj, "off") setup_state(p, pj, "normal boot") wait_for_boot(p, pj) def test_bootkick_ignition_line(p, pj): setup_state(p, pj, "ready to bootkick") pj.set_ignition(True) wait_for_boot(p, pj, bootkick=True) @pytest.mark.skip("test isn't reliable yet") def test_bootkick_can_ignition(p, pj): setup_state(p, pj, "ready to bootkick") for _ in range(10): # Mazda ignition signal pj.can_send(0x9E, b'\xc0\x00\x00\x00\x00\x00\x00\x00', 0) time.sleep(0.5) wait_for_boot(p, pj, bootkick=True) def test_recovery_from_qdl(p, pj): setup_state(p, pj, "ready to bootkick") # put into QDL using the FORCE_USB_BOOT pin for i in range(10): pj.set_header_pin(i, 1) # try to boot time.sleep(1) pj.set_ignition(True) time.sleep(3) # release FORCE_USB_BOOT for i in range(10): pj.set_header_pin(i, 0) # normally, this GPIO is set immediately since it's first enabled in the ABL for i in range(17): assert not p.read_som_gpio() time.sleep(1) # should boot after 45s wait_for_boot(p, pj, reset_expected=True, bootkick=True, timeout=120)