upgrade formatted to be flasher compatible

This commit is contained in:
liquidraver
2026-07-16 10:52:31 +02:00
parent 1a7826eff7
commit 5cb38b13dc
10 changed files with 151 additions and 23 deletions
+5
View File
@@ -46,6 +46,11 @@ rtt_*.log
*.a
*.zip
# ...except the prebuilt formatter DFU packages. These are shipped artifacts, not
# build output: build.sh publishes them as the Mesh America configurator's `erase`
# packages (see formatter/README.md, PROVIDER_CATALOG.md), so they must be tracked.
!formatter/*.zip
# OS junk
.DS_Store
Thumbs.db
+15 -13
View File
@@ -65,19 +65,21 @@ Notes on the mapping:
boards without dedicated art fall back to a neutral LoRa icon. Swap in ZephCore-branded
art by pointing `img` at your own absolute HTTPS URLs.
> [!WARNING]
> **The configurator's "Erase Flash" is not ZephCore-aware — do not use it on a ZephCore node.**
> `erase` is a MeshCore device-level field, not a provider field, so a folded nRF52 device
> inherits the *base* device's formatter (e.g. Wio Tracker L1 → `WioTrackerL1_QSPIFlash_Format`).
> That formatter targets MeshCore/Ripple's flash layout, which does not match ZephCore's
> LittleFS — so it performs a partial, inconsistent wipe (observed: nukes `channels`, leaves
> identity/prefs/contacts) and can leave the filesystem half-corrupted. New tiles inherit no
> `erase` at all, so Erase Flash is a no-op there.
>
> To factory-reset a ZephCore node, use ZephCore's own mechanism, which formats **all** of
> `/lfs` + `/ext` (and the NVS bond partition) and reboots: the `erase` command over the USB
> serial CLI, the companion app's factory reset, or simply reflash (ZephCore auto-formats on
> first boot when it detects an incompatible/blank FS).
- **Erase / "wipe settings" (nRF52) uses ZephCore's own formatter.** MeshCore's official erase
targets a different flash layout and only *partially* wipes a ZephCore node (observed on a Wio
Tracker L1: nuked `channels`, left identity/prefs/contacts, LittleFS half-corrupted). Since spec
§4a we override it: each nRF52 firmware option carries an `erase` pointing at ZephCore's
formatter. The formatter is **SoftDevice-specific** (v6 and v7 have different partition maps), so
`SOFTDEVICE` in the generator maps every nRF52 board to `SoftDevice_v6_formatter.zip` or
`SoftDevice_v7_formatter.zip`; `build.sh` publishes both from `formatter/` under stable, un-hashed
names so the erase URLs never change. The `.zip` drives the automated DFU erase flow; the `.uf2`
is published too as the manual drag-and-drop fallback.
- `erase` goes on the **firmware option**, never the device object — a device-level `erase` is an
official-only field that providers get silently ignored + warned for (§4a).
- ESP32 has no `erase` (the field is nRF52-only); `flash-wipe` already reinstalls cleanly.
- Users can still factory-reset on-device without the flasher: the `erase` command over the USB
serial CLI, the app's factory reset, or a reflash (ZephCore auto-formats on first boot when it
detects an incompatible/blank FS). All format `/lfs` + `/ext` + the NVS bond partition.
## Local test
+20
View File
@@ -82,6 +82,26 @@ if [[ $1 == "nrf" ]]; then
mv build/zephyr/zephyr.zip firmware/"$board"-repeater-noscreen-"$COMMIT_HASH".zip
fi
done
# ZephCore's storage formatter — published as the `erase` package for the
# Mesh America configurator (spec §4a). MeshCore's official erase targets a
# different flash layout and only partially wipes a ZephCore node, so each
# nRF52 board points `erase` at the formatter for its SoftDevice (v6/v7 have
# different partition maps; see SOFTDEVICE in gen_provider_catalog.py).
# Copied under stable, un-hashed names so the catalog's erase URLs stay
# stable. The .zip drives the configurator's automated DFU erase flow; the
# .uf2 is the manual drag-and-drop fallback. Skipped if not built yet.
for sd in 6 7; do
for ext in zip uf2; do
f="formatter/SoftDevice_v${sd}_formatter.${ext}"
if [[ -f "$f" ]]; then
cp "$f" firmware/
echo "Published formatter: $f"
else
echo "NOTE: $f not present — erase package for SoftDevice v${sd} will 404"
fi
done
done
fi
if [[ $1 == "linux" ]]; then
+50 -7
View File
@@ -1,7 +1,50 @@
nRF formatter tools
- QSPI is formatted for all supported boards
- Watch out for softdevice version! Flashing the wrong version can corrupt the node and you'll need a full bootloader reflash with adafruit-nrfutil!
- You can check what softdevice version you use if you open INFO_UF2.TXT on the storage drive when in DFU mode. Bootloader should say "sxxx 6.x.x" for v6 and "sxxx 7.x.x" for v7
- Formatter output logs from the process over serial
- After format, it puts back the device to Mass Storage DFU mode
nRF formatter tools
- QSPI is formatted for all supported boards
- Watch out for softdevice version! Flashing the wrong version can corrupt the node and you'll need a full bootloader reflash with adafruit-nrfutil!
- You can check what softdevice version you use if you open INFO_UF2.TXT on the storage drive when in DFU mode. Bootloader should say "sxxx 6.x.x" for v6 and "sxxx 7.x.x" for v7
- That warning applies to the **`.uf2`** files, which have no version guard. The **`.zip`** packages carry an `--sd-req` guard (v6 = `0x00B6`, v7 = `0x0123`), so the bootloader *rejects* a mismatched package instead of corrupting the node.
- Formatter output logs from the process over serial
- After format, it puts back the device to Mass Storage DFU mode
## Files
| SoftDevice | Boards |
|------------|--------|
| **v6** | RAK4631, RAK3401 1W, ThinkNode M1/M3/M6, RAK WisMesh Tag, LilyGo T-Echo, LilyGo T-Impulse Plus, ProMicro SX1262, Heltec T114, Heltec T096, GAT562 30s |
| **v7** | Wio Tracker L1, T1000-E, Ikoka Nano 30dBm, SenseCAP Solar, XIAO nRF52840 |
- **`.uf2`** — manual drag-and-drop onto the UF2 mass-storage drive.
- **`.zip`** — Adafruit DFU package. Used by the Mesh America configurator as ZephCore's
`erase` package (its automated erase flow), and flashable by hand with
`adafruit-nrfutil dfu serial -pkg <zip> -p COMx -b 115200 --singlebank --touch 1200`.
See `PROVIDER_CATALOG.md` for how the catalog wires these up per board.
`build.sh nrf` copies all four files into `firmware/` under these exact names so the
catalog's `erase` URLs stay stable across releases — don't rename them.
## Rebuilding
Two universal builds, one per SoftDevice. Source is `zephcore/tools/formatter`. The
partition map comes from the build board's overlay, and `qspi_probe.c` probes QSPI
bare-metal, so one image covers every board on that SoftDevice.
```bash
# SoftDevice v6 (universal target: rak4631)
west build -b rak4631 zephcore/tools/formatter --pristine -d build_fmt6
cp build_fmt6/zephyr/zephyr.uf2 formatter/SoftDevice_v6_formatter.uf2
cp build_fmt6/zephyr/zephyr.zip formatter/SoftDevice_v6_formatter.zip
# SoftDevice v7 (universal target: t1000_e)
west build -b t1000_e zephcore/tools/formatter --pristine -d build_fmt7
cp build_fmt7/zephyr/zephyr.uf2 formatter/SoftDevice_v7_formatter.uf2
cp build_fmt7/zephyr/zephyr.zip formatter/SoftDevice_v7_formatter.zip
```
The `.zip` needs `adafruit-nrfutil` on PATH (`pip install adafruit-nrfutil`); the build
prints `Formatter DFU zip: ENABLED (sd-req=...)` when wired up, and silently skips the
zip otherwise. `--sd-req` is read automatically from the build board's `board.conf`
(`CONFIG_ZEPHCORE_SD_FWID`), so it always matches the SoftDevice being targeted.
To support a new board, add its QSPI pin mapping to `known_boards[]` in
`zephcore/tools/formatter/src/qspi_probe.c` — no DTS or Kconfig changes needed.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+25 -3
View File
@@ -175,6 +175,21 @@ BOARDS = [
DEVICE_TYPE = {"nrf": "nrf52", "esp32": "esp32", "linux": "noflash"}
# nRF52 erase package (spec §4a). ZephCore's LittleFS layout differs from MeshCore's,
# so the official erase would wipe the wrong region — we point `erase` at ZephCore's
# own formatter tool instead. The formatter is SoftDevice-specific (partition map
# differs between SD v6 and v7), so each board maps to its SD's formatter .zip.
# Files are published to firmware-dist by build.sh (stable names, no hash).
SOFTDEVICE = {
"rak4631": 6, "rak3401_1watt": 6, "thinknode_m1": 6, "thinknode_m3": 6,
"thinknode_m6": 6, "rak_wismesh_tag": 6, "lilygo_techo": 6,
"lilygo_timpulse_plus": 6, "promicro_sx1262": 6, "heltec_t114": 6,
"heltec_t096": 6, "gat562_30s": 6,
"wio_tracker_l1": 7, "t1000_e": 7, "ikoka_nano_30dbm": 7,
"sensecap_solar": 7, "xiao_nrf52840": 7,
}
FORMATTER_FILE = {6: "SoftDevice_v6_formatter.zip", 7: "SoftDevice_v7_formatter.zip"}
# Companion firmware is a single image that serves both transports (and, on
# Linux, TCP). Expose it under each applicable role, all pointing at one file.
COMPANION_ROLES = {
@@ -241,10 +256,12 @@ def build(assets, url_base, version):
for (t, name, title) in triples
]
def add_option(dev, role, subtitle, version, triples):
def add_option(dev, role, subtitle, version, triples, erase=None):
opt = {"role": role, "version": {version: {"notes": "", "files": spec_files(triples)}}}
if subtitle:
opt["subTitle"] = subtitle
if erase:
opt["erase"] = erase # spec §4a: on the firmware option, not the device
dev["firmware"].append(opt)
used_roles.add(role)
@@ -275,11 +292,16 @@ def build(assets, url_base, version):
stats["new" if board.get("new") else "fold"] += 1
dev = devices[name]
# nRF52 boards get ZephCore's own SoftDevice-specific formatter as `erase`.
erase = None
if board["kind"] == "nrf":
erase = f"{base}/{FORMATTER_FILE[SOFTDEVICE[board['stem']]]}"
if comp:
for role in COMPANION_ROLES[board["kind"]]:
add_option(dev, role, subtitle, version, comp)
add_option(dev, role, subtitle, version, comp, erase)
if repe:
add_option(dev, "repeater", subtitle, version, repe)
add_option(dev, "repeater", subtitle, version, repe, erase)
catalog = {
"description": DESCRIPTION,
+36
View File
@@ -54,7 +54,43 @@ endif()
message(STATUS "Formatter board overlay: ${FORMATTER_BOARD_OVERLAY}")
message(STATUS "Formatter board conf: ${FMT_BOARD_CONF}")
# Read the SoftDevice firmware ID from the MAIN board.conf for this board.
# The Adafruit bootloader rejects a DFU package whose --sd-req doesn't match the
# SoftDevice it's running, which is exactly the guard we want: the SDv6 formatter
# must never apply to an SDv7 node (wrong partition map = corrupted node).
# SoftDevice v6 -> 0x00B6 SoftDevice v7 -> 0x0123
file(GLOB_RECURSE MAIN_BOARD_CONF_CANDIDATES
"${CMAKE_CURRENT_SOURCE_DIR}/../../boards/*/${BOARD_BASE}/board.conf")
if(MAIN_BOARD_CONF_CANDIDATES)
list(GET MAIN_BOARD_CONF_CANDIDATES 0 MAIN_BOARD_CONF)
file(STRINGS "${MAIN_BOARD_CONF}" _sd_fwid_line REGEX "^CONFIG_ZEPHCORE_SD_FWID=")
if(_sd_fwid_line)
string(REGEX REPLACE "^CONFIG_ZEPHCORE_SD_FWID=" "" FORMATTER_SD_REQ "${_sd_fwid_line}")
endif()
endif()
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(zephcore_formatter)
target_sources(app PRIVATE src/main.c src/qspi_probe.c)
# Generate an Adafruit DFU package (.zip) next to the .uf2. The .zip is what the
# Mesh America configurator uses for its automated `erase` flow (spec §4a); the
# .uf2 remains the manual drag-and-drop fallback. See PROVIDER_CATALOG.md.
find_program(ADAFRUIT_NRFUTIL adafruit-nrfutil)
if(ADAFRUIT_NRFUTIL AND FORMATTER_SD_REQ)
add_custom_target(dfu_zip ALL
COMMAND ${ADAFRUIT_NRFUTIL} dfu genpkg
--dev-type 0x0052
--sd-req ${FORMATTER_SD_REQ}
--application ${CMAKE_BINARY_DIR}/zephyr/zephyr.hex
${CMAKE_BINARY_DIR}/zephyr/zephyr.zip
DEPENDS ${CMAKE_BINARY_DIR}/zephyr/zephyr.hex
COMMENT "Generating formatter DFU package: zephyr.zip (sd-req=${FORMATTER_SD_REQ})"
VERBATIM
)
add_dependencies(dfu_zip zephyr_final)
message(STATUS "Formatter DFU zip: ENABLED (sd-req=${FORMATTER_SD_REQ})")
else()
message(STATUS "Formatter DFU zip: DISABLED (adafruit-nrfutil or SD_FWID missing)")
endif()