mpflash 0.7.6__tar.gz → 0.8.0__tar.gz

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 (50) hide show
  1. {mpflash-0.7.6 → mpflash-0.8.0}/PKG-INFO +20 -7
  2. {mpflash-0.7.6 → mpflash-0.8.0}/README.md +16 -5
  3. mpflash-0.8.0/mpflash/add_firmware.py +98 -0
  4. {mpflash-0.7.6 → mpflash-0.8.0}/mpflash/ask_input.py +74 -111
  5. {mpflash-0.7.6 → mpflash-0.8.0}/mpflash/cli_download.py +31 -21
  6. {mpflash-0.7.6 → mpflash-0.8.0}/mpflash/cli_flash.py +64 -34
  7. {mpflash-0.7.6 → mpflash-0.8.0}/mpflash/cli_group.py +14 -12
  8. mpflash-0.8.0/mpflash/cli_list.py +77 -0
  9. {mpflash-0.7.6 → mpflash-0.8.0}/mpflash/cli_main.py +16 -9
  10. mpflash-0.8.0/mpflash/common.py +151 -0
  11. {mpflash-0.7.6 → mpflash-0.8.0}/mpflash/config.py +2 -0
  12. mpflash-0.8.0/mpflash/connected.py +74 -0
  13. {mpflash-0.7.6 → mpflash-0.8.0}/mpflash/download.py +52 -42
  14. {mpflash-0.7.6 → mpflash-0.8.0}/mpflash/downloaded.py +9 -9
  15. {mpflash-0.7.6 → mpflash-0.8.0}/mpflash/flash.py +2 -2
  16. {mpflash-0.7.6 → mpflash-0.8.0}/mpflash/flash_esp.py +1 -1
  17. {mpflash-0.7.6 → mpflash-0.8.0}/mpflash/flash_uf2.py +15 -7
  18. {mpflash-0.7.6 → mpflash-0.8.0}/mpflash/flash_uf2_linux.py +5 -16
  19. mpflash-0.8.0/mpflash/flash_uf2_macos.py +78 -0
  20. {mpflash-0.7.6 → mpflash-0.8.0}/mpflash/list.py +4 -45
  21. {mpflash-0.7.6 → mpflash-0.8.0}/mpflash/mpboard_id/__init__.py +28 -38
  22. mpflash-0.8.0/mpflash/mpboard_id/add_boards.py +255 -0
  23. mpflash-0.8.0/mpflash/mpboard_id/board.py +37 -0
  24. {mpflash-0.7.6 → mpflash-0.8.0}/mpflash/mpboard_id/board_id.py +18 -23
  25. mpflash-0.8.0/mpflash/mpboard_id/board_info.zip +0 -0
  26. mpflash-0.8.0/mpflash/mpboard_id/store.py +42 -0
  27. mpflash-0.8.0/mpflash/uf2disk.py +12 -0
  28. mpflash-0.8.0/mpflash/vendor/basicgit.py +288 -0
  29. {mpflash-0.7.6 → mpflash-0.8.0}/mpflash/vendor/dfu.py +1 -0
  30. {mpflash-0.7.6 → mpflash-0.8.0}/mpflash/vendor/versions.py +7 -3
  31. {mpflash-0.7.6 → mpflash-0.8.0}/mpflash/worklist.py +70 -47
  32. {mpflash-0.7.6 → mpflash-0.8.0}/pyproject.toml +18 -16
  33. mpflash-0.7.6/mpflash/cli_list.py +0 -41
  34. mpflash-0.7.6/mpflash/common.py +0 -38
  35. mpflash-0.7.6/mpflash/mpboard_id/board_info.csv +0 -2213
  36. mpflash-0.7.6/mpflash/mpboard_id/board_info.json +0 -19910
  37. {mpflash-0.7.6 → mpflash-0.8.0}/LICENSE +0 -0
  38. {mpflash-0.7.6 → mpflash-0.8.0}/mpflash/__init__.py +0 -0
  39. {mpflash-0.7.6 → mpflash-0.8.0}/mpflash/errors.py +0 -0
  40. {mpflash-0.7.6 → mpflash-0.8.0}/mpflash/flash_stm32.py +0 -0
  41. {mpflash-0.7.6 → mpflash-0.8.0}/mpflash/flash_stm32_cube.py +0 -0
  42. {mpflash-0.7.6 → mpflash-0.8.0}/mpflash/flash_stm32_dfu.py +0 -0
  43. {mpflash-0.7.6 → mpflash-0.8.0}/mpflash/flash_uf2_boardid.py +0 -0
  44. {mpflash-0.7.6 → mpflash-0.8.0}/mpflash/flash_uf2_windows.py +0 -0
  45. {mpflash-0.7.6 → mpflash-0.8.0}/mpflash/logger.py +0 -0
  46. {mpflash-0.7.6 → mpflash-0.8.0}/mpflash/mpremoteboard/__init__.py +0 -0
  47. {mpflash-0.7.6 → mpflash-0.8.0}/mpflash/mpremoteboard/mpy_fw_info.py +0 -0
  48. {mpflash-0.7.6 → mpflash-0.8.0}/mpflash/mpremoteboard/runner.py +0 -0
  49. {mpflash-0.7.6 → mpflash-0.8.0}/mpflash/vendor/pydfu.py +0 -0
  50. {mpflash-0.7.6 → mpflash-0.8.0}/mpflash/vendor/readme.md +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mpflash
