micropython-stubber 1.20.0__py3-none-any.whl → 1.20.2__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.
Files changed (60) hide show
  1. {micropython_stubber-1.20.0.dist-info → micropython_stubber-1.20.2.dist-info}/METADATA +6 -6
  2. {micropython_stubber-1.20.0.dist-info → micropython_stubber-1.20.2.dist-info}/RECORD +58 -51
  3. mpflash/README.md +54 -35
  4. mpflash/libusb_flash.ipynb +203 -203
  5. mpflash/mpflash/add_firmware.py +98 -0
  6. mpflash/mpflash/ask_input.py +106 -114
  7. mpflash/mpflash/cli_download.py +58 -37
  8. mpflash/mpflash/cli_flash.py +77 -35
  9. mpflash/mpflash/cli_group.py +14 -12
  10. mpflash/mpflash/cli_list.py +40 -4
  11. mpflash/mpflash/cli_main.py +20 -8
  12. mpflash/mpflash/common.py +125 -12
  13. mpflash/mpflash/config.py +2 -0
  14. mpflash/mpflash/connected.py +74 -0
  15. mpflash/mpflash/download.py +67 -50
  16. mpflash/mpflash/downloaded.py +9 -9
  17. mpflash/mpflash/flash.py +2 -2
  18. mpflash/mpflash/flash_esp.py +2 -2
  19. mpflash/mpflash/flash_uf2.py +16 -8
  20. mpflash/mpflash/flash_uf2_linux.py +5 -16
  21. mpflash/mpflash/flash_uf2_macos.py +78 -0
  22. mpflash/mpflash/flash_uf2_windows.py +1 -1
  23. mpflash/mpflash/list.py +58 -57
  24. mpflash/mpflash/mpboard_id/__init__.py +37 -44
  25. mpflash/mpflash/mpboard_id/add_boards.py +255 -0
  26. mpflash/mpflash/mpboard_id/board.py +37 -0
  27. mpflash/mpflash/mpboard_id/board_id.py +50 -43
  28. mpflash/mpflash/mpboard_id/board_info.zip +0 -0
  29. mpflash/mpflash/mpboard_id/store.py +42 -0
  30. mpflash/mpflash/mpremoteboard/__init__.py +18 -6
  31. mpflash/mpflash/mpremoteboard/runner.py +12 -12
  32. mpflash/mpflash/uf2disk.py +12 -0
  33. mpflash/mpflash/vendor/basicgit.py +288 -0
  34. mpflash/mpflash/vendor/dfu.py +1 -0
  35. mpflash/mpflash/vendor/versions.py +7 -3
  36. mpflash/mpflash/worklist.py +71 -48
  37. mpflash/poetry.lock +163 -137
  38. mpflash/pyproject.toml +18 -15
  39. stubber/__init__.py +1 -1
  40. stubber/board/createstubs.py +4 -3
  41. stubber/board/createstubs_db.py +5 -7
  42. stubber/board/createstubs_db_min.py +1 -1
  43. stubber/board/createstubs_db_mpy.mpy +0 -0
  44. stubber/board/createstubs_mem.py +6 -7
  45. stubber/board/createstubs_mem_min.py +1 -1
  46. stubber/board/createstubs_mem_mpy.mpy +0 -0
  47. stubber/board/createstubs_min.py +2 -2
  48. stubber/board/createstubs_mpy.mpy +0 -0
  49. stubber/board/modulelist.txt +1 -0
  50. stubber/commands/get_core_cmd.py +7 -6
  51. stubber/commands/get_docstubs_cmd.py +8 -3
  52. stubber/commands/get_frozen_cmd.py +5 -2
  53. stubber/publish/publish.py +18 -7
  54. stubber/utils/makeversionhdr.py +3 -2
  55. stubber/utils/versions.py +2 -1
  56. mpflash/mpflash/mpboard_id/board_info.csv +0 -2213
  57. mpflash/mpflash/mpboard_id/board_info.json +0 -19910
  58. {micropython_stubber-1.20.0.dist-info → micropython_stubber-1.20.2.dist-info}/LICENSE +0 -0
  59. {micropython_stubber-1.20.0.dist-info → micropython_stubber-1.20.2.dist-info}/WHEEL +0 -0
  60. {micropython_stubber-1.20.0.dist-info → micropython_stubber-1.20.2.dist-info}/entry_points.txt +0 -0
