mpflash 0.7.7__py3-none-any.whl → 0.8.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,98 @@
1
+ import shutil
2
+ from pathlib import Path
3
+ from typing import Union
4
+
5
+ import jsonlines
6
+ import requests
7
+ from loguru import logger as log
8
+
9
+ # re-use logic from mpremote
10
+ from mpremote.mip import _rewrite_url as rewrite_url # type: ignore
11
+
12
+ from mpflash.common import FWInfo
13
+ from mpflash.config import config
14
+ from mpflash.vendor.versions import get_preview_mp_version, get_stable_mp_version
15
+
16
+
17
+ def add_firmware(
18
+ source: Union[Path, str],
19
+ new_fw: FWInfo,
20
+ *,
21
+ force: bool = False,
22
+ custom: bool = False,
23
+ description: str = "",
24
+ ) -> bool:
25
+ """Add a firmware to the firmware folder.
26
+
27
+ stored in the port folder, with the same filename as the source.
28
+
29
+ """
30
+ # Check minimal info needed
31
+ if not new_fw.port or not new_fw.board:
32
+ log.error("Port and board are required")
33
+ return False
34
+ if not isinstance(source, Path) and not source.startswith("http"):
35
+ log.error(f"Invalid source {source}")
36
+ return False
37
+
38
+ # use sensible defaults
39
+ source_2 = Path(source)
40
+ new_fw.ext = new_fw.ext or source_2.suffix
41
+ new_fw.variant = new_fw.variant or new_fw.board
42
+ new_fw.custom = new_fw.custom or custom
43
+ new_fw.description = new_fw.description or description
44
+ if not new_fw.version:
45
+ # TODO: Get version from filename
46
+ # or use the last preview version
47
+ new_fw.version = get_preview_mp_version() if new_fw.preview else get_stable_mp_version()
48
+
49
+ config.firmware_folder.mkdir(exist_ok=True)
50
+
51
+ fw_filename = config.firmware_folder / new_fw.port / source_2.name
52
+
53
+ new_fw.filename = str(fw_filename.relative_to(config.firmware_folder))
54
+ new_fw.firmware = source.as_uri() if isinstance(source, Path) else source
55
+
56
+ if not copy_firmware(source, fw_filename, force):
57
+ log.error(f"Failed to copy {source} to {fw_filename}")
58
+ return False
59
+ # add to inventory
60
+ with jsonlines.open(config.firmware_folder / "firmware.jsonl", "a") as writer:
61
+ log.info(f"Adding {new_fw.port} {new_fw.board}")
62
+ log.info(f" to {fw_filename}")
63
+
64
+ writer.write(new_fw.to_dict())
65
+ return True
66
+
67
+
68
+ def copy_firmware(source: Union[Path, str], fw_filename: Path, force: bool = False):
69
+ """Add a firmware to the firmware folder.
70
+ stored in the port folder, with the same filename as the source.
71
+ """
72
+ if fw_filename.exists() and not force:
73
+ log.error(f" {fw_filename} already exists. Use --force to overwrite")
74
+ return False
75
+ fw_filename.parent.mkdir(exist_ok=True)
76
+ if isinstance(source, Path):
77
+ if not source.exists():
78
+ log.error(f"File {source} does not exist")
79
+ return False
80
+ # file copy
81
+ log.debug(f"Copy {source} to {fw_filename}")
82
+ shutil.copy(source, fw_filename)
83
+ return True
84
+ # handle github urls
85
+ url = rewrite_url(source)
86
+ if str(source).startswith("http://") or str(source).startswith("https://"):
87
+ log.debug(f"Download {url} to {fw_filename}")
88
+ response = requests.get(url)
89
+
90
+ if response.status_code == 200:
91
+ with open(fw_filename, "wb") as file:
92
+ file.write(response.content)
93
+ log.info("File downloaded and saved successfully.")
94
+ return True
95
+ else:
96
+ print("Failed to download the file.")
97
+ return False
98
+ return False
mpflash/ask_input.py CHANGED
@@ -5,58 +5,33 @@ Note: The prompts can use "{version}" and "{action}" to insert the version and a
5
5
  The values are provided from the answers dictionary.
