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.
- mpflash/ask_input.py +7 -7
- mpflash/basicgit.py +23 -57
- mpflash/bootloader/__init__.py +0 -2
- mpflash/bootloader/activate.py +1 -1
- mpflash/bootloader/detect.py +1 -2
- mpflash/bootloader/manual.py +0 -1
- mpflash/bootloader/touch1200.py +2 -2
- mpflash/cli_flash.py +28 -5
- mpflash/cli_group.py +1 -0
- mpflash/cli_list.py +2 -2
- mpflash/cli_main.py +1 -1
- mpflash/common.py +6 -14
- mpflash/config.py +26 -7
- mpflash/connected.py +6 -14
- mpflash/download.py +56 -23
- mpflash/downloaded.py +1 -5
- mpflash/flash/__init__.py +33 -18
- mpflash/flash/esp.py +40 -9
- mpflash/flash/uf2/__init__.py +18 -2
- mpflash/flash/uf2/linux.py +4 -9
- mpflash/flash/uf2/macos.py +1 -1
- mpflash/flash/uf2/windows.py +1 -1
- mpflash/flash/worklist.py +7 -2
- mpflash/list.py +17 -6
- mpflash/logger.py +1 -3
- mpflash/mpboard_id/__init__.py +6 -87
- mpflash/mpboard_id/add_boards.py +6 -8
- mpflash/mpboard_id/board_id.py +7 -6
- mpflash/mpboard_id/board_info.json +30974 -0
- mpflash/mpboard_id/board_info.zip +0 -0
- mpflash/mpboard_id/known.py +94 -0
- mpflash/mpboard_id/store.py +0 -2
- mpflash/mpremoteboard/__init__.py +13 -9
- mpflash/mpremoteboard/mpy_fw_info.py +14 -17
- mpflash/py.typed +0 -0
- mpflash/vendor/click_aliases.py +64 -0
- mpflash/vendor/dfu.py +2 -8
- mpflash/vendor/pico-universal-flash-nuke/LICENSE.txt +21 -0
- mpflash/vendor/pico-universal-flash-nuke/universal_flash_nuke.uf2 +0 -0
- mpflash/vendor/pydfu.py +3 -14
- mpflash/vendor/readme.md +2 -0
- mpflash/versions.py +9 -6
- {mpflash-1.24.6.dist-info → mpflash-1.24.8.dist-info}/METADATA +71 -13
- mpflash-1.24.8.dist-info/RECORD +59 -0
- {mpflash-1.24.6.dist-info → mpflash-1.24.8.dist-info}/WHEEL +1 -1
- mpflash-1.24.6.dist-info/RECORD +0 -54
- {mpflash-1.24.6.dist-info → mpflash-1.24.8.dist-info}/LICENSE +0 -0
- {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.
|
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.
|
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
|
-
|
228
|
+
available_firmwares = get_firmware_list(ports, boards, versions, clean)
|
220
229
|
|
221
|
-
for b in
|
230
|
+
for b in available_firmwares:
|
222
231
|
log.debug(b.filename)
|
223
232
|
# relevant
|
224
233
|
|
225
|
-
log.info(f"Found {len(
|
226
|
-
if not
|
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
|
-
|
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
|
-
|
255
|
-
|
256
|
-
|
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
|
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 :
|
266
|
-
boards :
|
267
|
-
versions :
|
268
|
-
clean :
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
49
|
-
|
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(
|
18
|
-
|
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()
|
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
|
-
|
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 {
|
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 {
|
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)} ")
|
mpflash/flash/uf2/__init__.py
CHANGED
@@ -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
|
-
|
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.
|
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)
|
mpflash/flash/uf2/linux.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
"""
|
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 =
|
87
|
+
disk.mountpoint = ""
|
93
88
|
else:
|
94
89
|
log.warning(f"{disk.label} already dismounted")
|
95
90
|
|
mpflash/flash/uf2/macos.py
CHANGED
mpflash/flash/uf2/windows.py
CHANGED
@@ -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],
|
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": "
|
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=
|
63
|
-
header_style=
|
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
|
-
|
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):
|
mpflash/mpboard_id/__init__.py
CHANGED
@@ -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
|
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")
|