cmdr-joystick/tools/copy_uf2.py

111 lines
3.2 KiB
Python
Executable File

#!/usr/bin/env python3
"""Copy a UF2 artifact to a detected RP2040 mass-storage mount."""
from __future__ import annotations
import argparse
import os
import shutil
import sys
import time
from pathlib import Path
INFO_FILE = "INFO_UF2.TXT"
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--source", type=Path, required=True, help="Path to the UF2 file to copy")
parser.add_argument("--timeout", type=float, default=10.0, help="Seconds to wait for the mount")
parser.add_argument(
"--mount",
type=str,
default=os.environ.get("MOUNT", ""),
help="Explicit mount point (default: auto-detect)",
)
return parser.parse_args()
def candidate_paths(explicit: str, user: str) -> list[Path]:
paths: list[Path] = []
if explicit:
paths.append(Path(explicit))
roots = [
Path("/Volumes"),
Path("/media"),
Path(f"/media/{user}"),
Path("/run/media"),
Path(f"/run/media/{user}"),
]
for root in roots:
if not root.exists() or not root.is_dir():
continue
for child in root.iterdir():
if child.is_dir():
paths.append(child)
return paths
def choose_mount(explicit: str, user: str) -> Path | None:
candidates = candidate_paths(explicit, user)
if explicit:
path = Path(explicit)
return path if path.exists() and path.is_dir() else None
info_candidates = [path for path in candidates if (path / INFO_FILE).exists()]
if info_candidates:
return info_candidates[0]
for path in candidates:
if path.exists() and path.is_dir():
return path
return None
def main() -> int:
args = parse_args()
source = args.source
if not source.exists():
print(f"UF2 source file not found: {source}", file=sys.stderr)
return 1
explicit_mount = args.mount.strip()
user = os.environ.get("USER", "")
deadline = time.time() + float(args.timeout)
while time.time() <= deadline:
mount = choose_mount(explicit_mount, user)
if mount is not None:
if not mount.exists() or not mount.is_dir():
time.sleep(1)
continue
destination = mount / source.name
try:
shutil.copy2(source, destination)
try:
if hasattr(os, "sync"):
os.sync()
except Exception:
pass
time.sleep(0.5)
except Exception as exc: # noqa: BLE001
print(f"Failed to copy UF2 to {destination}: {exc}", file=sys.stderr)
return 1
print(f"Copied {source} to {destination}")
return 0
time.sleep(1)
if explicit_mount:
print(
f"Mount point '{explicit_mount}' not found within {args.timeout} seconds",
file=sys.stderr,
)
else:
print(
"Unable to detect RP2040 UF2 mount. Pass one via mount=/path",
file=sys.stderr,
)
return 1
if __name__ == "__main__": # pragma: no cover
sys.exit(main())