6
6
  """
7
7
 
8
- from dataclasses import dataclass, field
9
- from pathlib import Path
10
- from typing import Dict, List, Sequence, Tuple, Union
8
+ from typing import List, Sequence, Tuple, Union
11
9
 
12
10
  from loguru import logger as log
13
11
 
14
- from mpflash.config import config
15
- from mpflash.mpboard_id import get_known_boards_for_port, get_known_ports, known_stored_boards
16
- from mpflash.mpremoteboard import MPRemoteBoard
17
- from mpflash.vendor.versions import micropython_versions
18
-
19
-
20
- @dataclass
21
- class Params:
22
- ports: List[str] = field(default_factory=list)
23
- boards: List[str] = field(default_factory=list)
24
- versions: List[str] = field(default_factory=list)
25
- fw_folder: Path = Path()
26
-
27
-
28
- @dataclass
29
- class DownloadParams(Params):
30
- clean: bool = False
31
- force: bool = False
32
-
33
-
34
- @dataclass
35
- class FlashParams(Params):
36
- # TODO: Should Serial port be a list?
37
- serial: str = ""
38
- erase: bool = True
39
- bootloader: bool = True
40
- cpu: str = ""
41
-
42
-
43
- ParamType = Union[DownloadParams, FlashParams]
12
+ from .common import DownloadParams, FlashParams, ParamType
13
+ from .config import config
14
+ from .mpboard_id import get_known_boards_for_port, get_known_ports, known_stored_boards
15
+ from .mpremoteboard import MPRemoteBoard
16
+ from .vendor.versions import micropython_versions
44
17
 
45
18
 
46
19
  def ask_missing_params(
47
20
  params: ParamType,
48
- # action: str = "download",
49
21
  ) -> ParamType:
50
22
  """
51
23
  Asks the user for parameters that have not been supplied on the commandline and returns the updated params.
52
24
 
53
25
  Args:
54
26
  params (ParamType): The parameters to be updated.
55
- action (str, optional): The action to be performed. Defaults to "download".
56
27
 
57
28
  Returns:
58
29
  ParamType: The updated parameters.
59
30
  """
31
+ import inquirer
32
+
33
+ log.trace(f"ask_missing_params: {params}")
34
+
60
35
  # if action flash, single input
61
36
  # if action download, multiple input
62
37
  multi_select = isinstance(params, DownloadParams)
@@ -64,36 +39,36 @@ def ask_missing_params(
64
39
  if not config.interactive:
65
40
  # no interactivity allowed
66
41
  return params
67
- # import only when needed to reduce load time
68
- import inquirer
69
42
 
70
43
  questions = []
71
- answers = {"action": "download" if isinstance(params, DownloadParams) else "flash"}
44
+ answers: dict[str, Union[str, List]] = {"action": action}
72
45
  if not multi_select:
73
46
  if not params.serial or "?" in params.serial:
74
- ask_serialport(questions)
47
+ questions.append(ask_serialport(multi_select=False, bluetooth=False))
75
48
  else:
76
49
  answers["serial"] = params.serial
77
50
 
78
- if not params.versions or "?" in params.versions:
79
- ask_versions(questions, multi_select=multi_select, action=action)
51
+ if params.versions == [] or "?" in params.versions:
52
+ questions.append(ask_mp_version(multi_select=multi_select, action=action))
80
53
  else:
81
54
  # versions is used to show only the boards for the selected versions
82
55
  answers["versions"] = params.versions # type: ignore
83
56
 
84
57
  if not params.boards or "?" in params.boards:
85
- ask_port_board(questions, multi_select=multi_select, action=action)
58
+ questions.extend(ask_port_board(multi_select=multi_select, action=action))
86
59
  if questions:
87
- answers = inquirer.prompt(questions, answers=answers)
60
+ answers = inquirer.prompt(questions, answers=answers) # type: ignore
88
61
  if not answers:
89
62
  # input cancelled by user
90
63
  return [] # type: ignore
91
- # print(repr(answers))
64
+ log.trace(f"answers: {answers}")
92
65
  if isinstance(params, FlashParams) and "serial" in answers:
93
- params.serial = answers["serial"].split()[0] # split to remove the description
66
+ if isinstance(answers["serial"], str):
67
+ answers["serial"] = [answers["serial"]]
68
+ params.serial = [s.split()[0] for s in answers["serial"]] # split to remove the description
94
69
  if "port" in answers:
95
70
  params.ports = [p for p in params.ports if p != "?"] # remove the "?" if present
96
- params.ports.append(answers["port"])
71
+ params.ports.extend(answers["port"])
97
72
  if "boards" in answers:
98
73
  params.boards = [b for b in params.boards if b != "?"] # remove the "?" if present
99
74
  params.boards.extend(answers["boards"] if isinstance(answers["boards"], list) else [answers["boards"]])
@@ -108,7 +83,7 @@ def ask_missing_params(
108
83
  params.ports = list(set(params.ports))
109
84
  params.boards = list(set(params.boards))
110
85
  params.versions = list(set(params.versions))
111
- log.debug(repr(params))
86
+ log.trace(f"ask_missing_params returns: {params}")
112
87
 
113
88
  return params
114
89
 
@@ -123,19 +98,18 @@ def filter_matching_boards(answers: dict) -> Sequence[Tuple[str, str]]:
123
98
  Returns:
124
99
  Sequence[Tuple[str, str]]: The filtered boards.
125
100
  """
