mpflash 1.24.6__py3-none-any.whl → 1.24.8__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 (48) hide show
  1. mpflash/ask_input.py +7 -7
  2. mpflash/basicgit.py +23 -57
  3. mpflash/bootloader/__init__.py +0 -2
  4. mpflash/bootloader/activate.py +1 -1
  5. mpflash/bootloader/detect.py +1 -2
  6. mpflash/bootloader/manual.py +0 -1
  7. mpflash/bootloader/touch1200.py +2 -2
  8. mpflash/cli_flash.py +28 -5
  9. mpflash/cli_group.py +1 -0
  10. mpflash/cli_list.py +2 -2
  11. mpflash/cli_main.py +1 -1
  12. mpflash/common.py +6 -14
  13. mpflash/config.py +26 -7
  14. mpflash/connected.py +6 -14
  15. mpflash/download.py +56 -23
  16. mpflash/downloaded.py +1 -5
  17. mpflash/flash/__init__.py +33 -18
  18. mpflash/flash/esp.py +40 -9
  19. mpflash/flash/uf2/__init__.py +18 -2
  20. mpflash/flash/uf2/linux.py +4 -9
  21. mpflash/flash/uf2/macos.py +1 -1
  22. mpflash/flash/uf2/windows.py +1 -1
  23. mpflash/flash/worklist.py +7 -2
  24. mpflash/list.py +17 -6
  25. mpflash/logger.py +1 -3
  26. mpflash/mpboard_id/__init__.py +6 -87
  27. mpflash/mpboard_id/add_boards.py +6 -8
  28. mpflash/mpboard_id/board_id.py +7 -6
  29. mpflash/mpboard_id/board_info.json +30974 -0
  30. mpflash/mpboard_id/board_info.zip +0 -0
  31. mpflash/mpboard_id/known.py +94 -0
  32. mpflash/mpboard_id/store.py +0 -2
  33. mpflash/mpremoteboard/__init__.py +13 -9
  34. mpflash/mpremoteboard/mpy_fw_info.py +14 -17
  35. mpflash/py.typed +0 -0
  36. mpflash/vendor/click_aliases.py +64 -0
  37. mpflash/vendor/dfu.py +2 -8
  38. mpflash/vendor/pico-universal-flash-nuke/LICENSE.txt +21 -0
  39. mpflash/vendor/pico-universal-flash-nuke/universal_flash_nuke.uf2 +0 -0
  40. mpflash/vendor/pydfu.py +3 -14
  41. mpflash/vendor/readme.md +2 -0
  42. mpflash/versions.py +9 -6
  43. {mpflash-1.24.6.dist-info → mpflash-1.24.8.dist-info}/METADATA +71 -13
  44. mpflash-1.24.8.dist-info/RECORD +59 -0
  45. {mpflash-1.24.6.dist-info → mpflash-1.24.8.dist-info}/WHEEL +1 -1
  46. mpflash-1.24.6.dist-info/RECORD +0 -54
  47. {mpflash-1.24.6.dist-info → mpflash-1.24.8.dist-info}/LICENSE +0 -0
  48. {mpflash-1.24.6.dist-info → mpflash-1.24.8.dist-info}/entry_points.txt +0 -0
mpflash/download.py CHANGED
@@ -13,12 +13,11 @@ from urllib.parse import urljoin
13
13
  # #########################################################################################################
14
14
  # make sure that jsonlines does not mistake the MicroPython ujson for the CPython ujson
15
15
  import jsonlines
16
- import requests
17
- from bs4 import BeautifulSoup
18
16
  from loguru import logger as log
19
17
  from rich.progress import track
20
18
 
21
19
  from mpflash.common import PORT_FWTYPES, FWInfo
20
+ from mpflash.downloaded import clean_downloaded_firmwares
22
21
  from mpflash.errors import MPFlashError
23
22
  from mpflash.mpboard_id import get_known_ports
24
23
  from mpflash.versions import clean_version
@@ -51,6 +50,8 @@ RE_VERSION_PREVIEW = r"v([\d\.]+)-?(?:preview\.)?(\d+)?\."
51
50
  @functools.lru_cache(maxsize=500)
52
51
  def get_page(page_url: str) -> str:
53
52
  """Get the HTML of a page and return it as a string."""
53
+ # Just in time import
54
+ import requests
54
55
  response = requests.get(page_url)
55
56
  return response.content.decode()
56
57
 
@@ -68,14 +69,17 @@ def get_board_urls(page_url: str) -> List[Dict[str, str]]:
68
69
  List[Dict[str, str]]: A list of dictionaries containing the board name and url.
