mpflash 0.7.7__py3-none-any.whl → 0.8.1__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.
- mpflash/add_firmware.py +98 -0
- mpflash/ask_input.py +74 -111
- mpflash/cli_download.py +31 -21
- mpflash/cli_flash.py +64 -34
- mpflash/cli_group.py +5 -24
- mpflash/cli_list.py +39 -3
- mpflash/cli_main.py +16 -9
- mpflash/common.py +125 -12
- mpflash/connected.py +74 -0
- mpflash/download.py +82 -46
- mpflash/downloaded.py +9 -9
- mpflash/flash.py +2 -2
- mpflash/flash_esp.py +1 -1
- mpflash/flash_uf2.py +5 -5
- mpflash/flash_uf2_linux.py +1 -1
- mpflash/flash_uf2_macos.py +0 -2
- mpflash/list.py +4 -45
- mpflash/mpboard_id/__init__.py +28 -38
- mpflash/mpboard_id/add_boards.py +255 -0
- mpflash/mpboard_id/board.py +37 -0
- mpflash/mpboard_id/board_id.py +18 -23
- mpflash/mpboard_id/board_info.zip +0 -0
- mpflash/mpboard_id/store.py +42 -0
- mpflash/vendor/basicgit.py +288 -0
- mpflash/vendor/dfu.py +1 -0
- mpflash/vendor/versions.py +7 -3
- mpflash/worklist.py +70 -47
- {mpflash-0.7.7.dist-info → mpflash-0.8.1.dist-info}/METADATA +19 -7
- mpflash-0.8.1.dist-info/RECORD +47 -0
- mpflash/mpboard_id/Untitled-1.ipynb +0 -406
- mpflash/mpboard_id/board_info.csv +0 -2213
- mpflash/mpboard_id/board_info.json +0 -19910
- mpflash-0.7.7.dist-info/RECORD +0 -43
- {mpflash-0.7.7.dist-info → mpflash-0.8.1.dist-info}/LICENSE +0 -0
- {mpflash-0.7.7.dist-info → mpflash-0.8.1.dist-info}/WHEEL +0 -0
- {mpflash-0.7.7.dist-info → mpflash-0.8.1.dist-info}/entry_points.txt +0 -0
mpflash/add_firmware.py
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
import shutil
|
2
|
+
from pathlib import Path
|
3
|
+
from typing import Union
|
4
|
+
|
5
|
+
import jsonlines
|
6
|
+
import requests
|
7
|
+
from loguru import logger as log
|
8
|
+
|
9
|
+
# re-use logic from mpremote
|
10
|
+
from mpremote.mip import _rewrite_url as rewrite_url # type: ignore
|
11
|
+
|
12
|
+
from mpflash.common import FWInfo
|
13
|
+
from mpflash.config import config
|
14
|
+
from mpflash.vendor.versions import get_preview_mp_version, get_stable_mp_version
|
15
|
+
|
16
|
+
|
17
|
+
def add_firmware(
|
18
|
+
source: Union[Path, str],
|
19
|
+
new_fw: FWInfo,
|
20
|
+
*,
|
21
|
+
force: bool = False,
|
22
|
+
custom: bool = False,
|
23
|
+
description: str = "",
|
24
|
+
) -> bool:
|
25
|
+
"""Add a firmware to the firmware folder.
|
26
|
+
|
27
|
+
stored in the port folder, with the same filename as the source.
|
28
|
+
|
29
|
+
"""
|
30
|
+
# Check minimal info needed
|
31
|
+
if not new_fw.port or not new_fw.board:
|
32
|
+
log.error("Port and board are required")
|
33
|
+
return False
|
34
|
+
if not isinstance(source, Path) and not source.startswith("http"):
|
35
|
+
log.error(f"Invalid source {source}")
|
36
|
+
return False
|
37
|
+
|
38
|
+
# use sensible defaults
|
39
|
+
source_2 = Path(source)
|
40
|
+
new_fw.ext = new_fw.ext or source_2.suffix
|
41
|
+
new_fw.variant = new_fw.variant or new_fw.board
|
42
|
+
new_fw.custom = new_fw.custom or custom
|
43
|
+
new_fw.description = new_fw.description or description
|
44
|
+
if not new_fw.version:
|
45
|
+
# TODO: Get version from filename
|
46
|
+
# or use the last preview version
|
47
|
+
new_fw.version = get_preview_mp_version() if new_fw.preview else get_stable_mp_version()
|
48
|
+
|
49
|
+
config.firmware_folder.mkdir(exist_ok=True)
|
50
|
+
|
51
|
+
fw_filename = config.firmware_folder / new_fw.port / source_2.name
|
52
|
+
|
53
|
+
new_fw.filename = str(fw_filename.relative_to(config.firmware_folder))
|
54
|
+
new_fw.firmware = source.as_uri() if isinstance(source, Path) else source
|
55
|
+
|
56
|
+
if not copy_firmware(source, fw_filename, force):
|
57
|
+
log.error(f"Failed to copy {source} to {fw_filename}")
|
58
|
+
return False
|
59
|
+
# add to inventory
|
60
|
+
with jsonlines.open(config.firmware_folder / "firmware.jsonl", "a") as writer:
|
61
|
+
log.info(f"Adding {new_fw.port} {new_fw.board}")
|
62
|
+
log.info(f" to {fw_filename}")
|
63
|
+
|
64
|
+
writer.write(new_fw.to_dict())
|
65
|
+
return True
|
66
|
+
|
67
|
+
|
68
|
+
def copy_firmware(source: Union[Path, str], fw_filename: Path, force: bool = False):
|
69
|
+
"""Add a firmware to the firmware folder.
|
70
|
+
stored in the port folder, with the same filename as the source.
|
71
|
+
"""
|
72
|
+
if fw_filename.exists() and not force:
|
73
|
+
log.error(f" {fw_filename} already exists. Use --force to overwrite")
|
74
|
+
return False
|
75
|
+
fw_filename.parent.mkdir(exist_ok=True)
|
76
|
+
if isinstance(source, Path):
|
77
|
+
if not source.exists():
|
78
|
+
log.error(f"File {source} does not exist")
|
79
|
+
return False
|
80
|
+
# file copy
|
81
|
+
log.debug(f"Copy {source} to {fw_filename}")
|
82
|
+
shutil.copy(source, fw_filename)
|
83
|
+
return True
|
84
|
+
# handle github urls
|
85
|
+
url = rewrite_url(source)
|
86
|
+
if str(source).startswith("http://") or str(source).startswith("https://"):
|
87
|
+
log.debug(f"Download {url} to {fw_filename}")
|
88
|
+
response = requests.get(url)
|
89
|
+
|
90
|
+
if response.status_code == 200:
|
91
|
+
with open(fw_filename, "wb") as file:
|
92
|
+
file.write(response.content)
|
93
|
+
log.info("File downloaded and saved successfully.")
|
94
|
+
return True
|
95
|
+
else:
|
96
|
+
print("Failed to download the file.")
|
97
|
+
return False
|
98
|
+
return False
|
mpflash/ask_input.py
CHANGED
@@ -5,58 +5,33 @@ Note: The prompts can use "{version}" and "{action}" to insert the version and a
|
|
5
5
|
The values are provided from the answers dictionary.
|
6
6
|
"""
|
7
7
|
|
8
|
-
from
|
9
|
-
from pathlib import Path
|
10
|
-
from typing import Dict, List, Sequence, Tuple, Union
|
8
|
+
from typing import List, Sequence, Tuple, Union
|
11
9
|
|
12
10
|
from loguru import logger as log
|
13
11
|
|
14
|
-
from
|
15
|
-
from
|
16
|
-
from
|
17
|
-
from
|
18
|
-
|
19
|
-
|
20
|
-
@dataclass
|
21
|
-
class Params:
|
22
|
-
ports: List[str] = field(default_factory=list)
|
23
|
-
boards: List[str] = field(default_factory=list)
|
24
|
-
versions: List[str] = field(default_factory=list)
|
25
|
-
fw_folder: Path = Path()
|
26
|
-
|
27
|
-
|
28
|
-
@dataclass
|
29
|
-
class DownloadParams(Params):
|
30
|
-
clean: bool = False
|
31
|
-
force: bool = False
|
32
|
-
|
33
|
-
|
34
|
-
@dataclass
|
35
|
-
class FlashParams(Params):
|
36
|
-
# TODO: Should Serial port be a list?
|
37
|
-
serial: str = ""
|
38
|
-
erase: bool = True
|
39
|
-
bootloader: bool = True
|
40
|
-
cpu: str = ""
|
41
|
-
|
42
|
-
|
43
|
-
ParamType = Union[DownloadParams, FlashParams]
|
12
|
+
from .common import DownloadParams, FlashParams, ParamType
|
13
|
+
from .config import config
|
14
|
+
from .mpboard_id import get_known_boards_for_port, get_known_ports, known_stored_boards
|
15
|
+
from .mpremoteboard import MPRemoteBoard
|
16
|
+
from .vendor.versions import micropython_versions
|
44
17
|
|
45
18
|
|
46
19
|
def ask_missing_params(
|
47
20
|
params: ParamType,
|
48
|
-
# action: str = "download",
|
49
21
|
) -> ParamType:
|
50
22
|
"""
|
51
23
|
Asks the user for parameters that have not been supplied on the commandline and returns the updated params.
|
52
24
|
|
53
25
|
Args:
|
54
26
|
params (ParamType): The parameters to be updated.
|
55
|
-
action (str, optional): The action to be performed. Defaults to "download".
|
56
27
|
|
57
28
|
Returns:
|
58
29
|
ParamType: The updated parameters.
|
59
30
|
"""
|
31
|
+
import inquirer
|
32
|
+
|
33
|
+
log.trace(f"ask_missing_params: {params}")
|
34
|
+
|
60
35
|
# if action flash, single input
|
61
36
|
# if action download, multiple input
|
62
37
|
multi_select = isinstance(params, DownloadParams)
|
@@ -64,36 +39,36 @@ def ask_missing_params(
|
|
64
39
|
if not config.interactive:
|
65
40
|
# no interactivity allowed
|
66
41
|
return params
|
67
|
-
# import only when needed to reduce load time
|
68
|
-
import inquirer
|
69
42
|
|
70
43
|
questions = []
|
71
|
-
answers = {"action":
|
44
|
+
answers: dict[str, Union[str, List]] = {"action": action}
|
72
45
|
if not multi_select:
|
73
46
|
if not params.serial or "?" in params.serial:
|
74
|
-
ask_serialport(
|
47
|
+
questions.append(ask_serialport(multi_select=False, bluetooth=False))
|
75
48
|
else:
|
76
49
|
answers["serial"] = params.serial
|
77
50
|
|
78
|
-
if
|
79
|
-
|
51
|
+
if params.versions == [] or "?" in params.versions:
|
52
|
+
questions.append(ask_mp_version(multi_select=multi_select, action=action))
|
80
53
|
else:
|
81
54
|
# versions is used to show only the boards for the selected versions
|
82
55
|
answers["versions"] = params.versions # type: ignore
|
83
56
|
|
84
57
|
if not params.boards or "?" in params.boards:
|
85
|
-
ask_port_board(
|
58
|
+
questions.extend(ask_port_board(multi_select=multi_select, action=action))
|
86
59
|
if questions:
|
87
|
-
answers = inquirer.prompt(questions, answers=answers)
|
60
|
+
answers = inquirer.prompt(questions, answers=answers) # type: ignore
|
88
61
|
if not answers:
|
89
62
|
# input cancelled by user
|
90
63
|
return [] # type: ignore
|
91
|
-
|
64
|
+
log.trace(f"answers: {answers}")
|
92
65
|
if isinstance(params, FlashParams) and "serial" in answers:
|
93
|
-
|
66
|
+
if isinstance(answers["serial"], str):
|
67
|
+
answers["serial"] = [answers["serial"]]
|
68
|
+
params.serial = [s.split()[0] for s in answers["serial"]] # split to remove the description
|
94
69
|
if "port" in answers:
|
95
70
|
params.ports = [p for p in params.ports if p != "?"] # remove the "?" if present
|
96
|
-
params.ports.
|
71
|
+
params.ports.extend(answers["port"])
|
97
72
|
if "boards" in answers:
|
98
73
|
params.boards = [b for b in params.boards if b != "?"] # remove the "?" if present
|
99
74
|
params.boards.extend(answers["boards"] if isinstance(answers["boards"], list) else [answers["boards"]])
|
@@ -108,7 +83,7 @@ def ask_missing_params(
|
|
108
83
|
params.ports = list(set(params.ports))
|
109
84
|
params.boards = list(set(params.boards))
|
110
85
|
params.versions = list(set(params.versions))
|
111
|
-
log.
|
86
|
+
log.trace(f"ask_missing_params returns: {params}")
|
112
87
|
|
113
88
|
return params
|
114
89
|
|
@@ -123,19 +98,18 @@ def filter_matching_boards(answers: dict) -> Sequence[Tuple[str, str]]:
|
|
123
98
|
Returns:
|
124
99
|
Sequence[Tuple[str, str]]: The filtered boards.
|
125
100
|
"""
|
101
|
+
versions = None
|
126
102
|
# if version is not asked ; then need to get the version from the inputs
|
127
103
|
if "versions" in answers:
|
128
|
-
|
129
|
-
if "stable" in
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
else:
|
138
|
-
some_boards = known_stored_boards(answers["port"])
|
104
|
+
versions = list(answers["versions"])
|
105
|
+
if "stable" in versions:
|
106
|
+
versions.remove("stable")
|
107
|
+
versions.append(micropython_versions()[-2]) # latest stable
|
108
|
+
elif "preview" in versions:
|
109
|
+
versions.remove("preview")
|
110
|
+
versions.extend((micropython_versions()[-1], micropython_versions()[-2])) # latest preview and stable
|
111
|
+
|
112
|
+
some_boards = known_stored_boards(answers["port"], versions) # or known_mp_boards(answers["port"])
|
139
113
|
|
140
114
|
if some_boards:
|
141
115
|
# Create a dictionary where the keys are the second elements of the tuples
|
@@ -144,11 +118,11 @@ def filter_matching_boards(answers: dict) -> Sequence[Tuple[str, str]]:
|
|
144
118
|
# Get the values of the dictionary, which are the unique items from the original list
|
145
119
|
some_boards = list(unique_dict.values())
|
146
120
|
else:
|
147
|
-
some_boards = [(f"No {answers['port']} boards found for version(s) {
|
121
|
+
some_boards = [(f"No {answers['port']} boards found for version(s) {versions}", "")]
|
148
122
|
return some_boards
|
149
123
|
|
150
124
|
|
151
|
-
def ask_port_board(
|
125
|
+
def ask_port_board(*, multi_select: bool, action: str):
|
152
126
|
"""
|
153
127
|
Asks the user for the port and board selection.
|
154
128
|
|
@@ -165,29 +139,25 @@ def ask_port_board(questions: list, *, multi_select: bool, action: str):
|
|
165
139
|
# if action flash, single input
|
166
140
|
# if action download, multiple input
|
167
141
|
inquirer_ux = inquirer.Checkbox if multi_select else inquirer.List
|
168
|
-
|
169
|
-
(
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
"Which {port} board firmware do you want to {action} " + "to {serial} ?"
|
180
|
-
if action == "flash"
|
181
|
-
else "?"
|
182
|
-
),
|
183
|
-
choices=filter_matching_boards,
|
184
|
-
validate=lambda _, x: True if x else "Please select at least one board", # type: ignore
|
142
|
+
return [
|
143
|
+
inquirer.List(
|
144
|
+
"port",
|
145
|
+
message="Which port do you want to {action} " + "to {serial} ?" if action == "flash" else "?",
|
146
|
+
choices=get_known_ports(),
|
147
|
+
# autocomplete=True,
|
148
|
+
),
|
149
|
+
inquirer_ux(
|
150
|
+
"boards",
|
151
|
+
message=(
|
152
|
+
"Which {port} board firmware do you want to {action} " + "to {serial} ?" if action == "flash" else "?"
|
185
153
|
),
|
186
|
-
|
187
|
-
|
154
|
+
choices=filter_matching_boards,
|
155
|
+
validate=lambda _, x: True if x else "Please select at least one board", # type: ignore
|
156
|
+
),
|
157
|
+
]
|
188
158
|
|
189
159
|
|
190
|
-
def
|
160
|
+
def ask_mp_version(multi_select: bool, action: str):
|
191
161
|
"""
|
192
162
|
Asks the user for the version selection.
|
193
163
|
|
@@ -196,46 +166,43 @@ def ask_versions(questions: list, *, multi_select: bool, action: str):
|
|
196
166
|
action (str): The action to be performed.
|
197
167
|
|
198
168
|
Returns:
|
199
|
-
|
169
|
+
|
200
170
|
"""
|
201
171
|
# import only when needed to reduce load time
|
202
172
|
import inquirer
|
203
173
|
import inquirer.errors
|
204
174
|
|
205
175
|
input_ux = inquirer.Checkbox if multi_select else inquirer.List
|
176
|
+
|
206
177
|
mp_versions: List[str] = micropython_versions()
|
207
|
-
mp_versions
|
178
|
+
mp_versions.reverse() # newest first
|
208
179
|
|
209
180
|
# remove the versions for which there are no known boards in the board_info.json
|
210
181
|
# todo: this may be a little slow
|
211
|
-
mp_versions = [v for v in mp_versions if get_known_boards_for_port("stm32", [v])]
|
212
|
-
|
213
|
-
mp_versions.append("preview")
|
214
|
-
mp_versions.reverse() # newest first
|
182
|
+
mp_versions = [v for v in mp_versions if "preview" in v or get_known_boards_for_port("stm32", [v])]
|
215
183
|
|
216
184
|
def at_least_one_validation(answers, current) -> bool:
|
217
185
|
if not current:
|
218
186
|
raise inquirer.errors.ValidationError("", reason="Please select at least one version")
|
219
|
-
if isinstance(current, list):
|
220
|
-
|
221
|
-
raise inquirer.errors.ValidationError("", reason="Please select at least one version")
|
187
|
+
if isinstance(current, list) and not any(current):
|
188
|
+
raise inquirer.errors.ValidationError("", reason="Please select at least one version")
|
222
189
|
return True
|
223
190
|
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
)
|
191
|
+
message = "Which version(s) do you want to {action} " + ("to {serial} ?" if action == "flash" else "?")
|
192
|
+
q = input_ux(
|
193
|
+
# inquirer.List(
|
194
|
+
"versions",
|
195
|
+
message=message,
|
196
|
+
# Hints would be nice , but needs a hint for each and every option
|
197
|
+
# hints=["Use space to select multiple options"],
|
198
|
+
choices=mp_versions,
|
199
|
+
autocomplete=True,
|
200
|
+
validate=at_least_one_validation, # type: ignore
|
235
201
|
)
|
202
|
+
return q
|
236
203
|
|
237
204
|
|
238
|
-
def ask_serialport(
|
205
|
+
def ask_serialport(*, multi_select: bool = False, bluetooth: bool = False):
|
239
206
|
"""
|
240
207
|
Asks the user for the serial port selection.
|
241
208
|
|
@@ -250,14 +217,10 @@ def ask_serialport(questions: list, *, multi_select: bool = False, bluetooth: bo
|
|
250
217
|
import inquirer
|
251
218
|
|
252
219
|
comports = MPRemoteBoard.connected_boards(bluetooth=bluetooth, description=True)
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
validate=lambda _, x: True if x else "Please select or enter a serial port", # type: ignore
|
260
|
-
)
|
220
|
+
return inquirer.List(
|
221
|
+
"serial",
|
222
|
+
message="Which serial port do you want to {action} ?",
|
223
|
+
choices=comports,
|
224
|
+
other=True,
|
225
|
+
validate=lambda _, x: True if x else "Please select or enter a serial port", # type: ignore
|
261
226
|
)
|
262
|
-
|
263
|
-
return questions
|
mpflash/cli_download.py
CHANGED
@@ -1,18 +1,18 @@
|
|
1
1
|
"""CLI to Download MicroPython firmware for specific ports, boards and versions."""
|
2
2
|
|
3
3
|
from pathlib import Path
|
4
|
-
from typing import List, Tuple
|
5
4
|
|
6
5
|
import rich_click as click
|
7
6
|
from loguru import logger as log
|
8
7
|
|
8
|
+
from mpflash.connected import connected_ports_boards
|
9
9
|
from mpflash.errors import MPFlashError
|
10
10
|
from mpflash.mpboard_id import find_known_board
|
11
11
|
from mpflash.vendor.versions import clean_version
|
12
12
|
|
13
|
-
from .ask_input import
|
13
|
+
from .ask_input import ask_missing_params
|
14
14
|
from .cli_group import cli
|
15
|
-
from .
|
15
|
+
from .common import DownloadParams
|
16
16
|
from .config import config
|
17
17
|
from .download import download
|
18
18
|
|
@@ -50,6 +50,28 @@ from .download import download
|
|
50
50
|
help="The board(s) to download the firmware for.",
|
51
51
|
metavar="BOARD_ID or ?",
|
52
52
|
)
|
53
|
+
@click.option(
|
54
|
+
"--serial",
|
55
|
+
"--serial-port",
|
56
|
+
"-s",
|
57
|
+
"serial",
|
58
|
+
default=["*"],
|
59
|
+
show_default=True,
|
60
|
+
multiple=True,
|
61
|
+
help="Which serial port(s) to flash",
|
62
|
+
metavar="SERIALPORT",
|
63
|
+
)
|
64
|
+
@click.option(
|
65
|
+
"--ignore",
|
66
|
+
"-i",
|
67
|
+
is_eager=True,
|
68
|
+
help="Serial port(s) to ignore. Defaults to MPFLASH_IGNORE.",
|
69
|
+
multiple=True,
|
70
|
+
default=[],
|
71
|
+
envvar="MPFLASH_IGNORE",
|
72
|
+
show_default=True,
|
73
|
+
metavar="SERIALPORT",
|
74
|
+
)
|
53
75
|
@click.option(
|
54
76
|
"--clean/--no-clean",
|
55
77
|
default=True,
|
@@ -67,6 +89,10 @@ def cli_download(**kwargs) -> int:
|
|
67
89
|
params = DownloadParams(**kwargs)
|
68
90
|
params.versions = list(params.versions)
|
69
91
|
params.boards = list(params.boards)
|
92
|
+
params.serial = list(params.serial)
|
93
|
+
params.ignore = list(params.ignore)
|
94
|
+
|
95
|
+
# all_boards: List[MPRemoteBoard] = []
|
70
96
|
if params.boards:
|
71
97
|
if not params.ports:
|
72
98
|
# no ports specified - resolve ports from specified boards by resolving board IDs
|
@@ -74,12 +100,12 @@ def cli_download(**kwargs) -> int:
|
|
74
100
|
if board != "?":
|
75
101
|
try:
|
76
102
|
board_ = find_known_board(board)
|
77
|
-
params.ports.append(board_
|
103
|
+
params.ports.append(board_.port)
|
78
104
|
except MPFlashError as e:
|
79
105
|
log.error(f"{e}")
|
80
106
|
else:
|
81
107
|
# no boards specified - detect connected ports and boards
|
82
|
-
params.ports, params.boards = connected_ports_boards()
|
108
|
+
params.ports, params.boards, _ = connected_ports_boards(include=params.serial, ignore=params.ignore)
|
83
109
|
|
84
110
|
params = ask_missing_params(params)
|
85
111
|
if not params: # Cancelled by user
|
@@ -100,19 +126,3 @@ def cli_download(**kwargs) -> int:
|
|
100
126
|
except MPFlashError as e:
|
101
127
|
log.error(f"{e}")
|
102
128
|
return 1
|
103
|
-
|
104
|
-
|
105
|
-
def connected_ports_boards() -> Tuple[List[str], List[str]]:
|
106
|
-
"""
|
107
|
-
Returns a tuple containing lists of unique ports and boards from the connected MCUs.
|
108
|
-
Boards that are physically connected, but give no tangible response are ignored.
|
109
|
-
|
110
|
-
Returns:
|
111
|
-
A tuple containing two lists:
|
112
|
-
- A list of unique ports where MCUs are connected.
|
113
|
-
- A list of unique board names of the connected MCUs.
|
114
|
-
"""
|
115
|
-
mpr_boards = [b for b in list_mcus() if b.connected]
|
116
|
-
ports = list({b.port for b in mpr_boards})
|
117
|
-
boards = list({b.board for b in mpr_boards})
|
118
|
-
return ports, boards
|
mpflash/cli_flash.py
CHANGED
@@ -1,19 +1,23 @@
|
|
1
1
|
from pathlib import Path
|
2
|
+
from typing import List
|
2
3
|
|
3
4
|
import rich_click as click
|
4
5
|
from loguru import logger as log
|
5
6
|
|
7
|
+
# from mpflash.common import filtered_comports
|
6
8
|
from mpflash.errors import MPFlashError
|
7
9
|
from mpflash.mpboard_id import find_known_board
|
10
|
+
from mpflash.mpremoteboard import MPRemoteBoard
|
8
11
|
from mpflash.vendor.versions import clean_version
|
9
12
|
|
10
|
-
from .ask_input import
|
13
|
+
from .ask_input import ask_missing_params
|
11
14
|
from .cli_download import connected_ports_boards
|
12
15
|
from .cli_group import cli
|
13
16
|
from .cli_list import show_mcus
|
17
|
+
from .common import FlashParams
|
14
18
|
from .config import config
|
15
19
|
from .flash import flash_list
|
16
|
-
from .worklist import
|
20
|
+
from .worklist import WorkList, full_auto_worklist, manual_worklist, single_auto_worklist
|
17
21
|
|
18
22
|
# #########################################################################################################
|
19
23
|
# CLI
|
@@ -48,10 +52,22 @@ from .worklist import MPRemoteBoard, WorkList, full_auto_worklist, manual_workli
|
|
48
52
|
"--serial-port",
|
49
53
|
"-s",
|
50
54
|
"serial",
|
51
|
-
default="
|
55
|
+
default=["*"],
|
56
|
+
multiple=True,
|
52
57
|
show_default=True,
|
53
58
|
help="Which serial port(s) to flash",
|
54
|
-
metavar="
|
59
|
+
metavar="SERIALPORT",
|
60
|
+
)
|
61
|
+
@click.option(
|
62
|
+
"--ignore",
|
63
|
+
"-i",
|
64
|
+
is_eager=True,
|
65
|
+
help="Serial port(s) to ignore. Defaults to MPFLASH_IGNORE.",
|
66
|
+
multiple=True,
|
67
|
+
default=[],
|
68
|
+
envvar="MPFLASH_IGNORE",
|
69
|
+
show_default=True,
|
70
|
+
metavar="SERIALPORT",
|
55
71
|
)
|
56
72
|
@click.option(
|
57
73
|
"--port",
|
@@ -101,36 +117,28 @@ def cli_flash_board(**kwargs) -> int:
|
|
101
117
|
kwargs["boards"] = [kwargs.pop("board")]
|
102
118
|
|
103
119
|
params = FlashParams(**kwargs)
|
120
|
+
params.versions = list(params.versions)
|
121
|
+
params.boards = list(params.boards)
|
122
|
+
params.serial = list(params.serial)
|
123
|
+
params.ignore = list(params.ignore)
|
104
124
|
|
105
|
-
# make it simple for the user to flash one board
|
106
|
-
|
107
|
-
|
108
|
-
params.serial = "?"
|
125
|
+
# make it simple for the user to flash one board by asking for the serial port if not specified
|
126
|
+
if params.boards == ["?"] and params.serial == "*":
|
127
|
+
params.serial = ["?"]
|
109
128
|
|
110
129
|
# Detect connected boards if not specified,
|
111
130
|
# and ask for input if boards cannot be detected
|
131
|
+
all_boards: List[MPRemoteBoard] = []
|
112
132
|
if not params.boards or params.boards == []:
|
113
133
|
# nothing specified - detect connected boards
|
114
|
-
params.ports, params.boards = connected_ports_boards()
|
134
|
+
params.ports, params.boards, all_boards = connected_ports_boards(include=params.ports, ignore=params.ignore)
|
115
135
|
if params.boards == []:
|
116
136
|
# No MicroPython boards detected, but it could be unflashed or not in bootloader mode
|
117
137
|
# Ask for serial port and board_id to flash
|
118
|
-
params.serial = "?"
|
138
|
+
params.serial = ["?"]
|
119
139
|
params.boards = ["?"]
|
120
140
|
else:
|
121
|
-
|
122
|
-
if board_id == "":
|
123
|
-
params.boards.remove(board_id)
|
124
|
-
continue
|
125
|
-
if " " in board_id:
|
126
|
-
try:
|
127
|
-
info = find_known_board(board_id)
|
128
|
-
if info:
|
129
|
-
log.info(f"Resolved board description: {info['board']}")
|
130
|
-
params.boards.remove(board_id)
|
131
|
-
params.boards.append(info["board"])
|
132
|
-
except Exception as e:
|
133
|
-
log.warning(f"unable to resolve board description: {e}")
|
141
|
+
resolve_board_ids(params)
|
134
142
|
|
135
143
|
# Ask for missing input if needed
|
136
144
|
params = ask_missing_params(params)
|
@@ -143,27 +151,33 @@ def cli_flash_board(**kwargs) -> int:
|
|
143
151
|
if len(params.versions) > 1:
|
144
152
|
log.error(f"Only one version can be flashed at a time, not {params.versions}")
|
145
153
|
raise MPFlashError("Only one version can be flashed at a time")
|
146
|
-
# if len(params.boards) > 1:
|
147
|
-
# log.error(f"Only one board can be flashed at a time, not {params.boards}")
|
148
|
-
# raise MPFlashError("Only one board can be flashed at a time")
|
149
154
|
|
150
155
|
params.versions = [clean_version(v) for v in params.versions]
|
151
156
|
worklist: WorkList = []
|
152
157
|
# if serial port == auto and there are one or more specified/detected boards
|
153
|
-
if params.serial == "
|
154
|
-
|
158
|
+
if params.serial == ["*"] and params.boards:
|
159
|
+
if not all_boards:
|
160
|
+
log.trace("No boards detected yet, scanning for connected boards")
|
161
|
+
_, _, all_boards = connected_ports_boards(include=params.ports, ignore=params.ignore)
|
162
|
+
worklist = full_auto_worklist(
|
163
|
+
all_boards=all_boards,
|
164
|
+
version=params.versions[0],
|
165
|
+
fw_folder=params.fw_folder,
|
166
|
+
include=params.serial,
|
167
|
+
ignore=params.ignore,
|
168
|
+
)
|
155
169
|
elif params.versions[0] and params.boards[0] and params.serial:
|
156
|
-
# A
|
170
|
+
# A one or more serial port including the board / variant
|
157
171
|
worklist = manual_worklist(
|
158
|
-
params.
|
159
|
-
params.
|
160
|
-
params.
|
161
|
-
params.
|
172
|
+
params.serial[0],
|
173
|
+
board_id=params.boards[0],
|
174
|
+
version=params.versions[0],
|
175
|
+
fw_folder=params.fw_folder,
|
162
176
|
)
|
163
177
|
else:
|
164
178
|
# just this serial port on auto
|
165
179
|
worklist = single_auto_worklist(
|
166
|
-
|
180
|
+
serial=params.serial[0],
|
167
181
|
version=params.versions[0],
|
168
182
|
fw_folder=params.fw_folder,
|
169
183
|
)
|
@@ -180,3 +194,19 @@ def cli_flash_board(**kwargs) -> int:
|
|
180
194
|
else:
|
181
195
|
log.error("No boards were flashed")
|
182
196
|
return 1
|
197
|
+
|
198
|
+
|
199
|
+
def resolve_board_ids(params):
|
200
|
+
"""Resolve board descriptions to board_id, and remove empty strings from list of boards"""
|
201
|
+
for board_id in params.boards:
|
202
|
+
if board_id == "":
|
203
|
+
params.boards.remove(board_id)
|
204
|
+
continue
|
205
|
+
if " " in board_id:
|
206
|
+
try:
|
207
|
+
if info := find_known_board(board_id):
|
208
|
+
log.info(f"Resolved board description: {info.board_id}")
|
209
|
+
params.boards.remove(board_id)
|
210
|
+
params.boards.append(info.board_id)
|
211
|
+
except Exception as e:
|
212
|
+
log.warning(f"Unable to resolve board description: {e}")
|