diff --git a/check_rwd.py b/check_rwd.py deleted file mode 100755 index 4ba785421..000000000 --- a/check_rwd.py +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env python3 -"""Run the NRDR RWD validator from the repository root.""" - -from openpilot.sunnypilot.nrdr.tools.eps.launcher import exec_tool - - -if __name__ == "__main__": - exec_tool("check_rwd.py") diff --git a/eps-diag.py b/eps-diag.py deleted file mode 100755 index 6c53f65c7..000000000 --- a/eps-diag.py +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env python3 -"""Run the NRDR EPS diagnostic tool from the repository root.""" - -from openpilot.sunnypilot.nrdr.tools.eps.launcher import exec_tool - - -if __name__ == "__main__": - exec_tool("eps-diag.py") diff --git a/eps-update.py b/eps-update.py deleted file mode 100755 index 8029ac00f..000000000 --- a/eps-update.py +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env python3 -"""Run the NRDR EPS firmware updater from the repository root.""" - -from openpilot.sunnypilot.nrdr.tools.eps.launcher import exec_tool - - -if __name__ == "__main__": - exec_tool("eps-update.py") diff --git a/eps_tools/README.md b/eps_tools/README.md deleted file mode 100644 index e56976954..000000000 --- a/eps_tools/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# EPS tools moved - -The EPS implementation, firmware images, parser, and documentation now live at -[`openpilot/sunnypilot/nrdr/tools/eps`](../openpilot/sunnypilot/nrdr/tools/eps/README.md). - -These compatibility launchers keep existing `eps_tools/...` commands working. -New instructions use the shorter commands from the repository root, such as -`python3 flash.py`. diff --git a/eps_tools/_bootstrap.py b/eps_tools/_bootstrap.py deleted file mode 100644 index f83b202b5..000000000 --- a/eps_tools/_bootstrap.py +++ /dev/null @@ -1,14 +0,0 @@ -"""Compatibility bootstrap for the former eps_tools command paths.""" - -from pathlib import Path -import sys - - -REPOSITORY_ROOT = Path(__file__).resolve().parents[1] -if str(REPOSITORY_ROOT) not in sys.path: - sys.path.insert(0, str(REPOSITORY_ROOT)) - -from openpilot.sunnypilot.nrdr.tools.eps.launcher import exec_tool - - -__all__ = ("exec_tool",) diff --git a/eps_tools/check_rwd.py b/eps_tools/check_rwd.py deleted file mode 100755 index 81fe3aa9e..000000000 --- a/eps_tools/check_rwd.py +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env python3 -"""Compatibility launcher for the canonical NRDR RWD validator.""" - -from _bootstrap import exec_tool - - -if __name__ == "__main__": - exec_tool("check_rwd.py") diff --git a/eps_tools/eps-diag.py b/eps_tools/eps-diag.py deleted file mode 100755 index 2e94cec47..000000000 --- a/eps_tools/eps-diag.py +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env python3 -"""Compatibility launcher for the canonical NRDR EPS diagnostic tool.""" - -from _bootstrap import exec_tool - - -if __name__ == "__main__": - exec_tool("eps-diag.py") diff --git a/eps_tools/eps-update.py b/eps_tools/eps-update.py deleted file mode 100755 index 1c19e54e1..000000000 --- a/eps_tools/eps-update.py +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env python3 -"""Compatibility launcher for the canonical NRDR EPS updater.""" - -from _bootstrap import exec_tool - - -if __name__ == "__main__": - exec_tool("eps-update.py") diff --git a/eps_tools/flash.py b/eps_tools/flash.py deleted file mode 100755 index 1c1faf4af..000000000 --- a/eps_tools/flash.py +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env python3 -"""Compatibility launcher for the canonical NRDR EPS flasher.""" - -from _bootstrap import exec_tool - - -if __name__ == "__main__": - exec_tool("flash.py") diff --git a/flash.py b/flash.py deleted file mode 100755 index 8a5f08573..000000000 --- a/flash.py +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env python3 -"""Run the guided NRDR EPS flasher from the repository root.""" - -from openpilot.sunnypilot.nrdr.tools.eps.launcher import exec_tool - - -if __name__ == "__main__": - exec_tool("flash.py") diff --git a/openpilot/selfdrive/ui/sunnypilot/layouts/settings/eps_flash.py b/openpilot/selfdrive/ui/sunnypilot/layouts/settings/eps_flash.py deleted file mode 100644 index 30daa872d..000000000 --- a/openpilot/selfdrive/ui/sunnypilot/layouts/settings/eps_flash.py +++ /dev/null @@ -1,345 +0,0 @@ -"""EPS firmware flasher settings panel.""" -import importlib.util -import json -from pathlib import Path -import subprocess -import sys -import time - -from openpilot.selfdrive.ui.ui_state import ui_state -from openpilot.system.ui.lib.application import gui_app -from openpilot.system.ui.lib.multilang import tr -from openpilot.system.ui.widgets import DialogResult, Widget -from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog, alert_dialog -from openpilot.system.ui.widgets.scroller_tici import Scroller -from openpilot.system.ui.sunnypilot.widgets.list_view import LineSeparatorSP, button_item_sp - -EPS_DIR = Path("/data/openpilot/openpilot/sunnypilot/nrdr/tools/eps") -RUNNER = EPS_DIR / "ui_flash_runner.py" -STATUS_PATH = Path("/data/eps_flash/status.json") -PANDAD_BLOCK_FILE = Path("/data/eps_flash/block_pandad") -if str(EPS_DIR) not in sys.path: - sys.path.insert(0, str(EPS_DIR)) - - -def _load_eps_flash(): - spec = importlib.util.spec_from_file_location("eps_flash_module", EPS_DIR / "flash.py") - if spec is None or spec.loader is None: - raise ImportError("Could not load EPS flash module") - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) - return module - - -flash = _load_eps_flash() - - -class EPSFlashLayout(Widget): - def __init__(self): - super().__init__() - self._status: dict = {} - self._last_poll = 0.0 - self._last_match_poll = 0.0 - self._launch_grace_until = 0.0 - self._detected_fw = None - self._matched_images: list[Path] = [] - self._selected_image: Path | None = None - - self._status_item = button_item_sp( - lambda: tr("EPS Flash"), - lambda: self._status_label(), - lambda: self._status_description(), - callback=self._show_status, - ) - - self._release_item = button_item_sp( - lambda: tr("Panda USB"), - lambda: tr("RELEASE"), - lambda: tr("Offroad only. Stops pandad so Python can open Panda directly."), - callback=self._confirm_release, - enabled=self._can_release, - ) - - self._eps_info_item = button_item_sp( - lambda: tr("EPS Info") + " · " + self._eps_info_label(), - lambda: self._eps_info_button(), - lambda: self._eps_info_description(), - callback=self._read_eps, - enabled=self._can_read_eps, - ) - - self._restore_item = button_item_sp( - lambda: tr("Panda USB"), - lambda: tr("RESTORE"), - lambda: tr("Removes the release lock and lets manager restart pandad."), - callback=self._restore_panda, - enabled=self._can_restore, - ) - - self._match_item = button_item_sp( - lambda: tr("Detected EPS Firmware"), - lambda: tr("FLASH"), - lambda: self._match_description(), - callback=self._confirm_flash, - enabled=self._can_flash, - ) - - self._no_match_item = button_item_sp( - lambda: tr("Detected EPS Firmware"), - lambda: tr("NO MATCH"), - lambda: self._no_match_description(), - callback=None, - enabled=False, - ) - - self._selection_items: dict[Path, object] = {} - self._items = [ - self._status_item, - LineSeparatorSP(40), - self._release_item, - LineSeparatorSP(40), - self._eps_info_item, - LineSeparatorSP(40), - self._restore_item, - LineSeparatorSP(40), - self._match_item, - self._no_match_item, - ] - - for raw_path in flash.find_images(): - image = Path(raw_path).resolve() - category = tr("Proper Torque Mod") if "Proper Torque Mod" in str(image) else tr("Stock recovery") - item = button_item_sp( - flash.image_display_name(str(image)), - lambda: tr("SELECT"), - category, - callback=lambda _=None, p=image: self._select_image(p), - enabled=lambda: not self._is_running(), - ) - item.set_visible(False) - self._selection_items[image] = item - self._items.extend([item, LineSeparatorSP(40)]) - - self._scroller = Scroller(self._items, line_separator=False, spacing=0) - self._child(self._scroller) - self._refresh_matches(force=True) - - def _refresh_status(self): - now = time.monotonic() - if now - self._last_poll < 0.5: - return - self._last_poll = now - try: - self._status = json.loads(STATUS_PATH.read_text(encoding="utf-8")) - except (OSError, ValueError, TypeError): - self._status = {} - - def _refresh_matches(self, force: bool = False): - now = time.monotonic() - if not force and now - self._last_match_poll < 1.0: - return - self._last_match_poll = now - - self._refresh_status() - car_fw = flash.car_eps_fw() or self._status.get("detected_fw") - car_norm = flash.norm_fw(car_fw) if car_fw else None - matched: list[Path] = [] - if car_norm: - for raw_path in flash.find_images(): - image = Path(raw_path).resolve() - versions = flash.rwd_supported_versions(str(image)) - if versions and car_norm in versions and flash.validate(str(image)): - matched.append(image) - - matched.sort(key=lambda p: (0 if "Proper Torque Mod" in str(p) else 1, str(p))) - self._detected_fw = car_fw - self._matched_images = matched - - if self._selected_image not in matched: - self._selected_image = next((p for p in matched if "Proper Torque Mod" in str(p)), matched[0] if matched else None) - - for image, item in self._selection_items.items(): - item.set_visible(image in matched) - - self._match_item.set_visible(bool(matched)) - self._no_match_item.set_visible(not matched) - - def _is_running(self) -> bool: - if time.monotonic() < self._launch_grace_until: - return True - self._refresh_status() - if self._status.get("state") != "running": - return False - try: - return time.time() - float(self._status.get("updated_at", 0)) < 30.0 - except (TypeError, ValueError): - return False - - def _has_panda_lock(self) -> bool: - return PANDAD_BLOCK_FILE.exists() - - def _can_release(self) -> bool: - return ui_state.is_offroad() and not self._is_running() and not self._has_panda_lock() - - def _can_restore(self) -> bool: - return not self._is_running() and self._has_panda_lock() - - def _can_read_eps(self) -> bool: - return ui_state.is_offroad() and not self._is_running() and self._has_panda_lock() - - def _eps_info_button(self) -> str: - self._refresh_status() - if self._is_running() and self._status.get("action") == "identify": - return tr("READING") - if self._status.get("eps_read_at"): - return tr("RE-READ") - return tr("READ") - - def _eps_info_label(self) -> str: - self._refresh_status() - if self._is_running() and self._status.get("action") == "identify": - return tr("READING") - if self._status.get("eps_read_at"): - return tr("ONLINE") if self._status.get("eps_online") else tr("NOT FOUND") - return tr("NOT READ") - - def _eps_info_description(self) -> str: - self._refresh_status() - if not self._has_panda_lock(): - return tr("Release Panda first, then press READ to query EPS software ID and VIN.") - if self._is_running() and self._status.get("action") == "identify": - return tr("Reading EPS software ID and VIN...") - if not self._status.get("eps_read_at"): - return tr("Panda is released. Press READ to query EPS.") - if not self._status.get("eps_online"): - return tr("No EPS response was received.") - parts = [tr("Bus: {}").format(self._status.get("eps_bus"))] - if self._status.get("eps_part_number"): - parts.append(tr("Part: {}").format(self._status.get("eps_part_number"))) - if self._status.get("eps_vin"): - parts.append(tr("VIN: {}").format(self._status.get("eps_vin"))) - return " · ".join(parts) - - def _read_eps(self): - self._start_action("identify") - - def _can_flash(self) -> bool: - return (ui_state.is_offroad() and not self._is_running() and self._has_panda_lock() and - self._selected_image is not None and self._selected_image in self._matched_images) - - def _status_label(self) -> str: - self._refresh_status() - state = self._status.get("state") - if state == "success": - phase = self._status.get("phase") - if phase == "panda_released": - return tr("PANDA RELEASED") - if phase == "panda_restored": - return tr("PANDA RESTORED") - if phase == "complete": - return tr("FLASH SUCCESS") - return tr("SUCCESS") - if state == "failed": - return tr("FAILED") - if self._is_running(): - progress = self._status.get("progress") - if isinstance(progress, (int, float)) and progress > 0: - return f"{int(progress)}%" - return str(self._status.get("phase", "running")).replace("_", " ").upper() - return tr("PANDA RESTORED") if not self._has_panda_lock() else tr("PANDA RELEASED") - - def _status_description(self) -> str: - self._refresh_status() - if not self._status: - return tr("Use RELEASE first, then select a matched firmware and FLASH.") - image = self._status.get("image", "") - message = self._status.get("message", "") - phase = str(self._status.get("phase", "")).replace("_", " ").title() - prefix = f"{image}: " if image else "" - detail = f"{phase}: {message}" if phase and message else str(message) - return prefix + detail - - def _match_description(self) -> str: - if not self._detected_fw: - return tr("No EPS firmware reported by CarParams. Flashing is disabled.") - if not self._matched_images: - return tr("No curated firmware matches {}. Flashing is disabled.").format(self._detected_fw) - name = flash.image_display_name(str(self._selected_image)) - return tr("Matched {} image(s). Selected: {}").format(len(self._matched_images), name) - - def _no_match_description(self) -> str: - fw = self._detected_fw or tr("(not reported)") - return tr("No curated firmware matches {}. Flashing is disabled until the exact EPS firmware is supported.").format(fw) - - def _select_image(self, image: Path): - self._selected_image = image - - def _show_status(self): - self._refresh_status() - if not self._status: - gui_app.push_widget(alert_dialog(tr("No EPS helper action has run yet."))) - return - gui_app.push_widget(alert_dialog(f"{self._status_label()}: {self._status.get('message', '')}")) - - def _confirm_release(self): - text = tr( - "Release Panda?

" - "This stops pandad and keeps the Panda USB interface reserved for EPS flashing. " - "Use RESTORE afterward. Only run this while the vehicle is offroad." - ) - dialog = ConfirmDialog(text, tr("RELEASE"), tr("CANCEL"), rich=True, - callback=lambda result: self._start_action("release") if result == DialogResult.CONFIRM else None) - gui_app.push_widget(dialog) - - def _restore_panda(self): - self._start_action("restore") - - def _confirm_flash(self): - image = self._selected_image - if image is None or image not in self._matched_images: - gui_app.push_widget(alert_dialog(tr("No matching firmware; flashing is disabled."))) - return - if not self._has_panda_lock(): - gui_app.push_widget(alert_dialog(tr("Release Panda first."))) - return - - text = tr( - "Flash matched firmware?

" - "Firmware: {}

" - "Keep the vehicle in ACCESSORY mode and do not interrupt power.

