micropython-stubber 1.20.6__py3-none-any.whl → 1.23.1__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 (89) hide show
  1. {micropython_stubber-1.20.6.dist-info → micropython_stubber-1.23.1.dist-info}/METADATA +4 -3
  2. micropython_stubber-1.23.1.dist-info/RECORD +159 -0
  3. mpflash/README.md +14 -4
  4. mpflash/mpflash/add_firmware.py +1 -1
  5. mpflash/mpflash/ask_input.py +1 -1
  6. {stubber → mpflash/mpflash}/basicgit.py +3 -13
  7. mpflash/mpflash/bootloader/__init__.py +2 -37
  8. mpflash/mpflash/bootloader/activate.py +60 -0
  9. mpflash/mpflash/bootloader/detect.py +82 -0
  10. mpflash/mpflash/bootloader/manual.py +10 -11
  11. mpflash/mpflash/bootloader/micropython.py +2 -0
  12. mpflash/mpflash/bootloader/touch1200.py +13 -22
  13. mpflash/mpflash/cli_download.py +1 -1
  14. mpflash/mpflash/cli_flash.py +4 -4
  15. mpflash/mpflash/cli_group.py +18 -5
  16. mpflash/mpflash/cli_main.py +3 -5
  17. mpflash/mpflash/common.py +1 -0
  18. mpflash/mpflash/config.py +6 -9
  19. mpflash/mpflash/connected.py +9 -5
  20. mpflash/mpflash/download.py +9 -5
  21. mpflash/mpflash/downloaded.py +4 -3
  22. mpflash/mpflash/{flash.py → flash/__init__.py} +20 -17
  23. mpflash/mpflash/{flash_esp.py → flash/esp.py} +1 -1
  24. mpflash/mpflash/flash/stm32.py +19 -0
  25. mpflash/mpflash/{flash_stm32_dfu.py → flash/stm32_dfu.py} +4 -1
  26. mpflash/mpflash/{flash_uf2.py → flash/uf2/__init__.py} +44 -23
  27. mpflash/mpflash/{flash_uf2_linux.py → flash/uf2/linux.py} +15 -8
  28. mpflash/mpflash/{flash_uf2_macos.py → flash/uf2/macos.py} +13 -5
  29. mpflash/mpflash/{flash_uf2_windows.py → flash/uf2/windows.py} +16 -7
  30. mpflash/mpflash/{worklist.py → flash/worklist.py} +9 -9
  31. mpflash/mpflash/list.py +13 -3
  32. mpflash/mpflash/mpboard_id/__init__.py +1 -1
  33. mpflash/mpflash/mpboard_id/add_boards.py +3 -7
  34. mpflash/mpflash/mpboard_id/board_id.py +1 -1
  35. mpflash/mpflash/mpremoteboard/__init__.py +5 -4
  36. {stubber/utils → mpflash/mpflash}/versions.py +31 -24
  37. mpflash/poetry.lock +16 -5
  38. mpflash/pyproject.toml +8 -3
  39. stubber/__init__.py +1 -1
  40. stubber/board/createstubs.py +4 -4
  41. stubber/board/createstubs_db.py +5 -5
  42. stubber/board/createstubs_db_min.py +1 -1
  43. stubber/board/createstubs_db_mpy.mpy +0 -0
  44. stubber/board/createstubs_mem.py +5 -5
  45. stubber/board/createstubs_mem_min.py +1 -1
  46. stubber/board/createstubs_mem_mpy.mpy +0 -0
  47. stubber/board/createstubs_min.py +1 -1
  48. stubber/board/createstubs_mpy.mpy +0 -0
  49. stubber/codemod/utils.py +2 -3
  50. stubber/commands/build_cmd.py +1 -1
  51. stubber/commands/cli.py +8 -4
  52. stubber/commands/clone_cmd.py +16 -4
  53. stubber/commands/enrich_folder_cmd.py +2 -1
  54. stubber/commands/get_core_cmd.py +5 -3
  55. stubber/commands/get_docstubs_cmd.py +6 -4
  56. stubber/commands/get_frozen_cmd.py +5 -3
  57. stubber/commands/get_mcu_cmd.py +9 -4
  58. stubber/commands/merge_cmd.py +3 -3
  59. stubber/commands/publish_cmd.py +2 -3
  60. stubber/commands/stub_cmd.py +2 -1
  61. stubber/commands/switch_cmd.py +14 -6
  62. stubber/commands/variants_cmd.py +3 -3
  63. stubber/freeze/get_frozen.py +1 -1
  64. stubber/minify.py +4 -3
  65. stubber/publish/candidates.py +4 -10
  66. stubber/publish/defaults.py +4 -9
  67. stubber/publish/missing_class_methods.py +1 -1
  68. stubber/publish/package.py +1 -1
  69. stubber/publish/pathnames.py +1 -1
  70. stubber/publish/publish.py +1 -1
  71. stubber/publish/stubpackage.py +3 -5
  72. stubber/rst/reader.py +5 -13
  73. stubber/tools/manifestfile.py +71 -27
  74. stubber/tools/readme.md +3 -2
  75. stubber/update_fallback.py +1 -1
  76. stubber/utils/__init__.py +2 -1
  77. stubber/utils/config.py +3 -5
  78. stubber/utils/manifest.py +2 -4
  79. stubber/utils/repos.py +5 -12
  80. micropython_stubber-1.20.6.dist-info/RECORD +0 -159
  81. mpflash/mpflash/flash_stm32.py +0 -24
  82. mpflash/mpflash/flash_stm32_cube.py +0 -111
  83. mpflash/mpflash/vendor/versions.py +0 -119
  84. {micropython_stubber-1.20.6.dist-info → micropython_stubber-1.23.1.dist-info}/LICENSE +0 -0
  85. {micropython_stubber-1.20.6.dist-info → micropython_stubber-1.23.1.dist-info}/WHEEL +0 -0
  86. {micropython_stubber-1.20.6.dist-info → micropython_stubber-1.23.1.dist-info}/entry_points.txt +0 -0
  87. /mpflash/{mpflash/vendor/basicgit.py → basicgit.py} +0 -0
  88. /mpflash/mpflash/{flash_uf2_boardid.py → flash/uf2/boardid.py} +0 -0
  89. /mpflash/mpflash/{uf2disk.py → flash/uf2/uf2disk.py} +0 -0
