Skip to content

Commit 4d5e296

Browse files
authored
tooling: Add make deploy-usb for DAPLink mass-storage flashing. (#382)
1 parent 1f060e6 commit 4d5e296

3 files changed

Lines changed: 177 additions & 0 deletions

File tree

CONTRIBUTING.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ make firmware # Clone micropython-steami (if needed), link local drivers,
195195
make firmware-update # Refresh the MicroPython clone and board-specific submodules
196196
make deploy # Flash firmware via pyOCD (default)
197197
make deploy-openocd # Flash firmware via OpenOCD (alternative)
198+
make deploy-usb # Flash firmware via DAPLink USB mass-storage (alternative)
198199
make run SCRIPT=lib/steami_config/examples/show_config.py # Run with live output
199200
make deploy-script SCRIPT=lib/.../calibrate_magnetometer.py # Deploy as main.py for autonomous use
200201
make run-main # Re-execute the deployed main.py

Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,10 @@ deploy-pyocd: $(MPY_DIR) ## Flash firmware via pyOCD (CMSIS-DAP)
134134
deploy-openocd: $(MPY_DIR) ## Flash firmware via OpenOCD
135135
$(MAKE) -C $(STM32_DIR) BOARD=$(BOARD) deploy-openocd
136136

137+
.PHONY: deploy-usb
138+
deploy-usb: $(MPY_DIR) ## Flash firmware via DAPLink USB mass-storage
139+
@$(PYTHON) scripts/deploy_usb.py $(STM32_DIR)/build-$(BOARD)/firmware.bin
140+
137141
.PHONY: run
138142
run: ## Run a script on the board with live output (SCRIPT=path/to/file.py)
139143
@if [ -z "$(SCRIPT)" ]; then \

scripts/deploy_usb.py

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
"""Deploy MicroPython firmware to a STeaMi board via DAPLink USB mass-storage.
2+
3+
Detects the STeaMi volume by its label across Linux, macOS, and Windows,
4+
copies the firmware .bin to it, and lets DAPLink auto-reset the target.
5+
6+
Usage:
7+
python scripts/deploy_usb.py path/to/firmware.bin
8+
"""
9+
10+
import os
11+
import platform
12+
import shutil
13+
import subprocess
14+
import sys
15+
16+
VOLUME_LABEL = "STeaMi"
17+
18+
19+
def find_steami_linux():
20+
"""Find STeaMi mount point on Linux via findmnt.
21+
22+
Returns the mount path, or ``None`` if the board is not mounted
23+
or ``findmnt`` is not available.
24+
"""
25+
try:
26+
result = subprocess.run(
27+
["findmnt", "-n", "-o", "TARGET", "-S", "LABEL=" + VOLUME_LABEL],
28+
capture_output=True,
29+
text=True,
30+
check=False,
31+
)
32+
except FileNotFoundError:
33+
return None
34+
if result.returncode == 0:
35+
mount = result.stdout.strip().split("\n")[0]
36+
return mount or None
37+
return None
38+
39+
40+
def find_steami_macos():
41+
"""Find STeaMi mount point on macOS.
42+
43+
Returns ``/Volumes/STeaMi`` if the board is mounted, or ``None``.
44+
"""
45+
path = "/Volumes/" + VOLUME_LABEL
46+
if os.path.isdir(path):
47+
return path
48+
return None
49+
50+
51+
def _find_steami_windows_powershell():
52+
"""Find STeaMi drive letter via PowerShell Get-Volume (preferred)."""
53+
ps_cmd = (
54+
"Get-Volume | Where-Object FileSystemLabel -eq '"
55+
+ VOLUME_LABEL
56+
+ "' | Select-Object -First 1 -ExpandProperty DriveLetter"
57+
)
58+
try:
59+
result = subprocess.run(
60+
["powershell", "-NoProfile", "-Command", ps_cmd],
61+
capture_output=True,
62+
text=True,
63+
check=False,
64+
)
65+
except FileNotFoundError:
66+
return None
67+
if result.returncode == 0:
68+
letter = result.stdout.strip()
69+
if letter:
70+
return letter + ":\\"
71+
return None
72+
73+
74+
def _find_steami_windows_wmic():
75+
"""Find STeaMi drive letter via legacy wmic (fallback for older Windows)."""
76+
try:
77+
result = subprocess.run(
78+
[
79+
"wmic",
80+
"logicaldisk",
81+
"where",
82+
"VolumeName='" + VOLUME_LABEL + "'",
83+
"get",
84+
"DeviceID",
85+
"/value",
86+
],
87+
capture_output=True,
88+
text=True,
89+
check=False,
90+
)
91+
except FileNotFoundError:
92+
return None
93+
if result.returncode == 0:
94+
for line in result.stdout.splitlines():
95+
if line.startswith("DeviceID="):
96+
drive = line.split("=", 1)[1].strip()
97+
if drive:
98+
return drive + "\\"
99+
return None
100+
101+
102+
def find_steami_windows():
103+
"""Find STeaMi drive letter on Windows.
104+
105+
Tries PowerShell Get-Volume first (works on all modern Windows),
106+
falls back to wmic for older systems where PowerShell is unavailable.
107+
Returns the drive path (e.g. ``E:\\``), or ``None`` if the board is
108+
not mounted or neither tool is available.
109+
"""
110+
return _find_steami_windows_powershell() or _find_steami_windows_wmic()
111+
112+
113+
def find_steami():
114+
"""Detect the STeaMi USB volume across platforms.
115+
116+
Returns the mount path as a string (e.g. ``/media/user/STeaMi``,
117+
``/Volumes/STeaMi``, or ``E:\\``) when a volume with label ``STeaMi``
118+
is found, or ``None`` if the board is not mounted (or the detection
119+
tool — findmnt, PowerShell, wmic — is not available on the system).
120+
121+
Exits with an error on unsupported operating systems.
122+
"""
123+
system = platform.system()
124+
if system == "Linux":
125+
return find_steami_linux()
126+
if system == "Darwin":
127+
return find_steami_macos()
128+
if system == "Windows":
129+
return find_steami_windows()
130+
print("Error: unsupported OS: " + system, file=sys.stderr)
131+
sys.exit(1)
132+
133+
134+
def main():
135+
if len(sys.argv) != 2:
136+
print("Usage: deploy_usb.py <firmware.bin>", file=sys.stderr)
137+
sys.exit(1)
138+
139+
firmware = sys.argv[1]
140+
if not os.path.isfile(firmware):
141+
print("Error: firmware binary not found: " + firmware, file=sys.stderr)
142+
print("Run 'make firmware' first.", file=sys.stderr)
143+
sys.exit(1)
144+
145+
mount = find_steami()
146+
if not mount or not os.path.isdir(mount):
147+
print(
148+
"Error: STeaMi board not found (no volume with label '"
149+
+ VOLUME_LABEL
150+
+ "').",
151+
file=sys.stderr,
152+
)
153+
print("Check that the board is connected and mounted.", file=sys.stderr)
154+
if platform.system() == "Windows":
155+
print(
156+
"On Windows, this requires PowerShell (Get-Volume) or wmic.",
157+
file=sys.stderr,
158+
)
159+
sys.exit(1)
160+
161+
print("Copying firmware to " + mount + "...")
162+
shutil.copy(firmware, mount)
163+
164+
# Best-effort flush on Unix (no-op on Windows)
165+
if hasattr(os, "sync"):
166+
os.sync()
167+
168+
print("Firmware deployed via USB. Board will reset automatically.")
169+
170+
171+
if __name__ == "__main__":
172+
main()

0 commit comments

Comments
 (0)