micropython-stubber 1.17.6__py3-none-any.whl → 1.20.0__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 (83) hide show
  1. {micropython_stubber-1.17.6.dist-info → micropython_stubber-1.20.0.dist-info}/METADATA +7 -6
  2. micropython_stubber-1.20.0.dist-info/RECORD +147 -0
  3. mpflash/README.md +18 -2
  4. mpflash/libusb_flash.ipynb +203 -0
  5. mpflash/mpflash/ask_input.py +234 -0
  6. mpflash/mpflash/cli_download.py +107 -0
  7. mpflash/mpflash/cli_flash.py +170 -0
  8. mpflash/mpflash/cli_group.py +40 -7
  9. mpflash/mpflash/cli_list.py +41 -0
  10. mpflash/mpflash/cli_main.py +13 -8
  11. mpflash/mpflash/common.py +33 -121
  12. mpflash/mpflash/config.py +9 -0
  13. mpflash/mpflash/{downloader.py → download.py} +110 -96
  14. mpflash/mpflash/downloaded.py +108 -0
  15. mpflash/mpflash/errors.py +5 -0
  16. mpflash/mpflash/flash.py +69 -0
  17. mpflash/mpflash/flash_esp.py +17 -23
  18. mpflash/mpflash/flash_stm32.py +16 -113
  19. mpflash/mpflash/flash_stm32_cube.py +111 -0
  20. mpflash/mpflash/flash_stm32_dfu.py +101 -0
  21. mpflash/mpflash/flash_uf2.py +8 -8
  22. mpflash/mpflash/flash_uf2_linux.py +13 -6
  23. mpflash/mpflash/flash_uf2_windows.py +24 -12
  24. mpflash/mpflash/list.py +56 -39
  25. mpflash/mpflash/logger.py +12 -13
  26. mpflash/mpflash/mpboard_id/__init__.py +96 -0
  27. mpflash/mpflash/mpboard_id/board_id.py +63 -0
  28. mpflash/mpflash/mpboard_id/board_info.csv +2213 -0
  29. mpflash/mpflash/mpboard_id/board_info.json +19910 -0
  30. mpflash/mpflash/mpremoteboard/__init__.py +209 -0
  31. mpflash/mpflash/mpremoteboard/mpy_fw_info.py +141 -0
  32. {stubber/bulk → mpflash/mpflash/mpremoteboard}/runner.py +19 -4
  33. mpflash/mpflash/vendor/dfu.py +164 -0
  34. mpflash/mpflash/vendor/pydfu.py +605 -0
  35. mpflash/mpflash/vendor/readme.md +3 -0
  36. mpflash/mpflash/vendor/versions.py +113 -0
  37. mpflash/mpflash/worklist.py +147 -0
  38. mpflash/poetry.lock +427 -610
  39. mpflash/pyproject.toml +22 -6
  40. mpflash/stm32_udev_rules.md +63 -0
  41. stubber/__init__.py +1 -1
  42. stubber/basicgit.py +1 -0
  43. stubber/board/createstubs.py +11 -4
  44. stubber/board/createstubs_db.py +11 -5
  45. stubber/board/createstubs_db_min.py +61 -58
  46. stubber/board/createstubs_db_mpy.mpy +0 -0
  47. stubber/board/createstubs_mem.py +11 -5
  48. stubber/board/createstubs_mem_min.py +56 -53
  49. stubber/board/createstubs_mem_mpy.mpy +0 -0
  50. stubber/board/createstubs_min.py +54 -51
  51. stubber/board/createstubs_mpy.mpy +0 -0
  52. stubber/board/modulelist.txt +1 -0
  53. stubber/bulk/mcu_stubber.py +9 -5
  54. stubber/codemod/_partials/db_main.py +14 -25
  55. stubber/codemod/_partials/lvgl_main.py +2 -2
  56. stubber/codemod/board.py +10 -3
  57. stubber/commands/clone_cmd.py +7 -7
  58. stubber/commands/config_cmd.py +3 -0
  59. stubber/commands/{mcu_cmd.py → get_mcu_cmd.py} +20 -3
  60. stubber/freeze/get_frozen.py +0 -2
  61. stubber/publish/candidates.py +1 -1
  62. stubber/publish/package.py +1 -1
  63. stubber/publish/pathnames.py +1 -1
  64. stubber/publish/stubpackage.py +1 -0
  65. stubber/rst/lookup.py +1 -1
  66. stubber/stubber.py +1 -9
  67. stubber/tools/manifestfile.py +5 -3
  68. stubber/update_fallback.py +104 -104
  69. stubber/utils/config.py +32 -36
  70. stubber/utils/repos.py +2 -2
  71. stubber/utils/versions.py +1 -0
  72. micropython_stubber-1.17.6.dist-info/RECORD +0 -132
  73. mpflash/mpflash/flasher.py +0 -276
  74. stubber/bulk/board_id.py +0 -40
  75. stubber/bulk/mpremoteboard.py +0 -141
  76. stubber/commands/get_lobo_cmd.py +0 -58
  77. stubber/commands/minify_cmd.py +0 -60
  78. stubber/commands/upd_fallback_cmd.py +0 -36
  79. stubber/commands/upd_module_list_cmd.py +0 -18
  80. {micropython_stubber-1.17.6.dist-info → micropython_stubber-1.20.0.dist-info}/LICENSE +0 -0
  81. {micropython_stubber-1.17.6.dist-info → micropython_stubber-1.20.0.dist-info}/WHEEL +0 -0
  82. {micropython_stubber-1.17.6.dist-info → micropython_stubber-1.20.0.dist-info}/entry_points.txt +0 -0
  83. /mpflash/mpflash/{uf2_boardid.py → flash_uf2_boardid.py} +0 -0