@@ -27,7 +27,7 @@ def cb_verbose(ctx, param, value):
27
27
  return value
28
28
 
29
29
 
30
- def cb_interactive(ctx, param, value:bool):
30
+ def cb_interactive(ctx, param, value: bool):
31
31
  log.trace(f"Setting interactive mode to {value}")
32
32
  config.interactive = value
33
33
  return value
@@ -39,6 +39,10 @@ def cb_test(ctx, param, value):
39
39
  config.tests = value
40
40
  return value
41
41
 
42
+ def cb_usb(ctx, param, value: bool):
43
+ config.usb = bool(value)
44
+ return value
45
+
42
46
 
43
47
  def cb_quiet(ctx, param, value):
44
48
  log.trace(f"Setting quiet mode to {value}")
@@ -78,19 +82,28 @@ def cb_quiet(ctx, param, value):
78
82
  help="Enables verbose mode.",
79
83
  callback=cb_verbose,
80
84
  )
85
+ @click.option(
86
+ "--usb",
87
+ "-u",
88
+ is_eager=True,
89
+ is_flag=True,
90
+ default=False,
91
+ help="Shows USB location of the connected boards.",
92
+ callback=cb_usb,
93
+ show_default=True,
94
+ )
81
95
  @click.option(
82
96
  "--test",
83
97
  is_eager=True,
84
- help="test a specific feature",
98
+ help="Test a specific feature.",
85
99
  callback=cb_test,
86
100
  multiple=True,
87
101
  default=[],
88
102
  envvar="MPFLASH_TEST",
89
- show_default=True,
90
- metavar="TEST",
103
+ metavar="FLAG",
91
104
  )
92
105
  def cli(**kwargs):
93
- """mpflash - MicroPython Tool.
106
+ """mpflash - MicroPython flashing tool.
94
107
 
95
108
  A CLI to download and flash MicroPython firmware to different ports and boards.
