micropython-stubber 1.17.5__py3-none-any.whl → 1.19.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 (74) hide show
  1. {micropython_stubber-1.17.5.dist-info → micropython_stubber-1.19.0.dist-info}/METADATA +7 -6
  2. {micropython_stubber-1.17.5.dist-info → micropython_stubber-1.19.0.dist-info}/RECORD +71 -52
  3. mpflash/README.md +22 -3
  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 +165 -0
  8. mpflash/mpflash/cli_group.py +41 -8
  9. mpflash/mpflash/cli_list.py +41 -0
  10. mpflash/mpflash/cli_main.py +13 -8
  11. mpflash/mpflash/common.py +33 -122
  12. mpflash/mpflash/config.py +9 -0
  13. mpflash/mpflash/{downloader.py → download.py} +112 -120
  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 +25 -12
  23. mpflash/mpflash/flash_uf2_windows.py +24 -12
  24. mpflash/mpflash/list.py +34 -37
  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 +208 -0
  31. mpflash/mpflash/mpremoteboard/mpy_fw_info.py +141 -0
  32. {stubber/bulk → mpflash/mpflash/mpremoteboard}/runner.py +22 -5
  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 +411 -595
  39. mpflash/pyproject.toml +24 -8
  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 +10 -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/bulk/mcu_stubber.py +9 -5
  53. stubber/codemod/_partials/db_main.py +14 -25
  54. stubber/codemod/_partials/lvgl_main.py +2 -2
  55. stubber/codemod/board.py +10 -3
  56. stubber/commands/clone_cmd.py +7 -7
  57. stubber/commands/config_cmd.py +3 -0
  58. stubber/freeze/get_frozen.py +0 -2
  59. stubber/publish/candidates.py +1 -1
  60. stubber/publish/package.py +1 -1
  61. stubber/publish/pathnames.py +1 -1
  62. stubber/publish/stubpackage.py +1 -0
  63. stubber/rst/lookup.py +1 -1
  64. stubber/tools/manifestfile.py +5 -3
  65. stubber/utils/config.py +26 -36
  66. stubber/utils/repos.py +2 -2
  67. stubber/utils/versions.py +1 -0
  68. mpflash/mpflash/flasher.py +0 -287
  69. stubber/bulk/board_id.py +0 -40
  70. stubber/bulk/mpremoteboard.py +0 -141
  71. {micropython_stubber-1.17.5.dist-info → micropython_stubber-1.19.0.dist-info}/LICENSE +0 -0
  72. {micropython_stubber-1.17.5.dist-info → micropython_stubber-1.19.0.dist-info}/WHEEL +0 -0
  73. {micropython_stubber-1.17.5.dist-info → micropython_stubber-1.19.0.dist-info}/entry_points.txt +0 -0
  74. /mpflash/mpflash/{uf2_boardid.py → flash_uf2_boardid.py} +0 -0
