# Pure-Python firmware flasher + `pm3flash` **Status:** ✅ Landed (hardware-validated on a PM3 Easy) · **Date:** 2026-07-05 Related: [firmware-fork DT-Gitea migration](2026-07-05-firmware-fork-dt-migration.md) ## Goal Let pm3py get the fork firmware onto a device by itself — no C `pm3-flash`/DFU/JTAG — so `pip install pm3py` + one command flashes the build this pm3py corresponds to. Supports the project "in the meantime", before tagged firmware releases exist on the DT Gitea. ## What landed - **`Flasher`** (`pm3py/core/flash.py`) over the legacy 544-byte **OLD frame** (bootloader protocol, distinct from the NG frames the rest of the client speaks; OLD-frame plumbing in `core/transport.py`). Automatic OS→bootloader handover (`CMD_START_FLASH`, no button), `CMD_FINISH_WRITE` block loop with per-block ACK/NACK, reset, reopen-and-verify. - **Safety:** fullimage-only by default; the bootrom region is refused unless `allow_bootrom=True`. A bad OS write is recoverable (proven — recovered a corrupted-OS device); a bad bootrom write bricks to JTAG. - **Firmware pin** (`pm3py/_firmware.py`): `FIRMWARE_PIN` = the fork build pm3py corresponds to (currently the submodule SHA). `pm3.firmware.matches_pin` / `.expected_firmware` on connect. - **`pm3flash` CLI** (`pm3py/flash_cli.py`): detect → resolve/build image → flash → verify. `--image` (prebuilt), `--build PLATFORM` (`make` from the submodule; platform explicit — never inferred from `is_rdv4`, which reports the running firmware, not the board), `--force`, `--allow-bootrom`. ## Lessons (found on hardware / by adversarial review) - **Merge segments sharing a flash page.** The SAM7 erase-programs whole pages; two adjacent PT_LOAD segments in one page must be written once with both segments' data, else 0xFF padding clobbers real bytes (bug: the flashed OS booted but had invalid version info). - **`--build` must flash what it built,** not gate on the pin — a dirty rebuild keeps the same base SHA, so `matches_pin` can't distinguish it (`--build` therefore implies `--force`). - **No CWD-relative firmware discovery** — running `make` on a stray `./firmware` is arbitrary code execution; only an explicit arg / `$PM3PY_FIRMWARE_SRC` / the package repo root. ## Open follow-ups - **Auto-download** of the pinned release asset (toolchain-free install) — needs a tagged firmware release to exist; rides on the DT-Gitea migration. Drops into `resolve_image`. - **Read-back verify** via `CMD_READ_MEM_DOWNLOAD` (v1 trusts per-block ACK/NACK, like the C flasher). ## Provenance Built + hardware-validated in one session; the `--build` layer was adversarially reviewed by a multi-agent workflow (correctness / subprocess-safety / CLI-UX lenses with a verify pass), which surfaced the pin-skip, make-missing, and CWD-Makefile issues fixed above.