96
109
  """
@@ -1,10 +1,8 @@
1
1
  """mpflash is a CLI to download and flash MicroPython firmware to various boards."""
2
2
 
3
- # import rich_click as click
4
-
5
3
  import os
6
4
 
7
- import click
5
+ import click.exceptions as click_exceptions
8
6
  from loguru import logger as log
9
7
 
10
8
  from .cli_download import cli_download
@@ -29,10 +27,10 @@ def mpflash():
29
27
  except AttributeError as e:
30
28
  log.error(f"Error: {e}")
31
29
  exit(-1)
32
- except click.exceptions.ClickException as e:
30
+ except click_exceptions.ClickException as e:
33
31
  log.error(f"Error: {e}")
34
32
  exit(-2)
35
- except click.exceptions.Abort as e:
33
+ except click_exceptions.Abort as e:
36
34
  # Aborted - Ctrl-C
37
35
  exit(-3)
38
36
 
mpflash/mpflash/common.py CHANGED
@@ -92,6 +92,7 @@ class DownloadParams(Params):
92
92
 
93
93
 
94
94
  class BootloaderMethod(Enum):
95
+ AUTO = "auto"
95
96
  MANUAL = "manual"
96
97
  MPY = "mpy"
97
98
  TOUCH_1200 = "touch1200"
mpflash/mpflash/config.py CHANGED
@@ -1,10 +1,10 @@
1
1
  """centralized configuration for mpflash"""
2
2
 
3
3
  import os
4
+ from importlib.metadata import version
4
5
  from pathlib import Path
5
6
  from typing import List
6
7
 
7
- import pkg_resources
8
8
  import platformdirs
9
9
 
10
10
  from mpflash.logger import log
@@ -12,10 +12,7 @@ from mpflash.logger import log
12
12
 
13
13
  def get_version():
14
14
  name = __package__ or "mpflash"
15
- try:
16
- return pkg_resources.get_distribution(name).version
17
- except pkg_resources.DistributionNotFound:
18
- return "Package not found"
15
+ return version(name)
19
16
 
20
17
 
21
18
  class MPtoolConfig:
@@ -23,6 +20,7 @@ class MPtoolConfig:
23
20
 
24
21
  quiet: bool = False
25
22
  verbose: bool = False
23
+ usb: bool = False
26
24
  ignore_ports: List[str] = []
27
25
  firmware_folder: Path = platformdirs.user_downloads_path() / "firmware"
28
26
  # test options specified on the commandline
@@ -32,16 +30,15 @@ class MPtoolConfig:
32
30
  @property
33
31
  def interactive(self):
34
32
  # No interactions in CI
35
- if os.getenv('GITHUB_ACTIONS') == 'true':
33
+ if os.getenv("GITHUB_ACTIONS") == "true":
36
34
  log.warning("Disabling interactive mode in CI")
37
35
  return False
38
36
  return self._interactive
39
-
37
+
40
38
  @interactive.setter
41
- def interactive(self, value:bool):
39
+ def interactive(self, value: bool):
42
40
  self._interactive = value
43
41
 
44
42
 
45
-
46
43
  config = MPtoolConfig()
47
44
  __version__ = get_version()
@@ -9,9 +9,7 @@ from mpflash.mpremoteboard import MPRemoteBoard
9
9
  from .common import filtered_comports
10
10
 
11
11
 
12
- def connected_ports_boards(
13
- *, include: List[str], ignore: List[str]
14
- ) -> Tuple[List[str], List[str], List[MPRemoteBoard]]:
12
+ def connected_ports_boards(*, include: List[str], ignore: List[str]) -> Tuple[List[str], List[str], List[MPRemoteBoard]]:
15
13
  """
16
14
  Returns a tuple containing lists of unique ports and boards from the connected MCUs.
17
15
  Boards that are physically connected, but give no tangible response are ignored.
@@ -50,11 +48,17 @@ def list_mcus(*, ignore: List[str], include: List[str], bluetooth: bool = False)
50
48
  include=include,
51
49
  bluetooth=bluetooth,
52
50
  )
53
- conn_mcus = [MPRemoteBoard(c.device) for c in comports]
51
+ conn_mcus = [MPRemoteBoard(c.device, location=c.location or "?") for c in comports]
54
52
 
55
53
  # a lot of boilerplate to show a progress bar with the comport currently scanned
56
54
  # low update rate to facilitate screen readers/narration
57
- with Progress(rp_spinner, rp_text, rp_bar, TimeElapsedColumn(), refresh_per_second=2) as progress:
55
+ with Progress(
56
+ rp_spinner,
57
+ rp_text,
58
+ rp_bar,
59
+ TimeElapsedColumn(),
60
+ refresh_per_second=1,
61
+ ) as progress:
58
62
  tsk_scan = progress.add_task("[green]Scanning", visible=False, total=None)
59
63
  progress.tasks[tsk_scan].fields["device"] = "..."
60
64
  progress.tasks[tsk_scan].visible = True
@@ -21,7 +21,7 @@ from rich.progress import track
21
21
  from mpflash.common import PORT_FWTYPES, FWInfo
22
22
  from mpflash.errors import MPFlashError
23
23
  from mpflash.mpboard_id import get_known_ports
24
- from mpflash.vendor.versions import clean_version
24
+ from mpflash.versions import clean_version
25
25
 
