micropython-stubber 1.17.6__py3-none-any.whl → 1.19.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.
Files changed (74) hide show
  1. {micropython_stubber-1.17.6.dist-info → micropython_stubber-1.19.0.dist-info}/METADATA +7 -6
  2. {micropython_stubber-1.17.6.dist-info → micropython_stubber-1.19.0.dist-info}/RECORD +71 -52
  3. mpflash/README.md +18 -2
  4. mpflash/libusb_flash.ipynb +203 -0
  5. mpflash/mpflash/ask_input.py +234 -0
  6. mpflash/mpflash/cli_download.py +107 -0
  7. mpflash/mpflash/cli_flash.py +165 -0
  8. mpflash/mpflash/cli_group.py +39 -6
  9. mpflash/mpflash/cli_list.py +41 -0
  10. mpflash/mpflash/cli_main.py +13 -8
  11. mpflash/mpflash/common.py +33 -121
  12. mpflash/mpflash/config.py +9 -0
  13. mpflash/mpflash/{downloader.py → download.py} +110 -96
  14. mpflash/mpflash/downloaded.py +108 -0
  15. mpflash/mpflash/errors.py +5 -0
  16. mpflash/mpflash/flash.py +69 -0
  17. mpflash/mpflash/flash_esp.py +17 -23
  18. mpflash/mpflash/flash_stm32.py +16 -113
  19. mpflash/mpflash/flash_stm32_cube.py +111 -0
  20. mpflash/mpflash/flash_stm32_dfu.py +101 -0
  21. mpflash/mpflash/flash_uf2.py +8 -8
  22. mpflash/mpflash/flash_uf2_linux.py +13 -6
  23. mpflash/mpflash/flash_uf2_windows.py +24 -12
  24. mpflash/mpflash/list.py +34 -33
  25. mpflash/mpflash/logger.py +12 -13
  26. mpflash/mpflash/mpboard_id/__init__.py +96 -0
  27. mpflash/mpflash/mpboard_id/board_id.py +63 -0
  28. mpflash/mpflash/mpboard_id/board_info.csv +2213 -0
  29. mpflash/mpflash/mpboard_id/board_info.json +19910 -0
  30. mpflash/mpflash/mpremoteboard/__init__.py +208 -0
  31. mpflash/mpflash/mpremoteboard/mpy_fw_info.py +141 -0
  32. {stubber/bulk → mpflash/mpflash/mpremoteboard}/runner.py +19 -4
  33. mpflash/mpflash/vendor/dfu.py +164 -0
  34. mpflash/mpflash/vendor/pydfu.py +605 -0
  35. mpflash/mpflash/vendor/readme.md +3 -0
  36. mpflash/mpflash/vendor/versions.py +113 -0
  37. mpflash/mpflash/worklist.py +147 -0
  38. mpflash/poetry.lock +411 -595
  39. mpflash/pyproject.toml +22 -6
  40. mpflash/stm32_udev_rules.md +63 -0
  41. stubber/__init__.py +1 -1
  42. stubber/basicgit.py +1 -0
  43. stubber/board/createstubs.py +10 -4
  44. stubber/board/createstubs_db.py +11 -5
  45. stubber/board/createstubs_db_min.py +61 -58
  46. stubber/board/createstubs_db_mpy.mpy +0 -0
  47. stubber/board/createstubs_mem.py +11 -5
  48. stubber/board/createstubs_mem_min.py +56 -53
  49. stubber/board/createstubs_mem_mpy.mpy +0 -0
  50. stubber/board/createstubs_min.py +54 -51
  51. stubber/board/createstubs_mpy.mpy +0 -0
  52. stubber/bulk/mcu_stubber.py +9 -5
  53. stubber/codemod/_partials/db_main.py +14 -25
  54. stubber/codemod/_partials/lvgl_main.py +2 -2
  55. stubber/codemod/board.py +10 -3
  56. stubber/commands/clone_cmd.py +7 -7
  57. stubber/commands/config_cmd.py +3 -0
  58. stubber/freeze/get_frozen.py +0 -2
  59. stubber/publish/candidates.py +1 -1
  60. stubber/publish/package.py +1 -1
  61. stubber/publish/pathnames.py +1 -1
  62. stubber/publish/stubpackage.py +1 -0
  63. stubber/rst/lookup.py +1 -1
  64. stubber/tools/manifestfile.py +5 -3
  65. stubber/utils/config.py +26 -36
  66. stubber/utils/repos.py +2 -2
  67. stubber/utils/versions.py +1 -0
  68. mpflash/mpflash/flasher.py +0 -276
  69. stubber/bulk/board_id.py +0 -40
  70. stubber/bulk/mpremoteboard.py +0 -141
  71. {micropython_stubber-1.17.6.dist-info → micropython_stubber-1.19.0.dist-info}/LICENSE +0 -0
  72. {micropython_stubber-1.17.6.dist-info → micropython_stubber-1.19.0.dist-info}/WHEEL +0 -0
  73. {micropython_stubber-1.17.6.dist-info → micropython_stubber-1.19.0.dist-info}/entry_points.txt +0 -0
  74. /mpflash/mpflash/{uf2_boardid.py → flash_uf2_boardid.py} +0 -0
