#!/usr/bin/env python3 """Copy a UF2 artifact to a detected RP2040 mass-storage mount.""" from __future__ import annotations import argparse import os import sys import time from pathlib import Path import shutil 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 # Many systems mount the UF2 volume directly as a child of the root directory. 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: # For an explicit mount we only care whether it exists. path = Path(explicit) return path if path.exists() and path.is_dir() else None # Prefer candidates containing INFO_UF2.TXT, fall back to first existing directory. 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 # Give the device a moment to process the copied file before we exit. 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())