101
+ versions = None
126
102
  # if version is not asked ; then need to get the version from the inputs
127
103
  if "versions" in answers:
128
- _versions = list(answers["versions"])
129
- if "stable" in _versions:
130
- _versions.remove("stable")
131
- _versions.append(micropython_versions()[-2]) # latest stable
132
- if "preview" in _versions:
133
- _versions.remove("preview")
134
- _versions.extend((micropython_versions()[-1], micropython_versions()[-2])) # latest preview and stable
135
-
136
- some_boards = known_stored_boards(answers["port"], _versions) # or known_mp_boards(answers["port"])
137
- else:
138
- some_boards = known_stored_boards(answers["port"])
104
+ versions = list(answers["versions"])
105
+ if "stable" in versions:
106
+ versions.remove("stable")
107
+ versions.append(micropython_versions()[-2]) # latest stable
108
+ elif "preview" in versions:
109
+ versions.remove("preview")
110
+ versions.extend((micropython_versions()[-1], micropython_versions()[-2])) # latest preview and stable
111
+
112
+ some_boards = known_stored_boards(answers["port"], versions) # or known_mp_boards(answers["port"])
139
113
 
140
114
  if some_boards:
141
115
  # Create a dictionary where the keys are the second elements of the tuples
@@ -144,11 +118,11 @@ def filter_matching_boards(answers: dict) -> Sequence[Tuple[str, str]]:
144
118
  # Get the values of the dictionary, which are the unique items from the original list
145
119
  some_boards = list(unique_dict.values())
146
120
  else:
147
- some_boards = [(f"No {answers['port']} boards found for version(s) {_versions}", "")]
121
+ some_boards = [(f"No {answers['port']} boards found for version(s) {versions}", "")]
148
122
  return some_boards
149
123
 
150
124
 
