- Add check_app_on_device(): reads 256 bytes from APP_ADDR (0x10000),
if all 0xFF (blank flash) forces full flash automatically
- Restructure flash decision flow:
1. Detect board (reboot + flash_id)
2. Check if app exists on device -> no app = full flash
3. Check partition table matches -> mismatch = full flash
4. Ask 'Erase flash before writing?' -> Y = full flash
5. Full flash: merged binary at 0x0000
6. Normal update: extract app from merged, flash at 0x10000
- Remove old confusing erase/partition prompts
- Settings (NVS/EEPROM) preserved on normal updates, never accidentally wiped
- Read device partition table via esptool read_flash before app-only flash
- Compare against expected partitions.bin from PIO build
- On mismatch, automatically upgrade to full flash (mandatory)
- Prevents bricked devices when flashing over different firmware
Default behavior now checks GitHub for the latest release and downloads
firmware if the cache is empty or outdated. Cached binaries are stored
in .firmware_cache/{board}/ with SHA-256 integrity verification.
New flags:
--release TAG Flash a specific release version
--offline Skip online check, use cached/local firmware only
Removed --download flag (downloading is now the default).
Added .firmware_cache/ and rnodethv3_firmware.bin to .gitignore.
Performance optimizations:
- Move TLSF allocator pool to PSRAM (frees ~170KB internal SRAM)
- Raise TCP_IF_MAX_CLIENTS from 4 to 8 in BOUNDARY_MODE
- Raise path_table_maxsize from 48 to 128, persist from 16 to 32
- Add -DNDEBUG to boundary build: compiles out TRACE/DEBUG macros
- Log level defaults to LOG_VERBOSE when NDEBUG defined
- Serial baud 115200 -> 921600 in BOUNDARY_MODE (reduces CPU blocking)
Previous changes included in this commit:
- Comprehensive boundary filter with transitive whitelisting (7 checks)
- destination_table erase+insert fix (std::map::insert no-overwrite bug)
- Backbone-to-backbone routing guard in next-hop forwarding
- KISS serial output disabled for boundary mode
- flash.py updates for boundary mode support
Vendor microReticulum library with boundary mode transport fixes:
- Two-whitelist system gates backbone traffic (local addresses +
mentioned addresses from local devices)
- Allow control_hashes and local destinations through boundary filter
(fixes backbone→LoRa path discovery)
- Fix get_cached_packet() to call unpack() instead of update_hash()
(fixes empty destination_hash in path responses)
- LRPROOF Identity::recall null guard
- remaining_hops HEADER_1/BROADCAST fix for final-hop delivery
- PROOF packets excluded from boundary wrapping
- Iterator invalidation fix in transport table cleanup
- is_backbone flag replaces string matching for interface identification
Firmware changes:
- Set is_backbone(true) on backbone TCP interface
- Rename default TcpInterface name to BackboneInterface
- Update comments for dual-use TcpInterface (backbone + local AP)
- Use vendored lib/microReticulum instead of PlatformIO registry
- Erase flash prompt: asks user before flashing (in addition to --erase flag)
- 1200 baud reset: opens port at 1200 baud with DTR toggle to force
ESP32-S3 into download mode when device is stuck/unresponsive
- Re-scans serial ports after reset since port name may change
- Fix merged binary detection: check partition table magic (0xAA50) at
offset 0x8000 instead of bootloader magic (0xE9) at offset 0 — both
merged and app-only binaries start with 0xE9, causing app-only
binaries to be flashed at wrong address
- Fix boot_app0.bin discovery: handle versioned PlatformIO package
directories (e.g. framework-arduinoespressif32@3.20009.0)
- Add --erase flag: full flash erase before writing (recommended for
recovery from corrupted flash)
The merged binary (1,257,328 bytes) was below the 1.5MB size threshold,
causing it to be misidentified as app-only and flashed at 0x10000 instead
of 0x0. This corrupted the flash layout and bricked the device.
Now checks for ESP32 bootloader magic byte (0xE9) at offset 0 to reliably
distinguish merged binaries from app-only binaries, regardless of size.
Bundled esptool.py runs via sys.executable, which may be a Python
that lacks pyserial (e.g. miniconda). Reorder find_esptool() to
prefer pip-installed esptool first (handles its own deps), and
only fall back to bundled/PlatformIO scripts if pyserial is
importable. Give a clear error message if neither works.
- flash.py: standalone flash utility with serial port listing, merge-bin,
GitHub Releases download, and esptool flash support
- Display.h: hide LAN row when Local TCP disabled, show local TCP port
instead of backbone port
- README.md: comprehensive documentation — Quick Start with 3 flash options,
OLED display layout, interface modes, routing customizations, path table
fix, interface name uniqueness, hardware rationale (PSRAM/flash)
- Release/boot_app0.bin: bundled for flash.py standalone use
- .gitignore: exclude merged firmware binary build artifact