69
70
 
70
71
  """
72
+ # Just in time import
73
+ from bs4 import BeautifulSoup
74
+
71
75
  downloads_html = get_page(page_url)
72
76
  soup = BeautifulSoup(downloads_html, "html.parser")
73
- tags = soup.findAll("a", recursive=True, attrs={"class": "board-card"})
77
+ tags = soup.find_all("a", recursive=True, attrs={"class": "board-card"})
74
78
  # assumes that all links are relative to the page url
75
- boards = [tag.get("href") for tag in tags]
79
+ boards = [tag.get("href") for tag in tags] # type: ignore
76
80
  if "?" in page_url:
77
81
  page_url = page_url.split("?")[0]
78
- return [{"board": board, "url": page_url + board} for board in boards]
82
+ return [{"board": board, "url": page_url + board} for board in boards] # type: ignore
79
83
 
80
84
 
81
85
  def board_firmware_urls(board_url: str, base_url: str, ext: str) -> List[str]:
@@ -89,19 +93,22 @@ def board_firmware_urls(board_url: str, base_url: str, ext: str) -> List[str]:
89
93
  the urls are relative urls to the site root
90
94
 
91
95
  """
96
+ # Just in time import
97
+ from bs4 import BeautifulSoup
98
+
92
99
  html = get_page(board_url)
93
100
  soup = BeautifulSoup(html, "html.parser")
94
101
  # get all the a tags:
95
102
  # 1. that have a url that starts with `/resources/firmware/`
96
103
  # 2. end with a matching extension for this port.
