micropython-stubber 1.20.1__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 (58) hide show
  1. {micropython_stubber-1.20.1.dist-info → micropython_stubber-1.20.2.dist-info}/METADATA +3 -3
  2. {micropython_stubber-1.20.1.dist-info → micropython_stubber-1.20.2.dist-info}/RECORD +56 -49
  3. mpflash/README.md +16 -5
  4. mpflash/mpflash/add_firmware.py +98 -0
  5. mpflash/mpflash/ask_input.py +97 -120
  6. mpflash/mpflash/cli_download.py +42 -25
  7. mpflash/mpflash/cli_flash.py +70 -32
  8. mpflash/mpflash/cli_group.py +14 -12
  9. mpflash/mpflash/cli_list.py +39 -3
  10. mpflash/mpflash/cli_main.py +17 -6
  11. mpflash/mpflash/common.py +125 -12
  12. mpflash/mpflash/config.py +2 -0
  13. mpflash/mpflash/connected.py +74 -0
  14. mpflash/mpflash/download.py +56 -42
  15. mpflash/mpflash/downloaded.py +9 -9
  16. mpflash/mpflash/flash.py +2 -2
  17. mpflash/mpflash/flash_esp.py +2 -2
  18. mpflash/mpflash/flash_uf2.py +16 -8
  19. mpflash/mpflash/flash_uf2_linux.py +5 -16
  20. mpflash/mpflash/flash_uf2_macos.py +78 -0
  21. mpflash/mpflash/flash_uf2_windows.py +1 -1
  22. mpflash/mpflash/list.py +57 -57
  23. mpflash/mpflash/mpboard_id/__init__.py +37 -44
  24. mpflash/mpflash/mpboard_id/add_boards.py +255 -0
  25. mpflash/mpflash/mpboard_id/board.py +37 -0
  26. mpflash/mpflash/mpboard_id/board_id.py +38 -34
  27. mpflash/mpflash/mpboard_id/board_info.zip +0 -0
  28. mpflash/mpflash/mpboard_id/store.py +42 -0
  29. mpflash/mpflash/mpremoteboard/__init__.py +18 -6
  30. mpflash/mpflash/uf2disk.py +12 -0
  31. mpflash/mpflash/vendor/basicgit.py +288 -0
  32. mpflash/mpflash/vendor/dfu.py +1 -0
  33. mpflash/mpflash/vendor/versions.py +7 -3
  34. mpflash/mpflash/worklist.py +71 -48
  35. mpflash/poetry.lock +164 -138
  36. mpflash/pyproject.toml +18 -15
  37. stubber/__init__.py +1 -1
  38. stubber/board/createstubs.py +4 -3
  39. stubber/board/createstubs_db.py +5 -7
  40. stubber/board/createstubs_db_min.py +329 -825
  41. stubber/board/createstubs_db_mpy.mpy +0 -0
  42. stubber/board/createstubs_mem.py +6 -7
  43. stubber/board/createstubs_mem_min.py +304 -765
  44. stubber/board/createstubs_mem_mpy.mpy +0 -0
  45. stubber/board/createstubs_min.py +293 -975
  46. stubber/board/createstubs_mpy.mpy +0 -0
  47. stubber/board/modulelist.txt +1 -0
  48. stubber/commands/get_core_cmd.py +7 -6
  49. stubber/commands/get_docstubs_cmd.py +8 -3
  50. stubber/commands/get_frozen_cmd.py +5 -2
  51. stubber/publish/publish.py +18 -7
  52. stubber/utils/makeversionhdr.py +3 -2
  53. stubber/utils/versions.py +2 -1
  54. mpflash/mpflash/mpboard_id/board_info.csv +0 -2213
  55. mpflash/mpflash/mpboard_id/board_info.json +0 -19910
  56. {micropython_stubber-1.20.1.dist-info → micropython_stubber-1.20.2.dist-info}/LICENSE +0 -0
  57. {micropython_stubber-1.20.1.dist-info → micropython_stubber-1.20.2.dist-info}/WHEEL +0 -0
  58. {micropython_stubber-1.20.1.dist-info → micropython_stubber-1.20.2.dist-info}/entry_points.txt +0 -0
@@ -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
- from mpflash.mpboard_id import find_stored_board
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,14 +89,25 @@ 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
- pass
72
- # 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}")
73
106
  else:
74
- # no boards specified - detect connected boards
75
- 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)
76
109
 
77
- params = ask_missing_params(params, action="download")
110
+ params = ask_missing_params(params)
78
111
  if not params: # Cancelled by user
