micropython-stubber 1.17.5__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.5.dist-info → micropython_stubber-1.19.0.dist-info}/METADATA +7 -6
  2. {micropython_stubber-1.17.5.dist-info → micropython_stubber-1.19.0.dist-info}/RECORD +71 -52
  3. mpflash/README.md +22 -3
  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 +41 -8
  9. mpflash/mpflash/cli_list.py +41 -0
  10. mpflash/mpflash/cli_main.py +13 -8
  11. mpflash/mpflash/common.py +33 -122
  12. mpflash/mpflash/config.py +9 -0
  13. mpflash/mpflash/{downloader.py → download.py} +112 -120
  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 +25 -12
  23. mpflash/mpflash/flash_uf2_windows.py +24 -12
  24. mpflash/mpflash/list.py +34 -37
  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 +22 -5
  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 +24 -8
  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 -287
  69. stubber/bulk/board_id.py +0 -40
  70. stubber/bulk/mpremoteboard.py +0 -141
  71. {micropython_stubber-1.17.5.dist-info → micropython_stubber-1.19.0.dist-info}/LICENSE +0 -0
  72. {micropython_stubber-1.17.5.dist-info → micropython_stubber-1.19.0.dist-info}/WHEEL +0 -0
  73. {micropython_stubber-1.17.5.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,127 +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
- from github import Github
5
- from loguru import logger as log
6
- 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
7
10
 
8
11
  PORT_FWTYPES = {
9
- "stm32": ".hex",
10
- "esp32": ".bin",
11
- "esp8266": ".bin",
12
- "rp2": ".uf2",
13
- "samd": ".uf2",
14
- "mimxrt": ".hex",
15
- "nrf": ".hex",
16
- "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"],
17
20
  }
18
21
 
19
- DEFAULT_FW_PATH = Path.cwd() / "firmware"
20
- # DEFAULT_FW_PATH = Path.home() / "mp_firmware"
21
-
22
- FWInfo = Dict[str, Union[str, bool]]
23
-
24
- #############################################################
25
- # Version handling copied from stubber/utils/versions.py
26
- #############################################################
27
- V_PREVIEW = "preview"
28
- "Latest preview version"
29
-
30
- SET_PREVIEW = {"preview", "latest", "master"}
31
-
32
-
33
- def clean_version(
34
- version: str,
35
- *,
36
- build: bool = False,
37
- patch: bool = False,
38
- commit: bool = False,
39
- drop_v: bool = False,
40
- flat: bool = False,
41
- ):
42
- "Clean up and transform the many flavours of versions"
43
- # 'v1.13.0-103-gb137d064e' --> 'v1.13-103'
44
- if version in {"", "-"}:
45
- return version
46
- if version.lower() == "stable":
47
- _v = get_stable_version()
48
- if not _v:
49
- log.warning("Could not determine the latest stable version")
50
- return "stable"
51
- version = _v
52
- log.info(f"Using latest stable version: {version}")
53
- is_preview = "-preview" in version
54
- nibbles = version.split("-")
55
- ver_ = nibbles[0].lower().lstrip("v")
56
- if not patch and ver_ >= "1.10.0" and ver_ < "1.20.0" and ver_.endswith(".0"):
57
- # remove the last ".0" - but only for versions between 1.10 and 1.20 (because)
58
- nibbles[0] = nibbles[0][:-2]
59
- if len(nibbles) == 1:
60
- version = nibbles[0]
61
- elif build and not is_preview:
62
- version = "-".join(nibbles) if commit else "-".join(nibbles[:-1])
63
- else:
64
- # version = "-".join((nibbles[0], LATEST))
65
- # HACK: this is not always right, but good enough most of the time
66
- if is_preview:
67
- version = "-".join((nibbles[0], V_PREVIEW))
68
- else:
69
- version = V_PREVIEW
70
- if flat:
71
- version = version.strip().replace(".", "_").replace("-", "_")
72
- else:
73
- version = version.strip().replace("_preview", "-preview").replace("_", ".")
74
-
75
- if drop_v:
76
- version = version.lstrip("v")
77
- elif not version.startswith("v") and version.lower() not in SET_PREVIEW:
78
- version = "v" + version
79
- if version in SET_PREVIEW:
80
- version = V_PREVIEW
81
- return version
82
-
83
-
84
- def micropython_versions(minver: str = "v1.9.2"):
85
- """Get the list of micropython versions from github tags"""
86
- try:
87
- g = Github()
88
- _ = 1 / 0
89
- repo = g.get_repo("micropython/micropython")
90
- versions = [
91
- tag.name for tag in repo.get_tags() if parse(tag.name) >= parse(minver)
92
- ]
93
- except Exception:
94
- versions = [
95
- "v9.99.9-preview",
96
- "v1.22.2",
97
- "v1.22.1",
98
- "v1.22.0",
99
- "v1.21.1",
100
- "v1.21.0",
101
- "v1.20.0",
102
- "v1.19.1",
103
- "v1.19",
104
- "v1.18",
105
- "v1.17",
106
- "v1.16",
107
- "v1.15",
108
- "v1.14",
109
- "v1.13",
110
- "v1.12",
111
- "v1.11",
112
- "v1.10",
113
- "v1.9.4",
114
- "v1.9.3",
115
- ]
116
- versions = [v for v in versions if parse(v) >= parse(minver)]
117
- return sorted(versions)
118
-
119
-
120
- def get_stable_version() -> str:
121
- # read the versions from the git tags
122
- all_versions = micropython_versions(minver="v1.17")
123
- stable_version = [v for v in all_versions if not v.endswith(V_PREVIEW)][-1]
124
- return stable_version
125
-
126
-
127
- #############################################################
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 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,20 +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
- ]
44
-
45
35
 