@@ -0,0 +1,69 @@
1
+ import time
2
+ from pathlib import Path
3
+
4
+ from loguru import logger as log
5
+
6
+ from mpflash.common import PORT_FWTYPES
7
+ from mpflash.mpremoteboard import MPRemoteBoard
8
+
9
+ from .flash_esp import flash_esp
10
+ from .flash_stm32 import flash_stm32
11
+ from .flash_uf2 import flash_uf2
12
+ from .worklist import WorkList
13
+
14
+ # #########################################################################################################
15
+
16
+
17
+ def flash_list(
18
+ todo: WorkList,
19
+ fw_folder: Path,
20
+ erase: bool,
21
+ bootloader: bool,
22
+ ):
23
+ """Flash a list of boards with the specified firmware."""
24
+ flashed = []
25
+ for mcu, fw_info in todo:
26
+ fw_file = fw_folder / fw_info["filename"] # type: ignore
27
+ if not fw_file.exists():
28
+ log.error(f"File {fw_file} does not exist, skipping {mcu.board} on {mcu.serialport}")
29
+ continue
30
+ log.info(f"Updating {mcu.board} on {mcu.serialport} to {fw_info['version']}")
31
+ updated = None
32
+ # try:
33
+ if mcu.port in [port for port, exts in PORT_FWTYPES.items() if ".uf2" in exts] and fw_file.suffix == ".uf2":
34
+ # any .uf2 port ["samd", "rp2", "nrf"]:
35
+ if bootloader:
36
+ enter_bootloader(mcu)
37
+ updated = flash_uf2(mcu, fw_file=fw_file, erase=erase)
38
+ elif mcu.port in ["stm32"]:
39
+ if bootloader:
40
+ enter_bootloader(mcu)
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
+
48
+ if updated:
49
+ flashed.append(updated)
50
+ else:
51
+ log.error(f"Failed to flash {mcu.board} on {mcu.serialport}")
52
+ return flashed
53
+
54
+
55
+ def enter_bootloader(mcu: MPRemoteBoard, timeout: int = 10, wait_after: int = 2):
56
+ """Enter the bootloader mode for the board"""
57
+ log.info(f"Entering bootloader on {mcu.board} on {mcu.serialport}")
58
+ mcu.run_command("bootloader", timeout=timeout)
59
+ time.sleep(wait_after)
60
+
61
+
62
+ # TODO:
63
+ # flash from some sort of queue to allow different images to be flashed to the same board
64
+ # - flash variant 1
65
+ # - stub variant 1
66
+ # - flash variant 2
67
+ # - stub variant 2
68
+ #
69
+ # JIT download / download any missing firmwares based on the detected boards
@@ -4,47 +4,42 @@
4
4
  # #########################################################################################################