79
112
  return 2
80
113
  params.versions = [clean_version(v, drop_v=True) for v in params.versions]
@@ -93,19 +126,3 @@ def cli_download(**kwargs) -> int:
93
126
  except MPFlashError as e:
94
127
  log.error(f"{e}")
95
128
  return 1
96
-
97
-
98
- def connected_ports_boards() -> Tuple[List[str], List[str]]:
99
- """
100
- Returns a tuple containing lists of unique ports and boards from the connected MCUs.
101
- Boards that are physically connected, but give no tangible response are ignored.
102
-
103
- Returns:
104
- A tuple containing two lists:
105
- - A list of unique ports where MCUs are connected.
106
- - A list of unique board names of the connected MCUs.
107
- """
108
- mpr_boards = [b for b in list_mcus() if b.connected]
109
- ports = list({b.port for b in mpr_boards})
110
- boards = list({b.board for b in mpr_boards})
111
- return ports, boards
@@ -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
@@ -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,31 +117,31 @@ 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)
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
146
  return 2
131
147
  # TODO: Just in time Download of firmware
@@ -135,27 +151,33 @@ def cli_flash_board(**kwargs) -> int:
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
  )
@@ -172,3 +194,19 @@ def cli_flash_board(**kwargs) -> int:
172
194
  else:
173
195
  log.error("No boards were flashed")
174
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}")
@@ -12,23 +12,26 @@ from .logger import make_quiet, set_loglevel
12
12
  def cb_verbose(ctx, param, value):
13
13
  """Callback to set the log level to DEBUG if verbose is set"""
14
14
  if value and not config.quiet:
15
- set_loglevel("DEBUG")
16
15
  config.verbose = True
16
+ if value > 1:
17
+ set_loglevel("TRACE")
18
+ else:
19
+ set_loglevel("DEBUG")
17
20
  else:
18
21
  set_loglevel("INFO")
19
22
  config.verbose = False
20
23
  return value
21
24
 
22
25
 
23
- def cb_ignore(ctx, param, value):
26
+ def cb_interactive(ctx, param, value):
24
27
  if value:
25
- config.ignore_ports = list(value)
28
+ config.interactive = value
26
29
  return value
27
30
 
28
31
 
29
- def cb_interactive(ctx, param, value):
32
+ def cb_test(ctx, param, value):
30
33
  if value:
31
- config.interactive = value
34
+ config.tests = value
32
35
  return value
33
36
 
34
37
 
@@ -64,21 +67,20 @@ def cb_quiet(ctx, param, value):
64
67
  "-V",
65
68
  "--verbose",
66
69
  is_eager=True,
67
- is_flag=True,
70
+ count=True,
68
71
  help="Enables verbose mode.",
69
72
  callback=cb_verbose,
70
73
  )
71
74
  @click.option(
72
- "--ignore",
73
- "-i",
75
+ "--test",
74
76
  is_eager=True,
75
- help="Serial port(s) to ignore. Defaults to MPFLASH_IGNORE.",
76
- callback=cb_ignore,
77
+ help="test a specific feature",
78
+ callback=cb_test,
77
79
  multiple=True,
78
80
  default=[],
79
- envvar="MPFLASH_IGNORE",
81
+ envvar="MPFLASH_TEST",
80
82
  show_default=True,
81
- metavar="SERIALPORT",
83
+ metavar="TEST",
82
84
  )
83
85
  def cli(**kwargs):
