-
Notifications
You must be signed in to change notification settings - Fork 1.9k
body v1 updater #3289
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
stefpi
wants to merge
23
commits into
master
Choose a base branch
from
body-v1-update
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+179
−1
Draft
body v1 updater #3289
Changes from all commits
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
5a38900
update() on init
stefpi 13b280a
lazy import to fix ci
stefpi c6df953
move can flasher to standalone in opendbc
stefpi bc20c8a
fix flash script, allow UDS querys through body safety
stefpi f200718
move constants to body values
stefpi 45cee7b
fix linter and tests
stefpi a08cea6
Merge branch 'master' into body-v1-update
stefpi 5c1162f
add uds test
stefpi eb80c88
update body v1 fingerprint
stefpi 4235675
rearrange values
stefpi 9670bdc
refactor to use existing uds firmware query route
stefpi 1534420
make current_signature optional
stefpi 5b6f70e
remove unused imports
stefpi 10f1551
add back uds software fingerprint definitions
stefpi ceb84dc
add missing software fingerprint fallback
stefpi 25cd6cf
uds bypass in body safety no longer needed
stefpi a004871
clean diff
stefpi 2c0b932
increase timout on test_fw_query_timing for body
stefpi dcf614c
clear carparams cache after flash
stefpi d42142d
move can send/recv adapter to init()
stefpi ac8d600
remove extra fingerprints
stefpi 8955dd8
Merge branch 'master' into body-v1-update
stefpi 2f2ee6f
remove custom uds request
stefpi File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,148 @@ | ||
| import os | ||
| import time | ||
| import struct | ||
| import subprocess | ||
| from itertools import accumulate | ||
|
|
||
| from opendbc.car.uds import CanClient, IsoTpMessage, MessageTimeoutError | ||
|
|
||
| REQUEST_IN = 0xC0 | ||
| REQUEST_OUT = 0x40 | ||
| DEFAULT_ISOTP_TIMEOUT = 2 | ||
|
|
||
| F4Config = { | ||
| "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 flush_recv_buffer(can_recv): | ||
| while (1): | ||
| if len(can_recv()) == 0: | ||
| break | ||
|
|
||
|
|
||
| def fetch_bin(bin_path, update_url): | ||
| os.makedirs(os.path.dirname(bin_path), exist_ok=True) | ||
| result = subprocess.run( | ||
| ["curl", "-L", "-o", bin_path, "-w", "%{http_code}", "--silent", update_url], | ||
| capture_output=True, text=True | ||
| ) | ||
| status = result.stdout.strip() | ||
| if status == "200": | ||
| print("downloaded latest body firmware binary") | ||
| else: | ||
| raise RuntimeError(f"download failed with HTTP {status}") | ||
|
Comment on lines
+61
to
+71
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. openpilot will pass this in from the panda build obj dir |
||
|
|
||
|
|
||
| def flash_can(handle, code, mcu_config): | ||
| assert mcu_config is not None, "must set valid mcu_type to flash" | ||
|
|
||
| # confirm flasher is present | ||
| fr = handle.controlRead(REQUEST_IN, 0xb0, 0, 0, 0xc) | ||
| assert fr[4:8] == b"\xde\xad\xd0\x0d" | ||
|
|
||
| apps_sectors_cumsum = accumulate(mcu_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." | ||
|
|
||
| print("flash: unlocking") | ||
| handle.controlWrite(REQUEST_IN, 0xb1, 0, 0, b'') | ||
|
|
||
| print(f"flash: erasing sectors 1 - {last_sector}") | ||
| for i in range(1, last_sector + 1): | ||
| handle.controlWrite(REQUEST_IN, 0xb2, i, 0, b'') | ||
|
|
||
| STEP = 0x10 | ||
| print("flash: flashing") | ||
| for i in range(0, len(code), STEP): | ||
| handle.bulkWrite(2, code[i:i + STEP]) | ||
|
|
||
|
|
||
| def reset_body(handle): | ||
| print("flash: resetting") | ||
| handle.controlWrite(REQUEST_IN, 0xd8, 0, 0, b'', expect_disconnect=True) | ||
|
|
||
|
|
||
| def update(can_send, can_recv, addr, bus, file, update_url, current_signature=None): | ||
| if not os.path.exists(file): | ||
| print("local bin is not up-to-date, fetching latest") | ||
| fetch_bin(file, update_url) | ||
|
|
||
| if current_signature is not None: | ||
| print("checking body firmware signature") | ||
| with open(file, "rb") as f: | ||
| expected_signature = f.read()[-128:] | ||
|
|
||
| print(f"expected body signature: {expected_signature.hex()}") | ||
| print(f"current body signature: {current_signature.hex()}") | ||
|
|
||
| if current_signature is None or current_signature != expected_signature: | ||
| print("flashing motherboard") | ||
| can_send(addr, b"\xce\xfa\xad\xde\x1e\x0b\xb0\x0a", bus) | ||
| time.sleep(0.1) | ||
|
|
||
| print("flashing", file) | ||
| flush_recv_buffer(can_recv) | ||
| with open(file, "rb") as f: | ||
| code = f.read() | ||
| handle = CanHandle(can_send, can_recv, bus) | ||
| retries = 3 | ||
| for i in range(retries): | ||
| try: | ||
| flash_can(handle, code, F4Config) | ||
| reset_body(handle) | ||
| except (TimeoutError, RuntimeError) as e: | ||
| print(f"flash failed (attempt {i + 1}/{retries}): {e}, trying again...") | ||
| else: | ||
| print("successfully flashed") | ||
|
|
||
| # Clear cached CarParams so FW queries run fresh after flash | ||
| car_params_cache = "/data/params/d/CarParamsCache" | ||
| if os.path.isfile(car_params_cache): | ||
| os.remove(car_params_cache) | ||
| print("cleared CarParamsCache") | ||
| return | ||
|
|
||
| # on fail: attempt to exit bootloader | ||
| reset_body(handle) | ||
| raise RuntimeError(f"flash failed after {retries} attempts") | ||
| else: | ||
| print("body firmware is up to date") | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,4 @@ | ||
| import os | ||
| from opendbc.car import Bus, CarSpecs, PlatformConfig, Platforms | ||
| from opendbc.car.structs import CarParams | ||
| from opendbc.car.docs_definitions import CarDocs | ||
|
|
@@ -7,6 +8,15 @@ | |
|
|
||
| SPEED_FROM_RPM = 0.008587 | ||
|
|
||
| FIRMWARE_VERSION = "v0.3.1" | ||
| BIN_URL = f"https://github.com/commaai/body/releases/download/{FIRMWARE_VERSION}/body.bin.signed" | ||
| BIN_NAME = f"body-v1-{FIRMWARE_VERSION}.bin.signed" | ||
| BODY_DIR = os.path.dirname(os.path.realpath(__file__)) | ||
| _PANDA_BIN_DIR = os.path.join(BODY_DIR, "../../../../panda/board/body/v1") | ||
|
Comment on lines
+11
to
+15
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. let's just move the v1 stuff into panda |
||
| BIN_PATH = os.path.join(_PANDA_BIN_DIR, BIN_NAME) if os.path.isdir(_PANDA_BIN_DIR) else os.path.join(BODY_DIR, BIN_NAME) | ||
| FLASH_ADDR = 0x250 | ||
| BUS = 0 | ||
|
|
||
|
|
||
| class CarControllerParams: | ||
| ANGLE_DELTA_BP = [0., 5., 15.] | ||
|
|
||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should live in panda