@@ -5,97 +5,85 @@ 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 known_stored_boards, local_mp_ports
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
+
35
+ # if action flash, single input
36
+ # if action download, multiple input
37
+ multi_select = isinstance(params, DownloadParams)
38
+ action = "download" if isinstance(params, DownloadParams) else "flash"
60
39
  if not config.interactive:
61
40
  # no interactivity allowed
62
41
  return params
63
- # import only when needed to reduce load time
64
- import inquirer
65
42
 
66
43
  questions = []
67
- answers = {"action": action}
68
- if isinstance(params, FlashParams):
44
+ answers: dict[str, Union[str, List]] = {"action": action}
45
+ if not multi_select:
69
46
  if not params.serial or "?" in params.serial:
70
- ask_serialport(questions, action=action)
47
+ questions.append(ask_serialport(multi_select=False, bluetooth=False))
71
48
  else:
72
49
  answers["serial"] = params.serial
73
50
 
74
- if not params.versions or "?" in params.versions:
75
- ask_versions(questions, action=action)
51
+ if params.versions == [] or "?" in params.versions:
52
+ questions.append(ask_mp_version(multi_select=multi_select, action=action))
76
53
  else:
77
54
  # versions is used to show only the boards for the selected versions
78
55
  answers["versions"] = params.versions # type: ignore
79
56
 
80
57
  if not params.boards or "?" in params.boards:
81
- ask_port_board(questions, action=action)
82
-
83
- answers = inquirer.prompt(questions, answers=answers)
58
+ questions.extend(ask_port_board(multi_select=multi_select, action=action))
59
+ if questions:
60
+ answers = inquirer.prompt(questions, answers=answers) # type: ignore
84
61
  if not answers:
85
62
  # input cancelled by user
86
63
  return [] # type: ignore
87
- # print(repr(answers))
64
+ log.trace(f"answers: {answers}")
88
65
  if isinstance(params, FlashParams) and "serial" in answers:
89
- params.serial = answers["serial"]
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
90
69
  if "port" in answers:
91
- params.ports = [answers["port"]]
70
+ params.ports = [p for p in params.ports if p != "?"] # remove the "?" if present
71
+ params.ports.extend(answers["port"])
92
72
  if "boards" in answers:
93
- params.boards = answers["boards"] if isinstance(answers["boards"], list) else [answers["boards"]]
73
+ params.boards = [b for b in params.boards if b != "?"] # remove the "?" if present
74
+ params.boards.extend(answers["boards"] if isinstance(answers["boards"], list) else [answers["boards"]])
94
75
  if "versions" in answers:
76
+ params.versions = [v for v in params.versions if v != "?"] # remove the "?" if present
95
77
  # make sure it is a list
96
- params.versions = answers["versions"] if isinstance(answers["versions"], list) else [answers["versions"]]
97
-
98
- log.debug(repr(params))
78
+ if isinstance(answers["versions"], (list, tuple)):
79
+ params.versions.extend(answers["versions"])
80
+ else:
81
+ params.versions.append(answers["versions"])
82
+ # remove duplicates
83
+ params.ports = list(set(params.ports))
84
+ params.boards = list(set(params.boards))
85
+ params.versions = list(set(params.versions))
86
+ log.trace(f"ask_missing_params returns: {params}")
99
87
 
100
88
  return params
101
89
 
@@ -110,19 +98,18 @@ def filter_matching_boards(answers: dict) -> Sequence[Tuple[str, str]]:
110
98
  Returns:
111
99
  Sequence[Tuple[str, str]]: The filtered boards.
112
100
  """
101
+ versions = None
113
102
  # if version is not asked ; then need to get the version from the inputs
114
103
  if "versions" in answers:
115
- _versions = list(answers["versions"])
116
- if "stable" in _versions:
117
- _versions.remove("stable")
118
- _versions.append(micropython_versions()[-2]) # latest stable
119
- if "preview" in _versions:
120
- _versions.remove("preview")
121
- _versions.extend((micropython_versions()[-1], micropython_versions()[-2])) # latest preview and stable
122
-
123
- some_boards = known_stored_boards(answers["port"], _versions) # or known_mp_boards(answers["port"])
124
- else:
125
- 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"])
126
113
 
127
114
  if some_boards:
128
115
  # Create a dictionary where the keys are the second elements of the tuples
@@ -131,11 +118,11 @@ def filter_matching_boards(answers: dict) -> Sequence[Tuple[str, str]]:
131
118
  # Get the values of the dictionary, which are the unique items from the original list
132
119
  some_boards = list(unique_dict.values())
133
120
  else:
134
- some_boards = [("No boards found", "")]
121
+ some_boards = [(f"No {answers['port']} boards found for version(s) {versions}", "")]
135
122
  return some_boards
136
123
 
137
124
 
138
- def ask_port_board(questions: list, *, action: str):
125
+ def ask_port_board(*, multi_select: bool, action: str):
139
126
  """