97
- tags = soup.findAll(
104
+ tags = soup.find_all(
98
105
  "a",
99
106
  recursive=True,
100
107
  attrs={"href": re.compile(r"^/resources/firmware/.*\." + ext.lstrip(".") + "$")},
101
108
  )
102
109
  if "?" in base_url:
103
110
  base_url = base_url.split("?")[0]
104
- links: List = [urljoin(base_url, tag.get("href")) for tag in tags]
111
+ links: List = [urljoin(base_url, tag.get("href")) for tag in tags] # type: ignore
105
112
  return links
106
113
 
107
114
 
@@ -211,19 +218,21 @@ def download_firmwares(
211
218
  force : A flag indicating whether to force the download even if the firmware file already exists.
212
219
  clean : A flag indicating to clean the date from the firmware filename.
213
220
  """
221
+
222
+
214
223
  skipped = downloaded = 0
215
224
  versions = [] if versions is None else [clean_version(v) for v in versions]
216
225
  # handle renamed boards
217
226
  boards = add_renamed_boards(boards)
218
227
 
219
- unique_boards = get_firmware_list(ports, boards, versions, clean)
228
+ available_firmwares = get_firmware_list(ports, boards, versions, clean)
220
229
 
221
- for b in unique_boards:
230
+ for b in available_firmwares:
222
231
  log.debug(b.filename)
223
232
  # relevant
224
233
 
225
- log.info(f"Found {len(unique_boards)} relevant unique firmwares")
226
- if not unique_boards:
234
+ log.info(f"Found {len(available_firmwares)} relevant unique firmwares")
235
+ if not available_firmwares:
227
236
  log.error("No relevant firmwares could be found on https://micropython.org/download")
228
237
  log.info(f"{versions=} {ports=} {boards=}")
229
238
  log.info("Please check the website for the latest firmware files or try the preview version.")
@@ -231,8 +240,27 @@ def download_firmwares(
231
240
 
232
241
  firmware_folder.mkdir(exist_ok=True)
233
242
 
243
+ downloaded, skipped = download_firmware_files(available_firmwares, firmware_folder, force )
244
+ log.success(
245
+ f"Downloaded {downloaded} firmware images{f', skipped {str(skipped)} existing' if skipped else ''}."
246
+ )
247
+ return downloaded + skipped
248
+
249
+ def download_firmware_files(available_firmwares :List[FWInfo],firmware_folder:Path, force:bool ):
250
+ """
251
+ Downloads the firmware files to the specified folder.
252
+ Args:
253
+ firmware_folder : The folder to save the downloaded firmware files.
254
+ force : A flag indicating whether to force the download even if the firmware file already exists.
255
+ requests : The requests module to use for downloading the firmware files.
256
+ unique_boards : The list of unique firmware information to download.
257
+ """
258
+ # Just in time import
259
+ import requests
260
+
234
261
  with jsonlines.open(firmware_folder / "firmware.jsonl", "a") as writer:
235
- for board in unique_boards:
262
+ downloaded = skipped = 0
263
+ for board in available_firmwares:
236
264
  filename = firmware_folder / board.port / board.filename
237
265
  filename.parent.mkdir(exist_ok=True)
238
266
  if filename.exists() and not force:
@@ -249,23 +277,23 @@ def download_firmwares(
249
277
  except requests.RequestException as e:
250
278
  log.exception(e)
251
279
  continue
252
- writer.write(board.to_dict())
280
+ writer.write(board.to_dict()) # type: ignore
253
281
  downloaded += 1
254
- # if downloaded > 0:
255
- # clean_downloaded_firmwares(firmware_folder)
256
- log.success(f"Downloaded {downloaded} firmwares, skipped {skipped} existing files.")
257
- return downloaded + skipped
282
+ if downloaded > 0:
283
+ clean_downloaded_firmwares(firmware_folder)
284
+ return downloaded,skipped
258
285
 
259
286
 
260
- def get_firmware_list(ports: List[str], boards: List[str], versions: List[str], clean: bool):
287
+ def get_firmware_list(ports: List[str], boards: List[str], versions: List[str], clean: bool = True):
261
288
  """
262
- Retrieves a list of unique firmware information based on the specified ports, boards, versions, and clean flag.
289
+ Retrieves a list of unique firmware files available om micropython.org > downloads
290
+ based on the specified ports, boards, versions, and clean flag.
263
291
 
264
292
  Args:
265
- ports : The list of ports to check for firmware.
266
- boards : The list of boards to filter the firmware by.
267
- versions : The list of versions to filter the firmware by.
268
- clean : A flag indicating whether to perform a clean check.
293
+ ports : One or more ports to check for firmware.
294
+ boards : One or more boards to filter the firmware by.
295
+ versions : One or more versions to filter the firmware by.
296
+ clean : Remove date-stamp and Git Hash from the firmware name.
269
297
 
270
298
  Returns:
271
299
  List[FWInfo]: A list of unique firmware information.
@@ -273,7 +301,9 @@ def get_firmware_list(ports: List[str], boards: List[str], versions: List[str],
273
301
  """
274
302
 
275
303
  log.trace("Checking MicroPython download pages")
304
+ versions = [clean_version(v, drop_v=False) for v in versions]
276
305
  preview = "preview" in versions
306
+
277
307
  board_urls = sorted(get_boards(ports, boards, clean), key=key_fw_ver_pre_ext_bld)
278
308
 
279
309
  log.debug(f"Total {len(board_urls)} firmwares")
@@ -321,6 +351,9 @@ def download(
321
351
  MPFlashError : If no boards are found or specified.
322
352
 
323
353
  """
354
+ # Just in time import
355
+ import requests
356
+
324
357
  if not boards:
325
358
  log.critical("No boards found, please connect a board or specify boards to download firmware for.")
326
359
  raise MPFlashError("No boards found")
mpflash/downloaded.py CHANGED
@@ -113,11 +113,7 @@ def filter_downloaded_fwlist(
113
113
  log.trace(f"Filtering firmware for {version} : {len(fw_list)} found.")
114
114
  # filter by port
115
115
  if port:
116
- fw_list = [
117
- fw
118
- for fw in fw_list
119
- if fw.port == port and Path(fw.firmware).suffix in PORT_FWTYPES[port]
120
- ]
116
+ fw_list = [fw for fw in fw_list if fw.port == port and Path(fw.firmware).suffix in PORT_FWTYPES[port]]
121
117
  log.trace(f"Filtering firmware for {port} : {len(fw_list)} found.")
122
118
 
123
119
  if board_id:
mpflash/flash/__init__.py CHANGED
@@ -2,8 +2,9 @@ from pathlib import Path
2
2
 
3
3
  from loguru import logger as log
4
4
 
5
- from mpflash.bootloader import enter_bootloader
6
- from mpflash.common import PORT_FWTYPES, BootloaderMethod
5
+ from mpflash.bootloader.activate import enter_bootloader
6
+ from mpflash.common import PORT_FWTYPES, BootloaderMethod, UF2_PORTS
7
+ from mpflash.errors import MPFlashError
7
8
 
8
9
  from .esp import flash_esp
9
10
  from .stm32 import flash_stm32
@@ -12,44 +13,58 @@ from .worklist import WorkList
12
13
 
13
14
  # #########################################################################################################
14
15
 
15
-
16
-
17
16
  def flash_list(
18
17
  todo: WorkList,
19
18
  fw_folder: Path,
20
19
  erase: bool,
21
20
  bootloader: BootloaderMethod,
22
- ):
21
+ **kwargs
22
+ ): # sourcery skip: use-named-expression
23
23
  """Flash a list of boards with the specified firmware."""
24
- UF2_PORTS = [port for port, exts in PORT_FWTYPES.items() if ".uf2" in exts]
25
24
  flashed = []
26
25
  for mcu, fw_info in todo:
27
26
  fw_file = fw_folder / fw_info.filename
28
27
  if not fw_file.exists():
29
28
  log.error(f"File {fw_file} does not exist, skipping {mcu.board} on {mcu.serialport}")
30
29
  continue
30
+
31
31
  log.info(f"Updating {mcu.board} on {mcu.serialport} to {fw_info.version}")
32
+ try:
33
+ updated = flash_mcu(mcu, fw_file=fw_file, erase=erase, bootloader=bootloader, **kwargs)
34
+ except MPFlashError as e:
35
+ log.error(f"Failed to flash {mcu.board} on {mcu.serialport}: {e}")
36
+ continue
37
+ if updated:
38
+ flashed.append(updated)
39
+ else:
40
+ log.error(f"Failed to flash {mcu.board} on {mcu.serialport}")
41
+ return flashed
42
+
43
+
44
+ def flash_mcu(
45
+ mcu,
46
+ *,
47
+ fw_file: Path,
48
+ erase: bool = False,
49
+ bootloader: BootloaderMethod = BootloaderMethod.AUTO,
50
+ **kwargs
51
+ ):
52
+ """Flash a single MCU with the specified firmware."""
32
53
  updated = None
33
54
  try:
34
55
  if mcu.port in UF2_PORTS and fw_file.suffix == ".uf2":
35
56
  if not enter_bootloader(mcu, bootloader):
36
- continue
57
+ raise MPFlashError(f"Failed to enter bootloader for {mcu.board} on {mcu.serialport}")
37
58
  updated = flash_uf2(mcu, fw_file=fw_file, erase=erase)
38
59
  elif mcu.port in ["stm32"]:
39
60
  if not enter_bootloader(mcu, bootloader):
40
- continue
61
+ raise MPFlashError(f"Failed to enter bootloader for {mcu.board} on {mcu.serialport}")
41
62
  updated = flash_stm32(mcu, fw_file, erase=erase)
42
63
  elif mcu.port in ["esp32", "esp8266"]:
43
64
  # bootloader is handled by esptool for esp32/esp8266
44
- updated = flash_esp(mcu, fw_file=fw_file, erase=erase)
65
+ updated = flash_esp(mcu, fw_file=fw_file, erase=erase, **kwargs)
45
66
  else:
46
- log.error(f"Don't (yet) know how to flash {mcu.port}-{mcu.board} on {mcu.serialport}")
67
+ raise MPFlashError(f"Don't (yet) know how to flash {mcu.port}-{mcu.board} on {mcu.serialport}")
47
68
  except Exception as e:
48
- log.error(f"Failed to flash {mcu.board} on {mcu.serialport}")
49
- log.exception(e)
50
-
51
- if updated:
52
- flashed.append(updated)
53
- else:
54
- log.error(f"Failed to flash {mcu.board} on {mcu.serialport}")
55
- return flashed
69
+ raise MPFlashError(f"Failed to flash {mcu.board} on {mcu.serialport}") from e
70
+ return updated
mpflash/flash/esp.py CHANGED
@@ -5,7 +5,7 @@
5
5
  """
6
6
 
7
7
  from pathlib import Path
8
- from typing import List, Optional
8
+ from typing import List, Literal, Optional
9
9
 
10
10
  import esptool
11
11
  from loguru import logger as log
@@ -13,9 +13,17 @@ from loguru import logger as log
13
13
  from mpflash.mpboard_id import find_known_board
14
14
  from mpflash.mpremoteboard import MPRemoteBoard
15
15
 
16
+ FlashMode = Literal["keep", "qio", "qout", "dio", "dout"]
16
17
 
17
- def flash_esp(mcu: MPRemoteBoard, fw_file: Path, *, erase: bool = True) -> Optional[MPRemoteBoard]:
18
- if mcu.port not in ["esp32", "esp8266"] or mcu.board in ["ARDUINO_NANO_ESP32"]:
18
+ def flash_esp(
19
+ mcu: MPRemoteBoard,
20
+ fw_file: Path,
21
+ *,
22
+ erase: bool = True,
23
+ flash_mode: FlashMode = "keep", # keep, qio, qout, dio, dout
24
+ flash_size: str = "detect",
25
+ ) -> Optional[MPRemoteBoard]:
26
+ if mcu.port not in ["esp32", "esp8266"] or mcu.board.startswith("ARDUINO_"):
19
27
  log.error(f"esptool not supported for {mcu.port} {mcu.board} on {mcu.serialport}")
20
28
  return None
21
29
 
@@ -25,26 +33,49 @@ def flash_esp(mcu: MPRemoteBoard, fw_file: Path, *, erase: bool = True) -> Optio
25
33
  mcu.cpu = find_known_board(mcu.board).cpu
26
34
 
27
35
  cmds: List[List[str]] = []
28
- if erase:
29
- cmds.append(f"esptool --chip {mcu.cpu} --port {mcu.serialport} erase_flash".split())
30
36
 
37
+ chip = "auto"
38
+ start_addr = "0x0"
31
39
  if mcu.cpu.upper().startswith("ESP32"):
40
+ start_addr = "0x0"
41
+
32
42
  baud_rate = str(921_600)
33
- if mcu.cpu.upper() in ("ESP32", "ESP32S2"):
43
+ if mcu.cpu.upper() == "ESP32":
44
+ start_addr = "0x1000"
45
+ chip = "esp32"
46
+ elif "C2" in mcu.cpu.upper():
47
+ start_addr = "0x1000"
48
+ chip = "esp32c2"
49
+ elif "S2" in mcu.cpu.upper():
34
50
  start_addr = "0x1000"
35
- elif mcu.cpu.upper() in ("ESP32S3", "ESP32C3"):
51
+ chip = "esp32s2"
52
+ baud_rate = str(460_800)
53
+ elif "S3" in mcu.cpu.upper():
36
54
  start_addr = "0x0"
55
+ chip = "esp32s3"
56
+ elif "C3" in mcu.cpu.upper():
57
+ start_addr = "0x0"
58
+ chip = "esp32c3"
59
+ elif "C6" in mcu.cpu.upper():
60
+ start_addr = "0x0"
61
+ chip = "esp32c6"
62
+ baud_rate = str(460_800)
63
+
37
64
  cmds.append(
38
- f"esptool --chip {mcu.cpu} --port {mcu.serialport} -b {baud_rate} write_flash --compress {start_addr}".split()
65
+ f"esptool --chip {chip} --port {mcu.serialport} -b {baud_rate} write_flash --flash_mode {flash_mode} --flash_size {flash_size} --compress {start_addr}".split()
39
66
  + [str(fw_file)]
40
67
  )
41
68
  elif mcu.cpu.upper() == "ESP8266":
42
69
  baud_rate = str(460_800)
43
70
  start_addr = "0x0"
71
+ chip = "esp8266"
44
72
  cmds.append(
45
- f"esptool --chip {mcu.cpu} --port {mcu.serialport} -b {baud_rate} write_flash --flash_size=detect {start_addr}".split()
73
+ f"esptool --chip {chip} --port {mcu.serialport} -b {baud_rate} write_flash --flash_mode {flash_mode} --flash_size=detect {start_addr}".split()
46
74
  + [str(fw_file)]
47
75
  )
76
+ # now that we have the chip, we can do the erare properly
77
+ if erase:
78
+ cmds.insert(0, f"esptool --chip {chip} --port {mcu.serialport} erase_flash".split())
48
79
  try:
49
80
  for cmd in cmds:
50
81
  log.info(f"Running {' '.join(cmd)} ")
@@ -5,6 +5,7 @@ Flash SAMD and RP2 via UF2
5
5
  import shutil
6
6
  import sys
7
7
  from pathlib import Path
8
+ import time
8
9
  from typing import Optional
9
10
 
10
11
  import tenacity
@@ -38,7 +39,21 @@ def flash_uf2(mcu: MPRemoteBoard, fw_file: Path, erase: bool) -> Optional[MPRemo
38
39
  log.error(f"UF2 not supported on {mcu.board} on {mcu.serialport}")
39
40
  return None
40
41
  if erase:
41
- log.warning("Erase not (yet) supported on .UF2, skipping erase.")
42
+ if mcu.port == "rp2":
43
+ rp2_erase =Path(__file__).parent.joinpath("../../vendor/pico-universal-flash-nuke/universal_flash_nuke.uf2").resolve()
44
+ log.info(f"Erasing {mcu.port} with {rp2_erase.name}")
45
+ # optimistic
46
+ destination = waitfor_uf2(board_id=mcu.port.upper())
47
+ if not destination :
48
+ log.error("Board is not in bootloader mode")
49
+ return None
50
+ copy_firmware_to_uf2(rp2_erase, destination)
51
+ if sys.platform in ["linux"]:
52
+ dismount_uf2_linux()
53
+ # allow for MCU restart after erase
54
+ time.sleep(0.5)
55
+ else:
56
+ log.warning(f"Erase not (yet) supported on .UF2, for port {mcu.port}, skipping erase.")
42
57
 
43
58
  destination = waitfor_uf2(board_id=mcu.port.upper())
44
59
 
@@ -84,5 +99,6 @@ def copy_firmware_to_uf2(fw_file: Path, destination: Path):
84
99
  Copy the firmware file to the destination,
85
100
  Retry 3 times with 1s delay
86
101
  """
87
- log.info(f"Copying {fw_file} to {destination}.")
102
+ log.trace(f"Firmware: {fw_file}")
103
+ log.info(f"Copying {fw_file.name} to {destination}.")
88
104
  return shutil.copy(fw_file, destination)
@@ -1,4 +1,4 @@
1
- """ Flashing UF2 based MCU on Linux"""
1
+ """Flashing UF2 based MCU on Linux"""
2
2
 
3
3
  # sourcery skip: snake-case-functions
4
4
  from __future__ import annotations
@@ -42,10 +42,7 @@ def get_uf2_drives():
42
42
  uf2.mountpoint = uf2_part["mountpoint"]
43
43
  yield uf2
44
44
  elif disk["type"] == "disk" and disk.get("children") and len(disk.get("children")) > 0:
45
- if (
46
- disk.get("children")[0]["type"] == "part"
47
- and disk.get("children")[0]["fstype"] == "vfat"
48
- ):
45
+ if disk.get("children")[0]["type"] == "part" and disk.get("children")[0]["fstype"] == "vfat":
49
46
  uf2_part = disk.get("children")[0]
50
47
  # print( json.dumps(uf2_part, indent=4))
51
48
  uf2 = UF2Disk()
@@ -69,9 +66,7 @@ def pmount(disk: UF2Disk):
69
66
  # drive is always vfat - so specify to have quicker results
70
67
  subprocess.run(["pmount", "-t", "vfat", disk.device_path, disk.mountpoint])
71
68
  except FileNotFoundError:
72
- log.error(
73
- "pmount not found, please install it using 'sudo apt install pmount', and add this user to the plugdev group."
74
- )
69
+ log.error("pmount not found, please install it using 'sudo apt install pmount', and add this user to the plugdev group.")
75
70
  return
76
71
  log.debug(f"Mounted {disk.label} at {disk.mountpoint}")
77
72
  glb_dismount_me.append(disk)
@@ -89,7 +84,7 @@ def pumount(disk: UF2Disk):
89
84
  if disk.mountpoint:
90
85
  subprocess.run(["pumount", disk.mountpoint]) # ), f"/media/{disk.label}"])
91
86
  log.info(f"Unmounted {disk.label} from {disk.mountpoint}")
92
- disk.mountpoint = f""
87
+ disk.mountpoint = ""
93
88
  else:
94
89
  log.warning(f"{disk.label} already dismounted")
95
90
 
@@ -1,4 +1,4 @@
1
- """ Flashing UF2 based MCU on macos"""
1
+ """Flashing UF2 based MCU on macos"""
2
2
 
3
3
  # sourcery skip: snake-case-functions
4
4
  from __future__ import annotations
@@ -12,7 +12,7 @@ import psutil
12
12
  from rich.progress import track
13
13
 
14
14
 
15
- def wait_for_UF2_windows(board_id: str, s_max: int = 10)-> Optional[Path]:
15
+ def wait_for_UF2_windows(board_id: str, s_max: int = 10) -> Optional[Path]:
16
16
  """Wait for the MCU to mount as a drive"""
17
17
  if s_max < 1:
18
18
  s_max = 10
mpflash/flash/worklist.py CHANGED
@@ -44,7 +44,7 @@ def auto_update(
44
44
  continue
45
45
  board_firmwares = find_downloaded_firmware(
46
46
  fw_folder=fw_folder,
47
- board_id=mcu.board,
47
+ board_id=mcu.board if not mcu.variant else f"{mcu.board}-{mcu.variant}",
48
48
  version=target_version,
49
49
  port=mcu.port,
50
50
  selector=selector,
@@ -127,7 +127,12 @@ def single_auto_worklist(
127
127
 
128
128
 
129
129
  def full_auto_worklist(
130
- all_boards: List[MPRemoteBoard], *, include: List[str], ignore: List[str], version: str, fw_folder: Path
130
+ all_boards: List[MPRemoteBoard],
131
+ *,
132
+ include: List[str],
133
+ ignore: List[str],
134
+ version: str,
135
+ fw_folder: Path,
131
136
  ) -> WorkList:
132
137
  """
133
138
  Create a worklist for all connected micropython boards based on the information retrieved from the board.
mpflash/list.py CHANGED
@@ -14,13 +14,16 @@ def show_mcus(
14
14
  conn_mcus: List[MPRemoteBoard],
15
15
  title: str = "Connected boards",
16
16
  refresh: bool = True,
17
+ *,
18
+ title_style="magenta",
19
+ header_style="bold magenta",
17
20
  ):
18
- console.print(mcu_table(conn_mcus, title, refresh))
21
+ console.print(mcu_table(conn_mcus, title, refresh, title_style=title_style, header_style=header_style))
19
22
 
20
23
 
21
24
  def abbrv_family(family: str, is_wide: bool) -> str:
22
25
  if not is_wide:
23
- ABRV = {"micropython": "upy", "circuitpython": "cpy", "unknown": "?"}
26
+ ABRV = {"micropython": "mpy", "circuitpython": "cpy", "unknown": "?"}
24
27
  return ABRV.get(family, family[:4])
25
28
  return family
26
29
 
@@ -29,15 +32,19 @@ def mcu_table(
29
32
  conn_mcus: List[MPRemoteBoard],
30
33
  title: str = "Connected boards",
31
34
  refresh: bool = True,
35
+ *,
36
+ title_style="magenta",
37
+ header_style="bold magenta",
32
38
  ):
33
39
  """
34
40
  builds a rich table with the connected boards information
35
- The columns of the table are adjusted to the terminal width
41
+ The columns of the table are adjusted to the terminal width > 90
36
42
  the columns are :
37
43
  Narrow Wide
38
44
  - Serial Yes Yes
39
45
  - Family abbrv. Yes
40
46
  - Port - yes
47
+ - Variant Yes Yes only if any of the mcus have a variant
41
48
  - Board Yes Yes BOARD_ID and Description, and the description from board_info.toml
42
49
  - CPU - Yes
43
50
  - Version Yes Yes
@@ -59,8 +66,8 @@ def mcu_table(
59
66
  continue
60
67
  table = Table(
61
68
  title=title,
62
- title_style="magenta",
63
- header_style="bold magenta",
69
+ title_style=title_style,
70
+ header_style=header_style,
64
71
  collapse_padding=True,
65
72
  padding=(0, 0),
66
73
  )
@@ -68,13 +75,15 @@ def mcu_table(
68
75
  # check if the terminal is wide enough to show all columns or if we need to collapse some
69
76
  is_wide = console.width > 99
70
77
  needs_build = any(mcu.build for mcu in conn_mcus)
78
+ needs_variant = any(mcu.variant for mcu in conn_mcus)
71
79
 
72
80
  table.add_column("Serial" if is_wide else "Ser.", overflow="fold")
73
81
  table.add_column("Family" if is_wide else "Fam.", overflow="crop", max_width=None if is_wide else 4)
74
82
  if is_wide:
75
83
  table.add_column("Port")
76
84
  table.add_column("Board", overflow="fold")
77
- # table.add_column("Variant") # TODO: add variant
85
+ if needs_variant:
86
+ table.add_column("Variant")
78
87
  if is_wide:
79
88
  table.add_column("CPU")
80
89
  table.add_column("Version", overflow="fold", min_width=5, max_width=16)
@@ -94,6 +103,8 @@ def mcu_table(
94
103
  if is_wide:
95
104
  row.append(mcu.port)
96
105
  row.append(f"{mcu.board}\n{description}".strip())
106
+ if needs_variant:
107
+ row.append(mcu.variant)
97
108
  if is_wide:
98
109
  row.append(mcu.cpu)
99
110
  row.append(clean_version(mcu.version))
mpflash/logger.py CHANGED
@@ -20,9 +20,7 @@ def _log_formatter(record: dict) -> str:
20
20
  "CRITICAL": "bold white on red",
21
21
  }
22
22
  lvl_color = color_map.get(record["level"].name, "cyan")
23
- return (
24
- "[not bold green]{time:HH:mm:ss}[/not bold green] | {level.icon} " + f"[{lvl_color}]{{message}}[/{lvl_color}]"
25
- )
23
+ return "[not bold green]{time:HH:mm:ss}[/not bold green] | {level.icon} " + f"[{lvl_color}]{{message}}[/{lvl_color}]"
26
24
 
27
25
 
28
26
  def set_loglevel(loglevel: str):
@@ -6,93 +6,12 @@ that is included in the module.
6
6
 
7
7
  from functools import lru_cache
8
8
  from typing import List, Optional, Tuple
9
-
9
+ import importlib
10
10
  from mpflash.errors import MPFlashError
11
- from mpflash.mpboard_id.board import Board
12
- from mpflash.mpboard_id.store import read_known_boardinfo
13
- from mpflash.versions import clean_version
14
-
15
- # KNOWN ports and boards are sourced from the micropython repo,
16
- # this info is stored in the board_info.json file
17
-
18
-
19
- def get_known_ports() -> List[str]:
20
- # TODO: Filter for Version
21
- mp_boards = read_known_boardinfo()
22
- # select the unique ports from info
23
- ports = set({board.port for board in mp_boards if board.port})
24
- return sorted(list(ports))
25
-
26
-
27
- def get_known_boards_for_port(
28
- port: Optional[str] = "", versions: Optional[List[str]] = None
29
- ) -> List[Board]:
30
- """
31
- Returns a list of boards for the given port and version(s)
32
-
33
- port: The Micropython port to filter for
34
- versions: Optional, The Micropython versions to filter for (actual versions required)
35
- """
36
- mp_boards = read_known_boardinfo()
37
- if versions:
38
- preview_or_stable = "preview" in versions or "stable" in versions
39
- else:
40
- preview_or_stable = False
41
-
42
- # filter for 'preview' as they are not in the board_info.json
43
- # instead use stable version
44
- versions = versions or []
45
- if "preview" in versions:
46
- versions.remove("preview")
47
- versions.append("stable")
48
- if versions:
49
- # make sure of the v prefix
50
- versions = [clean_version(v) for v in versions]
51
- # filter for the version(s)
52
- mp_boards = [board for board in mp_boards if board.version in versions]
53
- if not mp_boards and preview_or_stable:
54
- # nothing found - perhaps there is a newer version for which we do not have the board info yet
55
- # use the latest known version from the board info
56
- mp_boards = read_known_boardinfo()
57
- last_known_version = sorted({b.version for b in mp_boards})[-1]
58
- mp_boards = [board for board in mp_boards if board.version == last_known_version]
59
-
60
- # filter for the port
61
- if port:
62
- mp_boards = [board for board in mp_boards if board.port == port]
63
- return mp_boards
64
-
65
-
66
- def known_stored_boards(port: str, versions: Optional[List[str]] = None) -> List[Tuple[str, str]]:
67
- """
68
- Returns a list of tuples with the description and board name for the given port and version
69
-
70
- port : str : The Micropython port to filter for
71
- versions : List[str] : The Micropython versions to filter for (actual versions required)
72
- """
73
- mp_boards = get_known_boards_for_port(port, versions)
74
-
75
- boards = set({(f"{board.version} {board.description}", board.board_id) for board in mp_boards})
76
- return sorted(list(boards))
11
+ from .board import Board
77
12
 
13
+ from mpflash.versions import clean_version
14
+ from .store import read_known_boardinfo
15
+ from .known import get_known_ports, get_known_boards_for_port
16
+ from .known import known_stored_boards, find_known_board
78
17
 
79
- @lru_cache(maxsize=20)
80
- def find_known_board(board_id: str) -> Board:
81
- """Find the board for the given BOARD_ID or 'board description' and return the board info as a Board object"""
82
- # Some functional overlap with:
83
- # mpboard_id\board_id.py _find_board_id_by_description
84
- info = read_known_boardinfo()
85
- for board_info in info:
86
- if board_id in (
87
- board_info.board_id,
88
- board_info.description,
89
- ) or board_info.description.startswith(board_id):
90
- if not board_info.cpu:
91
- # failsafe for older board_info.json files
92
- print(f"Board {board_id} has no CPU info, using port as CPU")
93
- if " with " in board_info.description:
94
- board_info.cpu = board_info.description.split(" with ")[-1]
95
- else:
96
- board_info.cpu = board_info.port
97
- return board_info
98
- raise MPFlashError(f"Board {board_id} not found")