@@ -1,121 +1,24 @@
1
- """
2
- Flash STM32 using STM32CubeProgrammer
3
- needs to be installed independenty from https://www.st.com/en/development-tools/stm32cubeprog.html
1
+ """Flash STM32 boards using either STM32CubeProgrammer CLI or dfu-util"""
4
2
 
5
- On Linux needs to be run with sudo - because ?
6
- """
7
-
8
- import subprocess
9
- import sys
10
- import time
11
3
  from pathlib import Path
12
- from typing import Optional
13
4
 
14
- import bincopy
15
5
  from loguru import logger as log
16
- from strip_ansi import strip_ansi
17
-
18
- from stubber.bulk.mpremoteboard import MPRemoteBoard
19
-
20
- STM32_CLI_WIN = "C:\\Program Files\\STMicroelectronics\\STM32Cube\\STM32CubeProgrammer\\bin\\STM32_Programmer_CLI.exe"
21
- STM32_CLI_LINUX = "/home/jos/STMicroelectronics/STM32Cube/STM32CubeProgrammer/bin/STM32_Programmer_CLI"
22
-
23
-
24
- def get_stm32_start_address(fw_file: Path):
25
- """
26
- Get the start address of the firmware file, to allow automatic restart from that address after flashing
27
- """
28
- try:
29
- fw_hex = bincopy.BinFile(str(fw_file))
30
- return f"0x{fw_hex.execution_start_address:08X}"
31
- except Exception:
32
-
33
- return ""
34
-
35
-
36
- def flash_stm32(
37
- mcu: MPRemoteBoard, fw_file: Path, *, erase: bool = True
38
- ) -> Optional[MPRemoteBoard]:
39
- """
40
- Flash STM32 devices using STM32CubeProgrammer CLI
41
- - Enter bootloader mode
42
- - wait 2s for the device to be detected
43
- - list the connected DFU devices
44
6
 
45
- Windows only for now
46
- """
47
- if sys.platform == "linux":
48
- STM32_CLI = STM32_CLI_LINUX
49
- elif sys.platform == "win32":
50
- STM32_CLI = STM32_CLI_WIN
51
- else:
52
- log.error(f"OS {sys.platform} not supported")
53
- return None
7
+ # from .flash_stm32_cube import flash_stm32_cubecli
8
+ from .flash_stm32_dfu import dfu_init, flash_stm32_dfu
9
+ from mpflash.mpremoteboard import MPRemoteBoard
54
10
 
55
- if not Path(STM32_CLI).exists():
56
- log.error(
57
- f"STM32CubeProgrammer not found at {STM32_CLI}\nPlease install it from https://www.st.com/en/development-tools/stm32cubeprog.html"
58
- )
59
- return None
60
11
 
