mpflash 1.24.8__py3-none-any.whl → 1.25.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.
- mpflash/basicgit.py +3 -2
- mpflash/cli_flash.py +1 -1
- mpflash/cli_list.py +5 -6
- mpflash/cli_main.py +1 -1
- mpflash/config.py +6 -1
- mpflash/db/boards.py +63 -0
- mpflash/db/downloads.py +87 -0
- mpflash/download/__init__.py +221 -0
- mpflash/download/from_web.py +204 -0
- mpflash/downloaded.py +8 -29
- mpflash/flash/worklist.py +3 -3
- mpflash/mpboard_id/board.py +5 -2
- mpflash/mpboard_id/board_id.py +67 -6
- mpflash/mpboard_id/board_info.zip +0 -0
- mpflash/mpboard_id/known.py +32 -18
- mpflash/mpboard_id/store.py +2 -1
- mpflash/mpremoteboard/__init__.py +81 -17
- mpflash/mpremoteboard/mpy_fw_info.py +13 -9
- mpflash/vendor/board_database.py +86 -1
- mpflash/versions.py +7 -0
- {mpflash-1.24.8.dist-info → mpflash-1.25.0.dist-info}/METADATA +1 -1
- {mpflash-1.24.8.dist-info → mpflash-1.25.0.dist-info}/RECORD +25 -22
- mpflash/download.py +0 -397
- {mpflash-1.24.8.dist-info → mpflash-1.25.0.dist-info}/LICENSE +0 -0
- {mpflash-1.24.8.dist-info → mpflash-1.25.0.dist-info}/WHEEL +0 -0
- {mpflash-1.24.8.dist-info → mpflash-1.25.0.dist-info}/entry_points.txt +0 -0
mpflash/vendor/board_database.py
CHANGED
@@ -37,7 +37,9 @@ from __future__ import annotations
|
|
37
37
|
import json
|
38
38
|
from dataclasses import dataclass, field
|
39
39
|
from glob import glob
|
40
|
+
from os import path
|
40
41
|
from pathlib import Path
|
42
|
+
import re
|
41
43
|
|
42
44
|
|
43
45
|
@dataclass(order=True)
|
@@ -52,6 +54,15 @@ class Variant:
|
|
52
54
|
"""
|
53
55
|
board: Board = field(repr=False)
|
54
56
|
|
57
|
+
@property
|
58
|
+
def description(self) -> str:
|
59
|
+
"""
|
60
|
+
Description of the board, if available.
|
61
|
+
Example: "Pyboard v1.1 with STM32F4"
|
62
|
+
"""
|
63
|
+
return description_from_source(self.board.path, self.name) or self.board.description
|
64
|
+
# f"{self.board.description}-{self.name}"
|
65
|
+
|
55
66
|
|
56
67
|
@dataclass(order=True)
|
57
68
|
class Board:
|
@@ -93,6 +104,12 @@ class Board:
|
|
93
104
|
"""
|
94
105
|
port: Port | None = field(default=None, compare=False)
|
95
106
|
|
107
|
+
path: str = ""
|
108
|
+
"""
|
109
|
+
the relative path to the boards files.
|
110
|
+
Example: "ports/stm32/boards/PYBV11"
|
111
|
+
"""
|
112
|
+
|
96
113
|
@staticmethod
|
97
114
|
def factory(filename_json: Path) -> Board:
|
98
115
|
with filename_json.open() as f:
|
@@ -101,18 +118,27 @@ class Board:
|
|
101
118
|
board = Board(
|
102
119
|
name=filename_json.parent.name,
|
103
120
|
variants=[],
|
104
|
-
url=board_json["url"],
|
121
|
+
url=board_json["url"] if "url" in board_json else "", # fix missing url
|
105
122
|
mcu=board_json["mcu"],
|
106
123
|
product=board_json["product"],
|
107
124
|
vendor=board_json["vendor"],
|
108
125
|
images=board_json["images"],
|
109
126
|
deploy=board_json["deploy"],
|
127
|
+
path=filename_json.parent.as_posix(),
|
110
128
|
)
|
111
129
|
board.variants.extend(
|
112
130
|
sorted([Variant(*v, board) for v in board_json.get("variants", {}).items()]) # type: ignore
|
113
131
|
)
|
114
132
|
return board
|
115
133
|
|
134
|
+
@property
|
135
|
+
def description(self) -> str:
|
136
|
+
"""
|
137
|
+
Description of the board, if available.
|
138
|
+
Example: "Pyboard v1.1 with STM32F4"
|
139
|
+
"""
|
140
|
+
return description_from_source(self.path, "") or self.name
|
141
|
+
|
116
142
|
|
117
143
|
@dataclass(order=True)
|
118
144
|
class Port:
|
@@ -140,6 +166,8 @@ class Database:
|
|
140
166
|
|
141
167
|
def __post_init__(self) -> None:
|
142
168
|
mpy_dir = self.mpy_root_directory
|
169
|
+
if not mpy_dir.is_dir():
|
170
|
+
raise ValueError(f"Invalid path to micropython directory: {mpy_dir}")
|
143
171
|
# Take care to avoid using Path.glob! Performance was 15x slower.
|
144
172
|
for p in glob(f"{mpy_dir}/ports/**/boards/**/board.json"):
|
145
173
|
filename_json = Path(p)
|
@@ -176,6 +204,7 @@ class Database:
|
|
176
204
|
"",
|
177
205
|
[],
|
178
206
|
[],
|
207
|
+
path=path.as_posix(),
|
179
208
|
)
|
180
209
|
board.variants = [Variant(v, "", board) for v in variant_names]
|
181
210
|
port = Port(special_port_name, {special_port_name: board})
|
@@ -183,3 +212,59 @@ class Database:
|
|
183
212
|
|
184
213
|
self.ports[special_port_name] = port
|
185
214
|
self.boards[board.name] = board
|
215
|
+
|
216
|
+
|
217
|
+
# look for all mpconfigboard.h files and extract the board name
|
218
|
+
# from the #define MICROPY_HW_BOARD_NAME "PYBD_SF6"
|
219
|
+
# and the #define MICROPY_HW_MCU_NAME "STM32F767xx"
|
220
|
+
RE_H_MICROPY_HW_BOARD_NAME = re.compile(r"#define\s+MICROPY_HW_BOARD_NAME\s+\"(.+)\"")
|
221
|
+
RE_H_MICROPY_HW_MCU_NAME = re.compile(r"#define\s+MICROPY_HW_MCU_NAME\s+\"(.+)\"")
|
222
|
+
# find boards and variants in the mpconfigboard*.cmake files
|
223
|
+
RE_CMAKE_MICROPY_HW_BOARD_NAME = re.compile(r"MICROPY_HW_BOARD_NAME\s?=\s?\"(?P<variant>[\w\s\S]*)\"")
|
224
|
+
RE_CMAKE_MICROPY_HW_MCU_NAME = re.compile(r"MICROPY_HW_MCU_NAME\s?=\s?\"(?P<variant>[\w\s\S]*)\"")
|
225
|
+
|
226
|
+
|
227
|
+
def description_from_source(board_path: str | Path, variant: str = "") -> str:
|
228
|
+
"""Get the board's description from the header or make files."""
|
229
|
+
return description_from_header(board_path, variant) or description_from_cmake(board_path, variant)
|
230
|
+
|
231
|
+
|
232
|
+
def description_from_header(board_path: str | Path, variant: str = "") -> str:
|
233
|
+
"""Get the board's description from the mpconfigboard.h file."""
|
234
|
+
|
235
|
+
mpconfig_path = path.join(board_path, f"mpconfigboard_{variant}.h" if variant else "mpconfigboard.h")
|
236
|
+
if not path.exists(mpconfig_path):
|
237
|
+
return f""
|
238
|
+
|
239
|
+
with open(mpconfig_path, "r") as f:
|
240
|
+
board_name = mcu_name = "-"
|
241
|
+
found = 0
|
242
|
+
for line in f:
|
243
|
+
if match := RE_H_MICROPY_HW_BOARD_NAME.match(line):
|
244
|
+
board_name = match[1]
|
245
|
+
found += 1
|
246
|
+
elif match := RE_H_MICROPY_HW_MCU_NAME.match(line):
|
247
|
+
mcu_name = match[1]
|
248
|
+
found += 1
|
249
|
+
if found == 2:
|
250
|
+
return f"{board_name} with {mcu_name}" if mcu_name != "-" else board_name
|
251
|
+
return board_name if found == 1 else ""
|
252
|
+
|
253
|
+
|
254
|
+
def description_from_cmake(board_path: str | Path, variant: str = "") -> str:
|
255
|
+
"""Get the board's description from the mpconfig[board|variant].cmake file."""
|
256
|
+
|
257
|
+
cmake_path = path.join(board_path, f"mpconfigvariant_{variant}.cmake" if variant else "mpconfigboard.cmake")
|
258
|
+
if not path.exists(cmake_path):
|
259
|
+
return f""
|
260
|
+
with open(cmake_path, "r") as f:
|
261
|
+
board_name = mcu_name = "-"
|
262
|
+
for line in f:
|
263
|
+
line = line.strip()
|
264
|
+
if match := RE_CMAKE_MICROPY_HW_BOARD_NAME.match(line):
|
265
|
+
description = match["variant"]
|
266
|
+
return description
|
267
|
+
elif match := RE_CMAKE_MICROPY_HW_MCU_NAME.match(line):
|
268
|
+
description = match["variant"]
|
269
|
+
return description
|
270
|
+
return ""
|
mpflash/versions.py
CHANGED
@@ -38,6 +38,13 @@ def clean_version(
|
|
38
38
|
return "stable"
|
39
39
|
version = _v
|
40
40
|
log.trace(f"Using latest stable version: {version}")
|
41
|
+
elif version.lower() in ["preview", "latest"]:
|
42
|
+
_v = get_preview_mp_version()
|
43
|
+
if not _v:
|
44
|
+
log.warning("Could not determine the preview version")
|
45
|
+
return "preview"
|
46
|
+
version = _v
|
47
|
+
log.trace(f"Using latest preview version: {version}")
|
41
48
|
is_preview = "-preview" in version
|
42
49
|
nibbles = version.split("-")
|
43
50
|
ver_ = nibbles[0].lower().lstrip("v")
|
@@ -1,7 +1,7 @@
|
|
1
1
|
mpflash/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
2
|
mpflash/add_firmware.py,sha256=1h0HsA-EVi3HXLmoEvzwY_a-GuWYzPwulTYHHBB8THg,3428
|
3
3
|
mpflash/ask_input.py,sha256=piwMuW7QlYsIvDdJFN1lVCkCGsmsSyyMaUOtvf_S330,8885
|
4
|
-
mpflash/basicgit.py,sha256=
|
4
|
+
mpflash/basicgit.py,sha256=e5utBpTObVl5fbSZ-dpiZuVi0touiFppS8NuKRKcxII,9670
|
5
5
|
mpflash/bootloader/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
6
6
|
mpflash/bootloader/activate.py,sha256=orQOw4XTkXVZI-rMInRb0T5Wp3qA_BlzbJUA2gyBToU,2361
|
7
7
|
mpflash/bootloader/detect.py,sha256=OagP2QVWeLLWkZt2paqEF6r4_x3QDcBGNCPOWfMy9NQ,2686
|
@@ -9,15 +9,18 @@ mpflash/bootloader/manual.py,sha256=C3PVLW3lwz66zG2mOekAJdwXD8PZcIyI3VuLEkusMpI,
|
|
9
9
|
mpflash/bootloader/micropython.py,sha256=v_kZkvg0uWZDbMrT78gmiYHbD83QLdnrctvEClI8iRg,529
|
10
10
|
mpflash/bootloader/touch1200.py,sha256=VND7_YniS9Vx6WEaAxjI72RZZ6WBOwmBTsKJkbuaAHk,1105
|
11
11
|
mpflash/cli_download.py,sha256=6p83u0Iqyq-V6zS_zQbngSgnCnE0thNPR02r_12_d_Y,3660
|
12
|
-
mpflash/cli_flash.py,sha256=
|
12
|
+
mpflash/cli_flash.py,sha256=6ZOHChF1cUyQEDzUpgJKko8EX__55Ol-fQkZNfTJpB8,8337
|
13
13
|
mpflash/cli_group.py,sha256=_Wj4EvRkmPri6wyJWT8cQ0WRMwrj9xtCDLaxFJs7ssM,2617
|
14
|
-
mpflash/cli_list.py,sha256=
|
15
|
-
mpflash/cli_main.py,sha256=
|
14
|
+
mpflash/cli_list.py,sha256=U4lrlE7O6o1BamHD0K_cJhuINanKNgXkg-WTU5RetF4,2354
|
15
|
+
mpflash/cli_main.py,sha256=aVlsA-ZHBKJyuGwSCbpO2Ky0X52wPeOOGumvuPPTFzQ,1136
|
16
16
|
mpflash/common.py,sha256=1mEsilJd3HpL7wFPsJ0EScYoJj7JqGFWDjSFEKJdjeU,7488
|
17
|
-
mpflash/config.py,sha256=
|
17
|
+
mpflash/config.py,sha256=GMuqhrof3lkG7x_CxitYRkm4mxHEYZ_uPFNO3QxSJCY,2166
|
18
18
|
mpflash/connected.py,sha256=oxZdk1o-AfNPhJsSxr3KrMH_gdYfrjqc_IpT6J8Ng9k,3496
|
19
|
-
mpflash/
|
20
|
-
mpflash/
|
19
|
+
mpflash/db/boards.py,sha256=KvpJ45oPwoxuXCzNpAPIyMOiXOpO72LBJowQ4QBJPiU,1960
|
20
|
+
mpflash/db/downloads.py,sha256=sHJzKLf5cYFftIcweoBfCqMY0MPJMmFYVRT7mprspvU,2975
|
21
|
+
mpflash/download/__init__.py,sha256=kI9zYSjFUO2vVbRofJmQw44ftlM5r1HnjmkdZenzo2c,8451
|
22
|
+
mpflash/download/from_web.py,sha256=ddUd7MZJj19e_nR9Iw79v3UuCPwvkWwQtuBEyu-XsbY,7860
|
23
|
+
mpflash/downloaded.py,sha256=kfUt07vDARZYw64TToXLxA-UlP7zFTGRjnBzIEDL5cU,4163
|
21
24
|
mpflash/errors.py,sha256=IAidY3qkZsXy6Pm1rdmVFmGyg81ywHhse3itaPctA2w,247
|
22
25
|
mpflash/flash/__init__.py,sha256=V--YRwDGtCCLVfCe9p9SGV1Cbi0FNd9zenKlIb-HFw0,2820
|
23
26
|
mpflash/flash/esp.py,sha256=N71fPMYhYexEF3YeJGJ74B3GtceXa-FdGqiBpa2qboI,3225
|
@@ -29,31 +32,31 @@ mpflash/flash/uf2/linux.py,sha256=uTgqyS7C7xfQ25RrTcSUkt-m2u2Ks_o7bPLzIecPoC8,43
|
|
29
32
|
mpflash/flash/uf2/macos.py,sha256=JTaIpqnR_0k4oSEvzs9amhmK-PMxUJyZLnZ_wZwxa-0,1228
|
30
33
|
mpflash/flash/uf2/uf2disk.py,sha256=4_P2l-kedM7VSliA2u706LQLxvu3xWSod1-lj-xjZis,298
|
31
34
|
mpflash/flash/uf2/windows.py,sha256=S---sVjVrC00ZcnpOewtJIBfSCj2cr7FGQwEm_ZEDnY,1334
|
32
|
-
mpflash/flash/worklist.py,sha256=
|
35
|
+
mpflash/flash/worklist.py,sha256=eOD7BigUEshwcc5p7SaSeegpSpIOX1zE_6HuOzssrqM,6083
|
33
36
|
mpflash/list.py,sha256=AjsVTWWTcfUhoT2lYJf2KvcOgMen9MgXY6fDYJvd0Mc,4014
|
34
37
|
mpflash/logger.py,sha256=D6S-KDhXTLl7bYMKfu71iVO1YlpGSd0BcHvkVFOCW5c,1080
|
35
38
|
mpflash/mpboard_id/__init__.py,sha256=i-HxDi4UPvQu2zFhWK5HENr_HmTdsnaqkLfNyxjOelg,515
|
36
39
|
mpflash/mpboard_id/add_boards.py,sha256=XCSyZC9v-B5gzW4dIWdqQVIiE8JviKx0GpyGEQ64dm8,9949
|
37
|
-
mpflash/mpboard_id/board.py,sha256=
|
38
|
-
mpflash/mpboard_id/board_id.py,sha256=
|
40
|
+
mpflash/mpboard_id/board.py,sha256=hzP_SNyigjRQsP3FmTCvuCDtPPalfkL5IfjrnATlVS0,1367
|
41
|
+
mpflash/mpboard_id/board_id.py,sha256=mJqVW_D8uHnuON7xpKBRRs2e3ExQGNYC2bSA2ItZBKY,5439
|
39
42
|
mpflash/mpboard_id/board_info.json,sha256=A3ZIt38KvAy2NMB5srHorSBd3Q3wOZIXufWiIs3XLrs,1019745
|
40
|
-
mpflash/mpboard_id/board_info.zip,sha256
|
41
|
-
mpflash/mpboard_id/known.py,sha256=
|
42
|
-
mpflash/mpboard_id/store.py,sha256=
|
43
|
-
mpflash/mpremoteboard/__init__.py,sha256=
|
44
|
-
mpflash/mpremoteboard/mpy_fw_info.py,sha256=
|
43
|
+
mpflash/mpboard_id/board_info.zip,sha256=-2bnQGRsIQuJUfz-7_-GQ8pMWJ1evhCez6yfjhXocNw,23213
|
44
|
+
mpflash/mpboard_id/known.py,sha256=pYw2z5w_PZC9BG6uqo4jFIoSpiacRxL9oj7pHKv-qjo,4521
|
45
|
+
mpflash/mpboard_id/store.py,sha256=UR5nWwywqSPY_2DBP2w8td3b7N6iS-B5rMcyh-s2JOQ,1750
|
46
|
+
mpflash/mpremoteboard/__init__.py,sha256=jj4FVK237BmQ1-N4ANePWEJFY7yyhbb0XvJx8n00vuc,11993
|
47
|
+
mpflash/mpremoteboard/mpy_fw_info.py,sha256=xhEjifU7pAUpey5T23k6x_1bhuS2XcVbSM21ZynWDdM,5148
|
45
48
|
mpflash/mpremoteboard/runner.py,sha256=-PgzAeBGbyXaAUlwyiw4mcINsP2U1XRRjP1_QdBrxpg,4786
|
46
49
|
mpflash/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
47
|
-
mpflash/vendor/board_database.py,sha256=
|
50
|
+
mpflash/vendor/board_database.py,sha256=Cb8fEhJaZ2siMkLPW5rPwV9yzBsTtKGOqWUd9TxNgFM,8763
|
48
51
|
mpflash/vendor/click_aliases.py,sha256=adLhqLxNpJEPjSCIRSTkR-QzSgavGFKT0cwRbjxpzRU,5395
|
49
52
|
mpflash/vendor/dfu.py,sha256=6rqGCBS8mTxxaLtkdzJ8O6nc74kFk8jrkmKvxw-x-u8,5693
|
50
53
|
mpflash/vendor/pico-universal-flash-nuke/LICENSE.txt,sha256=Zkc2iTNbib2NCMwtLjMEz0vFCPglgvaw6Mj7QiWldpQ,1484
|
51
54
|
mpflash/vendor/pico-universal-flash-nuke/universal_flash_nuke.uf2,sha256=QuPMppqHMVOt3vDVU0bikHRLsTiDRQYNUcGQ_OLRFGI,28160
|
52
55
|
mpflash/vendor/pydfu.py,sha256=uD0esm1iPHap7T93M_fli1LlXwn8RxKDFRnt8inxJu8,20389
|
53
56
|
mpflash/vendor/readme.md,sha256=BQ7Uxf8joeYMjTUuSLLBG49ob6a9MgFPIEwuc72-Mfw,415
|
54
|
-
mpflash/versions.py,sha256=
|
55
|
-
mpflash-1.
|
56
|
-
mpflash-1.
|
57
|
-
mpflash-1.
|
58
|
-
mpflash-1.
|
59
|
-
mpflash-1.
|
57
|
+
mpflash/versions.py,sha256=zDAUEMae0hvB8CFmVnf_VWtnVIpAiMGXKpvk2FgnWcg,4878
|
58
|
+
mpflash-1.25.0.dist-info/entry_points.txt,sha256=Jk_visOhYOsZIcSP2Ms9hKqfKy1iorR-6dYltSoWCpY,52
|
59
|
+
mpflash-1.25.0.dist-info/LICENSE,sha256=mWpNhsIxWzetYNnTpr4eb3HtgsxGIC8KcYWxXEcxQvE,1077
|
60
|
+
mpflash-1.25.0.dist-info/METADATA,sha256=lBovhakb7WQMmZ7n6KGv6bnUIYq_rVmZ_9rU1ZAwgJU,23930
|
61
|
+
mpflash-1.25.0.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
|
62
|
+
mpflash-1.25.0.dist-info/RECORD,,
|
mpflash/download.py
DELETED
@@ -1,397 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Module to download MicroPython firmware for specific boards and versions.
|
3
|
-
Uses the micropython.org website to get the available versions and locations to download firmware files.
|
4
|
-
"""
|
5
|
-
|
6
|
-
import functools
|
7
|
-
import itertools
|
8
|
-
import re
|
9
|
-
from pathlib import Path
|
10
|
-
from typing import Dict, List, Optional
|
11
|
-
from urllib.parse import urljoin
|
12
|
-
|
13
|
-
# #########################################################################################################
|
14
|
-
# make sure that jsonlines does not mistake the MicroPython ujson for the CPython ujson
|
15
|
-
import jsonlines
|
16
|
-
from loguru import logger as log
|
17
|
-
from rich.progress import track
|
18
|
-
|
19
|
-
from mpflash.common import PORT_FWTYPES, FWInfo
|
20
|
-
from mpflash.downloaded import clean_downloaded_firmwares
|
21
|
-
from mpflash.errors import MPFlashError
|
22
|
-
from mpflash.mpboard_id import get_known_ports
|
23
|
-
from mpflash.versions import clean_version
|
24
|
-
|
25
|
-
# avoid conflict with the ujson used by MicroPython
|
26
|
-
jsonlines.ujson = None # type: ignore
|
27
|
-
# #########################################################################################################
|
28
|
-
|
29
|
-
|
30
|
-
MICROPYTHON_ORG_URL = "https://micropython.org/"
|
31
|
-
|
32
|
-
# Regexes to remove dates and hashes in the filename that just get in the way
|
33
|
-
RE_DATE = r"(-\d{8}-)"
|
34
|
-
RE_HASH = r"(.g[0-9a-f]+\.)"
|
35
|
-
# regex to extract the version and the build from the firmware filename
|
36
|
-
# group 1 is the version, group 2 is the build
|
37
|
-
RE_VERSION_PREVIEW = r"v([\d\.]+)-?(?:preview\.)?(\d+)?\."
|
38
|
-
# 'RPI_PICO_W-v1.23.uf2'
|
39
|
-
# 'RPI_PICO_W-v1.23.0.uf2'
|
40
|
-
# 'RPI_PICO_W-v1.23.0-406.uf2'
|
41
|
-
# 'RPI_PICO_W-v1.23.0-preview.406.uf2'
|
42
|
-
# 'RPI_PICO_W-v1.23.0-preview.4.uf2'
|
43
|
-
# 'RPI_PICO_W-v1.23.0.uf2'
|
44
|
-
# 'https://micropython.org/resources/firmware/RPI_PICO_W-20240531-v1.24.0-preview.10.gc1a6b95bf.uf2'
|
45
|
-
# 'https://micropython.org/resources/firmware/RPI_PICO_W-20240531-v1.24.0-preview.10.uf2'
|
46
|
-
# 'RPI_PICO_W-v1.24.0-preview.10.gc1a6b95bf.uf2'
|
47
|
-
|
48
|
-
|
49
|
-
# use functools.lru_cache to avoid needing to download pages multiple times
|
50
|
-
@functools.lru_cache(maxsize=500)
|
51
|
-
def get_page(page_url: str) -> str:
|
52
|
-
"""Get the HTML of a page and return it as a string."""
|
53
|
-
# Just in time import
|
54
|
-
import requests
|
55
|
-
response = requests.get(page_url)
|
56
|
-
return response.content.decode()
|
57
|
-
|
58
|
-
|
59
|
-
@functools.lru_cache(maxsize=500)
|
60
|
-
def get_board_urls(page_url: str) -> List[Dict[str, str]]:
|
61
|
-
"""
|
62
|
-
Get the urls to all the board pages listed on this page.
|
63
|
-
Assumes that all links to firmware have "class": "board-card"
|
64
|
-
|
65
|
-
Args:
|
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
|
-
|
71
|
-
"""
|
72
|
-
# Just in time import
|
73
|
-
from bs4 import BeautifulSoup
|
74
|
-
|
75
|
-
downloads_html = get_page(page_url)
|
76
|
-
soup = BeautifulSoup(downloads_html, "html.parser")
|
77
|
-
tags = soup.find_all("a", recursive=True, attrs={"class": "board-card"})
|
78
|
-
# assumes that all links are relative to the page url
|
79
|
-
boards = [tag.get("href") for tag in tags] # type: ignore
|
80
|
-
if "?" in page_url:
|
81
|
-
page_url = page_url.split("?")[0]
|
82
|
-
return [{"board": board, "url": page_url + board} for board in boards] # type: ignore
|
83
|
-
|
84
|
-
|
85
|
-
def board_firmware_urls(board_url: str, base_url: str, ext: str) -> List[str]:
|
86
|
-
"""
|
87
|
-
Get the urls to all the firmware files for a board.
|
88
|
-
Args:
|
89
|
-
page_url (str): The url of the page to get the board urls from.
|
90
|
-
??? base_url (str): The base url to join the relative urls to.
|
91
|
-
ext (str): The extension of the firmware files to get. (with or withouth leading .)
|
92
|
-
|
93
|
-
the urls are relative urls to the site root
|
94
|
-
|
95
|
-
"""
|
96
|
-
# Just in time import
|
97
|
-
from bs4 import BeautifulSoup
|
98
|
-
|
99
|
-
html = get_page(board_url)
|
100
|
-
soup = BeautifulSoup(html, "html.parser")
|
101
|
-
# get all the a tags:
|
102
|
-
# 1. that have a url that starts with `/resources/firmware/`
|
103
|
-
# 2. end with a matching extension for this port.
|
104
|
-
tags = soup.find_all(
|
105
|
-
"a",
|
106
|
-
recursive=True,
|
107
|
-
attrs={"href": re.compile(r"^/resources/firmware/.*\." + ext.lstrip(".") + "$")},
|
108
|
-
)
|
109
|
-
if "?" in base_url:
|
110
|
-
base_url = base_url.split("?")[0]
|
111
|
-
links: List = [urljoin(base_url, tag.get("href")) for tag in tags] # type: ignore
|
112
|
-
return links
|
113
|
-
|
114
|
-
|
115
|
-
# boards we are interested in ( this avoids getting a lot of boards we don't care about)
|
116
|
-
# The first run takes ~60 seconds to run for 4 ports , all boards
|
117
|
-
# so it makes sense to cache the results and skip boards as soon as possible
|
118
|
-
def get_boards(ports: List[str], boards: List[str], clean: bool) -> List[FWInfo]:
|
119
|
-
# sourcery skip: use-getitem-for-re-match-groups
|
120
|
-
"""
|
121
|
-
Retrieves a list of firmware information for the specified ports and boards.
|
122
|
-
|
123
|
-
Args:
|
124
|
-
ports (List[str]): The list of ports to check for firmware.
|
125
|
-
boards (List[str]): The list of boards to retrieve firmware information for.
|
126
|
-
clean (bool): A flag indicating whether to perform a clean retrieval.
|
127
|
-
|
128
|
-
Returns:
|
129
|
-
List[FWInfo]: A list of firmware information for the specified ports and boards.
|
130
|
-
|
131
|
-
"""
|
132
|
-
board_urls: List[FWInfo] = []
|
133
|
-
if ports is None:
|
134
|
-
ports = get_known_ports()
|
135
|
-
for port in ports:
|
136
|
-
download_page_url = f"{MICROPYTHON_ORG_URL}download/?port={port}"
|
137
|
-
urls = get_board_urls(download_page_url)
|
138
|
-
# filter out boards we don't care about
|
139
|
-
urls = [board for board in urls if board["board"] in boards]
|
140
|
-
# add the port to the board urls
|
141
|
-
for board in urls:
|
142
|
-
board["port"] = port
|
143
|
-
|
144
|
-
for board in track(
|
145
|
-
urls,
|
146
|
-
description=f"Checking {port} download pages",
|
147
|
-
transient=True,
|
148
|
-
refresh_per_second=1,
|
149
|
-
show_speed=False,
|
150
|
-
):
|
151
|
-
# add a board to the list for each firmware found
|
152
|
-
firmware_urls: List[str] = []
|
153
|
-
for ext in PORT_FWTYPES[port]:
|
154
|
-
firmware_urls += board_firmware_urls(board["url"], MICROPYTHON_ORG_URL, ext)
|
155
|
-
for _url in firmware_urls:
|
156
|
-
board["firmware"] = _url
|
157
|
-
fname = Path(board["firmware"]).name
|
158
|
-
if clean:
|
159
|
-
# remove date from firmware name
|
160
|
-
fname = re.sub(RE_DATE, "-", fname)
|
161
|
-
# remove hash from firmware name
|
162
|
-
fname = re.sub(RE_HASH, ".", fname)
|
163
|
-
fw_info = FWInfo(
|
164
|
-
filename=fname,
|
165
|
-
port=port,
|
166
|
-
board=board["board"],
|
167
|
-
preview="preview" in _url,
|
168
|
-
firmware=_url,
|
169
|
-
version="",
|
170
|
-
)
|
171
|
-
# board["firmware"] = _url
|
172
|
-
# board["preview"] = "preview" in _url # type: ignore
|
173
|
-
if ver_match := re.search(RE_VERSION_PREVIEW, _url):
|
174
|
-
fw_info.version = clean_version(ver_match.group(1))
|
175
|
-
fw_info.build = ver_match.group(2) or "0"
|
176
|
-
fw_info.preview = fw_info.build != "0"
|
177
|
-
# # else:
|
178
|
-
# # board.$1= ""
|
179
|
-
# if "preview." in fw_info.version:
|
180
|
-
# fw_info.build = fw_info.version.split("preview.")[-1]
|
181
|
-
# else:
|
182
|
-
# fw_info.build = "0"
|
183
|
-
|
184
|
-
fw_info.ext = Path(fw_info.firmware).suffix
|
185
|
-
fw_info.variant = fw_info.filename.split("-v")[0] if "-v" in fw_info.filename else ""
|
186
|
-
|
187
|
-
board_urls.append(fw_info)
|
188
|
-
return board_urls
|
189
|
-
|
190
|
-
|
191
|
-
def key_fw_ver_pre_ext_bld(x: FWInfo):
|
192
|
-
"sorting key for the retrieved board urls"
|
193
|
-
return x.variant, x.version, x.preview, x.ext, x.build
|
194
|
-
|
195
|
-
|
196
|
-
def key_fw_var_pre_ext(x: FWInfo):
|
197
|
-
"Grouping key for the retrieved board urls"
|
198
|
-
return x.variant, x.preview, x.ext
|
199
|
-
|
200
|
-
|
201
|
-
def download_firmwares(
|
202
|
-
firmware_folder: Path,
|
203
|
-
ports: List[str],
|
204
|
-
boards: List[str],
|
205
|
-
versions: Optional[List[str]] = None,
|
206
|
-
*,
|
207
|
-
force: bool = False,
|
208
|
-
clean: bool = True,
|
209
|
-
) -> int:
|
210
|
-
"""
|
211
|
-
Downloads firmware files based on the specified firmware folder, ports, boards, versions, force flag, and clean flag.
|
212
|
-
|
213
|
-
Args:
|
214
|
-
firmware_folder : The folder to save the downloaded firmware files.
|
215
|
-
ports : The list of ports to check for firmware.
|
216
|
-
boards : The list of boards to download firmware for.
|
217
|
-
versions : The list of versions to download firmware for.
|
218
|
-
force : A flag indicating whether to force the download even if the firmware file already exists.
|
219
|
-
clean : A flag indicating to clean the date from the firmware filename.
|
220
|
-
"""
|
221
|
-
|
222
|
-
|
223
|
-
skipped = downloaded = 0
|
224
|
-
versions = [] if versions is None else [clean_version(v) for v in versions]
|
225
|
-
# handle renamed boards
|
226
|
-
boards = add_renamed_boards(boards)
|
227
|
-
|
228
|
-
available_firmwares = get_firmware_list(ports, boards, versions, clean)
|
229
|
-
|
230
|
-
for b in available_firmwares:
|
231
|
-
log.debug(b.filename)
|
232
|
-
# relevant
|
233
|
-
|
234
|
-
log.info(f"Found {len(available_firmwares)} relevant unique firmwares")
|
235
|
-
if not available_firmwares:
|
236
|
-
log.error("No relevant firmwares could be found on https://micropython.org/download")
|
237
|
-
log.info(f"{versions=} {ports=} {boards=}")
|
238
|
-
log.info("Please check the website for the latest firmware files or try the preview version.")
|
239
|
-
return 0
|
240
|
-
|
241
|
-
firmware_folder.mkdir(exist_ok=True)
|
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
|
-
|
261
|
-
with jsonlines.open(firmware_folder / "firmware.jsonl", "a") as writer:
|
262
|
-
downloaded = skipped = 0
|
263
|
-
for board in available_firmwares:
|
264
|
-
filename = firmware_folder / board.port / board.filename
|
265
|
-
filename.parent.mkdir(exist_ok=True)
|
266
|
-
if filename.exists() and not force:
|
267
|
-
skipped += 1
|
268
|
-
log.debug(f" {filename} already exists, skip download")
|
269
|
-
continue
|
270
|
-
log.info(f"Downloading {board.firmware}")
|
271
|
-
log.info(f" to {filename}")
|
272
|
-
try:
|
273
|
-
r = requests.get(board.firmware, allow_redirects=True)
|
274
|
-
with open(filename, "wb") as fw:
|
275
|
-
fw.write(r.content)
|
276
|
-
board.filename = str(filename.relative_to(firmware_folder))
|
277
|
-
except requests.RequestException as e:
|
278
|
-
log.exception(e)
|
279
|
-
continue
|
280
|
-
writer.write(board.to_dict()) # type: ignore
|
281
|
-
downloaded += 1
|
282
|
-
if downloaded > 0:
|
283
|
-
clean_downloaded_firmwares(firmware_folder)
|
284
|
-
return downloaded,skipped
|
285
|
-
|
286
|
-
|
287
|
-
def get_firmware_list(ports: List[str], boards: List[str], versions: List[str], clean: bool = True):
|
288
|
-
"""
|
289
|
-
Retrieves a list of unique firmware files available om micropython.org > downloads
|
290
|
-
based on the specified ports, boards, versions, and clean flag.
|
291
|
-
|
292
|
-
Args:
|
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.
|
297
|
-
|
298
|
-
Returns:
|
299
|
-
List[FWInfo]: A list of unique firmware information.
|
300
|
-
|
301
|
-
"""
|
302
|
-
|
303
|
-
log.trace("Checking MicroPython download pages")
|
304
|
-
versions = [clean_version(v, drop_v=False) for v in versions]
|
305
|
-
preview = "preview" in versions
|
306
|
-
|
307
|
-
board_urls = sorted(get_boards(ports, boards, clean), key=key_fw_ver_pre_ext_bld)
|
308
|
-
|
309
|
-
log.debug(f"Total {len(board_urls)} firmwares")
|
310
|
-
|
311
|
-
relevant = [
|
312
|
-
board for board in board_urls if board.version in versions and board.build == "0" and board.board in boards and not board.preview
|
313
|
-
]
|
314
|
-
|
315
|
-
if preview:
|
316
|
-
relevant.extend([board for board in board_urls if board.board in boards and board.preview])
|
317
|
-
log.debug(f"Matching firmwares: {len(relevant)}")
|
318
|
-
# select the unique boards
|
319
|
-
unique_boards: List[FWInfo] = []
|
320
|
-
for _, g in itertools.groupby(relevant, key=key_fw_var_pre_ext):
|
321
|
-
# list is aleady sorted by build so we can just get the last item
|
322
|
-
sub_list = list(g)
|
323
|
-
unique_boards.append(sub_list[-1])
|
324
|
-
log.debug(f"Last preview only: {len(unique_boards)}")
|
325
|
-
return unique_boards
|
326
|
-
|
327
|
-
|
328
|
-
def download(
|
329
|
-
destination: Path,
|
330
|
-
ports: List[str],
|
331
|
-
boards: List[str],
|
332
|
-
versions: List[str],
|
333
|
-
force: bool,
|
334
|
-
clean: bool,
|
335
|
-
) -> int:
|
336
|
-
"""
|
337
|
-
Downloads firmware files based on the specified destination, ports, boards, versions, force flag, and clean flag.
|
338
|
-
|
339
|
-
Args:
|
340
|
-
destination : The destination folder to save the downloaded firmware files.
|
341
|
-
ports : The list of ports to check for firmware.
|
342
|
-
boards : The list of boards to download firmware for.
|
343
|
-
versions : The list of versions to download firmware for.
|
344
|
-
force : A flag indicating whether to force the download even if the firmware file already exists.
|
345
|
-
clean : A flag indicating whether to clean the date from the firmware filename.
|
346
|
-
|
347
|
-
Returns:
|
348
|
-
int: The number of downloaded firmware files.
|
349
|
-
|
350
|
-
Raises:
|
351
|
-
MPFlashError : If no boards are found or specified.
|
352
|
-
|
353
|
-
"""
|
354
|
-
# Just in time import
|
355
|
-
import requests
|
356
|
-
|
357
|
-
if not boards:
|
358
|
-
log.critical("No boards found, please connect a board or specify boards to download firmware for.")
|
359
|
-
raise MPFlashError("No boards found")
|
360
|
-
|
361
|
-
try:
|
362
|
-
destination.mkdir(exist_ok=True, parents=True)
|
363
|
-
except (PermissionError, FileNotFoundError) as e:
|
364
|
-
log.critical(f"Could not create folder {destination}")
|
365
|
-
raise MPFlashError(f"Could not create folder {destination}") from e
|
366
|
-
try:
|
367
|
-
result = download_firmwares(destination, ports, boards, versions, force=force, clean=clean)
|
368
|
-
except requests.exceptions.RequestException as e:
|
369
|
-
log.exception(e)
|
370
|
-
raise MPFlashError("Could not connect to micropython.org") from e
|
371
|
-
|
372
|
-
return result
|
373
|
-
|
374
|
-
|
375
|
-
def add_renamed_boards(boards: List[str]) -> List[str]:
|
376
|
-
"""
|
377
|
-
Adds the renamed boards to the list of boards.
|
378
|
-
|
379
|
-
Args:
|
380
|
-
boards : The list of boards to add the renamed boards to.
|
381
|
-
|
382
|
-
Returns:
|
383
|
-
List[str]: The list of boards with the renamed boards added.
|
384
|
-
|
385
|
-
"""
|
386
|
-
renamed = {
|
387
|
-
"PICO": ["RPI_PICO"],
|
388
|
-
"PICO_W": ["RPI_PICO_W"],
|
389
|
-
"GENERIC": ["ESP32_GENERIC", "ESP8266_GENERIC"], # just add both of them
|
390
|
-
}
|
391
|
-
_boards = boards.copy()
|
392
|
-
for board in boards:
|
393
|
-
if board in renamed and renamed[board] not in boards:
|
394
|
-
_boards.extend(renamed[board])
|
395
|
-
if board != board.upper() and board.upper() not in boards:
|
396
|
-
_boards.append(board.upper())
|
397
|
-
return _boards
|
File without changes
|
File without changes
|
File without changes
|