140
127
  Asks the user for the port and board selection.
141
128
 
@@ -149,31 +136,28 @@ def ask_port_board(questions: list, *, action: str):
149
136
  # import only when needed to reduce load time
150
137
  import inquirer
151
138
 
152
- # TODO: if action = flash, Use Inquirer.List for boards
153
- inquirer_ux = inquirer.Checkbox if action == "download" else inquirer.List
154
- questions.extend(
155
- (
156
- inquirer.List(
157
- "port",
158
- message="Which port do you want to {action} " + "to {serial} ?" if action == "flash" else "?",
159
- choices=local_mp_ports(),
160
- autocomplete=True,
161
- ),
162
- inquirer_ux(
163
- "boards",
164
- message=(
165
- "Which {port} board firmware do you want to {action} " + "to {serial} ?"
166
- if action == "flash"
167
- else "?"
168
- ),
169
- choices=filter_matching_boards,
170
- validate=lambda _, x: True if x else "Please select at least one board", # type: ignore
139
+ # if action flash, single input
140
+ # if action download, multiple input
141
+ inquirer_ux = inquirer.Checkbox if multi_select else inquirer.List
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 "?"
171
153
  ),
172
- )
173
- )
154
+ choices=filter_matching_boards,
155
+ validate=lambda _, x: True if x else "Please select at least one board", # type: ignore
156
+ ),
157
+ ]
174
158
 
175
159
 
176
- def ask_versions(questions: list, *, action: str):
160
+ def ask_mp_version(multi_select: bool, action: str):
177
161
  """
178
162
  Asks the user for the version selection.
179
163
 
@@ -182,31 +166,43 @@ def ask_versions(questions: list, *, action: str):
182
166
  action (str): The action to be performed.
183
167
 
184
168
  Returns:
185
- None
169
+
186
170
  """
187
171
  # import only when needed to reduce load time
188
172
  import inquirer
173
+ import inquirer.errors
174
+
175
+ input_ux = inquirer.Checkbox if multi_select else inquirer.List
189
176
 
190
- input_ux = inquirer.Checkbox if action == "download" else inquirer.List
191
177
  mp_versions: List[str] = micropython_versions()
192
- mp_versions = [v for v in mp_versions if "preview" not in v]
193
- mp_versions.append("preview")
194
178
  mp_versions.reverse() # newest first
195
- questions.append(
196
- input_ux(
197
- # inquirer.List(
198
- "versions",
199
- message="Which version(s) do you want to {action} " + ("to {serial} ?" if action == "flash" else "?"),
200
- # Hints would be nice , but needs a hint for each and every option
201
- # hints=["Use space to select multiple options"],
202
- choices=mp_versions,
203
- autocomplete=True,
204
- validate=lambda _, x: True if x else "Please select at least one version", # type: ignore
205
- )
179
+
180
+ # remove the versions for which there are no known boards in the board_info.json
181
+ # todo: this may be a little slow
182
+ mp_versions = [v for v in mp_versions if "preview" in v or get_known_boards_for_port("stm32", [v])]
183
+
184
+ def at_least_one_validation(answers, current) -> bool:
185
+ if not current:
186
+ 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")
189
+ return True
190
+
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
206
201
  )
202
+ return q
207
203
 
208
204
 