mpflash/mpflash/common.py CHANGED
@@ -1,126 +1,38 @@
1
- from pathlib import Path
2
- from typing import Dict, Union
1
+ import os
2
+ import time
3
+ from typing import TypedDict
3
4
 
4
- import platformdirs
5
- from github import Github
6
- from loguru import logger as log
7
- from packaging.version import parse
5
+ from github import Auth, Github
6
+ from rich.progress import track
7
+
8
+ from mpflash.errors import MPFlashError
9
+ # from mpflash.mpremoteboard import MPRemoteBoard
8
10
 
9
11
  PORT_FWTYPES = {
10
- "stm32": ".hex",
11
- "esp32": ".bin",
12
- "esp8266": ".bin",
13
- "rp2": ".uf2",
14
- "samd": ".uf2",
15
- "mimxrt": ".hex",
16
- "nrf": ".uf2",
17
- "renesas-ra": ".hex",
12
+ "stm32": [".dfu"], # need .dfu for pydfu.py - .hex for cube cli/GUI
13
+ "esp32": [".bin"],
14
+ "esp8266": [".bin"],
15
+ "rp2": [".uf2"],
16
+ "samd": [".uf2"],
17
+ "mimxrt": [".hex"],
18
+ "nrf": [".uf2"],
19
+ "renesas-ra": [".hex"],
18
20
  }
19
21
 