" - "The tool runs a dry run and then flashes automatically. A bad or incompatible " - "firmware can permanently damage the EPS." - ).format(flash.image_display_name(str(image))) - dialog = ConfirmDialog(text, tr("FLASH"), tr("CANCEL"), rich=True, - callback=lambda result: self._start_action("flash", image) if result == DialogResult.CONFIRM else None) - gui_app.push_widget(dialog) - - def _start_action(self, action: str, image: Path | None = None): - command = [sys.executable, str(RUNNER), "--action", action] - if image is not None: - command.extend(["--rwd", str(image)]) - try: - subprocess.Popen( - command, - cwd=EPS_DIR, - stdin=subprocess.DEVNULL, - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, - start_new_session=True, - ) - except Exception as exc: - gui_app.push_widget(alert_dialog(tr("Unable to start EPS action: {}").format(exc))) - return - self._launch_grace_until = time.monotonic() + 5.0 - - def _update_state(self): - super()._update_state() - self._refresh_status() - self._refresh_matches() - - def _render(self, rect): - self._scroller.render(rect) - - def show_event(self): - self._refresh_matches(force=True) - self._scroller.show_event() \ No newline at end of file diff --git a/openpilot/selfdrive/ui/sunnypilot/layouts/settings/settings.py b/openpilot/selfdrive/ui/sunnypilot/layouts/settings/settings.py index 6ee98a2af..a5b66b3d3 100644 --- a/openpilot/selfdrive/ui/sunnypilot/layouts/settings/settings.py +++ b/openpilot/selfdrive/ui/sunnypilot/layouts/settings/settings.py @@ -15,7 +15,6 @@ from openpilot.selfdrive.ui.sunnypilot.layouts.settings.cruise import CruiseLayo from openpilot.selfdrive.ui.sunnypilot.layouts.settings.developer import DeveloperLayoutSP from openpilot.selfdrive.ui.sunnypilot.layouts.settings.device import DeviceLayoutSP from openpilot.selfdrive.ui.sunnypilot.layouts.settings.display import DisplayLayout -from openpilot.selfdrive.ui.sunnypilot.layouts.settings.eps_flash import EPSFlashLayout from openpilot.selfdrive.ui.sunnypilot.layouts.settings.models import ModelsLayout from openpilot.selfdrive.ui.sunnypilot.layouts.settings.network import NetworkUISP from openpilot.selfdrive.ui.sunnypilot.layouts.settings.osm import OSMLayout @@ -44,7 +43,6 @@ OP.PanelType = IntEnum( # type: ignore[assignment] # ty: ignore[invalid-assignm "SUNNYLINK", "MODELS", "STEERING", - "EPS_FLASH", "CRUISE", "VISUALS", "DISPLAY", @@ -119,7 +117,6 @@ class SettingsLayoutSP(OP.SettingsLayout): OP.PanelType.SOFTWARE: PanelInfo(tr_noop("Software"), SoftwareLayoutSP(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_software.png"), OP.PanelType.MODELS: PanelInfo(tr_noop("Models"), ModelsLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_models.png"), OP.PanelType.STEERING: PanelInfo(tr_noop("Steering"), SteeringLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_lateral.png"), - OP.PanelType.EPS_FLASH: PanelInfo(tr_noop("EPS Flash"), EPSFlashLayout(), icon="icons/shell.png"), OP.PanelType.CRUISE: PanelInfo(tr_noop("Cruise"), CruiseLayout(), icon="icons/speed_limit.png"), OP.PanelType.VISUALS: PanelInfo(tr_noop("Visuals"), VisualsLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_visuals.png"), OP.PanelType.DISPLAY: PanelInfo(tr_noop("Display"), DisplayLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_display.png"), diff --git a/openpilot/sunnypilot/nrdr/__init__.py b/openpilot/sunnypilot/nrdr/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/openpilot/sunnypilot/nrdr/tools/__init__.py b/openpilot/sunnypilot/nrdr/tools/__init__.py deleted file mode 100644 index d5b103ab4..000000000 --- a/openpilot/sunnypilot/nrdr/tools/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""NRDR-owned offline and device tools.""" diff --git a/openpilot/sunnypilot/nrdr/tools/eps/README.md b/openpilot/sunnypilot/nrdr/tools/eps/README.md deleted file mode 100644 index 0a9b13c72..000000000 --- a/openpilot/sunnypilot/nrdr/tools/eps/README.md +++ /dev/null @@ -1,197 +0,0 @@ -# EPS tools - -Standalone tooling for reading, validating, and flashing Honda/Acura EPS -firmware (`.rwd`). Works on the current opendbc layout — no panda-submodule or -`selfdrive.car` dependencies. - -Credit to mmmorks for the original flasher scripts, and to -[RiskyBiscuit-arc](https://github.com/RiskyBiscuit-arc) for updating them to the -current opendbc layout and adding the safety checks. - -> **Disclaimer:** Flashing EPS firmware can damage or permanently brick your -> power-steering ECU. **You alone are responsible for your EPS and your vehicle, -> and you use these tools entirely at your own risk** — no warranty of any kind, -> express or implied. Always validate the image (`check_rwd.py`) and do a dry run -> before flashing. - -``` -openpilot/sunnypilot/nrdr/tools/eps/ - flash.py guided flash: stop op -> pick image -> dry run -> --danger - eps-update.py UDS .rwd flasher (validate + erase/program over CAN) - check_rwd.py offline .rwd checksum validator (stdlib only) - eps-diag.py EPS CAN liveness/diagnostic (sniff, UDS ping, part number) - rwd_format/ vendored Python-3 .rwd container parser (0x5A/0x31) - rwd/ curated nested firmware library + hashes/safety catalog -``` - -The normal commands run from the **openpilot repository root**. The small root -launchers preserve arguments, prompts, and exit codes while finding the parser -and firmware here. Historical `eps_tools/...` commands remain compatibility -launchers, but contain no flashing implementation or firmware. - -## Guided flash (recommended) - -`flash.py` walks the whole thing for you: reads the car's current EPS firmware -and searches an explicit 15-file menu containing the seven Proper Torque Mod -images and eight clearly labelled stock recovery images. It lists only choices -compatible with the detected EPS (with checksum status), lets you pick one, -auto-detects the EPS CAN bus via a live UDS probe of the selected image's address -(asks if more than one bus responds), offers a dry run, and only flashes after -you confirm. If nothing matches your EPS (or the car isn't fingerprinted yet), -it asks before listing all curated choices. Nested category paths are shown so -similar filenames cannot be mistaken for one another. - -Legacy/testing images, the T6Z offline reference, TVA x31 files, the -extensionless Pilot WIP, and raw binaries are intentionally absent from the -guided menu. See `rwd/README.md` before using anything outside that menu. - -```bash -python3 flash.py # auto-detect bus (fallback default 1) -python3 flash.py --bus 0 # pin to a bus -python3 flash.py --skip-dry-run # skip dry-run prompt + dry run (not recommended) -python3 flash.py --seed-timeout 600 # wait up to 10 min for a security lockout to clear -``` - -It confirms the car is OFF, stops openpilot, then prompts you to switch to -accessory mode (ignition ON, engine OFF) before bus detect / dry run / flash. - -**Dry run:** by default you get an in-script choice — run dry run first -(recommended) or skip (not recommended). Some EPS units lock security access -after a dry run; if the real flash then fails with a timeout/security error, -`flash.py` offers a retry menu (choosing retry is enough — no second `FLASH` -prompt). `--skip-dry-run` bypasses the prompt and skips the dry run entirely. - -After the flash it offers a menu: run `eps-diag.py` as a post-flash check (which -loops back to the menu), restore openpilot (it was stopped for flashing), or -reboot. The manual steps below are the same flow if you'd rather run each -command yourself. - -## How to flash (manual) - -1. Make sure the comma power is connected to the car's OBD2 port. -2. With the car **OFF**, stop openpilot over SSH: - ``` - sudo systemctl stop comma - tmux kill-session -t comma 2>/dev/null || true - systemctl is-active comma - ``` - Do not continue if the final command reports `active`, `activating`, - `deactivating`, or `reloading`. Do not use `pkill -f openpilot`; a command - path containing `openpilot` can make it kill the flashing helper itself. -3. Put the car in full **accessory mode** (ignition ON, engine OFF). Turn off the - A/C to avoid draining the battery. -4. **Dry run first** — validates the image and walks the UDS/security flow but - aborts *before* any mutating action (`eps-update.py` defaults to bus **1**): - ``` - python3 eps-update.py "rwd/MODEL/CATEGORY/REPLACE_WITH_YOUR_FIRMWARE.rwd" -b 1 - ``` - When it aborts before performing mutating actions, that's your sign it's ready. - **Note:** some cars lock security access after a dry run. If the real flash - then fails, wait / power-cycle (OFF → accessory) and retry `--danger`, or skip - the dry run on a subsequent attempt. See - [Security-access lockout](#security-access-lockout-nrc-0x37) below. -5. **Flash for real** once you're committed: - ``` - python3 eps-update.py "rwd/MODEL/CATEGORY/REPLACE_WITH_YOUR_FIRMWARE.rwd" -b 1 --danger - ``` - You'll see warnings/errors on the dash while it flashes — that's normal. When - it reaches "Resetting ECU" with no traceback, turn the car off. Done. - -Example: -``` -python3 eps-update.py "rwd/39990-TLA-A040 - (Honda_CRV_5G)/Proper Torque Mod/39990-TLA-A040_Clarity_FF_tune_telemety_8cf8e537.rwd" -b 1 --danger -``` - -`eps-update.py` is the power-user flasher: you pass `-b` explicitly (default **1**). -Bus auto-detect lives only in `flash.py`. - -### Security-access lockout (NRC 0x37) - -If a run dies with: - -``` -NegativeResponseError: SECURITY_ACCESS - required time delay not expired -``` - -the EPS responded far enough to report its security-access delay. That confirms -this stage of communication only; it does **not** prove that the selected image -matches the vehicle or that the full CAN/power setup is safe for flashing. The -EPS is refusing to hand out an unlock seed because its delay timer is running -(typically a settling window enforced after power-up). - -**You do not need any flags for this.** A normal `flash.py` run already retries: -`eps-update.py` re-requests the seed every 10s for up to 120s and reports how long -the unlock took (so you learn your car's actual delay), and if 120s isn't enough -the dry run simply asks whether to keep waiting another 5 minutes: - -``` -The EPS security-access delay hasn't cleared after 120s. -The ECU reported its delay timer. Waiting may clear this response; -it does not prove image compatibility. -Keep waiting another 300s? [Y/n]: -``` - -`--seed-timeout N` is only for **unattended** runs (no operator to answer that -prompt): it pins the retry window to N seconds and skips the keep-waiting question. - -``` -python3 flash.py --seed-timeout 600 # scripted, 10-min window -python3 eps-update.py rwd/YOUR_FIRMWARE.rwd -b 1 --seed-timeout 600 # raw flasher -``` - -Leave the car in accessory mode while it retries; turning the ignition off -restarts the timer. Only the *seed request* is retried — a rejected seed request -doesn't touch the attempt counter, but re-sending a **key** (NRC 0x35, invalid -key) increments it and restarts the delay, so that error is never retried. - -### `--skip-checksum` (not recommended) -If a firmware isn't covered by the checksum checker, you can add `--skip-checksum`. -**Be careful — flashing a `.rwd` with invalid checksums can brick the EPS.** Only -use it on an image you trust. -``` -python3 eps-update.py rwd/SOME_FIRMWARE.rwd -b 1 --skip-checksum --danger -``` - -## Verify it worked -Turn the car back on and move the wheel by hand. Basic power-steering assist -only shows that the EPS is responding enough to provide assist; it does **not** -prove that the image, calibration, or safety behavior is correct. Confirm over -CAN and check that the reported part number is the one you expected: -``` -python3 eps-diag.py # checks buses 0 and 1 by default -python3 eps-diag.py -b 1 # pin to one bus -``` - -`eps-diag.py` reports which bus saw EPS activity and the detected part number when -available. For recovery it points you back to `flash.py` and choosing the **stock** -image for your EPS (it does not invent a specific `.rwd` filename). - -## If a flash fails / crashes -A failed or interrupted flash can leave the car without power-steering assist. -Do not drive it in that state. Recovery may be possible, but permanent EPS -damage or a nonrecoverable brick remains possible; repeated flashing is not a -guaranteed fix. - -Only retry with a verified, compatible image after diagnosing why the first run -failed, confirming stable vehicle power, checking the Panda/CAN connection, and -following a recovery procedure known to apply to that exact EPS. Keep the -verified stock image for that EPS available before starting any flash. If the -failure mode or compatibility is uncertain, stop and get experienced help -instead of guessing with `--danger`. - -For an image that is not tracked in this repository, flash from a persistent -copy under `/data/media/0/`. The updater may delete untracked files from -`/data/openpilot`, which could otherwise remove a custom image during a manual -workflow. - -## Validate an image offline -``` -python3 check_rwd.py "rwd/39990-TBA-C120/Proper Torque Mod/39990-TBA,C120-PTM.rwd" -python3 check_rwd.py "rwd/**/*.rwd" # includes unsupported x31 references; overall nonzero is expected -``` - -See `rwd/README.md` for the complete catalog, guided/reference boundaries, -source archive hash, missing slots, and rules for adding another image. - -`rwd/SHA256SUMS` records every shipped image. The validation test requires the -manifest and directory to contain the same exact filenames and bytes. diff --git a/openpilot/sunnypilot/nrdr/tools/eps/__init__.py b/openpilot/sunnypilot/nrdr/tools/eps/__init__.py deleted file mode 100644 index 612fe6afb..000000000 --- a/openpilot/sunnypilot/nrdr/tools/eps/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -"""Honda/Acura EPS firmware inspection, diagnostics, and flashing tools.""" - -from openpilot.sunnypilot.nrdr.tools.eps.launcher import exec_tool - - -__all__ = ("exec_tool",) diff --git a/openpilot/sunnypilot/nrdr/tools/eps/check_rwd.py b/openpilot/sunnypilot/nrdr/tools/eps/check_rwd.py deleted file mode 100644 index f3a28b8fe..000000000 --- a/openpilot/sunnypilot/nrdr/tools/eps/check_rwd.py +++ /dev/null @@ -1,169 +0,0 @@ -#!/usr/bin/env python3 -# Usage: python3 check_rwd.py [file2.rwd ...] - -import struct -import sys - -DECRYPT_LOOKUP = {144: 72, 218: 55, 255: 255, 164: 1, 195: 26, 99: 2, 28: 178, 205: 158, 125: 138, 45: 118, 222: 98, 142: 78, 62: 58, 243: 38, 163: 18, 83: 254, 3: 234, 172: 214, 92: 194, 12: 174, 189: 154, 109: 134, 29: 114, 206: 94, 126: 74, 46: 54, 227: 34, 147: 14, 113: 0, 67: 250, 236: 230, 156: 210, 76: 190, 252: 170, 173: 150, 93: 130, 13: 110, 148: 253, 120: 159, 199: 148, 198: 137, 77: 126, 23: 104, 73: 83, 203: 73, 78: 62, 123: 53, 254: 42, 43: 33, 90: 23, 161: 12, 10: 3, 132: 249, 191: 239, 226: 220, 197: 201, 248: 191, 117: 181, 34: 172, 37: 161, 88: 151, 141: 142, 8: 131, 134: 121, 185: 111, 54: 101, 190: 90, 57: 79, 128: 68, 139: 57, 14: 46, 138: 35, 131: 10, 100: 241, 1: 228, 146: 200, 133: 185, 168: 171, 104: 155, 40: 139, 251: 85, 94: 66, 91: 45, 103: 124, 55: 112, 231: 156, 80: 56, 224: 92, 102: 113, 96: 60, 98: 188, 97: 252, 140: 206, 122: 31, 232: 187, 16: 40, 202: 51, 26: 7, 239: 251, 5: 153, 219: 77, 119: 128, 21: 157, 238: 102, 180: 5, 217: 119, 30: 50, 7: 100, 32: 44, 183: 144, 50: 176, 110: 70, 157: 146, 2: 164, 44: 182, 145: 8, 58: 15, 27: 29, 64: 52, 9: 67, 31: 199, 179: 22, 42: 11, 193: 20, 211: 30, 129: 4, 241: 32, 74: 19, 178: 208, 247: 160, 112: 64, 242: 224, 114: 192, 165: 193, 0: 36, 59: 37, 196: 9, 154: 39, 75: 41, 72: 147, 249: 127, 162: 204, 130: 196, 229: 209, 182: 133, 48: 48, 86: 109, 240: 96, 137: 99, 151: 136, 209: 24, 108: 198, 181: 197, 212: 13, 244: 21, 11: 25, 118: 117, 228: 17, 214: 141, 52: 229, 160: 76, 115: 6, 106: 27, 56: 143, 25: 71, 36: 225, 194: 212, 208: 88, 187: 69, 171: 65, 153: 103, 38: 97, 207: 243, 82: 184, 184: 175, 188: 218, 213: 205, 121: 95, 15: 195, 81: 248, 24: 135, 70: 105, 150: 125, 174: 86, 158: 82, 220: 226, 201: 115, 71: 116, 51: 246, 177: 16, 176: 80, 22: 93, 39: 108, 159: 231, 223: 247, 186: 47, 169: 107, 245: 213, 235: 81, 192: 84, 124: 202, 175: 235, 84: 237, 79: 211, 234: 59, 143: 227, 237: 166, 33: 236, 253: 106, 65: 244, 111: 219, 200: 179, 101: 177, 17: 232, 20: 221, 166: 129, 60: 186, 61: 122, 167: 140, 204: 222, 87: 120, 41: 75, 135: 132, 136: 163, 49: 240, 250: 63, 107: 49, 170: 43, 18: 168, 221: 162, 35: 242, 225: 28, 149: 189, 85: 173, 152: 167, 95: 215, 53: 165, 89: 87, 66: 180, 6: 89, 47: 203, 210: 216, 215: 152, 233: 123, 116: 245, 127: 223, 19: 238, 69: 169, 105: 91, 4: 217, 216: 183, 68: 233, 63: 207, 155: 61, 246: 149, 230: 145} - -FIRMWARE_CHECKSUMS = { - 0x6c000: [(0, 0x6bf80, "sum"), (1, 0x6bffe, "negative-sum")], - 0x4c000: [(0, 0x4bf80, "sum"), (1, 0x4bffe, "negative-sum")], -} -PILOT_APP_ID = b'39990-TG7-A060\x00\x00' -T6Z_APP_IDS = ( - b'39990-T6Z-A110\x00\x00', - b'39990-TG7-A040\x00\x00', - b'39990-TG7-A030\x00\x00', - b'39990-TG7-A020\x00\x00', -) -T6Z_SECRETS = (b'\x00\x11\x00\x12\x10\x20',) * 4 -V850_APP_ID_GROUPS = ((PILOT_APP_ID,), T6Z_APP_IDS) -T6Z_REFERENCE_NOTICE = "".join(( - "T6Z checksum validation is offline/catalog-only; the current ", - "eps-update.py and guided flash.py do not support flashing this image.", -)) -PILOT_KEYS = b'\x01\x02\x03' -PILOT_FLASH_START = 0x10000 -PILOT_CHECKSUM_ENDS = (0xa000, 0x1d000, 0x4ff00) -T6Z_HEADER_PROFILE = ( - (b'\x00',), - (), - (b'0',), # tester address byte 0x30 -> UDS address 0x18DA30F1 - T6Z_APP_IDS, - T6Z_SECRETS, - (PILOT_KEYS,), -) -T6Z_IDENTITY_OFFSET = 0x3438 - - -def check(path): - print(f"\n{'─'*60}") - print(f" {path}") - print(f"{'─'*60}") - - try: - with open(path, 'rb') as f: - raw = f.read() - except FileNotFoundError: - print(f" ERROR: file not found") - return False - - print(f" Size: {len(raw)} bytes (0x{len(raw):x})") - - # 1. File checksum (last 4 bytes, little-endian 32-bit) - stored_fc = struct.unpack(' [file2.rwd ...]") - sys.exit(1) - - results = [(path, check(path)) for path in sys.argv[1:]] - - if len(results) > 1: - print(f"\n{'─'*60}") - print(" SUMMARY") - print(f"{'─'*60}") - for path, ok in results: - print(f" {'PASS ✓' if ok else 'FAIL ✗'} {path}") - - sys.exit(0 if all(ok for _, ok in results) else 1) diff --git a/openpilot/sunnypilot/nrdr/tools/eps/eps-diag.py b/openpilot/sunnypilot/nrdr/tools/eps/eps-diag.py deleted file mode 100644 index cf62afaaf..000000000 --- a/openpilot/sunnypilot/nrdr/tools/eps/eps-diag.py +++ /dev/null @@ -1,352 +0,0 @@ -""" -EPS CAN bus diagnostic tool for Honda/Acura EPS ECUs. - -Checks whether the EPS (Electric Power Steering) ECU is alive on the CAN bus -after a potentially bad flash. Runs three checks: - - 1. Passive sniff – listen for any raw CAN traffic from the EPS physical addr - 2. UDS ping – send Tester Present and wait for a positive response - 3. UDS session – try Default → Extended diagnostic sessions and read the - Application Software ID (part number) - -By default checks buses 0 and 1. Pass --bus to pin to a single bus. - -Usage (on the comma device via SSH): - python eps-diag.py # check buses 0 and 1, address 0x18DA30F1 - python eps-diag.py -b 1 # pin to bus 1 - python eps-diag.py --scan # brute-force scan all common Honda ECU addrs -""" - -import argparse -import os -import sys -import time - -from panda import Panda -from opendbc.car.structs import CarParams -from opendbc.car.uds import ( - UdsClient, - SESSION_TYPE, - DATA_IDENTIFIER_TYPE, - NegativeResponseError, -) - -# ── addresses ──────────────────────────────────────────────────────────────── -EPS_ADDR = 0x18DA30F1 # tester → EPS (29-bit, ISO 15765-4 physical) -DEFAULT_BUSES = (0, 1) - - -def response_addr_for(req_addr: int) -> int: - """Honda ISO-TP physical response for a 29-bit request (0x18DAXXF1 → 0x18DAF1XX).""" - return (req_addr & 0xFFFF0000) | 0x0000F100 | ((req_addr >> 8) & 0xFF) - -# A handful of other Honda ECU addresses useful for --scan -HONDA_SCAN_ADDRS = [ - (0x18DA30F1, "EPS"), - (0x18DA10F1, "PCM/ECM"), - (0x18DA60F1, "Combination meter"), - (0x18DA28F1, "SRS airbag"), - (0x18DA0EF1, "VSA/ABS"), - (0x18DA21F1, "Transmission"), - (0x18DA3DF1, "LKAS camera"), - (0x18DAB0F1, "LKAS camera alt"), -] - -SNIFF_DURATION_S = 3.0 # seconds to passively listen -UDS_TIMEOUT_S = 3.0 # seconds to wait for each UDS response - - -def connect_panda() -> Panda: - p = Panda(disable_checks=True) - p.set_safety_mode(CarParams.SafetyModel.elm327) - print(f" Connected to Panda (serial: {p.get_serial()})") - return p - - -def format_app_id(data) -> str: - if isinstance(data, (bytes, bytearray)): - return bytes(data).decode("latin-1", "replace").strip("\x00").strip() - return str(data).strip("\x00").strip() - - -def car_eps_fw_from_params(): - """EPS firmware string from CarParamsPersistent / CarParams, or None.""" - try: - from opendbc.car.structs import car - except Exception: - if os.path.isdir("/data/openpilot"): - sys.path.append("/data/openpilot") - try: - from opendbc.car.structs import car - except Exception: - try: - from cereal import car - except Exception: - return None - for p in ("/data/params/d/CarParamsPersistent", "/data/params/d/CarParams"): - try: - data = open(p, "rb").read() - except OSError: - continue - try: - with car.CarParams.from_bytes(data) as CP: - for fw in CP.carFw: - if fw.ecu == "eps": - v = format_app_id(bytes(fw.fwVersion)) - if v: - return v - except Exception: - continue - return None - - -def passive_sniff(panda: Panda, buses: list[int], resp_addr: int, - duration: float = SNIFF_DURATION_S) -> dict[int, bool]: - """Listen for raw CAN frames from the EPS response address on each bus.""" - bus_list = list(buses) - label = ", ".join(str(b) for b in bus_list) - print(f"\n{'='*60}") - print(f"[1/3] Passive CAN sniff on bus(es) {label} for {duration:.0f}s …") - print(f" Watching for frames from 0x{resp_addr:08X} (EPS response addr)") - - for bus in bus_list: - panda.can_clear(bus) - - deadline = time.monotonic() + duration - eps_counts = {b: 0 for b in bus_list} - all_extended: dict[int, set[int]] = {b: set() for b in bus_list} - - while time.monotonic() < deadline: - for addr, dat, recv_bus in panda.can_recv(): - if recv_bus not in eps_counts: - continue - if addr > 0x7FF: # 29-bit extended frame - all_extended[recv_bus].add(addr) - if addr == resp_addr: - eps_counts[recv_bus] += 1 - print(f" *** EPS frame on bus {recv_bus}! addr=0x{addr:08X} data={dat.hex()}") - - results = {} - for bus in bus_list: - count = eps_counts[bus] - results[bus] = count > 0 - if count: - print(f" RESULT bus {bus}: EPS responding passively ({count} frames)") - else: - print(f" RESULT bus {bus}: No frames from EPS response addr") - ext = all_extended[bus] - if ext: - print(f" Other 29-bit addresses seen: " + - ", ".join(f"0x{a:08X}" for a in sorted(ext))) - else: - print(" No 29-bit extended frames seen on this bus.") - - return results - - -def uds_tester_present(uds: UdsClient, bus: int) -> bool: - """Send a single Tester Present and check whether the EPS is present on this bus. - - A negative response still means the ECU is alive (same idea as flash.py probe). - """ - print(f"\n{'='*60}") - print(f"[2/3] UDS Tester Present ping (bus {bus}) …") - try: - uds.tester_present() - print(" RESULT: EPS acknowledged Tester Present ✓") - return True - except NegativeResponseError as e: - print(f" RESULT: EPS present (negative response): {e}") - return True - except Exception as e: - print(f" RESULT: No response / timeout: {e}") - return False - - -def uds_session_and_id(uds: UdsClient, bus: int) -> tuple[bool, str | None]: - """Try default → extended session and read the software part number. - - Returns (session_ok, app_id_or_None). - """ - print(f"\n{'='*60}") - print(f"[3/3] UDS session + Application Software ID read (bus {bus}) …") - app_id = None - - # Default session (0x01) — most ECUs come up here even after a bad flash - try: - uds.diagnostic_session_control(SESSION_TYPE.DEFAULT) - print(" Default session: OK") - except Exception as e: - print(f" Default session failed: {e}") - - # Try reading the part number in default session first - for did, label in [ - (DATA_IDENTIFIER_TYPE.APPLICATION_SOFTWARE_IDENTIFICATION, "Application SW ID (F181)"), - (DATA_IDENTIFIER_TYPE.VIN, "VIN (F190)"), - ]: - try: - data = uds.read_data_by_identifier(did) - print(f" {label}: {data!r}") - if did == DATA_IDENTIFIER_TYPE.APPLICATION_SOFTWARE_IDENTIFICATION: - app_id = format_app_id(data) or app_id - except NegativeResponseError as e: - print(f" {label}: negative response – {e}") - except Exception as e: - print(f" {label}: timeout/error – {e}") - - # Extended diagnostic session - try: - uds.diagnostic_session_control(SESSION_TYPE.EXTENDED_DIAGNOSTIC) - print(" Extended diagnostic session: OK") - - try: - data = uds.read_data_by_identifier(DATA_IDENTIFIER_TYPE.APPLICATION_SOFTWARE_IDENTIFICATION) - print(f" Application SW ID (extended): {data!r}") - app_id = format_app_id(data) or app_id - return True, app_id - except Exception as e: - print(f" Application SW ID (extended): {e}") - except Exception as e: - print(f" Extended diagnostic session failed: {e}") - - return False, app_id - - -def scan_all(panda: Panda, bus: int): - """Probe every common Honda ECU address with a Tester Present.""" - print(f"\n{'='*60}") - print(f"Scanning all common Honda UDS addresses on bus {bus} …\n") - for addr, name in HONDA_SCAN_ADDRS: - uds = UdsClient(panda, addr, bus=bus, timeout=1.5) - try: - uds.tester_present() - print(f" 0x{addr:08X} {name:<25} ALIVE ✓") - except NegativeResponseError as e: - print(f" 0x{addr:08X} {name:<25} negative response: {e}") - except Exception: - print(f" 0x{addr:08X} {name:<25} no response") - - -def print_recovery_advice(kind: str, detected_eps: str | None): - """Print next-step guidance without inventing a specific .rwd filename.""" - if kind == "dead": - print(""" -DIAGNOSIS: EPS is not responding at all on the checked bus(es). - Possible causes: - - Ignition/ACC not on — EPS needs power (key in ON position) - - EPS is bricked (bad flash left it in bootloader with no CAN output) - - Wiring / Panda connection issue - - Wrong bus (if you pinned --bus, try without it to check 0 and 1) - - Next steps: - 1. Confirm ignition is ON (not just ACC) — some Bosch EPS needs full ON - 2. Re-run without --bus to check buses 0 and 1 - 3. Run a full scan: python3 eps-diag.py --scan - 4. If completely unresponsive, stop. Any bench recovery requires an - EPS-specific verified procedure, experienced help, and stable power. -""") - return - - if kind == "bootloader": - print(""" -DIAGNOSIS: EPS is broadcasting CAN frames but not responding to UDS. - This is consistent with a bad flash that left the bootloader active - but the application layer non-functional. The ECU is alive but stuck. -""") - else: - print(""" -DIAGNOSIS: EPS is responding to diagnostics. - That proves basic liveness only. It does not prove the previous image was - compatible or that another flash attempt is safe. -""") - - if detected_eps: - print(f" Detected EPS: {detected_eps}") - print(""" Before any recovery attempt, identify the exact EPS/software part number, - diagnose the original failure, and confirm stable vehicle power and CAN/Panda. - If you have a verified matching stock image and a recovery procedure known to - apply to this EPS, python3 flash.py can show the reviewed candidates. - Do not blindly reuse the previous .rwd or add --danger; a wrong image may have - caused the failure. If compatibility is uncertain, get experienced help. -""") - - -def main(): - ap = argparse.ArgumentParser(description="Honda/Acura EPS CAN bus diagnostic") - ap.add_argument("-b", "--bus", default=None, type=lambda x: int(x, 0), - help="Pin to a single CAN bus (default: check buses 0 and 1)") - ap.add_argument("--addr", default=EPS_ADDR, type=lambda x: int(x, 0), - help=f"EPS UDS address (default: 0x{EPS_ADDR:08X})") - ap.add_argument("--scan", action="store_true", - help="Scan all common Honda ECU addresses") - ap.add_argument("--sniff-only", action="store_true", - help="Only run the passive sniff, skip UDS probes") - args = ap.parse_args() - - buses = [args.bus] if args.bus is not None else list(DEFAULT_BUSES) - resp_addr = response_addr_for(args.addr) - - print("Honda/Acura EPS diagnostic") - print(f" Bus(es): {', '.join(str(b) for b in buses)}") - print(f" EPS addr: 0x{args.addr:08X} (response 0x{resp_addr:08X})") - - try: - panda = connect_panda() - except Exception as e: - print(f"\nFailed to connect to Panda: {e}") - print("Make sure the Panda is connected via USB and the car is in ACC ON / ignition.") - return - - if args.scan: - for bus in buses: - scan_all(panda, bus) - return - - # ── check 1: passive sniff ───────────────────────────────────────────────── - sniff_by_bus = passive_sniff(panda, buses, resp_addr) - eps_alive_passive = any(sniff_by_bus.values()) - - if args.sniff_only: - return - - # ── checks 2 & 3: UDS probes (per bus) ───────────────────────────────────── - ping_by_bus: dict[int, bool] = {} - session_by_bus: dict[int, bool] = {} - app_id = None - - for bus in buses: - uds = UdsClient(panda, args.addr, bus=bus, timeout=UDS_TIMEOUT_S) - ping_by_bus[bus] = uds_tester_present(uds, bus) - ok, found_id = uds_session_and_id(uds, bus) - session_by_bus[bus] = ok - if found_id and not app_id: - app_id = found_id - - eps_alive_ping = any(ping_by_bus.values()) - eps_alive_session = any(session_by_bus.values()) - detected_eps = app_id or car_eps_fw_from_params() - - # ── summary ──────────────────────────────────────────────────────────────── - print(f"\n{'='*60}") - print("SUMMARY") - for bus in buses: - print(f" Bus {bus}:") - print(f" Passive CAN frames: {'YES' if sniff_by_bus.get(bus) else 'NO'}") - print(f" Tester Present: {'YES' if ping_by_bus.get(bus) else 'NO'}") - print(f" UDS session/ID read: {'YES' if session_by_bus.get(bus) else 'NO'}") - responding = [b for b in buses if sniff_by_bus.get(b) or ping_by_bus.get(b) or session_by_bus.get(b)] - if responding: - print(f" EPS activity seen on bus(es): {', '.join(str(b) for b in responding)}") - if detected_eps: - print(f" Detected EPS part number: {detected_eps}") - - if not any([eps_alive_passive, eps_alive_ping, eps_alive_session]): - print_recovery_advice("dead", detected_eps) - elif eps_alive_passive and not eps_alive_ping: - print_recovery_advice("bootloader", detected_eps) - else: - print_recovery_advice("alive", detected_eps) - - -if __name__ == "__main__": - main() diff --git a/openpilot/sunnypilot/nrdr/tools/eps/eps-update.py b/openpilot/sunnypilot/nrdr/tools/eps/eps-update.py deleted file mode 100644 index 2347fd6fd..000000000 --- a/openpilot/sunnypilot/nrdr/tools/eps/eps-update.py +++ /dev/null @@ -1,352 +0,0 @@ -import gzip -import os -import struct -import sys -import time -import tqdm -import traceback -from argparse import ArgumentParser -from opendbc.car.structs import CarParams -from rwd_format.x5a import x5a -from panda import Panda -from opendbc.car.uds import UdsClient, SESSION_TYPE, ACCESS_TYPE, ROUTINE_CONTROL_TYPE, ROUTINE_IDENTIFIER_TYPE, DATA_IDENTIFIER_TYPE, RESET_TYPE, NegativeResponseError -from unittest import mock - -# Honda EPS firmware decryption-key DID. Not present in stock opendbc -# DATA_IDENTIFIER_TYPE (only the old flasher-branch opendbc defined it). -FLASH_DECRYPTION_KEY = 0xF101 - -# NRCs that mean the ECU's security-access delay timer is running — waiting and -# re-requesting the seed is the only way through. 0x35 (invalid key) is -# deliberately absent: re-sending a key increments the ECU's failed-attempt -# counter and restarts the timer, making the lockout worse. -SEED_DELAY_NRCS = {0x36: 'exceed number of attempts', 0x37: 'required time delay not expired'} -SEED_RETRY_INTERVAL_S = 10.0 -DEFAULT_SEED_TIMEOUT_S = 120.0 -# The EPS refuses security access for a period after power-up and immediately -# after a session change; don't send the first seed request into that window. -SESSION_SETTLE_S = 0.5 - -DECRYPT_LOOKUP = {144: 72, 218: 55, 255: 255, 164: 1, 195: 26, 99: 2, 28: 178, 205: 158, 125: 138, 45: 118, 222: 98, 142: 78, 62: 58, 243: 38, 163: 18, 83: 254, 3: 234, 172: 214, 92: 194, 12: 174, 189: 154, 109: 134, 29: 114, 206: 94, 126: 74, 46: 54, 227: 34, 147: 14, 113: 0, 67: 250, 236: 230, 156: 210, 76: 190, 252: 170, 173: 150, 93: 130, 13: 110, 148: 253, 120: 159, 199: 148, 198: 137, 77: 126, 23: 104, 73: 83, 203: 73, 78: 62, 123: 53, 254: 42, 43: 33, 90: 23, 161: 12, 10: 3, 132: 249, 191: 239, 226: 220, 197: 201, 248: 191, 117: 181, 34: 172, 37: 161, 88: 151, 141: 142, 8: 131, 134: 121, 185: 111, 54: 101, 190: 90, 57: 79, 128: 68, 139: 57, 14: 46, 138: 35, 131: 10, 100: 241, 1: 228, 146: 200, 133: 185, 168: 171, 104: 155, 40: 139, 251: 85, 94: 66, 91: 45, 103: 124, 55: 112, 231: 156, 80: 56, 224: 92, 102: 113, 96: 60, 98: 188, 97: 252, 140: 206, 122: 31, 232: 187, 16: 40, 202: 51, 26: 7, 239: 251, 5: 153, 219: 77, 119: 128, 21: 157, 238: 102, 180: 5, 217: 119, 30: 50, 7: 100, 32: 44, 183: 144, 50: 176, 110: 70, 157: 146, 2: 164, 44: 182, 145: 8, 58: 15, 27: 29, 64: 52, 9: 67, 31: 199, 179: 22, 42: 11, 193: 20, 211: 30, 129: 4, 241: 32, 74: 19, 178: 208, 247: 160, 112: 64, 242: 224, 114: 192, 165: 193, 0: 36, 59: 37, 196: 9, 154: 39, 75: 41, 72: 147, 249: 127, 162: 204, 130: 196, 229: 209, 182: 133, 48: 48, 86: 109, 240: 96, 137: 99, 151: 136, 209: 24, 108: 198, 181: 197, 212: 13, 244: 21, 11: 25, 118: 117, 228: 17, 214: 141, 52: 229, 160: 76, 115: 6, 106: 27, 56: 143, 25: 71, 36: 225, 194: 212, 208: 88, 187: 69, 171: 65, 153: 103, 38: 97, 207: 243, 82: 184, 184: 175, 188: 218, 213: 205, 121: 95, 15: 195, 81: 248, 24: 135, 70: 105, 150: 125, 174: 86, 158: 82, 220: 226, 201: 115, 71: 116, 51: 246, 177: 16, 176: 80, 22: 93, 39: 108, 159: 231, 223: 247, 186: 47, 169: 107, 245: 213, 235: 81, 192: 84, 124: 202, 175: 235, 84: 237, 79: 211, 234: 59, 143: 227, 237: 166, 33: 236, 253: 106, 65: 244, 111: 219, 200: 179, 101: 177, 17: 232, 20: 221, 166: 129, 60: 186, 61: 122, 167: 140, 204: 222, 87: 120, 41: 75, 135: 132, 136: 163, 49: 240, 250: 63, 107: 49, 170: 43, 18: 168, 221: 162, 35: 242, 225: 28, 149: 189, 85: 173, 152: 167, 95: 215, 53: 165, 89: 87, 66: 180, 6: 89, 47: 203, 210: 216, 215: 152, 233: 123, 116: 245, 127: 223, 19: 238, 69: 169, 105: 91, 4: 217, 216: 183, 68: 233, 63: 207, 155: 61, 246: 149, 230: 145} - -# Checksum offsets embedded in decrypted firmware, keyed by firmware data length. -# Each entry is (checksum_func_index, offset, label): -# 0 = sum of 16-bit BE words from 0..offset must equal value stored at offset -# 1 = negative-sum variant (same range, negated) -FIRMWARE_CHECKSUM_OFFSETS = { - 0x6c000: [(0, 0x6bf80, "sum"), (1, 0x6bffe, "negative-sum")], # CR-V TLA, Insight TXM (524288 byte bin) - 0x4c000: [(0, 0x4bf80, "sum"), (1, 0x4bffe, "negative-sum")], # Civic TBA/TEA/TGG/TGN, Clarity TRW (393216 byte bin) -} -PILOT_APP_ID = b'39990-TG7-A060\x00\x00' -PILOT_KEYS = b'\x01\x02\x03' -PILOT_FLASH_START = 0x10000 -PILOT_CHECKSUM_ENDS = (0xa000, 0x1d000, 0x4ff00) - -def auto_int(i): - return int(i, 0) - -def emit_progress(phase: str, percent: float) -> None: - value = max(0, min(100, int(round(percent)))) - print(f"PROGRESS {phase} {value}", flush=True) - - -def read_file(fn): - f_name, f_ext = os.path.splitext(fn) - open_fn = open - if f_ext == ".gz": - open_fn = gzip.open - f_name, f_ext = os.path.splitext(f_name) - - with open_fn(fn, 'rb') as f: - f_data = f.read() - - return f_data - -def validate_fw(fw_encrypted): - assert len(fw_encrypted.firmware_blocks) == 1 - fw_len = fw_encrypted.firmware_blocks[0]["length"] - - print(f"\n [1] Firmware checksums") - print(f" Firmware length: 0x{fw_len:x} ({fw_len} bytes)") - - pilot_v850 = fw_len == 0x50000 - cs_defs = FIRMWARE_CHECKSUM_OFFSETS.get(fw_len) - if pilot_v850: - app_ids = [value.value for value in fw_encrypted.file_headers[3].values] - flash_start = fw_encrypted.firmware_blocks[0]["start"] - assert app_ids == [PILOT_APP_ID] and bytes(fw_encrypted.keys) == PILOT_KEYS and flash_start == PILOT_FLASH_START, \ - "Unexpected 0x50000 firmware identity, layout, or encryption keys" - k0, k1, k2 = PILOT_KEYS - decrypted = bytes(((((b + k0) ^ k1) - k2) & 0xff) for b in fw_encrypted.firmware_encrypted[0]) - checksum = 0 - start = 0 - all_passed = True - for off in PILOT_CHECKSUM_ENDS: - checksum = (checksum + sum(struct.unpack(' 1: - print(f" seed granted after {time.monotonic() - started:.0f}s ({attempt} attempts)") - return seed - except NegativeResponseError as e: - if e.error_code not in SEED_DELAY_NRCS: - raise - reason = SEED_DELAY_NRCS[e.error_code] - remaining = deadline - time.monotonic() - if remaining <= 0: - print(f" still refused ({reason}) after {timeout_s:.0f}s over {attempt} attempts.") - print(" The EPS security-access lockout has not expired. Leave the car in") - print(" accessory mode and retry with a longer --seed-timeout.") - raise - wait = min(SEED_RETRY_INTERVAL_S, remaining) - print(f" attempt {attempt}: {reason} — waiting {wait:.0f}s " - f"({remaining:.0f}s of {timeout_s:.0f}s budget left)") - time.sleep(wait) - -def leave_diagnostic_session(uds_client): - """Drop back to the default session so the EPS isn't left in extended/programming.""" - try: - uds_client.diagnostic_session_control(SESSION_TYPE.DEFAULT) - print("Returned EPS to default diagnostic session.") - except Exception as e: - print(f"Could not return EPS to default session ({e}) — power-cycle the car.") - - -if __name__ == "__main__": - parser = ArgumentParser() - parser.add_argument("rwd", help="RWD firmware file to flash") - parser.add_argument("-b", "--bus", default=1, type=auto_int, help="CAN bus number (default 1)") - parser.add_argument("--debug", action="store_true", help="Enable debug output") - parser.add_argument("--danger", action="store_true", help="Run in danger mode that actually performs mutating actions") - parser.add_argument("--skip-checksum", action="store_true", help="Skip firmware checksum validation (not recommended — a bad checksum will cause the EPS to reject the flash)") - parser.add_argument("--seed-timeout", default=DEFAULT_SEED_TIMEOUT_S, type=float, - help=f"Seconds to keep retrying the security-access seed request while the EPS " - f"reports a delay/lockout (default {DEFAULT_SEED_TIMEOUT_S:.0f})") - args = parser.parse_args() - - fw = x5a(read_file(args.rwd)) - if not args.skip_checksum: - validate_fw(fw) - emit_progress("validated", 0) - - print(fw) - - can_addr = get_can_address(fw) - print(f"Connecting to CAN address 0x{can_addr:08X}") - uds_client = get_uds_client(can_addr, args.bus) - # --danger must never proceed on the mock fallback — it would "succeed" without - # programming the EPS. - if args.danger and isinstance(uds_client, mock.Mock): - raise SystemExit("ERROR: --danger requires a real Panda connection (got mock client)") - - debug_output: list[bytes | None] = list() - - print("tester present ...") - uds_client.tester_present() - - try: - print("Getting software version") - app_id = uds_client.read_data_by_identifier(DATA_IDENTIFIER_TYPE.APPLICATION_SOFTWARE_IDENTIFICATION) - print(f"Application Software ID = {app_id}") - - print("Set diagnostic session type to 3 (extended diagnostic)") - data = uds_client.diagnostic_session_control(SESSION_TYPE.EXTENDED_DIAGNOSTIC) - debug_output = debug_output + [data] - time.sleep(SESSION_SETTLE_S) - - print("Security access request key for seed 1") - data = request_seed(uds_client, args.seed_timeout) - debug_output = debug_output + [data] - secret_key = get_seed_secret(fw, app_id) - if data is None: - raise ValueError("Security access request returned no seed data") - key = calculate_session_key(secret_key, data[-2:]) - print("key = ", key) - - print("Security access send key for seed 1") - data = uds_client.security_access(ACCESS_TYPE.SEND_KEY, key) - debug_output = debug_output + [data] - - print("Set diagnostic session type to programming") - data = uds_client.diagnostic_session_control(SESSION_TYPE.PROGRAMMING) - debug_output = debug_output + [data] - - if not args.danger: - raise RuntimeError('Safe mode: aborting before mutating actions') - - print("Erasing flash") - emit_progress("erase", 0) - data = uds_client.routine_control(ROUTINE_CONTROL_TYPE.START, ROUTINE_IDENTIFIER_TYPE.ERASE_MEMORY) - debug_output = debug_output + [data] - emit_progress("erase", 10) - - print("Setting firmware decryption key") - data = uds_client.write_data_by_identifier(FLASH_DECRYPTION_KEY, fw.keys) - debug_output = debug_output + [data] - - print("Requesting download") - emit_progress("program", 10) - assert len(fw.firmware_blocks) == 1 - block = fw.firmware_blocks[0] - length = block["length"] - max_chunk_size = uds_client.request_download(block["start"], length) - max_chunk_size -= 2 # subtract header bytes - - with tqdm.tqdm(total=length, unit='B', unit_scale=True) as t: - cursor = 0x0 - seq = 1 - last_progress = -1 - while cursor < length: - block_size = min(max_chunk_size, length - cursor) - data = uds_client.transfer_data(seq, fw.firmware_encrypted[0][cursor:cursor+block_size]) - debug_output = debug_output + [data] - seq = (seq + 1) & 0xFF - cursor += block_size - t.update(block_size) - percent = 10 + (cursor / length) * 85 - if int(percent) >= last_progress + 2 or cursor >= length: - emit_progress("program", percent) - last_progress = int(percent) - - print("Requesting transfer exit") - emit_progress("verify", 95) - data = uds_client.request_transfer_exit() - debug_output = debug_output + [data] - - print("Checking programming dependencies") - data = uds_client.routine_control(ROUTINE_CONTROL_TYPE.START, ROUTINE_IDENTIFIER_TYPE.CHECK_PROGRAMMING_DEPENDENCIES) - debug_output = debug_output + [data] - emit_progress("reset", 99) - - print("Resetting ECU") - data = uds_client.ecu_reset(RESET_TYPE.HARD) - debug_output = debug_output + [data] - emit_progress("complete", 100) - - except Exception as e: - print(traceback.format_exc()) - if not isinstance(uds_client, mock.Mock): - leave_diagnostic_session(uds_client) - # Dry-run intentionally raises here; treat that as success. Any other failure - # must be a non-zero exit so callers (e.g. flash.py) don't treat it as done. - if isinstance(e, RuntimeError) and str(e) == "Safe mode: aborting before mutating actions": - pass - else: - sys.exit(1) - - if isinstance(uds_client, mock.Mock): - from unittest.mock import ANY, call - - #print(uds_client.method_calls) - - calls = [] - calls += [call.read_data_by_identifier(DATA_IDENTIFIER_TYPE.APPLICATION_SOFTWARE_IDENTIFICATION)] - calls += [call.diagnostic_session_control(SESSION_TYPE.EXTENDED_DIAGNOSTIC)] - calls += [call.security_access(ACCESS_TYPE.REQUEST_SEED)] - calls += [call.security_access(ACCESS_TYPE.SEND_KEY, ANY)] - calls += [call.diagnostic_session_control(SESSION_TYPE.PROGRAMMING)] - calls += [call.routine_control(ROUTINE_CONTROL_TYPE.START, ROUTINE_IDENTIFIER_TYPE.ERASE_MEMORY)] - calls += [call.write_data_by_identifier(FLASH_DECRYPTION_KEY, fw.keys)] - calls += [call.request_download(0x10000, 0x50000)] - calls += [call.transfer_data(1, fw.firmware_encrypted[0][0:512])] - calls += [call.transfer_data(2, fw.firmware_encrypted[0][512:1024])] - uds_client.assert_has_calls(calls) - - num_blocks = -(len(fw.firmware_encrypted[0]) // -512) # sneaky math ceil - assert uds_client.transfer_data.call_count == num_blocks - - calls = [] - calls += [call.transfer_data(num_blocks & 0xFF - 1, fw.firmware_encrypted[0][((num_blocks-1)*512):])] - calls += [call.request_transfer_exit()] - calls += [call.routine_control(ROUTINE_CONTROL_TYPE.START, ROUTINE_IDENTIFIER_TYPE.CHECK_PROGRAMMING_DEPENDENCIES)] - calls += [call.ecu_reset(RESET_TYPE.HARD)] - uds_client.assert_has_calls(calls) - - if args.debug: - print("\nDebug output:") - print(*debug_output, sep="\n") diff --git a/openpilot/sunnypilot/nrdr/tools/eps/flash.py b/openpilot/sunnypilot/nrdr/tools/eps/flash.py deleted file mode 100644 index f61534675..000000000 --- a/openpilot/sunnypilot/nrdr/tools/eps/flash.py +++ /dev/null @@ -1,586 +0,0 @@ -#!/usr/bin/env python3 -""" -Guided EPS flash helper. - -Steps it runs for you: - 1. Reads the car's current EPS firmware and shows the curated Proper Torque - Mod and clearly-labelled stock images compatible with your EPS — - pre-validating each with check_rwd.py. Reference, legacy, and experimental - files stay out of this menu. If none are compatible (or the car isn't - fingerprinted yet), it asks before listing the curated images. - 2. Shows a numbered list; you pick one. - 3. Confirms the car is OFF, then stops openpilot (frees the panda), then - prompts you to switch the car to accessory mode (ignition ON, engine OFF). - 4. Auto-detects the EPS CAN bus via live UDS probe of the selected .rwd's - address (asks if ambiguous) unless --bus was passed. - 5. Offers a dry run (recommended) or skip (not recommended); --skip-dry-run - bypasses that prompt. Confirms dry run reaches the safe abort when run. - 6. Asks for explicit confirmation (FLASH), then runs the real flash (--danger). - On failure, offers retry (menu choice is enough — no second FLASH prompt). - 7. Post-flash menu: run eps-diag.py (loops back here), restore openpilot, or - reboot the device. - -Security-access delay: the EPS refuses the seed request with NRC 0x37 while its -delay timer runs (e.g. a settling window after power-up). Normal `flash.py` runs -handle this for you — eps-update.py retries the seed on a slow interval, and if the -default window elapses the dry run just asks whether to keep waiting. No flag needed. ---seed-timeout is only for unattended runs: it pins the window and skips that prompt. - -Run from the openpilot repository root on the comma: - python3 flash.py # normal use — auto bus + auto seed retry - python3 flash.py --bus 0 # pin to a bus - python3 flash.py --skip-dry-run # skip dry-run prompt + dry run (not recommended) - python3 flash.py --seed-timeout 600 # unattended: wait up to 10 min, no keep-waiting prompt -""" -import argparse -import contextlib -import glob -import io -import os -import subprocess -import sys -import time - -HERE = os.path.dirname(os.path.abspath(__file__)) -REPO_ROOT = os.path.abspath(os.path.join(HERE, "..", "..", "..", "..", "..")) -RWD_DIR = os.path.join(HERE, "rwd") -EPS_UPDATE = os.path.join(HERE, "eps-update.py") -DRY_RUN_OK_MARKER = "Safe mode: aborting before mutating actions" -REAL_CLIENT_MARKER = "Using real client" -# Printed by eps-update.py when the security-access seed stayed refused for the -# whole --seed-timeout window. -SEED_LOCKOUT_MARKER = "still refused" -# Normal-use seed-retry window (mirrors eps-update.py's DEFAULT_SEED_TIMEOUT_S) and -# how much longer each interactive "keep waiting?" extension waits. -DEFAULT_SEED_TIMEOUT_S = 120.0 -SEED_EXTEND_S = 300.0 -EPS_ADDR = 0x18DA30F1 -DEFAULT_BUS = 1 -PROBE_BUSES = (0, 1) - -# This is deliberately narrower than "every valid .rwd in the library". Only -# the current Proper Torque Mod images and unambiguous stock recovery images are -# safe choices for the ordinary guided menu. Legacy, testing, WIP, T6Z, and x31 -# files remain available as catalogued reference material for expert/manual use. -GUIDED_FLASH_FILES = ( - "39990-TBA-A030/Legacy and Stock/stock-39990-TBA-A030.rwd", - "39990-TBA-A030/Proper Torque Mod/39990-TBA,A030-PTM.rwd", - "39990-TBA-C020 (2019 Honda Civic Sport)/Legacy and Stock/stock-39990-TBA-C020.rwd", - "39990-TBA-C020 (2019 Honda Civic Sport)/Proper Torque Mod/39990-TBA,C020-Trk4000-PTM.rwd", - "39990-TBA-C120/Legacy and Stock/stock-39990-TBA-C120.rwd", - "39990-TBA-C120/Proper Torque Mod/39990-TBA,C120-PTM.rwd", - "39990-TEG-A010/Proper Torque Mod/39990-TEG,A010-PTM.rwd", - "39990-TG7-A060 (2019 Honda Pilot)/Legacy and Stock/39990-TG7-A060-STOCK.rwd", - "39990-TGG-A020/Legacy and Stock/stock-39990-TGG-A020-HatchbackSport.rwd", - "39990-TGG-A120/stock-39990-TGG-A120.rwd", - "39990-TLA-A040 - (Honda_CRV_5G)/Legacy and Stock/39990-TLA-A040-stock.rwd", - "39990-TLA-A040 - (Honda_CRV_5G)/Proper Torque Mod/39990-TLA-A040_Clarity_FF_tune_telemety_8cf8e537.rwd", - "39990-TRW-A020 (Honda Clarity)/Proper Torque Mod/ClarityMax-PTM.rwd", - "39990-TXM-A040 (2019-2022 Honda Insight)/Legacy and Stock/TXM-A040-STOCK.rwd", - "39990-TXM-A040 (2019-2022 Honda Insight)/Proper Torque Mod/39990-TXM,A040-PTM.rwd", -) - -sys.path.insert(0, HERE) -import check_rwd # noqa: E402 (reuse the validated checksum logic) - - -def subprocess_env(): - """opendbc/panda live in the openpilot root; rwd_format lives next to the script.""" - env = os.environ.copy() - paths = [HERE, REPO_ROOT] - if os.path.isdir("/data/openpilot"): - paths.append("/data/openpilot") - if env.get("PYTHONPATH"): - paths.append(env["PYTHONPATH"]) - env["PYTHONPATH"] = os.pathsep.join(paths) - return env - - -def validate(path): - """True if the image passes check_rwd (run quietly).""" - try: - with contextlib.redirect_stdout(io.StringIO()): - return bool(check_rwd.check(path)) - except Exception: - return False - - -def find_images(): - files = { - os.path.join(RWD_DIR, *relative_path.split("/")) - for relative_path in GUIDED_FLASH_FILES - if os.path.isfile(os.path.join(RWD_DIR, *relative_path.split("/"))) - } - # Preserve the historical escape hatch for an operator's own flat .rwd: - # immediately inside rwd/ or beside this script. Nested library files must - # be explicitly allowlisted above, so a reference image cannot leak in. - files |= set(glob.glob(os.path.join(RWD_DIR, "*.rwd"))) - files |= set(glob.glob(os.path.join(HERE, "*.rwd"))) - return sorted(files) - - -def image_display_name(path): - """Readable library-relative path, including its safety/category folders.""" - absolute = os.path.abspath(path) - try: - inside_library = os.path.commonpath((RWD_DIR, absolute)) == RWD_DIR - except ValueError: - inside_library = False - base = RWD_DIR if inside_library else HERE - return os.path.relpath(absolute, base).replace(os.sep, "/") - - -def norm_fw(s): - """Normalize a part number for matching: '-'/',' interchangeable, strip NULs/space.""" - if isinstance(s, (bytes, bytearray)): - s = bytes(s).decode("latin-1", "replace") - return s.replace("-", ",").strip("\x00").strip() - - -def car_eps_fw(): - """EPS firmware string from CarParamsPersistent / CarParams, or None. - - Same source the nrdr settings UI uses: the EPS entry in CarParams.carFw. - (Do not use CarFw.bus — Cap'n Proto UInt8 defaults to 0 when unset.) - """ - try: - from opendbc.car.structs import car - except Exception: - if os.path.isdir("/data/openpilot"): - sys.path.append("/data/openpilot") - try: - from opendbc.car.structs import car - except Exception: - try: - from cereal import car - except Exception: - return None - for p in ("/data/params/d/CarParamsPersistent", "/data/params/d/CarParams"): - try: - data = open(p, "rb").read() - except OSError: - continue - try: - with car.CarParams.from_bytes(data) as CP: - for fw in CP.carFw: - if fw.ecu == "eps": - v = bytes(fw.fwVersion).decode("latin-1", "replace").strip("\x00").strip() - if v: - return v - except Exception: - continue - return None - - -def rwd_supported_versions(path): - """Normalized set of part numbers an .rwd supports (its header[3]); None if unparseable.""" - try: - from rwd_format.x5a import x5a - fw = x5a(open(path, "rb").read()) - return {norm_fw(bytes(v.value)) for v in fw.file_headers[3].values} - except Exception: - return None - - -def rwd_can_address(path): - """UDS tester address from the .rwd (same as eps-update.get_can_address); None if unparseable.""" - try: - import struct - from rwd_format.x5a import x5a - fw = x5a(open(path, "rb").read()) - return 0x18da00f1 | struct.unpack('!B', fw.file_headers[2].values[0].value)[0] << 8 - except Exception: - return None - - -def probe_eps_buses(addr): - """Live-probe buses 0/1 for Tester Present at addr. Return responding bus list. - - A negative UDS response still means the ECU is on that bus (same as eps-diag). - """ - try: - from panda import Panda - from opendbc.car.structs import CarParams - from opendbc.car.uds import UdsClient, NegativeResponseError - except Exception as e: - print(f" Live bus probe unavailable ({e})") - return [] - - panda = None - try: - panda = Panda(disable_checks=True) - panda.set_safety_mode(CarParams.SafetyModel.elm327) - - alive = [] - for bus in PROBE_BUSES: - uds = UdsClient(panda, addr, bus=bus, timeout=1.5) - try: - uds.tester_present() - alive.append(bus) - print(f" EPS responded on bus {bus}") - except NegativeResponseError as e: - alive.append(bus) - print(f" EPS on bus {bus} (negative response: {e})") - except Exception: - print(f" No EPS response on bus {bus}") - return alive - except Exception as e: - print(f" Live bus probe: could not open/configure Panda ({e})") - return [] - finally: - # Always release USB so eps-update can open the Panda for dry run / flash. - if panda is not None: - try: - panda.close() - except Exception: - pass - - -def ask_pick_bus(choices, default): - """Prompt for a bus from choices. Empty input selects default.""" - choices = list(choices) - if default not in choices: - default = choices[0] - label = "/".join(str(b) for b in choices) - while True: - raw = input(f"Select bus to flash [{label}] (default {default}): ").strip() - if raw == "": - return default - try: - bus = int(raw, 0) - except ValueError: - print(" Enter a bus number.") - continue - if bus not in choices: - print(f" Pick one of: {label}") - continue - return bus - - -def resolve_bus(cli_bus, rwd_path): - """Pick the flash bus: --bus override, else live probe, else ask the user.""" - if cli_bus is not None: - print(f"Using bus {cli_bus} (--bus override)") - return cli_bus - - addr = rwd_can_address(rwd_path) - if addr is None: - addr = EPS_ADDR - print(f"Could not read CAN address from .rwd — probing default 0x{addr:08X}") - else: - print(f"Probing for EPS bus (UDS 0x{addr:08X} from selected .rwd) ...") - - alive = probe_eps_buses(addr) - if len(alive) == 1: - print(f"Detected EPS on bus {alive[0]}") - return alive[0] - - if len(alive) > 1: - print(f"EPS responded on multiple buses: {', '.join(str(b) for b in alive)}") - # Prefer the usual Honda default when both look alive (gateway echo, etc.). - default = DEFAULT_BUS if DEFAULT_BUS in alive else alive[0] - return ask_pick_bus(alive, default=default) - - print("Live probe found no EPS response on buses 0 or 1.") - return ask_pick_bus(list(PROBE_BUSES), default=DEFAULT_BUS) - - -def stop_openpilot(): - # Stop the managed service and tear down the comma tmux session so openpilot - # cannot respawn and reclaim the panda. Do NOT `pkill -f openpilot` — that - # matches this script's argv when launched from a path containing "openpilot". - print("Stopping openpilot to free the panda ...") - subprocess.run(["sudo", "systemctl", "stop", "comma"], check=False) - subprocess.run( - ["tmux", "kill-session", "-t", "comma"], - check=False, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, - ) - time.sleep(2) - status = subprocess.run( - ["systemctl", "is-active", "comma"], - capture_output=True, text=True, check=False, - ).stdout.strip() - if status in ("active", "activating", "deactivating", "reloading"): - sys.exit( - f"Failed to stop openpilot (comma service is '{status}'). " - "Run `sudo systemctl stop comma` manually and retry." - ) - print(" done.\n") - - -def run_eps_update(rel_path, bus, skip_checksum, danger, seed_timeout=None): - cmd = [sys.executable, EPS_UPDATE, rel_path, "-b", str(bus)] - if skip_checksum: - cmd.append("--skip-checksum") - if danger: - cmd.append("--danger") - if seed_timeout is not None: - cmd += ["--seed-timeout", str(seed_timeout)] - return cmd - - -def restore_openpilot(): - print("\nRestoring openpilot (restarting the comma service) ...") - subprocess.run("tmux kill-session -t comma 2>/dev/null; sudo systemctl restart comma", - shell=True, check=False) - print(" Requested restart — openpilot should come up in ~30s. If not, reboot.") - - -def reboot_device(): - print("\nRebooting ...") - subprocess.run(["sudo", "reboot"], check=False) - - -def exit_with_op_stopped(msg): - """Abort after stop_openpilot(); offer to restore so the device isn't left dead.""" - print(msg) - print("\nNote: openpilot is still stopped.") - if input("Restore openpilot now? type 'yes': ").strip().lower() == "yes": - restore_openpilot() - else: - print("Restore later with: sudo systemctl restart comma (or reboot).") - sys.exit(1) - - -def post_flash_menu(env, bus, eps_addr=None): - """After a flash: optionally run eps-diag (loops back), restore op, or reboot.""" - diag = os.path.join(HERE, "eps-diag.py") - while True: - print("\nWhat next?") - print(" [1] Run eps-diag.py — post-flash EPS check (returns here after)") - print(" [2] Restore openpilot — it was stopped for flashing") - print(" [3] Reboot the device (sudo reboot)") - choice = input("Select 1/2/3 (q = leave openpilot stopped and exit): ").strip().lower() - if choice == "1": - print() - # Pin to the bus/address we just flashed so the check matches the flash path. - cmd = [sys.executable, diag, "-b", str(bus)] - if eps_addr is not None: - cmd += ["--addr", hex(eps_addr)] - subprocess.run(cmd, env=env, cwd=HERE) - elif choice == "2": - restore_openpilot() - return - elif choice == "3": - reboot_device() - return - elif choice in ("q", "quit", ""): - print("Leaving openpilot stopped. Restore later with: sudo systemctl restart comma (or reboot).") - return - else: - print(" Invalid choice — pick 1, 2, or 3.") - - -def choose_dry_run(skip_dry_run_flag): - """Return True if a dry run should run. Flag bypasses the in-script prompt.""" - if skip_dry_run_flag: - print("\nSkipping dry run (--skip-dry-run). Not recommended.") - return False - print("\nDry run validates the image and security flow, then aborts before erase.") - print("Some EPS units lock security access after a dry run; skip only if you need to.") - print(" [1] Run dry run first (recommended)") - print(" [2] Skip dry run (not recommended)") - choice = input("Select 1/2 [1]: ").strip() or "1" - if choice == "2": - print("Skipping dry run (not recommended).") - return False - if choice != "1": - print(" Invalid choice — running dry run.") - return True - - -def run_and_tee(cmd, env): - """Run cmd, printing output as it arrives while capturing it for marker checks. - - The seed request can retry for minutes against an EPS security lockout, so the - output has to stream — a captured run looks like a hang. - """ - proc = subprocess.Popen(cmd, env=env, cwd=HERE, stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, text=True, bufsize=1) - lines = [] - for line in proc.stdout: - print(line, end="", flush=True) - lines.append(line) - proc.wait() - return proc.returncode, "".join(lines) - - -def run_dry_run(rel, bus, skip_checksum, env, name, seed_timeout=None): - # seed_timeout None = normal use: fall back to eps-update.py's own default and - # let an interactive lockout be handled by prompting to keep waiting, so plain - # `flash.py` never needs a flag. An explicit --seed-timeout pins the window and - # skips the prompt (for unattended runs). - interactive = seed_timeout is None - effective = DEFAULT_SEED_TIMEOUT_S if seed_timeout is None else seed_timeout - while True: - print(f"\n=== DRY RUN: {name} (validates + auth flow, stops before erase) ===\n") - _, combined = run_and_tee( - run_eps_update(rel, bus, skip_checksum, danger=False, seed_timeout=effective), env) - # Mock UDS client also reaches the safe-abort marker; require a real Panda. - if REAL_CLIENT_MARKER not in combined: - exit_with_op_stopped( - "\nDry run did NOT talk to a real Panda/EPS (mock client) — NOT flashing. " - "Fix the Panda connection and retry." - ) - if SEED_LOCKOUT_MARKER in combined: - print(f"\nThe EPS security-access delay hasn't cleared after {effective:.0f}s.") - print("The ECU reported its delay timer. Waiting may clear this response;") - print("it does not prove that the selected image matches this EPS.") - if interactive and input( - f"Keep waiting another {SEED_EXTEND_S:.0f}s? [Y/n]: ").strip().lower() in ("", "y", "yes"): - effective = SEED_EXTEND_S - continue - exit_with_op_stopped("".join(( - "\nStopping while the EPS delay is still active — NOT flashing.\n", - "After re-checking image compatibility, leave the car in accessory mode (ignition off restarts the timer) and retry the seed flow.", - ))) - if DRY_RUN_OK_MARKER not in combined: - exit_with_op_stopped( - "\nDry run did NOT reach the safe abort point — NOT flashing. Review the output above." - ) - break - print("Dry run OK — real client + safe abort (the traceback above is expected for a dry run).") - print("Note: some EPS units lock security access briefly after a dry run.") - print("If the real flash fails with a security/timeout error, wait or power-cycle, then retry.") - - -def run_real_flash(rel, bus, skip_checksum, env, name, seed_timeout=None): - print(f"\n=== FLASHING: {name} (bus {bus}) ===\n") - return subprocess.run(run_eps_update(rel, bus, skip_checksum, danger=True, seed_timeout=seed_timeout), - env=env, cwd=HERE).returncode - - -def flash_failure_menu(rel, bus, skip_checksum, env, name, rc, seed_timeout=None): - """After a failed flash: retry (--danger only) or continue to post-flash menu. - - Choosing retry is enough confirmation — no second FLASH prompt. - Returns the final return code. - """ - while rc != 0: - print(f"\nFlash process exited ({rc}).") - print("FLASH FAILED or incomplete — power-steering assist may be unavailable.") - print("A retry is not guaranteed recovery. First verify this image matches the exact") - print("EPS, diagnose the failure, and confirm stable vehicle power and CAN/Panda.") - print("A security lockout after a dry run is common on some cars:") - print(" wait a bit, power-cycle the car (OFF → accessory), then retry.") - print(" [r] Retry flash (--danger only, no dry run)") - print(" [m] Continue to post-flash menu") - print(" [q] Quit (leave openpilot stopped)") - choice = input("Select r/m/q: ").strip().lower() - if choice == "r": - rc = run_real_flash(rel, bus, skip_checksum, env, name, seed_timeout) - if rc == 0: - print(f"\nFlash process exited ({rc}).") - print("Flash reported success (exit 0).") - return rc - continue - if choice == "m": - print("Continuing to post-flash menu without a successful flash.") - return rc - if choice in ("q", "quit"): - exit_with_op_stopped("Leaving after failed flash — openpilot is still stopped.") - print(" Invalid choice — pick r, m, or q.") - return rc - - -def main(): - ap = argparse.ArgumentParser(description="Guided EPS flash helper") - ap.add_argument("-b", "--bus", type=int, default=None, - help="CAN bus number (default: auto-detect; fallback 1)") - ap.add_argument("--skip-dry-run", action="store_true", - help="Skip the dry-run prompt and dry run (not recommended)") - ap.add_argument("--seed-timeout", type=float, default=None, - help="Seconds to keep retrying the security-access seed request while the " - "EPS reports a delay/lockout (passed to eps-update.py)") - args = ap.parse_args() - - images = find_images() - if not images: - sys.exit(f"No .rwd files found in {RWD_DIR} or {HERE}") - - # Filter to images compatible with the car's current EPS firmware. - car_fw = car_eps_fw() - car_norm = norm_fw(car_fw) if car_fw else None - if car_norm: - print(f"Detected EPS firmware: {car_fw}") - compatible = [f for f in images if (sv := rwd_supported_versions(f)) and car_norm in sv] - if compatible: - hidden = len(images) - len(compatible) - extra = f" ({hidden} incompatible image(s) hidden)" if hidden else "" - print(f"Showing firmware compatible with your EPS{extra}:\n") - shown = compatible - else: - print(f"\n!! No .rwd here is compatible with your EPS ({car_fw}).") - if input(" Show all curated guided images and continue anyway? type 'yes': ").strip().lower() != "yes": - sys.exit("Aborted.") - print("\nAll curated guided firmware (compatibility NOT guaranteed — pre-validated):\n") - shown = images - else: - print("Could not read the car's EPS firmware (not fingerprinted yet?). " - "Showing all curated guided images.\n") - shown = images - - rows = [] - for i, f in enumerate(shown): - ok = validate(f) - rows.append((f, ok)) - status = "valid ✓" if ok else "INVALID ✗ -> will need --skip-checksum (brick risk)" - print(f" [{i + 1:>2}] {image_display_name(f)} {status}") - - sel = input("\nSelect a firmware number to flash (q to quit): ").strip() - if sel.lower() in ("", "q", "quit"): - sys.exit("Aborted.") - try: - idx = int(sel) - 1 - except ValueError: - sys.exit("Invalid selection.") - # Reject 0 and out-of-range: int("0")-1 == -1 would silently pick the last row. - if idx < 0 or idx >= len(rows): - sys.exit("Invalid selection.") - path, ok = rows[idx] - - rel = os.path.relpath(path, HERE) - name = image_display_name(path) - skip_checksum = not ok - - if not ok: - print(f"\n!! {name} FAILED checksum validation.") - print("!! It can only be flashed with --skip-checksum. A bad image can BRICK the EPS.") - if input(" Type 'yes' to continue with this unvalidated image: ").strip().lower() != "yes": - sys.exit("Aborted.") - - # Power sequence: kill openpilot with the car OFF, then switch to accessory - # mode — so openpilot doesn't re-grab the panda when CAN comes alive. - if input("\nConfirm the car is fully OFF (ignition off) — type 'yes': ").strip().lower() != "yes": - sys.exit("Aborted — turn the car off first, then re-run.") - stop_openpilot() - input("Now switch the car to ACCESSORY mode (ignition ON, engine OFF, A/C off).\n" - "Press Enter when the dash is on and ready: ") - - bus = resolve_bus(args.bus, path) - eps_addr = rwd_can_address(path) - env = subprocess_env() - - if choose_dry_run(args.skip_dry_run): - run_dry_run(rel, bus, skip_checksum, env, name, args.seed_timeout) - - print(f"\n=== READY TO FLASH: {name} (bus {bus}) ===") - print("Make sure the car is in accessory mode (ignition ON, engine OFF, A/C off).") - print("An interrupted flash can remove steering assist or permanently damage the EPS;") - print("recovery is not guaranteed.") - if input("Type 'FLASH' to commit the real flash (--danger): ").strip() != "FLASH": - exit_with_op_stopped("Aborted — no flash performed.") - - rc = run_real_flash(rel, bus, skip_checksum, env, name, args.seed_timeout) - if rc == 0: - print(f"\nFlash process exited ({rc}).") - print("Flash reported success (exit 0).") - else: - rc = flash_failure_menu(rel, bus, skip_checksum, env, name, rc, args.seed_timeout) - - post_flash_menu(env, bus, eps_addr=eps_addr) - sys.exit(rc) - - -if __name__ == "__main__": - main() diff --git a/openpilot/sunnypilot/nrdr/tools/eps/launcher.py b/openpilot/sunnypilot/nrdr/tools/eps/launcher.py deleted file mode 100644 index 305d7ec9a..000000000 --- a/openpilot/sunnypilot/nrdr/tools/eps/launcher.py +++ /dev/null @@ -1,68 +0,0 @@ -"""Stable launch boundary for canonical and compatibility EPS commands.""" - -import glob -import os -from pathlib import Path -import subprocess -import sys - - -EPS_DIR = Path(__file__).resolve().parent -REPOSITORY_ROOT = EPS_DIR.parents[4] -TOOLS = frozenset(("flash.py", "eps-update.py", "eps-diag.py", "check_rwd.py")) -_RWD_ALIASES = ("rwd/", "eps_tools/rwd/") - - -def _expand_rwd_alias(argument: str) -> list[str]: - normalized = argument.replace("\\", "/") - for prefix in _RWD_ALIASES: - if not normalized.startswith(prefix): - continue - canonical = EPS_DIR / "rwd" / normalized.removeprefix(prefix) - matches = sorted(glob.glob(str(canonical), recursive=True)) - return matches or [str(canonical)] - return [argument] - - -def normalized_arguments(tool: str, arguments: list[str]) -> list[str]: - if tool not in TOOLS: - raise ValueError(f"Unknown EPS tool: {tool}") - if tool not in ("check_rwd.py", "eps-update.py"): - return list(arguments) - - normalized: list[str] = [] - for argument in arguments: - expanded = _expand_rwd_alias(argument) - if tool == "eps-update.py" and len(expanded) > 1: - raise SystemExit(f"Firmware path matched more than one image: {argument}") - normalized.extend(expanded) - return normalized - - -def exec_tool(tool: str) -> None: - """Replace the launcher process with the canonical tool. - - Using exec rather than importing the command preserves its arguments, exit - status, signals, prompts, and __file__-relative firmware/parser lookup. - """ - if tool not in TOOLS: - raise ValueError(f"Unknown EPS tool: {tool}") - - environment = os.environ.copy() - python_path = [str(EPS_DIR), str(REPOSITORY_ROOT)] - if environment.get("PYTHONPATH"): - python_path.append(environment["PYTHONPATH"]) - environment["PYTHONPATH"] = os.pathsep.join(python_path) - - target = EPS_DIR / tool - arguments = normalized_arguments(tool, sys.argv[1:]) - command = [sys.executable, str(target), *arguments] - if os.name == "nt": - # Windows Store Python cannot reliably replace itself with os.execve. - # Keep Linux/device launchers as true exec boundaries while preserving - # the same arguments and exit status for off-device Windows validation. - raise SystemExit(subprocess.call(command, env=environment)) - os.execve(sys.executable, command, environment) - - -__all__ = ("EPS_DIR", "REPOSITORY_ROOT", "TOOLS", "exec_tool", "normalized_arguments") diff --git a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-T6Z-A110/39990-T6Z-A110.rwd b/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-T6Z-A110/39990-T6Z-A110.rwd deleted file mode 100644 index 97948069f..000000000 Binary files a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-T6Z-A110/39990-T6Z-A110.rwd and /dev/null differ diff --git a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TBA-A030/Legacy and Stock/39990-TBA,A030-linear-max.rwd b/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TBA-A030/Legacy and Stock/39990-TBA,A030-linear-max.rwd deleted file mode 100644 index 35afb8397..000000000 Binary files a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TBA-A030/Legacy and Stock/39990-TBA,A030-linear-max.rwd and /dev/null differ diff --git a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TBA-A030/Legacy and Stock/mod-39990-TBA,A030.rwd b/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TBA-A030/Legacy and Stock/mod-39990-TBA,A030.rwd deleted file mode 100644 index 7984085f0..000000000 Binary files a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TBA-A030/Legacy and Stock/mod-39990-TBA,A030.rwd and /dev/null differ diff --git a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TBA-A030/Legacy and Stock/stock-39990-TBA-A030.rwd b/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TBA-A030/Legacy and Stock/stock-39990-TBA-A030.rwd deleted file mode 100644 index d4d9fc383..000000000 Binary files a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TBA-A030/Legacy and Stock/stock-39990-TBA-A030.rwd and /dev/null differ diff --git a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TBA-A030/Proper Torque Mod/39990-TBA,A030-PTM.rwd b/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TBA-A030/Proper Torque Mod/39990-TBA,A030-PTM.rwd deleted file mode 100644 index 61bcb971e..000000000 Binary files a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TBA-A030/Proper Torque Mod/39990-TBA,A030-PTM.rwd and /dev/null differ diff --git a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TBA-C020 (2019 Honda Civic Sport)/Legacy and Stock/2.5x-mod-39990-TBA,C020.rwd b/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TBA-C020 (2019 Honda Civic Sport)/Legacy and Stock/2.5x-mod-39990-TBA,C020.rwd deleted file mode 100644 index fa6e6ff4e..000000000 Binary files a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TBA-C020 (2019 Honda Civic Sport)/Legacy and Stock/2.5x-mod-39990-TBA,C020.rwd and /dev/null differ diff --git a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TBA-C020 (2019 Honda Civic Sport)/Legacy and Stock/39990-TBA-C020-linear-max-V2.1-Linear45-FlatGain.rwd b/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TBA-C020 (2019 Honda Civic Sport)/Legacy and Stock/39990-TBA-C020-linear-max-V2.1-Linear45-FlatGain.rwd deleted file mode 100644 index 76b2e43e2..000000000 Binary files a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TBA-C020 (2019 Honda Civic Sport)/Legacy and Stock/39990-TBA-C020-linear-max-V2.1-Linear45-FlatGain.rwd and /dev/null differ diff --git a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TBA-C020 (2019 Honda Civic Sport)/Legacy and Stock/39990-TBA-C020-max.rwd b/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TBA-C020 (2019 Honda Civic Sport)/Legacy and Stock/39990-TBA-C020-max.rwd deleted file mode 100644 index a4d66f859..000000000 Binary files a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TBA-C020 (2019 Honda Civic Sport)/Legacy and Stock/39990-TBA-C020-max.rwd and /dev/null differ diff --git a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TBA-C020 (2019 Honda Civic Sport)/Legacy and Stock/stock-39990-TBA-C020.rwd b/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TBA-C020 (2019 Honda Civic Sport)/Legacy and Stock/stock-39990-TBA-C020.rwd deleted file mode 100644 index 85010e614..000000000 Binary files a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TBA-C020 (2019 Honda Civic Sport)/Legacy and Stock/stock-39990-TBA-C020.rwd and /dev/null differ diff --git a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TBA-C020 (2019 Honda Civic Sport)/Proper Torque Mod/39990-TBA,C020-Trk4000-PTM.rwd b/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TBA-C020 (2019 Honda Civic Sport)/Proper Torque Mod/39990-TBA,C020-Trk4000-PTM.rwd deleted file mode 100644 index 90a97d506..000000000 Binary files a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TBA-C020 (2019 Honda Civic Sport)/Proper Torque Mod/39990-TBA,C020-Trk4000-PTM.rwd and /dev/null differ diff --git a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TBA-C120/Legacy and Stock/mod-39990-TBA,C120.rwd b/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TBA-C120/Legacy and Stock/mod-39990-TBA,C120.rwd deleted file mode 100644 index 439d706ab..000000000 Binary files a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TBA-C120/Legacy and Stock/mod-39990-TBA,C120.rwd and /dev/null differ diff --git a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TBA-C120/Legacy and Stock/stock-39990-TBA-C120.rwd b/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TBA-C120/Legacy and Stock/stock-39990-TBA-C120.rwd deleted file mode 100644 index b25a946c0..000000000 Binary files a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TBA-C120/Legacy and Stock/stock-39990-TBA-C120.rwd and /dev/null differ diff --git a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TBA-C120/Proper Torque Mod/39990-TBA,C120-PTM.rwd b/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TBA-C120/Proper Torque Mod/39990-TBA,C120-PTM.rwd deleted file mode 100644 index 20ac3983d..000000000 Binary files a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TBA-C120/Proper Torque Mod/39990-TBA,C120-PTM.rwd and /dev/null differ diff --git a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TEG-A010/Legacy and Stock/39990-TEG-A010-linear-max.rwd b/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TEG-A010/Legacy and Stock/39990-TEG-A010-linear-max.rwd deleted file mode 100644 index bfa2fb713..000000000 Binary files a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TEG-A010/Legacy and Stock/39990-TEG-A010-linear-max.rwd and /dev/null differ diff --git a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TEG-A010/Proper Torque Mod/39990-TEG,A010-PTM.rwd b/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TEG-A010/Proper Torque Mod/39990-TEG,A010-PTM.rwd deleted file mode 100644 index 0d94afcfa..000000000 Binary files a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TEG-A010/Proper Torque Mod/39990-TEG,A010-PTM.rwd and /dev/null differ diff --git a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TG7-A060 (2019 Honda Pilot)/Legacy and Stock/39990-TG7-A060-STOCK.bin b/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TG7-A060 (2019 Honda Pilot)/Legacy and Stock/39990-TG7-A060-STOCK.bin deleted file mode 100644 index e2774910b..000000000 Binary files a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TG7-A060 (2019 Honda Pilot)/Legacy and Stock/39990-TG7-A060-STOCK.bin and /dev/null differ diff --git a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TG7-A060 (2019 Honda Pilot)/Legacy and Stock/39990-TG7-A060-STOCK.rwd b/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TG7-A060 (2019 Honda Pilot)/Legacy and Stock/39990-TG7-A060-STOCK.rwd deleted file mode 100644 index 92905e68c..000000000 Binary files a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TG7-A060 (2019 Honda Pilot)/Legacy and Stock/39990-TG7-A060-STOCK.rwd and /dev/null differ diff --git a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TG7-A060 (2019 Honda Pilot)/Work in Progress/39990-TG7-A060-2X (2019 Honda Pilot) b/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TG7-A060 (2019 Honda Pilot)/Work in Progress/39990-TG7-A060-2X (2019 Honda Pilot) deleted file mode 100644 index f6b1b0886..000000000 Binary files a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TG7-A060 (2019 Honda Pilot)/Work in Progress/39990-TG7-A060-2X (2019 Honda Pilot) and /dev/null differ diff --git a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TGG-A020/Legacy and Stock/mod-39990-TGG,A020-HatchbackSport.rwd b/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TGG-A020/Legacy and Stock/mod-39990-TGG,A020-HatchbackSport.rwd deleted file mode 100644 index 170b26a13..000000000 Binary files a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TGG-A020/Legacy and Stock/mod-39990-TGG,A020-HatchbackSport.rwd and /dev/null differ diff --git a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TGG-A020/Legacy and Stock/stock-39990-TGG-A020-HatchbackSport.rwd b/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TGG-A020/Legacy and Stock/stock-39990-TGG-A020-HatchbackSport.rwd deleted file mode 100644 index f1ab581af..000000000 Binary files a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TGG-A020/Legacy and Stock/stock-39990-TGG-A020-HatchbackSport.rwd and /dev/null differ diff --git a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TGG-A120/39990-TGG-A120-08072026-4250.rwd b/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TGG-A120/39990-TGG-A120-08072026-4250.rwd deleted file mode 100644 index b52dc560b..000000000 Binary files a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TGG-A120/39990-TGG-A120-08072026-4250.rwd and /dev/null differ diff --git a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TGG-A120/mod-39990-TGG,A120.rwd b/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TGG-A120/mod-39990-TGG,A120.rwd deleted file mode 100644 index 1841e66f4..000000000 Binary files a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TGG-A120/mod-39990-TGG,A120.rwd and /dev/null differ diff --git a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TGG-A120/stock-39990-TGG-A120.rwd b/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TGG-A120/stock-39990-TGG-A120.rwd deleted file mode 100644 index 196c2803c..000000000 Binary files a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TGG-A120/stock-39990-TGG-A120.rwd and /dev/null differ diff --git a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TLA-A040 - (Honda_CRV_5G)/Legacy and Stock/39990-TLA-A040-linear-max.rwd b/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TLA-A040 - (Honda_CRV_5G)/Legacy and Stock/39990-TLA-A040-linear-max.rwd deleted file mode 100644 index d35c170c6..000000000 Binary files a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TLA-A040 - (Honda_CRV_5G)/Legacy and Stock/39990-TLA-A040-linear-max.rwd and /dev/null differ diff --git a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TLA-A040 - (Honda_CRV_5G)/Legacy and Stock/39990-TLA-A040-stock.bin b/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TLA-A040 - (Honda_CRV_5G)/Legacy and Stock/39990-TLA-A040-stock.bin deleted file mode 100644 index 3871b45ff..000000000 Binary files a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TLA-A040 - (Honda_CRV_5G)/Legacy and Stock/39990-TLA-A040-stock.bin and /dev/null differ diff --git a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TLA-A040 - (Honda_CRV_5G)/Legacy and Stock/39990-TLA-A040-stock.rwd b/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TLA-A040 - (Honda_CRV_5G)/Legacy and Stock/39990-TLA-A040-stock.rwd deleted file mode 100644 index 8ef9f90e5..000000000 Binary files a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TLA-A040 - (Honda_CRV_5G)/Legacy and Stock/39990-TLA-A040-stock.rwd and /dev/null differ diff --git a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TLA-A040 - (Honda_CRV_5G)/Legacy and Stock/39990-TLA-A040_tq30000_a9000_44256c0b.rwd b/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TLA-A040 - (Honda_CRV_5G)/Legacy and Stock/39990-TLA-A040_tq30000_a9000_44256c0b.rwd deleted file mode 100644 index 5621fe122..000000000 Binary files a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TLA-A040 - (Honda_CRV_5G)/Legacy and Stock/39990-TLA-A040_tq30000_a9000_44256c0b.rwd and /dev/null differ diff --git a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TLA-A040 - (Honda_CRV_5G)/Proper Torque Mod/39990-TLA-A040_Clarity_FF_tune_telemety_8cf8e537.rwd b/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TLA-A040 - (Honda_CRV_5G)/Proper Torque Mod/39990-TLA-A040_Clarity_FF_tune_telemety_8cf8e537.rwd deleted file mode 100644 index 2ae0f20b9..000000000 Binary files a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TLA-A040 - (Honda_CRV_5G)/Proper Torque Mod/39990-TLA-A040_Clarity_FF_tune_telemety_8cf8e537.rwd and /dev/null differ diff --git a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TRW-A020 (Honda Clarity)/Proper Torque Mod/ClarityMax-PTM.rwd b/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TRW-A020 (Honda Clarity)/Proper Torque Mod/ClarityMax-PTM.rwd deleted file mode 100644 index 617fea117..000000000 Binary files a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TRW-A020 (Honda Clarity)/Proper Torque Mod/ClarityMax-PTM.rwd and /dev/null differ diff --git a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TVA-A160 (Honda Accord)/39990-TVA,A160-V37-2xTorque.rwd b/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TVA-A160 (Honda Accord)/39990-TVA,A160-V37-2xTorque.rwd deleted file mode 100644 index d73f31635..000000000 Binary files a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TVA-A160 (Honda Accord)/39990-TVA,A160-V37-2xTorque.rwd and /dev/null differ diff --git a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TVA-A160 (Honda Accord)/Stock/39990-TVA-A160-V9B-Stock.rwd b/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TVA-A160 (Honda Accord)/Stock/39990-TVA-A160-V9B-Stock.rwd deleted file mode 100644 index 42fe6796c..000000000 Binary files a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TVA-A160 (Honda Accord)/Stock/39990-TVA-A160-V9B-Stock.rwd and /dev/null differ diff --git a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TXM-A040 (2019-2022 Honda Insight)/Legacy and Stock/39990-TXM-A040 Bin/user.bin b/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TXM-A040 (2019-2022 Honda Insight)/Legacy and Stock/39990-TXM-A040 Bin/user.bin deleted file mode 100644 index 84807b82b..000000000 Binary files a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TXM-A040 (2019-2022 Honda Insight)/Legacy and Stock/39990-TXM-A040 Bin/user.bin and /dev/null differ diff --git a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TXM-A040 (2019-2022 Honda Insight)/Legacy and Stock/TXM-A040-2.5x-spike.rwd b/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TXM-A040 (2019-2022 Honda Insight)/Legacy and Stock/TXM-A040-2.5x-spike.rwd deleted file mode 100644 index e1a134602..000000000 Binary files a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TXM-A040 (2019-2022 Honda Insight)/Legacy and Stock/TXM-A040-2.5x-spike.rwd and /dev/null differ diff --git a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TXM-A040 (2019-2022 Honda Insight)/Legacy and Stock/TXM-A040-2x-linear.rwd b/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TXM-A040 (2019-2022 Honda Insight)/Legacy and Stock/TXM-A040-2x-linear.rwd deleted file mode 100644 index 9a4f0c3cd..000000000 Binary files a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TXM-A040 (2019-2022 Honda Insight)/Legacy and Stock/TXM-A040-2x-linear.rwd and /dev/null differ diff --git a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TXM-A040 (2019-2022 Honda Insight)/Legacy and Stock/TXM-A040-STOCK.rwd b/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TXM-A040 (2019-2022 Honda Insight)/Legacy and Stock/TXM-A040-STOCK.rwd deleted file mode 100644 index a2a3d5d2c..000000000 Binary files a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TXM-A040 (2019-2022 Honda Insight)/Legacy and Stock/TXM-A040-STOCK.rwd and /dev/null differ diff --git a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TXM-A040 (2019-2022 Honda Insight)/Legacy and Stock/TXM-A040-linear-max.rwd b/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TXM-A040 (2019-2022 Honda Insight)/Legacy and Stock/TXM-A040-linear-max.rwd deleted file mode 100644 index 809ddb394..000000000 Binary files a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TXM-A040 (2019-2022 Honda Insight)/Legacy and Stock/TXM-A040-linear-max.rwd and /dev/null differ diff --git a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TXM-A040 (2019-2022 Honda Insight)/Proper Torque Mod/39990-TXM,A040-PTM.rwd b/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TXM-A040 (2019-2022 Honda Insight)/Proper Torque Mod/39990-TXM,A040-PTM.rwd deleted file mode 100644 index dc123b677..000000000 Binary files a/openpilot/sunnypilot/nrdr/tools/eps/rwd/39990-TXM-A040 (2019-2022 Honda Insight)/Proper Torque Mod/39990-TXM,A040-PTM.rwd and /dev/null differ diff --git a/openpilot/sunnypilot/nrdr/tools/eps/rwd/README.md b/openpilot/sunnypilot/nrdr/tools/eps/rwd/README.md deleted file mode 100644 index 048ea9d5e..000000000 --- a/openpilot/sunnypilot/nrdr/tools/eps/rwd/README.md +++ /dev/null @@ -1,119 +0,0 @@ -# Curated Honda EPS firmware library - -This directory is the byte-for-byte firmware catalog imported from Brett's -`Modded Honda RWDs-20260828T042240Z-1-001.zip` archive. The outer archive's -SHA-256 is: - -```text -b36446d02ee5f67250495528881a2ea239c3df3a40b0c463be7bc9d9a9f8721b -``` - -The source's model/category layout is preserved here, with trailing whitespace -removed from directory names so the paths work consistently on Windows and the -comma. There are 37 firmware artifacts: 33 named `.rwd` containers, one -extensionless Pilot work-in-progress container, and three raw `.bin` dumps. -`SHA256SUMS` pins every artifact recursively. - -## What `flash.py` shows - -The guided command deliberately shows only 15 reviewed choices: - -- all seven files in a `Proper Torque Mod` directory; and -- eight `.rwd` recovery files whose filename clearly says `stock`. - -Those choices are an explicit allowlist in `flash.py`. A valid checksum alone -does not make an old tune appropriate for a car, so files labelled legacy, -linear, max, spike, mod, testing, dated, or work in progress are not added to -the ordinary menu. The nested category path is displayed alongside every menu -choice so similarly named images are not confused. - -Run the guided tool from the openpilot root: - -```bash -python3 flash.py -``` - -## Guided catalog - -| EPS family | Proper Torque Mod | clearly labelled stock recovery | -| --- | --- | --- | -| TBA-A030 | `Proper Torque Mod/39990-TBA,A030-PTM.rwd` | `Legacy and Stock/stock-39990-TBA-A030.rwd` | -| TBA-C020 | `Proper Torque Mod/39990-TBA,C020-Trk4000-PTM.rwd` | `Legacy and Stock/stock-39990-TBA-C020.rwd` | -| TBA-C120 | `Proper Torque Mod/39990-TBA,C120-PTM.rwd` | `Legacy and Stock/stock-39990-TBA-C120.rwd` | -| TEG-A010 | `Proper Torque Mod/39990-TEG,A010-PTM.rwd` | not present in the archive | -| TG7-A060 Pilot | not present in the archive | `Legacy and Stock/39990-TG7-A060-STOCK.rwd` | -| TGG-A020 | not present in the archive | `Legacy and Stock/stock-39990-TGG-A020-HatchbackSport.rwd` | -| TGG-A120 | not labelled as PTM in the archive | `stock-39990-TGG-A120.rwd` | -| TLA-A040 CR-V | `Proper Torque Mod/39990-TLA-A040_Clarity_FF_tune_telemety_8cf8e537.rwd` | `Legacy and Stock/39990-TLA-A040-stock.rwd` | -| TRW-A020 Clarity | `Proper Torque Mod/ClarityMax-PTM.rwd` | not present in the archive | -| TXM-A040 Insight | `Proper Torque Mod/39990-TXM,A040-PTM.rwd` | `Legacy and Stock/TXM-A040-STOCK.rwd` | - -The archive included three empty category slots. No fake file or empty directory -was created for them: - -- `39990-TG7-A060 (2019 Honda Pilot)/Proper Torque Mod` -- `39990-TGG-A020/Proper Torque Mod` -- `39990-TRW-A020 (Honda Clarity)/Legacy and Stock` - -## Catalogued but not guided or approved for flashing - -- **Legacy/testing x5A RWDs:** retained under their source paths for comparison - and research. They are checksum-valid but are not automatically offered. -- **T6Z-A110:** the offline checker accepts only its exact four-ID group - (T6Z-A110 plus TG7-A040/A030/A020), CAN address `0x18DA30F1`, all four - `00 11 00 12 10 20` software secrets, key `01 02 03`, start `0x10000`, length - `0x50000`, known checksum boundaries, and the decrypted T6Z identity. It - prints a reference-only notice. The current `eps-update.py` intentionally - rejects this identity, and `flash.py` never lists it. Offline validation is - not flash approval. -- **TVA-A160:** the stock and 2x files use the different `0x31` container format. - They are retained as reference material. `check_rwd.py`, `eps-update.py`, and - the guided tool do not support or offer them. -- **Pilot 2X work in progress:** retained with its original extensionless name - under `Work in Progress`; it is never discovered by the guided tool. -- **Raw `.bin` dumps:** retained for research and recovery analysis only. No EPS - command discovers or flashes raw binaries. - -Do not bypass these boundaries just because a file exists in the repository. -Hardware compatibility and a valid recovery procedure must be established -before any reference file becomes a guided choice. - -## Integrity and offline validation - -All 34 RWD/container artifacts pass their outer little-endian file checksum. -The full offline checker passes all 32 x5A containers: 30 ordinary `.rwd` -files, the one explicitly profiled T6Z `.rwd`, and the extensionless Pilot WIP. -The two x31 files are expected to be rejected by the x5A checker. Raw binaries -are not RWD containers. - -Quote nested paths because several directory names contain spaces: - -```bash -python3 check_rwd.py "rwd/39990-TBA-C120/Proper Torque Mod/39990-TBA,C120-PTM.rwd" -python3 check_rwd.py "rwd/39990-T6Z-A110/39990-T6Z-A110.rwd" -``` - -The root and historical `eps_tools/` launchers also expand recursive quoted -patterns such as `rwd/**/*.rwd`. That broad pattern includes the two unsupported -x31 reference files, so a nonzero overall result is expected. - -## Deliberately excluded archive files - -The following three attachment-borne documents/scripts are not firmware and -were not imported or executed: - -- `39990-TVA-A160 (Honda Accord)/Flashing script and instructions via ssh/eps-update-tva.py` -- `39990-TVA-A160 (Honda Accord)/Flashing script and instructions via ssh/ONBOARDING.md` -- `39990-TXM-A040 (2019-2022 Honda Insight)/Legacy and Stock/readme.txt` - -The TVA script describes an unverified flashing path outside the safe x5A tool, -and its onboarding text would conflict with the canonical root workflow. The -TXM note refers to a file that is not in the archive. - -## Adding another image - -Add real files only; never create placeholders for missing firmware. Preserve a -clear model/category path, record the exact SHA-256 in `SHA256SUMS`, validate the -container and decrypted firmware where supported, and document whether it is -guided or reference-only. Adding a file to the guided menu is a separate safety -decision and requires an explicit `GUIDED_FLASH_FILES` entry. diff --git a/openpilot/sunnypilot/nrdr/tools/eps/rwd/SHA256SUMS b/openpilot/sunnypilot/nrdr/tools/eps/rwd/SHA256SUMS deleted file mode 100644 index 5a5b5689f..000000000 --- a/openpilot/sunnypilot/nrdr/tools/eps/rwd/SHA256SUMS +++ /dev/null @@ -1,37 +0,0 @@ -7fac5c25732761f60cb123ba8ea34d010486f3e7bea1859f9c2d42ff53af4f9d 39990-T6Z-A110/39990-T6Z-A110.rwd -835b99414d4927bac327996ac6771a8f2dcd3633baac1933b2ed2d091a568fb9 39990-TBA-A030/Legacy and Stock/39990-TBA,A030-linear-max.rwd -b023258dd774729cfcca3f0d3f6b9fefe1bc111f8b4d1b6bb0e83d788cbfec6f 39990-TBA-A030/Legacy and Stock/mod-39990-TBA,A030.rwd -50aa2a555b44ac80149cd5528290a6ac9b3452d6d127a03a6c9f9cf6758c9226 39990-TBA-A030/Legacy and Stock/stock-39990-TBA-A030.rwd -3694903e9ffa242097b4f649c61acf6df77d4b76c4f5d97f67194613731e8520 39990-TBA-A030/Proper Torque Mod/39990-TBA,A030-PTM.rwd -077d2dda35d43bf17c631837c28c980fa0b274de7a151efff0b75439ed87bc6b 39990-TBA-C020 (2019 Honda Civic Sport)/Legacy and Stock/2.5x-mod-39990-TBA,C020.rwd -b112945d28b0f3031bcf5f35c9a3609f9153110edd0dd6342c7e5a1a48b4576e 39990-TBA-C020 (2019 Honda Civic Sport)/Legacy and Stock/39990-TBA-C020-linear-max-V2.1-Linear45-FlatGain.rwd -7a5ddf7fb20724647d7b251b48d43dd60ab351eae96e099d31644bec60ac90bf 39990-TBA-C020 (2019 Honda Civic Sport)/Legacy and Stock/39990-TBA-C020-max.rwd -ad30cbd9faade0e2011f97229632c6c43e9c08e72aa5160f0a282c79843e9367 39990-TBA-C020 (2019 Honda Civic Sport)/Legacy and Stock/stock-39990-TBA-C020.rwd -2d8836e81a53d5e1cb44fb7645ab81336481b2676361805d8de07a199347b33d 39990-TBA-C020 (2019 Honda Civic Sport)/Proper Torque Mod/39990-TBA,C020-Trk4000-PTM.rwd -ac9c0fd6e57ad5fb118122863ae86d5aba40f88d8edd786dbfa82653a96f3dcd 39990-TBA-C120/Legacy and Stock/mod-39990-TBA,C120.rwd -273726b6bbcfc83b49a07be3a75bcffcaa9f87e8692277a918a9d8a89c18cd38 39990-TBA-C120/Legacy and Stock/stock-39990-TBA-C120.rwd -15e63414b3137a9c6906305bbfe90c207b48083f2b173207a8084ff7312002e3 39990-TBA-C120/Proper Torque Mod/39990-TBA,C120-PTM.rwd -4ebf643a99eda27c68ec162c639627a43aa07c3ce5924a9fd8910c13d2022040 39990-TEG-A010/Legacy and Stock/39990-TEG-A010-linear-max.rwd -80fbd0e023453553719dc7363923a5d036d05f025f705809b4f0603e664cf8b2 39990-TEG-A010/Proper Torque Mod/39990-TEG,A010-PTM.rwd -d28c27b573ef610a479875107002ac7d9a84689ba5f3660686d0aecf1afb7555 39990-TG7-A060 (2019 Honda Pilot)/Legacy and Stock/39990-TG7-A060-STOCK.bin -f13d6b28e60ed5a39d2117c461c0c759a3d8b742b39ea9bfd6861ed7cc9fe184 39990-TG7-A060 (2019 Honda Pilot)/Legacy and Stock/39990-TG7-A060-STOCK.rwd -6ef205bceb5128f48e27182fa4e128173f2f6c0995192efd57809c237b81e40f 39990-TG7-A060 (2019 Honda Pilot)/Work in Progress/39990-TG7-A060-2X (2019 Honda Pilot) -3a8cdf57ad75306e74d387d399306aa355628f2101c4589f9c7c1721901e235c 39990-TGG-A020/Legacy and Stock/mod-39990-TGG,A020-HatchbackSport.rwd -9b43471586cd39e31e7d932d31684b5c4bee056bc4de53d638bb3b02ee9ad73d 39990-TGG-A020/Legacy and Stock/stock-39990-TGG-A020-HatchbackSport.rwd -470559fa7e02d11b8531fb83fcac19ca609a451dc3bd931ff5810343d9f4e8b4 39990-TGG-A120/39990-TGG-A120-08072026-4250.rwd -357b2571cd65c2100c07b33b1326101f9ce8ea9790aaa67358df69f98c089fc7 39990-TGG-A120/mod-39990-TGG,A120.rwd -3780431a47003e13d3654bf767697c73b52c9fa0be9a2d96cf1e5f6fba6c73b6 39990-TGG-A120/stock-39990-TGG-A120.rwd -042fe60ba415f9214d82196efc595f78aea2e54cf95f9b8f1e2216e6ca7268ad 39990-TLA-A040 - (Honda_CRV_5G)/Legacy and Stock/39990-TLA-A040-linear-max.rwd -0ceab577022d7504562c9cbb9fe882b7995283c072baeffee54e78e18d5f3740 39990-TLA-A040 - (Honda_CRV_5G)/Legacy and Stock/39990-TLA-A040-stock.bin -f84968c6b2b1fbba8b0d538b80a1782213186f92f2fc706be8eece485cb717d1 39990-TLA-A040 - (Honda_CRV_5G)/Legacy and Stock/39990-TLA-A040-stock.rwd -ac6dce68bc3c36e7fa990170f7f56df678f9edd92617fd62cdf74a3e7274bc42 39990-TLA-A040 - (Honda_CRV_5G)/Legacy and Stock/39990-TLA-A040_tq30000_a9000_44256c0b.rwd -26f5390b654ace80b759bd20156d4c8ef834d97dc0e22889c2d247c052980afb 39990-TLA-A040 - (Honda_CRV_5G)/Proper Torque Mod/39990-TLA-A040_Clarity_FF_tune_telemety_8cf8e537.rwd -d4fe903bcf347495f4321be65a3c650c8c931f1650c3bef6443721da0dd80f7d 39990-TRW-A020 (Honda Clarity)/Proper Torque Mod/ClarityMax-PTM.rwd -8a809d1b320957e4846c9145ee662858759d514681ce46c247a4a197784202da 39990-TVA-A160 (Honda Accord)/39990-TVA,A160-V37-2xTorque.rwd -7ad6d49d4a97b661693423bc5ad9f1a754e7f80523010491e9097708294dcfea 39990-TVA-A160 (Honda Accord)/Stock/39990-TVA-A160-V9B-Stock.rwd -ccc59bb576114ae6ee906281bdc6041e425fb3fb1a320d4e7816bee96b599782 39990-TXM-A040 (2019-2022 Honda Insight)/Legacy and Stock/39990-TXM-A040 Bin/user.bin -ad7457d97dd165fc6426ad9f7948b7eac652dbbcf5e3490a7e401f25e773a59e 39990-TXM-A040 (2019-2022 Honda Insight)/Legacy and Stock/TXM-A040-2.5x-spike.rwd -02cf82ca9992f18796a64484cf0fe5470d77a09bd5fc5cd1a098dc61a5c3d2fb 39990-TXM-A040 (2019-2022 Honda Insight)/Legacy and Stock/TXM-A040-2x-linear.rwd -563e15e3895b7b6da4a036e98d7998bdfb272b871c4ccfa9e4c7cafe0755d828 39990-TXM-A040 (2019-2022 Honda Insight)/Legacy and Stock/TXM-A040-linear-max.rwd -834a2fc06e625553994580b1b8fbfd627e0572f5d1f86123f36bc5055c545d4a 39990-TXM-A040 (2019-2022 Honda Insight)/Legacy and Stock/TXM-A040-STOCK.rwd -833a41bf9c3a8b4051c37271e0f7e1b970532e58b143d7882729203fa399b6e8 39990-TXM-A040 (2019-2022 Honda Insight)/Proper Torque Mod/39990-TXM,A040-PTM.rwd diff --git a/openpilot/sunnypilot/nrdr/tools/eps/rwd_format/__init__.py b/openpilot/sunnypilot/nrdr/tools/eps/rwd_format/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/openpilot/sunnypilot/nrdr/tools/eps/rwd_format/base.py b/openpilot/sunnypilot/nrdr/tools/eps/rwd_format/base.py deleted file mode 100644 index 74b6bb015..000000000 --- a/openpilot/sunnypilot/nrdr/tools/eps/rwd_format/base.py +++ /dev/null @@ -1,60 +0,0 @@ -import struct - -class Base(object): - def __init__(self, data, headers, keys, addr_blocks, encrypted): - self._file_format = data[0:1] - self._file_headers = headers - self._file_checksum = struct.unpack('= addr_next, "address decreased" - if addr != addr_next: - if len(block_data) > 0: - firmware.append(block_data) - addr_blocks.append({"start": block_start, "length": len(block_data)}) - block_start = addr - block_data = "" - - block_data += data[i+2:i+data_size+2] - addr_next = addr + data_size - if len(block_data) > 0: - firmware.append(block_data) - addr_blocks.append({"start": block_start, "length": len(block_data)}) - - assert len(addr_blocks) > 0, "could not find firmware address blocks!" - - return addr_blocks, firmware diff --git a/openpilot/sunnypilot/nrdr/tools/eps/rwd_format/x5a.py b/openpilot/sunnypilot/nrdr/tools/eps/rwd_format/x5a.py deleted file mode 100644 index 064cb36ae..000000000 --- a/openpilot/sunnypilot/nrdr/tools/eps/rwd_format/x5a.py +++ /dev/null @@ -1,60 +0,0 @@ -import struct -from .base import Base -from .header import Header -from .header_value import HeaderValue - -class x5a(Base): - def __init__(self, data): - start_idx = 3 # skip file type indicator bytes - headers, header_data_len = self._parse_file_headers(data[start_idx:]) - keys = self._get_keys(headers) - - start_idx += header_data_len - addr_blocks, encrypted = self._get_firmware(data[start_idx:-4]) # exclude file checksum - - Base.__init__(self, data, headers, keys, addr_blocks, encrypted) - - def _parse_file_headers(self, data): - headers = list() - d_idx = 0 - - for h_idx in range(6): - h_prefix = data[d_idx] - d_idx += 1 - - # first byte is number of values - cnt = h_prefix - - f_header = Header(h_idx, h_prefix, "") - for v_idx in range(cnt): - v_prefix = data[d_idx] - d_idx += 1 - - # first byte is length of value - length = v_prefix - v_data = data[d_idx:d_idx+length] - d_idx += length - - h_value = HeaderValue(v_prefix, "", v_data) - f_header.values.append(h_value) - - headers.append(f_header) - - return headers, d_idx - - def _get_keys(self, headers): - for header in headers: - if header.id == 5: - assert len(header.values) == 1, "encryption key header does not have exactly one value!" - assert len(header.values[0].value) == 3, "encryption key header not three bytes!" - return header.values[0].value - - raise Exception("could not find encryption key header!") - - def _get_firmware(self, data): - start = struct.unpack('!I', data[0:4])[0] - length = struct.unpack('!I', data[4:8])[0] - - firmware = data[8:] - assert len(firmware) == length, "firmware length incorrect!" - return [{"start": start, "length": length}], [firmware] diff --git a/openpilot/sunnypilot/nrdr/tools/eps/tests/__init__.py b/openpilot/sunnypilot/nrdr/tools/eps/tests/__init__.py deleted file mode 100644 index b10c77366..000000000 --- a/openpilot/sunnypilot/nrdr/tools/eps/tests/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Tests for the canonical NRDR EPS tool bundle.""" diff --git a/openpilot/sunnypilot/nrdr/tools/eps/tests/test_rwd_validation.py b/openpilot/sunnypilot/nrdr/tools/eps/tests/test_rwd_validation.py deleted file mode 100644 index d63c5961b..000000000 --- a/openpilot/sunnypilot/nrdr/tools/eps/tests/test_rwd_validation.py +++ /dev/null @@ -1,440 +0,0 @@ -import contextlib -import hashlib -import importlib.util -import io -import os -from pathlib import Path -import struct -import subprocess -import sys -import tempfile -import types -import unittest -from unittest import mock - -from openpilot.sunnypilot.nrdr.tools.eps import flash -from openpilot.sunnypilot.nrdr.tools.eps import launcher -from openpilot.sunnypilot.nrdr.tools.eps.rwd_format.x5a import x5a - -ROOT = Path(__file__).resolve().parents[6] -EPS_DIR = ROOT / "openpilot" / "sunnypilot" / "nrdr" / "tools" / "eps" -CHECK_RWD_PATH = EPS_DIR / "check_rwd.py" -EPS_UPDATE_PATH = EPS_DIR / "eps-update.py" -RWD_DIR = EPS_DIR / "rwd" -SHA256_MANIFEST = RWD_DIR / "SHA256SUMS" - -EXPECTED_ASSETS = { - "39990-T6Z-A110/39990-T6Z-A110.rwd": "7fac5c25732761f60cb123ba8ea34d010486f3e7bea1859f9c2d42ff53af4f9d", - "39990-TBA-A030/Legacy and Stock/39990-TBA,A030-linear-max.rwd": "835b99414d4927bac327996ac6771a8f2dcd3633baac1933b2ed2d091a568fb9", - "39990-TBA-A030/Legacy and Stock/mod-39990-TBA,A030.rwd": "b023258dd774729cfcca3f0d3f6b9fefe1bc111f8b4d1b6bb0e83d788cbfec6f", - "39990-TBA-A030/Legacy and Stock/stock-39990-TBA-A030.rwd": "50aa2a555b44ac80149cd5528290a6ac9b3452d6d127a03a6c9f9cf6758c9226", - "39990-TBA-A030/Proper Torque Mod/39990-TBA,A030-PTM.rwd": "3694903e9ffa242097b4f649c61acf6df77d4b76c4f5d97f67194613731e8520", - "39990-TBA-C020 (2019 Honda Civic Sport)/Legacy and Stock/2.5x-mod-39990-TBA,C020.rwd": "077d2dda35d43bf17c631837c28c980fa0b274de7a151efff0b75439ed87bc6b", - "39990-TBA-C020 (2019 Honda Civic Sport)/Legacy and Stock/39990-TBA-C020-linear-max-V2.1-Linear45-FlatGain.rwd": ( - "b112945d28b0f3031bcf5f35c9a3609f9153110edd0dd6342c7e5a1a48b4576e" - ), - "39990-TBA-C020 (2019 Honda Civic Sport)/Legacy and Stock/39990-TBA-C020-max.rwd": "7a5ddf7fb20724647d7b251b48d43dd60ab351eae96e099d31644bec60ac90bf", - "39990-TBA-C020 (2019 Honda Civic Sport)/Legacy and Stock/stock-39990-TBA-C020.rwd": "ad30cbd9faade0e2011f97229632c6c43e9c08e72aa5160f0a282c79843e9367", - "39990-TBA-C020 (2019 Honda Civic Sport)/Proper Torque Mod/39990-TBA,C020-Trk4000-PTM.rwd": ( - "2d8836e81a53d5e1cb44fb7645ab81336481b2676361805d8de07a199347b33d" - ), - "39990-TBA-C120/Legacy and Stock/mod-39990-TBA,C120.rwd": "ac9c0fd6e57ad5fb118122863ae86d5aba40f88d8edd786dbfa82653a96f3dcd", - "39990-TBA-C120/Legacy and Stock/stock-39990-TBA-C120.rwd": "273726b6bbcfc83b49a07be3a75bcffcaa9f87e8692277a918a9d8a89c18cd38", - "39990-TBA-C120/Proper Torque Mod/39990-TBA,C120-PTM.rwd": "15e63414b3137a9c6906305bbfe90c207b48083f2b173207a8084ff7312002e3", - "39990-TEG-A010/Legacy and Stock/39990-TEG-A010-linear-max.rwd": "4ebf643a99eda27c68ec162c639627a43aa07c3ce5924a9fd8910c13d2022040", - "39990-TEG-A010/Proper Torque Mod/39990-TEG,A010-PTM.rwd": "80fbd0e023453553719dc7363923a5d036d05f025f705809b4f0603e664cf8b2", - "39990-TG7-A060 (2019 Honda Pilot)/Legacy and Stock/39990-TG7-A060-STOCK.bin": "d28c27b573ef610a479875107002ac7d9a84689ba5f3660686d0aecf1afb7555", - "39990-TG7-A060 (2019 Honda Pilot)/Legacy and Stock/39990-TG7-A060-STOCK.rwd": "f13d6b28e60ed5a39d2117c461c0c759a3d8b742b39ea9bfd6861ed7cc9fe184", - "39990-TG7-A060 (2019 Honda Pilot)/Work in Progress/39990-TG7-A060-2X (2019 Honda Pilot)": "6ef205bceb5128f48e27182fa4e128173f2f6c0995192efd57809c237b81e40f", - "39990-TGG-A020/Legacy and Stock/mod-39990-TGG,A020-HatchbackSport.rwd": "3a8cdf57ad75306e74d387d399306aa355628f2101c4589f9c7c1721901e235c", - "39990-TGG-A020/Legacy and Stock/stock-39990-TGG-A020-HatchbackSport.rwd": "9b43471586cd39e31e7d932d31684b5c4bee056bc4de53d638bb3b02ee9ad73d", - "39990-TGG-A120/39990-TGG-A120-08072026-4250.rwd": "470559fa7e02d11b8531fb83fcac19ca609a451dc3bd931ff5810343d9f4e8b4", - "39990-TGG-A120/mod-39990-TGG,A120.rwd": "357b2571cd65c2100c07b33b1326101f9ce8ea9790aaa67358df69f98c089fc7", - "39990-TGG-A120/stock-39990-TGG-A120.rwd": "3780431a47003e13d3654bf767697c73b52c9fa0be9a2d96cf1e5f6fba6c73b6", - "39990-TLA-A040 - (Honda_CRV_5G)/Legacy and Stock/39990-TLA-A040-linear-max.rwd": "042fe60ba415f9214d82196efc595f78aea2e54cf95f9b8f1e2216e6ca7268ad", - "39990-TLA-A040 - (Honda_CRV_5G)/Legacy and Stock/39990-TLA-A040-stock.bin": "0ceab577022d7504562c9cbb9fe882b7995283c072baeffee54e78e18d5f3740", - "39990-TLA-A040 - (Honda_CRV_5G)/Legacy and Stock/39990-TLA-A040-stock.rwd": "f84968c6b2b1fbba8b0d538b80a1782213186f92f2fc706be8eece485cb717d1", - "39990-TLA-A040 - (Honda_CRV_5G)/Legacy and Stock/39990-TLA-A040_tq30000_a9000_44256c0b.rwd": ( - "ac6dce68bc3c36e7fa990170f7f56df678f9edd92617fd62cdf74a3e7274bc42" - ), - "39990-TLA-A040 - (Honda_CRV_5G)/Proper Torque Mod/39990-TLA-A040_Clarity_FF_tune_telemety_8cf8e537.rwd": ( - "26f5390b654ace80b759bd20156d4c8ef834d97dc0e22889c2d247c052980afb" - ), - "39990-TRW-A020 (Honda Clarity)/Proper Torque Mod/ClarityMax-PTM.rwd": "d4fe903bcf347495f4321be65a3c650c8c931f1650c3bef6443721da0dd80f7d", - "39990-TVA-A160 (Honda Accord)/39990-TVA,A160-V37-2xTorque.rwd": "8a809d1b320957e4846c9145ee662858759d514681ce46c247a4a197784202da", - "39990-TVA-A160 (Honda Accord)/Stock/39990-TVA-A160-V9B-Stock.rwd": "7ad6d49d4a97b661693423bc5ad9f1a754e7f80523010491e9097708294dcfea", - "39990-TXM-A040 (2019-2022 Honda Insight)/Legacy and Stock/39990-TXM-A040 Bin/user.bin": "ccc59bb576114ae6ee906281bdc6041e425fb3fb1a320d4e7816bee96b599782", - "39990-TXM-A040 (2019-2022 Honda Insight)/Legacy and Stock/TXM-A040-2.5x-spike.rwd": "ad7457d97dd165fc6426ad9f7948b7eac652dbbcf5e3490a7e401f25e773a59e", - "39990-TXM-A040 (2019-2022 Honda Insight)/Legacy and Stock/TXM-A040-2x-linear.rwd": "02cf82ca9992f18796a64484cf0fe5470d77a09bd5fc5cd1a098dc61a5c3d2fb", - "39990-TXM-A040 (2019-2022 Honda Insight)/Legacy and Stock/TXM-A040-linear-max.rwd": "563e15e3895b7b6da4a036e98d7998bdfb272b871c4ccfa9e4c7cafe0755d828", - "39990-TXM-A040 (2019-2022 Honda Insight)/Legacy and Stock/TXM-A040-STOCK.rwd": "834a2fc06e625553994580b1b8fbfd627e0572f5d1f86123f36bc5055c545d4a", - "39990-TXM-A040 (2019-2022 Honda Insight)/Proper Torque Mod/39990-TXM,A040-PTM.rwd": "833a41bf9c3a8b4051c37271e0f7e1b970532e58b143d7882729203fa399b6e8", -} - -GUIDED_IMAGES = { - "39990-TBA-A030/Legacy and Stock/stock-39990-TBA-A030.rwd", - "39990-TBA-A030/Proper Torque Mod/39990-TBA,A030-PTM.rwd", - "39990-TBA-C020 (2019 Honda Civic Sport)/Legacy and Stock/stock-39990-TBA-C020.rwd", - "39990-TBA-C020 (2019 Honda Civic Sport)/Proper Torque Mod/39990-TBA,C020-Trk4000-PTM.rwd", - "39990-TBA-C120/Legacy and Stock/stock-39990-TBA-C120.rwd", - "39990-TBA-C120/Proper Torque Mod/39990-TBA,C120-PTM.rwd", - "39990-TEG-A010/Proper Torque Mod/39990-TEG,A010-PTM.rwd", - "39990-TG7-A060 (2019 Honda Pilot)/Legacy and Stock/39990-TG7-A060-STOCK.rwd", - "39990-TGG-A020/Legacy and Stock/stock-39990-TGG-A020-HatchbackSport.rwd", - "39990-TGG-A120/stock-39990-TGG-A120.rwd", - "39990-TLA-A040 - (Honda_CRV_5G)/Legacy and Stock/39990-TLA-A040-stock.rwd", - "39990-TLA-A040 - (Honda_CRV_5G)/Proper Torque Mod/39990-TLA-A040_Clarity_FF_tune_telemety_8cf8e537.rwd", - "39990-TRW-A020 (Honda Clarity)/Proper Torque Mod/ClarityMax-PTM.rwd", - "39990-TXM-A040 (2019-2022 Honda Insight)/Legacy and Stock/TXM-A040-STOCK.rwd", - "39990-TXM-A040 (2019-2022 Honda Insight)/Proper Torque Mod/39990-TXM,A040-PTM.rwd", -} - -T6Z_IMAGE = "39990-T6Z-A110/39990-T6Z-A110.rwd" -X31_IMAGES = { - "39990-TVA-A160 (Honda Accord)/39990-TVA,A160-V37-2xTorque.rwd", - "39990-TVA-A160 (Honda Accord)/Stock/39990-TVA-A160-V9B-Stock.rwd", -} -WIP_IMAGE = "39990-TG7-A060 (2019 Honda Pilot)/Work in Progress/39990-TG7-A060-2X (2019 Honda Pilot)" -RAW_IMAGES = { - "39990-TG7-A060 (2019 Honda Pilot)/Legacy and Stock/39990-TG7-A060-STOCK.bin", - "39990-TLA-A040 - (Honda_CRV_5G)/Legacy and Stock/39990-TLA-A040-stock.bin", - "39990-TXM-A040 (2019-2022 Honda Insight)/Legacy and Stock/39990-TXM-A040 Bin/user.bin", -} -RWD_IMAGES = {name for name in EXPECTED_ASSETS if name.endswith(".rwd")} -X5A_RWD_IMAGES = RWD_IMAGES - X31_IMAGES -X5A_CONTAINERS = X5A_RWD_IMAGES | {WIP_IMAGE} -OUTER_CHECKSUM_IMAGES = RWD_IMAGES | {WIP_IMAGE} - -spec = importlib.util.spec_from_file_location("check_rwd", CHECK_RWD_PATH) -assert spec is not None and spec.loader is not None -check_rwd = importlib.util.module_from_spec(spec) -spec.loader.exec_module(check_rwd) - - -def block_offset(data): - idx = 3 - for _ in range(6): - count = data[idx] - idx += 1 - for _ in range(count): - length = data[idx] - idx += length + 1 - return idx - - -def header_value_offset(data, wanted_header, wanted_value): - idx = 3 - for header_index in range(6): - count = data[idx] - idx += 1 - for value_index in range(count): - length = data[idx] - idx += 1 - if (header_index, value_index) == (wanted_header, wanted_value): - return idx - idx += length - raise ValueError(f"No header value {wanted_header}:{wanted_value}") - - -def repair_file_checksum(data): - struct.pack_into("I", wrong_start, block_offset(wrong_start), 0x20000) - repair_file_checksum(wrong_start) - mutations["flash-start"] = wrong_start - - wrong_payload_identity = bytearray(original) - payload_identity = block_offset(wrong_payload_identity) + 8 + check_rwd.T6Z_IDENTITY_OFFSET - wrong_payload_identity[payload_identity] ^= 1 - repair_file_checksum(wrong_payload_identity) - mutations["decrypted-identity"] = wrong_payload_identity - - with tempfile.TemporaryDirectory() as tmpdir: - for label, data in mutations.items(): - with self.subTest(label=label): - path = Path(tmpdir) / f"t6z-{label}.rwd" - path.write_bytes(data) - self.assertFalse(self.check_quietly(path)) - - def test_exact_two_x31_files_are_reference_only_and_rejected(self): - self.assertEqual(len(X31_IMAGES), 2) - self.assertTrue(X31_IMAGES.isdisjoint(flash.GUIDED_FLASH_FILES)) - for name in X31_IMAGES: - with self.subTest(name=name): - output = io.StringIO() - with contextlib.redirect_stdout(output): - self.assertFalse(check_rwd.check(RWD_DIR / name)) - self.assertIn("not a 0x5A format RWD", output.getvalue()) - - def test_wip_raw_and_noncurated_library_files_are_not_guided(self): - self.assertNotIn(WIP_IMAGE, flash.GUIDED_FLASH_FILES) - self.assertTrue(RAW_IMAGES.isdisjoint(flash.GUIDED_FLASH_FILES)) - self.assertTrue((set(EXPECTED_ASSETS) - GUIDED_IMAGES).isdisjoint(flash.GUIDED_FLASH_FILES)) - - def test_no_old_flat_payload_or_trailing_space_path_component(self): - self.assertEqual( - {path.name for path in RWD_DIR.iterdir() if path.is_file()}, - {"README.md", "SHA256SUMS"}, - ) - for name in EXPECTED_ASSETS: - with self.subTest(name=name): - self.assertTrue(all(part == part.rstrip() for part in Path(name).parts)) - - def test_payload_corruption_fails_and_cli_exits_nonzero(self): - stock = "39990-TG7-A060 (2019 Honda Pilot)/Legacy and Stock/39990-TG7-A060-STOCK.rwd" - data = bytearray((RWD_DIR / stock).read_bytes()) - data[block_offset(data) + 8 + 0x100] ^= 1 - repair_file_checksum(data) - - with tempfile.TemporaryDirectory() as tmpdir: - path = Path(tmpdir) / "corrupt.rwd" - path.write_bytes(data) - self.assertFalse(self.check_quietly(path)) - env = os.environ.copy() - env["PYTHONUTF8"] = "1" - result = subprocess.run([sys.executable, str(CHECK_RWD_PATH), str(path)], - env=env, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=False) - self.assertNotEqual(result.returncode, 0) - - def test_wrong_v850_identity_and_flash_start_fail_with_valid_outer_checksum(self): - stock = "39990-TG7-A060 (2019 Honda Pilot)/Legacy and Stock/39990-TG7-A060-STOCK.rwd" - original = (RWD_DIR / stock).read_bytes() - mutations = {} - - wrong_identity = bytearray(original) - identity_at = wrong_identity.index(b"39990-TG7-A060") - original_identity = b"39990-TG7-A060" - wrong_identity[identity_at:identity_at + len(original_identity)] = b"39990-TG7-A999" - repair_file_checksum(wrong_identity) - mutations["wrong-identity.rwd"] = wrong_identity - - wrong_start = bytearray(original) - struct.pack_into(">I", wrong_start, block_offset(wrong_start), 0x20000) - repair_file_checksum(wrong_start) - mutations["wrong-start.rwd"] = wrong_start - - with tempfile.TemporaryDirectory() as tmpdir: - for filename, data in mutations.items(): - with self.subTest(filename=filename): - path = Path(tmpdir) / filename - path.write_bytes(data) - self.assertFalse(self.check_quietly(path)) - - def test_launcher_maps_nested_aliases_and_recursive_globs(self): - nested = "39990-TBA-C120/Proper Torque Mod/39990-TBA,C120-PTM.rwd" - expected = str(RWD_DIR / Path(nested)) - self.assertEqual(launcher.normalized_arguments("check_rwd.py", [f"rwd/{nested}"]), [expected]) - self.assertEqual(launcher.normalized_arguments("eps-update.py", [f"eps_tools/rwd/{nested}"]), [expected]) - self.assertEqual(launcher.normalized_arguments("flash.py", ["--bus", "1"]), ["--bus", "1"]) - - expanded = launcher.normalized_arguments("check_rwd.py", ["rwd/**/*.rwd"]) - self.assertEqual( - {Path(path).relative_to(RWD_DIR).as_posix() for path in expanded}, - RWD_IMAGES, - ) - with self.assertRaisesRegex(SystemExit, "matched more than one image"): - launcher.normalized_arguments("eps-update.py", ["rwd/**/*.rwd"]) - - def test_guided_flasher_keeps_danger_explicit(self): - dry_run = flash.run_eps_update("rwd/example.rwd", 1, skip_checksum=False, danger=False) - real_flash = flash.run_eps_update("rwd/example.rwd", 1, skip_checksum=False, danger=True) - self.assertNotIn("--danger", dry_run) - self.assertEqual(real_flash.count("--danger"), 1) - self.assertIn(str(EPS_DIR / "eps-update.py"), dry_run) - self.assertIn(str(ROOT), flash.subprocess_env()["PYTHONPATH"].split(os.pathsep)) - - def test_recovery_guidance_does_not_promise_success_or_blind_retry(self): - readme = (EPS_DIR / "README.md").read_text() - flash_source = (EPS_DIR / "flash.py").read_text() - diag_source = (EPS_DIR / "eps-diag.py").read_text() - - self.assertNotIn("the EPS is not permanently bricked", readme) - self.assertNotIn("the EPS is recoverable: just run this again", flash_source) - self.assertNotIn("EPS is responding — try the flash again", diag_source) - self.assertNotIn("re-run eps-update.py with the same .rwd", diag_source) - self.assertIn("recovery is not guaranteed", flash_source) - self.assertIn("it does **not** prove that the selected image", readme) - self.assertIn("Do not blindly reuse the previous .rwd", diag_source) - - def test_root_and_historical_commands_accept_nested_paths_with_spaces(self): - nested = "rwd/39990-TBA-C120/Proper Torque Mod/39990-TBA,C120-PTM.rwd" - environment = os.environ.copy() - environment["PYTHONUTF8"] = "1" - commands = ( - ([sys.executable, str(ROOT / "check_rwd.py"), nested], ROOT), - ([sys.executable, "check_rwd.py", nested], ROOT / "eps_tools"), - ) - for command, cwd in commands: - with self.subTest(command=command, cwd=cwd): - result = subprocess.run(command, cwd=cwd, env=environment, capture_output=True, text=True, check=False) - self.assertEqual(result.returncode, 0, result.stdout + result.stderr) - self.assertIn("ALL GOOD", result.stdout) - - def test_legacy_eps_directory_contains_launchers_only(self): - legacy = ROOT / "eps_tools" - self.assertFalse((legacy / "rwd").exists()) - self.assertFalse((legacy / "rwd_format").exists()) - self.assertEqual( - {path.name for path in legacy.iterdir() if path.name != "__pycache__"}, - {"README.md", "_bootstrap.py", "check_rwd.py", "eps-diag.py", "eps-update.py", "flash.py"}, - ) - - def test_root_guided_command_lists_only_curated_nested_paths_before_quit(self): - environment = os.environ.copy() - environment["PYTHONUTF8"] = "1" - result = subprocess.run( - [sys.executable, str(ROOT / "flash.py")], - cwd=ROOT, - env=environment, - input="q\n", - capture_output=True, - text=True, - timeout=60, - check=False, - ) - output = result.stdout + result.stderr - self.assertNotEqual(result.returncode, 0) - self.assertIn("Aborted", output) - for name in GUIDED_IMAGES: - with self.subTest(guided=name): - self.assertIn(name, output) - for name in set(EXPECTED_ASSETS) - GUIDED_IMAGES: - with self.subTest(reference=name): - self.assertNotIn(name, output) - - def test_eps_bundle_and_launchers_ship_in_release(self): - output = subprocess.check_output([sys.executable, "tools/release/release_files.py"], cwd=ROOT) - released = {os.fsdecode(path) for path in output.split(b"\0") if path} - expected = { - path.relative_to(ROOT).as_posix() - for path in EPS_DIR.rglob("*") - if path.is_file() and "__pycache__" not in path.parts and path.suffix != ".pyc" - } - expected.update(("flash.py", "eps-update.py", "eps-diag.py", "check_rwd.py")) - expected.update( - f"eps_tools/{name}" - for name in ("README.md", "_bootstrap.py", "check_rwd.py", "eps-diag.py", "eps-update.py", "flash.py") - ) - self.assertEqual(expected - released, set()) - - -if __name__ == "__main__": - unittest.main() diff --git a/openpilot/sunnypilot/nrdr/tools/eps/ui_flash_runner.py b/openpilot/sunnypilot/nrdr/tools/eps/ui_flash_runner.py deleted file mode 100755 index 426e9645f..000000000 --- a/openpilot/sunnypilot/nrdr/tools/eps/ui_flash_runner.py +++ /dev/null @@ -1,430 +0,0 @@ -#!/usr/bin/env python3 -"""Detached EPS UI runner. - -The settings UI watches status.json while this process releases only the Panda -USB interface, validates and flashes the selected image, then restores pandad. -It never stops the comma service or the UI. -""" -from __future__ import annotations - -import argparse -import contextlib -import fcntl -import importlib.util -import io -import json -import os -from pathlib import Path -import subprocess -import sys -import time -import traceback -from datetime import datetime, timezone - -EPS_DIR = Path(__file__).resolve().parent -REPO_ROOT = EPS_DIR.parents[4] -sys.path.insert(0, str(REPO_ROOT)) -sys.path.insert(0, str(EPS_DIR)) - - -def _load_eps_flash(): - spec = importlib.util.spec_from_file_location("eps_flash_module", EPS_DIR / "flash.py") - if spec is None or spec.loader is None: - raise ImportError("Could not load EPS flash module") - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) - return module - - -flash = _load_eps_flash() -from openpilot.cereal import messaging # noqa: E402 -STATE_DIR = Path("/data/eps_flash") -STATUS_PATH = STATE_DIR / "status.json" -LOG_PATH = STATE_DIR / "run.log" -LOCK_PATH = STATE_DIR / "runner.lock" -PANDAD_BLOCK_FILE = STATE_DIR / "block_pandad" -DRY_RUN_MARKERS = ("Using real client", "Safe mode: aborting before mutating actions") - - -def _timestamp() -> str: - return datetime.now(timezone.utc).isoformat(timespec="seconds") - - -def _log(message: str) -> None: - STATE_DIR.mkdir(parents=True, exist_ok=True) - with LOG_PATH.open("a", encoding="utf-8") as f: - f.write(f"[{_timestamp()}] {message.rstrip()}\n") - - -def _write_status(record: dict, **updates) -> None: - record.update(updates) - record["updated_at"] = time.time() - STATE_DIR.mkdir(parents=True, exist_ok=True) - tmp = STATUS_PATH.with_suffix(f".tmp.{os.getpid()}") - with tmp.open("w", encoding="utf-8") as f: - json.dump(record, f, ensure_ascii=False, sort_keys=True) - os.chmod(tmp, 0o644) - os.replace(tmp, STATUS_PATH) - - -def _vehicle_is_offroad() -> bool: - messaging.reset_context() - sm = messaging.SubMaster(["deviceState"]) - # A fresh SubMaster can need a few cycles before deviceState is valid. Poll - # instead of treating one unlucky timeout as an onroad state. - for _ in range(20): - sm.update(250) - if sm.all_checks(["deviceState"]): - return not sm["deviceState"].started - return False - - -def _pandad_pids() -> list[int]: - pids: list[int] = [] - for entry in Path("/proc").iterdir(): - if not entry.name.isdigit(): - continue - try: - raw_tokens = [t for t in (entry / "cmdline").read_bytes().split(b"\0") if t] - except (OSError, PermissionError): - continue - if any(token == b"openpilot.selfdrive.pandad.pandad" for token in raw_tokens) or \ - any(Path(os.fsdecode(token)).name == "pandad" for token in raw_tokens): - pids.append(int(entry.name)) - return sorted(pids) - - -def _wait_for_pandad(absent: bool, timeout: float) -> bool: - deadline = time.monotonic() + timeout - while time.monotonic() < deadline: - pids = _pandad_pids() - if bool(not pids) == absent: - return True - time.sleep(0.2) - return bool(not _pandad_pids()) == absent - - -def _set_block_pandad(blocked: bool) -> None: - STATE_DIR.mkdir(parents=True, exist_ok=True) - if blocked: - PANDAD_BLOCK_FILE.write_text(f"{os.getpid()}\n", encoding="utf-8") - os.chmod(PANDAD_BLOCK_FILE, 0o644) - else: - PANDAD_BLOCK_FILE.unlink(missing_ok=True) - - -def _run_streamed(state: dict, command: list[str], env: dict[str, str], phase: str) -> tuple[int, str]: - _log("RUN " + " ".join(command)) - _write_status(state, phase=phase, message=f"Running {Path(command[1]).name}") - proc = subprocess.Popen( - command, - cwd=EPS_DIR, - env=env, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - text=True, - bufsize=1, - ) - lines: list[str] = [] - assert proc.stdout is not None - for raw in proc.stdout: - line = raw.rstrip("\n") - lines.append(raw) - if line.strip(): - _log(line) - if line.startswith("PROGRESS "): - try: - _, progress_phase, percent = line.split(maxsplit=2) - progress = max(0, min(100, int(percent))) - _write_status(state, phase=progress_phase, progress=progress, - message=f"{progress_phase} {progress}%") - except (TypeError, ValueError): - _write_status(state, phase=phase, message=line.strip()[-240:]) - else: - _write_status(state, phase=phase, message=line.strip()[-240:]) - rc = proc.wait() - _write_status(state, phase=phase, message=f"{Path(command[1]).name} exited {rc}") - return rc, "".join(lines) - - -def _validated_image(raw_path: str) -> Path: - path = Path(raw_path) - if not path.is_absolute(): - path = EPS_DIR / path - path = path.resolve() - allowed = {Path(p).resolve() for p in flash.find_images()} - if path not in allowed: - raise ValueError("Firmware is not in the curated EPS guided-flash list") - if not flash.validate(str(path)): - raise ValueError("Firmware failed check_rwd validation") - return path - - -def _image_matches_car(image: Path) -> bool: - car_fw = flash.car_eps_fw() - if not car_fw: - try: - car_fw = json.loads(STATUS_PATH.read_text(encoding="utf-8")).get("detected_fw") - except (OSError, ValueError, TypeError): - car_fw = None - if not car_fw: - return False - versions = flash.rwd_supported_versions(str(image)) or set() - return flash.norm_fw(car_fw) in versions - - -def _read_live_eps_info() -> dict: - info = { - "eps_online": False, - "eps_bus": None, - "eps_part_number": None, - "eps_vin": None, - "eps_read_at": time.time(), - } - try: - from panda import Panda - from opendbc.car.structs import CarParams - from opendbc.car.uds import DATA_IDENTIFIER_TYPE, SESSION_TYPE, UdsClient, NegativeResponseError - except Exception as exc: - _log(f"Live EPS identify unavailable: {exc}") - return info - - panda = None - try: - panda = Panda(disable_checks=True) - panda.set_safety_mode(CarParams.SafetyModel.elm327) - for bus in (1, 0): - uds = UdsClient(panda, flash.EPS_ADDR, bus=bus, timeout=2.0) - try: - uds.tester_present() - except NegativeResponseError: - pass - except Exception: - continue - - info["eps_online"] = True - info["eps_bus"] = bus - _log(f"EPS responded on bus {bus}") - - try: - uds.diagnostic_session_control(SESSION_TYPE.DEFAULT) - except Exception: - pass - - try: - data = uds.read_data_by_identifier(DATA_IDENTIFIER_TYPE.APPLICATION_SOFTWARE_IDENTIFICATION) - part_number = bytes(data).decode("latin-1", "replace").strip("\x00").strip() - if part_number: - info["eps_part_number"] = part_number - _log(f"Live EPS part number: {part_number}") - except Exception as exc: - _log(f"EPS part number read failed on bus {bus}: {exc}") - - try: - data = uds.read_data_by_identifier(DATA_IDENTIFIER_TYPE.VIN) - vin = bytes(data).decode("latin-1", "replace").strip("\x00").strip() - if vin: - info["eps_vin"] = vin - _log(f"EPS VIN: {vin}") - except Exception: - pass - break - except Exception as exc: - _log(f"Live EPS identify failed: {exc}") - finally: - if panda is not None: - panda.close() - return info - - -def _resolve_bus(requested: str, image: Path, state: dict) -> int: - if requested != "auto": - return int(requested, 0) - - _write_status(state, phase="bus_detect", message="Detecting EPS CAN bus") - addr = flash.rwd_can_address(str(image)) or flash.EPS_ADDR - output = io.StringIO() - with contextlib.redirect_stdout(output): - alive = flash.probe_eps_buses(addr) - for line in output.getvalue().splitlines(): - if line.strip(): - _log(line) - if 1 in alive: - return 1 - if alive: - return alive[0] - _log("No live EPS bus detected; falling back to bus 1") - return 1 - - -def _new_state(action: str, image: Path | None = None) -> dict: - return { - "state": "running", - "phase": f"{action}_starting", - "message": f"Starting {action}", - "action": action, - "image": flash.image_display_name(str(image)) if image else "", - "image_path": str(image) if image else "", - "started_at": time.time(), - "updated_at": time.time(), - "bus": None, - "progress": 0, - "returncode": None, - } - - -def _failed(state: dict, exc: Exception) -> int: - _log(f"FAILED: {exc}") - _log(traceback.format_exc()) - _write_status(state, state="failed", phase="failed", message=str(exc)) - return 1 - - -def _release_panda(state: dict, args: argparse.Namespace) -> int: - try: - if not _vehicle_is_offroad(): - raise RuntimeError("Vehicle must be offroad before releasing Panda") - _write_status(state, phase="releasing_panda", message="Stopping pandad and releasing Panda USB") - _set_block_pandad(True) - if not _wait_for_pandad(absent=True, timeout=args.release_timeout): - _set_block_pandad(False) - raise RuntimeError("pandad did not exit; Panda was not released") - time.sleep(args.release_delay) - eps_info = _read_live_eps_info() - detected_fw = eps_info.get("eps_part_number") - if detected_fw: - message = f"Panda released; EPS {detected_fw} on bus {eps_info.get('eps_bus')}" - else: - message = "Panda released; EPS did not respond" - _write_status(state, detected_fw=detected_fw, state="success", phase="panda_released", **eps_info) - _write_status(state, message=message) - _log("Panda released") - return 0 - except Exception as exc: - _set_block_pandad(False) - return _failed(state, exc) - - -def _restore_panda(state: dict, args: argparse.Namespace) -> int: - try: - _write_status(state, phase="restoring_panda", message="Removing Panda release lock") - _set_block_pandad(False) - if not _wait_for_pandad(absent=False, timeout=args.restore_timeout): - raise RuntimeError("pandad did not restart before timeout") - _write_status(state, state="success", phase="panda_restored", message="pandad restored") - _log("pandad restored") - return 0 - except Exception as exc: - return _failed(state, exc) - - -def _identify_eps(state: dict, args: argparse.Namespace) -> int: - try: - if not _vehicle_is_offroad(): - raise RuntimeError("Vehicle must be offroad before reading EPS") - if not PANDAD_BLOCK_FILE.exists(): - raise RuntimeError("Release Panda first") - if not _wait_for_pandad(absent=True, timeout=args.release_timeout): - raise RuntimeError("pandad is still running") - _write_status(state, phase="identify", message="Reading EPS software ID and VIN") - eps_info = _read_live_eps_info() - detected_fw = eps_info.get("eps_part_number") - message = f"EPS {detected_fw} on bus {eps_info.get('eps_bus')}" if detected_fw else "No EPS part number received" - _write_status(state, detected_fw=detected_fw, state="success", phase="eps_identified", message=message, **eps_info) - return 0 - except Exception as exc: - return _failed(state, exc) - - -def _flash_panda(state: dict, args: argparse.Namespace, image: Path) -> int: - try: - if not _vehicle_is_offroad(): - raise RuntimeError("Vehicle must be offroad before flashing EPS") - if not _image_matches_car(image): - raise RuntimeError("Selected firmware does not match the reported EPS firmware") - if not PANDAD_BLOCK_FILE.exists(): - raise RuntimeError("Release Panda first") - if not _wait_for_pandad(absent=True, timeout=args.release_timeout): - raise RuntimeError("pandad is still running; restore and release Panda again") - - env = flash.subprocess_env() - bus = _resolve_bus(args.bus, image, state) - _write_status(state, bus=bus) - _log(f"Using CAN bus {bus}") - - dry_cmd = [ - sys.executable, - str(EPS_DIR / "eps-update.py"), - str(image), - "-b", - str(bus), - "--seed-timeout", - str(args.seed_timeout), - ] - dry_rc, dry_output = _run_streamed(state, dry_cmd, env, "dry_run") - if not all(marker in dry_output for marker in DRY_RUN_MARKERS): - raise RuntimeError(f"Dry run did not reach the safe abort point (exit {dry_rc})") - if not _vehicle_is_offroad(): - raise RuntimeError("Vehicle left offroad during the dry run; refusing to flash") - _write_status(state, phase="dry_run_ok", message="Dry run passed; flashing now") - - flash_rc, _ = _run_streamed(state, [*dry_cmd, "--danger"], env, "flashing") - _write_status(state, returncode=flash_rc) - if flash_rc != 0: - raise RuntimeError(f"EPS flash failed with exit code {flash_rc}") - - _write_status(state, state="success", phase="complete", message="EPS flash completed; restore Panda when ready") - _log("EPS flash completed successfully") - return 0 - except Exception as exc: - return _failed(state, exc) - - -def run(args: argparse.Namespace) -> int: - image = None - if args.action == "flash": - try: - image = _validated_image(args.rwd) - except Exception as exc: - return _failed(_new_state(args.action), exc) - - state = _new_state(args.action, image) - _write_status(state) - if args.action == "release": - return _release_panda(state, args) - if args.action == "restore": - return _restore_panda(state, args) - if args.action == "identify": - return _identify_eps(state, args) - return _flash_panda(state, args, image) - -def main() -> int: - parser = argparse.ArgumentParser(description="UI-owned Honda/Acura EPS helper") - parser.add_argument("--action", choices=("release", "restore", "identify", "flash"), default="flash") - parser.add_argument("--rwd", help="Curated .rwd path or path relative to eps/") - parser.add_argument("--bus", default="auto", help="auto, 0, or 1") - parser.add_argument("--seed-timeout", type=float, default=600.0, help="Security-access seed wait in seconds") - parser.add_argument("--release-timeout", type=float, default=20.0, help="Seconds to wait for pandad to exit") - parser.add_argument("--release-delay", type=float, default=2.0, help="Extra USB release settling time") - parser.add_argument("--restore-timeout", type=float, default=20.0, help="Seconds to wait for pandad to restart") - args = parser.parse_args() - if args.action == "flash" and not args.rwd: - parser.error("--rwd is required for --action flash") - - STATE_DIR.mkdir(parents=True, exist_ok=True) - lock_file = LOCK_PATH.open("a+") - try: - fcntl.flock(lock_file.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB) - except BlockingIOError: - _log("Another EPS UI flash runner is already active") - return 2 - - try: - return run(args) - finally: - fcntl.flock(lock_file.fileno(), fcntl.LOCK_UN) - lock_file.close() - - -if __name__ == "__main__": - raise SystemExit(main()) \ No newline at end of file diff --git a/openpilot/system/manager/manager.py b/openpilot/system/manager/manager.py index 80cc0d5e3..7befb7f4a 100755 --- a/openpilot/system/manager/manager.py +++ b/openpilot/system/manager/manager.py @@ -15,9 +15,6 @@ from openpilot.common.text_window import TextWindow from openpilot.common.hardware import HARDWARE, PC from openpilot.system.manager.helpers import unblock_stdout, save_bootlog from openpilot.system.manager.process import ensure_running - - -PANDAD_BLOCK_FILE = "/data/eps_flash/block_pandad" from openpilot.system.manager.process_config import managed_processes from openpilot.system.athena.registration import register, UNREGISTERED_DONGLE_ID from openpilot.common.swaglog import cloudlog, add_file_handler @@ -133,21 +130,11 @@ def manager_thread() -> None: ignore.append("pandad") ignore += [x for x in os.getenv("BLOCK", "").split(",") if len(x) > 0] - # A stale lock must never survive a manager restart. The EPS UI creates this - # file only while it owns the Panda USB interface, then removes it when done. - try: - os.unlink(PANDAD_BLOCK_FILE) - except FileNotFoundError: - pass - - def get_not_run() -> list[str]: - return [*ignore, *(["pandad"] if os.path.exists(PANDAD_BLOCK_FILE) else [])] - sm = messaging.SubMaster(['deviceState', 'carParams', 'pandaStates'], poll='deviceState') pm = messaging.PubMaster(['managerState']) params.put_bool("IsOffroad", True, block=True) - ensure_running(managed_processes.values(), False, params=params, CP=sm['carParams'], not_run=get_not_run()) + ensure_running(managed_processes.values(), False, params=params, CP=sm['carParams'], not_run=ignore) started_prev = False ignition_prev = False @@ -173,7 +160,7 @@ def manager_thread() -> None: started_prev = started ignition_prev = ignition - ensure_running(managed_processes.values(), started, params=params, CP=sm['carParams'], not_run=get_not_run()) + ensure_running(managed_processes.values(), started, params=params, CP=sm['carParams'], not_run=ignore) running = ' '.join("{}{}\u001b[0m".format("\u001b[32m" if p.proc.is_alive() else "\u001b[31m", p.name) for p in managed_processes.values() if p.proc)