mpflash 0.7.4__py3-none-any.whl → 0.7.6__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.
mpflash/ask_input.py CHANGED
@@ -12,7 +12,7 @@ from typing import Dict, List, Sequence, Tuple, Union
12
12
  from loguru import logger as log
13
13
 
14
14
  from mpflash.config import config
15
- from mpflash.mpboard_id import get_stored_boards_for_port, known_stored_boards, local_mp_ports
15
+ from mpflash.mpboard_id import get_known_boards_for_port, get_known_ports, known_stored_boards
16
16
  from mpflash.mpremoteboard import MPRemoteBoard
17
17
  from mpflash.vendor.versions import micropython_versions
18
18
 
@@ -45,7 +45,7 @@ ParamType = Union[DownloadParams, FlashParams]
45
45
 
46
46
  def ask_missing_params(
47
47
  params: ParamType,
48
- action: str = "download",
48
+ # action: str = "download",
49
49
  ) -> ParamType:
50
50
  """
51
51
  Asks the user for parameters that have not been supplied on the commandline and returns the updated params.
@@ -57,6 +57,10 @@ def ask_missing_params(
57
57
  Returns:
58
58
  ParamType: The updated parameters.
59
59
  """
60
+ # if action flash, single input
61
+ # if action download, multiple input
62
+ multi_select = isinstance(params, DownloadParams)
63
+ action = "download" if isinstance(params, DownloadParams) else "flash"
60
64
  if not config.interactive:
61
65
  # no interactivity allowed
62
66
  return params
@@ -64,37 +68,46 @@ def ask_missing_params(
64
68
  import inquirer
65
69
 
66
70
  questions = []
67
- answers = {"action": action}
68
- if isinstance(params, FlashParams):
71
+ answers = {"action": "download" if isinstance(params, DownloadParams) else "flash"}
72
+ if not multi_select:
69
73
  if not params.serial or "?" in params.serial:
70
- ask_serialport(questions, action=action)
74
+ ask_serialport(questions)
71
75
  else:
72
76
  answers["serial"] = params.serial
73
77
 
74
78
  if not params.versions or "?" in params.versions:
75
- ask_versions(questions, action=action)
79
+ ask_versions(questions, multi_select=multi_select, action=action)
76
80
  else:
77
81
  # versions is used to show only the boards for the selected versions
78
82
  answers["versions"] = params.versions # type: ignore
79
83
 
80
84
  if not params.boards or "?" in params.boards:
81
- ask_port_board(questions, action=action)
82
-
83
- answers = inquirer.prompt(questions, answers=answers)
85
+ ask_port_board(questions, multi_select=multi_select, action=action)
86
+ if questions:
87
+ answers = inquirer.prompt(questions, answers=answers)
84
88
  if not answers:
85
89
  # input cancelled by user
86
90
  return [] # type: ignore
87
91
  # print(repr(answers))
88
92
  if isinstance(params, FlashParams) and "serial" in answers:
89
- params.serial = answers["serial"]
93
+ params.serial = answers["serial"].split()[0] # split to remove the description
90
94
  if "port" in answers:
91
- params.ports = [answers["port"]]
95
+ params.ports = [p for p in params.ports if p != "?"] # remove the "?" if present
96
+ params.ports.append(answers["port"])
92
97
  if "boards" in answers:
93
- params.boards = answers["boards"] if isinstance(answers["boards"], list) else [answers["boards"]]
98
+ params.boards = [b for b in params.boards if b != "?"] # remove the "?" if present
99
+ params.boards.extend(answers["boards"] if isinstance(answers["boards"], list) else [answers["boards"]])
94
100
  if "versions" in answers:
101
+ params.versions = [v for v in params.versions if v != "?"] # remove the "?" if present
95
102
  # make sure it is a list
96
- params.versions = answers["versions"] if isinstance(answers["versions"], list) else [answers["versions"]]
97
-
103
+ if isinstance(answers["versions"], (list, tuple)):
104
+ params.versions.extend(answers["versions"])
105
+ else:
106
+ params.versions.append(answers["versions"])
107
+ # remove duplicates
108
+ params.ports = list(set(params.ports))
109
+ params.boards = list(set(params.boards))
110
+ params.versions = list(set(params.versions))
98
111
  log.debug(repr(params))
99
112
 
100
113
  return params
@@ -135,7 +148,7 @@ def filter_matching_boards(answers: dict) -> Sequence[Tuple[str, str]]:
135
148
  return some_boards
136
149
 
137
150
 
138
- def ask_port_board(questions: list, *, action: str):
151
+ def ask_port_board(questions: list, *, multi_select: bool, action: str):
139
152
  """
140
153
  Asks the user for the port and board selection.
141
154
 
@@ -149,14 +162,15 @@ def ask_port_board(questions: list, *, action: str):
149
162
  # import only when needed to reduce load time
150
163
  import inquirer
151
164
 
152
- # TODO: if action = flash, Use Inquirer.List for boards
153
- inquirer_ux = inquirer.Checkbox if action == "download" else inquirer.List
165
+ # if action flash, single input
166
+ # if action download, multiple input
167
+ inquirer_ux = inquirer.Checkbox if multi_select else inquirer.List
154
168
  questions.extend(
155
169
  (
156
170
  inquirer.List(
157
171
  "port",
158
172
  message="Which port do you want to {action} " + "to {serial} ?" if action == "flash" else "?",
159
- choices=local_mp_ports(),
173
+ choices=get_known_ports(),
160
174
  autocomplete=True,
161
175
  ),
162
176
  inquirer_ux(
@@ -173,7 +187,7 @@ def ask_port_board(questions: list, *, action: str):
173
187
  )
174
188
 
175
189
 
176
- def ask_versions(questions: list, *, action: str):
190
+ def ask_versions(questions: list, *, multi_select: bool, action: str):
177
191
  """
178
192
  Asks the user for the version selection.
179
193
 
@@ -188,13 +202,13 @@ def ask_versions(questions: list, *, action: str):
188
202
  import inquirer
189
203
  import inquirer.errors
190
204
 
191
- input_ux = inquirer.Checkbox if action == "download" else inquirer.List
205
+ input_ux = inquirer.Checkbox if multi_select else inquirer.List
192
206
  mp_versions: List[str] = micropython_versions()
193
207
  mp_versions = [v for v in mp_versions if "preview" not in v]
194
208
 
195
209
  # remove the versions for which there are no known boards in the board_info.json
196
210
  # todo: this may be a little slow
197
- mp_versions = [v for v in mp_versions if get_stored_boards_for_port("stm32", [v])]
211
+ mp_versions = [v for v in mp_versions if get_known_boards_for_port("stm32", [v])]
198
212
 
199
213
  mp_versions.append("preview")
200
214
  mp_versions.reverse() # newest first
@@ -216,12 +230,12 @@ def ask_versions(questions: list, *, action: str):
216
230
  # hints=["Use space to select multiple options"],
217
231
  choices=mp_versions,
218
232
  autocomplete=True,
219
- validate=at_least_one_validation,
233
+ validate=at_least_one_validation, # type: ignore
220
234
  )
