micropython-stubber 1.20.1__py3-none-any.whl → 1.20.4__py3-none-any.whl

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