61
- log.info(f"Entering STM bootloader on {mcu.board} on {mcu.serialport}")
62
- mcu.run_command("bootloader")
63
- time.sleep(2)
64
- # run STM32_Programmer_CLI.exe --list
65
- cmd = [
66
- STM32_CLI,
67
- "--list",
68
- ]
69
- results = subprocess.run(cmd, capture_output=True, text=True).stdout.splitlines()
70
- results = [strip_ansi(line) for line in results]
71
- if not any(["Product ID : STM32 BOOTLOADER" in l for l in results]):
72
- log.error("No STM32 BOOTLOADER detected")
73
- return None
74
- echo = False
75
- for line in results:
76
- if line.startswith("===== DFU Interface"):
77
- echo = True
78
- if line.startswith("===== STLink"):
79
- echo = False
80
- if echo:
81
- print(line)
82
- # Try to connect - no action
83
- cmd = [
84
- STM32_CLI,
85
- "--connect",
86
- "port=USB1",
87
- ]
88
- results = subprocess.run(cmd, capture_output=True, text=True).stdout.splitlines()
89
- if erase:
90
- log.info("Erasing flash")
91
- cmd = [
92
- STM32_CLI,
93
- "--connect",
94
- "port=USB1",
95
- "--erase",
96
- "all",
97
- ]
98
- results = subprocess.run(
99
- cmd, capture_output=True, text=True
100
- ).stdout.splitlines()
101
- results = [strip_ansi(line) for line in results]
102
- log.info("Flashing")
103
- start_address = get_stm32_start_address(fw_file)
12
+ def flash_stm32(mcu: MPRemoteBoard, fw_file: Path, *, erase: bool, stm32_dfu: bool = True):
13
+ # sourcery skip: lift-return-into-if
14
+ log.info("Using dfu-util")
15
+ dfu_init()
16
+ updated = flash_stm32_dfu(mcu, fw_file=fw_file, erase=erase)
17
+ # if stm32_dfu:
18
+ # else:
19
+ # log.info("Using STM32CubeProgrammer CLI")
20
+ # updated = flash_stm32_cubecli(mcu, fw_file=fw_file, erase=erase)
104
21
 