221
235
  )
222
236
 
223
237
 
224
- def ask_serialport(questions: list, *, action: str):
238
+ def ask_serialport(questions: list, *, multi_select: bool = False, bluetooth: bool = False):
225
239
  """
226
240
  Asks the user for the serial port selection.
227
241
 
@@ -235,12 +249,12 @@ def ask_serialport(questions: list, *, action: str):
235
249
  # import only when needed to reduce load time
236
250
  import inquirer
237
251
 
238
- serialports = MPRemoteBoard.connected_boards()
252
+ comports = MPRemoteBoard.connected_boards(bluetooth=bluetooth, description=True)
239
253
  questions.append(
240
254
  inquirer.List(
241
255
  "serial",
242
256
  message="Which serial port do you want to {action} ?",
243
- choices=serialports,
257
+ choices=comports,
244
258
  other=True,
245
259
  validate=lambda _, x: True if x else "Please select or enter a serial port", # type: ignore
246
260
  )
mpflash/cli_download.py CHANGED
@@ -7,7 +7,7 @@ import rich_click as click
7
7
  from loguru import logger as log
8
8
 
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
13
  from .ask_input import DownloadParams, ask_missing_params
@@ -68,13 +68,20 @@ def cli_download(**kwargs) -> int:
68
68
  params.versions = list(params.versions)
69
69
  params.boards = list(params.boards)
70
70
  if params.boards:
71
- pass
72
- # TODO Clean board - same as in cli_flash.py
71
+ if not params.ports:
72
+ # no ports specified - resolve ports from specified boards by resolving board IDs
73
+ for board in params.boards:
74
+ if board != "?":
75
+ try:
76
+ board_ = find_known_board(board)
77
+ params.ports.append(board_["port"])
78
+ except MPFlashError as e:
79
+ log.error(f"{e}")
73
80
  else:
74
- # no boards specified - detect connected boards
81
+ # no boards specified - detect connected ports and boards
75
82
  params.ports, params.boards = connected_ports_boards()
76
83
 
77
- params = ask_missing_params(params, action="download")
84
+ params = ask_missing_params(params)
78
85
  if not params: # Cancelled by user
79
86
  return 2
80
87
  params.versions = [clean_version(v, drop_v=True) for v in params.versions]
mpflash/cli_flash.py CHANGED
@@ -4,7 +4,7 @@ import rich_click as click
4
4
  from loguru import logger as log
5
5
 
6
6
  from mpflash.errors import MPFlashError
7
- from mpflash.mpboard_id import find_stored_board
7
+ from mpflash.mpboard_id import find_known_board
8
8
  from mpflash.vendor.versions import clean_version
9
9
 
10
10
  from .ask_input import FlashParams, ask_missing_params
@@ -101,6 +101,14 @@ def cli_flash_board(**kwargs) -> int:
101
101
  kwargs["boards"] = [kwargs.pop("board")]
102
102
 
103
103
  params = FlashParams(**kwargs)
104
+
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 = "?"
109
+
110
+ # Detect connected boards if not specified,
111
+ # and ask for input if boards cannot be detected
104
112
  if not params.boards or params.boards == []:
105
113
  # nothing specified - detect connected boards
106
114
  params.ports, params.boards = connected_ports_boards()
@@ -116,7 +124,7 @@ def cli_flash_board(**kwargs) -> int:
116
124
  continue
117
125
  if " " in board_id:
118
126
  try:
119
- info = find_stored_board(board_id)
127
+ info = find_known_board(board_id)
120
128
  if info:
121
129
  log.info(f"Resolved board description: {info['board']}")
122
130
  params.boards.remove(board_id)
@@ -125,7 +133,7 @@ def cli_flash_board(**kwargs) -> int:
125
133
  log.warning(f"unable to resolve board description: {e}")
126
134
 
127
135
  # Ask for missing input if needed
128
- params = ask_missing_params(params, action="flash")
136
+ params = ask_missing_params(params)
129
137
  if not params: # Cancelled by user
130
138
  return 2
131
139
  # TODO: Just in time Download of firmware
mpflash/cli_main.py CHANGED
@@ -3,6 +3,7 @@
3
3
  # import rich_click as click
4
4
 
5
5
  import click
6
+ from loguru import logger as log
6
7
 
7
8
  from .cli_download import cli_download
8
9
  from .cli_flash import cli_flash_board
@@ -19,8 +20,11 @@ def mpflash():
19
20
  result = cli(standalone_mode=False)
20
21
  exit(result)
21
22
  except AttributeError as e:
22
- print(f"Error: {e}")
23
+ log.error(f"Error: {e}")
23
24
  exit(-1)
25
+ except click.exceptions.ClickException as e:
26
+ log.error(f"Error: {e}")
27
+ exit(-2)
24
28
 
25
29
 
26
30
  if __name__ == "__main__":
mpflash/download.py CHANGED
@@ -20,6 +20,7 @@ from rich.progress import track
20
20
 
21
21
  from mpflash.common import PORT_FWTYPES
22
22
  from mpflash.errors import MPFlashError
23
+ from mpflash.mpboard_id import get_known_ports
23
24
 
24
25
  jsonlines.ujson = None # type: ignore
25
26
  # #########################################################################################################
@@ -109,6 +110,8 @@ def get_boards(ports: List[str], boards: List[str], clean: bool) -> List[Firmwar
109
110
 
110
111
  """