84
86
  """mpflash - MicroPython Tool.
@@ -1,11 +1,13 @@
1
1
  import json
2
+ from typing import List
2
3
 
3
4
  import rich_click as click
4
5
  from rich import print
5
6
 
6
7
  from .cli_group import cli
7
- from .list import list_mcus, show_mcus
8
+ from .list import show_mcus
8
9
  from .logger import make_quiet
10
+ from .connected import list_mcus
9
11
 
10
12
 
11
13
  @cli.command("list", help="List the connected MCU boards.")
@@ -18,21 +20,55 @@ from .logger import make_quiet
18
20
  show_default=True,
19
21
  help="""Output in json format""",
20
22
  )
23
+ @click.option(
24
+ "--serial",
25
+ "--serial-port",
26
+ "-s",
27
+ "serial",
28
+ default=["*"],
29
+ multiple=True,
30
+ show_default=True,
31
+ help="Which serial port(s) to list. ",
32
+ metavar="SERIALPORT",
33
+ )
34
+ @click.option(
35
+ "--ignore",
36
+ "-i",
37
+ is_eager=True,
38
+ help="Serial port(s) to ignore. Defaults to MPFLASH_IGNORE.",
39
+ multiple=True,
40
+ default=[],
41
+ envvar="MPFLASH_IGNORE",
42
+ show_default=True,
43
+ metavar="SERIALPORT",
44
+ )
45
+ @click.option(
46
+ "--bluetooth/--no-bluetooth",
47
+ "-b/-nb",
48
+ is_flag=True,
49
+ default=False,
50
+ show_default=True,
51
+ help="""Include bluetooth ports in the list""",
52
+ )
21
53
  @click.option(
22
54
  "--progress/--no-progress",
55
+ # "-p/-np", -p is already used for --port
23
56
  "progress",
24
57
  is_flag=True,
25
58
  default=True,
26
59
  show_default=True,
27
60
  help="""Show progress""",
28
61
  )
29
- def cli_list_mcus(as_json: bool, progress: bool = True) -> int:
62
+ def cli_list_mcus(serial: List[str], ignore: List[str], bluetooth: bool, as_json: bool, progress: bool = True) -> int:
30
63
  """List the connected MCU boards, and output in a nice table or json."""
64
+ serial = list(serial)
65
+ ignore = list(ignore)
31
66
  if as_json:
32
67
  # avoid noise in json output
33
68
  make_quiet()
69
+ # TODO? Ask user to select a serialport if [?] is given ?
34
70
 
35
- conn_mcus = list_mcus()
71
+ conn_mcus = list_mcus(ignore=ignore, include=serial, bluetooth=bluetooth)
36
72
  if as_json:
37
73
  print(json.dumps([mcu.__dict__ for mcu in conn_mcus], indent=4))
38
74
  progress = False
@@ -2,7 +2,10 @@
2
2
 
3
3
  # import rich_click as click
4
4
 
5
+ import os
6
+
5
7
  import click
8
+ from loguru import logger as log
6
9
 
7
10
  from .cli_download import cli_download
8
11
  from .cli_flash import cli_flash_board
@@ -11,16 +14,24 @@ from .cli_list import cli_list_mcus
11
14
 
12
15
 
13
16
  def mpflash():
14
- cli.add_command(cli_flash_board)
15
17
  cli.add_command(cli_list_mcus)
16
18
  cli.add_command(cli_download)
19
+ cli.add_command(cli_flash_board)
20
+
17
21
  # cli(auto_envvar_prefix="MPFLASH")
18
- try:
22
+ if False and os.environ.get("COMPUTERNAME") == "JOSVERL-S4":
23
+ # intentional less error suppression on dev machine
19
24
  result = cli(standalone_mode=False)
20
- exit(result)
21
- except AttributeError as e:
22
- print(f"Error: {e}")
23
- exit(-1)
25
+ else:
26
+ try:
27
+ result = cli(standalone_mode=False)
28
+ exit(result)
29
+ except AttributeError as e:
30
+ log.error(f"Error: {e}")
31
+ exit(-1)
32
+ except click.exceptions.ClickException as e:
33
+ log.error(f"Error: {e}")
34
+ exit(-2)
24
35
 
25
36
 
26
37
  if __name__ == "__main__":
mpflash/mpflash/common.py CHANGED
@@ -1,11 +1,16 @@
1
+ import fnmatch
1
2
  import os
2
- import time
3
- from typing import TypedDict
3
+ import sys
4
+ from dataclasses import dataclass, field
5
+ from pathlib import Path
6
+ from typing import List, Optional, Union
4
7
 
5
8
  from github import Auth, Github
6
- from rich.progress import track
9
+ from serial.tools import list_ports
10
+ from serial.tools.list_ports_common import ListPortInfo
11
+
12
+ from .logger import log
7
13
 
8
- from mpflash.errors import MPFlashError
9
14
  # from mpflash.mpremoteboard import MPRemoteBoard
10
15
 
11
16
  PORT_FWTYPES = {
@@ -14,6 +19,7 @@ PORT_FWTYPES = {
14
19
  "esp8266": [".bin"],
15
20
  "rp2": [".uf2"],
16
21
  "samd": [".uf2"],
22
+ # below this not yet implemented / tested
17
23
  "mimxrt": [".hex"],
18
24
  "nrf": [".uf2"],
19
25
  "renesas-ra": [".hex"],
@@ -28,11 +34,118 @@ PAT = os.environ.get("GITHUB_TOKEN") or PAT_NO_ACCESS
28
34
  GH_CLIENT = Github(auth=Auth.Token(PAT))
29
35
 
30
36
 
31
- class FWInfo(TypedDict):
32
- filename: str
33
- port: str
34
- board: str
35
- variant: str
36
- preview: bool
37
- version: str
38
- build: str
37
+ @dataclass
38
+ class FWInfo:
39
+ """
40
+ Downloaded Firmware information
41
+ is somewhat related to the BOARD class in the mpboard_id module
42
+ """
43
+
44
+ port: str # MicroPython port
45
+ board: str # MicroPython board
46
+ filename: str = field(default="") # relative filename of the firmware image
47
+ firmware: str = field(default="") # url or path to original firmware image
48
+ variant: str = field(default="") # MicroPython variant
49
+ preview: bool = field(default=False) # True if the firmware is a preview version
50
+ version: str = field(default="") # MicroPython version
51
+ url: str = field(default="") # url to the firmware image download folder
52
+ build: str = field(default="0") # The build = number of commits since the last release
53
+ ext: str = field(default="") # the file extension of the firmware
54
+ family: str = field(default="micropython") # The family of the firmware
55
+ custom: bool = field(default=False) # True if the firmware is a custom build
56
+ description: str = field(default="") # Description used by this firmware (custom only)
57
+
58
+ def to_dict(self) -> dict:
59
+ """Convert the object to a dictionary"""
60
+ return self.__dict__
61
+
62
+ @classmethod
63
+ def from_dict(cls, data: dict) -> "FWInfo":
64
+ """Create a FWInfo object from a dictionary"""
65
+ # add missing keys
66
+ if "ext" not in data:
67
+ data["ext"] = Path(data["firmware"]).suffix
68
+ if "family" not in data:
69
+ data["family"] = "micropython"
70
+ return cls(**data)
71
+
72
+
73
+ @dataclass
74
+ class Params:
75
+ """Common parameters for downloading and flashing firmware"""
76
+
77
+ ports: List[str] = field(default_factory=list)
78
+ boards: List[str] = field(default_factory=list)
79
+ versions: List[str] = field(default_factory=list)
80
+ fw_folder: Path = Path()
81
+ serial: List[str] = field(default_factory=list)
82
+ ignore: List[str] = field(default_factory=list)
83
+
84
+
85
+ @dataclass
86
+ class DownloadParams(Params):
87
+ """Parameters for downloading firmware"""
88
+
89
+ clean: bool = False
90
+ force: bool = False
91
+
92
+
93
+ @dataclass
94
+ class FlashParams(Params):
95
+ """Parameters for flashing a board"""
96
+
97
+ erase: bool = True
98
+ bootloader: bool = True
99
+ cpu: str = ""
100
+
101
+
102
+ ParamType = Union[DownloadParams, FlashParams]
103
+
104
+
105
+ def filtered_comports(
106
+ ignore: Optional[List[str]] = None,
107
+ include: Optional[List[str]] = None,
108
+ bluetooth: bool = False,
109
+ ) -> List[ListPortInfo]: # sourcery skip: assign-if-exp
110
+ """
111
+ Get a list of filtered comports.
112
+ """
113
+ if not ignore:
114
+ ignore = []
115
+ elif not isinstance(ignore, list): # type: ignore
116
+ ignore = list(ignore)
117
+ if not include:
118
+ include = ["*"]
119
+ elif not isinstance(include, list): # type: ignore
120
+ include = list(include)
121
+
122
+ # remove ports that are to be ignored
123
+ log.trace(f"{include=}, {ignore=}, {bluetooth=}")
124
+ comports = [p for p in list_ports.comports() if not any(fnmatch.fnmatch(p.device, i) for i in ignore)]
125
+ log.trace(f"comports: {[p.device for p in comports]}")
126
+ # remove bluetooth ports
127
+
128
+ if include != ["*"]:
129
+ # if there are explicit ports to include, add them to the list
130
+ explicit = [p for p in list_ports.comports() if any(fnmatch.fnmatch(p.device, i) for i in include)]
131
+ log.trace(f"explicit: {[p.device for p in explicit]}")
132
+ if ignore == []:
133
+ # if nothing to ignore, just use the explicit list as a sinple sane default
134
+ comports = explicit
135
+ else:
136
+ # if there are ports to ignore, add the explicit list to the filtered list
137
+ comports = list(set(explicit) | set(comports))
138
+ if not bluetooth:
139
+ # filter out bluetooth ports
140
+ comports = [p for p in comports if "bluetooth" not in p.description.lower()]
141
+ comports = [p for p in comports if "BTHENUM" not in p.hwid]
142
+ if sys.platform == "darwin":
143
+ comports = [p for p in comports if ".Bluetooth" not in p.device]
144
+ log.trace(f"no Bluetooth: {[p.device for p in comports]}")
145
+ log.debug(f"filtered_comports: {[p.device for p in comports]}")
146
+ # sort
147
+ if sys.platform == "win32":
148
+ # Windows sort of comports by number - but fallback to device name
149
+ return sorted(comports, key=lambda x: int(x.device.split()[0][3:]) if x.device.split()[0][3:].isdigit() else x)
150
+ # sort by device name
151
+ return sorted(comports, key=lambda x: x.device)
mpflash/mpflash/config.py CHANGED
@@ -14,6 +14,8 @@ class MPtoolConfig:
14
14
  ignore_ports: List[str] = []
15
15
  interactive: bool = True
16
16
  firmware_folder: Path = platformdirs.user_downloads_path() / "firmware"
17
+ # test options specified on the commandline
18
+ tests: List[str] = []
17
19
 
18
20
 
19
21
  config = MPtoolConfig()
@@ -0,0 +1,74 @@
1
+ from typing import List, Tuple
2
+
3
+ from rich import print
4
+ from rich.progress import BarColumn, Progress, SpinnerColumn, TextColumn, TimeElapsedColumn
5
+ from rich.table import Column
6
+
7
+ from mpflash.mpremoteboard import MPRemoteBoard
8
+
9
+ from .common import filtered_comports
10
+
11
+
12
+ def connected_ports_boards(
13
+ *, include: List[str], ignore: List[str]
14
+ ) -> Tuple[List[str], List[str], List[MPRemoteBoard]]:
15
+ """
16
+ Returns a tuple containing lists of unique ports and boards from the connected MCUs.
17
+ Boards that are physically connected, but give no tangible response are ignored.
18
+
19
+ Returns:
20
+ A tuple containing three lists:
21
+ - A list of unique ports where MCUs are connected.
22
+ - A list of unique board names of the connected MCUs.
23
+ - A list of MPRemoteBoard instances of the connected MCUs.
24
+ """
25
+ mpr_boards = [b for b in list_mcus(include=include, ignore=ignore) if b.connected]
26
+ ports = list({b.port for b in mpr_boards})
27
+ boards = list({b.board for b in mpr_boards})
28
+ return (ports, boards, mpr_boards)
29
+
30
+
31
+ # #########################################################################################################
32
+ rp_spinner = SpinnerColumn(finished_text="✅")
33
+ rp_text = TextColumn("{task.description} {task.fields[device]}", table_column=Column())
34
+ rp_bar = BarColumn(bar_width=None, table_column=Column())
35
+
36
+
37
+ def list_mcus(*, ignore: List[str], include: List[str], bluetooth: bool = False):
38
+ """
39
+ Retrieves information about connected microcontroller boards.
40
+
41
+ Returns:
42
+ List[MPRemoteBoard]: A list of MPRemoteBoard instances with board information.
43
+ Raises:
44
+ ConnectionError: If there is an error connecting to a board.
45
+ """
46
+ # conn_mcus = [MPRemoteBoard(sp) for sp in MPRemoteBoard.connected_boards(bluetooth) if sp not in config.ignore_ports]
47
+
48
+ comports = filtered_comports(
49
+ ignore=ignore,
50
+ include=include,
51
+ bluetooth=bluetooth,
52
+ )
53
+ conn_mcus = [MPRemoteBoard(c.device) for c in comports]
54
+
55
+ # a lot of boilerplate to show a progress bar with the comport currently scanned
56
+ # low update rate to facilitate screen readers/narration
57
+ with Progress(rp_spinner, rp_text, rp_bar, TimeElapsedColumn(), refresh_per_second=2) as progress:
58
+ tsk_scan = progress.add_task("[green]Scanning", visible=False, total=None)
59
+ progress.tasks[tsk_scan].fields["device"] = "..."
60
+ progress.tasks[tsk_scan].visible = True
61
+ progress.start_task(tsk_scan)
62
+ try:
63
+ for mcu in conn_mcus:
64
+ progress.update(tsk_scan, device=mcu.serialport.replace("/dev/", ""))
65
+ try:
66
+ mcu.get_mcu_info()
67
+ except ConnectionError as e:
68
+ print(f"Error: {e}")
69
+ continue
70
+ finally:
71
+ # transient
72
+ progress.stop_task(tsk_scan)
73
+ progress.tasks[tsk_scan].visible = False
74
+ return conn_mcus