20
- DEFAULT_FW_PATH = platformdirs.user_downloads_path() / "firmware"
21
- # DEFAULT_FW_PATH = Path.home() / "mp_firmware"
22
-
23
- FWInfo = Dict[str, Union[str, bool]]
24
-
25
- #############################################################
26
- # Version handling copied from stubber/utils/versions.py
27
- #############################################################
28
- V_PREVIEW = "preview"
29
- "Latest preview version"
30
-
31
- SET_PREVIEW = {"preview", "latest", "master"}
32
-
33
-
34
- def clean_version(
35
- version: str,
36
- *,
37
- build: bool = False,
38
- patch: bool = False,
39
- commit: bool = False,
40
- drop_v: bool = False,
41
- flat: bool = False,
42
- ):
43
- "Clean up and transform the many flavours of versions"
44
- # 'v1.13.0-103-gb137d064e' --> 'v1.13-103'
45
- if version in {"", "-"}:
46
- return version
47
- if version.lower() == "stable":
48
- _v = get_stable_version()
49
- if not _v:
50
- log.warning("Could not determine the latest stable version")
51
- return "stable"
52
- version = _v
53
- log.info(f"Using latest stable version: {version}")
54
- is_preview = "-preview" in version
55
- nibbles = version.split("-")
56
- ver_ = nibbles[0].lower().lstrip("v")
57
- if not patch and ver_ >= "1.10.0" and ver_ < "1.20.0" and ver_.endswith(".0"):
58
- # remove the last ".0" - but only for versions between 1.10 and 1.20 (because)
59
- nibbles[0] = nibbles[0][:-2]
60
- if len(nibbles) == 1:
61
- version = nibbles[0]
62
- elif build and not is_preview:
63
- version = "-".join(nibbles) if commit else "-".join(nibbles[:-1])
64
- else:
65
- # version = "-".join((nibbles[0], LATEST))
66
- # HACK: this is not always right, but good enough most of the time
67
- if is_preview:
68
- version = "-".join((nibbles[0], V_PREVIEW))
69
- else:
70
- version = V_PREVIEW
71
- if flat:
72
- version = version.strip().replace(".", "_").replace("-", "_")
73
- else:
74
- version = version.strip().replace("_preview", "-preview").replace("_", ".")
75
-
76
- if drop_v:
77
- version = version.lstrip("v")
78
- elif not version.startswith("v") and version.lower() not in SET_PREVIEW:
79
- version = "v" + version
80
- if version in SET_PREVIEW:
81
- version = V_PREVIEW
82
- return version
83
-
84
-
85
- def micropython_versions(minver: str = "v1.9.2"):
86
- """Get the list of micropython versions from github tags"""
87
- try:
88
- g = Github()
89
- _ = 1 / 0
90
- repo = g.get_repo("micropython/micropython")
91
- versions = [tag.name for tag in repo.get_tags() if parse(tag.name) >= parse(minver)]
92
- except Exception:
93
- versions = [
94
- "v9.99.9-preview",
95
- "v1.22.2",
96
- "v1.22.1",
97
- "v1.22.0",
98
- "v1.21.1",
99
- "v1.21.0",
100
- "v1.20.0",
101
- "v1.19.1",
102
- "v1.19",
103
- "v1.18",
104
- "v1.17",
105
- "v1.16",
106
- "v1.15",
107
- "v1.14",
108
- "v1.13",
109
- "v1.12",
110
- "v1.11",
111
- "v1.10",
112
- "v1.9.4",
113
- "v1.9.3",
114
- ]
115
- versions = [v for v in versions if parse(v) >= parse(minver)]
116
- return sorted(versions)
117
-
118
-
119
- def get_stable_version() -> str:
120
- # read the versions from the git tags
121
- all_versions = micropython_versions(minver="v1.17")
122
- stable_version = [v for v in all_versions if not v.endswith(V_PREVIEW)][-1]
123
- return stable_version
124
-
125
-
126
- #############################################################
22
+ # Token with no permissions to avoid throttling
23
+ # https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api?apiVersion=2022-11-28#getting-a-higher-rate-limit
24
+ PAT_NO_ACCESS = (
25
+ "github_pat" + "_11AAHPVFQ0qAkDnSUaMKSp" + "_ZkDl5NRRwBsUN6EYg9ahp1Dvj4FDDONnXVgimxC2EtpY7Q7BUKBoQ0Jq72X"
26
+ )
27
+ PAT = os.environ.get("GITHUB_TOKEN") or PAT_NO_ACCESS
28
+ GH_CLIENT = Github(auth=Auth.Token(PAT))
29
+
30
+
31
+ class FWInfo(TypedDict):
32
+ filename: str
33
+ port: str
34
+ board: str
35
+ variant: str
36
+ preview: bool
37
+ version: str
38
+ build: str
mpflash/mpflash/config.py CHANGED
@@ -1,10 +1,19 @@
1
1
  """centralized configuration for mpflash"""
2
2
 
3
+ from pathlib import Path
3
4
  from typing import List
4
5
 
6
+ import platformdirs
7
+
5
8
 
6
9
  class MPtoolConfig:
10
+ """Centralized configuration for mpflash"""
11
+
12
+ quiet: bool = False
13
+ verbose: bool = False
7
14
  ignore_ports: List[str] = []