209
- def ask_serialport(questions: list, *, action: str):
205
+ def ask_serialport(*, multi_select: bool = False, bluetooth: bool = False):
210
206
  """
211
207
  Asks the user for the serial port selection.
212
208
 
@@ -220,15 +216,11 @@ def ask_serialport(questions: list, *, action: str):
220
216
  # import only when needed to reduce load time
221
217
  import inquirer
222
218
 
223
- serialports = MPRemoteBoard.connected_boards()
224
- questions.append(
225
- inquirer.List(
226
- "serial",
227
- message="Which serial port do you want to {action} ?",
228
- choices=serialports,
229
- other=True,
230
- validate=lambda _, x: True if x else "Please select or enter a serial port", # type: ignore
231
- )
219
+ comports = MPRemoteBoard.connected_boards(bluetooth=bluetooth, description=True)
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
232
226
  )
233
-
234
- return questions
@@ -1,17 +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
 
9
- from mpflash.mpboard_id import find_stored_board
8
+ from mpflash.connected import connected_ports_boards
9
+ from mpflash.errors import MPFlashError
10
+ from mpflash.mpboard_id import find_known_board
10
11
  from mpflash.vendor.versions import clean_version
11
12
 
12
- from .ask_input import DownloadParams, ask_missing_params
13
+ from .ask_input import ask_missing_params
13
14
  from .cli_group import cli
14
- from .cli_list import list_mcus
15
+ from .common import DownloadParams
15
16
  from .config import config
16
17
  from .download import download
17
18
 
@@ -49,6 +50,28 @@ from .download import download
49
50
  help="The board(s) to download the firmware for.",
50
51
  metavar="BOARD_ID or ?",
51
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
+ )
52
75
  @click.option(
53
76
  "--clean/--no-clean",
54
77
  default=True,
@@ -62,46 +85,44 @@ from .download import download
62
85
  show_default=True,
63
86
  help="""Force download of firmware even if it already exists.""",
64
87
  )
65
- def cli_download(
66
- **kwargs,
67
- ):
88
+ def cli_download(**kwargs) -> int:
68
89
  params = DownloadParams(**kwargs)
69
90
  params.versions = list(params.versions)
70
91
  params.boards = list(params.boards)
92
+ params.serial = list(params.serial)
93
+ params.ignore = list(params.ignore)
94
+
95
+ # all_boards: List[MPRemoteBoard] = []
71
96
  if params.boards:
72
- pass
73
- # TODO Clean board - same as in cli_flash.py
97
+ if not params.ports:
98
+ # no ports specified - resolve ports from specified boards by resolving board IDs
99
+ for board in params.boards:
100
+ if board != "?":
101
+ try:
102
+ board_ = find_known_board(board)
103
+ params.ports.append(board_.port)
104
+ except MPFlashError as e:
105
+ log.error(f"{e}")
74
106
  else:
75
- # no boards specified - detect connected boards
76
- params.ports, params.boards = connected_ports_boards()
107
+ # no boards specified - detect connected ports and boards
108
+ params.ports, params.boards, _ = connected_ports_boards(include=params.serial, ignore=params.ignore)
77
109
 
78
- params = ask_missing_params(params, action="download")
110
+ params = ask_missing_params(params)
79
111
  if not params: # Cancelled by user
80
- exit(1)
112
+ return 2
81
113
  params.versions = [clean_version(v, drop_v=True) for v in params.versions]
82
114
  assert isinstance(params, DownloadParams)
83
115
 
84
- download(
85
- params.fw_folder,
86
- params.ports,
87
- params.boards,
88
- params.versions,
89
- params.force,
90
- params.clean,
91
- )
92
-
93
-
94
- def connected_ports_boards() -> Tuple[List[str], List[str]]:
95
- """
96
- Returns a tuple containing lists of unique ports and boards from the connected MCUs.
97
- Boards that are physically connected, but give no tangible response are ignored.
98
-
99
- Returns:
100
- A tuple containing two lists:
101
- - A list of unique ports where MCUs are connected.
102
- - A list of unique board names of the connected MCUs.
103
- """
104
- mpr_boards = [b for b in list_mcus() if b.connected]
105
- ports = list({b.port for b in mpr_boards})
106
- boards = list({b.board for b in mpr_boards})
107
- return ports, boards
116
+ try:
117
+ download(
118
+ params.fw_folder,
119
+ params.ports,
120
+ params.boards,
121
+ params.versions,
122
+ params.force,
123
+ params.clean,
124
+ )
125
+ return 0
126
+ except MPFlashError as e:
127
+ log.error(f"{e}")
128
+ return 1
@@ -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
- from mpflash.mpboard_id import find_stored_board
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
@@ -28,7 +32,7 @@ from .worklist import MPRemoteBoard, WorkList, full_auto_worklist, manual_workli
28
32
  "--firmware",
29
33
  "-f",
30
34
  "fw_folder",
31
- type=click.Path(exists=True, file_okay=False, dir_okay=True, path_type=Path),
35
+ type=click.Path(file_okay=False, dir_okay=True, path_type=Path),
32
36
  default=config.firmware_folder,
33
37
  show_default=True,
34
38
  help="The folder to retrieve the firmware from.",
@@ -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",
@@ -91,7 +107,7 @@ from .worklist import MPRemoteBoard, WorkList, full_auto_worklist, manual_workli
91
107
  show_default=True,
92
108
  help="""Enter micropython bootloader mode before flashing.""",
93
109
  )
94
- def cli_flash_board(**kwargs):
110
+ def cli_flash_board(**kwargs) -> int:
95
111
  # version to versions, board to boards
96
112
  kwargs["versions"] = [kwargs.pop("version")] if kwargs["version"] != None else []
97
113
  if kwargs["board"] is None:
@@ -101,33 +117,33 @@ def cli_flash_board(**kwargs):
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)
124
+
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 = ["?"]
128
+
129
+ # Detect connected boards if not specified,
130
+ # and ask for input if boards cannot be detected
131
+ all_boards: List[MPRemoteBoard] = []
104
132
  if not params.boards or params.boards == []:
105
133
  # nothing specified - detect connected boards
106
- params.ports, params.boards = connected_ports_boards()
134
+ params.ports, params.boards, all_boards = connected_ports_boards(include=params.ports, ignore=params.ignore)
107
135
  if params.boards == []:
108
136
  # No MicroPython boards detected, but it could be unflashed or not in bootloader mode
109
137
  # Ask for serial port and board_id to flash
110
- params.serial = "?"
138
+ params.serial = ["?"]
111
139
  params.boards = ["?"]
112
140
  else:
113
- for board_id in params.boards:
114
- if board_id == "":
115
- params.boards.remove(board_id)
116
- continue
117
- if " " in board_id:
118
- try:
119
- info = find_stored_board(board_id)
120
- if info:
121
- log.info(f"Resolved board description: {info['board']}")
122
- params.boards.remove(board_id)
123
- params.boards.append(info["board"])
124
- except Exception as e:
125
- log.warning(f"unable to resolve board description: {e}")
141
+ resolve_board_ids(params)
126
142
 
127
143
  # Ask for missing input if needed
128
- params = ask_missing_params(params, action="flash")
144
+ params = ask_missing_params(params)
129
145
  if not params: # Cancelled by user
130
- exit(1)
146
+ return 2
131
147
  # TODO: Just in time Download of firmware
132
148
 
133
149
  assert isinstance(params, FlashParams)
@@ -135,27 +151,33 @@ def cli_flash_board(**kwargs):
135
151
  if len(params.versions) > 1:
136
152
  log.error(f"Only one version can be flashed at a time, not {params.versions}")
137
153
  raise MPFlashError("Only one version can be flashed at a time")
138
- # if len(params.boards) > 1:
139
- # log.error(f"Only one board can be flashed at a time, not {params.boards}")
140
- # raise MPFlashError("Only one board can be flashed at a time")
141
154
 
142
155
  params.versions = [clean_version(v) for v in params.versions]
143
156
  worklist: WorkList = []
144
157
  # if serial port == auto and there are one or more specified/detected boards
145
- if params.serial == "auto" and params.boards:
146
- 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
+ )
147
169
  elif params.versions[0] and params.boards[0] and params.serial:
148
- # A single serial port including the board / variant
170
+ # A one or more serial port including the board / variant
149
171
  worklist = manual_worklist(
150
- params.versions[0],
151
- params.fw_folder,
152
- params.serial,
153
- params.boards[0],
172
+ params.serial[0],
173
+ board_id=params.boards[0],
174
+ version=params.versions[0],
175
+ fw_folder=params.fw_folder,
154
176
  )
155
177
  else:
156
178
  # just this serial port on auto
157
179
  worklist = single_auto_worklist(
158
- serial_port=params.serial,
180
+ serial=params.serial[0],
159
181
  version=params.versions[0],
160
182
  fw_folder=params.fw_folder,
161
183
  )
@@ -168,3 +190,23 @@ def cli_flash_board(**kwargs):
168
190
  ):
169
191
  log.info(f"Flashed {len(flashed)} boards")
170
192
  show_mcus(flashed, title="Updated boards after flashing")
193
+ return 0
194
+ else:
195
+ log.error("No boards were flashed")
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}")