46
36
  # use functools.lru_cache to avoid needing to download pages multiple times
47
37
  @functools.lru_cache(maxsize=500)
@@ -56,6 +46,9 @@ def get_board_urls(page_url: str) -> List[Dict[str, str]]:
56
46
  """
57
47
  Get the urls to all the board pages listed on this page.
58
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.
59
52
  """
60
53
  downloads_html = get_page(page_url)
61
54
  soup = BeautifulSoup(downloads_html, "html.parser")
@@ -67,8 +60,17 @@ def get_board_urls(page_url: str) -> List[Dict[str, str]]:
67
60
  return [{"board": board, "url": page_url + board} for board in boards]
68
61
 
69
62
 
70
- def firmware_list(board_url: str, base_url: str, ext: str) -> List[str]:
71
- """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
+ """
72
74
  html = get_page(board_url)
73
75
  soup = BeautifulSoup(html, "html.parser")
74
76
  # get all the a tags:
@@ -77,9 +79,7 @@ def firmware_list(board_url: str, base_url: str, ext: str) -> List[str]:
77
79
  tags = soup.findAll(
78
80
  "a",
79
81
  recursive=True,
80
- attrs={
81
- "href": re.compile(r"^/resources/firmware/.*\." + ext.lstrip(".") + "$")
82
- },
82
+ attrs={"href": re.compile(r"^/resources/firmware/.*\." + ext.lstrip(".") + "$")},
83
83
  )
84
84
  if "?" in base_url:
85
85
  base_url = base_url.split("?")[0]
@@ -94,24 +94,35 @@ FirmwareInfo = Dict[str, str]
94
94
  # boards we are interested in ( this avoids getting a lot of boards we don't care about)
95
95
  # The first run takes ~60 seconds to run for 4 ports , all boards
96
96
  # so it makes sense to cache the results and skip boards as soon as possible
