@@ -410,15 +410,55 @@ def enrich_downloads_from_page(
410410 return {"" : json_default }
411411
412412
413+ def artifact_kind (url : str ) -> str :
414+ """``bin`` / ``uf2`` from a firmware URL, else empty."""
415+ u = (url or "" ).rsplit ("?" , 1 )[0 ].lower ()
416+ if u .endswith (".uf2" ):
417+ return "uf2"
418+ if u .endswith (".bin" ):
419+ return "bin"
420+ return ""
421+
422+
423+ def preferred_artifact_kind (variant : dict [str , Any ], * , uf2 : bool = False ) -> str :
424+ """Which image serial vs UF2 flashing wants.
425+
426+ Espressif boards are written with esptool (``.bin``). rp2/samd (and an
427+ explicit ``uf2=True``, e.g. ESP32-S3 + tinyuf2) want ``.uf2``.
428+ """
429+ if uf2 :
430+ return "uf2"
431+ port = (variant .get ("port" ) or port_for_family (str (variant .get ("family" ) or "" ))).lower ()
432+ if port in ("rp2" , "samd" ):
433+ return "uf2"
434+ return "bin"
435+
436+
437+ def _select_download (items : list [dict [str , str ]], * , kind : str ) -> Optional [dict [str , str ]]:
438+ """Prefer ``kind`` (bin/uf2); fall back to the first entry if none match."""
439+ if not items :
440+ return None
441+ for item in items :
442+ if artifact_kind (item .get ("url" ) or "" ) == kind :
443+ return item
444+ return items [0 ]
445+
446+
413447def pick_download (
414448 variant : dict [str , Any ],
415449 * ,
416450 version : Optional [str ] = None ,
417451 preview : bool = False ,
418452 mp_variant : str = "" ,
419453 fetch : Optional [Fetcher ] = None ,
454+ uf2 : bool = False ,
420455) -> dict [str , str ]:
421- """Choose a download entry for board + MP variant (e.g. C6_WIFI)."""
456+ """Choose a download entry for board + MP variant (e.g. C6_WIFI).
457+
458+ When both ``.bin`` and ``.uf2`` exist for the same version, prefer the
459+ format the flasher will use (``.bin`` for esp32/esptool, ``.uf2`` for
460+ rp2/samd). Pass ``uf2=True`` to force a UF2 (tinyuf2 on ESP32-S3, etc.).
461+ """
422462 by_var = enrich_downloads_from_page (variant , fetch = fetch )
423463 mp_variant = mp_variant or ""
424464 if mp_variant not in by_var :
@@ -428,35 +468,46 @@ def pick_download(
428468 f"{ variant .get ('board' )} ; have: { known } "
429469 )
430470 downloads = by_var [mp_variant ]
471+ kind = preferred_artifact_kind (variant , uf2 = uf2 )
472+ board = variant .get ("board" )
473+ extra = f" / { mp_variant } " if mp_variant else ""
474+
431475 if preview :
432- for d in downloads :
433- if d ["channel" ] == "preview" :
434- return d
435- # Fall back to Thonny regex tweak filtered by variant name in URL.
476+ chosen = _select_download (
477+ [d for d in downloads if d ["channel" ] == "preview" ], kind = kind
478+ )
479+ if chosen :
480+ return chosen
436481 patched = maybe_latest_preview (
437- variant , fetch = fetch , mp_variant = mp_variant , by_variant = by_var
482+ variant ,
483+ fetch = fetch ,
484+ mp_variant = mp_variant ,
485+ by_variant = by_var ,
486+ kind = kind ,
438487 )
439488 if patched :
440489 return patched
441- raise RuntimeError (
442- f"no preview build for { variant .get ('board' )} "
443- + (f" / { mp_variant } " if mp_variant else "" )
444- )
490+ raise RuntimeError (f"no preview build for { board } { extra } " )
491+
445492 if version :
446493 ver = version .lstrip ("v" )
447- for d in downloads :
448- if d ["version" ].lstrip ("v" ) == ver :
449- return d
450- raise RuntimeError (
451- f"version { version } not found for { variant .get ('board' )} "
452- + (f" / { mp_variant } " if mp_variant else "" )
494+ chosen = _select_download (
495+ [d for d in downloads if d ["version" ].lstrip ("v" ) == ver ], kind = kind
453496 )
454- for d in downloads :
455- if d ["channel" ] == "release" :
456- return d
497+ if chosen :
498+ return chosen
499+ raise RuntimeError (f"version { version } not found for { board } { extra } " )
500+
501+ chosen = _select_download (
502+ [d for d in downloads if d ["channel" ] == "release" ], kind = kind
503+ )
504+ if chosen :
505+ return chosen
457506 if downloads :
458- return downloads [0 ]
459- raise RuntimeError (f"no downloads for { variant .get ('board' )} " )
507+ fallback = _select_download (downloads , kind = kind )
508+ if fallback :
509+ return fallback
510+ raise RuntimeError (f"no downloads for { board } " )
460511
461512
462513def maybe_latest_preview (
@@ -465,13 +516,15 @@ def maybe_latest_preview(
465516 fetch : Optional [Fetcher ] = None ,
466517 mp_variant : str = "" ,
467518 by_variant : Optional [dict [str , list [dict [str , str ]]]] = None ,
519+ kind : str = "" ,
468520) -> Optional [dict [str , str ]]:
469521 """Pick latest preview for an MP variant from scraped page data."""
470522 if by_variant is None :
471523 by_variant = enrich_downloads_from_page (variant , fetch = fetch )
472- for d in by_variant .get (mp_variant or "" , []):
473- if d ["channel" ] == "preview" :
474- return d
524+ previews = [d for d in by_variant .get (mp_variant or "" , []) if d ["channel" ] == "preview" ]
525+ chosen = _select_download (previews , kind = kind ) if kind else (previews [0 ] if previews else None )
526+ if chosen :
527+ return chosen
475528 # Thonny regex fallback (base image only).
476529 regex_s = variant .get ("latest_prerelease_regex" )
477530 info_url = variant .get ("info_url" ) or ""
@@ -501,6 +554,8 @@ def maybe_latest_preview(
501554 m = rx .search (name )
502555 if not m :
503556 continue
557+ if kind and artifact_kind (rel ) and artifact_kind (rel ) != kind :
558+ continue
504559 ver_m = re .search (r"(v?\d+\.\d+\.\d+-preview\.\d+\.[a-z0-9]+)" , name , re .I )
505560 version = ver_m .group (1 ) if ver_m else m .group (0 )
506561 return {
@@ -554,6 +609,7 @@ def download_board(
554609 version : Optional [str ] = None ,
555610 preview : bool = False ,
556611 mp_variant : str = "" ,
612+ uf2 : bool = False ,
557613 data_prefix : str = DEFAULT_DATA_PREFIX ,
558614 fetch : Optional [Fetcher ] = None ,
559615 catalog : Optional [list [dict [str , Any ]]] = None ,
@@ -568,6 +624,7 @@ def download_board(
568624 preview = preview ,
569625 mp_variant = mp_variant or "" ,
570626 fetch = fetch ,
627+ uf2 = uf2 ,
571628 )
572629 path = download_file (chosen ["url" ])
573630 st = path .stat ()
0 commit comments