mpflash 0.4.0.post3__tar.gz → 0.4.2__tar.gz
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.
- {mpflash-0.4.0.post3 → mpflash-0.4.2}/PKG-INFO +1 -1
- mpflash-0.4.2/mpflash/ask_input.py +163 -0
- {mpflash-0.4.0.post3 → mpflash-0.4.2}/mpflash/cli_download.py +16 -12
- mpflash-0.4.2/mpflash/cli_flash.py +225 -0
- {mpflash-0.4.0.post3 → mpflash-0.4.2}/mpflash/cli_group.py +20 -1
- {mpflash-0.4.0.post3 → mpflash-0.4.2}/mpflash/cli_list.py +5 -4
- {mpflash-0.4.0.post3 → mpflash-0.4.2}/mpflash/cli_main.py +5 -4
- {mpflash-0.4.0.post3 → mpflash-0.4.2}/mpflash/common.py +24 -9
- {mpflash-0.4.0.post3 → mpflash-0.4.2}/mpflash/config.py +5 -3
- {mpflash-0.4.0.post3 → mpflash-0.4.2}/mpflash/download.py +58 -13
- {mpflash-0.4.0.post3 → mpflash-0.4.2}/mpflash/flash.py +36 -24
- {mpflash-0.4.0.post3 → mpflash-0.4.2}/mpflash/flash_esp.py +11 -11
- {mpflash-0.4.0.post3 → mpflash-0.4.2}/mpflash/flash_stm32.py +1 -1
- {mpflash-0.4.0.post3 → mpflash-0.4.2}/mpflash/flash_stm32_dfu.py +4 -7
- {mpflash-0.4.0.post3 → mpflash-0.4.2}/mpflash/flash_uf2.py +1 -1
- mpflash-0.4.2/mpflash/mpboard_id/api.py +89 -0
- mpflash-0.4.2/mpflash/mpboard_id/board_info.csv +2213 -0
- mpflash-0.4.2/mpflash/mpboard_id/board_info.json +19910 -0
- {mpflash-0.4.0.post3 → mpflash-0.4.2}/mpflash/vendored/dfu.py +9 -8
- {mpflash-0.4.0.post3 → mpflash-0.4.2}/mpflash/vendored/pydfu.py +1 -1
- {mpflash-0.4.0.post3 → mpflash-0.4.2}/pyproject.toml +6 -1
- mpflash-0.4.0.post3/mpflash/basic.py +0 -41
- mpflash-0.4.0.post3/mpflash/cli_flash.py +0 -175
- mpflash-0.4.0.post3/mpflash/download_input.py +0 -69
- mpflash-0.4.0.post3/mpflash/inq.py +0 -66
- mpflash-0.4.0.post3/mpflash/mpboard_id/api.py +0 -48
- mpflash-0.4.0.post3/mpflash/mpboard_id/board_info.csv +0 -193
- mpflash-0.4.0.post3/mpflash/mpboard_id/board_info.json +0 -1730
- mpflash-0.4.0.post3/mpflash/mpboard_id/get_boardnames.py +0 -213
- {mpflash-0.4.0.post3 → mpflash-0.4.2}/LICENSE +0 -0
- {mpflash-0.4.0.post3 → mpflash-0.4.2}/README.md +0 -0
- {mpflash-0.4.0.post3 → mpflash-0.4.2}/mpflash/__init__.py +0 -0
- {mpflash-0.4.0.post3 → mpflash-0.4.2}/mpflash/flash_stm32_cube.py +0 -0
- {mpflash-0.4.0.post3 → mpflash-0.4.2}/mpflash/flash_uf2_boardid.py +0 -0
- {mpflash-0.4.0.post3 → mpflash-0.4.2}/mpflash/flash_uf2_linux.py +0 -0
- {mpflash-0.4.0.post3 → mpflash-0.4.2}/mpflash/flash_uf2_windows.py +0 -0
- {mpflash-0.4.0.post3 → mpflash-0.4.2}/mpflash/logger.py +0 -0
- {mpflash-0.4.0.post3 → mpflash-0.4.2}/mpflash/mpboard_id/board_id.py +0 -0
- {mpflash-0.4.0.post3 → mpflash-0.4.2}/mpflash/mpremoteboard/__init__.py +0 -0
- {mpflash-0.4.0.post3 → mpflash-0.4.2}/mpflash/mpremoteboard/mpy_fw_info.py +0 -0
- {mpflash-0.4.0.post3 → mpflash-0.4.2}/mpflash/mpremoteboard/runner.py +0 -0
- {mpflash-0.4.0.post3 → mpflash-0.4.2}/mpflash/vendored/readme.md +0 -0
@@ -0,0 +1,163 @@
|
|
1
|
+
"""Download input handling for mpflash."""
|
2
|
+
|
3
|
+
from dataclasses import dataclass, field
|
4
|
+
from pathlib import Path
|
5
|
+
from typing import List, Sequence, Tuple, Union
|
6
|
+
|
7
|
+
from loguru import logger as log
|
8
|
+
|
9
|
+
from mpflash.common import micropython_versions
|
10
|
+
from mpflash.config import config
|
11
|
+
from mpflash.mpboard_id.api import known_mp_boards, known_mp_ports
|
12
|
+
from mpflash.mpremoteboard import MPRemoteBoard
|
13
|
+
|
14
|
+
|
15
|
+
@dataclass
|
16
|
+
class Params:
|
17
|
+
ports: List[str] = field(default_factory=list)
|
18
|
+
boards: List[str] = field(default_factory=list)
|
19
|
+
versions: List[str] = field(default_factory=list)
|
20
|
+
fw_folder: Path = Path()
|
21
|
+
|
22
|
+
|
23
|
+
@dataclass
|
24
|
+
class DownloadParams(Params):
|
25
|
+
clean: bool = False
|
26
|
+
force: bool = False
|
27
|
+
|
28
|
+
|
29
|
+
@dataclass
|
30
|
+
class FlashParams(Params):
|
31
|
+
# TODO: Should Serial port be a list?
|
32
|
+
serial: str = ""
|
33
|
+
erase: bool = True
|
34
|
+
bootloader: bool = True
|
35
|
+
cpu: str = ""
|
36
|
+
|
37
|
+
|
38
|
+
ParamType = Union[DownloadParams, FlashParams]
|
39
|
+
|
40
|
+
|
41
|
+
def ask_missing_params(
|
42
|
+
params: ParamType,
|
43
|
+
action: str = "download",
|
44
|
+
) -> ParamType:
|
45
|
+
if not config.interactive:
|
46
|
+
# no interactivity allowed
|
47
|
+
return params
|
48
|
+
# import only when needed to reduce load time
|
49
|
+
import inquirer
|
50
|
+
|
51
|
+
questions = []
|
52
|
+
if isinstance(params, FlashParams) and (not params.serial or "?" in params.versions):
|
53
|
+
ask_serialport(questions, action=action)
|
54
|
+
|
55
|
+
if not params.versions or "?" in params.versions:
|
56
|
+
ask_versions(questions, action=action)
|
57
|
+
|
58
|
+
if not params.boards or "?" in params.boards:
|
59
|
+
ask_port_board(questions, action=action)
|
60
|
+
|
61
|
+
answers = inquirer.prompt(questions)
|
62
|
+
if not answers:
|
63
|
+
return params
|
64
|
+
# print(repr(answers))
|
65
|
+
if isinstance(params, FlashParams) and "serial" in answers:
|
66
|
+
params.serial = answers["serial"]
|
67
|
+
if "port" in answers:
|
68
|
+
params.ports = [answers["port"]]
|
69
|
+
if "boards" in answers:
|
70
|
+
params.boards = answers["boards"]
|
71
|
+
if "versions" in answers:
|
72
|
+
# make sure it is a list
|
73
|
+
params.versions = answers["versions"] if isinstance(answers["versions"], list) else [answers["versions"]]
|
74
|
+
|
75
|
+
log.debug(repr(params))
|
76
|
+
|
77
|
+
return params
|
78
|
+
|
79
|
+
|
80
|
+
def some_boards(answers: dict) -> Sequence[Tuple[str, str]]:
|
81
|
+
if "versions" in answers:
|
82
|
+
_versions = list(answers["versions"])
|
83
|
+
if "stable" in _versions:
|
84
|
+
_versions.remove("stable")
|
85
|
+
_versions.append(micropython_versions()[-2])
|
86
|
+
if "preview" in _versions:
|
87
|
+
_versions.remove("preview")
|
88
|
+
_versions.append(micropython_versions()[-1])
|
89
|
+
_versions.append(micropython_versions()[-2])
|
90
|
+
|
91
|
+
some_boards = known_mp_boards(answers["port"], _versions) # or known_mp_boards(answers["port"])
|
92
|
+
else:
|
93
|
+
some_boards = known_mp_boards(answers["port"])
|
94
|
+
|
95
|
+
if some_boards:
|
96
|
+
# Create a dictionary where the keys are the second elements of the tuples
|
97
|
+
# This will automatically remove duplicates because dictionaries cannot have duplicate keys
|
98
|
+
unique_dict = {item[1]: item for item in some_boards}
|
99
|
+
# Get the values of the dictionary, which are the unique items from the original list
|
100
|
+
some_boards = list(unique_dict.values())
|
101
|
+
else:
|
102
|
+
some_boards = [("No boards found", "")]
|
103
|
+
return some_boards
|
104
|
+
|
105
|
+
|
106
|
+
def ask_port_board(questions: list, *, action: str):
|
107
|
+
# import only when needed to reduce load time
|
108
|
+
import inquirer
|
109
|
+
|
110
|
+
questions.extend(
|
111
|
+
(
|
112
|
+
inquirer.List(
|
113
|
+
"port",
|
114
|
+
message=f"What port do you want to {action}?",
|
115
|
+
choices=known_mp_ports(),
|
116
|
+
autocomplete=True,
|
117
|
+
),
|
118
|
+
inquirer.Checkbox(
|
119
|
+
"boards",
|
120
|
+
message=f"What board do you want to {action}?",
|
121
|
+
choices=some_boards,
|
122
|
+
validate=lambda _, x: True if x else "Please select at least one board", # type: ignore
|
123
|
+
),
|
124
|
+
)
|
125
|
+
)
|
126
|
+
|
127
|
+
|
128
|
+
def ask_versions(questions: list, *, action: str):
|
129
|
+
# import only when needed to reduce load time
|
130
|
+
import inquirer
|
131
|
+
|
132
|
+
input_ux = inquirer.Checkbox if action == "download" else inquirer.List
|
133
|
+
mp_versions: List[str] = micropython_versions()
|
134
|
+
mp_versions = [v for v in mp_versions if "preview" not in v]
|
135
|
+
mp_versions.append("preview")
|
136
|
+
mp_versions.reverse() # newest first
|
137
|
+
questions.append(
|
138
|
+
input_ux(
|
139
|
+
"versions",
|
140
|
+
message=f"What version(s) do you want to {action}?",
|
141
|
+
choices=mp_versions,
|
142
|
+
autocomplete=True,
|
143
|
+
validate=lambda _, x: True if x else "Please select at least one version", # type: ignore
|
144
|
+
)
|
145
|
+
)
|
146
|
+
|
147
|
+
|
148
|
+
def ask_serialport(questions: list, *, action: str):
|
149
|
+
# import only when needed to reduce load time
|
150
|
+
import inquirer
|
151
|
+
|
152
|
+
serialports = MPRemoteBoard.connected_boards()
|
153
|
+
questions.append(
|
154
|
+
inquirer.List(
|
155
|
+
"serial",
|
156
|
+
message="What serial port do you want use ?",
|
157
|
+
validate=lambda _, x: True if x else "Please enter a serial port", # type: ignore
|
158
|
+
choices=serialports,
|
159
|
+
other=True,
|
160
|
+
)
|
161
|
+
)
|
162
|
+
|
163
|
+
return questions
|
@@ -3,14 +3,15 @@
|
|
3
3
|
from pathlib import Path
|
4
4
|
from typing import List, Tuple
|
5
5
|
|
6
|
-
from mpflash.common import clean_version
|
7
6
|
import rich_click as click
|
8
7
|
|
8
|
+
from mpflash.common import clean_version
|
9
|
+
|
10
|
+
from .ask_input import DownloadParams, ask_missing_params
|
9
11
|
from .cli_group import cli
|
12
|
+
from .cli_list import list_mcus
|
10
13
|
from .config import config
|
11
14
|
from .download import download
|
12
|
-
from .download_input import DownloadParams, ask_missing_params
|
13
|
-
from .cli_list import list_mcus
|
14
15
|
|
15
16
|
|
16
17
|
def connected_ports_boards() -> Tuple[List[str], List[str]]:
|
@@ -27,6 +28,7 @@ def connected_ports_boards() -> Tuple[List[str], List[str]]:
|
|
27
28
|
@click.option(
|
28
29
|
"--destination",
|
29
30
|
"-d",
|
31
|
+
"fw_folder",
|
30
32
|
type=click.Path(file_okay=False, dir_okay=True, path_type=Path),
|
31
33
|
default=config.firmware_folder,
|
32
34
|
show_default=True,
|
@@ -36,18 +38,21 @@ def connected_ports_boards() -> Tuple[List[str], List[str]]:
|
|
36
38
|
"--version",
|
37
39
|
"-v",
|
38
40
|
"versions",
|
41
|
+
default=["stable"],
|
39
42
|
multiple=True,
|
40
|
-
help="The version of MicroPython to to download. Use 'preview' to include preview versions.",
|
41
43
|
show_default=True,
|
42
|
-
|
44
|
+
help="The version of MicroPython to to download.",
|
45
|
+
metavar="SEMVER, 'stable', 'preview' or '?'",
|
43
46
|
)
|
44
47
|
@click.option(
|
45
48
|
"--board",
|
46
49
|
"-b",
|
47
50
|
"boards",
|
48
51
|
multiple=True,
|
52
|
+
default=["?"],
|
49
53
|
show_default=True,
|
50
|
-
help="The board(s) to download the firmware for.",
|
54
|
+
help="The board(s) to download the firmware for.",
|
55
|
+
metavar="BOARD_ID or ?",
|
51
56
|
)
|
52
57
|
@click.option(
|
53
58
|
"--clean/--no-clean",
|
@@ -59,8 +64,8 @@ def connected_ports_boards() -> Tuple[List[str], List[str]]:
|
|
59
64
|
"--force",
|
60
65
|
default=False,
|
61
66
|
is_flag=True,
|
62
|
-
help="""Force download of firmware even if it already exists.""",
|
63
67
|
show_default=True,
|
68
|
+
help="""Force download of firmware even if it already exists.""",
|
64
69
|
)
|
65
70
|
def cli_download(
|
66
71
|
**kwargs,
|
@@ -70,18 +75,17 @@ def cli_download(
|
|
70
75
|
if not params.boards:
|
71
76
|
# nothing specified - detect connected boards
|
72
77
|
params.ports, params.boards = connected_ports_boards()
|
78
|
+
# ask for any remaining parameters
|
79
|
+
params = ask_missing_params(params, action="download")
|
80
|
+
assert isinstance(params, DownloadParams)
|
73
81
|
|
74
82
|
params.versions = [clean_version(v, drop_v=True) for v in params.versions] # remove leading v from version
|
75
83
|
|
76
|
-
# ask for any remaining parameters
|
77
|
-
params = ask_missing_params(params)
|
78
|
-
# preview is not a version, it is an option to include preview versions
|
79
84
|
download(
|
80
|
-
params.
|
85
|
+
params.fw_folder,
|
81
86
|
params.ports,
|
82
87
|
params.boards,
|
83
88
|
params.versions,
|
84
89
|
params.force,
|
85
90
|
params.clean,
|
86
|
-
params.preview,
|
87
91
|
)
|
@@ -0,0 +1,225 @@
|
|
1
|
+
from pathlib import Path
|
2
|
+
|
3
|
+
import rich_click as click
|
4
|
+
from loguru import logger as log
|
5
|
+
|
6
|
+
from mpflash.mpboard_id.api import find_mp_board
|
7
|
+
|
8
|
+
from .ask_input import FlashParams, ask_missing_params
|
9
|
+
from .cli_download import connected_ports_boards
|
10
|
+
from .cli_group import cli
|
11
|
+
from .cli_list import show_mcus
|
12
|
+
from .common import clean_version
|
13
|
+
from .config import config
|
14
|
+
from .flash import WorkList, auto_update, enter_bootloader, find_firmware
|
15
|
+
from .flash_esp import flash_esp
|
16
|
+
from .flash_stm32 import flash_stm32
|
17
|
+
from .flash_uf2 import flash_uf2
|
18
|
+
from .mpremoteboard import MPRemoteBoard
|
19
|
+
|
20
|
+
# #########################################################################################################
|
21
|
+
# CLI
|
22
|
+
# #########################################################################################################
|
23
|
+
|
24
|
+
|
25
|
+
@cli.command(
|
26
|
+
"flash",
|
27
|
+
short_help="Flash one or all connected MicroPython boards with a specific firmware and version.",
|
28
|
+
)
|
29
|
+
@click.option(
|
30
|
+
"--firmware",
|
31
|
+
"-f",
|
32
|
+
"fw_folder",
|
33
|
+
type=click.Path(exists=True, file_okay=False, dir_okay=True, path_type=Path),
|
34
|
+
default=config.firmware_folder,
|
35
|
+
show_default=True,
|
36
|
+
help="The folder to retrieve the firmware from.",
|
37
|
+
)
|
38
|
+
@click.option(
|
39
|
+
"--version",
|
40
|
+
"-v",
|
41
|
+
"version", # single version
|
42
|
+
default="stable",
|
43
|
+
multiple=False,
|
44
|
+
show_default=True,
|
45
|
+
help="The version of MicroPython to flash.",
|
46
|
+
metavar="SEMVER, 'stable', 'preview' or '?'",
|
47
|
+
)
|
48
|
+
@click.option(
|
49
|
+
"--serial",
|
50
|
+
"--serial-port",
|
51
|
+
"-s",
|
52
|
+
"serial",
|
53
|
+
default="auto",
|
54
|
+
show_default=True,
|
55
|
+
help="Which serial port(s) to flash",
|
56
|
+
metavar="SERIAL_PORT",
|
57
|
+
)
|
58
|
+
@click.option(
|
59
|
+
"--port",
|
60
|
+
"-p",
|
61
|
+
"ports",
|
62
|
+
help="The MicroPython port to flash",
|
63
|
+
metavar="PORT",
|
64
|
+
default=[],
|
65
|
+
multiple=True,
|
66
|
+
)
|
67
|
+
@click.option(
|
68
|
+
"--board",
|
69
|
+
"-b",
|
70
|
+
"boards",
|
71
|
+
multiple=False,
|
72
|
+
default=[],
|
73
|
+
help="The MicroPython board ID to flash. If not specified will try to read the BOARD_ID from the connected MCU.",
|
74
|
+
metavar="BOARD_ID or ?",
|
75
|
+
)
|
76
|
+
@click.option(
|
77
|
+
"--cpu",
|
78
|
+
"--chip",
|
79
|
+
"-c",
|
80
|
+
"cpu",
|
81
|
+
help="The CPU type to flash. If not specified will try to read the CPU from the connected MCU.",
|
82
|
+
metavar="CPU",
|
83
|
+
)
|
84
|
+
@click.option(
|
85
|
+
"--erase/--no-erase",
|
86
|
+
default=True,
|
87
|
+
show_default=True,
|
88
|
+
help="""Erase flash before writing new firmware. (not on UF2 boards)""",
|
89
|
+
)
|
90
|
+
@click.option(
|
91
|
+
"--bootloader/--no-bootloader",
|
92
|
+
default=True,
|
93
|
+
is_flag=True,
|
94
|
+
show_default=True,
|
95
|
+
help="""Enter micropython bootloader mode before flashing.""",
|
96
|
+
)
|
97
|
+
def cli_flash_board(**kwargs):
|
98
|
+
todo: WorkList = []
|
99
|
+
|
100
|
+
# version to versions
|
101
|
+
if "version" in kwargs:
|
102
|
+
kwargs["versions"] = [kwargs.pop("version")]
|
103
|
+
params = FlashParams(**kwargs)
|
104
|
+
print(f"{params=}")
|
105
|
+
# print(f"{params.version=}")
|
106
|
+
print(f"{params.versions=}")
|
107
|
+
if not params.boards:
|
108
|
+
# nothing specified - detect connected boards
|
109
|
+
params.ports, params.boards = connected_ports_boards()
|
110
|
+
# Ask for missing input if needed
|
111
|
+
params = ask_missing_params(params, action="flash")
|
112
|
+
# TODO: Just in time Download of firmware
|
113
|
+
|
114
|
+
assert isinstance(params, FlashParams)
|
115
|
+
|
116
|
+
if len(params.versions) > 1:
|
117
|
+
print(repr(params.versions))
|
118
|
+
log.error(f"Only one version can be flashed at a time, not {params.versions}")
|
119
|
+
return
|
120
|
+
params.versions = [clean_version(v) for v in params.versions]
|
121
|
+
if params.versions[0] and params.boards[0] and params.serial:
|
122
|
+
# update a single board
|
123
|
+
todo = manual_worklist(
|
124
|
+
params.versions[0],
|
125
|
+
params.fw_folder,
|
126
|
+
params.serial,
|
127
|
+
params.boards[0],
|
128
|
+
# params.ports[0],
|
129
|
+
)
|
130
|
+
elif params.serial:
|
131
|
+
if params.serial == "auto":
|
132
|
+
# Update all micropython boards to the latest version
|
133
|
+
todo = auto_worklist(params.versions[0], params.fw_folder)
|
134
|
+
else:
|
135
|
+
# just this serial port on auto
|
136
|
+
todo = oneport_worklist(
|
137
|
+
params.versions[0],
|
138
|
+
params.fw_folder,
|
139
|
+
params.serial,
|
140
|
+
)
|
141
|
+
|
142
|
+
if flashed := flash_list(
|
143
|
+
todo,
|
144
|
+
params.fw_folder,
|
145
|
+
params.erase,
|
146
|
+
params.bootloader,
|
147
|
+
):
|
148
|
+
log.info(f"Flashed {len(flashed)} boards")
|
149
|
+
show_mcus(flashed, title="Connected boards after flashing")
|
150
|
+
|
151
|
+
|
152
|
+
def oneport_worklist(
|
153
|
+
version: str,
|
154
|
+
fw_folder: Path,
|
155
|
+
serial_port: str,
|
156
|
+
# preview: bool,
|
157
|
+
) -> WorkList:
|
158
|
+
"""Create a worklist for a single serial-port."""
|
159
|
+
conn_boards = [MPRemoteBoard(serial_port)]
|
160
|
+
todo = auto_update(conn_boards, version, fw_folder)
|
161
|
+
show_mcus(conn_boards)
|
162
|
+
return todo
|
163
|
+
|
164
|
+
|
165
|
+
def auto_worklist(version: str, fw_folder: Path) -> WorkList:
|
166
|
+
conn_boards = [MPRemoteBoard(sp) for sp in MPRemoteBoard.connected_boards() if sp not in config.ignore_ports]
|
167
|
+
return auto_update(conn_boards, version, fw_folder)
|
168
|
+
|
169
|
+
|
170
|
+
def manual_worklist(
|
171
|
+
version: str,
|
172
|
+
fw_folder: Path,
|
173
|
+
serial_port: str,
|
174
|
+
board: str,
|
175
|
+
# port: str,
|
176
|
+
) -> WorkList:
|
177
|
+
mcu = MPRemoteBoard(serial_port)
|
178
|
+
# TODO : Find a way to avoid needing to specify the port
|
179
|
+
# Lookup the matching port and cpu in board_info based in the board name
|
180
|
+
port = find_mp_board(board)["port"]
|
181
|
+
mcu.port = port
|
182
|
+
mcu.cpu = port if port.startswith("esp") else ""
|
183
|
+
mcu.board = board
|
184
|
+
firmwares = find_firmware(fw_folder=fw_folder, board=board, version=version, port=port)
|
185
|
+
if not firmwares:
|
186
|
+
log.error(f"No firmware found for {port} {board} version {version}")
|
187
|
+
return []
|
188
|
+
# use the most recent matching firmware
|
189
|
+
return [(mcu, firmwares[-1])]
|
190
|
+
|
191
|
+
|
192
|
+
def flash_list(
|
193
|
+
todo: WorkList,
|
194
|
+
fw_folder: Path,
|
195
|
+
erase: bool,
|
196
|
+
bootloader: bool,
|
197
|
+
):
|
198
|
+
"""Flash a list of boards with the specified firmware."""
|
199
|
+
flashed = []
|
200
|
+
for mcu, fw_info in todo:
|
201
|
+
fw_file = fw_folder / fw_info["filename"] # type: ignore
|
202
|
+
if not fw_file.exists():
|
203
|
+
log.error(f"File {fw_file} does not exist, skipping {mcu.board} on {mcu.serialport}")
|
204
|
+
continue
|
205
|
+
log.info(f"Updating {mcu.board} on {mcu.serialport} to {fw_info['version']}")
|
206
|
+
updated = None
|
207
|
+
# try:
|
208
|
+
if mcu.port in ["samd", "rp2", "nrf"]: # [k for k, v in PORT_FWTYPES.items() if v == ".uf2"]:
|
209
|
+
if bootloader:
|
210
|
+
enter_bootloader(mcu)
|
211
|
+
updated = flash_uf2(mcu, fw_file=fw_file, erase=erase)
|
212
|
+
elif mcu.port in ["stm32"]:
|
213
|
+
if bootloader:
|
214
|
+
enter_bootloader(mcu)
|
215
|
+
updated = flash_stm32(mcu, fw_file, erase=erase)
|
216
|
+
elif mcu.port in ["esp32", "esp8266"]:
|
217
|
+
# bootloader is handled by esptool for esp32/esp8266
|
218
|
+
updated = flash_esp(mcu, fw_file=fw_file, erase=erase)
|
219
|
+
else:
|
220
|
+
log.error(f"Don't (yet) know how to flash {mcu.port}-{mcu.board} on {mcu.serialport}")
|
221
|
+
|
222
|
+
if updated:
|
223
|
+
flashed.append(updated)
|
224
|
+
else:
|
225
|
+
log.error(f"Failed to flash {mcu.board} on {mcu.serialport}")
|
@@ -13,8 +13,10 @@ def cb_verbose(ctx, param, value):
|
|
13
13
|
"""Callback to set the log level to DEBUG if verbose is set"""
|
14
14
|
if value:
|
15
15
|
set_loglevel("DEBUG")
|
16
|
+
config.verbose = True
|
16
17
|
else:
|
17
18
|
set_loglevel("INFO")
|
19
|
+
config.verbose = False
|
18
20
|
return value
|
19
21
|
|
20
22
|
|
@@ -24,6 +26,12 @@ def cb_ignore(ctx, param, value):
|
|
24
26
|
return value
|
25
27
|
|
26
28
|
|
29
|
+
def cb_interactive(ctx, param, value):
|
30
|
+
if value:
|
31
|
+
config.interactive = value
|
32
|
+
return value
|
33
|
+
|
34
|
+
|
27
35
|
def cb_quiet(ctx, param, value):
|
28
36
|
if value:
|
29
37
|
make_quiet()
|
@@ -31,6 +39,7 @@ def cb_quiet(ctx, param, value):
|
|
31
39
|
|
32
40
|
|
33
41
|
@click.group()
|
42
|
+
@click.version_option(package_name="mpflash")
|
34
43
|
@click.option(
|
35
44
|
"--quiet",
|
36
45
|
"-q",
|
@@ -41,6 +50,16 @@ def cb_quiet(ctx, param, value):
|
|
41
50
|
envvar="MPFLASH_QUIET",
|
42
51
|
show_default=True,
|
43
52
|
)
|
53
|
+
@click.option(
|
54
|
+
"--interactive/--no-interactive",
|
55
|
+
"-i/-x",
|
56
|
+
is_eager=True,
|
57
|
+
help="Suppresses all request for Input.",
|
58
|
+
callback=cb_interactive,
|
59
|
+
# envvar="MPFLASH_QUIET",
|
60
|
+
default=True,
|
61
|
+
show_default=True,
|
62
|
+
)
|
44
63
|
@click.option(
|
45
64
|
"-V",
|
46
65
|
"--verbose",
|
@@ -61,7 +80,7 @@ def cb_quiet(ctx, param, value):
|
|
61
80
|
show_default=True,
|
62
81
|
metavar="SERIALPORT",
|
63
82
|
)
|
64
|
-
def cli(
|
83
|
+
def cli(**kwargs):
|
65
84
|
"""mpflash - MicroPython Tool.
|
66
85
|
|
67
86
|
A CLI to download and flash MicroPython firmware to different ports and boards.
|
@@ -6,10 +6,11 @@ from rich import print
|
|
6
6
|
from rich.progress import track
|
7
7
|
from rich.table import Table
|
8
8
|
|
9
|
+
from mpflash.mpremoteboard import MPRemoteBoard
|
10
|
+
|
9
11
|
from .cli_group import cli
|
10
12
|
from .config import config
|
11
13
|
from .logger import console, make_quiet
|
12
|
-
from .mpremoteboard import MPRemoteBoard
|
13
14
|
|
14
15
|
|
15
16
|
@cli.command("list", help="List the connected MCU boards.")
|
@@ -67,8 +68,8 @@ def show_mcus(
|
|
67
68
|
title=title,
|
68
69
|
header_style="bold blue",
|
69
70
|
collapse_padding=True,
|
70
|
-
width=
|
71
|
-
|
71
|
+
width=110,
|
72
|
+
row_styles=["blue", "yellow"],
|
72
73
|
)
|
73
74
|
table.add_column("Serial", overflow="fold")
|
74
75
|
table.add_column("Family")
|
@@ -89,7 +90,7 @@ def show_mcus(
|
|
89
90
|
mcu.serialport.replace("/dev/", ""),
|
90
91
|
mcu.family,
|
91
92
|
mcu.port,
|
92
|
-
mcu.board
|
93
|
+
f"{mcu.board}\n{mcu.description}".strip(),
|
93
94
|
# mcu.variant,
|
94
95
|
mcu.cpu,
|
95
96
|
mcu.version,
|
@@ -3,8 +3,8 @@
|
|
3
3
|
# import rich_click as click
|
4
4
|
|
5
5
|
from .cli_download import cli_download
|
6
|
-
from .cli_group import cli
|
7
6
|
from .cli_flash import cli_flash_board
|
7
|
+
from .cli_group import cli
|
8
8
|
from .cli_list import cli_list_mcus
|
9
9
|
|
10
10
|
# from loguru import logger as log
|
@@ -14,8 +14,9 @@ def mpflash():
|
|
14
14
|
cli.add_command(cli_flash_board)
|
15
15
|
cli.add_command(cli_list_mcus)
|
16
16
|
cli.add_command(cli_download)
|
17
|
-
cli(auto_envvar_prefix="MPFLASH")
|
17
|
+
# cli(auto_envvar_prefix="MPFLASH")
|
18
|
+
cli()
|
18
19
|
|
19
20
|
|
20
|
-
if __name__ == "__main__":
|
21
|
-
|
21
|
+
# if __name__ == "__main__":
|
22
|
+
mpflash()
|
@@ -1,7 +1,9 @@
|
|
1
|
+
import os
|
1
2
|
import time
|
3
|
+
from functools import lru_cache
|
2
4
|
from typing import TypedDict
|
3
5
|
|
4
|
-
from github import Github
|
6
|
+
from github import Auth, Github
|
5
7
|
from loguru import logger as log
|
6
8
|
from packaging.version import parse
|
7
9
|
from rich.progress import track
|
@@ -19,6 +21,14 @@ PORT_FWTYPES = {
|
|
19
21
|
"renesas-ra": [".hex"],
|
20
22
|
}
|
21
23
|
|
24
|
+
# Token with no permissions to avoid throttling
|
25
|
+
# 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
|
26
|
+
PAT_NO_ACCESS = (
|
27
|
+
"github_pat" + "_11AAHPVFQ0qAkDnSUaMKSp" + "_ZkDl5NRRwBsUN6EYg9ahp1Dvj4FDDONnXVgimxC2EtpY7Q7BUKBoQ0Jq72X"
|
28
|
+
)
|
29
|
+
PAT = os.environ.get("GITHUB_TOKEN") or PAT_NO_ACCESS
|
30
|
+
GH_CLIENT = Github(auth=Auth.Token(PAT))
|
31
|
+
|
22
32
|
|
23
33
|
class FWInfo(TypedDict):
|
24
34
|
filename: str
|
@@ -27,6 +37,7 @@ class FWInfo(TypedDict):
|
|
27
37
|
variant: str
|
28
38
|
preview: bool
|
29
39
|
version: str
|
40
|
+
build: str
|
30
41
|
|
31
42
|
|
32
43
|
#############################################################
|
@@ -52,7 +63,7 @@ def clean_version(
|
|
52
63
|
if version in {"", "-"}:
|
53
64
|
return version
|
54
65
|
if version.lower() == "stable":
|
55
|
-
_v =
|
66
|
+
_v = get_stable_mp_version()
|
56
67
|
if not _v:
|
57
68
|
log.warning("Could not determine the latest stable version")
|
58
69
|
return "stable"
|
@@ -89,12 +100,12 @@ def clean_version(
|
|
89
100
|
return version
|
90
101
|
|
91
102
|
|
92
|
-
|
103
|
+
@lru_cache(maxsize=10)
|
104
|
+
def micropython_versions(minver: str = "v1.20"):
|
93
105
|
"""Get the list of micropython versions from github tags"""
|
94
106
|
try:
|
95
|
-
|
96
|
-
|
97
|
-
repo = g.get_repo("micropython/micropython")
|
107
|
+
gh_client = GH_CLIENT
|
108
|
+
repo = gh_client.get_repo("micropython/micropython")
|
98
109
|
versions = [tag.name for tag in repo.get_tags() if parse(tag.name) >= parse(minver)]
|
99
110
|
except Exception:
|
100
111
|
versions = [
|
@@ -116,19 +127,23 @@ def micropython_versions(minver: str = "v1.9.2"):
|
|
116
127
|
"v1.12",
|
117
128
|
"v1.11",
|
118
129
|
"v1.10",
|
119
|
-
"v1.9.4",
|
120
|
-
"v1.9.3",
|
121
130
|
]
|
122
131
|
versions = [v for v in versions if parse(v) >= parse(minver)]
|
123
132
|
return sorted(versions)
|
124
133
|
|
125
134
|
|
126
|
-
def
|
135
|
+
def get_stable_mp_version() -> str:
|
127
136
|
# read the versions from the git tags
|
128
137
|
all_versions = micropython_versions(minver="v1.17")
|
129
138
|
return [v for v in all_versions if not v.endswith(V_PREVIEW)][-1]
|
130
139
|
|
131
140
|
|
141
|
+
def get_preview_mp_version() -> str:
|
142
|
+
# read the versions from the git tags
|
143
|
+
all_versions = micropython_versions(minver="v1.17")
|
144
|
+
return [v for v in all_versions if v.endswith(V_PREVIEW)][-1]
|
145
|
+
|
146
|
+
|
132
147
|
#############################################################
|
133
148
|
def wait_for_restart(mcu: MPRemoteBoard, timeout: int = 10):
|
134
149
|
"""wait for the board to restart"""
|
@@ -1,16 +1,18 @@
|
|
1
1
|
"""centralized configuration for mpflash"""
|
2
2
|
|
3
|
-
import platformdirs
|
4
|
-
|
5
|
-
from typing import List
|
6
3
|
from pathlib import Path
|
4
|
+
from typing import List
|
5
|
+
|
6
|
+
import platformdirs
|
7
7
|
|
8
8
|
|
9
9
|
class MPtoolConfig:
|
10
10
|
"""Centralized configuration for mpflash"""
|
11
11
|
|
12
12
|
quiet: bool = False
|
13
|
+
verbose: bool = False
|
13
14
|
ignore_ports: List[str] = []
|
15
|
+
interactive: bool = True
|
14
16
|
firmware_folder: Path = platformdirs.user_downloads_path() / "firmware"
|
15
17
|
|
16
18
|
|