Skip to content
File

Blob: scripts/flash_device.py

python64 lines
1#!/usr/bin/env python3
2"""Flash firmware and music, or either alone, then reboot. Requires a verified saved backup."""
3import argparse
4from contextlib import nullcontext
5from serial_device import device_port, device_identity
6from pathlib import Path
7import subprocess
8import sys
9import tempfile
10from project import ROOT, build_dir
11from flash_backup import require_backup
12from music_partition import MUSIC_BYTES, MUSIC_OFFSET, require_music_partition
13from flash_cache import FlashCache, flash_images, layout_digest
14 
15def main():
16 parser = argparse.ArgumentParser(description=__doc__)
17 contents = parser.add_mutually_exclusive_group()
18 contents.add_argument("--firmware-only", action="store_true", help="Keep the previously flashed music partition")
19 contents.add_argument("--music-only", action="store_true", help="Update only the music and track metadata, then reboot")
20 parser.add_argument("--full", action="store_true", help="Write the selected images in full")
21 args = parser.parse_args()
22 build = build_dir()
23 try:
24 require_backup()
25 except ValueError as error:
26 raise SystemExit(str(error)) from None
27 if not args.firmware_only:
28 music = ROOT / "artifacts/radio/music.bin"
29 header = music.read_bytes()[:64]
30 if len(header) != 64 or header[:8] != b"S3MUSIC\0" or music.stat().st_size > MUSIC_BYTES:
31 raise SystemExit("Prepare a valid music pack with make music before flashing")
32 images = []
33 if not args.music_only:
34 images.extend([(0, build / "bootloader/bootloader.bin"), (0x8000, build / "partition_table/partition-table.bin"), (0x10000, build / "s3_radio.bin")])
35 if not args.firmware_only:
36 images.append((MUSIC_OFFSET, music))
37 if any(not path.is_file() for _, path in images):
38 raise SystemExit("Build the selected firmware and prepare music before flashing")
39 port = device_port()
40 identity = device_identity(port)
41 cache = FlashCache(identity, "") if identity else None
42 with cache.locked() if cache else nullcontext():
43 if args.music_only:
44 # Read the installed table; a locally built table does not prove migration.
45 with tempfile.TemporaryDirectory(prefix="radio-partition-") as directory:
46 table = Path(directory) / "partition.bin"
47 subprocess.run([sys.executable, "-m", "esptool", "--chip", "esp32s3", "--port", port,
48 "--before", "default-reset", "--after", "hard-reset", "read-flash", "0x8000", "0x1000", str(table)], check=True)
49 installed_table = table.read_bytes()
50 try: require_music_partition(installed_table)
51 except ValueError as error: raise SystemExit(str(error)) from None
52 table = installed_table if args.music_only else (build / "partition_table/partition-table.bin").read_bytes()
53 require_music_partition(table)
54 command = [sys.executable, "-m", "esptool", "--chip", "esp32s3", "--port", port,
55 "--baud", "460800", "--before", "default-reset", "--after", "hard-reset"]
56 if cache:
57 cache.layout = layout_digest(table)
58 result = flash_images(images, command, subprocess.run, cache, args.full)
59 raise SystemExit(result)
60 
61 
62if __name__ == "__main__":
63 main()