3
- Version: 0.7.6
3
+ Version: 0.8.0
4
4
  Summary: Flash and download tool for MicroPython firmwares
5
5
  Home-page: https://github.com/Josverl/micropython-stubber/blob/main/src/mpflash/README.md
6
6
  License: MIT
@@ -19,9 +19,11 @@ Classifier: Topic :: Software Development :: Build Tools
19
19
  Requires-Dist: beautifulsoup4 (>=4.12.3,<5.0.0)
20
20
  Requires-Dist: bincopy (>=20.0.0,<21.0.0)
21
21
  Requires-Dist: blkinfo (>=0.2.0,<0.3.0)
22
+ Requires-Dist: cachetools (>=5.3.0,<6.0.0)
22
23
  Requires-Dist: esptool (>=4.7.0,<5.0.0)
23
24
  Requires-Dist: inquirer (>=3.2.4,<4.0.0)
24
25
  Requires-Dist: jsonlines (>=4.0.0,<5.0.0)
26
+ Requires-Dist: jsons (>=1.6.3,<2.0.0)
25
27
  Requires-Dist: libusb (>=1.0.27,<2.0.0) ; sys_platform == "win32"
26
28
  Requires-Dist: loguru (>=0.7.2,<0.8.0)
27
29
  Requires-Dist: mpremote (>=1.22.0,<2.0.0)
@@ -31,20 +33,32 @@ Requires-Dist: psutil (>=5.9.8,<6.0.0)
31
33
  Requires-Dist: pygithub (>=2.1.1,<3.0.0)
32
34
  Requires-Dist: pyusb (>=1.2.1,<2.0.0)
33
35
  Requires-Dist: requests (>=2.31.0,<3.0.0)
34
- Requires-Dist: rich-click (>=1.7.3,<2.0.0)
36
+ Requires-Dist: rich-click (>=1.8.1,<2.0.0)
35
37
  Requires-Dist: tenacity (==8.2.3)
36
38
  Project-URL: Repository, https://github.com/Josverl/micropython-stubber
37
39
  Description-Content-Type: text/markdown
38
40
 
39
41
  # MPFLASH