151
- def ask_port_board(questions: list, *, multi_select: bool, action: str):
125
+ def ask_port_board(*, multi_select: bool, action: str):
152
126
  """
153
127
  Asks the user for the port and board selection.
154
128
 
@@ -165,29 +139,25 @@ def ask_port_board(questions: list, *, multi_select: bool, action: str):
165
139
  # if action flash, single input
166
140
  # if action download, multiple input
167
141
  inquirer_ux = inquirer.Checkbox if multi_select else inquirer.List
168
- questions.extend(
169
- (
170
- inquirer.List(
171
- "port",
172
- message="Which port do you want to {action} " + "to {serial} ?" if action == "flash" else "?",
173
- choices=get_known_ports(),
174
- autocomplete=True,
175
- ),
176
- inquirer_ux(
177
- "boards",
178
- message=(
179
- "Which {port} board firmware do you want to {action} " + "to {serial} ?"
180
- if action == "flash"
181
- else "?"
182
- ),
183
- choices=filter_matching_boards,
184
- validate=lambda _, x: True if x else "Please select at least one board", # type: ignore
142
+ return [
143
+ inquirer.List(
144
+ "port",
145
+ message="Which port do you want to {action} " + "to {serial} ?" if action == "flash" else "?",
146
+ choices=get_known_ports(),
147
+ # autocomplete=True,
148
+ ),
149
+ inquirer_ux(
150
+ "boards",
151
+ message=(
152
+ "Which {port} board firmware do you want to {action} " + "to {serial} ?" if action == "flash" else "?"
185
153
  ),
186
- )
187
- )
154
+ choices=filter_matching_boards,
155
+ validate=lambda _, x: True if x else "Please select at least one board", # type: ignore
156
+ ),
157
+ ]
188
158
 
189
159
 
190
- def ask_versions(questions: list, *, multi_select: bool, action: str):
160
+ def ask_mp_version(multi_select: bool, action: str):
191
161
  """
192
162
  Asks the user for the version selection.
193
163
 
@@ -196,46 +166,43 @@ def ask_versions(questions: list, *, multi_select: bool, action: str):
196
166
  action (str): The action to be performed.
197
167
 
198
168
  Returns:
199
- None
169
+
200
170
  """
201
171
  # import only when needed to reduce load time
202
172
  import inquirer
203
173
  import inquirer.errors
204
174
 
205
175
  input_ux = inquirer.Checkbox if multi_select else inquirer.List
176
+
206
177
  mp_versions: List[str] = micropython_versions()
207
- mp_versions = [v for v in mp_versions if "preview" not in v]
178
+ mp_versions.reverse() # newest first
208
179
 
209
180
  # remove the versions for which there are no known boards in the board_info.json
210
181
  # todo: this may be a little slow
211
- mp_versions = [v for v in mp_versions if get_known_boards_for_port("stm32", [v])]
212
-
213
- mp_versions.append("preview")
214
- mp_versions.reverse() # newest first
182
+ mp_versions = [v for v in mp_versions if "preview" in v or get_known_boards_for_port("stm32", [v])]
215
183
 
216
184
  def at_least_one_validation(answers, current) -> bool:
217
185
  if not current:
218
186
  raise inquirer.errors.ValidationError("", reason="Please select at least one version")
219
- if isinstance(current, list):
220
- if not any(current):
221
- raise inquirer.errors.ValidationError("", reason="Please select at least one version")
187
+ if isinstance(current, list) and not any(current):
188
+ raise inquirer.errors.ValidationError("", reason="Please select at least one version")
222
189
  return True
223
190
 
224
- questions.append(
225
- input_ux(
226
- # inquirer.List(
227
- "versions",
228
- message="Which version(s) do you want to {action} " + ("to {serial} ?" if action == "flash" else "?"),
229
- # Hints would be nice , but needs a hint for each and every option
230
- # hints=["Use space to select multiple options"],
231
- choices=mp_versions,
232
- autocomplete=True,
233
- validate=at_least_one_validation, # type: ignore
234
- )
191
+ message = "Which version(s) do you want to {action} " + ("to {serial} ?" if action == "flash" else "?")
192
+ q = input_ux(
193
+ # inquirer.List(
194
+ "versions",
195
+ message=message,
196
+ # Hints would be nice , but needs a hint for each and every option
197
+ # hints=["Use space to select multiple options"],
198
+ choices=mp_versions,
199
+ autocomplete=True,
200
+ validate=at_least_one_validation, # type: ignore
235
201
  )
202
+ return q
236
203
 
237
204
 
238
- def ask_serialport(questions: list, *, multi_select: bool = False, bluetooth: bool = False):
205
+ def ask_serialport(*, multi_select: bool = False, bluetooth: bool = False):
239
206
  """