111
112
  board_urls: List[FirmwareInfo] = []
113
+ if ports is None:
114
+ ports = get_known_ports()
112
115
  for port in ports:
113
116
  download_page_url = f"{MICROPYTHON_ORG_URL}download/?port={port}"
114
117
  _urls = get_board_urls(download_page_url)
@@ -118,7 +121,7 @@ def get_boards(ports: List[str], boards: List[str], clean: bool) -> List[Firmwar
118
121
  for board in _urls:
119
122
  board["port"] = port
120
123
 
121
- for board in track(_urls, description=f"Checking {port} download pages", transient=True):
124
+ for board in track(_urls, description=f"Checking {port} download pages", transient=True,refresh_per_second=2):
122
125
  # add a board to the list for each firmware found
123
126
  firmwares = []
124
127
  for ext in PORT_FWTYPES[port]:
@@ -170,6 +173,7 @@ def download_firmwares(
170
173
  skipped = downloaded = 0
171
174
  if versions is None:
172
175
  versions = []
176
+
173
177
  unique_boards = get_firmware_list(ports, boards, versions, clean)
174
178
 
175
179
  for b in unique_boards:
mpflash/flash_esp.py CHANGED
@@ -10,7 +10,7 @@ from typing import List, Optional
10
10
  import esptool
11
11
  from loguru import logger as log
12
12
 
13
- from mpflash.mpboard_id import find_stored_board
13
+ from mpflash.mpboard_id import find_known_board
14
14
  from mpflash.mpremoteboard import MPRemoteBoard
15
15
 
16
16
 
@@ -22,7 +22,7 @@ def flash_esp(mcu: MPRemoteBoard, fw_file: Path, *, erase: bool = True) -> Optio
22
22
  log.info(f"Flashing {fw_file} on {mcu.board} on {mcu.serialport}")
23
23
  if not mcu.cpu:
24
24
  # Lookup CPU based on the board name
25
- mcu.cpu = find_stored_board(mcu.board)["cpu"]
25
+ mcu.cpu = find_known_board(mcu.board)["cpu"]
26
26
 
27
27
  cmds: List[List[str]] = []
28
28
  if erase:
mpflash/flash_uf2.py CHANGED
@@ -56,6 +56,6 @@ def flash_uf2(mcu: MPRemoteBoard, fw_file: Path, erase: bool) -> Optional[MPRemo
56
56
  log.success("Done copying, resetting the board and wait for it to restart")
57
57
  if sys.platform in ["linux", "darwin"]:
58
58
  dismount_uf2()
59
- for _ in track(range(5 + 2), description="Waiting for the board to restart", transient=True):
59
+ for _ in track(range(5 + 2), description="Waiting for the board to restart", transient=True, refresh_per_second=2):
60
60
  time.sleep(1) # 5 secs to short on linux
61
61
  return mcu
@@ -113,7 +113,7 @@ def wait_for_UF2_linux(s_max: int = 10):
113
113
  wait = 10
114
114
  uf2_drives = []
115
115
  # while not destination and wait > 0:
116
- for _ in track(range(s_max), description="Waiting for mcu to mount as a drive", transient=True):
116
+ for _ in track(range(s_max), description="Waiting for mcu to mount as a drive", transient=True,refresh_per_second=2):
117
117
  # log.info(f"Waiting for mcu to mount as a drive : {wait} seconds left")
118
118
  uf2_drives += list(get_uf2_drives())
119
119
  for drive in get_uf2_drives():
@@ -17,7 +17,7 @@ def wait_for_UF2_windows(s_max: int = 10):
17
17
  if s_max < 1:
18
18
  s_max = 10
19
19
  destination = ""
20
- for _ in track(range(s_max), description="Waiting for mcu to mount as a drive", transient=True):
20
+ for _ in track(range(s_max), description="Waiting for mcu to mount as a drive", transient=True,refresh_per_second=2):
21
21
  # log.info(f"Waiting for mcu to mount as a drive : {n} seconds left")
22
22
  drives = [drive.device for drive in psutil.disk_partitions()]
23
23
  for drive in drives:
mpflash/list.py CHANGED
@@ -26,8 +26,9 @@ def list_mcus(bluetooth: bool = False):
26
26
  """
27
27
  conn_mcus = [MPRemoteBoard(sp) for sp in MPRemoteBoard.connected_boards(bluetooth) if sp not in config.ignore_ports]
28
28
 
29
- # a lot of boilerplate to show a progress bar with the comport currenlty scanned
30
- with Progress(rp_spinner, rp_text, rp_bar, TimeElapsedColumn()) as progress:
29
+ # a lot of boilerplate to show a progress bar with the comport currently scanned
30
+ # low update rate to facilitate screen readers/narration
31
+ with Progress(rp_spinner, rp_text, rp_bar, TimeElapsedColumn(), refresh_per_second=2) as progress:
31
32
  tsk_scan = progress.add_task("[green]Scanning", visible=False, total=None)
32
33
  progress.tasks[tsk_scan].fields["device"] = "..."
33
34
  progress.tasks[tsk_scan].visible = True
@@ -51,39 +52,79 @@ def show_mcus(
51
52
  conn_mcus: List[MPRemoteBoard],
52
53
  title: str = "Connected boards",
53
54
  refresh: bool = True,
54
- ): # sourcery skip: extract-duplicate-method
55
- """Show the list of connected boards in a nice table"""
55
+ ):
56
+ console.print(mcu_table(conn_mcus, title, refresh))
57
+
58
+
59
+ def abbrv_family(family: str, is_wide: bool) -> str:
60
+ ABRV = {"micropython": "upy", "circuitpython": "cpy"}
61
+ if not is_wide:
62
+ if family in ABRV:
63
+ return ABRV[family]
64
+ return family[:4]
65
+ return family
66
+
67
+
68
+ def mcu_table(
69
+ conn_mcus: List[MPRemoteBoard],
70
+ title: str = "Connected boards",
71
+ refresh: bool = True,
72
+ ):
73
+ """
74
+ builds a rich table with the connected boards information
75
+ The columns of the table are adjusted to the terminal width
76
+ the columns are :
77
+ Narrow Wide
78
+ - Serial Yes Yes
79
+ - Family abbrv. Yes
80
+ - Port - yes
81
+ - Board Yes Yes BOARD_ID and Description
82
+ - CPU - Yes
83
+ - Version Yes Yes
84
+ - Build * * only if any of the mcus have a build
85
+ """
56
86
  table = Table(
57
87
  title=title,
58
88
  title_style="magenta",
59
89
  header_style="bold magenta",
60
90
  collapse_padding=True,
61
- width=110,
91
+ padding=(0, 0),
62
92
  )
63
- table.add_column("Serial", overflow="fold")
64
- table.add_column("Family")
65
- table.add_column("Port")
93
+ # check if the terminal is wide enough to show all columns or if we need to collapse some
94
+ is_wide = console.width > 99
95
+ needs_build = any(mcu.build for mcu in conn_mcus)
96
+
97
+ table.add_column("Serial" if is_wide else "Ser.", overflow="fold")
98
+ table.add_column("Family" if is_wide else "Fam.", overflow="crop", max_width=None if is_wide else 4)
99
+ if is_wide:
100
+ table.add_column("Port")
66
101
  table.add_column("Board", overflow="fold")
67
102
  # table.add_column("Variant") # TODO: add variant
68
- table.add_column("CPU")
69
- table.add_column("Version")
70
- table.add_column("build", justify="right")
103
+ if is_wide:
104
+ table.add_column("CPU")
105
+ table.add_column("Version", overflow="fold", min_width=5, max_width=16)
106
+ if needs_build:
107
+ table.add_column("Build" if is_wide else "Bld", justify="right")
71
108
 
72
- for mcu in track(conn_mcus, description="Updating board info", transient=True, update_period=0.1):
109
+ for mcu in track(conn_mcus, description="Updating board info", transient=True, refresh_per_second=2):
73
110
  if refresh:
74
111
  try:
75
112
  mcu.get_mcu_info()
76
113
  except ConnectionError:
77
114
  continue
78
115
  description = f"[italic bright_cyan]{mcu.description}" if mcu.description else ""
79
- table.add_row(
116
+ row = [
80
117
  mcu.serialport.replace("/dev/", ""),
81
- mcu.family,
82
- mcu.port,
83
- f"{mcu.board}\n{description}".strip(),
84
- # mcu.variant,
85
- mcu.cpu,
86
- clean_version(mcu.version),
87
- mcu.build,
88
- )
89
- console.print(table)
118
+ abbrv_family(mcu.family, is_wide),
119
+ ]
120
+ if is_wide:
121
+ row.append(mcu.port)
122
+ row.append(f"{mcu.board}\n{description}".strip())
123
+ if is_wide:
124
+ row.append(mcu.cpu)
125
+ row.append(clean_version(mcu.version))
126
+ if needs_build:
127
+ row.append(mcu.build)
128
+
129
+ table.add_row(*row)
130
+ return table
@@ -9,10 +9,13 @@ from functools import lru_cache
9
9
  from pathlib import Path
10
10
  from typing import List, Optional, Tuple, TypedDict, Union
11
11
 
12
- from mpflash.errors import MPFlashError
13
12
  from mpflash.common import PORT_FWTYPES
13
+ from mpflash.errors import MPFlashError
14
14
  from mpflash.vendor.versions import clean_version
15
15
 
16
+ # KNOWN ports and boards are sourced from the micropython repo,
17
+ # this info is stored in the board_info.json file
18
+
16
19
 
17
20
  # Board based on the dataclass Board but changed to TypedDict
18
21
  # - source : get_boardnames.py
@@ -30,27 +33,27 @@ class Board(TypedDict):
30
33
 
31
34
 
32
35
  @lru_cache(maxsize=None)
33
- def read_stored_boardinfo() -> List[Board]:
36
+ def read_known_boardinfo() -> List[Board]:
34
37
  """Reads the board_info.json file and returns the data as a list of Board objects"""
35
38
  with open(Path(__file__).parent / "board_info.json", "r") as file:
36
39
  return json.load(file)
37
40
 
38
41
 
39
- def local_mp_ports() -> List[str]:
42
+ def get_known_ports() -> List[str]:
40
43
  # TODO: Filter for Version
41
- mp_boards = read_stored_boardinfo()
44
+ mp_boards = read_known_boardinfo()
42
45
  # select the unique ports from info
43
46
  ports = set({board["port"] for board in mp_boards if board["port"] in PORT_FWTYPES.keys()})
44
47
  return sorted(list(ports))
45
48
 
46
49
 
47
- def get_stored_boards_for_port(port: str, versions: Optional[List[str]] = None):
50
+ def get_known_boards_for_port(port: str, versions: Optional[List[str]] = None):
48
51
  """
49
52
  Returns a list of boards for the given port and version(s)
50
53
 
51
54
  port : str : The Micropython port to filter for
52
55
  versions : List[str] : The Micropython versions to filter for (actual versions required)"""
53
- mp_boards = read_stored_boardinfo()
56
+ mp_boards = read_known_boardinfo()
54
57
 
55
58
  # filter for 'preview' as they are not in the board_info.json
56
59
  # instead use stable version
@@ -75,16 +78,16 @@ def known_stored_boards(port: str, versions: Optional[List[str]] = None) -> List
75
78
  port : str : The Micropython port to filter for
76
79
  versions : List[str] : The Micropython versions to filter for (actual versions required)
77
80
  """
78
- mp_boards = get_stored_boards_for_port(port, versions)
81
+ mp_boards = get_known_boards_for_port(port, versions)
79
82
 
80
83
  boards = set({(f'{board["version"]} {board["description"]}', board["board"]) for board in mp_boards})
81
84
  return sorted(list(boards))
82
85
 
83
86
 
84
87
  @lru_cache(maxsize=20)
85
- def find_stored_board(board_id: str) -> Board:
86
- """Find the board for the given board_ID or 'board description' and return the board info as a Board object"""
87
- info = read_stored_boardinfo()
88
+ def find_known_board(board_id: str) -> Board:
89
+ """Find the board for the given BOARD_ID or 'board description' and return the board info as a Board object"""
90
+ info = read_known_boardinfo()
88
91
  for board_info in info:
89
92
  if board_id in (board_info["board"], board_info["description"]):
90
93
  if "cpu" not in board_info or not board_info["cpu"]:
@@ -8,23 +8,28 @@ from pathlib import Path
8
8
  from typing import Optional
9
9
 
10
10
  from mpflash.errors import MPFlashError
11
- from mpflash.vendor.versions import clean_version
11
+ from mpflash.vendor.versions import clean_version, get_stable_mp_version
12
12
 
13
13
  ###############################################################################################
14
14
  HERE = Path(__file__).parent
15
15
  ###############################################################################################
16
16
 
17
17
 
18
- def find_board_id(
19
- descr: str, short_descr: str, board_info: Optional[Path] = None, version: str = "stable"
18
+ def find_board_id_by_description(
19
+ descr: str,
20
+ short_descr: str,
21
+ *,
22
+ version: str,
23
+ board_info: Optional[Path] = None,
20
24
  ) -> Optional[str]:
21
25
  """Find the MicroPython BOARD_ID based on the description in the firmware"""
26
+
22
27
  try:
23
- boards = find_board_id_by_description(
28
+ boards = _find_board_id_by_description(
24
29
  descr=descr,
25
30
  short_descr=short_descr,
26
31
  board_info=board_info,
27
- version=clean_version(version),
32
+ version=clean_version(version) if version else None,
28
33
  )
29
34
  return boards[-1]["board"]
30
35
  except MPFlashError:
@@ -32,7 +37,9 @@ def find_board_id(
32
37
 
33
38
 
34
39
  @functools.lru_cache(maxsize=20)
35
- def find_board_id_by_description(*, descr: str, short_descr: str, version="v1.21.0", board_info: Optional[Path] = None):
40
+ def _find_board_id_by_description(
41
+ *, descr: str, short_descr: str, version: Optional[str] = None, board_info: Optional[Path] = None
42
+ ):
36
43
  """
37
44
  Find the MicroPython BOARD_ID based on the description in the firmware
38
45
  using the pre-built board_info.json file
@@ -42,18 +49,20 @@ def find_board_id_by_description(*, descr: str, short_descr: str, version="v1.21
42
49
  if not board_info.exists():
43
50
  raise FileNotFoundError(f"Board info file not found: {board_info}")
44
51
 
45
- info = _read_board_info(board_info)
52
+ candidate_boards = _read_board_info(board_info)
46
53
 
47
- # filter for matching version
48
- if version == "preview":
49
- # TODO: match last stable
50
- version = "v1.22.2"
51
- version_matches = [b for b in info if b["version"].startswith(version)]
52
- if not version_matches:
53
- raise MPFlashError(f"No board info found for version {version}")
54
- matches = [b for b in version_matches if b["description"] == descr]
54
+ if version:
55
+ # filter for matching version
56
+ if version in ("preview", "stable"):
57
+ # match last stable
58
+ version = get_stable_mp_version()
59
+ version_matches = [b for b in candidate_boards if b["version"].startswith(version)]
60
+ if not version_matches:
61
+ raise MPFlashError(f"No board info found for version {version}")
62
+ candidate_boards = version_matches
63
+ matches = [b for b in candidate_boards if b["description"] == descr]
55
64
  if not matches and short_descr:
56
- matches = [b for b in version_matches if b["description"] == short_descr]
65
+ matches = [b for b in candidate_boards if b["description"] == short_descr]
57
66
  if not matches:
58
67
  raise MPFlashError(f"No board info found for description '{descr}' or '{short_descr}'")
59
68
  return sorted(matches, key=lambda x: x["version"])
@@ -13,7 +13,7 @@ from rich.progress import track
13
13
  from tenacity import retry, stop_after_attempt, wait_fixed
14
14
 
15
15
  from mpflash.errors import MPFlashError
16
- from mpflash.mpboard_id.board_id import find_board_id
16
+ from mpflash.mpboard_id.board_id import find_board_id_by_description
17
17
  from mpflash.mpremoteboard.runner import run
18
18
 
19
19
  ###############################################################################################
@@ -64,7 +64,7 @@ class MPRemoteBoard:
64
64
  return f"MPRemoteBoard({self.serialport}, {self.family} {self.port}, {self.board}, {self.version})"
65
65
 
66
66
  @staticmethod
67
- def connected_boards(bluetooth: bool = False) -> List[str]:
67
+ def connected_boards(bluetooth: bool = False, description: bool = False) -> List[str]:
68
68
  # TODO: rename to connected_comports
69
69
  """
70
70
  Get a list of connected comports.
@@ -81,8 +81,19 @@ class MPRemoteBoard:
81
81
  # filter out bluetooth ports
82
82
  comports = [p for p in comports if "bluetooth" not in p.description.lower()]
83
83
  comports = [p for p in comports if "BTHENUM" not in p.hwid]
84
-
85
- return sorted([p.device for p in comports])
84
+ if description:
85
+ output = [
86
+ f"{p.device} {(p.manufacturer + ' ') if p.manufacturer and not p.description.startswith(p.manufacturer) else ''}{p.description}"
87
+ for p in comports
88
+ ]
89
+ else:
90
+ output = [p.device for p in comports]
91
+
92
+ if sys.platform == "win32":
93
+ # Windows sort of comports by number - but fallback to device name
94
+ return sorted(output, key=lambda x: int(x.split()[0][3:]) if x.split()[0][3:].isdigit() else x)
95
+ # sort by device name
96
+ return sorted(output)
86
97
 
87
98
  @retry(stop=stop_after_attempt(RETRIES), wait=wait_fixed(1), reraise=True) # type: ignore ## retry_error_cls=ConnectionError,
88
99
  def get_mcu_info(self, timeout: int = 2):
@@ -116,10 +127,10 @@ class MPRemoteBoard:
116
127
  self.description = descr = info["board"]
117
128
  pos = descr.rfind(" with")
118
129
  short_descr = descr[:pos].strip() if pos != -1 else ""
119
- if board_name := find_board_id(descr, short_descr):
130
+ if board_name := find_board_id_by_description(descr, short_descr, version=self.version):
120
131
  self.board = board_name
121
132
  else:
122
- self.board = "UNKNOWN"
133
+ self.board = "UNKNOWN_BOARD"
123
134
 
124
135
  def disconnect(self) -> bool:
125
136
  """
@@ -200,6 +211,7 @@ class MPRemoteBoard:
200
211
  transient=True,
201
212
  get_time=lambda: time.time(),
202
213
  show_speed=False,
214
+ refresh_per_second=1,
203
215
  ):
204
216
  time.sleep(1)
205
217
  try:
mpflash/worklist.py CHANGED
@@ -9,7 +9,7 @@ from mpflash.errors import MPFlashError
9
9
  from .config import config
10
10
  from .downloaded import find_downloaded_firmware
11
11
  from .list import show_mcus
12
- from .mpboard_id import find_stored_board
12
+ from .mpboard_id import find_known_board
13
13
  from .mpremoteboard import MPRemoteBoard
14
14
 
15
15
  # #########################################################################################################
@@ -131,7 +131,7 @@ def manual_worklist(
131
131
  # TODO : Find a way to avoid needing to specify the port
132
132
  # Lookup the matching port and cpu in board_info based in the board name
133
133
  try:
134
- info = find_stored_board(board)
134
+ info = find_known_board(board)
135
135
  mcu.port = info["port"]
136
136
  # need the CPU type for the esptool
137
137
  mcu.cpu = info["cpu"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mpflash
3
- Version: 0.7.4
3
+ Version: 0.7.6
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
@@ -1,40 +1,40 @@
1
1
  mpflash/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- mpflash/ask_input.py,sha256=_LlatVyrWbY1Q4TDn8TWXJO5s22q_bGl3NXmdt13jxU,8442
3
- mpflash/cli_download.py,sha256=RNEsk1eMqzKCf9_sUPZQkDnzw6w-8L0Jnl6gDQup2Tc,3266
4
- mpflash/cli_flash.py,sha256=DTZ1mQSL31aEr3ROGIGWzzkiir4MRSqXdwq2p4ArDWE,5847
2
+ mpflash/ask_input.py,sha256=XxYbO2DwB59pyv1uec9fN7nDxQcHLPnp55LSU8eOMg8,9459
3
+ mpflash/cli_download.py,sha256=YHoQOaY5Cg0-jKkEHNa9jvlTr9AfVw9xNm33eWahfdI,3623
4
+ mpflash/cli_flash.py,sha256=yh8JH88LduSRW0gUvCpTUVmY6PaojciPZeEdHqtxq6M,6133
5
5
  mpflash/cli_group.py,sha256=nL3H06PHm_XUDlMuRyjgmTYeLnkrLa9mKDdahYw-KRo,1967
6
6
  mpflash/cli_list.py,sha256=KIlEeqcIIBf0g-emS43fzKspUy6fn9TUuFl0u00XaK8,1024
7
- mpflash/cli_main.py,sha256=BgqkDeEV0LBdT_Xn_Ay3zQOVJ-73pWSA4ngRf9KxGpw,656
7
+ mpflash/cli_main.py,sha256=GJzA4_WTiwAzQP0b2tCXwfyNId1TeBnrzU1GP5xZans,796
8
8
  mpflash/common.py,sha256=lucFGMLl03qz-5Ic2XVv4g5XVt6hloUU6N5v0tSaUYE,1049
9
9
  mpflash/config.py,sha256=G6TxliEGxoYXy1SHQYBKgywnKccz9QzD3mGq_Vv1frg,419
10
- mpflash/download.py,sha256=_yYENI7oew4tD51xEer1Ohv2B3LHrGg1lIF98LWDMCY,10991
10
+ mpflash/download.py,sha256=HMYpLVIcy_FaZGHIuucsmJ4fceYhf5DFJgG8f7r-mcA,11120
11
11
  mpflash/downloaded.py,sha256=ADMJqZn7WVcU-Rm2X6RqA8ejtBNBYXcpwxVyT3v7r6s,3803
12
12
  mpflash/errors.py,sha256=Q5LR12Wo8iUCg5n_qq4GjdBdBflbvCOdKsRJ5InYRfI,96
13
13
  mpflash/flash.py,sha256=YGYXuNNbjro4QvZmpwpLCo86nFsh4UxWrOJHOowUYDY,2490
14
- mpflash/flash_esp.py,sha256=TjBOk2y1eLrcE8T3iYGypsiskPX7BFNfxYmCuUo_3v4,2316
14
+ mpflash/flash_esp.py,sha256=1_jtZnmJl_aGznJZR1X9N2sK7ys8s_3N21LWllRB-lI,2314
15
15
  mpflash/flash_stm32.py,sha256=d4BoQl3a9Tchnvn2ZTuq2MpYBB4MTaRukwtEncI95k0,823
16
16
  mpflash/flash_stm32_cube.py,sha256=w7aGWjReeWUKl0Q3ZjXH8BRqNO1Tk9AO7gtRNUg1c9Y,3970
17
17
  mpflash/flash_stm32_dfu.py,sha256=G70EZodWb-aRi507Jxbys-VEwbBGU1oZacow3_nq-d4,2972
18
- mpflash/flash_uf2.py,sha256=KvNPk1zDwQexJfPI5MlIoR7zTD0u-pQQwSHuFQjuMXg,2093
18
+ mpflash/flash_uf2.py,sha256=ps2gUXZ4bdsgMqQzveYLLoXOmSC-aCmIG9L_tJjQ7Sk,2115
19
19
  mpflash/flash_uf2_boardid.py,sha256=WZKucGu_hJ8ymb236uuZbiR6pD6AA_l4LA-7LwtQhq8,414
20
- mpflash/flash_uf2_linux.py,sha256=LAGkzTImVq-wKo7LGUNlwkUHv1L4rGO7igR5dwxY07o,4298
21
- mpflash/flash_uf2_windows.py,sha256=dcmA-koavH7duOuNwI0n2aDDbhF1_5ZZ-mXFAXgj8z4,1072
22
- mpflash/list.py,sha256=R3upYux3mEltpqfrt467Ufs4hVatW1NE40jjhN7Ei1g,3252
20
+ mpflash/flash_uf2_linux.py,sha256=jMnoW-Mmj33RJFQkqQMs9iKmAAtH4QoD7gYGztkvhH8,4319
21
+ mpflash/flash_uf2_windows.py,sha256=94YoO2UIzfyJs4CPJ9sjG_WY26SX8aUPl9mf9R9W5xk,1093
22
+ mpflash/list.py,sha256=IpDhYrr5dXWY88PiKkvhqt3ioNF9tlsakTZVD05YBfQ,4713
23
23
  mpflash/logger.py,sha256=dI_H_a7EOdQJyvoeRHQuYeZuTKYVUS3DUPTLhE9rkdM,1098
24
- mpflash/mpboard_id/__init__.py,sha256=JYGe7VwpBV4ig2M9a6vJUQrMtgdNjZKHt_Z5N13Ycrs,3509
25
- mpflash/mpboard_id/board_id.py,sha256=G2oW969pqDuLbQS7UrFNmbWNdDIXfWBAoZMt1EWHLak,2361
24
+ mpflash/mpboard_id/__init__.py,sha256=LT52MV3tIHgBu_bFdvae2csahzonsCwno2ardU5ivsE,3621
25
+ mpflash/mpboard_id/board_id.py,sha256=vGPfxMVmQCBRlsZJAhsbhZStlDl21zLC0sqXcaBrSXc,2592
26
26
  mpflash/mpboard_id/board_info.csv,sha256=KPWDo-zHWfrPGQn9oInsDH-5IdCzhBCs6K_YAmqqSpQ,96983
27
27
  mpflash/mpboard_id/board_info.json,sha256=JtVyOMIO1O7vLKzJ0hyXQ4JSxXiQBJyay2hjdNLnZM0,674442
28
- mpflash/mpremoteboard/__init__.py,sha256=DxlO_7LiyWDz5hNRI77fzp3sI3fZQ9Sd23dnGLx4Zl0,7017
28
+ mpflash/mpremoteboard/__init__.py,sha256=fJ_N1F6R3CfP9F7pmocb5l8yRvzmSmtHi4u_uTQHR1w,7683
29
29
  mpflash/mpremoteboard/mpy_fw_info.py,sha256=6AQbN3jtQgllqWQYl4e-63KeEtV08EXk8_JnM6XBkvo,4554
30
30
  mpflash/mpremoteboard/runner.py,sha256=-PgzAeBGbyXaAUlwyiw4mcINsP2U1XRRjP1_QdBrxpg,4786
31
31
  mpflash/vendor/dfu.py,sha256=oK_MRSOyDJrUuS6D24IMIsfL7oLcrvUq0yp_h4WIY2U,5739
32
32
  mpflash/vendor/pydfu.py,sha256=_MdBRo1EeNeKDqFPSTB5tNL1jGSBJgsVeVjE5e7Pb8s,20542
33
33
  mpflash/vendor/readme.md,sha256=iIIZxuLUIGHQ0KODzYVtMezsztvyxCXcNJp_AzwTIPk,86
34
34
  mpflash/vendor/versions.py,sha256=ooRZjeeYepQHwp12hMu2m0p8nZXQ5s942w5mGkKmgeI,3629
35
- mpflash/worklist.py,sha256=qZsqF3Lf5Bl7QQ31ZLVHewP6WC8fmwQPMbyNgbG7LB4,5299
36
- mpflash-0.7.4.dist-info/entry_points.txt,sha256=Jk_visOhYOsZIcSP2Ms9hKqfKy1iorR-6dYltSoWCpY,52
37
- mpflash-0.7.4.dist-info/LICENSE,sha256=mWpNhsIxWzetYNnTpr4eb3HtgsxGIC8KcYWxXEcxQvE,1077
38
- mpflash-0.7.4.dist-info/METADATA,sha256=yuYr4EWKmSRD8yg7M6tyfP-EYSt-4UrZjGEycTSUH4o,14633
39
- mpflash-0.7.4.dist-info/WHEEL,sha256=d2fvjOD7sXsVzChCqf0Ty0JbHKBaLYwDbGQDwQTnJ50,88
40
- mpflash-0.7.4.dist-info/RECORD,,
35
+ mpflash/worklist.py,sha256=izHCPR39OYMXydXpLjtjsgaYlNAfrlQz0_joDPmhDJM,5297
36
+ mpflash-0.7.6.dist-info/entry_points.txt,sha256=Jk_visOhYOsZIcSP2Ms9hKqfKy1iorR-6dYltSoWCpY,52
37
+ mpflash-0.7.6.dist-info/LICENSE,sha256=mWpNhsIxWzetYNnTpr4eb3HtgsxGIC8KcYWxXEcxQvE,1077
38
+ mpflash-0.7.6.dist-info/METADATA,sha256=ci7Uf09buIB9AfiBv5NVsO-LFb__tlkF295aZhTtjyU,14633
39
+ mpflash-0.7.6.dist-info/WHEEL,sha256=d2fvjOD7sXsVzChCqf0Ty0JbHKBaLYwDbGQDwQTnJ50,88
40
+ mpflash-0.7.6.dist-info/RECORD,,