From f2eed4c3d73fd5a914cf7cd1c0765b8573417c04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20NEDJAR?= Date: Mon, 13 Apr 2026 04:16:12 +0200 Subject: [PATCH] tooling: Add make deploy-usb for DAPLink mass-storage flashing. --- CONTRIBUTING.md | 1 + Makefile | 4 + scripts/deploy_usb.py | 172 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 177 insertions(+) create mode 100644 scripts/deploy_usb.py diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 700be303..3ef6589d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -195,6 +195,7 @@ make firmware # Clone micropython-steami (if needed), link local drivers, make firmware-update # Refresh the MicroPython clone and board-specific submodules make deploy # Flash firmware via pyOCD (default) make deploy-openocd # Flash firmware via OpenOCD (alternative) +make deploy-usb # Flash firmware via DAPLink USB mass-storage (alternative) make run SCRIPT=lib/steami_config/examples/show_config.py # Run with live output make deploy-script SCRIPT=lib/.../calibrate_magnetometer.py # Deploy as main.py for autonomous use make run-main # Re-execute the deployed main.py diff --git a/Makefile b/Makefile index 1c57082a..d98dc8f5 100644 --- a/Makefile +++ b/Makefile @@ -134,6 +134,10 @@ deploy-pyocd: $(MPY_DIR) ## Flash firmware via pyOCD (CMSIS-DAP) deploy-openocd: $(MPY_DIR) ## Flash firmware via OpenOCD $(MAKE) -C $(STM32_DIR) BOARD=$(BOARD) deploy-openocd +.PHONY: deploy-usb +deploy-usb: $(MPY_DIR) ## Flash firmware via DAPLink USB mass-storage + @$(PYTHON) scripts/deploy_usb.py $(STM32_DIR)/build-$(BOARD)/firmware.bin + .PHONY: run run: ## Run a script on the board with live output (SCRIPT=path/to/file.py) @if [ -z "$(SCRIPT)" ]; then \ diff --git a/scripts/deploy_usb.py b/scripts/deploy_usb.py new file mode 100644 index 00000000..efb94b2b --- /dev/null +++ b/scripts/deploy_usb.py @@ -0,0 +1,172 @@ +"""Deploy MicroPython firmware to a STeaMi board via DAPLink USB mass-storage. + +Detects the STeaMi volume by its label across Linux, macOS, and Windows, +copies the firmware .bin to it, and lets DAPLink auto-reset the target. + +Usage: + python scripts/deploy_usb.py path/to/firmware.bin +""" + +import os +import platform +import shutil +import subprocess +import sys + +VOLUME_LABEL = "STeaMi" + + +def find_steami_linux(): + """Find STeaMi mount point on Linux via findmnt. + + Returns the mount path, or ``None`` if the board is not mounted + or ``findmnt`` is not available. + """ + try: + result = subprocess.run( + ["findmnt", "-n", "-o", "TARGET", "-S", "LABEL=" + VOLUME_LABEL], + capture_output=True, + text=True, + check=False, + ) + except FileNotFoundError: + return None + if result.returncode == 0: + mount = result.stdout.strip().split("\n")[0] + return mount or None + return None + + +def find_steami_macos(): + """Find STeaMi mount point on macOS. + + Returns ``/Volumes/STeaMi`` if the board is mounted, or ``None``. + """ + path = "/Volumes/" + VOLUME_LABEL + if os.path.isdir(path): + return path + return None + + +def _find_steami_windows_powershell(): + """Find STeaMi drive letter via PowerShell Get-Volume (preferred).""" + ps_cmd = ( + "Get-Volume | Where-Object FileSystemLabel -eq '" + + VOLUME_LABEL + + "' | Select-Object -First 1 -ExpandProperty DriveLetter" + ) + try: + result = subprocess.run( + ["powershell", "-NoProfile", "-Command", ps_cmd], + capture_output=True, + text=True, + check=False, + ) + except FileNotFoundError: + return None + if result.returncode == 0: + letter = result.stdout.strip() + if letter: + return letter + ":\\" + return None + + +def _find_steami_windows_wmic(): + """Find STeaMi drive letter via legacy wmic (fallback for older Windows).""" + try: + result = subprocess.run( + [ + "wmic", + "logicaldisk", + "where", + "VolumeName='" + VOLUME_LABEL + "'", + "get", + "DeviceID", + "/value", + ], + capture_output=True, + text=True, + check=False, + ) + except FileNotFoundError: + return None + if result.returncode == 0: + for line in result.stdout.splitlines(): + if line.startswith("DeviceID="): + drive = line.split("=", 1)[1].strip() + if drive: + return drive + "\\" + return None + + +def find_steami_windows(): + """Find STeaMi drive letter on Windows. + + Tries PowerShell Get-Volume first (works on all modern Windows), + falls back to wmic for older systems where PowerShell is unavailable. + Returns the drive path (e.g. ``E:\\``), or ``None`` if the board is + not mounted or neither tool is available. + """ + return _find_steami_windows_powershell() or _find_steami_windows_wmic() + + +def find_steami(): + """Detect the STeaMi USB volume across platforms. + + Returns the mount path as a string (e.g. ``/media/user/STeaMi``, + ``/Volumes/STeaMi``, or ``E:\\``) when a volume with label ``STeaMi`` + is found, or ``None`` if the board is not mounted (or the detection + tool — findmnt, PowerShell, wmic — is not available on the system). + + Exits with an error on unsupported operating systems. + """ + system = platform.system() + if system == "Linux": + return find_steami_linux() + if system == "Darwin": + return find_steami_macos() + if system == "Windows": + return find_steami_windows() + print("Error: unsupported OS: " + system, file=sys.stderr) + sys.exit(1) + + +def main(): + if len(sys.argv) != 2: + print("Usage: deploy_usb.py ", file=sys.stderr) + sys.exit(1) + + firmware = sys.argv[1] + if not os.path.isfile(firmware): + print("Error: firmware binary not found: " + firmware, file=sys.stderr) + print("Run 'make firmware' first.", file=sys.stderr) + sys.exit(1) + + mount = find_steami() + if not mount or not os.path.isdir(mount): + print( + "Error: STeaMi board not found (no volume with label '" + + VOLUME_LABEL + + "').", + file=sys.stderr, + ) + print("Check that the board is connected and mounted.", file=sys.stderr) + if platform.system() == "Windows": + print( + "On Windows, this requires PowerShell (Get-Volume) or wmic.", + file=sys.stderr, + ) + sys.exit(1) + + print("Copying firmware to " + mount + "...") + shutil.copy(firmware, mount) + + # Best-effort flush on Unix (no-op on Windows) + if hasattr(os, "sync"): + os.sync() + + print("Firmware deployed via USB. Board will reset automatically.") + + +if __name__ == "__main__": + main()