From 7285675794396aa6700c5f51c7ddec359e84d8fa Mon Sep 17 00:00:00 2001 From: stefpi <19478336+stefpi@users.noreply.github.com> Date: Tue, 12 May 2026 13:35:07 -0700 Subject: [PATCH 01/10] add can handle and f4 config to canloader --- board/canloader.py | 51 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/board/canloader.py b/board/canloader.py index 624e6bf..4b99eb8 100755 --- a/board/canloader.py +++ b/board/canloader.py @@ -3,9 +3,56 @@ import time import argparse import _thread -from panda import Panda, MCU_TYPE_F4 # pylint: disable=import-error -from panda.tests.pedal.canhandle import CanHandle # pylint: disable=import-error +import struct +from panda import Panda # pylint: disable=import-error +from opendbc.car.uds import CanClient, IsoTpMessage, MessageTimeoutError + + +REQUEST_IN = 0xC0 +REQUEST_OUT = 0x40 +DEFAULT_ISOTP_TIMEOUT = 2 + +MCU_TYPE_F4 = { + "sector_sizes": [0x4000 for _ in range(4)] + [0x10000] + [0x20000 for _ in range(11)], +} + +class CanHandle: + def __init__(self, can_send, can_recv, bus): + self.client = CanClient(can_send, can_recv, tx_addr=1, rx_addr=2, bus=bus) + + def transact(self, dat, timeout=DEFAULT_ISOTP_TIMEOUT, expect_disconnect=False): + try: + msg = IsoTpMessage(self.client, timeout=timeout) + msg.send(dat) + if expect_disconnect: + deadline = time.monotonic() + timeout + while not msg.tx_done: + msg.recv(timeout=0) + if not msg.tx_done and time.monotonic() > deadline: + raise MessageTimeoutError("timeout waiting for flow control") + time.sleep(0.01) + return b"" + ret, _ = msg.recv() + return ret + except MessageTimeoutError as e: + raise TimeoutError from e + + def controlWrite(self, request_type, request, value, index, data, timeout=DEFAULT_ISOTP_TIMEOUT, expect_disconnect=False): + dat = struct.pack("HHBBHHH", 0, 0, request_type, request, value, index, 0) + return self.transact(dat, timeout=timeout, expect_disconnect=expect_disconnect) + + def controlRead(self, request_type, request, value, index, length, timeout=DEFAULT_ISOTP_TIMEOUT): + dat = struct.pack("HHBBHHH", 0, 0, request_type, request, value, index, length) + return self.transact(dat, timeout=timeout) + + def bulkWrite(self, endpoint, data, timeout=DEFAULT_ISOTP_TIMEOUT): + dat = struct.pack("HH", endpoint, len(data)) + data + return self.transact(dat, timeout=timeout) + + def bulkRead(self, endpoint, timeout=DEFAULT_ISOTP_TIMEOUT): + dat = struct.pack("HH", endpoint, 0) + return self.transact(dat, timeout=timeout) def heartbeat_thread(p): while True: From e0b609f931e1ebb7e1eebc0c487e0c667d225983 Mon Sep 17 00:00:00 2001 From: stefpi <19478336+stefpi@users.noreply.github.com> Date: Tue, 12 May 2026 13:49:00 -0700 Subject: [PATCH 02/10] update safety model --- board/canloader.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/board/canloader.py b/board/canloader.py index 4b99eb8..191f466 100755 --- a/board/canloader.py +++ b/board/canloader.py @@ -6,6 +6,7 @@ import struct from panda import Panda # pylint: disable=import-error +from opendbc.car.structs import CarParams from opendbc.car.uds import CanClient, IsoTpMessage, MessageTimeoutError @@ -98,7 +99,7 @@ def flasher(p, addr, file): p = Panda() _thread.start_new_thread(heartbeat_thread, (p,)) - p.set_safety_mode(Panda.SAFETY_BODY) + p.set_safety_mode(CarParams.SafetyModel.body) print("Flashing motherboard") flasher(p, addr, args.fn) From cadb1557e835145cc8ce2b2df743cd17bcb4015c Mon Sep 17 00:00:00 2001 From: stefpi <19478336+stefpi@users.noreply.github.com> Date: Tue, 12 May 2026 13:50:24 -0700 Subject: [PATCH 03/10] missing bus arg --- board/canloader.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/board/canloader.py b/board/canloader.py index 191f466..4f9b467 100755 --- a/board/canloader.py +++ b/board/canloader.py @@ -19,8 +19,8 @@ } class CanHandle: - def __init__(self, can_send, can_recv, bus): - self.client = CanClient(can_send, can_recv, tx_addr=1, rx_addr=2, bus=bus) + def __init__(self, can_send, can_recv): + self.client = CanClient(can_send, can_recv, tx_addr=1, rx_addr=2, bus=0) def transact(self, dat, timeout=DEFAULT_ISOTP_TIMEOUT, expect_disconnect=False): try: From a850522aa9eb1ba821ade3834f317660d779a44e Mon Sep 17 00:00:00 2001 From: stefpi <19478336+stefpi@users.noreply.github.com> Date: Tue, 12 May 2026 13:52:27 -0700 Subject: [PATCH 04/10] fix canHandle call --- board/canloader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/board/canloader.py b/board/canloader.py index 4f9b467..51460c0 100755 --- a/board/canloader.py +++ b/board/canloader.py @@ -77,7 +77,7 @@ def flasher(p, addr, file): retries = 3 # How many times to retry on timeout error while(retries+1>0): try: - Panda.flash_static(CanHandle(p, 0), code, MCU_TYPE_F4) + Panda.flash_static(CanHandle(p.can_send, p.can_recv), code, MCU_TYPE_F4) except TimeoutError: print("Timeout, trying again...") retries -= 1 From 92f5a481e8768da14c53791859cbb7ce1a38c3b7 Mon Sep 17 00:00:00 2001 From: stefpi <19478336+stefpi@users.noreply.github.com> Date: Tue, 12 May 2026 13:55:00 -0700 Subject: [PATCH 05/10] fix f4 config --- board/canloader.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/board/canloader.py b/board/canloader.py index 51460c0..7ae9296 100755 --- a/board/canloader.py +++ b/board/canloader.py @@ -14,9 +14,9 @@ REQUEST_OUT = 0x40 DEFAULT_ISOTP_TIMEOUT = 2 -MCU_TYPE_F4 = { - "sector_sizes": [0x4000 for _ in range(4)] + [0x10000] + [0x20000 for _ in range(11)], -} +class MCU_TYPE_F4: + class config: + sector_sizes = [0x4000 for _ in range(4)] + [0x10000] + [0x20000 for _ in range(11)] class CanHandle: def __init__(self, can_send, can_recv): From 9e973dadc7659cecd3f847aaec1260c43ccfe961 Mon Sep 17 00:00:00 2001 From: stefpi <19478336+stefpi@users.noreply.github.com> Date: Tue, 12 May 2026 14:09:24 -0700 Subject: [PATCH 06/10] add old flash can for 0x10 step --- board/canloader.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/board/canloader.py b/board/canloader.py index 7ae9296..ba68394 100755 --- a/board/canloader.py +++ b/board/canloader.py @@ -4,6 +4,7 @@ import argparse import _thread import struct +from itertools import accumulate from panda import Panda # pylint: disable=import-error from opendbc.car.structs import CarParams @@ -13,6 +14,8 @@ REQUEST_IN = 0xC0 REQUEST_OUT = 0x40 DEFAULT_ISOTP_TIMEOUT = 2 +# bootstub reads the bulk-write length as a single byte and its ISO-TP buffer is 0x110 +FLASH_STEP = 0x10 class MCU_TYPE_F4: class config: @@ -68,6 +71,26 @@ def flush_panda(): if len(p.can_recv()) == 0: break +def flash_can(handle, code, mcu_type): + assert handle.controlRead(REQUEST_IN, 0xb0, 0, 0, 0xc)[4:8] == b"\xde\xad\xd0\x0d", "flasher not present" + + apps_sectors_cumsum = accumulate(mcu_type.config.sector_sizes[1:]) + last_sector = next((i + 1 for i, v in enumerate(apps_sectors_cumsum) if v > len(code)), -1) + assert last_sector >= 1, "Binary too small? No sector to erase." + assert last_sector < 7, "Binary too large! Risk of overwriting provisioning chunk." + + handle.controlWrite(REQUEST_IN, 0xb1, 0, 0, b'') + for i in range(1, last_sector + 1): + handle.controlWrite(REQUEST_IN, 0xb2, i, 0, b'') + + for i in range(0, len(code), FLASH_STEP): + handle.bulkWrite(2, code[i:i + FLASH_STEP]) + + try: + handle.controlWrite(REQUEST_IN, 0xd8, 0, 0, b'', expect_disconnect=True) + except Exception: + pass + def flasher(p, addr, file): p.can_send(addr, b"\xce\xfa\xad\xde\x1e\x0b\xb0\x0a", 0) time.sleep(0.1) @@ -77,7 +100,7 @@ def flasher(p, addr, file): retries = 3 # How many times to retry on timeout error while(retries+1>0): try: - Panda.flash_static(CanHandle(p.can_send, p.can_recv), code, MCU_TYPE_F4) + flash_can(CanHandle(p.can_send, p.can_recv), code, MCU_TYPE_F4) except TimeoutError: print("Timeout, trying again...") retries -= 1 From 7127aeaa83d97fe73aa618381885600ab0ee7a10 Mon Sep 17 00:00:00 2001 From: stefpi <19478336+stefpi@users.noreply.github.com> Date: Tue, 12 May 2026 14:42:48 -0700 Subject: [PATCH 07/10] add to readme --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 641aaa4..93f6370 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,14 @@ Compile: `scons` Flash bootstub and app: `board/recover.sh` # STM flasher should be connected to debug port, needs openocd -Flash app through CAN bus with panda: +Flash app through CAN bus with (standalone) panda: `board/flash_base.sh` # base motherboard `board/flash_knee.sh` # knee motherboard + +Flash app through CAN bus with (comma device) panda: + +1. kill openpilot or just the panda processes +2. run `board/flash_base.sh` +3. restart openpilot via `op start` or restart device \ No newline at end of file From fef84a617b3221ae9eefd4907447b438a8c732fc Mon Sep 17 00:00:00 2001 From: stefpi <19478336+stefpi@users.noreply.github.com> Date: Tue, 12 May 2026 15:01:46 -0700 Subject: [PATCH 08/10] clean --- board/canloader.py | 1 - 1 file changed, 1 deletion(-) diff --git a/board/canloader.py b/board/canloader.py index ba68394..6bc3467 100755 --- a/board/canloader.py +++ b/board/canloader.py @@ -14,7 +14,6 @@ REQUEST_IN = 0xC0 REQUEST_OUT = 0x40 DEFAULT_ISOTP_TIMEOUT = 2 -# bootstub reads the bulk-write length as a single byte and its ISO-TP buffer is 0x110 FLASH_STEP = 0x10 class MCU_TYPE_F4: From 225613e7bdae49c21c829b63f0125b45b5966d73 Mon Sep 17 00:00:00 2001 From: stefpi <19478336+stefpi@users.noreply.github.com> Date: Tue, 12 May 2026 15:10:01 -0700 Subject: [PATCH 09/10] add step to readme --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 93f6370..dd96c8e 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ Flash app through CAN bus with (standalone) panda: Flash app through CAN bus with (comma device) panda: -1. kill openpilot or just the panda processes -2. run `board/flash_base.sh` -3. restart openpilot via `op start` or restart device \ No newline at end of file +1. compile body firmware on your computer and transfer it to the comma device +2. kill openpilot or just the panda processes +3. run `board/flash_base.sh` +4. restart openpilot via `op start` or restart device From 6a7c2d71708168c3506ac7368bd76b8a5ac7a958 Mon Sep 17 00:00:00 2001 From: stefpi <19478336+stefpi@users.noreply.github.com> Date: Tue, 12 May 2026 15:17:40 -0700 Subject: [PATCH 10/10] increase step to 0xF8 --- board/canloader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/board/canloader.py b/board/canloader.py index 6bc3467..7fe9533 100755 --- a/board/canloader.py +++ b/board/canloader.py @@ -14,7 +14,7 @@ REQUEST_IN = 0xC0 REQUEST_OUT = 0x40 DEFAULT_ISOTP_TIMEOUT = 2 -FLASH_STEP = 0x10 +FLASH_STEP = 0xF8 class MCU_TYPE_F4: class config: