micropython-stubber 1.20.0__py3-none-any.whl → 1.20.2__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.
- {micropython_stubber-1.20.0.dist-info → micropython_stubber-1.20.2.dist-info}/METADATA +6 -6
- {micropython_stubber-1.20.0.dist-info → micropython_stubber-1.20.2.dist-info}/RECORD +58 -51
- mpflash/README.md +54 -35
- mpflash/libusb_flash.ipynb +203 -203
- mpflash/mpflash/add_firmware.py +98 -0
- mpflash/mpflash/ask_input.py +106 -114
- mpflash/mpflash/cli_download.py +58 -37
- mpflash/mpflash/cli_flash.py +77 -35
- mpflash/mpflash/cli_group.py +14 -12
- mpflash/mpflash/cli_list.py +40 -4
- mpflash/mpflash/cli_main.py +20 -8
- mpflash/mpflash/common.py +125 -12
- mpflash/mpflash/config.py +2 -0
- mpflash/mpflash/connected.py +74 -0
- mpflash/mpflash/download.py +67 -50
- mpflash/mpflash/downloaded.py +9 -9
- mpflash/mpflash/flash.py +2 -2
- mpflash/mpflash/flash_esp.py +2 -2
- mpflash/mpflash/flash_uf2.py +16 -8
- mpflash/mpflash/flash_uf2_linux.py +5 -16
- mpflash/mpflash/flash_uf2_macos.py +78 -0
- mpflash/mpflash/flash_uf2_windows.py +1 -1
- mpflash/mpflash/list.py +58 -57
- mpflash/mpflash/mpboard_id/__init__.py +37 -44
- mpflash/mpflash/mpboard_id/add_boards.py +255 -0
- mpflash/mpflash/mpboard_id/board.py +37 -0
- mpflash/mpflash/mpboard_id/board_id.py +50 -43
- mpflash/mpflash/mpboard_id/board_info.zip +0 -0
- mpflash/mpflash/mpboard_id/store.py +42 -0
- mpflash/mpflash/mpremoteboard/__init__.py +18 -6
- mpflash/mpflash/mpremoteboard/runner.py +12 -12
- mpflash/mpflash/uf2disk.py +12 -0
- mpflash/mpflash/vendor/basicgit.py +288 -0
- mpflash/mpflash/vendor/dfu.py +1 -0
- mpflash/mpflash/vendor/versions.py +7 -3
- mpflash/mpflash/worklist.py +71 -48
- mpflash/poetry.lock +163 -137
- mpflash/pyproject.toml +18 -15
- stubber/__init__.py +1 -1
- stubber/board/createstubs.py +4 -3
- stubber/board/createstubs_db.py +5 -7
- stubber/board/createstubs_db_min.py +1 -1
- stubber/board/createstubs_db_mpy.mpy +0 -0
- stubber/board/createstubs_mem.py +6 -7
- stubber/board/createstubs_mem_min.py +1 -1
- stubber/board/createstubs_mem_mpy.mpy +0 -0
- stubber/board/createstubs_min.py +2 -2
- stubber/board/createstubs_mpy.mpy +0 -0
- stubber/board/modulelist.txt +1 -0
- stubber/commands/get_core_cmd.py +7 -6
- stubber/commands/get_docstubs_cmd.py +8 -3
- stubber/commands/get_frozen_cmd.py +5 -2
- stubber/publish/publish.py +18 -7
- stubber/utils/makeversionhdr.py +3 -2
- stubber/utils/versions.py +2 -1
- mpflash/mpflash/mpboard_id/board_info.csv +0 -2213
- mpflash/mpflash/mpboard_id/board_info.json +0 -19910
- {micropython_stubber-1.20.0.dist-info → micropython_stubber-1.20.2.dist-info}/LICENSE +0 -0
- {micropython_stubber-1.20.0.dist-info → micropython_stubber-1.20.2.dist-info}/WHEEL +0 -0
- {micropython_stubber-1.20.0.dist-info → micropython_stubber-1.20.2.dist-info}/entry_points.txt +0 -0
mpflash/mpflash/cli_group.py
CHANGED
@@ -12,23 +12,26 @@ from .logger import make_quiet, set_loglevel
|
|
12
12
|
def cb_verbose(ctx, param, value):
|
13
13
|
"""Callback to set the log level to DEBUG if verbose is set"""
|
14
14
|
if value and not config.quiet:
|
15
|
-
set_loglevel("DEBUG")
|
16
15
|
config.verbose = True
|
16
|
+
if value > 1:
|
17
|
+
set_loglevel("TRACE")
|
18
|
+
else:
|
19
|
+
set_loglevel("DEBUG")
|
17
20
|
else:
|
18
21
|
set_loglevel("INFO")
|
19
22
|
config.verbose = False
|
20
23
|
return value
|
21
24
|
|
22
25
|
|
23
|
-
def
|
26
|
+
def cb_interactive(ctx, param, value):
|
24
27
|
if value:
|
25
|
-
config.
|
28
|
+
config.interactive = value
|
26
29
|
return value
|
27
30
|
|
28
31
|
|
29
|
-
def
|
32
|
+
def cb_test(ctx, param, value):
|
30
33
|
if value:
|
31
|
-
config.
|
34
|
+
config.tests = value
|
32
35
|
return value
|
33
36
|
|
34
37
|
|
@@ -64,21 +67,20 @@ def cb_quiet(ctx, param, value):
|
|
64
67
|
"-V",
|
65
68
|
"--verbose",
|
66
69
|
is_eager=True,
|
67
|
-
|
70
|
+
count=True,
|
68
71
|
help="Enables verbose mode.",
|
69
72
|
callback=cb_verbose,
|
70
73
|
)
|
71
74
|
@click.option(
|
72
|
-
"--
|
73
|
-
"-i",
|
75
|
+
"--test",
|
74
76
|
is_eager=True,
|
75
|
-
help="
|
76
|
-
callback=
|
77
|
+
help="test a specific feature",
|
78
|
+
callback=cb_test,
|
77
79
|
multiple=True,
|
78
80
|
default=[],
|
79
|
-
envvar="
|
81
|
+
envvar="MPFLASH_TEST",
|
80
82
|
show_default=True,
|
81
|
-
metavar="
|
83
|
+
metavar="TEST",
|
82
84
|
)
|
83
85
|
def cli(**kwargs):
|
84
86
|
"""mpflash - MicroPython Tool.
|
mpflash/mpflash/cli_list.py
CHANGED
@@ -1,11 +1,13 @@
|
|
1
1
|
import json
|
2
|
+
from typing import List
|
2
3
|
|
3
4
|
import rich_click as click
|
4
5
|
from rich import print
|
5
6
|
|
6
7
|
from .cli_group import cli
|
7
|
-
from .list import
|
8
|
+
from .list import show_mcus
|
8
9
|
from .logger import make_quiet
|
10
|
+
from .connected import list_mcus
|
9
11
|
|
10
12
|
|
11
13
|
@cli.command("list", help="List the connected MCU boards.")
|
@@ -18,24 +20,58 @@ from .logger import make_quiet
|
|
18
20
|
show_default=True,
|
19
21
|
help="""Output in json format""",
|
20
22
|
)
|
23
|
+
@click.option(
|
24
|
+
"--serial",
|
25
|
+
"--serial-port",
|
26
|
+
"-s",
|
27
|
+
"serial",
|
28
|
+
default=["*"],
|
29
|
+
multiple=True,
|
30
|
+
show_default=True,
|
31
|
+
help="Which serial port(s) to list. ",
|
32
|
+
metavar="SERIALPORT",
|
33
|
+
)
|
34
|
+
@click.option(
|
35
|
+
"--ignore",
|
36
|
+
"-i",
|
37
|
+
is_eager=True,
|
38
|
+
help="Serial port(s) to ignore. Defaults to MPFLASH_IGNORE.",
|
39
|
+
multiple=True,
|
40
|
+
default=[],
|
41
|
+
envvar="MPFLASH_IGNORE",
|
42
|
+
show_default=True,
|
43
|
+
metavar="SERIALPORT",
|
44
|
+
)
|
45
|
+
@click.option(
|
46
|
+
"--bluetooth/--no-bluetooth",
|
47
|
+
"-b/-nb",
|
48
|
+
is_flag=True,
|
49
|
+
default=False,
|
50
|
+
show_default=True,
|
51
|
+
help="""Include bluetooth ports in the list""",
|
52
|
+
)
|
21
53
|
@click.option(
|
22
54
|
"--progress/--no-progress",
|
55
|
+
# "-p/-np", -p is already used for --port
|
23
56
|
"progress",
|
24
57
|
is_flag=True,
|
25
58
|
default=True,
|
26
59
|
show_default=True,
|
27
60
|
help="""Show progress""",
|
28
61
|
)
|
29
|
-
def cli_list_mcus(as_json: bool, progress: bool = True):
|
62
|
+
def cli_list_mcus(serial: List[str], ignore: List[str], bluetooth: bool, as_json: bool, progress: bool = True) -> int:
|
30
63
|
"""List the connected MCU boards, and output in a nice table or json."""
|
64
|
+
serial = list(serial)
|
65
|
+
ignore = list(ignore)
|
31
66
|
if as_json:
|
32
67
|
# avoid noise in json output
|
33
68
|
make_quiet()
|
69
|
+
# TODO? Ask user to select a serialport if [?] is given ?
|
34
70
|
|
35
|
-
conn_mcus = list_mcus()
|
71
|
+
conn_mcus = list_mcus(ignore=ignore, include=serial, bluetooth=bluetooth)
|
36
72
|
if as_json:
|
37
73
|
print(json.dumps([mcu.__dict__ for mcu in conn_mcus], indent=4))
|
38
74
|
progress = False
|
39
75
|
if progress:
|
40
76
|
show_mcus(conn_mcus, refresh=False)
|
41
|
-
return conn_mcus
|
77
|
+
return 0 if conn_mcus else 1
|
mpflash/mpflash/cli_main.py
CHANGED
@@ -2,24 +2,36 @@
|
|
2
2
|
|
3
3
|
# import rich_click as click
|
4
4
|
|
5
|
+
import os
|
6
|
+
|
7
|
+
import click
|
8
|
+
from loguru import logger as log
|
9
|
+
|
5
10
|
from .cli_download import cli_download
|
6
11
|
from .cli_flash import cli_flash_board
|
7
12
|
from .cli_group import cli
|
8
13
|
from .cli_list import cli_list_mcus
|
9
14
|
|
10
|
-
# from loguru import logger as log
|
11
|
-
|
12
15
|
|
13
16
|
def mpflash():
|
14
|
-
cli.add_command(cli_flash_board)
|
15
17
|
cli.add_command(cli_list_mcus)
|
16
18
|
cli.add_command(cli_download)
|
19
|
+
cli.add_command(cli_flash_board)
|
20
|
+
|
17
21
|
# cli(auto_envvar_prefix="MPFLASH")
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
22
|
+
if False and os.environ.get("COMPUTERNAME") == "JOSVERL-S4":
|
23
|
+
# intentional less error suppression on dev machine
|
24
|
+
result = cli(standalone_mode=False)
|
25
|
+
else:
|
26
|
+
try:
|
27
|
+
result = cli(standalone_mode=False)
|
28
|
+
exit(result)
|
29
|
+
except AttributeError as e:
|
30
|
+
log.error(f"Error: {e}")
|
31
|
+
exit(-1)
|
32
|
+
except click.exceptions.ClickException as e:
|
33
|
+
log.error(f"Error: {e}")
|
34
|
+
exit(-2)
|
23
35
|
|
24
36
|
|
25
37
|
if __name__ == "__main__":
|
mpflash/mpflash/common.py
CHANGED
@@ -1,11 +1,16 @@
|
|
1
|
+
import fnmatch
|
1
2
|
import os
|
2
|
-
import
|
3
|
-
from
|
3
|
+
import sys
|
4
|
+
from dataclasses import dataclass, field
|
5
|
+
from pathlib import Path
|
6
|
+
from typing import List, Optional, Union
|
4
7
|
|
5
8
|
from github import Auth, Github
|
6
|
-
from
|
9
|
+
from serial.tools import list_ports
|
10
|
+
from serial.tools.list_ports_common import ListPortInfo
|
11
|
+
|
12
|
+
from .logger import log
|
7
13
|
|
8
|
-
from mpflash.errors import MPFlashError
|
9
14
|
# from mpflash.mpremoteboard import MPRemoteBoard
|
10
15
|
|
11
16
|
PORT_FWTYPES = {
|
@@ -14,6 +19,7 @@ PORT_FWTYPES = {
|
|
14
19
|
"esp8266": [".bin"],
|
15
20
|
"rp2": [".uf2"],
|
16
21
|
"samd": [".uf2"],
|
22
|
+
# below this not yet implemented / tested
|
17
23
|
"mimxrt": [".hex"],
|
18
24
|
"nrf": [".uf2"],
|
19
25
|
"renesas-ra": [".hex"],
|
@@ -28,11 +34,118 @@ PAT = os.environ.get("GITHUB_TOKEN") or PAT_NO_ACCESS
|
|
28
34
|
GH_CLIENT = Github(auth=Auth.Token(PAT))
|
29
35
|
|
30
36
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
37
|
+
@dataclass
|
38
|
+
class FWInfo:
|
39
|
+
"""
|
40
|
+
Downloaded Firmware information
|
41
|
+
is somewhat related to the BOARD class in the mpboard_id module
|
42
|
+
"""
|
43
|
+
|
44
|
+
port: str # MicroPython port
|
45
|
+
board: str # MicroPython board
|
46
|
+
filename: str = field(default="") # relative filename of the firmware image
|
47
|
+
firmware: str = field(default="") # url or path to original firmware image
|
48
|
+
variant: str = field(default="") # MicroPython variant
|
49
|
+
preview: bool = field(default=False) # True if the firmware is a preview version
|
50
|
+
version: str = field(default="") # MicroPython version
|
51
|
+
url: str = field(default="") # url to the firmware image download folder
|
52
|
+
build: str = field(default="0") # The build = number of commits since the last release
|
53
|
+
ext: str = field(default="") # the file extension of the firmware
|
54
|
+
family: str = field(default="micropython") # The family of the firmware
|
55
|
+
custom: bool = field(default=False) # True if the firmware is a custom build
|
56
|
+
description: str = field(default="") # Description used by this firmware (custom only)
|
57
|
+
|
58
|
+
def to_dict(self) -> dict:
|
59
|
+
"""Convert the object to a dictionary"""
|
60
|
+
return self.__dict__
|
61
|
+
|
62
|
+
@classmethod
|
63
|
+
def from_dict(cls, data: dict) -> "FWInfo":
|
64
|
+
"""Create a FWInfo object from a dictionary"""
|
65
|
+
# add missing keys
|
66
|
+
if "ext" not in data:
|
67
|
+
data["ext"] = Path(data["firmware"]).suffix
|
68
|
+
if "family" not in data:
|
69
|
+
data["family"] = "micropython"
|
70
|
+
return cls(**data)
|
71
|
+
|
72
|
+
|
73
|
+
@dataclass
|
74
|
+
class Params:
|
75
|
+
"""Common parameters for downloading and flashing firmware"""
|
76
|
+
|
77
|
+
ports: List[str] = field(default_factory=list)
|
78
|
+
boards: List[str] = field(default_factory=list)
|
79
|
+
versions: List[str] = field(default_factory=list)
|
80
|
+
fw_folder: Path = Path()
|
81
|
+
serial: List[str] = field(default_factory=list)
|
82
|
+
ignore: List[str] = field(default_factory=list)
|
83
|
+
|
84
|
+
|
85
|
+
@dataclass
|
86
|
+
class DownloadParams(Params):
|
87
|
+
"""Parameters for downloading firmware"""
|
88
|
+
|
89
|
+
clean: bool = False
|
90
|
+
force: bool = False
|
91
|
+
|
92
|
+
|
93
|
+
@dataclass
|
94
|
+
class FlashParams(Params):
|
95
|
+
"""Parameters for flashing a board"""
|
96
|
+
|
97
|
+
erase: bool = True
|
98
|
+
bootloader: bool = True
|
99
|
+
cpu: str = ""
|
100
|
+
|
101
|
+
|
102
|
+
ParamType = Union[DownloadParams, FlashParams]
|
103
|
+
|
104
|
+
|
105
|
+
def filtered_comports(
|
106
|
+
ignore: Optional[List[str]] = None,
|
107
|
+
include: Optional[List[str]] = None,
|
108
|
+
bluetooth: bool = False,
|
109
|
+
) -> List[ListPortInfo]: # sourcery skip: assign-if-exp
|
110
|
+
"""
|
111
|
+
Get a list of filtered comports.
|
112
|
+
"""
|
113
|
+
if not ignore:
|
114
|
+
ignore = []
|
115
|
+
elif not isinstance(ignore, list): # type: ignore
|
116
|
+
ignore = list(ignore)
|
117
|
+
if not include:
|
118
|
+
include = ["*"]
|
119
|
+
elif not isinstance(include, list): # type: ignore
|
120
|
+
include = list(include)
|
121
|
+
|
122
|
+
# remove ports that are to be ignored
|
123
|
+
log.trace(f"{include=}, {ignore=}, {bluetooth=}")
|
124
|
+
comports = [p for p in list_ports.comports() if not any(fnmatch.fnmatch(p.device, i) for i in ignore)]
|
125
|
+
log.trace(f"comports: {[p.device for p in comports]}")
|
126
|
+
# remove bluetooth ports
|
127
|
+
|
128
|
+
if include != ["*"]:
|
129
|
+
# if there are explicit ports to include, add them to the list
|
130
|
+
explicit = [p for p in list_ports.comports() if any(fnmatch.fnmatch(p.device, i) for i in include)]
|
131
|
+
log.trace(f"explicit: {[p.device for p in explicit]}")
|
132
|
+
if ignore == []:
|
133
|
+
# if nothing to ignore, just use the explicit list as a sinple sane default
|
134
|
+
comports = explicit
|
135
|
+
else:
|
136
|
+
# if there are ports to ignore, add the explicit list to the filtered list
|
137
|
+
comports = list(set(explicit) | set(comports))
|
138
|
+
if not bluetooth:
|
139
|
+
# filter out bluetooth ports
|
140
|
+
comports = [p for p in comports if "bluetooth" not in p.description.lower()]
|
141
|
+
comports = [p for p in comports if "BTHENUM" not in p.hwid]
|
142
|
+
if sys.platform == "darwin":
|
143
|
+
comports = [p for p in comports if ".Bluetooth" not in p.device]
|
144
|
+
log.trace(f"no Bluetooth: {[p.device for p in comports]}")
|
145
|
+
log.debug(f"filtered_comports: {[p.device for p in comports]}")
|
146
|
+
# sort
|
147
|
+
if sys.platform == "win32":
|
148
|
+
# Windows sort of comports by number - but fallback to device name
|
149
|
+
return sorted(comports, key=lambda x: int(x.device.split()[0][3:]) if x.device.split()[0][3:].isdigit() else x)
|
150
|
+
# sort by device name
|
151
|
+
return sorted(comports, key=lambda x: x.device)
|
mpflash/mpflash/config.py
CHANGED
@@ -0,0 +1,74 @@
|
|
1
|
+
from typing import List, Tuple
|
2
|
+
|
3
|
+
from rich import print
|
4
|
+
from rich.progress import BarColumn, Progress, SpinnerColumn, TextColumn, TimeElapsedColumn
|
5
|
+
from rich.table import Column
|
6
|
+
|
7
|
+
from mpflash.mpremoteboard import MPRemoteBoard
|
8
|
+
|
9
|
+
from .common import filtered_comports
|
10
|
+
|
11
|
+
|
12
|
+
def connected_ports_boards(
|
13
|
+
*, include: List[str], ignore: List[str]
|
14
|
+
) -> Tuple[List[str], List[str], List[MPRemoteBoard]]:
|
15
|
+
"""
|
16
|
+
Returns a tuple containing lists of unique ports and boards from the connected MCUs.
|
17
|
+
Boards that are physically connected, but give no tangible response are ignored.
|
18
|
+
|
19
|
+
Returns:
|
20
|
+
A tuple containing three lists:
|
21
|
+
- A list of unique ports where MCUs are connected.
|
22
|
+
- A list of unique board names of the connected MCUs.
|
23
|
+
- A list of MPRemoteBoard instances of the connected MCUs.
|
24
|
+
"""
|
25
|
+
mpr_boards = [b for b in list_mcus(include=include, ignore=ignore) if b.connected]
|
26
|
+
ports = list({b.port for b in mpr_boards})
|
27
|
+
boards = list({b.board for b in mpr_boards})
|
28
|
+
return (ports, boards, mpr_boards)
|
29
|
+
|
30
|
+
|
31
|
+
# #########################################################################################################
|
32
|
+
rp_spinner = SpinnerColumn(finished_text="✅")
|
33
|
+
rp_text = TextColumn("{task.description} {task.fields[device]}", table_column=Column())
|
34
|
+
rp_bar = BarColumn(bar_width=None, table_column=Column())
|
35
|
+
|
36
|
+
|
37
|
+
def list_mcus(*, ignore: List[str], include: List[str], bluetooth: bool = False):
|
38
|
+
"""
|
39
|
+
Retrieves information about connected microcontroller boards.
|
40
|
+
|
41
|
+
Returns:
|
42
|
+
List[MPRemoteBoard]: A list of MPRemoteBoard instances with board information.
|
43
|
+
Raises:
|
44
|
+
ConnectionError: If there is an error connecting to a board.
|
45
|
+
"""
|
46
|
+
# conn_mcus = [MPRemoteBoard(sp) for sp in MPRemoteBoard.connected_boards(bluetooth) if sp not in config.ignore_ports]
|
47
|
+
|
48
|
+
comports = filtered_comports(
|
49
|
+
ignore=ignore,
|
50
|
+
include=include,
|
51
|
+
bluetooth=bluetooth,
|
52
|
+
)
|
53
|
+
conn_mcus = [MPRemoteBoard(c.device) for c in comports]
|
54
|
+
|
55
|
+
# a lot of boilerplate to show a progress bar with the comport currently scanned
|
56
|
+
# low update rate to facilitate screen readers/narration
|
57
|
+
with Progress(rp_spinner, rp_text, rp_bar, TimeElapsedColumn(), refresh_per_second=2) as progress:
|
58
|
+
tsk_scan = progress.add_task("[green]Scanning", visible=False, total=None)
|
59
|
+
progress.tasks[tsk_scan].fields["device"] = "..."
|
60
|
+
progress.tasks[tsk_scan].visible = True
|
61
|
+
progress.start_task(tsk_scan)
|
62
|
+
try:
|
63
|
+
for mcu in conn_mcus:
|
64
|
+
progress.update(tsk_scan, device=mcu.serialport.replace("/dev/", ""))
|
65
|
+
try:
|
66
|
+
mcu.get_mcu_info()
|
67
|
+
except ConnectionError as e:
|
68
|
+
print(f"Error: {e}")
|
69
|
+
continue
|
70
|
+
finally:
|
71
|
+
# transient
|
72
|
+
progress.stop_task(tsk_scan)
|
73
|
+
progress.tasks[tsk_scan].visible = False
|
74
|
+
return conn_mcus
|
mpflash/mpflash/download.py
CHANGED
@@ -18,8 +18,11 @@ 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.errors import MPFlashError
|
23
|
+
from mpflash.mpboard_id import get_known_ports
|
22
24
|
|
25
|
+
# avoid conflict with the ujson used by MicroPython
|
23
26
|
jsonlines.ujson = None # type: ignore
|
24
27
|
# #########################################################################################################
|
25
28
|
|
@@ -49,6 +52,10 @@ def get_board_urls(page_url: str) -> List[Dict[str, str]]:
|
|
49
52
|
|
50
53
|
Args:
|
51
54
|
page_url (str): The url of the page to get the board urls from.
|
55
|
+
|
56
|
+
Returns:
|
57
|
+
List[Dict[str, str]]: A list of dictionaries containing the board name and url.
|
58
|
+
|
52
59
|
"""
|
53
60
|
downloads_html = get_page(page_url)
|
54
61
|
soup = BeautifulSoup(downloads_html, "html.parser")
|
@@ -87,14 +94,10 @@ def board_firmware_urls(board_url: str, base_url: str, ext: str) -> List[str]:
|
|
87
94
|
return links
|
88
95
|
|
89
96
|
|
90
|
-
# type alias for the firmware info
|
91
|
-
FirmwareInfo = Dict[str, str]
|
92
|
-
|
93
|
-
|
94
97
|
# boards we are interested in ( this avoids getting a lot of boards we don't care about)
|
95
98
|
# The first run takes ~60 seconds to run for 4 ports , all boards
|
96
99
|
# so it makes sense to cache the results and skip boards as soon as possible
|
97
|
-
def get_boards(ports: List[str], boards: List[str], clean: bool) -> List[
|
100
|
+
def get_boards(ports: List[str], boards: List[str], clean: bool) -> List[FWInfo]:
|
98
101
|
"""
|
99
102
|
Retrieves a list of firmware information for the specified ports and boards.
|
100
103
|
|
@@ -104,57 +107,68 @@ def get_boards(ports: List[str], boards: List[str], clean: bool) -> List[Firmwar
|
|
104
107
|
clean (bool): A flag indicating whether to perform a clean retrieval.
|
105
108
|
|
106
109
|
Returns:
|
107
|
-
List[
|
110
|
+
List[FWInfo]: A list of firmware information for the specified ports and boards.
|
108
111
|
|
109
112
|
"""
|
110
|
-
board_urls: List[
|
113
|
+
board_urls: List[FWInfo] = []
|
114
|
+
if ports is None:
|
115
|
+
ports = get_known_ports()
|
111
116
|
for port in ports:
|
112
117
|
download_page_url = f"{MICROPYTHON_ORG_URL}download/?port={port}"
|
113
|
-
|
118
|
+
urls = get_board_urls(download_page_url)
|
114
119
|
# filter out boards we don't care about
|
115
|
-
|
120
|
+
urls = [board for board in urls if board["board"] in boards]
|
116
121
|
# add the port to the board urls
|
117
|
-
for board in
|
122
|
+
for board in urls:
|
118
123
|
board["port"] = port
|
119
124
|
|
120
|
-
for board in track(
|
125
|
+
for board in track(urls, description=f"Checking {port} download pages", transient=True, refresh_per_second=2):
|
121
126
|
# add a board to the list for each firmware found
|
122
|
-
|
127
|
+
firmware_urls: List[str] = []
|
123
128
|
for ext in PORT_FWTYPES[port]:
|
124
|
-
|
125
|
-
|
126
|
-
for _url in firmwares:
|
129
|
+
firmware_urls += board_firmware_urls(board["url"], MICROPYTHON_ORG_URL, ext)
|
130
|
+
for _url in firmware_urls:
|
127
131
|
board["firmware"] = _url
|
128
|
-
board["preview"] = "preview" in _url # type: ignore
|
129
|
-
if ver_match := re.search(RE_VERSION_PREVIEW, _url):
|
130
|
-
board["version"] = ver_match[1]
|
131
|
-
else:
|
132
|
-
board["version"] = ""
|
133
|
-
if "preview." in board["version"]:
|
134
|
-
board["build"] = board["version"].split("preview.")[-1]
|
135
|
-
else:
|
136
|
-
board["build"] = "0"
|
137
132
|
fname = Path(board["firmware"]).name
|
138
133
|
if clean:
|
139
134
|
# remove date from firmware name
|
140
135
|
fname = re.sub(RE_DATE, "-", fname)
|
141
136
|
# remove hash from firmware name
|
142
137
|
fname = re.sub(RE_HASH, ".", fname)
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
138
|
+
fw_info = FWInfo(
|
139
|
+
filename=fname,
|
140
|
+
port=port,
|
141
|
+
board=board["board"],
|
142
|
+
preview="preview" in _url,
|
143
|
+
firmware=_url,
|
144
|
+
version="",
|
145
|
+
)
|
146
|
+
# board["firmware"] = _url
|
147
|
+
# board["preview"] = "preview" in _url # type: ignore
|
148
|
+
if ver_match := re.search(RE_VERSION_PREVIEW, _url):
|
149
|
+
fw_info.version = ver_match[1]
|
150
|
+
# else:
|
151
|
+
# board.$1= ""
|
152
|
+
if "preview." in fw_info.version:
|
153
|
+
fw_info.build = fw_info.version.split("preview.")[-1]
|
154
|
+
else:
|
155
|
+
fw_info.build = "0"
|
156
|
+
|
157
|
+
fw_info.ext = Path(fw_info.firmware).suffix
|
158
|
+
fw_info.variant = fw_info.filename.split("-v")[0] if "-v" in fw_info.filename else ""
|
159
|
+
|
160
|
+
board_urls.append(fw_info)
|
147
161
|
return board_urls
|
148
162
|
|
149
163
|
|
150
|
-
def key_fw_ver_pre_ext_bld(x:
|
164
|
+
def key_fw_ver_pre_ext_bld(x: FWInfo):
|
151
165
|
"sorting key for the retrieved board urls"
|
152
|
-
return x
|
166
|
+
return x.variant, x.version, x.preview, x.ext, x.build
|
153
167
|
|
154
168
|
|
155
|
-
def key_fw_var_pre_ext(x:
|
169
|
+
def key_fw_var_pre_ext(x: FWInfo):
|
156
170
|
"Grouping key for the retrieved board urls"
|
157
|
-
return x
|
171
|
+
return x.variant, x.preview, x.ext
|
158
172
|
|
159
173
|
|
160
174
|
def download_firmwares(
|
@@ -165,14 +179,15 @@ def download_firmwares(
|
|
165
179
|
*,
|
166
180
|
force: bool = False,
|
167
181
|
clean: bool = True,
|
168
|
-
):
|
182
|
+
) -> int:
|
169
183
|
skipped = downloaded = 0
|
170
184
|
if versions is None:
|
171
185
|
versions = []
|
186
|
+
|
172
187
|
unique_boards = get_firmware_list(ports, boards, versions, clean)
|
173
188
|
|
174
189
|
for b in unique_boards:
|
175
|
-
log.debug(b
|
190
|
+
log.debug(b.filename)
|
176
191
|
# relevant
|
177
192
|
|
178
193
|
log.info(f"Found {len(unique_boards)} relevant unique firmwares")
|
@@ -181,25 +196,26 @@ def download_firmwares(
|
|
181
196
|
|
182
197
|
with jsonlines.open(firmware_folder / "firmware.jsonl", "a") as writer:
|
183
198
|
for board in unique_boards:
|
184
|
-
filename = firmware_folder / board
|
199
|
+
filename = firmware_folder / board.port / board.filename
|
185
200
|
filename.parent.mkdir(exist_ok=True)
|
186
201
|
if filename.exists() and not force:
|
187
202
|
skipped += 1
|
188
203
|
log.debug(f" {filename} already exists, skip download")
|
189
204
|
continue
|
190
|
-
log.info(f"Downloading {board
|
205
|
+
log.info(f"Downloading {board.firmware}")
|
191
206
|
log.info(f" to {filename}")
|
192
207
|
try:
|
193
|
-
r = requests.get(board
|
208
|
+
r = requests.get(board.firmware, allow_redirects=True)
|
194
209
|
with open(filename, "wb") as fw:
|
195
210
|
fw.write(r.content)
|
196
|
-
board
|
211
|
+
board.filename = str(filename.relative_to(firmware_folder))
|
197
212
|
except requests.RequestException as e:
|
198
213
|
log.exception(e)
|
199
214
|
continue
|
200
|
-
writer.write(board)
|
215
|
+
writer.write(board.to_dict())
|
201
216
|
downloaded += 1
|
202
217
|
log.info(f"Downloaded {downloaded} firmwares, skipped {skipped} existing files.")
|
218
|
+
return downloaded + skipped
|
203
219
|
|
204
220
|
|
205
221
|
def get_firmware_list(ports: List[str], boards: List[str], versions: List[str], clean: bool):
|
@@ -213,7 +229,7 @@ def get_firmware_list(ports: List[str], boards: List[str], versions: List[str],
|
|
213
229
|
clean : A flag indicating whether to perform a clean check.
|
214
230
|
|
215
231
|
Returns:
|
216
|
-
List[
|
232
|
+
List[FWInfo]: A list of unique firmware information.
|
217
233
|
|
218
234
|
"""
|
219
235
|
|
@@ -225,12 +241,12 @@ def get_firmware_list(ports: List[str], boards: List[str], versions: List[str],
|
|
225
241
|
relevant = [
|
226
242
|
board
|
227
243
|
for board in board_urls
|
228
|
-
if board
|
244
|
+
if board.board in boards and (board.version in versions or board.preview and preview)
|
229
245
|
# and b["port"] in ["esp32", "rp2"]
|
230
246
|
]
|
231
247
|
log.debug(f"Matching firmwares: {len(relevant)}")
|
232
248
|
# select the unique boards
|
233
|
-
unique_boards: List[
|
249
|
+
unique_boards: List[FWInfo] = []
|
234
250
|
for _, g in itertools.groupby(relevant, key=key_fw_var_pre_ext):
|
235
251
|
# list is aleady sorted by build so we can just get the last item
|
236
252
|
sub_list = list(g)
|
@@ -246,7 +262,7 @@ def download(
|
|
246
262
|
versions: List[str],
|
247
263
|
force: bool,
|
248
264
|
clean: bool,
|
249
|
-
):
|
265
|
+
) -> int:
|
250
266
|
"""
|
251
267
|
Downloads firmware files based on the specified destination, ports, boards, versions, force flag, and clean flag.
|
252
268
|
|
@@ -259,19 +275,20 @@ def download(
|
|
259
275
|
clean : A flag indicating whether to perform a clean download.
|
260
276
|
|
261
277
|
Returns:
|
262
|
-
|
278
|
+
int: The number of downloaded firmware files.
|
263
279
|
|
264
280
|
Raises:
|
265
|
-
|
281
|
+
MPFlashError : If no boards are found or specified.
|
266
282
|
|
267
283
|
"""
|
268
284
|
if not boards:
|
269
285
|
log.critical("No boards found, please connect a board or specify boards to download firmware for.")
|
270
|
-
|
286
|
+
raise MPFlashError("No boards found")
|
271
287
|
# versions = [clean_version(v, drop_v=True) for v in versions] # remove leading v from version
|
272
288
|
try:
|
273
289
|
destination.mkdir(exist_ok=True, parents=True)
|
274
290
|
except (PermissionError, FileNotFoundError) as e:
|
275
|
-
log.critical(f"Could not create folder {destination}
|
276
|
-
|
277
|
-
|
291
|
+
log.critical(f"Could not create folder {destination}")
|
292
|
+
raise MPFlashError(f"Could not create folder {destination}") from e
|
293
|
+
|
294
|
+
return download_firmwares(destination, ports, boards, versions, force=force, clean=clean)
|