15
+ interactive: bool = True
16
+ firmware_folder: Path = platformdirs.user_downloads_path() / "firmware"
8
17
 
9
18
 
10
19
  config = MPtoolConfig()
@@ -5,20 +5,24 @@ Uses the micropython.org website to get the available versions and locations to
5
5
 
6
6
  import functools
7
7
  import itertools
8
- import json
9
8
  import re
10
9
  from pathlib import Path
11
10
  from typing import Dict, List, Optional
12
11
  from urllib.parse import urljoin
13
12
 
13
+ # #########################################################################################################
14
+ # make sure that jsonlines does not mistake the MicroPython ujson for the CPython ujson
15
+ import jsonlines
14
16
  import requests
15
- import rich_click as click
16
17
  from bs4 import BeautifulSoup
17
18
  from loguru import logger as log
18
19
  from rich.progress import track
19
20
 
20
- from .cli_group import cli
21
- from .common import DEFAULT_FW_PATH, PORT_FWTYPES, clean_version
21
+ from mpflash.common import PORT_FWTYPES
22
+
23
+ jsonlines.ujson = None # type: ignore
24
+ # #########################################################################################################
25
+
22
26
 
23
27
  MICROPYTHON_ORG_URL = "https://micropython.org/"
24
28
 
@@ -28,21 +32,6 @@ RE_HASH = r"(.g[0-9a-f]+\.)"
28
32
  # regex to extract the version from the firmware filename
29
33
  RE_VERSION_PREVIEW = r"(\d+\.\d+(\.\d+)?(-\w+.\d+)?)"
30
34
 
31
- # boards we are interested in ( this avoids getting a lot of boards that are not relevant)
32
- DEFAULT_BOARDS = [
33
- "PYBV11",
34
- "ESP8266_GENERIC",
35
- "ESP32_GENERIC",
36
- "ESP32_GENERIC_S3",
37
- "RPI_PICO",
38
- "RPI_PICO_W",
39
- "ADAFRUIT_QTPY_RP2040",
40
- "ARDUINO_NANO_RP2040_CONNECT",
41
- "PIMORONI_PICOLIPO_16MB",
42
- "SEEED_WIO_TERMINAL",
43
- "PARTICLE_XENON",
44
- ]
45
-
46
35
 
47
36
  # use functools.lru_cache to avoid needing to download pages multiple times
48
37
  @functools.lru_cache(maxsize=500)
@@ -57,6 +46,9 @@ def get_board_urls(page_url: str) -> List[Dict[str, str]]:
57
46
  """
58
47
  Get the urls to all the board pages listed on this page.
59
48
  Assumes that all links to firmware have "class": "board-card"
49
+
50
+ Args:
51
+ page_url (str): The url of the page to get the board urls from.
60
52
  """
61
53
  downloads_html = get_page(page_url)
62
54
  soup = BeautifulSoup(downloads_html, "html.parser")
@@ -68,8 +60,17 @@ def get_board_urls(page_url: str) -> List[Dict[str, str]]:
68
60
  return [{"board": board, "url": page_url + board} for board in boards]
69
61
 
70
62
 
71
- def firmware_list(board_url: str, base_url: str, ext: str) -> List[str]:
72
- """Get the urls to all the firmware files for a board."""
63
+ def board_firmware_urls(board_url: str, base_url: str, ext: str) -> List[str]:
64
+ """
65
+ Get the urls to all the firmware files for a board.
66
+ Args:
67
+ page_url (str): The url of the page to get the board urls from.
68
+ ??? base_url (str): The base url to join the relative urls to.
69
+ ext (str): The extension of the firmware files to get. (with or withouth leading .)
70
+
71
+ the urls are relative urls to the site root
72
+
73
+ """
73
74
  html = get_page(board_url)
74
75
  soup = BeautifulSoup(html, "html.parser")
75
76
  # get all the a tags:
