flash.py: default to app-only flash, preserving settings
This commit is contained in:
67
flash.py
67
flash.py
@@ -5,12 +5,18 @@ RNodeTHV4 Flash Utility
|
|||||||
Flash the RNodeTHV4 boundary node firmware to a Heltec WiFi LoRa 32 V4.
|
Flash the RNodeTHV4 boundary node firmware to a Heltec WiFi LoRa 32 V4.
|
||||||
No PlatformIO required — just Python 3 and a USB cable.
|
No PlatformIO required — just Python 3 and a USB cable.
|
||||||
|
|
||||||
|
Default mode flashes only the app partition (0x10000), preserving
|
||||||
|
bootloader, partition table, NVS, and EEPROM settings.
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
# Flash a pre-built merged binary (from GitHub Releases or local build)
|
# Update firmware (preserves WiFi/boundary settings)
|
||||||
python flash.py
|
python flash.py
|
||||||
|
|
||||||
# Flash a specific file
|
# Full flash with merged binary (overwrites everything)
|
||||||
python flash.py --file rnodethv4_firmware.bin
|
python flash.py --full
|
||||||
|
|
||||||
|
# Flash a specific file (auto-detects merged vs app-only)
|
||||||
|
python flash.py --file firmware.bin
|
||||||
|
|
||||||
# Download latest from GitHub and flash
|
# Download latest from GitHub and flash
|
||||||
python flash.py --download
|
python flash.py --download
|
||||||
@@ -18,7 +24,7 @@ Usage:
|
|||||||
# Specify serial port manually
|
# Specify serial port manually
|
||||||
python flash.py --port /dev/ttyACM0
|
python flash.py --port /dev/ttyACM0
|
||||||
|
|
||||||
# Just build the merged binary (requires PlatformIO build output)
|
# Just build the merged binary (for GitHub Releases)
|
||||||
python flash.py --merge-only
|
python flash.py --merge-only
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -442,12 +448,13 @@ def main():
|
|||||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||||
epilog="""
|
epilog="""
|
||||||
Examples:
|
Examples:
|
||||||
python flash.py # Flash local merged binary
|
python flash.py # App-only update (preserves settings)
|
||||||
|
python flash.py --full # Full flash with merged binary
|
||||||
python flash.py --download # Download latest release and flash
|
python flash.py --download # Download latest release and flash
|
||||||
python flash.py --file firmware.bin # Flash a specific file
|
python flash.py --file firmware.bin # Flash a specific file
|
||||||
python flash.py --merge-only # Build merged binary from PlatformIO output
|
python flash.py --merge-only # Build merged binary for release
|
||||||
python flash.py --port /dev/ttyACM0 # Specify serial port
|
python flash.py --port /dev/ttyACM0 # Specify serial port
|
||||||
python flash.py --erase --download # Erase flash, then download and flash
|
python flash.py --erase --full # Erase flash, then full flash
|
||||||
""",
|
""",
|
||||||
)
|
)
|
||||||
parser.add_argument("--file", "-f", help="Path to firmware binary to flash")
|
parser.add_argument("--file", "-f", help="Path to firmware binary to flash")
|
||||||
@@ -457,10 +464,10 @@ Examples:
|
|||||||
help="Download latest firmware from GitHub Releases")
|
help="Download latest firmware from GitHub Releases")
|
||||||
parser.add_argument("--merge-only", action="store_true",
|
parser.add_argument("--merge-only", action="store_true",
|
||||||
help="Merge PlatformIO build output into single binary, don't flash")
|
help="Merge PlatformIO build output into single binary, don't flash")
|
||||||
parser.add_argument("--no-merge", action="store_true",
|
parser.add_argument("--full", action="store_true",
|
||||||
help="Skip merge step, use existing merged binary or --file")
|
help="Flash merged binary (bootloader + partitions + app) — overwrites everything")
|
||||||
parser.add_argument("--erase", action="store_true",
|
parser.add_argument("--erase", action="store_true",
|
||||||
help="Erase entire flash before writing (recommended for recovery)")
|
help="Erase entire flash before writing (implies --full)")
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
baud = args.baud
|
baud = args.baud
|
||||||
@@ -479,6 +486,10 @@ Examples:
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
print(f"Using esptool: {' '.join(esptool_cmd)}")
|
print(f"Using esptool: {' '.join(esptool_cmd)}")
|
||||||
|
|
||||||
|
# --erase implies --full (after erase, device needs bootloader + partitions)
|
||||||
|
if args.erase:
|
||||||
|
args.full = True
|
||||||
|
|
||||||
# Determine firmware file
|
# Determine firmware file
|
||||||
firmware_path = None
|
firmware_path = None
|
||||||
|
|
||||||
@@ -500,26 +511,43 @@ Examples:
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
return
|
return
|
||||||
|
|
||||||
else:
|
elif args.full:
|
||||||
# Try to find or create a merged binary
|
# Full flash: use or create merged binary
|
||||||
if os.path.isfile(MERGED_FILENAME) and not args.no_merge:
|
|
||||||
# Check if PlatformIO build is newer
|
|
||||||
if os.path.isfile(FIRMWARE_BIN):
|
if os.path.isfile(FIRMWARE_BIN):
|
||||||
|
# Build exists — (re-)merge
|
||||||
|
if os.path.isfile(MERGED_FILENAME):
|
||||||
build_time = os.path.getmtime(FIRMWARE_BIN)
|
build_time = os.path.getmtime(FIRMWARE_BIN)
|
||||||
merge_time = os.path.getmtime(MERGED_FILENAME)
|
merge_time = os.path.getmtime(MERGED_FILENAME)
|
||||||
if build_time > merge_time:
|
if build_time > merge_time:
|
||||||
print("Build output is newer than merged binary, re-merging...")
|
print("Build output is newer than merged binary, re-merging...")
|
||||||
if not merge_firmware(MERGED_FILENAME, esptool_cmd):
|
if not merge_firmware(MERGED_FILENAME, esptool_cmd):
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
firmware_path = MERGED_FILENAME
|
else:
|
||||||
elif os.path.isfile(FIRMWARE_BIN):
|
print("Creating merged binary from PlatformIO build output...")
|
||||||
# Build exists but no merged binary — create one
|
|
||||||
print("Found PlatformIO build output, creating merged binary...")
|
|
||||||
if not merge_firmware(MERGED_FILENAME, esptool_cmd):
|
if not merge_firmware(MERGED_FILENAME, esptool_cmd):
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
firmware_path = MERGED_FILENAME
|
firmware_path = MERGED_FILENAME
|
||||||
elif os.path.isfile(MERGED_FILENAME):
|
elif os.path.isfile(MERGED_FILENAME):
|
||||||
firmware_path = MERGED_FILENAME
|
firmware_path = MERGED_FILENAME
|
||||||
|
else:
|
||||||
|
print("No firmware found for full flash!")
|
||||||
|
print()
|
||||||
|
print("Options:")
|
||||||
|
print(" 1. Build with PlatformIO first: pio run -e heltec_V4_boundary")
|
||||||
|
print(" 2. Download from GitHub: python flash.py --download")
|
||||||
|
print(" 3. Specify a file: python flash.py --file <path>")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Default: app-only flash (preserves settings)
|
||||||
|
if os.path.isfile(FIRMWARE_BIN):
|
||||||
|
firmware_path = FIRMWARE_BIN
|
||||||
|
print(f"App-only update (preserves WiFi/boundary settings)")
|
||||||
|
print(f" Use --full for a complete flash, or --erase for recovery.")
|
||||||
|
elif os.path.isfile(MERGED_FILENAME):
|
||||||
|
firmware_path = MERGED_FILENAME
|
||||||
|
print(f"No build output found, using merged binary: {MERGED_FILENAME}")
|
||||||
|
print(f" Note: merged binary will overwrite bootloader + partitions.")
|
||||||
else:
|
else:
|
||||||
print("No firmware found!")
|
print("No firmware found!")
|
||||||
print()
|
print()
|
||||||
@@ -562,11 +590,12 @@ Examples:
|
|||||||
# Offer erase unless --erase was already passed
|
# Offer erase unless --erase was already passed
|
||||||
if not args.erase:
|
if not args.erase:
|
||||||
try:
|
try:
|
||||||
erase_choice = input("Erase flash before writing? (recommended for recovery) [y/N] ").strip().lower()
|
erase_choice = input("Erase flash before writing? (wipes all settings) [y/N] ").strip().lower()
|
||||||
except EOFError:
|
except EOFError:
|
||||||
erase_choice = ""
|
erase_choice = ""
|
||||||
if erase_choice == "y":
|
if erase_choice == "y":
|
||||||
args.erase = True
|
args.erase = True
|
||||||
|
# Erase needs bootloader+partitions, auto-merge if we have app-only
|
||||||
|
|
||||||
# ── Safety check: erase + app-only → auto-merge ────────────────────────
|
# ── Safety check: erase + app-only → auto-merge ────────────────────────
|
||||||
if args.erase and not is_merged_binary(firmware_path):
|
if args.erase and not is_merged_binary(firmware_path):
|
||||||
|
|||||||
Reference in New Issue
Block a user