97
- def get_boards(
98
- fw_types: Dict[str, str], board_list: List[str], clean: bool
99
- ) -> 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
+ """
100
110
  board_urls: List[FirmwareInfo] = []
101
- for port in fw_types:
111
+ for port in ports:
102
112
  download_page_url = f"{MICROPYTHON_ORG_URL}download/?port={port}"
103
113
  _urls = get_board_urls(download_page_url)
104
114
  # filter out boards we don't care about
105
- _urls = [board for board in _urls if board["board"] in board_list]
115
+ _urls = [board for board in _urls if board["board"] in boards]
106
116
  # add the port to the board urls
107
117
  for board in _urls:
108
118
  board["port"] = port
109
119
 
110
- for board in track(
111
- _urls, description="Checking download pages", transient=True
112
- ):
120
+ for board in track(_urls, description=f"Checking {port} download pages", transient=True):
113
121
  # add a board to the list for each firmware found
114
- 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
+
115
126
  for _url in firmwares:
116
127
  board["firmware"] = _url
117
128
  board["preview"] = "preview" in _url # type: ignore
@@ -130,38 +141,35 @@ def get_boards(
130
141
  # remove hash from firmware name
131
142
  fname = re.sub(RE_HASH, ".", fname)
132
143
  board["filename"] = fname
133
- board["variant"] = (
134
- board["filename"].split("-v")[0]
135
- if "-v" in board["filename"]
136
- else ""
137
- )
144
+ board["ext"] = Path(board["firmware"]).suffix
145
+ board["variant"] = board["filename"].split("-v")[0] if "-v" in board["filename"] else ""
138
146
  board_urls.append(board.copy())
139
147
  return board_urls
140
148
 
141
149
 
142
- def key_fw_variant_ver(x: FirmwareInfo):
150
+ def key_fw_ver_pre_ext_bld(x: FirmwareInfo):
143
151
  "sorting key for the retrieved board urls"
144
- return x["variant"], x["version"], x["preview"], x["build"]
152
+ return x["variant"], x["version"], x["preview"], x["ext"], x["build"]
145
153
 
146
154
 
147
- def key_fw_variant(x: FirmwareInfo):
155
+ def key_fw_var_pre_ext(x: FirmwareInfo):
148
156
  "Grouping key for the retrieved board urls"
149
- return x["variant"], x["preview"]
157
+ return x["variant"], x["preview"], x["ext"]
150
158
 
151
159
 
152
160
  def download_firmwares(
153
161
  firmware_folder: Path,
154
- board_list: List[str],
155
- version_list: Optional[List[str]] = None,
162
+ ports: List[str],
163
+ boards: List[str],
164
+ versions: Optional[List[str]] = None,
156
165
  *,
157
- preview: bool = False,
158
166
  force: bool = False,
159
167
  clean: bool = True,
160
168
  ):
161
169
  skipped = downloaded = 0
162
- if version_list is None:
163
- version_list = []
164
- 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)
165
173
 
166
174
  for b in unique_boards:
167
175
  log.debug(b["filename"])
@@ -171,9 +179,7 @@ def download_firmwares(
171
179
 
172
180
  firmware_folder.mkdir(exist_ok=True)
173
181
 
174
- with open(
175
- firmware_folder / "firmware.jsonl", "a", encoding="utf-8", buffering=1
176
- ) as f_jsonl:
182
+ with jsonlines.open(firmware_folder / "firmware.jsonl", "a") as writer:
177
183
  for board in unique_boards:
178
184
  filename = firmware_folder / board["port"] / board["filename"]
179
185
  filename.parent.mkdir(exist_ok=True)
@@ -181,7 +187,8 @@ def download_firmwares(
181
187
  skipped += 1
182
188
  log.debug(f" {filename} already exists, skip download")
183
189
  continue
184
- log.info(f"Downloading {board['firmware']} to {filename}")
190
+ log.info(f"Downloading {board['firmware']}")
191
+ log.info(f" to {filename}")
185
192
  try:
186
193
  r = requests.get(board["firmware"], allow_redirects=True)
187
194
  with open(filename, "wb") as fw:
@@ -190,34 +197,41 @@ def download_firmwares(
190
197
  except requests.RequestException as e:
191
198
  log.exception(e)
192
199
  continue
193
- # add the firmware to the jsonl file
194
- json_str = json.dumps(board) + "\n"
195
- f_jsonl.write(json_str)
200
+ writer.write(board)
196
201
  downloaded += 1
197
202
  log.info(f"Downloaded {downloaded} firmwares, skipped {skipped} existing files.")
198
203
 
199
204
 
200
- def get_firmware_list(
201
- board_list: List[str], version_list: List[str], preview: bool, clean: bool
202
- ):
203
- 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.
204
208
 
205
- board_urls = sorted(
206
- get_boards(PORT_FWTYPES, board_list, clean), key=key_fw_variant_ver
207
- )
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.
214
+
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)
208
223
 
209
224
  log.debug(f"Total {len(board_urls)} firmwares")
210
225
  relevant = [
211
226
  board
212
227
  for board in board_urls
213
- if board["board"] in board_list
214
- 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)
215
229
  # and b["port"] in ["esp32", "rp2"]
216
230
  ]
217
231
  log.debug(f"Matching firmwares: {len(relevant)}")
218
232
  # select the unique boards
219
233
  unique_boards: List[FirmwareInfo] = []
220
- for _, g in itertools.groupby(relevant, key=key_fw_variant):
234
+ for _, g in itertools.groupby(relevant, key=key_fw_var_pre_ext):
221
235
  # list is aleady sorted by build so we can just get the last item
222
236
  sub_list = list(g)
223
237
  unique_boards.append(sub_list[-1])
@@ -225,61 +239,39 @@ def get_firmware_list(
225
239
  return unique_boards
226
240
 
227
241
 
228
- @cli.command(
229
- "download",
230
- help="Download MicroPython firmware for specific ports, boards and versions.",
231
- )
232
- @click.option(
233
- "--destination",
234
- "-d",
235
- type=click.Path(file_okay=False, dir_okay=True, path_type=Path),
236
- default="./firmware",
237
- show_default=True,
238
- help="The folder to download the firmware to.",
239
- )
240
- @click.option(
241
- "--version",
242
- "-v",
243
- "versions",
244
- multiple=True,
245
- help="The version of MicroPython to to download. Use 'preview' to include preview versions.",
246
- show_default=True,
247
- default=["stable"],
248
- )
249
- @click.option(
250
- "--board",
251
- "-b",
252
- "boards",
253
- multiple=True,
254
- show_default=True,
255
- help="The board(s) to download the firmware for.", # Use '--board all' to download all boards.",
256
- )
257
- @click.option(
258
- "--clean/--no-clean",
259
- default=True,
260
- show_default=True,
261
- help="""Remove dates and hashes from the downloaded firmware filenames.""",
262
- )
263
- @click.option(
264
- "--force",
265
- default=False,
266
- is_flag=True,
267
- help="""Force download of firmware even if it already exists.""",
268
- show_default=True,
269
- )
270
242
  def download(
271
- destination: Path, boards: List[str], versions: List[str], force: bool, clean: bool
243
+ destination: Path,
244
+ ports: List[str],
245
+ boards: List[str],
246
+ versions: List[str],
247
+ force: bool,
248
+ clean: bool,
272
249
  ):
273
- versions = list(versions)
274
- # preview is not a version, it is an option to include preview versions
275
- preview = "preview" in versions
276
- versions = [v for v in versions if v != "preview"]
277
-
278
- boards = list(boards) or DEFAULT_BOARDS
279
- versions = [
280
- clean_version(v, drop_v=True) for v in versions
281
- ] # remove leading v from version
282
- destination.mkdir(exist_ok=True)
283
- download_firmwares(
284
- destination, boards, versions, preview=preview, force=force, clean=clean
285
- )
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.
260
+
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