26
26
  # avoid conflict with the ujson used by MicroPython
27
27
  jsonlines.ujson = None # type: ignore
@@ -134,7 +134,13 @@ def get_boards(ports: List[str], boards: List[str], clean: bool) -> List[FWInfo]
134
134
  for board in urls:
135
135
  board["port"] = port
136
136
 
137
- for board in track(urls, description=f"Checking {port} download pages", transient=True, refresh_per_second=2):
137
+ for board in track(
138
+ urls,
139
+ description=f"Checking {port} download pages",
140
+ transient=True,
141
+ refresh_per_second=1,
142
+ show_speed=False,
143
+ ):
138
144
  # add a board to the list for each firmware found
139
145
  firmware_urls: List[str] = []
140
146
  for ext in PORT_FWTYPES[port]:
@@ -273,9 +279,7 @@ def get_firmware_list(ports: List[str], boards: List[str], versions: List[str],
273
279
  log.debug(f"Total {len(board_urls)} firmwares")
274
280
 
275
281
  relevant = [
276
- board
277
- for board in board_urls
278
- if board.version in versions and board.build == "0" and board.board in boards and not board.preview
282
+ board for board in board_urls if board.version in versions and board.build == "0" and board.board in boards and not board.preview
279
283
  ]
280
284
 
281
285
  if preview:
@@ -4,8 +4,8 @@ from typing import Dict, List, Optional
4
4
  import jsonlines
5
5
  from loguru import logger as log
6
6
 
7
- from mpflash.common import FWInfo
8
- from mpflash.vendor.versions import clean_version
7
+ from mpflash.common import PORT_FWTYPES, FWInfo
8
+ from mpflash.versions import clean_version
9
9
 
10
10
  from .config import config
11
11
 
@@ -109,7 +109,7 @@ def filter_downloaded_fwlist(
109
109
  log.trace(f"Filtering firmware for {version} : {len(fw_list)} found.")
110
110
  # filter by port
111
111
  if port:
112
- fw_list = [fw for fw in fw_list if fw.port == port]
112
+ fw_list = [fw for fw in fw_list if fw.port == port and Path(fw.firmware).suffix in PORT_FWTYPES[port]]
113
113
  log.trace(f"Filtering firmware for {port} : {len(fw_list)} found.")
114
114
 
115
115
  if board_id:
@@ -120,6 +120,7 @@ def filter_downloaded_fwlist(
120
120
  # the firmware variant should match exactly the board_id
121
121
  fw_list = [fw for fw in fw_list if fw.variant == board_id]
122
122
  log.trace(f"Filtering firmware for {board_id} : {len(fw_list)} found.")
123
+
123
124
  if selector and port in selector:
124
125
  fw_list = [fw for fw in fw_list if fw.filename.endswith(selector[port])]
125
126
  return fw_list
@@ -5,9 +5,9 @@ from loguru import logger as log
5
5
  from mpflash.bootloader import enter_bootloader
6
6
  from mpflash.common import PORT_FWTYPES, BootloaderMethod
7
7
 
8
- from .flash_esp import flash_esp
9
- from .flash_stm32 import flash_stm32
10
- from .flash_uf2 import flash_uf2
8
+ from .esp import flash_esp
9
+ from .stm32 import flash_stm32
10
+ from .uf2 import flash_uf2
11
11
  from .worklist import WorkList
12
12
 
13
13
  # #########################################################################################################
@@ -30,20 +30,23 @@ def flash_list(
30
30
  continue
31
31
  log.info(f"Updating {mcu.board} on {mcu.serialport} to {fw_info.version}")
32
32
  updated = None
33
- # try:
34
- if mcu.port in UF2_PORTS and fw_file.suffix == ".uf2":
35
- if not enter_bootloader(mcu, bootloader):
36
- continue
37
- updated = flash_uf2(mcu, fw_file=fw_file, erase=erase)
38
- elif mcu.port in ["stm32"]:
39
- if not enter_bootloader(mcu, bootloader):
40
- continue
41
- updated = flash_stm32(mcu, fw_file, erase=erase)
42
- elif mcu.port in ["esp32", "esp8266"]:
43
- # bootloader is handled by esptool for esp32/esp8266
44
- updated = flash_esp(mcu, fw_file=fw_file, erase=erase)
45
- else:
46
- log.error(f"Don't (yet) know how to flash {mcu.port}-{mcu.board} on {mcu.serialport}")
33
+ try:
34
+ if mcu.port in UF2_PORTS and fw_file.suffix == ".uf2":
35
+ if not enter_bootloader(mcu, bootloader):
36
+ continue
37
+ updated = flash_uf2(mcu, fw_file=fw_file, erase=erase)
38
+ elif mcu.port in ["stm32"]:
39
+ if not enter_bootloader(mcu, bootloader):
40
+ continue
41
+ updated = flash_stm32(mcu, fw_file, erase=erase)
42
+ elif mcu.port in ["esp32", "esp8266"]:
43
+ # bootloader is handled by esptool for esp32/esp8266
44
+ updated = flash_esp(mcu, fw_file=fw_file, erase=erase)
45
+ else:
46
+ log.error(f"Don't (yet) know how to flash {mcu.port}-{mcu.board} on {mcu.serialport}")
47
+ except Exception as e:
48
+ log.error(f"Failed to flash {mcu.board} on {mcu.serialport}")
49
+ log.exception(e)
47
50
 
48
51
  if updated:
49
52
  flashed.append(updated)
@@ -53,7 +53,7 @@ def flash_esp(mcu: MPRemoteBoard, fw_file: Path, *, erase: bool = True) -> Optio
53
53
  log.error(f"Failed to flash {mcu.board} on {mcu.serialport} : {e}")
54
54
  return None
55
55
 
56
- log.info("Done flashing, resetting the board and wait for it to restart")
56
+ log.info("Done flashing, resetting the board...")
57
57
  mcu.wait_for_restart()
58
58
  log.success(f"Flashed {mcu.serialport} to {mcu.board} {mcu.version}")
59
59
  return mcu
@@ -0,0 +1,19 @@
1
+ """Flash STM32 boards using pydfu"""
2
+
3
+ from pathlib import Path
4
+
5
+ from loguru import logger as log
6
+
7
+ from mpflash.mpremoteboard import MPRemoteBoard
8
+
9
+ # from .flash_stm32_cube import flash_stm32_cubecli
10
+ from .stm32_dfu import dfu_init, flash_stm32_dfu
11
+
12
+
13
+ def flash_stm32(mcu: MPRemoteBoard, fw_file: Path, *, erase: bool):
14
+ # sourcery skip: lift-return-into-if
15
+ dfu_init()
16
+ if updated := flash_stm32_dfu(mcu, fw_file=fw_file, erase=erase):
17
+ mcu.wait_for_restart()
18
+ log.success(f"Flashed {mcu.version} to {mcu.board}")
19
+ return updated
@@ -26,7 +26,7 @@ def init_libusb_windows() -> bool:
26
26
 
27
27
 
28
28
  try:
29
- from .vendor import pydfu as pydfu
29
+ from ..vendor import pydfu as pydfu
30
30
  except ImportError:
31
31
  pydfu = None
32
32
 
@@ -39,6 +39,7 @@ def dfu_init():
39
39
  log.error("pydfu not found")
40
40
  return None
41
41
  if platform.system() == "Windows":
42
+ log.debug("Initializing libusb backend on Windows...")
42
43
  init_libusb_windows()
43
44
 
44
45
 
@@ -59,6 +60,8 @@ def flash_stm32_dfu(
59
60
  Returns:
60
61
  Optional[MPRemoteBoard]: The flashed remote board if successful, None otherwise.
61
62
  """
63
+ log.info("Using pydfu to flash STM32 boards")
64
+
62
65
  if not pydfu:
63
66
  log.error("pydfu not found, please install it with 'pip install pydfu' if supported")
64
67
  return None
@@ -4,20 +4,21 @@ Flash SAMD and RP2 via UF2
4
4
 
5
5
  import shutil
6
6
  import sys
7
- import time
8
7
  from pathlib import Path
9
8
  from typing import Optional
10
9
 
10
+ import tenacity
11
11
  from loguru import logger as log
12
- from rich.progress import track
12
+
13
+ from tenacity import stop_after_attempt, wait_fixed
13
14
 
14
15
  from mpflash.mpremoteboard import MPRemoteBoard
15
16
 
16
- from .common import PORT_FWTYPES
17
- from .flash_uf2_boardid import get_board_id
18
- from .flash_uf2_linux import dismount_uf2_linux, wait_for_UF2_linux
19
- from .flash_uf2_macos import wait_for_UF2_macos
20
- from .flash_uf2_windows import wait_for_UF2_windows
17
+ from mpflash.common import PORT_FWTYPES
18
+ from .boardid import get_board_id
19
+ from .linux import dismount_uf2_linux, wait_for_UF2_linux
20
+ from .macos import wait_for_UF2_macos
21
+ from .windows import wait_for_UF2_windows
21
22
 
22
23
 
23
24
  def flash_uf2(mcu: MPRemoteBoard, fw_file: Path, erase: bool) -> Optional[MPRemoteBoard]:
@@ -37,18 +38,9 @@ def flash_uf2(mcu: MPRemoteBoard, fw_file: Path, erase: bool) -> Optional[MPRemo
37
38
  log.error(f"UF2 not supported on {mcu.board} on {mcu.serialport}")
38
39
  return None
39
40
  if erase:
40
- log.info("Erasing not yet implemented for UF2 flashing.")
41
+ log.warning("Erase not (yet) supported on .UF2, skipping erase.")
41
42
 
42
- if sys.platform == "linux":
43
- destination = wait_for_UF2_linux()
44
- elif sys.platform == "win32":
45
- destination = wait_for_UF2_windows()
46
- elif sys.platform == "darwin":
47
- log.warning(f"OS {sys.platform} not tested/supported")
48
- destination = wait_for_UF2_macos()
49
- else:
50
- log.warning(f"OS {sys.platform} not tested/supported")
51
- return None
43
+ destination = waitfor_uf2(board_id=mcu.port.upper())
52
44
 
53
45
  if not destination or not destination.exists() or not (destination / "INFO_UF2.TXT").exists():
54
46
  log.error("Board is not in bootloader mode")
@@ -57,11 +49,40 @@ def flash_uf2(mcu: MPRemoteBoard, fw_file: Path, erase: bool) -> Optional[MPRemo
57
49
  log.info("Board is in bootloader mode")
58
50
  board_id = get_board_id(destination) # type: ignore
59
51
  log.info(f"Board ID: {board_id}")
60
- log.info(f"Copying {fw_file} to {destination}.")
61
- shutil.copy(fw_file, destination)
62
- log.success("Done copying, resetting the board and wait for it to restart")
52
+ try:
53
+ copy_firmware_to_uf2(fw_file, destination)
54
+ log.success("Done copying, resetting the board.")
55
+ except tenacity.RetryError:
56
+ log.error("Failed to copy the firmware file to the board.")
57
+ return None
58
+
63
59
  if sys.platform in ["linux"]:
64
60
  dismount_uf2_linux()
65
- for _ in track(range(5 + 2), description="Waiting for the board to restart", transient=True, refresh_per_second=2):
66
- time.sleep(1) # 5 secs to short on linux
61
+
62
+ mcu.wait_for_restart()
67
63
  return mcu
64
+
65
+
66
+ def waitfor_uf2(board_id: str):
67
+ """
68
+ Wait for the UF2 drive to mount
69
+ """
70
+ if sys.platform == "linux":
71
+ return wait_for_UF2_linux(board_id=board_id)
72
+ elif sys.platform == "win32":
73
+ return wait_for_UF2_windows(board_id=board_id)
74
+ elif sys.platform == "darwin":
75
+ return wait_for_UF2_macos(board_id=board_id)
76
+ else:
77
+ log.warning(f"OS {sys.platform} not tested/supported")
78
+ return None
79
+
80
+
81
+ @tenacity.retry(stop=stop_after_attempt(3), wait=wait_fixed(1), reraise=False)
82
+ def copy_firmware_to_uf2(fw_file: Path, destination: Path):
83
+ """
84
+ Copy the firmware file to the destination,
85
+ Retry 3 times with 1s delay
86
+ """
87
+ log.info(f"Copying {fw_file} to {destination}.")
88
+ return shutil.copy(fw_file, destination)
@@ -12,7 +12,7 @@ from typing import List
12
12
  from loguru import logger as log
13
13
  from rich.progress import track
14
14
 
15
- from .flash_uf2_boardid import get_board_id
15
+ from .boardid import get_board_id
16
16
  from .uf2disk import UF2Disk
17
17
 
18
18
  glb_dismount_me: List[UF2Disk] = []
@@ -70,7 +70,7 @@ def pmount(disk: UF2Disk):
70
70
  log.debug(f"Mounted {disk.label} at {disk.mountpoint}")
71
71
  glb_dismount_me.append(disk)
72
72
  else:
73
- log.debug(f"\n{disk.label} already mounted at {disk.mountpoint}")
73
+ log.trace(f"\n{disk.label} already mounted at {disk.mountpoint}")
74
74
 
75
75
 
76
76
  def pumount(disk: UF2Disk):
@@ -95,24 +95,31 @@ def dismount_uf2_linux():
95
95
  glb_dismount_me = []
96
96
 
97
97
 
98
- def wait_for_UF2_linux(s_max: int = 10):
98
+ def wait_for_UF2_linux(board_id: str, s_max: int = 10):
99
99
  destination = ""
100
100
  wait = 10
101
101
  uf2_drives = []
102
102
  # while not destination and wait > 0:
103
103
  for _ in track(
104
- range(s_max), description="Waiting for mcu to mount as a drive", transient=True, refresh_per_second=2
104
+ range(s_max),
105
+ description=f"Waiting for mcu to mount as a drive ({s_max}s)",
106
+ transient=True,
107
+ show_speed=False,
108
+ refresh_per_second=1,
109
+ total=s_max,
105
110
  ):
106
- # log.info(f"Waiting for mcu to mount as a drive : {wait} seconds left")
107
111
  uf2_drives += list(get_uf2_drives())
108
112
  for drive in get_uf2_drives():
109
113
  pmount(drive)
110
114
  time.sleep(1)
111
115
  try:
112
116
  if Path(drive.mountpoint, "INFO_UF2.TXT").exists():
113
- board_id = get_board_id(Path(drive.mountpoint)) # type: ignore
114
- destination = Path(drive.mountpoint)
115
- break
117
+ this_board_id = get_board_id(Path(drive.mountpoint))
118
+ if not board_id or board_id.upper() in this_board_id.upper():
119
+ # is it the correct board?
120
+ destination = Path(drive.mountpoint)
121
+ break
122
+ continue
116
123
  except PermissionError:
117
124
  log.debug(f"Permission error on {drive.mountpoint}")
118
125
  continue
@@ -9,23 +9,31 @@ from typing import Optional
9
9
 
10
10
  from rich.progress import track
11
11
 
12
+ from .boardid import get_board_id
12
13
 
13
14
 
14
- def wait_for_UF2_macos(s_max: int = 10) -> Optional[Path]:
15
+ def wait_for_UF2_macos(board_id: str, s_max: int = 10) -> Optional[Path]:
15
16
  """Wait for the MCU to mount as a drive"""
16
17
  if s_max < 1:
17
18
  s_max = 10
18
19
  destination = None
19
20
  for _ in track(
20
- range(s_max), description="Waiting for mcu to mount as a drive", transient=True, refresh_per_second=2
21
+ range(s_max),
22
+ description=f"Waiting for mcu to mount as a drive ({s_max}s)",
23
+ transient=True,
24
+ show_speed=False,
25
+ refresh_per_second=1,
26
+ total=s_max,
21
27
  ):
22
- # log.info(f"Waiting for mcu to mount as a drive : {n} seconds left")
23
28
  vol_mounts = Path("/Volumes").iterdir()
24
29
  for vol in vol_mounts:
25
30
  try:
26
31
  if Path(vol, "INFO_UF2.TXT").exists():
27
- destination = Path(vol)
28
- break
32
+ this_board_id = get_board_id(Path(vol))
33
+ if not board_id or board_id.upper() in this_board_id.upper():
34
+ destination = Path(vol)
35
+ break
36
+ continue
29
37
  except OSError:
30
38
  pass
31
39
  if destination:
@@ -7,25 +7,34 @@ import time
7
7
  from pathlib import Path
8
8
  from typing import Optional
9
9
 
10
+ from .boardid import get_board_id
10
11
  import psutil
11
12
  from rich.progress import track
12
13
 
13
14
 
14
-
15
-
16
- def wait_for_UF2_windows(s_max: int = 10) -> Optional[Path]:
15
+ def wait_for_UF2_windows(board_id: str, s_max: int = 10)-> Optional[Path]:
17
16
  """Wait for the MCU to mount as a drive"""
18
17
  if s_max < 1:
19
18
  s_max = 10
20
19
  destination = None
21
- for _ in track(range(s_max), description="Waiting for mcu to mount as a drive", transient=True,refresh_per_second=2):
22
- # log.info(f"Waiting for mcu to mount as a drive : {n} seconds left")
20
+ for _ in track(
21
+ range(s_max),
22
+ description=f"Waiting for mcu to mount as a drive ({s_max}s)",
23
+ transient=True,
24
+ show_speed=False,
25
+ refresh_per_second=1,
26
+ total=s_max,
27
+ ):
23
28
  drives = [drive.device for drive in psutil.disk_partitions()]
24
29
  for drive in drives:
25
30
  try:
26
31
  if Path(drive, "INFO_UF2.TXT").exists():
27
- destination = Path(drive)
28
- break
32
+ this_board_id = get_board_id(Path(drive))
33
+ if not board_id or board_id.upper() in this_board_id.upper():
34
+ # is it the correct board?
35
+ destination = Path(drive)
36
+ break
37
+ continue
29
38
  except OSError:
30
39
  pass
31
40
  if destination:
@@ -1,15 +1,16 @@
1
+ """Worklist for updating boards"""
2
+
1
3
  from pathlib import Path
2
4
  from typing import Dict, List, Optional, Tuple
3
5
 
4
6
  from loguru import logger as log
5
7
 
6
8
  from mpflash.common import FWInfo, filtered_comports
9
+ from mpflash.downloaded import find_downloaded_firmware
7
10
  from mpflash.errors import MPFlashError
8
-
9
- from .downloaded import find_downloaded_firmware
10
- from .list import show_mcus
11
- from .mpboard_id import find_known_board
12
- from .mpremoteboard import MPRemoteBoard
11
+ from mpflash.list import show_mcus
12
+ from mpflash.mpboard_id import find_known_board
13
+ from mpflash.mpremoteboard import MPRemoteBoard
13
14
 
14
15
  # #########################################################################################################
15
16
  WorkList = List[Tuple[MPRemoteBoard, FWInfo]]
@@ -39,9 +40,7 @@ def auto_update(
39
40
  wl: WorkList = []
40
41
  for mcu in conn_boards:
41
42
  if mcu.family not in ("micropython", "unknown"):
42
- log.warning(
43
- f"Skipping flashing {mcu.family} {mcu.port} {mcu.board} on {mcu.serialport} as it is not a MicroPython firmware"
44
- )
43
+ log.warning(f"Skipping flashing {mcu.family} {mcu.port} {mcu.board} on {mcu.serialport} as it is not a MicroPython firmware")
45
44
  continue
46
45
  board_firmwares = find_downloaded_firmware(
47
46
  fw_folder=fw_folder,
@@ -54,8 +53,9 @@ def auto_update(
54
53
  if not board_firmwares:
55
54
  log.error(f"No {target_version} firmware found for {mcu.board} on {mcu.serialport}.")
56
55
  continue
56
+
57
57
  if len(board_firmwares) > 1:
58
- log.debug(f"Multiple {target_version} firmwares found for {mcu.board} on {mcu.serialport}.")
58
+ log.warning(f"Multiple {target_version} firmwares found for {mcu.board} on {mcu.serialport}.")
59
59
 
60
60
  # just use the last firmware
61
61
  fw_info = board_firmwares[-1]
mpflash/mpflash/list.py CHANGED
@@ -3,8 +3,9 @@ from typing import List
3
3
  from rich.progress import track
4
4
  from rich.table import Table
5
5
 
6
+ from mpflash.config import config
6
7
  from mpflash.mpremoteboard import MPRemoteBoard
7
- from mpflash.vendor.versions import clean_version
8
+ from mpflash.versions import clean_version
8
9
 
9
10
  from .logger import console
10
11
 
@@ -64,8 +65,15 @@ def mcu_table(
64
65
  table.add_column("Version", overflow="fold", min_width=5, max_width=16)
65
66
  if needs_build:
66
67
  table.add_column("Build" if is_wide else "Bld", justify="right")
67
-
68
- for mcu in track(conn_mcus, description="Updating board info", transient=True, refresh_per_second=2):
68
+ if config.usb:
69
+ table.add_column("Location", overflow="fold", max_width=40)
70
+ for mcu in track(
71
+ conn_mcus,
72
+ description="Updating board info",
73
+ transient=True,
74
+ show_speed=False,
75
+ refresh_per_second=1,
76
+ ):
69
77
  if refresh:
70
78
  try:
71
79
  mcu.get_mcu_info()
@@ -84,6 +92,8 @@ def mcu_table(
84
92
  row.append(clean_version(mcu.version))
85
93
  if needs_build:
86
94
  row.append(mcu.build)
95
+ if config.usb:
96
+ row.append(mcu.location)
87
97
 
88
98
  table.add_row(*row)
89
99
  return table
@@ -10,7 +10,7 @@ from typing import List, Optional, Tuple
10
10
  from mpflash.errors import MPFlashError
11
11
  from mpflash.mpboard_id.board import Board
12
12
  from mpflash.mpboard_id.store import read_known_boardinfo
13
- from mpflash.vendor.versions import clean_version
13
+ from mpflash.versions import clean_version
14
14
 
15
15
  # KNOWN ports and boards are sourced from the micropython repo,
16
16
  # this info is stored in the board_info.json file