Skip to content

Commit ae86a3c

Browse files
committed
fix firmware selection logic for backend compatibility.
Signed-off-by: Jos Verlinde <Jos_Verlinde@hotmail.com>
1 parent 9183536 commit ae86a3c

2 files changed

Lines changed: 95 additions & 3 deletions

File tree

mpflash/cli_flash.py

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,11 @@ def _create_worklist_or_fail(*create_args, **create_kwargs) -> FlashTaskList:
205205
"Try specifying both --board and --serial, or use '--serial *' to auto-detect ports."
206206
) from None
207207

208+
# Track whether the board was explicitly provided on CLI.
209+
# This prevents mixed auto-detected board/variant state from multiple
210+
# connected devices when only --serial was specified.
211+
board_specified = kwargs.get("board") is not None
212+
208213
# version to versions, board to boards
209214
kwargs["versions"] = [kwargs.pop("version")] if kwargs["version"] is not None else []
210215
if kwargs["board"] is None:
@@ -249,8 +254,8 @@ def _create_worklist_or_fail(*create_args, **create_kwargs) -> FlashTaskList:
249254
ignore=params.ignore,
250255
bluetooth=params.bluetooth,
251256
)
252-
if variants and len(variants) >= 1:
253-
params.variant = variants[0]
257+
# Do not auto-pick a variant from a set of connected boards; this can
258+
# leak a variant from another attached device (e.g. ESP32 SPIRAM).
254259
if params.boards == []:
255260
# No MicroPython boards detected, but it could be unflashed or in bootloader mode
256261
# Ask for serial port and board_id to flash
@@ -323,6 +328,26 @@ def _create_worklist_or_fail(*create_args, **create_kwargs) -> FlashTaskList:
323328
include_ports=params.serial,
324329
ignore_ports=params.ignore,
325330
)
331+
elif params.versions[0] and params.serial and not board_specified:
332+
# Serial port(s) were explicitly provided, but board_id was not.
333+
# Build tasks from per-port auto-detection to avoid cross-device board/variant mixing.
334+
# IMPORTANT: honor explicit serial targets exactly. Do not broaden with
335+
# include/ignore set logic, otherwise unrelated attached boards can leak
336+
# into this run.
337+
specified = [p for p in params.serial if p and p != "*"]
338+
ignored = set(params.ignore or [])
339+
comports = [p for p in specified if p not in ignored]
340+
if not comports:
341+
serial_filter = ", ".join(params.serial)
342+
raise click.UsageError(
343+
f"No serial ports matched: {serial_filter}. Check the port name, "
344+
"or use '--serial *' to auto-detect."
345+
)
346+
connected_comports = [MPRemoteBoard(port, update=True) for port in comports]
347+
tasks = _create_worklist_or_fail(
348+
params.versions[0],
349+
connected_comports=connected_comports,
350+
)
326351
elif params.versions[0] and params.boards and params.serial:
327352
# Manual specification of serial ports + board
328353
comports = filtered_comports(

mpflash/flash/__init__.py

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
from mpflash.common import BootloaderMethod, FlashMethod
1818
from mpflash.config import config
19+
from mpflash.downloaded import find_downloaded_firmware
1920
from mpflash.errors import MPFlashError
2021
from mpflash.logger import log
2122

@@ -52,10 +53,76 @@ def flash_tasks(
5253
**kwargs,
5354
):
5455
"""Flash every entry in ``tasks`` and return the updated boards."""
56+
57+
def _pick_backend_compatible_firmware(task, fw_info):
58+
"""Pick a firmware image matching the explicit backend's supported formats."""
59+
if fw_info is None:
60+
return None
61+
62+
requested_name = _resolve_backend_name(method)
63+
if not requested_name:
64+
return fw_info
65+
66+
backend = get_backend(requested_name)
67+
if backend is None or not backend.supported_formats:
68+
return fw_info
69+
70+
current_suffix = Path(fw_info.firmware_file).suffix.lower()
71+
if current_suffix in backend.supported_formats:
72+
return fw_info
73+
74+
# Fast path: if a same-stem file with a backend-supported extension
75+
# exists next to the selected firmware, use it directly.
76+
selected_path = config.firmware_folder / fw_info.firmware_file
77+
for suffix in backend.supported_formats:
78+
sibling = selected_path.with_suffix(suffix)
79+
if sibling.exists():
80+
rel = sibling.relative_to(config.firmware_folder).as_posix()
81+
log.info(
82+
f"Using {requested_name} compatible sibling firmware {rel} "
83+
f"instead of {fw_info.firmware_file} for {task.board.board} on {task.board.serialport}"
84+
)
85+
fw_info.firmware_file = rel
86+
return fw_info
87+
88+
board = task.board
89+
detected_board_id = f"{board.board}-{board.variant}" if board.variant else board.board
90+
board_ids = [getattr(fw_info, "board_id", ""), detected_board_id]
91+
92+
candidates = []
93+
seen_files = set()
94+
for bid in board_ids:
95+
if not bid:
96+
continue
97+
# First prefer exact port match, then broaden to any port.
98+
for cand in find_downloaded_firmware(
99+
board_id=bid,
100+
version=fw_info.version,
101+
port=board.port,
102+
custom=bool(fw_info.custom),
103+
) + find_downloaded_firmware(
104+
board_id=bid,
105+
version=fw_info.version,
106+
port="",
107+
custom=bool(fw_info.custom),
108+
):
109+
if cand.firmware_file not in seen_files:
110+
seen_files.add(cand.firmware_file)
111+
candidates.append(cand)
112+
113+
for cand in reversed(candidates):
114+
if Path(cand.firmware_file).suffix.lower() in backend.supported_formats:
115+
log.info(
116+
f"Using {requested_name} compatible firmware {cand.firmware_file} "
117+
f"instead of {fw_info.firmware_file} for {board.board} on {board.serialport}"
118+
)
119+
return cand
120+
return fw_info
121+
55122
flashed = []
56123
for task in tasks:
57124
mcu = task.board
58-
fw_info = task.firmware
125+
fw_info = _pick_backend_compatible_firmware(task, task.firmware)
59126
if not fw_info:
60127
log.error(f"Firmware not found for {mcu.board} on {mcu.serialport}, skipping")
61128
continue

0 commit comments

Comments
 (0)