5
5
  """
6
6
 
7
- import time
8
7
  from pathlib import Path
9
8
  from typing import List, Optional
10
9
 
11
10
  import esptool
12
11
  from loguru import logger as log
13
12
 
14
- from stubber.bulk.mpremoteboard import MPRemoteBoard
13
+ from mpflash.mpboard_id import find_stored_board
14
+ from mpflash.mpremoteboard import MPRemoteBoard
15
15
 
16
16
 
17
- def flash_esp(
18
- mcu: MPRemoteBoard, fw_file: Path, *, erase: bool = True
19
- ) -> Optional[MPRemoteBoard]:
17
+ def flash_esp(mcu: MPRemoteBoard, fw_file: Path, *, erase: bool = True) -> Optional[MPRemoteBoard]:
20
18
  if mcu.port not in ["esp32", "esp8266"] or mcu.board in ["ARDUINO_NANO_ESP32"]:
21
- log.error(
22
- f"esptool not supported for {mcu.port} {mcu.board} on {mcu.serialport}"
23
- )
19
+ log.error(f"esptool not supported for {mcu.port} {mcu.board} on {mcu.serialport}")
24
20
  return None
25
21
 
26
22
  log.info(f"Flashing {fw_file} on {mcu.board} on {mcu.serialport}")
27
- if mcu.port == "esp8266":
28
- baud_rate = str(460_800)
29
- else:
30
- baud_rate = str(512_000)
31
- # baud_rate = str(115_200)
23
+ if not mcu.cpu:
24
+ # Lookup CPU based on the board name
25
+ mcu.cpu = find_stored_board(mcu.board)["cpu"]
26
+
32
27
  cmds: List[List[str]] = []
33
28
  if erase:
34
- cmds.append(
35
- f"esptool --chip {mcu.cpu} --port {mcu.serialport} erase_flash".split()
36
- )
29
+ cmds.append(f"esptool --chip {mcu.cpu} --port {mcu.serialport} erase_flash".split())
37
30
 
38
- if mcu.cpu.upper() in ("ESP32", "ESP32S2"):
39
- start_addr = "0x1000"
40
- elif mcu.cpu.upper() in ("ESP32S3", "ESP32C3"):
41
- start_addr = "0x0"
42
31
  if mcu.cpu.upper().startswith("ESP32"):
32
+ baud_rate = str(921_600)
33
+ if mcu.cpu.upper() in ("ESP32", "ESP32S2"):
34
+ start_addr = "0x1000"
35
+ elif mcu.cpu.upper() in ("ESP32S3", "ESP32C3"):
36
+ start_addr = "0x0"
43
37
  cmds.append(
44
38
  f"esptool --chip {mcu.cpu} --port {mcu.serialport} -b {baud_rate} write_flash --compress {start_addr}".split()
45
39
  + [str(fw_file)]
46
40
  )
47
41
  elif mcu.cpu.upper() == "ESP8266":
42
+ baud_rate = str(460_800)
48
43
  start_addr = "0x0"
49
44
  cmds.append(
50
45
  f"esptool --chip {mcu.cpu} --port {mcu.serialport} -b {baud_rate} write_flash --flash_size=detect {start_addr}".split()
@@ -59,7 +54,6 @@ def flash_esp(
59
54
  return None
60
55
 
61
56
  log.info("Done flashing, resetting the board and wait for it to restart")
62
- time.sleep(5)
63
- mcu.get_mcu_info()
64
- log.success(f"Flashed {mcu.version} to {mcu.board} on {mcu.serialport} done")
57
+ mcu.wait_for_restart()
58
+ log.success(f"Flashed {mcu.serialport} to {mcu.board} {mcu.version}")
65
59
  return mcu
@@ -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)):
60
+ time.sleep(1) # 5 secs to short on linux
61
61
  return mcu
@@ -1,12 +1,18 @@
1
+ """ Flashing UF2 based MCU on Linux"""
2
+
3
+ # sourcery skip: snake-case-functions
1
4
  from __future__ import annotations
2
- from pathlib import Path
5
+
3
6
  import subprocess
4
7
  import sys
5
8
  import time
9
+ from pathlib import Path
10
+ from typing import List
11
+
6
12
  from loguru import logger as log
13
+ from rich.progress import track
7
14
 
8
- from typing import List
9
- from .uf2_boardid import get_board_id
15
+ from .flash_uf2_boardid import get_board_id
10
16
 
11
17
  glb_dismount_me: List[UF2Disk] = []
12
18
 
@@ -74,10 +80,10 @@ def pmount(disk: UF2Disk):
74
80
  except FileNotFoundError:
75
81
  log.error("pmount not found, please install it using 'sudo apt install pmount'")
76
82
  return
77
- log.info(f"Mounted {disk.label} at {disk.mountpoint}")
83
+ log.debug(f"Mounted {disk.label} at {disk.mountpoint}")
78
84
  glb_dismount_me.append(disk)
79
85
  else:
80
- log.warning(f"{disk.label} already mounted at {disk.mountpoint}")
86
+ log.debug(f"\n{disk.label} already mounted at {disk.mountpoint}")
81
87
 
82
88
 
83
89
  def pumount(disk: UF2Disk):
@@ -102,20 +108,27 @@ def dismount_uf2():
102
108
  glb_dismount_me = []
103
109
 
104
110
 
105
- def wait_for_UF2_linux():
111
+ def wait_for_UF2_linux(s_max: int = 10):
106
112
  destination = ""
107
113
  wait = 10
108
114
  uf2_drives = []
109
- while not destination and wait > 0:
110
- 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")
111
118
  uf2_drives += list(get_uf2_drives())
112
119
  for drive in get_uf2_drives():
113
120
  pmount(drive)
114
121
  time.sleep(1)
115
- if Path(drive.mountpoint, "INFO_UF2.TXT").exists():
116
- board_id = get_board_id(Path(drive.mountpoint)) # type: ignore
117
- destination = Path(drive.mountpoint)
118
- break
122
+ try:
123
+ if Path(drive.mountpoint, "INFO_UF2.TXT").exists():
124
+ board_id = get_board_id(Path(drive.mountpoint)) # type: ignore
125
+ destination = Path(drive.mountpoint)
126
+ break
127
+ except PermissionError:
128
+ log.debug(f"Permission error on {drive.mountpoint}")
129
+ continue
130
+ if destination:
131
+ break
119
132
  time.sleep(1)
120
133
  wait -= 1
121
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