From 2422bb3cd27945901f3d2c7d9908c331ec84909d Mon Sep 17 00:00:00 2001 From: Comma Device Date: Mon, 11 May 2026 23:56:33 +0000 Subject: [PATCH 01/10] speedup slowest --- selfdrive/pandad/tests/test_pandad.py | 10 +++------- system/hardware/tici/hardware.py | 6 +++--- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/selfdrive/pandad/tests/test_pandad.py b/selfdrive/pandad/tests/test_pandad.py index 6a5840d4875b36..1e5cee487cec33 100644 --- a/selfdrive/pandad/tests/test_pandad.py +++ b/selfdrive/pandad/tests/test_pandad.py @@ -45,10 +45,6 @@ def _go_to_dfu(self): 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) @@ -90,9 +86,9 @@ def test_release_to_devel_bootstub(self): 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) diff --git a/system/hardware/tici/hardware.py b/system/hardware/tici/hardware.py index dcdeb8ac47b831..c9676e29b345d3 100644 --- a/system/hardware/tici/hardware.py +++ b/system/hardware/tici/hardware.py @@ -414,7 +414,7 @@ def reset_internal_panda(self): gpio_set(GPIO.STM_RST_N, 1) gpio_set(GPIO.STM_BOOT0, 0) - time.sleep(1) + time.sleep(0.1) gpio_set(GPIO.STM_RST_N, 0) def recover_internal_panda(self): @@ -423,9 +423,9 @@ def recover_internal_panda(self): gpio_set(GPIO.STM_RST_N, 1) gpio_set(GPIO.STM_BOOT0, 1) - time.sleep(0.5) + time.sleep(0.1) gpio_set(GPIO.STM_RST_N, 0) - time.sleep(0.5) + time.sleep(0.1) gpio_set(GPIO.STM_BOOT0, 0) def booted(self): From 5c9dcf08dd2d63567c599b818d39b1f1ec9decf3 Mon Sep 17 00:00:00 2001 From: Comma Device Date: Tue, 12 May 2026 00:03:22 +0000 Subject: [PATCH 02/10] less setup --- selfdrive/pandad/tests/test_pandad.py | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/selfdrive/pandad/tests/test_pandad.py b/selfdrive/pandad/tests/test_pandad.py index 1e5cee487cec33..cdd31f50b02d58 100644 --- a/selfdrive/pandad/tests/test_pandad.py +++ b/selfdrive/pandad/tests/test_pandad.py @@ -15,12 +15,6 @@ @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 @@ def _run_test(self, timeout=30) -> float: 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 @@ -52,7 +46,6 @@ def _flash_bootstub(self, fn): 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): @@ -68,16 +61,13 @@ def test_in_bootstub(self): def test_internal_panda_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_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): self._flash_bootstub(None) From 155395c255a77af0887878684521c39ed7141484 Mon Sep 17 00:00:00 2001 From: Comma Device Date: Tue, 12 May 2026 00:09:46 +0000 Subject: [PATCH 03/10] rm som reset logging --- common/params_keys.h | 1 - selfdrive/pandad/pandad.py | 3 --- 2 files changed, 4 deletions(-) diff --git a/common/params_keys.h b/common/params_keys.h index b81a373d0876ef..d149901a36551b 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -103,7 +103,6 @@ inline static std::unordered_map 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}}, diff --git a/selfdrive/pandad/pandad.py b/selfdrive/pandad/pandad.py index df2b4f7ee895a6..9af883c4e9b692 100755 --- a/selfdrive/pandad/pandad.py +++ b/selfdrive/pandad/pandad.py @@ -129,9 +129,6 @@ def signal_handler(signum, frame): 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 From 488509879bbf011b9959bc9e9814b2e93bacdcac Mon Sep 17 00:00:00 2001 From: Comma Device Date: Tue, 12 May 2026 01:26:51 +0000 Subject: [PATCH 04/10] simpliy a lil more --- common/params_keys.h | 1 - selfdrive/pandad/pandad.py | 71 +++++++++++--------------------------- 2 files changed, 21 insertions(+), 51 deletions(-) diff --git a/common/params_keys.h b/common/params_keys.h index d149901a36551b..64d77177846542 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -103,7 +103,6 @@ inline static std::unordered_map keys = { {"OnroadCycleRequested", {CLEAR_ON_MANAGER_START, BOOL}}, {"OpenpilotEnabledToggle", {PERSISTENT, BOOL, "1"}}, {"PandaHeartbeatLost", {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"}}, diff --git a/selfdrive/pandad/pandad.py b/selfdrive/pandad/pandad.py index 9af883c4e9b692..1142dab3f1642c 100755 --- a/selfdrive/pandad/pandad.py +++ b/selfdrive/pandad/pandad.py @@ -74,68 +74,41 @@ def signal_handler(signum, frame): 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() + + if (count % 2) == 0: + HARDWARE.reset_internal_panda() + else: + HARDWARE.recover_internal_panda() time.sleep(3) # wait to come back up # 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 - 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 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() + cloudlog.info(f"{len(panda_serials)} panda found, connecting - {panda_serials}") + flash_panda(panda_serials[0]) # 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 @@ -148,8 +121,6 @@ def signal_handler(signum, frame): 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")) From 1b8fb2a871f50cc5bf731daafa8e73c3ecd8720c Mon Sep 17 00:00:00 2001 From: Comma Device Date: Tue, 12 May 2026 01:32:10 +0000 Subject: [PATCH 05/10] lil more --- selfdrive/pandad/pandad.py | 22 ++++++---------------- selfdrive/pandad/tests/test_pandad.py | 2 ++ 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/selfdrive/pandad/pandad.py b/selfdrive/pandad/pandad.py index 1142dab3f1642c..6e139038dbd8cf 100755 --- a/selfdrive/pandad/pandad.py +++ b/selfdrive/pandad/pandad.py @@ -14,21 +14,11 @@ 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"" + 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: - try: - panda = Panda(panda_serial) - except PandaProtocolMismatch: - cloudlog.warning("detected protocol mismatch, reflashing panda") - HARDWARE.recover_internal_panda() - raise - + 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: @@ -121,9 +111,9 @@ def signal_handler(signum, frame): cloudlog.exception("pandad.uncaught_exception") continue - # run pandad with all connected serials as arguments + # run real pandad os.environ['MANAGER_DAEMON'] = 'pandad' - process = subprocess.Popen(["./pandad", panda_serial], cwd=os.path.join(BASEDIR, "selfdrive/pandad")) + process = subprocess.Popen(["./pandad"], cwd=os.path.join(BASEDIR, "selfdrive/pandad")) process.wait() diff --git a/selfdrive/pandad/tests/test_pandad.py b/selfdrive/pandad/tests/test_pandad.py index cdd31f50b02d58..8724225cfdf139 100644 --- a/selfdrive/pandad/tests/test_pandad.py +++ b/selfdrive/pandad/tests/test_pandad.py @@ -70,7 +70,9 @@ def test_internal_panda_reset(self): # self._run_test(45) def test_release_to_devel_bootstub(self): + st = time.monotonic() self._flash_bootstub(None) + print("flash done", time.monotonic() - st) self._run_test(45) def test_recover_from_bad_bootstub(self): From 401ab84556b1c1cc0045f1860ce6ab60387f5e65 Mon Sep 17 00:00:00 2001 From: Comma Device Date: Tue, 12 May 2026 01:49:22 +0000 Subject: [PATCH 06/10] down to 36s --- selfdrive/pandad/pandad.py | 4 +--- selfdrive/pandad/tests/test_pandad.py | 6 +++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/selfdrive/pandad/pandad.py b/selfdrive/pandad/pandad.py index 6e139038dbd8cf..a5b6d092ae3e3d 100755 --- a/selfdrive/pandad/pandad.py +++ b/selfdrive/pandad/pandad.py @@ -78,14 +78,12 @@ def signal_handler(signum, frame): count = 0 while not do_exit: try: - count += 1 cloudlog.event("pandad.flash_and_connect", count=count) - if (count % 2) == 0: HARDWARE.reset_internal_panda() else: HARDWARE.recover_internal_panda() - time.sleep(3) # wait to come back up + count += 1 # Flash all Pandas in DFU mode for serial in PandaDFU.list(): diff --git a/selfdrive/pandad/tests/test_pandad.py b/selfdrive/pandad/tests/test_pandad.py index 8724225cfdf139..4a7059db34422c 100644 --- a/selfdrive/pandad/tests/test_pandad.py +++ b/selfdrive/pandad/tests/test_pandad.py @@ -50,7 +50,7 @@ def _flash_bootstub(self, fn): 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: @@ -73,7 +73,7 @@ def test_release_to_devel_bootstub(self): st = time.monotonic() self._flash_bootstub(None) print("flash done", time.monotonic() - st) - self._run_test(45) + self._run_test() def test_recover_from_bad_bootstub(self): self._go_to_dfu() @@ -83,4 +83,4 @@ def test_recover_from_bad_bootstub(self): assert not Panda.list() assert not PandaDFU.list() - self._run_test(60) + self._run_test() From 8bb68172d5bee76e499feef3a5a93dff151763fa Mon Sep 17 00:00:00 2001 From: Comma Device Date: Tue, 12 May 2026 02:00:30 +0000 Subject: [PATCH 07/10] sleeeeeeep --- system/hardware/tici/hardware.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/system/hardware/tici/hardware.py b/system/hardware/tici/hardware.py index c9676e29b345d3..120ca77107992e 100644 --- a/system/hardware/tici/hardware.py +++ b/system/hardware/tici/hardware.py @@ -414,7 +414,7 @@ def reset_internal_panda(self): gpio_set(GPIO.STM_RST_N, 1) gpio_set(GPIO.STM_BOOT0, 0) - time.sleep(0.1) + time.sleep(0.01) gpio_set(GPIO.STM_RST_N, 0) def recover_internal_panda(self): @@ -423,9 +423,9 @@ def recover_internal_panda(self): gpio_set(GPIO.STM_RST_N, 1) gpio_set(GPIO.STM_BOOT0, 1) - time.sleep(0.1) + time.sleep(0.01) gpio_set(GPIO.STM_RST_N, 0) - time.sleep(0.1) + time.sleep(0.01) gpio_set(GPIO.STM_BOOT0, 0) def booted(self): From 1775a1e9f6ca36f54f800168eed9100caa2710b7 Mon Sep 17 00:00:00 2001 From: Comma Device Date: Tue, 12 May 2026 02:05:54 +0000 Subject: [PATCH 08/10] finishing touches --- selfdrive/pandad/pandad.py | 17 ++++++----------- .../pandad/tests/bootstub.panda_h7_spiv0.bin | Bin 16860 -> 0 bytes selfdrive/pandad/tests/test_pandad.py | 7 +------ 3 files changed, 7 insertions(+), 17 deletions(-) delete mode 100755 selfdrive/pandad/tests/bootstub.panda_h7_spiv0.bin diff --git a/selfdrive/pandad/pandad.py b/selfdrive/pandad/pandad.py index a5b6d092ae3e3d..afa3f59ea16dbf 100755 --- a/selfdrive/pandad/pandad.py +++ b/selfdrive/pandad/pandad.py @@ -93,26 +93,21 @@ def signal_handler(signum, frame): panda_serials = Panda.list() if len(panda_serials) == 0: - continue + cloudlog.info(f"{len(panda_serials)} panda found, connecting - {panda_serials}") + flash_panda(panda_serials[0]) - cloudlog.info(f"{len(panda_serials)} panda found, connecting - {panda_serials}") - flash_panda(panda_serials[0]) + # 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 - - # run real pandad - os.environ['MANAGER_DAEMON'] = 'pandad' - process = subprocess.Popen(["./pandad"], cwd=os.path.join(BASEDIR, "selfdrive/pandad")) - process.wait() if __name__ == "__main__": diff --git a/selfdrive/pandad/tests/bootstub.panda_h7_spiv0.bin b/selfdrive/pandad/tests/bootstub.panda_h7_spiv0.bin deleted file mode 100755 index 5cf2fa45196484fdfeb8ca952ca2ab89ff951e22..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16860 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 HPRRcNEoX1h diff --git a/selfdrive/pandad/tests/test_pandad.py b/selfdrive/pandad/tests/test_pandad.py index 4a7059db34422c..9dacd3f5a88d40 100644 --- a/selfdrive/pandad/tests/test_pandad.py +++ b/selfdrive/pandad/tests/test_pandad.py @@ -58,17 +58,12 @@ def test_in_bootstub(self): 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) assert not Panda.list() self._run_test() - #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) From 066545410bd354599721b7860fe37ef27cd438c0 Mon Sep 17 00:00:00 2001 From: Comma Device Date: Tue, 12 May 2026 02:10:25 +0000 Subject: [PATCH 09/10] oopsie --- selfdrive/pandad/pandad.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/selfdrive/pandad/pandad.py b/selfdrive/pandad/pandad.py index afa3f59ea16dbf..4b7c4138907894 100755 --- a/selfdrive/pandad/pandad.py +++ b/selfdrive/pandad/pandad.py @@ -92,7 +92,8 @@ def signal_handler(signum, frame): time.sleep(1) panda_serials = Panda.list() - if len(panda_serials) == 0: + 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]) From 93879ebfb98b8d38266ff742f62b9c40cabd282e Mon Sep 17 00:00:00 2001 From: Comma Device Date: Tue, 12 May 2026 02:11:27 +0000 Subject: [PATCH 10/10] ty fix --- selfdrive/pandad/pandad.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/pandad/pandad.py b/selfdrive/pandad/pandad.py index 4b7c4138907894..fcae0d8b3779a8 100755 --- a/selfdrive/pandad/pandad.py +++ b/selfdrive/pandad/pandad.py @@ -17,7 +17,7 @@ def get_expected_signature() -> bytes: 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: +def flash_panda(panda_serial: str): panda = Panda(panda_serial) fw_signature = get_expected_signature() internal_panda = panda.is_internal()