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.
@@ -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,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: mpflash
3
- Version: 1.24.8
3
+ Version: 1.25.0
4
4
  Summary: Flash and download tool for MicroPython firmwares
5
5
  License: MIT
6
6
  Keywords: MicroPython,firmware,flash,download,UF2,esptool
@@ -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=K2pf8p59-tnQgbmP94yveCKHLm7sOLGdF4OLPI6vvKg,9599
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=2D4OYkuJbq6hSQ7sP2r33ILLahtwAvmnNKqjsZ6-8nY,8316
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=jwnfHN-RE-8x37YhPrnQs10SHfD1kVwESkJsXyqLBIo,2411
15
- mpflash/cli_main.py,sha256=3zUiWBRCdOHTXZ7BPuBcafsOW7GClUvGBH1OhIs76js,1128
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=X7nufiMA3rKTFBiZkJY_WHtRv8U_x19QrAb49xsszRM,2024
17
+ mpflash/config.py,sha256=GMuqhrof3lkG7x_CxitYRkm4mxHEYZ_uPFNO3QxSJCY,2166
18
18
  mpflash/connected.py,sha256=oxZdk1o-AfNPhJsSxr3KrMH_gdYfrjqc_IpT6J8Ng9k,3496
19
- mpflash/download.py,sha256=IFPK3lj5BZpL9ylwjiLFn1ZNQ3mCibAYRq836hnquD0,15550
20
- mpflash/downloaded.py,sha256=5kmbap2SumCpxYvI8QVZm87u9Ns3dIsZ7Mds2quiuQk,5110
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=lNCzi_Hyr4xNWmCsUMcH4T589nIfN00Xcpoa56mDNOE,6087
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=JKb4T67HmK7widW-4c1PgilvywMbZYToLk9Fyokm-6Q,1163
38
- mpflash/mpboard_id/board_id.py,sha256=8MDExMMDL8mR1kioXMgRQzJ02GMxzYUQarLsKtY6DAw,3372
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=0rQkVLOBRg5dwvkLmJtRdCXwKiA9arkWYVH7N8dYuNw,21720
41
- mpflash/mpboard_id/known.py,sha256=7aJ0_RPsNipGQGKk3Y7FN5J4Stu33yOxc7j9hXHNd98,3852
42
- mpflash/mpboard_id/store.py,sha256=oxk84RI8y1AMXtVz_lW9iY5wWJBN_71mo8VUp6vOGBE,1663
43
- mpflash/mpremoteboard/__init__.py,sha256=h7AgDt9IVHn31cM5a_Ux7vB2P91Mz__u1-32dlayu8Y,9936
44
- mpflash/mpremoteboard/mpy_fw_info.py,sha256=gGuEYLJJbIix7sXM8PNnT4rY4TMyEFcIh_nkZ3K_JjU,4943
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=QE3oXj96oTAsx94gNfHMYWu_RgBTHW1v9Wp5dq_Dt-Q,5253
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=ax7DBbBwqmJTdm1fkrQVe9zi3cdU3jv_-mwEz0-SydI,4584
55
- mpflash-1.24.8.dist-info/entry_points.txt,sha256=Jk_visOhYOsZIcSP2Ms9hKqfKy1iorR-6dYltSoWCpY,52
56
- mpflash-1.24.8.dist-info/LICENSE,sha256=mWpNhsIxWzetYNnTpr4eb3HtgsxGIC8KcYWxXEcxQvE,1077
57
- mpflash-1.24.8.dist-info/METADATA,sha256=ZEWCmCHX7tawk9vvh6SyOA-NVZOIf6q-Vv5a69HBKjM,23930
58
- mpflash-1.24.8.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
59
- mpflash-1.24.8.dist-info/RECORD,,
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