105
- log.trace(
106
- f"STM32_Programmer_CLI --connect port=USB1 --write {str(fw_file)} --go {start_address}"
107
- )
108
- cmd = [
109
- STM32_CLI,
110
- "--connect",
111
- "port=USB1",
112
- "--write",
113
- str(fw_file),
114
- "--go",
115
- start_address,
116
- ]
117
- results = subprocess.run(cmd, capture_output=True, text=True).stdout.splitlines()
118
- log.success("Done flashing, resetting the board and wait for it to restart")
119
- time.sleep(5)
120
- mcu.get_mcu_info()
121
- return mcu
22
+ mcu.wait_for_restart()
23
+ log.success(f"Flashed {mcu.version} to {mcu.board}")
24
+ return updated
@@ -0,0 +1,111 @@
1
+ # """
2
+ # Flash STM32 using STM32CubeProgrammer
3
+ # needs to be installed independenty from https://www.st.com/en/development-tools/stm32cubeprog.html
4
+
5
+ # On Linux needs to be run with sudo - unless udev rules are set to allow access to the device as a regular user
6
+ # """
7
+
8
+ # import subprocess
9
+ # import sys
10
+ # import time
11
+ # from pathlib import Path
12
+ # from typing import Optional
13
+
14
+ # import bincopy
15
+ # from loguru import logger as log
16
+ # from rich.progress import track
17
+ # from strip_ansi import strip_ansi
18
+
19
+ # from .mpremoteboard.mpremoteboard import MPRemoteBoard
20
+
21
+ # STM32_CLI_WIN = "C:\\Program Files\\STMicroelectronics\\STM32Cube\\STM32CubeProgrammer\\bin\\STM32_Programmer_CLI.exe"
22
+ # STM32_CLI_LINUX = "~/STMicroelectronics/STM32Cube/STM32CubeProgrammer/bin/STM32_Programmer_CLI"
23
+
24
+
25
+ # def get_stm32_start_address(fw_file: Path):
26
+ # """
27
+ # Get the start address of the firmware file, to allow automatic restart from that address after flashing
28
+ # """
29
+ # try:
30
+ # fw_hex = bincopy.BinFile(str(fw_file))
31
+ # return f"0x{fw_hex.execution_start_address:08X}"
32
+ # except Exception:
33
+
34
+ # return ""
35
+
36
+
37
+ # def flash_stm32_cubecli(mcu: MPRemoteBoard, fw_file: Path, *, erase: bool = True) -> Optional[MPRemoteBoard]:
38
+ # """
39
+ # Flash STM32 devices using STM32CubeProgrammer CLI
40
+ # - Enter bootloader mode
41
+ # - wait 2s for the device to be detected
42
+ # - list the connected DFU devices
43
+
44
+ # On Linux: requires udev rules to allow access to the device as a regular user
45
+ # """
46
+ # if sys.platform == "linux":
47
+ # STM32_CLI = Path(STM32_CLI_LINUX).expanduser().as_posix()
48
+ # elif sys.platform == "win32":
49
+ # STM32_CLI = str(Path(STM32_CLI_WIN).expanduser())
50
+ # else:
51
+ # log.error(f"OS {sys.platform} not supported")
52
+ # return None
53
+
54
+ # if not Path(STM32_CLI).exists():
55
+ # log.error(
56
+ # f"STM32CubeProgrammer not found at {STM32_CLI}\nPlease install it from https://www.st.com/en/development-tools/stm32cubeprog.html"
57
+ # )
58
+ # return None
59
+
60
+ # # run STM32_Programmer_CLI.exe --list
61
+ # cmd = [
62
+ # STM32_CLI,
63
+ # "--list",
64
+ # ]
65
+ # results = subprocess.run(cmd, capture_output=True, text=True).stdout.splitlines()
66
+ # results = [strip_ansi(line) for line in results]
67
+ # if not any(["Product ID : STM32 BOOTLOADER" in l for l in results]):
68
+ # log.error("No STM32 BOOTLOADER detected")
69
+ # return None
70
+ # echo = False
71
+ # for line in results:
72
+ # if line.startswith("===== DFU Interface"):
73
+ # echo = True
74
+ # if line.startswith("===== STLink"):
75
+ # echo = False
76
+ # if echo:
77
+ # print(line)
78
+ # # Try to connect - no action
79
+ # cmd = [
80
+ # STM32_CLI,
81
+ # "--connect",
82
+ # "port=USB1",
83
+ # ]
84
+ # results = subprocess.run(cmd, capture_output=True, text=True).stdout.splitlines()
85
+ # if erase:
86
+ # log.info("Erasing flash")
87
+ # cmd = [
88
+ # STM32_CLI,
89
+ # "--connect",
90
+ # "port=USB1",
91
+ # "--erase",
92
+ # "all",
93
+ # ]
94
+ # results = subprocess.run(cmd, capture_output=True, text=True).stdout.splitlines()
95
+ # results = [strip_ansi(line) for line in results]
96
+ # log.info(f"Flashing {fw_file.name} using STM32CubeProgrammer CLI")
97
+ # start_address = get_stm32_start_address(fw_file)
98
+
99
+ # log.trace(f"STM32_Programmer_CLI --connect port=USB1 --write {str(fw_file)} --go {start_address}")
100
+ # cmd = [
101
+ # STM32_CLI,
102
+ # "--connect",
103
+ # "port=USB1",
104
+ # "--write",
105
+ # str(fw_file),
106
+ # "--go",
107
+ # start_address,
108
+ # ]
109
+ # results = subprocess.run(cmd, capture_output=True, text=True).stdout.splitlines()
110
+ # log.success("Done flashing, resetting the board and wait for it to restart")
111
+ # return mcu
@@ -0,0 +1,101 @@
1
+ import platform
2
+ from pathlib import Path
3
+ from typing import Optional
4
+
5
+ from loguru import logger as log
6
+
7
+ from mpflash.mpremoteboard import MPRemoteBoard
8
+
9
+
10
+ def init_libusb_windows() -> bool:
11
+ """
12
+ Initializes the libusb backend on Windows.
13
+
14
+ Returns:
15
+ bool: True if the initialization is successful, False otherwise.
16
+ """
17
+ import libusb # type: ignore
18
+ import usb.backend.libusb1 as libusb1
19
+
20
+ arch = "x64" if platform.architecture()[0] == "64bit" else "x86"
21
+ libusb1_dll = Path(libusb.__file__).parent / f"_platform\\_windows\\{arch}\\libusb-1.0.dll"
22
+ if not libusb1_dll.exists():
23
+ raise FileNotFoundError(f"libusb1.dll not found at {libusb1_dll}")
24
+ backend = libusb1.get_backend(find_library=lambda x: libusb1_dll.as_posix())
25
+ return backend is not None
26
+
27
+
28
+ try:
29
+ from .vendor import pydfu as pydfu
30
+ except ImportError:
31
+ pydfu = None
32
+
33
+
34
+ def dfu_init():
35
+ """
36
+ Initializes the DFU (Device Firmware Upgrade) process.
37
+ """
38
+ if not pydfu:
39
+ log.error("pydfu not found")
40
+ return None
41
+ if platform.system() == "Windows":
42
+ init_libusb_windows()
43
+
44
+
45
+ def flash_stm32_dfu(
46
+ mcu: MPRemoteBoard,
47
+ fw_file: Path,
48
+ *,
49
+ erase: bool = True,
50
+ ) -> Optional[MPRemoteBoard]:
51
+ """
52
+ Flashes the STM32 microcontroller using DFU (Device Firmware Upgrade).
53
+
54
+ Args:
55
+ mcu (MPRemoteBoard): The remote board to flash.
56
+ fw_file (Path): The path to the firmware file (.dfu).
57
+ erase (bool, optional): Whether to erase the memory before flashing. Defaults to True.
58
+
59
+ Returns:
60
+ Optional[MPRemoteBoard]: The flashed remote board if successful, None otherwise.
61
+ """
62
+ if not pydfu:
63
+ log.error("pydfu not found, please install it with 'pip install pydfu' if supported")
64
+ return None
65
+
66
+ if not fw_file.exists():
67
+ log.error(f"File {fw_file} not found")
68
+ return None
69
+
70
+ if fw_file.suffix != ".dfu":
71
+ log.error(f"File {fw_file} is not a .dfu file")
72
+ return None
73
+
74
+ kwargs = {"idVendor": 0x0483, "idProduct": 0xDF11}
75
+ log.debug("List SPECIFIED DFU devices...")
76
+ try:
77
+ pydfu.list_dfu_devices(**kwargs)
78
+ except ValueError as e:
79
+ log.error(f"Insuffient permissions to access usb DFU devices: {e}")
80
+ return None
81
+
82
+ # Needs to be a list of serial ports
83
+ log.debug("Inititialize pydfu...")
84
+ pydfu.init(**kwargs)
85
+
86
+ if erase:
87
+ log.info("Mass erase...")
88
+ pydfu.mass_erase()
89
+
90
+ log.debug("Read DFU file...")
91
+ elements = pydfu.read_dfu_file(fw_file)
92
+ if not elements:
93
+ print("No data in dfu file")
94
+ return
95
+ log.info("Writing memory...")
96
+ pydfu.write_elements(elements, False, progress=pydfu.cli_progress)
97
+
98
+ log.debug("Exiting DFU...")
99
+ pydfu.exit_dfu()
100
+ log.success("Done flashing, resetting the board and wait for it to restart")
101
+ return mcu
@@ -9,8 +9,9 @@ from pathlib import Path
9
9
  from typing import Optional
