micropython-stubber 1.20.2__py3-none-any.whl → 1.20.5__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.
- {micropython_stubber-1.20.2.dist-info → micropython_stubber-1.20.5.dist-info}/METADATA +5 -4
- {micropython_stubber-1.20.2.dist-info → micropython_stubber-1.20.5.dist-info}/RECORD +51 -46
- {micropython_stubber-1.20.2.dist-info → micropython_stubber-1.20.5.dist-info}/WHEEL +1 -1
- mpflash/mpflash/ask_input.py +24 -14
- mpflash/mpflash/bootloader/__init__.py +36 -0
- mpflash/mpflash/bootloader/manual.py +102 -0
- mpflash/mpflash/bootloader/micropython.py +10 -0
- mpflash/mpflash/bootloader/touch1200.py +45 -0
- mpflash/mpflash/cli_download.py +1 -0
- mpflash/mpflash/cli_flash.py +16 -9
- mpflash/mpflash/cli_group.py +13 -6
- mpflash/mpflash/cli_list.py +6 -2
- mpflash/mpflash/cli_main.py +5 -2
- mpflash/mpflash/common.py +15 -2
- mpflash/mpflash/config.py +27 -1
- mpflash/mpflash/download.py +82 -16
- mpflash/mpflash/downloaded.py +28 -7
- mpflash/mpflash/errors.py +5 -1
- mpflash/mpflash/flash.py +10 -27
- mpflash/mpflash/flash_uf2.py +4 -6
- mpflash/mpflash/flash_uf2_boardid.py +2 -1
- mpflash/mpflash/flash_uf2_macos.py +13 -57
- mpflash/mpflash/flash_uf2_windows.py +4 -4
- mpflash/mpflash/mpboard_id/__init__.py +4 -0
- mpflash/mpflash/mpboard_id/board_id.py +19 -3
- mpflash/mpflash/mpboard_id/store.py +2 -1
- mpflash/mpflash/vendor/click_aliases.py +91 -0
- mpflash/poetry.lock +102 -137
- mpflash/pyproject.toml +1 -1
- stubber/__init__.py +1 -1
- stubber/board/createstubs.py +12 -4
- stubber/board/createstubs_db.py +4 -5
- stubber/board/createstubs_db_min.py +1 -1
- stubber/board/createstubs_db_mpy.mpy +0 -0
- stubber/board/createstubs_mem.py +4 -5
- stubber/board/createstubs_mem_min.py +1 -1
- stubber/board/createstubs_mem_mpy.mpy +0 -0
- stubber/board/createstubs_min.py +2 -2
- stubber/board/createstubs_mpy.mpy +0 -0
- stubber/board/modulelist.txt +9 -0
- stubber/bulk/mcu_stubber.py +0 -1
- stubber/codemod/_partials/__init__.py +0 -2
- stubber/publish/candidates.py +7 -28
- stubber/publish/enums.py +0 -6
- stubber/publish/package.py +15 -46
- stubber/publish/publish.py +1 -2
- stubber/rst/lookup.py +7 -7
- stubber/rst/reader.py +26 -27
- stubber/update_module_list.py +2 -26
- {micropython_stubber-1.20.2.dist-info → micropython_stubber-1.20.5.dist-info}/LICENSE +0 -0
- {micropython_stubber-1.20.2.dist-info → micropython_stubber-1.20.5.dist-info}/entry_points.txt +0 -0
mpflash/mpflash/cli_main.py
CHANGED
@@ -19,12 +19,12 @@ def mpflash():
|
|
19
19
|
cli.add_command(cli_flash_board)
|
20
20
|
|
21
21
|
# cli(auto_envvar_prefix="MPFLASH")
|
22
|
-
if False and os.environ.get("COMPUTERNAME")
|
22
|
+
if False and os.environ.get("COMPUTERNAME").startswith("JOSVERL"):
|
23
23
|
# intentional less error suppression on dev machine
|
24
24
|
result = cli(standalone_mode=False)
|
25
25
|
else:
|
26
26
|
try:
|
27
|
-
result = cli(standalone_mode=
|
27
|
+
result = cli(standalone_mode=True)
|
28
28
|
exit(result)
|
29
29
|
except AttributeError as e:
|
30
30
|
log.error(f"Error: {e}")
|
@@ -32,6 +32,9 @@ def mpflash():
|
|
32
32
|
except click.exceptions.ClickException as e:
|
33
33
|
log.error(f"Error: {e}")
|
34
34
|
exit(-2)
|
35
|
+
except click.exceptions.Abort as e:
|
36
|
+
# Aborted - Ctrl-C
|
37
|
+
exit(-3)
|
35
38
|
|
36
39
|
|
37
40
|
if __name__ == "__main__":
|
mpflash/mpflash/common.py
CHANGED
@@ -2,6 +2,7 @@ import fnmatch
|
|
2
2
|
import os
|
3
3
|
import sys
|
4
4
|
from dataclasses import dataclass, field
|
5
|
+
from enum import Enum
|
5
6
|
from pathlib import Path
|
6
7
|
from typing import List, Optional, Union
|
7
8
|
|
@@ -47,7 +48,7 @@ class FWInfo:
|
|
47
48
|
firmware: str = field(default="") # url or path to original firmware image
|
48
49
|
variant: str = field(default="") # MicroPython variant
|
49
50
|
preview: bool = field(default=False) # True if the firmware is a preview version
|
50
|
-
version: str = field(default="") # MicroPython version
|
51
|
+
version: str = field(default="") # MicroPython version (NO v prefix)
|
51
52
|
url: str = field(default="") # url to the firmware image download folder
|
52
53
|
build: str = field(default="0") # The build = number of commits since the last release
|
53
54
|
ext: str = field(default="") # the file extension of the firmware
|
@@ -90,14 +91,26 @@ class DownloadParams(Params):
|
|
90
91
|
force: bool = False
|
91
92
|
|
92
93
|
|
94
|
+
class BootloaderMethod(Enum):
|
95
|
+
MANUAL = "manual"
|
96
|
+
MPY = "mpy"
|
97
|
+
TOUCH_1200 = "touch1200"
|
98
|
+
NONE = "none"
|
99
|
+
|
100
|
+
|
101
|
+
|
93
102
|
@dataclass
|
94
103
|
class FlashParams(Params):
|
95
104
|
"""Parameters for flashing a board"""
|
96
105
|
|
97
106
|
erase: bool = True
|
98
|
-
bootloader:
|
107
|
+
bootloader: BootloaderMethod = BootloaderMethod.NONE
|
99
108
|
cpu: str = ""
|
100
109
|
|
110
|
+
def __post_init__(self):
|
111
|
+
if isinstance(self.bootloader, str):
|
112
|
+
self.bootloader = BootloaderMethod(self.bootloader)
|
113
|
+
|
101
114
|
|
102
115
|
ParamType = Union[DownloadParams, FlashParams]
|
103
116
|
|
mpflash/mpflash/config.py
CHANGED
@@ -1,10 +1,22 @@
|
|
1
1
|
"""centralized configuration for mpflash"""
|
2
2
|
|
3
|
+
import os
|
3
4
|
from pathlib import Path
|
4
5
|
from typing import List
|
5
6
|
|
7
|
+
import pkg_resources
|
6
8
|
import platformdirs
|
7
9
|
|
10
|
+
from mpflash.logger import log
|
11
|
+
|
12
|
+
|
13
|
+
def get_version():
|
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"
|
19
|
+
|
8
20
|
|
9
21
|
class MPtoolConfig:
|
10
22
|
"""Centralized configuration for mpflash"""
|
@@ -12,10 +24,24 @@ class MPtoolConfig:
|
|
12
24
|
quiet: bool = False
|
13
25
|
verbose: bool = False
|
14
26
|
ignore_ports: List[str] = []
|
15
|
-
interactive: bool = True
|
16
27
|
firmware_folder: Path = platformdirs.user_downloads_path() / "firmware"
|
17
28
|
# test options specified on the commandline
|
18
29
|
tests: List[str] = []
|
30
|
+
_interactive: bool = True
|
31
|
+
|
32
|
+
@property
|
33
|
+
def interactive(self):
|
34
|
+
# No interactions in CI
|
35
|
+
if os.getenv('GITHUB_ACTIONS') == 'true':
|
36
|
+
log.warning("Disabling interactive mode in CI")
|
37
|
+
return False
|
38
|
+
return self._interactive
|
39
|
+
|
40
|
+
@interactive.setter
|
41
|
+
def interactive(self, value:bool):
|
42
|
+
self._interactive = value
|
43
|
+
|
19
44
|
|
20
45
|
|
21
46
|
config = MPtoolConfig()
|
47
|
+
__version__ = get_version()
|
mpflash/mpflash/download.py
CHANGED
@@ -21,6 +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
25
|
|
25
26
|
# avoid conflict with the ujson used by MicroPython
|
26
27
|
jsonlines.ujson = None # type: ignore
|
@@ -32,8 +33,18 @@ MICROPYTHON_ORG_URL = "https://micropython.org/"
|
|
32
33
|
# Regexes to remove dates and hashes in the filename that just get in the way
|
33
34
|
RE_DATE = r"(-\d{8}-)"
|
34
35
|
RE_HASH = r"(.g[0-9a-f]+\.)"
|
35
|
-
# regex to extract the version from the firmware filename
|
36
|
-
|
36
|
+
# regex to extract the version and the build from the firmware filename
|
37
|
+
# group 1 is the version, group 2 is the build
|
38
|
+
RE_VERSION_PREVIEW = r"v([\d\.]+)-?(?:preview\.)?(\d+)?\."
|
39
|
+
# 'RPI_PICO_W-v1.23.uf2'
|
40
|
+
# 'RPI_PICO_W-v1.23.0.uf2'
|
41
|
+
# 'RPI_PICO_W-v1.23.0-406.uf2'
|
42
|
+
# 'RPI_PICO_W-v1.23.0-preview.406.uf2'
|
43
|
+
# 'RPI_PICO_W-v1.23.0-preview.4.uf2'
|
44
|
+
# 'RPI_PICO_W-v1.23.0.uf2'
|
45
|
+
# 'https://micropython.org/resources/firmware/RPI_PICO_W-20240531-v1.24.0-preview.10.gc1a6b95bf.uf2'
|
46
|
+
# 'https://micropython.org/resources/firmware/RPI_PICO_W-20240531-v1.24.0-preview.10.uf2'
|
47
|
+
# 'RPI_PICO_W-v1.24.0-preview.10.gc1a6b95bf.uf2'
|
37
48
|
|
38
49
|
|
39
50
|
# use functools.lru_cache to avoid needing to download pages multiple times
|
@@ -98,6 +109,7 @@ def board_firmware_urls(board_url: str, base_url: str, ext: str) -> List[str]:
|
|
98
109
|
# The first run takes ~60 seconds to run for 4 ports , all boards
|
99
110
|
# so it makes sense to cache the results and skip boards as soon as possible
|
100
111
|
def get_boards(ports: List[str], boards: List[str], clean: bool) -> List[FWInfo]:
|
112
|
+
# sourcery skip: use-getitem-for-re-match-groups
|
101
113
|
"""
|
102
114
|
Retrieves a list of firmware information for the specified ports and boards.
|
103
115
|
|
@@ -146,13 +158,15 @@ def get_boards(ports: List[str], boards: List[str], clean: bool) -> List[FWInfo]
|
|
146
158
|
# board["firmware"] = _url
|
147
159
|
# board["preview"] = "preview" in _url # type: ignore
|
148
160
|
if ver_match := re.search(RE_VERSION_PREVIEW, _url):
|
149
|
-
fw_info.version = ver_match
|
161
|
+
fw_info.version = clean_version(ver_match.group(1))
|
162
|
+
fw_info.build = ver_match.group(2) or "0"
|
163
|
+
fw_info.preview = fw_info.build != "0"
|
164
|
+
# # else:
|
165
|
+
# # board.$1= ""
|
166
|
+
# if "preview." in fw_info.version:
|
167
|
+
# fw_info.build = fw_info.version.split("preview.")[-1]
|
150
168
|
# else:
|
151
|
-
#
|
152
|
-
if "preview." in fw_info.version:
|
153
|
-
fw_info.build = fw_info.version.split("preview.")[-1]
|
154
|
-
else:
|
155
|
-
fw_info.build = "0"
|
169
|
+
# fw_info.build = "0"
|
156
170
|
|
157
171
|
fw_info.ext = Path(fw_info.firmware).suffix
|
158
172
|
fw_info.variant = fw_info.filename.split("-v")[0] if "-v" in fw_info.filename else ""
|
@@ -180,9 +194,21 @@ def download_firmwares(
|
|
180
194
|
force: bool = False,
|
181
195
|
clean: bool = True,
|
182
196
|
) -> int:
|
197
|
+
"""
|
198
|
+
Downloads firmware files based on the specified firmware folder, ports, boards, versions, force flag, and clean flag.
|
199
|
+
|
200
|
+
Args:
|
201
|
+
firmware_folder : The folder to save the downloaded firmware files.
|
202
|
+
ports : The list of ports to check for firmware.
|
203
|
+
boards : The list of boards to download firmware for.
|
204
|
+
versions : The list of versions to download firmware for.
|
205
|
+
force : A flag indicating whether to force the download even if the firmware file already exists.
|
206
|
+
clean : A flag indicating to clean the date from the firmware filename.
|
207
|
+
"""
|
183
208
|
skipped = downloaded = 0
|
184
|
-
if versions is None
|
185
|
-
|
209
|
+
versions = [] if versions is None else [clean_version(v) for v in versions]
|
210
|
+
# handle renamed boards
|
211
|
+
boards = add_renamed_boards(boards)
|
186
212
|
|
187
213
|
unique_boards = get_firmware_list(ports, boards, versions, clean)
|
188
214
|
|
@@ -191,6 +217,11 @@ def download_firmwares(
|
|
191
217
|
# relevant
|
192
218
|
|
193
219
|
log.info(f"Found {len(unique_boards)} relevant unique firmwares")
|
220
|
+
if not unique_boards:
|
221
|
+
log.error("No relevant firmwares could be found on https://micropython.org/download")
|
222
|
+
log.info(f"{versions=} {ports=} {boards=}")
|
223
|
+
log.info("Please check the website for the latest firmware files or try the preview version.")
|
224
|
+
return 0
|
194
225
|
|
195
226
|
firmware_folder.mkdir(exist_ok=True)
|
196
227
|
|
@@ -214,7 +245,9 @@ def download_firmwares(
|
|
214
245
|
continue
|
215
246
|
writer.write(board.to_dict())
|
216
247
|
downloaded += 1
|
217
|
-
|
248
|
+
# if downloaded > 0:
|
249
|
+
# clean_downloaded_firmwares(firmware_folder)
|
250
|
+
log.success(f"Downloaded {downloaded} firmwares, skipped {skipped} existing files.")
|
218
251
|
return downloaded + skipped
|
219
252
|
|
220
253
|
|
@@ -238,12 +271,15 @@ def get_firmware_list(ports: List[str], boards: List[str], versions: List[str],
|
|
238
271
|
board_urls = sorted(get_boards(ports, boards, clean), key=key_fw_ver_pre_ext_bld)
|
239
272
|
|
240
273
|
log.debug(f"Total {len(board_urls)} firmwares")
|
274
|
+
|
241
275
|
relevant = [
|
242
276
|
board
|
243
277
|
for board in board_urls
|
244
|
-
if board.
|
245
|
-
# and b["port"] in ["esp32", "rp2"]
|
278
|
+
if board.version in versions and board.build == "0" and board.board in boards and not board.preview
|
246
279
|
]
|
280
|
+
|
281
|
+
if preview:
|
282
|
+
relevant.extend([board for board in board_urls if board.board in boards and board.preview])
|
247
283
|
log.debug(f"Matching firmwares: {len(relevant)}")
|
248
284
|
# select the unique boards
|
249
285
|
unique_boards: List[FWInfo] = []
|
@@ -272,7 +308,7 @@ def download(
|
|
272
308
|
boards : The list of boards to download firmware for.
|
273
309
|
versions : The list of versions to download firmware for.
|
274
310
|
force : A flag indicating whether to force the download even if the firmware file already exists.
|
275
|
-
clean : A flag indicating whether to
|
311
|
+
clean : A flag indicating whether to clean the date from the firmware filename.
|
276
312
|
|
277
313
|
Returns:
|
278
314
|
int: The number of downloaded firmware files.
|
@@ -284,11 +320,41 @@ def download(
|
|
284
320
|
if not boards:
|
285
321
|
log.critical("No boards found, please connect a board or specify boards to download firmware for.")
|
286
322
|
raise MPFlashError("No boards found")
|
287
|
-
|
323
|
+
|
288
324
|
try:
|
289
325
|
destination.mkdir(exist_ok=True, parents=True)
|
290
326
|
except (PermissionError, FileNotFoundError) as e:
|
291
327
|
log.critical(f"Could not create folder {destination}")
|
292
328
|
raise MPFlashError(f"Could not create folder {destination}") from e
|
329
|
+
try:
|
330
|
+
result = download_firmwares(destination, ports, boards, versions, force=force, clean=clean)
|
331
|
+
except requests.exceptions.RequestException as e:
|
332
|
+
log.exception(e)
|
333
|
+
raise MPFlashError("Could not connect to micropython.org") from e
|
334
|
+
|
335
|
+
return result
|
336
|
+
|
337
|
+
|
338
|
+
def add_renamed_boards(boards: List[str]) -> List[str]:
|
339
|
+
"""
|
340
|
+
Adds the renamed boards to the list of boards.
|
293
341
|
|
294
|
-
|
342
|
+
Args:
|
343
|
+
boards : The list of boards to add the renamed boards to.
|
344
|
+
|
345
|
+
Returns:
|
346
|
+
List[str]: The list of boards with the renamed boards added.
|
347
|
+
|
348
|
+
"""
|
349
|
+
renamed = {
|
350
|
+
"PICO": ["RPI_PICO"],
|
351
|
+
"PICO_W": ["RPI_PICO_W"],
|
352
|
+
"GENERIC": ["ESP32_GENERIC", "ESP8266_GENERIC"], # just add both of them
|
353
|
+
}
|
354
|
+
_boards = boards.copy()
|
355
|
+
for board in boards:
|
356
|
+
if board in renamed and renamed[board] not in boards:
|
357
|
+
_boards.extend(renamed[board])
|
358
|
+
if board != board.upper() and board.upper() not in boards:
|
359
|
+
_boards.append(board.upper())
|
360
|
+
return _boards
|
mpflash/mpflash/downloaded.py
CHANGED
@@ -24,10 +24,26 @@ def downloaded_firmwares(fw_folder: Path) -> List[FWInfo]:
|
|
24
24
|
return firmwares
|
25
25
|
|
26
26
|
|
27
|
+
def clean_downloaded_firmwares(fw_folder: Path) -> None:
|
28
|
+
"""
|
29
|
+
Remove duplicate entries from the firmware.jsonl file, keeping the latest one
|
30
|
+
uniqueness is based on the filename
|
31
|
+
"""
|
32
|
+
firmwares = downloaded_firmwares(fw_folder)
|
33
|
+
if not firmwares:
|
34
|
+
return
|
35
|
+
# keep the latest entry
|
36
|
+
unique_fw = {fw.filename: fw for fw in firmwares}
|
37
|
+
with jsonlines.open(fw_folder / "firmware.jsonl", "w") as writer:
|
38
|
+
for fw in unique_fw.values():
|
39
|
+
writer.write(fw.to_dict())
|
40
|
+
log.info(f"Removed duplicate entries from firmware.jsonl in {fw_folder}")
|
41
|
+
|
42
|
+
|
27
43
|
def find_downloaded_firmware(
|
28
44
|
*,
|
29
45
|
board_id: str,
|
30
|
-
version: str = "",
|
46
|
+
version: str = "", # v1.2.3
|
31
47
|
port: str = "",
|
32
48
|
variants: bool = False,
|
33
49
|
fw_folder: Optional[Path] = None,
|
@@ -38,21 +54,22 @@ def find_downloaded_firmware(
|
|
38
54
|
selector = {}
|
39
55
|
fw_folder = fw_folder or config.firmware_folder
|
40
56
|
# Use the information in firmwares.jsonl to find the firmware file
|
57
|
+
log.debug(f"{trie}] Looking for firmware for {board_id} {version} ")
|
41
58
|
fw_list = downloaded_firmwares(fw_folder)
|
42
59
|
if not fw_list:
|
43
60
|
log.error("No firmware files found. Please download the firmware first.")
|
44
61
|
return []
|
45
62
|
# filter by version
|
46
|
-
version = clean_version(version
|
63
|
+
version = clean_version(version)
|
47
64
|
fw_list = filter_downloaded_fwlist(fw_list, board_id, version, port, variants, selector)
|
48
65
|
|
49
66
|
if not fw_list and trie < 3:
|
50
67
|
log.info(f"Try ({trie+1}) to find a firmware for the board {board_id}")
|
51
68
|
if trie == 1:
|
52
|
-
# ESP board naming conventions have changed by adding a PORT
|
69
|
+
# ESP board naming conventions have changed by adding a PORT prefix
|
53
70
|
if port.startswith("esp") and not board_id.startswith(port.upper()):
|
54
71
|
board_id = f"{port.upper()}_{board_id}"
|
55
|
-
# RP2 board naming conventions have changed by adding a
|
72
|
+
# RP2 board naming conventions have changed by adding a _RPI prefix
|
56
73
|
if port == "rp2" and not board_id.startswith("RPI_"):
|
57
74
|
board_id = f"RPI_{board_id}"
|
58
75
|
elif trie == 2:
|
@@ -75,7 +92,7 @@ def find_downloaded_firmware(
|
|
75
92
|
def filter_downloaded_fwlist(
|
76
93
|
fw_list: List[FWInfo],
|
77
94
|
board_id: str,
|
78
|
-
version: str,
|
95
|
+
version: str, # v1.2.3
|
79
96
|
port: str,
|
80
97
|
# preview: bool,
|
81
98
|
variants: bool,
|
@@ -86,11 +103,14 @@ def filter_downloaded_fwlist(
|
|
86
103
|
# never get a preview for an older version
|
87
104
|
fw_list = [fw for fw in fw_list if fw.preview]
|
88
105
|
else:
|
89
|
-
|
90
|
-
|
106
|
+
# older FWInfo version has no v1.2.3 prefix
|
107
|
+
either = [clean_version(version, drop_v=False), clean_version(version, drop_v=True)]
|
108
|
+
fw_list = [fw for fw in fw_list if fw.version in either]
|
109
|
+
log.trace(f"Filtering firmware for {version} : {len(fw_list)} found.")
|
91
110
|
# filter by port
|
92
111
|
if port:
|
93
112
|
fw_list = [fw for fw in fw_list if fw.port == port]
|
113
|
+
log.trace(f"Filtering firmware for {port} : {len(fw_list)} found.")
|
94
114
|
|
95
115
|
if board_id:
|
96
116
|
if variants:
|
@@ -99,6 +119,7 @@ def filter_downloaded_fwlist(
|
|
99
119
|
else:
|
100
120
|
# the firmware variant should match exactly the board_id
|
101
121
|
fw_list = [fw for fw in fw_list if fw.variant == board_id]
|
122
|
+
log.trace(f"Filtering firmware for {board_id} : {len(fw_list)} found.")
|
102
123
|
if selector and port in selector:
|
103
124
|
fw_list = [fw for fw in fw_list if fw.filename.endswith(selector[port])]
|
104
125
|
return fw_list
|
mpflash/mpflash/errors.py
CHANGED
mpflash/mpflash/flash.py
CHANGED
@@ -1,10 +1,9 @@
|
|
1
|
-
import time
|
2
1
|
from pathlib import Path
|
3
2
|
|
4
3
|
from loguru import logger as log
|
5
4
|
|
6
|
-
from mpflash.
|
7
|
-
from mpflash.
|
5
|
+
from mpflash.bootloader import enter_bootloader
|
6
|
+
from mpflash.common import PORT_FWTYPES, BootloaderMethod
|
8
7
|
|
9
8
|
from .flash_esp import flash_esp
|
10
9
|
from .flash_stm32 import flash_stm32
|
@@ -14,13 +13,15 @@ from .worklist import WorkList
|
|
14
13
|
# #########################################################################################################
|
15
14
|
|
16
15
|
|
16
|
+
|
17
17
|
def flash_list(
|
18
18
|
todo: WorkList,
|
19
19
|
fw_folder: Path,
|
20
20
|
erase: bool,
|
21
|
-
bootloader:
|
21
|
+
bootloader: BootloaderMethod,
|
22
22
|
):
|
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]
|
24
25
|
flashed = []
|
25
26
|
for mcu, fw_info in todo:
|
26
27
|
fw_file = fw_folder / fw_info.filename
|
@@ -30,14 +31,13 @@ def flash_list(
|
|
30
31
|
log.info(f"Updating {mcu.board} on {mcu.serialport} to {fw_info.version}")
|
31
32
|
updated = None
|
32
33
|
# try:
|
33
|
-
if mcu.port in
|
34
|
-
|
35
|
-
|
36
|
-
enter_bootloader(mcu)
|
34
|
+
if mcu.port in UF2_PORTS and fw_file.suffix == ".uf2":
|
35
|
+
if not enter_bootloader(mcu, bootloader):
|
36
|
+
continue
|
37
37
|
updated = flash_uf2(mcu, fw_file=fw_file, erase=erase)
|
38
38
|
elif mcu.port in ["stm32"]:
|
39
|
-
if bootloader:
|
40
|
-
|
39
|
+
if not enter_bootloader(mcu, bootloader):
|
40
|
+
continue
|
41
41
|
updated = flash_stm32(mcu, fw_file, erase=erase)
|
42
42
|
elif mcu.port in ["esp32", "esp8266"]:
|
43
43
|
# bootloader is handled by esptool for esp32/esp8266
|
@@ -50,20 +50,3 @@ def flash_list(
|
|
50
50
|
else:
|
51
51
|
log.error(f"Failed to flash {mcu.board} on {mcu.serialport}")
|
52
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
|
mpflash/mpflash/flash_uf2.py
CHANGED
@@ -14,7 +14,7 @@ from rich.progress import track
|
|
14
14
|
from mpflash.mpremoteboard import MPRemoteBoard
|
15
15
|
|
16
16
|
from .common import PORT_FWTYPES
|
17
|
-
from .
|
17
|
+
from .flash_uf2_boardid import get_board_id
|
18
18
|
from .flash_uf2_linux import dismount_uf2_linux, wait_for_UF2_linux
|
19
19
|
from .flash_uf2_macos import wait_for_UF2_macos
|
20
20
|
from .flash_uf2_windows import wait_for_UF2_windows
|
@@ -45,11 +45,7 @@ def flash_uf2(mcu: MPRemoteBoard, fw_file: Path, erase: bool) -> Optional[MPRemo
|
|
45
45
|
destination = wait_for_UF2_windows()
|
46
46
|
elif sys.platform == "darwin":
|
47
47
|
log.warning(f"OS {sys.platform} not tested/supported")
|
48
|
-
|
49
|
-
if "macos_uf2" in config.tests:
|
50
|
-
destination = wait_for_UF2_macos()
|
51
|
-
else:
|
52
|
-
destination = wait_for_UF2_linux()
|
48
|
+
destination = wait_for_UF2_macos()
|
53
49
|
else:
|
54
50
|
log.warning(f"OS {sys.platform} not tested/supported")
|
55
51
|
return None
|
@@ -59,6 +55,8 @@ def flash_uf2(mcu: MPRemoteBoard, fw_file: Path, erase: bool) -> Optional[MPRemo
|
|
59
55
|
return None
|
60
56
|
|
61
57
|
log.info("Board is in bootloader mode")
|
58
|
+
board_id = get_board_id(destination) # type: ignore
|
59
|
+
log.info(f"Board ID: {board_id}")
|
62
60
|
log.info(f"Copying {fw_file} to {destination}.")
|
63
61
|
shutil.copy(fw_file, destination)
|
64
62
|
log.success("Done copying, resetting the board and wait for it to restart")
|
@@ -1,4 +1,5 @@
|
|
1
1
|
from pathlib import Path
|
2
|
+
|
2
3
|
from loguru import logger as log
|
3
4
|
|
4
5
|
|
@@ -10,5 +11,5 @@ def get_board_id(path: Path):
|
|
10
11
|
for line in data:
|
11
12
|
if line.startswith("Board-ID"):
|
12
13
|
board_id = line[9:].strip()
|
13
|
-
log.
|
14
|
+
log.debug(f"INFO_UF2.TXT Board-ID={board_id}")
|
14
15
|
return board_id
|
@@ -3,76 +3,32 @@
|
|
3
3
|
# sourcery skip: snake-case-functions
|
4
4
|
from __future__ import annotations
|
5
5
|
|
6
|
-
import sys
|
7
6
|
import time
|
8
7
|
from pathlib import Path
|
8
|
+
from typing import Optional
|
9
9
|
|
10
|
-
from loguru import logger as log
|
11
10
|
from rich.progress import track
|
12
11
|
|
13
|
-
from .flash_uf2_boardid import get_board_id
|
14
|
-
from .uf2disk import UF2Disk
|
15
12
|
|
16
13
|
|
17
|
-
def
|
18
|
-
"""
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
log.error("pumount only works on Linux")
|
23
|
-
return
|
24
|
-
# import blkinfo only on linux
|
25
|
-
from blkinfo import BlkDiskInfo
|
26
|
-
|
27
|
-
myblkd = BlkDiskInfo()
|
28
|
-
filters = {
|
29
|
-
"tran": "usb",
|
30
|
-
}
|
31
|
-
usb_disks = myblkd.get_disks(filters)
|
32
|
-
for disk in usb_disks:
|
33
|
-
if disk["fstype"] == "vfat":
|
34
|
-
uf2_part = disk
|
35
|
-
# unpartioned usb disk or partition (e.g. /dev/sdb )
|
36
|
-
# SEEED WIO Terminal is unpartioned
|
37
|
-
# print( json.dumps(uf2_part, indent=4))
|
38
|
-
uf2 = UF2Disk()
|
39
|
-
uf2.device_path = "/dev/" + uf2_part["name"]
|
40
|
-
uf2.label = uf2_part["label"]
|
41
|
-
uf2.mountpoint = uf2_part["mountpoint"]
|
42
|
-
yield uf2
|
43
|
-
elif disk["type"] == "disk" and disk.get("children") and len(disk.get("children")) > 0:
|
44
|
-
if disk.get("children")[0]["type"] == "part" and disk.get("children")[0]["fstype"] == "vfat":
|
45
|
-
uf2_part = disk.get("children")[0]
|
46
|
-
# print( json.dumps(uf2_part, indent=4))
|
47
|
-
uf2 = UF2Disk()
|
48
|
-
uf2.device_path = "/dev/" + uf2_part["name"]
|
49
|
-
uf2.label = uf2_part["label"]
|
50
|
-
uf2.mountpoint = uf2_part["mountpoint"]
|
51
|
-
yield uf2
|
52
|
-
|
53
|
-
|
54
|
-
def wait_for_UF2_macos(s_max: int = 10):
|
55
|
-
destination = ""
|
56
|
-
wait = 10
|
57
|
-
uf2_drives = []
|
58
|
-
# while not destination and wait > 0:
|
14
|
+
def wait_for_UF2_macos(s_max: int = 10) -> Optional[Path]:
|
15
|
+
"""Wait for the MCU to mount as a drive"""
|
16
|
+
if s_max < 1:
|
17
|
+
s_max = 10
|
18
|
+
destination = None
|
59
19
|
for _ in track(
|
60
20
|
range(s_max), description="Waiting for mcu to mount as a drive", transient=True, refresh_per_second=2
|
61
21
|
):
|
62
|
-
# log.info(f"Waiting for mcu to mount as a drive : {
|
63
|
-
|
64
|
-
for
|
65
|
-
time.sleep(1)
|
22
|
+
# log.info(f"Waiting for mcu to mount as a drive : {n} seconds left")
|
23
|
+
vol_mounts = Path("/Volumes").iterdir()
|
24
|
+
for vol in vol_mounts:
|
66
25
|
try:
|
67
|
-
if Path(
|
68
|
-
|
69
|
-
destination = Path(drive.mountpoint)
|
26
|
+
if Path(vol, "INFO_UF2.TXT").exists():
|
27
|
+
destination = Path(vol)
|
70
28
|
break
|
71
|
-
except
|
72
|
-
|
73
|
-
continue
|
29
|
+
except OSError:
|
30
|
+
pass
|
74
31
|
if destination:
|
75
32
|
break
|
76
33
|
time.sleep(1)
|
77
|
-
wait -= 1
|
78
34
|
return destination
|
@@ -5,25 +5,25 @@ from __future__ import annotations
|
|
5
5
|
|
6
6
|
import time
|
7
7
|
from pathlib import Path
|
8
|
+
from typing import Optional
|
8
9
|
|
9
10
|
import psutil
|
10
11
|
from rich.progress import track
|
11
12
|
|
12
|
-
from .flash_uf2_boardid import get_board_id
|
13
13
|
|
14
14
|
|
15
|
-
|
15
|
+
|
16
|
+
def wait_for_UF2_windows(s_max: int = 10) -> Optional[Path]:
|
16
17
|
"""Wait for the MCU to mount as a drive"""
|
17
18
|
if s_max < 1:
|
18
19
|
s_max = 10
|
19
|
-
destination =
|
20
|
+
destination = None
|
20
21
|
for _ in track(range(s_max), description="Waiting for mcu to mount as a drive", transient=True,refresh_per_second=2):
|
21
22
|
# log.info(f"Waiting for mcu to mount as a drive : {n} seconds left")
|
22
23
|
drives = [drive.device for drive in psutil.disk_partitions()]
|
23
24
|
for drive in drives:
|
24
25
|
try:
|
25
26
|
if Path(drive, "INFO_UF2.TXT").exists():
|
26
|
-
board_id = get_board_id(Path(drive)) # type: ignore
|
27
27
|
destination = Path(drive)
|
28
28
|
break
|
29
29
|
except OSError:
|
@@ -77,10 +77,14 @@ def known_stored_boards(port: str, versions: Optional[List[str]] = None) -> List
|
|
77
77
|
@lru_cache(maxsize=20)
|
78
78
|
def find_known_board(board_id: str) -> Board:
|
79
79
|
"""Find the board for the given BOARD_ID or 'board description' and return the board info as a Board object"""
|
80
|
+
# FIXME : functional overlap with:
|
81
|
+
# mpboard_id\board_id.py _find_board_id_by_description
|
80
82
|
info = read_known_boardinfo()
|
81
83
|
for board_info in info:
|
82
84
|
if board_id in (board_info.board_id, board_info.description):
|
83
85
|
if not board_info.cpu:
|
86
|
+
# safeguard for older board_info.json files
|
87
|
+
print(f"Board {board_id} has no CPU info, using port as CPU")
|
84
88
|
if " with " in board_info.description:
|
85
89
|
board_info.cpu = board_info.description.split(" with ")[-1]
|
86
90
|
else:
|