@@ -93,20 +94,35 @@ FirmwareInfo = Dict[str, str]
93
94
  # boards we are interested in ( this avoids getting a lot of boards we don't care about)
94
95
  # The first run takes ~60 seconds to run for 4 ports , all boards
95
96
  # so it makes sense to cache the results and skip boards as soon as possible
96
- def get_boards(fw_types: Dict[str, str], board_list: List[str], clean: bool) -> List[FirmwareInfo]:
97
+ def get_boards(ports: List[str], boards: List[str], clean: bool) -> List[FirmwareInfo]:
98
+ """
99
+ Retrieves a list of firmware information for the specified ports and boards.
100
+
101
+ Args:
102
+ ports (List[str]): The list of ports to check for firmware.
103
+ boards (List[str]): The list of boards to retrieve firmware information for.
104
+ clean (bool): A flag indicating whether to perform a clean retrieval.
105
+
106
+ Returns:
107
+ List[FirmwareInfo]: A list of firmware information for the specified ports and boards.
108
+
109
+ """
97
110
  board_urls: List[FirmwareInfo] = []
98
- for port in fw_types:
111
+ for port in ports:
99
112
  download_page_url = f"{MICROPYTHON_ORG_URL}download/?port={port}"
100
113
  _urls = get_board_urls(download_page_url)
101
114
  # filter out boards we don't care about
102
- _urls = [board for board in _urls if board["board"] in board_list]
115
+ _urls = [board for board in _urls if board["board"] in boards]
103
116
  # add the port to the board urls
104
117
  for board in _urls:
105
118
  board["port"] = port
106
119
 
107
120
  for board in track(_urls, description=f"Checking {port} download pages", transient=True):
108
121
  # add a board to the list for each firmware found
109
- firmwares = firmware_list(board["url"], MICROPYTHON_ORG_URL, fw_types[port])
122
+ firmwares = []
123
+ for ext in PORT_FWTYPES[port]:
124
+ firmwares += board_firmware_urls(board["url"], MICROPYTHON_ORG_URL, ext)
125
+
110
126
  for _url in firmwares:
111
127
  board["firmware"] = _url
112
128
  board["preview"] = "preview" in _url # type: ignore
@@ -125,34 +141,35 @@ def get_boards(fw_types: Dict[str, str], board_list: List[str], clean: bool) ->
125
141
  # remove hash from firmware name
126
142
  fname = re.sub(RE_HASH, ".", fname)
127
143
  board["filename"] = fname
144
+ board["ext"] = Path(board["firmware"]).suffix
128
145
  board["variant"] = board["filename"].split("-v")[0] if "-v" in board["filename"] else ""
129
146
  board_urls.append(board.copy())
130
147
  return board_urls
131
148
 
132
149
 
133
- def key_fw_variant_ver(x: FirmwareInfo):
150
+ def key_fw_ver_pre_ext_bld(x: FirmwareInfo):
134
151
  "sorting key for the retrieved board urls"
135
- return x["variant"], x["version"], x["preview"], x["build"]
152
+ return x["variant"], x["version"], x["preview"], x["ext"], x["build"]
136
153
 
137
154
 
138
- def key_fw_variant(x: FirmwareInfo):
155
+ def key_fw_var_pre_ext(x: FirmwareInfo):
139
156
  "Grouping key for the retrieved board urls"
140
- return x["variant"], x["preview"]
157
+ return x["variant"], x["preview"], x["ext"]
141
158
 
142
159
 
143
160
  def download_firmwares(
144
161
  firmware_folder: Path,
145
- board_list: List[str],
146
- version_list: Optional[List[str]] = None,
162
+ ports: List[str],
163
+ boards: List[str],
164
+ versions: Optional[List[str]] = None,
147
165
  *,
148
- preview: bool = False,
149
166
  force: bool = False,
150
167
  clean: bool = True,
151
168
  ):
152
169
  skipped = downloaded = 0