42
+ [![pypi version](https://badgen.net/pypi/v/mpflash)](https://pypi.org/project/mpflash/)
43
+ [![python versions](https://badgen.net/pypi/python/mpflash)](https://badgen.net/pypi/python/mpflash)
44
+ [![Downloads](https://static.pepy.tech/badge/mpflash)](https://pepy.tech/project/mpflash)
45
+
40
46
 
41
47
  `mpflash` is a command-line tool for working with MicroPython firmware. It provides features to help you flash and update Micropython on one or more .
42
48
 
43
- This tool was initially created to be used in a CI/CD pipeline to automate the process of downloading and flashing MicroPython firmware to multiple boards, but it has been extend with a TUI to me be used for manual downloadig, flashing and development.
49
+ This tool was initially created to be used in a CI/CD pipeline to automate the process of downloading and flashing MicroPython firmware to multiple boards, but it has been extend with a TUI to be used for manual downloadig, flashing and development.
44
50
 
45
- `mpflash` has been tested on Windows x64, Linux X64, but not (yet) macOS.
46
- Tested ports: `rp2`, `samd`, `esp32`, `esp32s3`, `esp32c3`, `esp8266` and `stm32`
51
+ `mpflash` has been tested on:
52
+ - OS: Windows x64, Linux X64, but not (yet) macOS.
53
+ - Micropython (hardware) ports:
54
+ - `rp2`, using `.uf2`, using filecopy (macos not tested yet)
55
+ - `samd`, using ` .uf2`, using filecopy (macos not tested yet)
56
+ - `esp32`, using `.bin`, using esptool,
57
+ - `esp8266`, using `.bin`, using esptool
58
+ - `stm32`, using ` .dfu`, using pydfu
47
59
 
60
+ Not yet implemented: `nrf`, `cc3200`, `mimxrt`
61
+
48
62
  ## Features
49
63
  1. List the connected boards including their firmware details, in a tabular or json format
50
64
  2. Download MicroPython firmware for versions, and matching a specified board or matches your current attached board.
@@ -72,8 +86,7 @@ On Windows this will not be an issue, but on Linux you can use udev rules to gi
72
86
  ## Detailed usage
73
87
  You can list the connected boards using the following command:
74
88
  ```bash
75
- $ mpflash list
76
- D:\MyPython\micropython-stubber> mpflash list
89
+ $> mpflash list
77
90
  Connected boards
78
91
  ┏━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━┓
79
92
  ┃ Serial ┃Family ┃Port ┃Board ┃CPU ┃Version ┃build ┃
@@ -1,12 +1,24 @@
1
1
  # MPFLASH
2
+ [![pypi version](https://badgen.net/pypi/v/mpflash)](https://pypi.org/project/mpflash/)
3
+ [![python versions](https://badgen.net/pypi/python/mpflash)](https://badgen.net/pypi/python/mpflash)
4
+ [![Downloads](https://static.pepy.tech/badge/mpflash)](https://pepy.tech/project/mpflash)
5
+
2
6
 
3
7
  `mpflash` is a command-line tool for working with MicroPython firmware. It provides features to help you flash and update Micropython on one or more .
4
8
 
5
- This tool was initially created to be used in a CI/CD pipeline to automate the process of downloading and flashing MicroPython firmware to multiple boards, but it has been extend with a TUI to me be used for manual downloadig, flashing and development.
9
+ This tool was initially created to be used in a CI/CD pipeline to automate the process of downloading and flashing MicroPython firmware to multiple boards, but it has been extend with a TUI to be used for manual downloadig, flashing and development.
6
10
 
7
- `mpflash` has been tested on Windows x64, Linux X64, but not (yet) macOS.
8
- Tested ports: `rp2`, `samd`, `esp32`, `esp32s3`, `esp32c3`, `esp8266` and `stm32`
11
+ `mpflash` has been tested on:
12
+ - OS: Windows x64, Linux X64, but not (yet) macOS.
13
+ - Micropython (hardware) ports:
14
+ - `rp2`, using `.uf2`, using filecopy (macos not tested yet)
15
+ - `samd`, using ` .uf2`, using filecopy (macos not tested yet)
16
+ - `esp32`, using `.bin`, using esptool,
17
+ - `esp8266`, using `.bin`, using esptool
18
+ - `stm32`, using ` .dfu`, using pydfu
9
19
 
20
+ Not yet implemented: `nrf`, `cc3200`, `mimxrt`
21
+
10
22
  ## Features
11
23
  1. List the connected boards including their firmware details, in a tabular or json format
12
24
  2. Download MicroPython firmware for versions, and matching a specified board or matches your current attached board.
@@ -34,8 +46,7 @@ On Windows this will not be an issue, but on Linux you can use udev rules to gi
34
46
  ## Detailed usage
35
47
  You can list the connected boards using the following command:
36
48
  ```bash
37
- $ mpflash list
38
- D:\MyPython\micropython-stubber> mpflash list
49
+ $> mpflash list
39
50
  Connected boards
40
51
  ┏━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━┓
41
52
  ┃ Serial ┃Family ┃Port ┃Board ┃CPU ┃Version ┃build ┃
@@ -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
@@ -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
@@ -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