micropython-stubber 1.20.1__py3-none-any.whl → 1.20.4__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.1.dist-info → micropython_stubber-1.20.4.dist-info}/METADATA +4 -3
- {micropython_stubber-1.20.1.dist-info → micropython_stubber-1.20.4.dist-info}/RECORD +58 -51
- {micropython_stubber-1.20.1.dist-info → micropython_stubber-1.20.4.dist-info}/WHEEL +1 -1
- mpflash/README.md +16 -5
- mpflash/mpflash/add_firmware.py +98 -0
- mpflash/mpflash/ask_input.py +97 -120
- mpflash/mpflash/cli_download.py +42 -25
- mpflash/mpflash/cli_flash.py +70 -32
- mpflash/mpflash/cli_group.py +17 -14
- mpflash/mpflash/cli_list.py +39 -3
- mpflash/mpflash/cli_main.py +17 -6
- mpflash/mpflash/common.py +125 -12
- mpflash/mpflash/config.py +12 -0
- mpflash/mpflash/connected.py +74 -0
- mpflash/mpflash/download.py +132 -51
- mpflash/mpflash/downloaded.py +36 -15
- mpflash/mpflash/flash.py +2 -2
- mpflash/mpflash/flash_esp.py +2 -2
- mpflash/mpflash/flash_uf2.py +14 -8
- mpflash/mpflash/flash_uf2_boardid.py +2 -1
- mpflash/mpflash/flash_uf2_linux.py +5 -16
- mpflash/mpflash/flash_uf2_macos.py +37 -0
- mpflash/mpflash/flash_uf2_windows.py +5 -5
- mpflash/mpflash/list.py +57 -57
- mpflash/mpflash/mpboard_id/__init__.py +41 -44
- mpflash/mpflash/mpboard_id/add_boards.py +255 -0
- mpflash/mpflash/mpboard_id/board.py +37 -0
- mpflash/mpflash/mpboard_id/board_id.py +54 -34
- mpflash/mpflash/mpboard_id/board_info.zip +0 -0
- mpflash/mpflash/mpboard_id/store.py +43 -0
- mpflash/mpflash/mpremoteboard/__init__.py +18 -6
- mpflash/mpflash/uf2disk.py +12 -0
- mpflash/mpflash/vendor/basicgit.py +288 -0
- mpflash/mpflash/vendor/dfu.py +1 -0
- mpflash/mpflash/vendor/versions.py +7 -3
- mpflash/mpflash/worklist.py +71 -48
- mpflash/poetry.lock +164 -138
- mpflash/pyproject.toml +18 -15
- stubber/__init__.py +1 -1
- stubber/board/createstubs.py +13 -3
- stubber/board/createstubs_db.py +5 -7
- stubber/board/createstubs_db_min.py +329 -825
- stubber/board/createstubs_db_mpy.mpy +0 -0
- stubber/board/createstubs_mem.py +6 -7
- stubber/board/createstubs_mem_min.py +304 -765
- stubber/board/createstubs_mem_mpy.mpy +0 -0
- stubber/board/createstubs_min.py +293 -975
- stubber/board/createstubs_mpy.mpy +0 -0
- stubber/board/modulelist.txt +10 -0
- stubber/commands/get_core_cmd.py +7 -6
- stubber/commands/get_docstubs_cmd.py +8 -3
- stubber/commands/get_frozen_cmd.py +5 -2
- stubber/publish/publish.py +18 -7
- stubber/update_module_list.py +2 -24
- stubber/utils/makeversionhdr.py +3 -2
- stubber/utils/versions.py +2 -1
- mpflash/mpflash/mpboard_id/board_info.csv +0 -2213
- mpflash/mpflash/mpboard_id/board_info.json +0 -19910
- {micropython_stubber-1.20.1.dist-info → micropython_stubber-1.20.4.dist-info}/LICENSE +0 -0
- {micropython_stubber-1.20.1.dist-info → micropython_stubber-1.20.4.dist-info}/entry_points.txt +0 -0
mpflash/mpflash/download.py
CHANGED
@@ -18,9 +18,13 @@ from bs4 import BeautifulSoup
|
|
18
18
|
from loguru import logger as log
|
19
19
|
from rich.progress import track
|
20
20
|
|
21
|
-
from mpflash.common import PORT_FWTYPES
|
21
|
+
from mpflash.common import PORT_FWTYPES, FWInfo
|
22
|
+
from mpflash.downloaded import clean_downloaded_firmwares
|
22
23
|
from mpflash.errors import MPFlashError
|
24
|
+
from mpflash.mpboard_id import get_known_ports
|
25
|
+
from mpflash.vendor.versions import clean_version
|
23
26
|
|
27
|
+
# avoid conflict with the ujson used by MicroPython
|
24
28
|
jsonlines.ujson = None # type: ignore
|
25
29
|
# #########################################################################################################
|
26
30
|
|
@@ -30,8 +34,18 @@ MICROPYTHON_ORG_URL = "https://micropython.org/"
|
|
30
34
|
# Regexes to remove dates and hashes in the filename that just get in the way
|
31
35
|
RE_DATE = r"(-\d{8}-)"
|
32
36
|
RE_HASH = r"(.g[0-9a-f]+\.)"
|
33
|
-
# regex to extract the version from the firmware filename
|
34
|
-
|
37
|
+
# regex to extract the version and the build from the firmware filename
|
38
|
+
# group 1 is the version, group 2 is the build
|
39
|
+
RE_VERSION_PREVIEW = r"v([\d\.]+)-?(?:preview\.)?(\d+)?\."
|
40
|
+
# 'RPI_PICO_W-v1.23.uf2'
|
41
|
+
# 'RPI_PICO_W-v1.23.0.uf2'
|
42
|
+
# 'RPI_PICO_W-v1.23.0-406.uf2'
|
43
|
+
# 'RPI_PICO_W-v1.23.0-preview.406.uf2'
|
44
|
+
# 'RPI_PICO_W-v1.23.0-preview.4.uf2'
|
45
|
+
# 'RPI_PICO_W-v1.23.0.uf2'
|
46
|
+
# 'https://micropython.org/resources/firmware/RPI_PICO_W-20240531-v1.24.0-preview.10.gc1a6b95bf.uf2'
|
47
|
+
# 'https://micropython.org/resources/firmware/RPI_PICO_W-20240531-v1.24.0-preview.10.uf2'
|
48
|
+
# 'RPI_PICO_W-v1.24.0-preview.10.gc1a6b95bf.uf2'
|
35
49
|
|
36
50
|
|
37
51
|
# use functools.lru_cache to avoid needing to download pages multiple times
|
@@ -50,6 +64,10 @@ def get_board_urls(page_url: str) -> List[Dict[str, str]]:
|
|
50
64
|
|
51
65
|
Args:
|
52
66
|
page_url (str): The url of the page to get the board urls from.
|
67
|
+
|
68
|
+
Returns:
|
69
|
+
List[Dict[str, str]]: A list of dictionaries containing the board name and url.
|
70
|
+
|
53
71
|
"""
|
54
72
|
downloads_html = get_page(page_url)
|
55
73
|
soup = BeautifulSoup(downloads_html, "html.parser")
|
@@ -88,14 +106,11 @@ def board_firmware_urls(board_url: str, base_url: str, ext: str) -> List[str]:
|
|
88
106
|
return links
|
89
107
|
|
90
108
|
|
91
|
-
# type alias for the firmware info
|
92
|
-
FirmwareInfo = Dict[str, str]
|
93
|
-
|
94
|
-
|
95
109
|
# boards we are interested in ( this avoids getting a lot of boards we don't care about)
|
96
110
|
# The first run takes ~60 seconds to run for 4 ports , all boards
|
97
111
|
# so it makes sense to cache the results and skip boards as soon as possible
|
98
|
-
def get_boards(ports: List[str], boards: List[str], clean: bool) -> List[
|
112
|
+
def get_boards(ports: List[str], boards: List[str], clean: bool) -> List[FWInfo]:
|
113
|
+
# sourcery skip: use-getitem-for-re-match-groups
|
99
114
|
"""
|
100
115
|
Retrieves a list of firmware information for the specified ports and boards.
|
101
116
|
|
@@ -105,57 +120,70 @@ def get_boards(ports: List[str], boards: List[str], clean: bool) -> List[Firmwar
|
|
105
120
|
clean (bool): A flag indicating whether to perform a clean retrieval.
|
106
121
|
|
107
122
|
Returns:
|
108
|
-
List[
|
123
|
+
List[FWInfo]: A list of firmware information for the specified ports and boards.
|
109
124
|
|
110
125
|
"""
|
111
|
-
board_urls: List[
|
126
|
+
board_urls: List[FWInfo] = []
|
127
|
+
if ports is None:
|
128
|
+
ports = get_known_ports()
|
112
129
|
for port in ports:
|
113
130
|
download_page_url = f"{MICROPYTHON_ORG_URL}download/?port={port}"
|
114
|
-
|
131
|
+
urls = get_board_urls(download_page_url)
|
115
132
|
# filter out boards we don't care about
|
116
|
-
|
133
|
+
urls = [board for board in urls if board["board"] in boards]
|
117
134
|
# add the port to the board urls
|
118
|
-
for board in
|
135
|
+
for board in urls:
|
119
136
|
board["port"] = port
|
120
137
|
|
121
|
-
for board in track(
|
138
|
+
for board in track(urls, description=f"Checking {port} download pages", transient=True, refresh_per_second=2):
|
122
139
|
# add a board to the list for each firmware found
|
123
|
-
|
140
|
+
firmware_urls: List[str] = []
|
124
141
|
for ext in PORT_FWTYPES[port]:
|
125
|
-
|
126
|
-
|
127
|
-
for _url in firmwares:
|
142
|
+
firmware_urls += board_firmware_urls(board["url"], MICROPYTHON_ORG_URL, ext)
|
143
|
+
for _url in firmware_urls:
|
128
144
|
board["firmware"] = _url
|
129
|
-
board["preview"] = "preview" in _url # type: ignore
|
130
|
-
if ver_match := re.search(RE_VERSION_PREVIEW, _url):
|
131
|
-
board["version"] = ver_match[1]
|
132
|
-
else:
|
133
|
-
board["version"] = ""
|
134
|
-
if "preview." in board["version"]:
|
135
|
-
board["build"] = board["version"].split("preview.")[-1]
|
136
|
-
else:
|
137
|
-
board["build"] = "0"
|
138
145
|
fname = Path(board["firmware"]).name
|
139
146
|
if clean:
|
140
147
|
# remove date from firmware name
|
141
148
|
fname = re.sub(RE_DATE, "-", fname)
|
142
149
|
# remove hash from firmware name
|
143
150
|
fname = re.sub(RE_HASH, ".", fname)
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
151
|
+
fw_info = FWInfo(
|
152
|
+
filename=fname,
|
153
|
+
port=port,
|
154
|
+
board=board["board"],
|
155
|
+
preview="preview" in _url,
|
156
|
+
firmware=_url,
|
157
|
+
version="",
|
158
|
+
)
|
159
|
+
# board["firmware"] = _url
|
160
|
+
# board["preview"] = "preview" in _url # type: ignore
|
161
|
+
if ver_match := re.search(RE_VERSION_PREVIEW, _url):
|
162
|
+
fw_info.version = clean_version(ver_match.group(1))
|
163
|
+
fw_info.build = ver_match.group(2) or "0"
|
164
|
+
fw_info.preview = fw_info.build != "0"
|
165
|
+
# # else:
|
166
|
+
# # board.$1= ""
|
167
|
+
# if "preview." in fw_info.version:
|
168
|
+
# fw_info.build = fw_info.version.split("preview.")[-1]
|
169
|
+
# else:
|
170
|
+
# fw_info.build = "0"
|
171
|
+
|
172
|
+
fw_info.ext = Path(fw_info.firmware).suffix
|
173
|
+
fw_info.variant = fw_info.filename.split("-v")[0] if "-v" in fw_info.filename else ""
|
174
|
+
|
175
|
+
board_urls.append(fw_info)
|
148
176
|
return board_urls
|
149
177
|
|
150
178
|
|
151
|
-
def key_fw_ver_pre_ext_bld(x:
|
179
|
+
def key_fw_ver_pre_ext_bld(x: FWInfo):
|
152
180
|
"sorting key for the retrieved board urls"
|
153
|
-
return x
|
181
|
+
return x.variant, x.version, x.preview, x.ext, x.build
|
154
182
|
|
155
183
|
|
156
|
-
def key_fw_var_pre_ext(x:
|
184
|
+
def key_fw_var_pre_ext(x: FWInfo):
|
157
185
|
"Grouping key for the retrieved board urls"
|
158
|
-
return x
|
186
|
+
return x.variant, x.preview, x.ext
|
159
187
|
|
160
188
|
|
161
189
|
def download_firmwares(
|
@@ -167,40 +195,60 @@ def download_firmwares(
|
|
167
195
|
force: bool = False,
|
168
196
|
clean: bool = True,
|
169
197
|
) -> int:
|
198
|
+
"""
|
199
|
+
Downloads firmware files based on the specified firmware folder, ports, boards, versions, force flag, and clean flag.
|
200
|
+
|
201
|
+
Args:
|
202
|
+
firmware_folder : The folder to save the downloaded firmware files.
|
203
|
+
ports : The list of ports to check for firmware.
|
204
|
+
boards : The list of boards to download firmware for.
|
205
|
+
versions : The list of versions to download firmware for.
|
206
|
+
force : A flag indicating whether to force the download even if the firmware file already exists.
|
207
|
+
clean : A flag indicating to clean the date from the firmware filename.
|
208
|
+
"""
|
170
209
|
skipped = downloaded = 0
|
171
|
-
if versions is None
|
172
|
-
|
210
|
+
versions = [] if versions is None else [clean_version(v) for v in versions]
|
211
|
+
# handle renamed boards
|
212
|
+
boards = add_renamed_boards(boards)
|
213
|
+
|
173
214
|
unique_boards = get_firmware_list(ports, boards, versions, clean)
|
174
215
|
|
175
216
|
for b in unique_boards:
|
176
|
-
log.debug(b
|
217
|
+
log.debug(b.filename)
|
177
218
|
# relevant
|
178
219
|
|
179
220
|
log.info(f"Found {len(unique_boards)} relevant unique firmwares")
|
221
|
+
if not unique_boards:
|
222
|
+
log.error("No relevant firmwares could be found on https://micropython.org/download")
|
223
|
+
log.info(f"{versions=} {ports=} {boards=}")
|
224
|
+
log.info("Please check the website for the latest firmware files or try the preview version.")
|
225
|
+
return 0
|
180
226
|
|
181
227
|
firmware_folder.mkdir(exist_ok=True)
|
182
228
|
|
183
229
|
with jsonlines.open(firmware_folder / "firmware.jsonl", "a") as writer:
|
184
230
|
for board in unique_boards:
|
185
|
-
filename = firmware_folder / board
|
231
|
+
filename = firmware_folder / board.port / board.filename
|
186
232
|
filename.parent.mkdir(exist_ok=True)
|
187
233
|
if filename.exists() and not force:
|
188
234
|
skipped += 1
|
189
235
|
log.debug(f" {filename} already exists, skip download")
|
190
236
|
continue
|
191
|
-
log.info(f"Downloading {board
|
237
|
+
log.info(f"Downloading {board.firmware}")
|
192
238
|
log.info(f" to {filename}")
|
193
239
|
try:
|
194
|
-
r = requests.get(board
|
240
|
+
r = requests.get(board.firmware, allow_redirects=True)
|
195
241
|
with open(filename, "wb") as fw:
|
196
242
|
fw.write(r.content)
|
197
|
-
board
|
243
|
+
board.filename = str(filename.relative_to(firmware_folder))
|
198
244
|
except requests.RequestException as e:
|
199
245
|
log.exception(e)
|
200
246
|
continue
|
201
|
-
writer.write(board)
|
247
|
+
writer.write(board.to_dict())
|
202
248
|
downloaded += 1
|
203
|
-
|
249
|
+
if downloaded > 0:
|
250
|
+
clean_downloaded_firmwares(firmware_folder)
|
251
|
+
log.success(f"Downloaded {downloaded} firmwares, skipped {skipped} existing files.")
|
204
252
|
return downloaded + skipped
|
205
253
|
|
206
254
|
|
@@ -215,7 +263,7 @@ def get_firmware_list(ports: List[str], boards: List[str], versions: List[str],
|
|
215
263
|
clean : A flag indicating whether to perform a clean check.
|
216
264
|
|
217
265
|
Returns:
|
218
|
-
List[
|
266
|
+
List[FWInfo]: A list of unique firmware information.
|
219
267
|
|
220
268
|
"""
|
221
269
|
|
@@ -224,15 +272,18 @@ def get_firmware_list(ports: List[str], boards: List[str], versions: List[str],
|
|
224
272
|
board_urls = sorted(get_boards(ports, boards, clean), key=key_fw_ver_pre_ext_bld)
|
225
273
|
|
226
274
|
log.debug(f"Total {len(board_urls)} firmwares")
|
275
|
+
|
227
276
|
relevant = [
|
228
277
|
board
|
229
278
|
for board in board_urls
|
230
|
-
if board
|
231
|
-
# and b["port"] in ["esp32", "rp2"]
|
279
|
+
if board.version in versions and board.build == "0" and board.board in boards and not board.preview
|
232
280
|
]
|
281
|
+
|
282
|
+
if preview:
|
283
|
+
relevant.extend([board for board in board_urls if board.board in boards and board.preview])
|
233
284
|
log.debug(f"Matching firmwares: {len(relevant)}")
|
234
285
|
# select the unique boards
|
235
|
-
unique_boards: List[
|
286
|
+
unique_boards: List[FWInfo] = []
|
236
287
|
for _, g in itertools.groupby(relevant, key=key_fw_var_pre_ext):
|
237
288
|
# list is aleady sorted by build so we can just get the last item
|
238
289
|
sub_list = list(g)
|
@@ -258,7 +309,7 @@ def download(
|
|
258
309
|
boards : The list of boards to download firmware for.
|
259
310
|
versions : The list of versions to download firmware for.
|
260
311
|
force : A flag indicating whether to force the download even if the firmware file already exists.
|
261
|
-
clean : A flag indicating whether to
|
312
|
+
clean : A flag indicating whether to clean the date from the firmware filename.
|
262
313
|
|
263
314
|
Returns:
|
264
315
|
int: The number of downloaded firmware files.
|
@@ -270,11 +321,41 @@ def download(
|
|
270
321
|
if not boards:
|
271
322
|
log.critical("No boards found, please connect a board or specify boards to download firmware for.")
|
272
323
|
raise MPFlashError("No boards found")
|
273
|
-
|
324
|
+
|
274
325
|
try:
|
275
326
|
destination.mkdir(exist_ok=True, parents=True)
|
276
327
|
except (PermissionError, FileNotFoundError) as e:
|
277
328
|
log.critical(f"Could not create folder {destination}")
|
278
329
|
raise MPFlashError(f"Could not create folder {destination}") from e
|
330
|
+
try:
|
331
|
+
result = download_firmwares(destination, ports, boards, versions, force=force, clean=clean)
|
332
|
+
except requests.exceptions.RequestException as e:
|
333
|
+
log.exception(e)
|
334
|
+
raise MPFlashError("Could not connect to micropython.org") from e
|
335
|
+
|
336
|
+
return result
|
337
|
+
|
279
338
|
|
280
|
-
|
339
|
+
def add_renamed_boards(boards: List[str]) -> List[str]:
|
340
|
+
"""
|
341
|
+
Adds the renamed boards to the list of boards.
|
342
|
+
|
343
|
+
Args:
|
344
|
+
boards : The list of boards to add the renamed boards to.
|
345
|
+
|
346
|
+
Returns:
|
347
|
+
List[str]: The list of boards with the renamed boards added.
|
348
|
+
|
349
|
+
"""
|
350
|
+
renamed = {
|
351
|
+
"PICO": ["RPI_PICO"],
|
352
|
+
"PICO_W": ["RPI_PICO_W"],
|
353
|
+
"GENERIC": ["ESP32_GENERIC", "ESP8266_GENERIC"], # just add both of them
|
354
|
+
}
|
355
|
+
_boards = boards.copy()
|
356
|
+
for board in boards:
|
357
|
+
if board in renamed and renamed[board] not in boards:
|
358
|
+
_boards.extend(renamed[board])
|
359
|
+
if board != board.upper() and board.upper() not in boards:
|
360
|
+
_boards.append(board.upper())
|
361
|
+
return _boards
|
mpflash/mpflash/downloaded.py
CHANGED
@@ -16,18 +16,34 @@ def downloaded_firmwares(fw_folder: Path) -> List[FWInfo]:
|
|
16
16
|
firmwares: List[FWInfo] = []
|
17
17
|
try:
|
18
18
|
with jsonlines.open(fw_folder / "firmware.jsonl") as reader:
|
19
|
-
firmwares.
|
19
|
+
firmwares = [FWInfo.from_dict(item) for item in reader]
|
20
20
|
except FileNotFoundError:
|
21
21
|
log.error(f"No firmware.jsonl found in {fw_folder}")
|
22
22
|
# sort by filename
|
23
|
-
firmwares.sort(key=lambda x: x
|
23
|
+
firmwares.sort(key=lambda x: x.filename)
|
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:
|
@@ -68,14 +85,14 @@ def find_downloaded_firmware(
|
|
68
85
|
)
|
69
86
|
# hope we have a match now for the board
|
70
87
|
# sort by filename
|
71
|
-
fw_list.sort(key=lambda x: x
|
88
|
+
fw_list.sort(key=lambda x: x.filename)
|
72
89
|
return fw_list
|
73
90
|
|
74
91
|
|
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,
|
@@ -84,23 +101,27 @@ def filter_downloaded_fwlist(
|
|
84
101
|
"""Filter the downloaded firmware list based on the provided parameters"""
|
85
102
|
if "preview" in version:
|
86
103
|
# never get a preview for an older version
|
87
|
-
fw_list = [fw for fw in fw_list if fw
|
104
|
+
fw_list = [fw for fw in fw_list if fw.preview]
|
88
105
|
else:
|
89
|
-
|
90
|
-
|
106
|
+
# FWInfo version has no v1.2.3 prefix
|
107
|
+
_version = {clean_version(version, drop_v=True), clean_version(version, drop_v=False)}
|
108
|
+
fw_list = [fw for fw in fw_list if fw.version in _version]
|
109
|
+
log.trace(f"Filtering firmware for {version} : {len(fw_list)} found.")
|
91
110
|
# filter by port
|
92
111
|
if port:
|
93
|
-
fw_list = [fw for fw in fw_list if fw
|
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:
|
97
117
|
# any variant of this board_id
|
98
|
-
fw_list = [fw for fw in fw_list if fw
|
118
|
+
fw_list = [fw for fw in fw_list if fw.board == board_id]
|
99
119
|
else:
|
100
120
|
# the firmware variant should match exactly the board_id
|
101
|
-
fw_list = [fw for fw in fw_list if fw
|
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
|
-
fw_list = [fw for fw in fw_list if fw
|
124
|
+
fw_list = [fw for fw in fw_list if fw.filename.endswith(selector[port])]
|
104
125
|
return fw_list
|
105
126
|
|
106
127
|
|
mpflash/mpflash/flash.py
CHANGED
@@ -23,11 +23,11 @@ def flash_list(
|
|
23
23
|
"""Flash a list of boards with the specified firmware."""
|
24
24
|
flashed = []
|
25
25
|
for mcu, fw_info in todo:
|
26
|
-
fw_file = fw_folder / fw_info
|
26
|
+
fw_file = fw_folder / fw_info.filename
|
27
27
|
if not fw_file.exists():
|
28
28
|
log.error(f"File {fw_file} does not exist, skipping {mcu.board} on {mcu.serialport}")
|
29
29
|
continue
|
30
|
-
log.info(f"Updating {mcu.board} on {mcu.serialport} to {fw_info
|
30
|
+
log.info(f"Updating {mcu.board} on {mcu.serialport} to {fw_info.version}")
|
31
31
|
updated = None
|
32
32
|
# try:
|
33
33
|
if mcu.port in [port for port, exts in PORT_FWTYPES.items() if ".uf2" in exts] and fw_file.suffix == ".uf2":
|
mpflash/mpflash/flash_esp.py
CHANGED
@@ -10,7 +10,7 @@ from typing import List, Optional
|
|
10
10
|
import esptool
|
11
11
|
from loguru import logger as log
|
12
12
|
|
13
|
-
from mpflash.mpboard_id import
|
13
|
+
from mpflash.mpboard_id import find_known_board
|
14
14
|
from mpflash.mpremoteboard import MPRemoteBoard
|
15
15
|
|
16
16
|
|
@@ -22,7 +22,7 @@ def flash_esp(mcu: MPRemoteBoard, fw_file: Path, *, erase: bool = True) -> Optio
|
|
22
22
|
log.info(f"Flashing {fw_file} on {mcu.board} on {mcu.serialport}")
|
23
23
|
if not mcu.cpu:
|
24
24
|
# Lookup CPU based on the board name
|
25
|
-
mcu.cpu =
|
25
|
+
mcu.cpu = find_known_board(mcu.board).cpu
|
26
26
|
|
27
27
|
cmds: List[List[str]] = []
|
28
28
|
if erase:
|
mpflash/mpflash/flash_uf2.py
CHANGED
@@ -14,7 +14,9 @@ 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
|
+
from .flash_uf2_linux import dismount_uf2_linux, wait_for_UF2_linux
|
19
|
+
from .flash_uf2_macos import wait_for_UF2_macos
|
18
20
|
from .flash_uf2_windows import wait_for_UF2_windows
|
19
21
|
|
20
22
|
|
@@ -27,9 +29,9 @@ def flash_uf2(mcu: MPRemoteBoard, fw_file: Path, erase: bool) -> Optional[MPRemo
|
|
27
29
|
- copy the firmware file to the drive
|
28
30
|
- wait for the device to restart (5s)
|
29
31
|
|
30
|
-
for
|
31
|
-
|
32
|
-
|
32
|
+
for Linux - to support headless operation ( GH Actions ) :
|
33
|
+
pmount and pumount are used to mount and unmount the drive
|
34
|
+
as this is not done automatically by the OS in headless mode.
|
33
35
|
"""
|
34
36
|
if ".uf2" not in PORT_FWTYPES[mcu.port]:
|
35
37
|
log.error(f"UF2 not supported on {mcu.board} on {mcu.serialport}")
|
@@ -41,9 +43,11 @@ def flash_uf2(mcu: MPRemoteBoard, fw_file: Path, erase: bool) -> Optional[MPRemo
|
|
41
43
|
destination = wait_for_UF2_linux()
|
42
44
|
elif sys.platform == "win32":
|
43
45
|
destination = wait_for_UF2_windows()
|
46
|
+
elif sys.platform == "darwin":
|
47
|
+
log.warning(f"OS {sys.platform} not tested/supported")
|
48
|
+
destination = wait_for_UF2_macos()
|
44
49
|
else:
|
45
50
|
log.warning(f"OS {sys.platform} not tested/supported")
|
46
|
-
destination = wait_for_UF2_linux()
|
47
51
|
return None
|
48
52
|
|
49
53
|
if not destination or not destination.exists() or not (destination / "INFO_UF2.TXT").exists():
|
@@ -51,11 +55,13 @@ def flash_uf2(mcu: MPRemoteBoard, fw_file: Path, erase: bool) -> Optional[MPRemo
|
|
51
55
|
return None
|
52
56
|
|
53
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}")
|
54
60
|
log.info(f"Copying {fw_file} to {destination}.")
|
55
61
|
shutil.copy(fw_file, destination)
|
56
62
|
log.success("Done copying, resetting the board and wait for it to restart")
|
57
|
-
if sys.platform in ["linux"
|
58
|
-
|
59
|
-
for _ in track(range(5 + 2), description="Waiting for the board to restart", transient=True):
|
63
|
+
if sys.platform in ["linux"]:
|
64
|
+
dismount_uf2_linux()
|
65
|
+
for _ in track(range(5 + 2), description="Waiting for the board to restart", transient=True, refresh_per_second=2):
|
60
66
|
time.sleep(1) # 5 secs to short on linux
|
61
67
|
return mcu
|
@@ -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
|
@@ -13,28 +13,15 @@ from loguru import logger as log
|
|
13
13
|
from rich.progress import track
|
14
14
|
|
15
15
|
from .flash_uf2_boardid import get_board_id
|
16
|
+
from .uf2disk import UF2Disk
|
16
17
|
|
17
18
|
glb_dismount_me: List[UF2Disk] = []
|
18
19
|
|
19
20
|
|
20
|
-
class UF2Disk:
|
21
|
-
"""Info to support mounting and unmounting of UF2 drives on linux"""
|
22
|
-
|
23
|
-
device_path: str
|
24
|
-
label: str
|
25
|
-
mountpoint: str
|
26
|
-
|
27
|
-
def __repr__(self):
|
28
|
-
return repr(self.__dict__)
|
29
|
-
|
30
|
-
|
31
21
|
def get_uf2_drives():
|
32
22
|
"""
|
33
23
|
Get a list of all the (un)mounted UF2 drives
|
34
24
|
"""
|
35
|
-
if sys.platform != "linux":
|
36
|
-
log.error("pumount only works on Linux")
|
37
|
-
return
|
38
25
|
# import blkinfo only on linux
|
39
26
|
from blkinfo import BlkDiskInfo
|
40
27
|
|
@@ -101,7 +88,7 @@ def pumount(disk: UF2Disk):
|
|
101
88
|
log.warning(f"{disk.label} already dismounted")
|
102
89
|
|
103
90
|
|
104
|
-
def
|
91
|
+
def dismount_uf2_linux():
|
105
92
|
global glb_dismount_me
|
106
93
|
for disk in glb_dismount_me:
|
107
94
|
pumount(disk)
|
@@ -113,7 +100,9 @@ def wait_for_UF2_linux(s_max: int = 10):
|
|
113
100
|
wait = 10
|
114
101
|
uf2_drives = []
|
115
102
|
# while not destination and wait > 0:
|
116
|
-
for _ in track(
|
103
|
+
for _ in track(
|
104
|
+
range(s_max), description="Waiting for mcu to mount as a drive", transient=True, refresh_per_second=2
|
105
|
+
):
|
117
106
|
# log.info(f"Waiting for mcu to mount as a drive : {wait} seconds left")
|
118
107
|
uf2_drives += list(get_uf2_drives())
|
119
108
|
for drive in get_uf2_drives():
|
@@ -0,0 +1,37 @@
|
|
1
|
+
""" Flashing UF2 based MCU on macos"""
|
2
|
+
|
3
|
+
# sourcery skip: snake-case-functions
|
4
|
+
from __future__ import annotations
|
5
|
+
|
6
|
+
import sys
|
7
|
+
import time
|
8
|
+
from pathlib import Path
|
9
|
+
from typing import Optional
|
10
|
+
|
11
|
+
from loguru import logger as log
|
12
|
+
from rich.progress import track
|
13
|
+
|
14
|
+
from .flash_uf2_boardid import get_board_id
|
15
|
+
|
16
|
+
|
17
|
+
def wait_for_UF2_macos(s_max: int = 10) -> Optional[Path]:
|
18
|
+
"""Wait for the MCU to mount as a drive"""
|
19
|
+
if s_max < 1:
|
20
|
+
s_max = 10
|
21
|
+
destination = None
|
22
|
+
for _ in track(
|
23
|
+
range(s_max), description="Waiting for mcu to mount as a drive", transient=True, refresh_per_second=2
|
24
|
+
):
|
25
|
+
# log.info(f"Waiting for mcu to mount as a drive : {n} seconds left")
|
26
|
+
vol_mounts = Path("/Volumes").iterdir()
|
27
|
+
for vol in vol_mounts:
|
28
|
+
try:
|
29
|
+
if Path(vol, "INFO_UF2.TXT").exists():
|
30
|
+
destination = Path(vol)
|
31
|
+
break
|
32
|
+
except OSError:
|
33
|
+
pass
|
34
|
+
if destination:
|
35
|
+
break
|
36
|
+
time.sleep(1)
|
37
|
+
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
|
-
for _ in track(range(s_max), description="Waiting for mcu to mount as a drive", transient=True):
|
20
|
+
destination = None
|
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:
|