240
207
  Asks the user for the serial port selection.
241
208
 
@@ -250,14 +217,10 @@ def ask_serialport(questions: list, *, multi_select: bool = False, bluetooth: bo
250
217
  import inquirer
251
218
 
252
219
  comports = MPRemoteBoard.connected_boards(bluetooth=bluetooth, description=True)
253
- questions.append(
254
- inquirer.List(
255
- "serial",
256
- message="Which serial port do you want to {action} ?",
257
- choices=comports,
258
- other=True,
259
- validate=lambda _, x: True if x else "Please select or enter a serial port", # type: ignore
260
- )
220
+ return inquirer.List(
221
+ "serial",
222
+ message="Which serial port do you want to {action} ?",
223
+ choices=comports,
224
+ other=True,
225
+ validate=lambda _, x: True if x else "Please select or enter a serial port", # type: ignore
261
226
  )
262
-
263
- return questions
mpflash/cli_download.py CHANGED
@@ -1,18 +1,18 @@
1
1
  """CLI to Download MicroPython firmware for specific ports, boards and versions."""
2
2
 
3
3
  from pathlib import Path
4
- from typing import List, Tuple
5
4
 
6
5
  import rich_click as click
7
6
  from loguru import logger as log
8
7
 
8
+ from mpflash.connected import connected_ports_boards
9
9
  from mpflash.errors import MPFlashError
10
10
  from mpflash.mpboard_id import find_known_board
11
11
  from mpflash.vendor.versions import clean_version
12
12
 
13
- from .ask_input import DownloadParams, ask_missing_params
13
+ from .ask_input import ask_missing_params
14
14
  from .cli_group import cli
15
- from .cli_list import list_mcus
15
+ from .common import DownloadParams
16
16
  from .config import config
17
17
  from .download import download
18
18
 
@@ -50,6 +50,28 @@ from .download import download
50
50
  help="The board(s) to download the firmware for.",
51
51
  metavar="BOARD_ID or ?",
52
52
  )
53
+ @click.option(
54
+ "--serial",
55
+ "--serial-port",
56
+ "-s",
57
+ "serial",
58
+ default=["*"],
59
+ show_default=True,
60
+ multiple=True,
61
+ help="Which serial port(s) to flash",
62
+ metavar="SERIALPORT",
63
+ )
64
+ @click.option(
65
+ "--ignore",
66
+ "-i",
67
+ is_eager=True,
68
+ help="Serial port(s) to ignore. Defaults to MPFLASH_IGNORE.",
69
+ multiple=True,
70
+ default=[],
71
+ envvar="MPFLASH_IGNORE",
72
+ show_default=True,
73
+ metavar="SERIALPORT",
74
+ )
53
75
  @click.option(
54
76
  "--clean/--no-clean",
55
77
  default=True,
@@ -67,6 +89,10 @@ def cli_download(**kwargs) -> int:
67
89
  params = DownloadParams(**kwargs)
68
90
  params.versions = list(params.versions)
69
91
  params.boards = list(params.boards)
92
+ params.serial = list(params.serial)
93
+ params.ignore = list(params.ignore)
94
+
95
+ # all_boards: List[MPRemoteBoard] = []
70
96
  if params.boards:
71
97
  if not params.ports:
72
98
  # no ports specified - resolve ports from specified boards by resolving board IDs
@@ -74,12 +100,12 @@ def cli_download(**kwargs) -> int:
74
100
  if board != "?":
75
101
  try:
76
102
  board_ = find_known_board(board)
77
- params.ports.append(board_["port"])
103
+ params.ports.append(board_.port)
78
104
  except MPFlashError as e:
79
105
  log.error(f"{e}")
80
106
  else:
81
107
  # no boards specified - detect connected ports and boards
82
- params.ports, params.boards = connected_ports_boards()
108
+ params.ports, params.boards, _ = connected_ports_boards(include=params.serial, ignore=params.ignore)
83
109
 
84
110
  params = ask_missing_params(params)
85
111
  if not params: # Cancelled by user
@@ -100,19 +126,3 @@ def cli_download(**kwargs) -> int:
100
126
  except MPFlashError as e:
101
127
  log.error(f"{e}")
102
128
  return 1
103
-
104
-
105
- def connected_ports_boards() -> Tuple[List[str], List[str]]:
106
- """
107
- Returns a tuple containing lists of unique ports and boards from the connected MCUs.
108
- Boards that are physically connected, but give no tangible response are ignored.
109
-
110
- Returns:
111
- A tuple containing two lists:
112
- - A list of unique ports where MCUs are connected.
113
- - A list of unique board names of the connected MCUs.
114
- """
115
- mpr_boards = [b for b in list_mcus() if b.connected]
116
- ports = list({b.port for b in mpr_boards})
117
- boards = list({b.board for b in mpr_boards})
118
- return ports, boards
mpflash/cli_flash.py CHANGED
@@ -1,19 +1,23 @@
1
1
  from pathlib import Path
2
+ from typing import List
2
3
 
3
4
  import rich_click as click
4
5
  from loguru import logger as log
5
6
 
7
+ # from mpflash.common import filtered_comports
6
8
  from mpflash.errors import MPFlashError
7
9
  from mpflash.mpboard_id import find_known_board
10
+ from mpflash.mpremoteboard import MPRemoteBoard
8
11
  from mpflash.vendor.versions import clean_version
9
12
 
10
- from .ask_input import FlashParams, ask_missing_params
13
+ from .ask_input import ask_missing_params
11
14
  from .cli_download import connected_ports_boards
12
15
  from .cli_group import cli
13
16
  from .cli_list import show_mcus
17
+ from .common import FlashParams
14
18
  from .config import config
15
19
  from .flash import flash_list
16
- from .worklist import MPRemoteBoard, WorkList, full_auto_worklist, manual_worklist, single_auto_worklist
20
+ from .worklist import WorkList, full_auto_worklist, manual_worklist, single_auto_worklist
17
21
 
18
22
  # #########################################################################################################
19
23
  # CLI
@@ -48,10 +52,22 @@ from .worklist import MPRemoteBoard, WorkList, full_auto_worklist, manual_workli
48
52
  "--serial-port",
49
53
  "-s",
50
54
  "serial",
51
- default="auto",
55
+ default=["*"],
56
+ multiple=True,
52
57
  show_default=True,
53
58
  help="Which serial port(s) to flash",
54
- metavar="SERIAL_PORT",
59
+ metavar="SERIALPORT",
60
+ )
61
+ @click.option(
62
+ "--ignore",
63
+ "-i",
64
+ is_eager=True,
65
+ help="Serial port(s) to ignore. Defaults to MPFLASH_IGNORE.",
66
+ multiple=True,
67
+ default=[],
68
+ envvar="MPFLASH_IGNORE",
69
+ show_default=True,
70
+ metavar="SERIALPORT",
55
71
  )
56
72
  @click.option(
57
73
  "--port",
@@ -101,36 +117,28 @@ def cli_flash_board(**kwargs) -> int:
101
117
  kwargs["boards"] = [kwargs.pop("board")]
102
118
 
103
119
  params = FlashParams(**kwargs)
120
+ params.versions = list(params.versions)
121
+ params.boards = list(params.boards)
122
+ params.serial = list(params.serial)
123
+ params.ignore = list(params.ignore)
104
124
 
105
- # make it simple for the user to flash one board
106
- # by asking for the serial port if not specified
107
- if params.boards == ["?"] and params.serial == "auto":
108
- params.serial = "?"
125
+ # make it simple for the user to flash one board by asking for the serial port if not specified
126
+ if params.boards == ["?"] and params.serial == "*":
127
+ params.serial = ["?"]
109
128
 
110
129
  # Detect connected boards if not specified,
111
130
  # and ask for input if boards cannot be detected
131
+ all_boards: List[MPRemoteBoard] = []
112
132
  if not params.boards or params.boards == []:
113
133
  # nothing specified - detect connected boards
114
- params.ports, params.boards = connected_ports_boards()
134
+ params.ports, params.boards, all_boards = connected_ports_boards(include=params.ports, ignore=params.ignore)
115
135
  if params.boards == []:
116
136
  # No MicroPython boards detected, but it could be unflashed or not in bootloader mode
117
137
  # Ask for serial port and board_id to flash
118
- params.serial = "?"
138
+ params.serial = ["?"]
119
139
  params.boards = ["?"]
120
140
  else:
121
- for board_id in params.boards:
122
- if board_id == "":
123
- params.boards.remove(board_id)
124
- continue
125
- if " " in board_id:
126
- try:
127
- info = find_known_board(board_id)
128
- if info:
129
- log.info(f"Resolved board description: {info['board']}")
130
- params.boards.remove(board_id)
131
- params.boards.append(info["board"])
132
- except Exception as e:
133
- log.warning(f"unable to resolve board description: {e}")
141
+ resolve_board_ids(params)
134
142
 
135
143
  # Ask for missing input if needed
136
144
  params = ask_missing_params(params)
@@ -143,27 +151,33 @@ def cli_flash_board(**kwargs) -> int:
143
151
  if len(params.versions) > 1:
144
152
  log.error(f"Only one version can be flashed at a time, not {params.versions}")
145
153
  raise MPFlashError("Only one version can be flashed at a time")
146
- # if len(params.boards) > 1:
147
- # log.error(f"Only one board can be flashed at a time, not {params.boards}")
148
- # raise MPFlashError("Only one board can be flashed at a time")
149
154
 
150
155
  params.versions = [clean_version(v) for v in params.versions]
151
156
  worklist: WorkList = []
152
157
  # if serial port == auto and there are one or more specified/detected boards
153
- if params.serial == "auto" and params.boards:
154
- worklist = full_auto_worklist(version=params.versions[0], fw_folder=params.fw_folder)
158
+ if params.serial == ["*"] and params.boards:
159
+ if not all_boards:
160
+ log.trace("No boards detected yet, scanning for connected boards")
161
+ _, _, all_boards = connected_ports_boards(include=params.ports, ignore=params.ignore)
162
+ worklist = full_auto_worklist(
163
+ all_boards=all_boards,
164
+ version=params.versions[0],
165
+ fw_folder=params.fw_folder,
166
+ include=params.serial,
167
+ ignore=params.ignore,
168
+ )
155
169
  elif params.versions[0] and params.boards[0] and params.serial:
156
- # A single serial port including the board / variant
170
+ # A one or more serial port including the board / variant
157
171
  worklist = manual_worklist(
158
- params.versions[0],
159
- params.fw_folder,
160
- params.serial,
161
- params.boards[0],
172
+ params.serial[0],
173
+ board_id=params.boards[0],
174
+ version=params.versions[0],
175
+ fw_folder=params.fw_folder,
162
176
  )
163
177
  else:
164
178
  # just this serial port on auto
165
179
  worklist = single_auto_worklist(
166
- serial_port=params.serial,
180
+ serial=params.serial[0],
167
181
  version=params.versions[0],
168
182
  fw_folder=params.fw_folder,
169
183
  )
@@ -180,3 +194,19 @@ def cli_flash_board(**kwargs) -> int:
180
194
  else:
181
195
  log.error("No boards were flashed")
182
196
  return 1
197
+
198
+
199
+ def resolve_board_ids(params):
200
+ """Resolve board descriptions to board_id, and remove empty strings from list of boards"""
201
+ for board_id in params.boards:
202
+ if board_id == "":
203
+ params.boards.remove(board_id)
204
+ continue
205
+ if " " in board_id:
206
+ try:
207
+ if info := find_known_board(board_id):
208
+ log.info(f"Resolved board description: {info.board_id}")
209
+ params.boards.remove(board_id)
210
+ params.boards.append(info.board_id)
211
+ except Exception as e:
212
+ log.warning(f"Unable to resolve board description: {e}")