10
10
 
11
11
  from loguru import logger as log
12
+ from rich.progress import track
12
13
 
13
- from stubber.bulk.mpremoteboard import MPRemoteBoard
14
+ from mpflash.mpremoteboard import MPRemoteBoard
14
15
 
15
16
  from .common import PORT_FWTYPES
16
17
  from .flash_uf2_linux import dismount_uf2, wait_for_UF2_linux
@@ -30,21 +31,19 @@ def flash_uf2(mcu: MPRemoteBoard, fw_file: Path, erase: bool) -> Optional[MPRemo
30
31
  pmount and pumount are used to mount and unmount the drive
31
32
  as this is not done automatically by the OS in headless mode.
32
33
  """
33
- if PORT_FWTYPES[mcu.port] not in [".uf2"]:
34
+ if ".uf2" not in PORT_FWTYPES[mcu.port]:
34
35
  log.error(f"UF2 not supported on {mcu.board} on {mcu.serialport}")
35
36
  return None
36
37
  if erase:
37
38
  log.info("Erasing not yet implemented for UF2 flashing.")
38
39
 
39
- log.info(f"Entering UF2 bootloader on {mcu.board} on {mcu.serialport}")
40
- mcu.run_command("bootloader", timeout=10)
41
-
42
40
  if sys.platform == "linux":
43
41
  destination = wait_for_UF2_linux()
44
42
  elif sys.platform == "win32":
45
43
  destination = wait_for_UF2_windows()
46
44
  else:
47
- log.error(f"OS {sys.platform} not supported")
45
+ log.warning(f"OS {sys.platform} not tested/supported")
46
+ destination = wait_for_UF2_linux()
48
47
  return None
49
48
 
50
49
  if not destination or not destination.exists() or not (destination / "INFO_UF2.TXT").exists():
@@ -55,7 +54,8 @@ def flash_uf2(mcu: MPRemoteBoard, fw_file: Path, erase: bool) -> Optional[MPRemo
55
54
  log.info(f"Copying {fw_file} to {destination}.")
56
55
  shutil.copy(fw_file, destination)
57
56
  log.success("Done copying, resetting the board and wait for it to restart")
58
- if sys.platform == "linux":
57
+ if sys.platform in ["linux", "darwin"]:
59
58
  dismount_uf2()
60
- time.sleep(5 * 2) # 5 secs to short on linux
59
+ for _ in track(range(5 + 2), description="Waiting for the board to restart", transient=True):
60
+ time.sleep(1) # 5 secs to short on linux
61
61
  return mcu
@@ -1,3 +1,6 @@
1
+ """ Flashing UF2 based MCU on Linux"""
2
+
3
+ # sourcery skip: snake-case-functions
1
4
  from __future__ import annotations
2
5
 
3
6
  import subprocess
@@ -7,8 +10,9 @@ from pathlib import Path
7
10
  from typing import List
8
11
 
9
12
  from loguru import logger as log
13
+ from rich.progress import track
10
14
 
11
- from .uf2_boardid import get_board_id
15
+ from .flash_uf2_boardid import get_board_id
12
16
 
13
17
  glb_dismount_me: List[UF2Disk] = []
14
18
 
@@ -76,10 +80,10 @@ def pmount(disk: UF2Disk):
76
80
  except FileNotFoundError:
77
81
  log.error("pmount not found, please install it using 'sudo apt install pmount'")
78
82
  return
79
- log.info(f"Mounted {disk.label} at {disk.mountpoint}")
83
+ log.debug(f"Mounted {disk.label} at {disk.mountpoint}")
80
84
  glb_dismount_me.append(disk)
81
85
  else:
82
- log.warning(f"{disk.label} already mounted at {disk.mountpoint}")
86
+ log.debug(f"\n{disk.label} already mounted at {disk.mountpoint}")
83
87
 
84
88
 
85
89
  def pumount(disk: UF2Disk):
@@ -104,12 +108,13 @@ def dismount_uf2():
104
108
  glb_dismount_me = []
105
109
 
106
110
 
107
- def wait_for_UF2_linux():
111
+ def wait_for_UF2_linux(s_max: int = 10):
108
112
  destination = ""
109
113
  wait = 10
110
114
  uf2_drives = []
111
- while not destination and wait > 0:
112
- log.info(f"Waiting for mcu to mount as a drive : {wait} seconds left")
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):
117
+ # log.info(f"Waiting for mcu to mount as a drive : {wait} seconds left")
113
118
  uf2_drives += list(get_uf2_drives())
114
119
  for drive in get_uf2_drives():
115
120
  pmount(drive)
@@ -122,6 +127,8 @@ def wait_for_UF2_linux():
122
127
  except PermissionError:
123
128
  log.debug(f"Permission error on {drive.mountpoint}")
124
129
  continue
130
+ if destination:
131
+ break
125
132
  time.sleep(1)
126
133
  wait -= 1
127
134
  return destination
@@ -1,22 +1,34 @@
1
+ # sourcery skip: snake-case-functions
2
+ """Flash a MCU with a UF2 bootloader on Windows"""
3
+
1
4
  from __future__ import annotations
2
- from pathlib import Path
5
+
3
6
  import time
7
+ from pathlib import Path
8
+
4
9
  import psutil
5
- from loguru import logger as log
6
- from .uf2_boardid import get_board_id
10
+ from rich.progress import track
11
+
12
+ from .flash_uf2_boardid import get_board_id
7
13
 
8
14
 
9
- def wait_for_UF2_windows():
15
+ def wait_for_UF2_windows(s_max: int = 10):
16
+ """Wait for the MCU to mount as a drive"""
17
+ if s_max < 1:
18
+ s_max = 10
10
19
  destination = ""
11
- wait = 10
12
- while not destination and wait > 0:
13
- log.info(f"Waiting for mcu to mount as a drive : {wait} seconds left")
20
+ for _ in track(range(s_max), description="Waiting for mcu to mount as a drive", transient=True):
21
+ # log.info(f"Waiting for mcu to mount as a drive : {n} seconds left")
14
22
  drives = [drive.device for drive in psutil.disk_partitions()]
15
23
  for drive in drives:
16
- if Path(drive, "INFO_UF2.TXT").exists():
17
- board_id = get_board_id(Path(drive)) # type: ignore
18
- destination = Path(drive)
19
- break
24
+ try:
25
+ if Path(drive, "INFO_UF2.TXT").exists():
26
+ board_id = get_board_id(Path(drive)) # type: ignore
27
+ destination = Path(drive)
28
+ break
29
+ except OSError:
30
+ pass
31
+ if destination:
32
+ break
20
33
  time.sleep(1)
21
- wait -= 1
22
34
  return destination
mpflash/mpflash/list.py CHANGED
@@ -1,71 +1,88 @@
1
- import json
2
1
  from typing import List
3
2
 
4
- import rich_click as click
5
3
  from rich import print
6
- from rich.progress import track
7
- from rich.table import Table
4
+ from rich.progress import BarColumn, Progress, SpinnerColumn, TextColumn, TimeElapsedColumn, track
5
+ from rich.table import Column, Table
8
6
 
9
- # TODO: - refactor so that we do not need the entire stubber package
10
- from stubber.bulk.mpremoteboard import MPRemoteBoard
7
+ from mpflash.mpremoteboard import MPRemoteBoard
11
8
 
12
- from .cli_group import cli
13
9
  from .config import config
10
+ from .logger import console
14
11
 
12
+ rp_spinner = SpinnerColumn(finished_text="✅")
13
+ rp_text = TextColumn("{task.description} {task.fields[device]}", table_column=Column())
14
+ rp_bar = BarColumn(bar_width=None, table_column=Column())
15
15
 
16
- @cli.command("list", help="List the connected boards.")
17
- @click.option(
18
- "--json",
19
- "-j",
20
- "as_json",
21
- is_flag=True,
22
- default=False,
23
- show_default=True,
24
- help="""Output in json format""",
25
- )
26
- def list_boards(as_json: bool):
27
- conn_boards = [MPRemoteBoard(sp) for sp in MPRemoteBoard.connected_boards() if sp not in config.ignore_ports]
28
16
 
29
- for mcu in track(conn_boards, description="Getting board info"):
17
+ def list_mcus(bluetooth: bool = False):
18
+ """
19
+ Retrieves information about connected microcontroller boards.
20
+
21
+ Returns:
22
+ List[MPRemoteBoard]: A list of MPRemoteBoard instances with board information.
23
+ Raises:
24
+ ConnectionError: If there is an error connecting to a board.
25
+ """
26
+ conn_mcus = [MPRemoteBoard(sp) for sp in MPRemoteBoard.connected_boards(bluetooth) if sp not in config.ignore_ports]
27
+
28
+ # a lot of boilerplate to show a progress bar with the comport currenlty scanned
29
+ with Progress(rp_spinner, rp_text, rp_bar, TimeElapsedColumn()) as progress:
30
+ tsk_scan = progress.add_task("[green]Scanning", visible=False, total=None)
31
+ progress.tasks[tsk_scan].fields["device"] = "..."
32
+ progress.tasks[tsk_scan].visible = True
33
+ progress.start_task(tsk_scan)
30
34
  try:
31
- mcu.get_mcu_info()
32
- except ConnectionError as e:
33
- print(f"Error: {e}")
34
- continue
35
- if as_json:
36
- print(json.dumps([mcu.__dict__ for mcu in conn_boards], indent=4))
37
- else:
38
- show_boards(conn_boards, refresh=False)
35
+ for mcu in conn_mcus:
36
+ progress.update(tsk_scan, device=mcu.serialport.replace("/dev/", ""))
37
+ try:
38
+ mcu.get_mcu_info()
39
+ except ConnectionError as e:
40
+ print(f"Error: {e}")
41
+ continue
42
+ finally:
43
+ # transient
44
+ progress.stop_task(tsk_scan)
45
+ progress.tasks[tsk_scan].visible = False
46
+ return conn_mcus
39
47
 
40
48
 
41
- def show_boards(
42
- conn_boards: List[MPRemoteBoard],
49
+ def show_mcus(
50
+ conn_mcus: List[MPRemoteBoard],
43
51
  title: str = "Connected boards",
44
52
  refresh: bool = True,
45
- ):
53
+ ): # sourcery skip: extract-duplicate-method
46
54
  """Show the list of connected boards in a nice table"""
47
- table = Table(title=title)
48
- table.add_column("Serial")
55
+ table = Table(
56
+ title=title,
57
+ title_style="magenta",
58
+ header_style="bold magenta",
59
+ collapse_padding=True,
60
+ width=110,
61
+ )
62
+ table.add_column("Serial", overflow="fold")
49
63
  table.add_column("Family")
50
64
  table.add_column("Port")
51
- table.add_column("Board")
65
+ table.add_column("Board", overflow="fold")
66
+ # table.add_column("Variant") # TODO: add variant
52
67
  table.add_column("CPU")
53
68
  table.add_column("Version")
54
- table.add_column("build")
69
+ table.add_column("build", justify="right")
55
70
 
56
- for mcu in track(conn_boards, transient=True, description="Updating board info"):
71
+ for mcu in track(conn_mcus, description="Updating board info", transient=True, update_period=0.1):
57
72
  if refresh:
58
73
  try:
59
74
  mcu.get_mcu_info()
60
75
  except ConnectionError:
61
76
  continue
77
+ description = f"[italic bright_cyan]{mcu.description}" if mcu.description else ""
62
78
  table.add_row(
63
- mcu.serialport,
79
+ mcu.serialport.replace("/dev/", ""),
64
80
  mcu.family,
65
81
  mcu.port,
66
- mcu.board,
82
+ f"{mcu.board}\n{description}".strip(),
83
+ # mcu.variant,
67
84
  mcu.cpu,
68
85
  mcu.version,
69
86
  mcu.build,
70
87
  )
71
- print(table)
88
+ console.print(table)
mpflash/mpflash/logger.py CHANGED
@@ -1,13 +1,15 @@
1
- import sys
1
+ """Logging."""
2
2
 
3
3
  from loguru import logger as log
4
4
  from rich.console import Console
5
5
 
6
+ from .config import config
7
+
6
8
  console = Console()
7
9
 
8
10
 
9
11
  def _log_formatter(record: dict) -> str:
10
- """Log message formatter"""
12
+ """Log message formatter to combine loguru and rich formatting."""
11
13
  color_map = {
12
14
  "TRACE": "dim blue",
13
15
  "DEBUG": "cyan",
@@ -19,8 +21,7 @@ def _log_formatter(record: dict) -> str:
19
21
  }
20
22
  lvl_color = color_map.get(record["level"].name, "cyan")
21
23
  return (
22
- "[not bold green]{time:HH:mm:ss}[/not bold green] | {level.icon}"
23
- + f" - [{lvl_color}]{{message}}[/{lvl_color}]"
24
+ "[not bold green]{time:HH:mm:ss}[/not bold green] | {level.icon} " + f"[{lvl_color}]{{message}}[/{lvl_color}]"
24
25
  )
25
26
 
26
27
 
@@ -30,13 +31,11 @@ def set_loglevel(loglevel: str):
30
31
  log.remove()
31
32
  except ValueError:
32
33
  pass
33
- log.add(
34
- console.print, level=loglevel.upper(), colorize=False, format=_log_formatter
35
- )
34
+ log.add(console.print, level=loglevel.upper(), colorize=False, format=_log_formatter) # type: ignore
35
+
36
36
 
37
- # log.add(
38
- # console.print,
39
- # level=loglevel.upper(),
40
- # format=_log_formatter,
41
- # colorize=True,
42
- # )
37
+ def make_quiet():
38
+ """Make the logger quiet"""
39
+ config.quiet = True
40
+ console.quiet = True
41
+ set_loglevel("CRITICAL")