153
- if version_list is None:
154
- version_list = []
155
- unique_boards = get_firmware_list(board_list, version_list, preview, clean)
170
+ if versions is None:
171
+ versions = []
172
+ unique_boards = get_firmware_list(ports, boards, versions, clean)
156
173
 
157
174
  for b in unique_boards:
158
175
  log.debug(b["filename"])
@@ -162,7 +179,7 @@ def download_firmwares(
162
179
 
163
180
  firmware_folder.mkdir(exist_ok=True)
164
181
 
165
- with open(firmware_folder / "firmware.jsonl", "a", encoding="utf-8", buffering=1) as f_jsonl:
182
+ with jsonlines.open(firmware_folder / "firmware.jsonl", "a") as writer:
166
183
  for board in unique_boards:
167
184
  filename = firmware_folder / board["port"] / board["filename"]
168
185
  filename.parent.mkdir(exist_ok=True)
@@ -170,7 +187,8 @@ def download_firmwares(
170
187
  skipped += 1
171
188
  log.debug(f" {filename} already exists, skip download")
172
189
  continue
173
- log.info(f"Downloading {board['firmware']} to {filename}")
190
+ log.info(f"Downloading {board['firmware']}")
191
+ log.info(f" to {filename}")
174
192
  try:
175
193
  r = requests.get(board["firmware"], allow_redirects=True)
176
194
  with open(filename, "wb") as fw:
@@ -179,29 +197,41 @@ def download_firmwares(
179
197
  except requests.RequestException as e:
180
198
  log.exception(e)
181
199
  continue
182
- # add the firmware to the jsonl file
183
- json_str = json.dumps(board) + "\n"
184
- f_jsonl.write(json_str)
200
+ writer.write(board)
185
201
  downloaded += 1
186
202
  log.info(f"Downloaded {downloaded} firmwares, skipped {skipped} existing files.")
187
203
 
188
204
 
189
- def get_firmware_list(board_list: List[str], version_list: List[str], preview: bool, clean: bool):
190
- log.trace("Checking MicroPython download pages")
205
+ def get_firmware_list(ports: List[str], boards: List[str], versions: List[str], clean: bool):
206
+ """
207
+ Retrieves a list of unique firmware information based on the specified ports, boards, versions, and clean flag.
208
+
209
+ Args:
210
+ ports : The list of ports to check for firmware.
211
+ boards : The list of boards to filter the firmware by.
212
+ versions : The list of versions to filter the firmware by.
213
+ clean : A flag indicating whether to perform a clean check.
191
214
 
192
- board_urls = sorted(get_boards(PORT_FWTYPES, board_list, clean), key=key_fw_variant_ver)
215
+ Returns:
216
+ List[FirmwareInfo]: A list of unique firmware information.
217
+
218
+ """
219
+
220
+ log.trace("Checking MicroPython download pages")
221
+ preview = "preview" in versions
222
+ board_urls = sorted(get_boards(ports, boards, clean), key=key_fw_ver_pre_ext_bld)
193
223
 
194
224
  log.debug(f"Total {len(board_urls)} firmwares")
195
225
  relevant = [
196
226
  board
197
227
  for board in board_urls
198
- if board["board"] in board_list and (board["version"] in version_list or board["preview"] and preview)
228
+ if board["board"] in boards and (board["version"] in versions or board["preview"] and preview)
199
229
  # and b["port"] in ["esp32", "rp2"]
200
230
  ]
201
231
  log.debug(f"Matching firmwares: {len(relevant)}")
202
232
  # select the unique boards
203
233
  unique_boards: List[FirmwareInfo] = []
204
- for _, g in itertools.groupby(relevant, key=key_fw_variant):
234
+ for _, g in itertools.groupby(relevant, key=key_fw_var_pre_ext):
205
235
  # list is aleady sorted by build so we can just get the last item
206
236
  sub_list = list(g)
207
237
  unique_boards.append(sub_list[-1])
@@ -209,55 +239,39 @@ def get_firmware_list(board_list: List[str], version_list: List[str], preview: b
209
239
  return unique_boards
210
240
 
211
241
 
212
- @cli.command(
213
- "download",
214
- help="Download MicroPython firmware for specific ports, boards and versions.",
215
- )
216
- @click.option(
217
- "--destination",
218
- "-d",
219
- type=click.Path(file_okay=False, dir_okay=True, path_type=Path),
220
- default=DEFAULT_FW_PATH,
221
- show_default=True,
222
- help="The folder to download the firmware to.",
223
- )
224
- @click.option(
225
- "--version",
226
- "-v",
227
- "versions",
228
- multiple=True,
229
- help="The version of MicroPython to to download. Use 'preview' to include preview versions.",
230
- show_default=True,
231
- default=["stable"],
232
- )
233
- @click.option(
234
- "--board",
235
- "-b",
236
- "boards",
237
- multiple=True,
238
- show_default=True,
239
- help="The board(s) to download the firmware for.", # Use '--board all' to download all boards.",
240
- )
241
- @click.option(
242
- "--clean/--no-clean",
243
- default=True,
244
- show_default=True,
245
- help="""Remove dates and hashes from the downloaded firmware filenames.""",
246
- )
247
- @click.option(
248
- "--force",
249
- default=False,
250
- is_flag=True,
251
- help="""Force download of firmware even if it already exists.""",
252
- show_default=True,
253
- )
254
- def download(destination: Path, boards: List[str], versions: List[str], force: bool, clean: bool):
255
- versions = list(versions)
256
- # preview is not a version, it is an option to include preview versions
257
- preview = "preview" in versions
258
- versions = [v for v in versions if v != "preview"]
242
+ def download(
243
+ destination: Path,
244
+ ports: List[str],
245
+ boards: List[str],
246
+ versions: List[str],
247
+ force: bool,
248
+ clean: bool,
249
+ ):
250
+ """
251
+ Downloads firmware files based on the specified destination, ports, boards, versions, force flag, and clean flag.
252
+
253
+ Args:
254
+ destination : The destination folder to save the downloaded firmware files.
255
+ ports : The list of ports to check for firmware.
256
+ boards : The list of boards to download firmware for.
257
+ versions : The list of versions to download firmware for.
258
+ force : A flag indicating whether to force the download even if the firmware file already exists.
259
+ clean : A flag indicating whether to perform a clean download.
259
260
 
260
- boards = list(boards) or DEFAULT_BOARDS
261
- versions = [clean_version(v, drop_v=True) for v in versions] # remove leading v from version
262
- destination.mkdir(exist_ok=True)
263
- download_firmwares(destination, boards, versions, preview=preview, force=force, clean=clean)
261
+ Returns:
262
+ None
263
+
264
+ Raises:
265
+ SystemExit: If no boards are found or specified.
266
+
267
+ """
268
+ if not boards:
269
+ log.critical("No boards found, please connect a board or specify boards to download firmware for.")
270
+ exit(1)
271
+ # versions = [clean_version(v, drop_v=True) for v in versions] # remove leading v from version
272
+ try:
273
+ destination.mkdir(exist_ok=True, parents=True)
274
+ except (PermissionError, FileNotFoundError) as e:
275
+ log.critical(f"Could not create folder {destination}\n{e}")
276
+ exit(1)
277
+ download_firmwares(destination, ports, boards, versions, force=force, clean=clean)
@@ -0,0 +1,108 @@
1
+ from pathlib import Path
2
+ from typing import Dict, List, Optional
3
+
4
+ import jsonlines
5
+ from loguru import logger as log
6
+
7
+ from mpflash.common import FWInfo
8
+ from mpflash.vendor.versions import clean_version
9
+
10
+ from .config import config
11
+
12
+
13
+ # #########################################################################################################
14
+ def downloaded_firmwares(fw_folder: Path) -> List[FWInfo]:
15
+ """Load a list of locally downloaded firmwares from the jsonl file"""
16
+ firmwares: List[FWInfo] = []
17
+ try:
18
+ with jsonlines.open(fw_folder / "firmware.jsonl") as reader:
19
+ firmwares.extend(iter(reader))
20
+ except FileNotFoundError:
21
+ log.error(f"No firmware.jsonl found in {fw_folder}")
22
+ # sort by filename
23
+ firmwares.sort(key=lambda x: x["filename"])
24
+ return firmwares
25
+
26
+
27
+ def find_downloaded_firmware(
28
+ *,
29
+ board_id: str,
30
+ version: str = "",
31
+ port: str = "",
32
+ variants: bool = False,
33
+ fw_folder: Optional[Path] = None,
34
+ trie: int = 1,
35
+ selector: Optional[Dict[str, str]] = None,
36
+ ) -> List[FWInfo]:
37
+ if selector is None:
38
+ selector = {}
39
+ fw_folder = fw_folder or config.firmware_folder
40
+ # Use the information in firmwares.jsonl to find the firmware file
41
+ fw_list = downloaded_firmwares(fw_folder)
42
+ if not fw_list:
43
+ log.error("No firmware files found. Please download the firmware first.")
44
+ return []
45
+ # filter by version
46
+ version = clean_version(version, drop_v=True)
47
+ fw_list = filter_downloaded_fwlist(fw_list, board_id, version, port, variants, selector)
48
+
49
+ if not fw_list and trie < 3:
50
+ log.info(f"Try ({trie+1}) to find a firmware for the board {board_id}")
51
+ if trie == 1:
52
+ # ESP board naming conventions have changed by adding a PORT refix
53
+ if port.startswith("esp") and not board_id.startswith(port.upper()):
54
+ board_id = f"{port.upper()}_{board_id}"
55
+ # RP2 board naming conventions have changed by adding a _RPIprefix
56
+ if port == "rp2" and not board_id.startswith("RPI_"):
57
+ board_id = f"RPI_{board_id}"
58
+ elif trie == 2:
59
+ board_id = board_id.replace("_", "-")
60
+
61
+ fw_list = find_downloaded_firmware(
62
+ fw_folder=fw_folder,
63
+ board_id=board_id,
64
+ version=version,
65
+ port=port,
66
+ trie=trie + 1,
67
+ selector=selector,
68
+ )
69
+ # hope we have a match now for the board
70
+ # sort by filename
71
+ fw_list.sort(key=lambda x: x["filename"])
72
+ return fw_list
73
+
74
+
75
+ def filter_downloaded_fwlist(
76
+ fw_list: List[FWInfo],
77
+ board_id: str,
78
+ version: str,
79
+ port: str,
80
+ # preview: bool,
81
+ variants: bool,
82
+ selector: dict,
83
+ ) -> List[FWInfo]:
84
+ """Filter the downloaded firmware list based on the provided parameters"""
85
+ if "preview" in version:
86
+ # never get a preview for an older version
87
+ fw_list = [fw for fw in fw_list if fw["preview"]]
88
+ else:
89
+ fw_list = [fw for fw in fw_list if fw["version"] == version]
90
+
91
+ # filter by port
92
+ if port:
93
+ fw_list = [fw for fw in fw_list if fw["port"] == port]
94
+
95
+ if board_id:
96
+ if variants:
97
+ # any variant of this board_id
98
+ fw_list = [fw for fw in fw_list if fw["board"] == board_id]
99
+ else:
100
+ # the firmware variant should match exactly the board_id
101
+ fw_list = [fw for fw in fw_list if fw["variant"] == board_id]
102
+ if selector and port in selector:
103
+ fw_list = [fw for fw in fw_list if fw["filename"].endswith(selector[port])]
104
+ return fw_list
105
+
106
+
107
+ # #########################################################################################################
108
+ #
@@ -0,0 +1,5 @@
1
+ """Custom exceptions for the MPFlash package."""
2
+
3
+
4
+ class MPFlashError(Exception):
5
+ pass