mpflash 0.5.0__tar.gz → 0.6.0__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.5.0 → mpflash-0.6.0}/PKG-INFO +4 -4
- {mpflash-0.5.0 → mpflash-0.6.0}/README.md +3 -3
- mpflash-0.6.0/mpflash/ask_input.py +234 -0
- {mpflash-0.5.0 → mpflash-0.6.0}/mpflash/cli_download.py +31 -15
- mpflash-0.6.0/mpflash/cli_flash.py +165 -0
- mpflash-0.6.0/mpflash/cli_list.py +41 -0
- {mpflash-0.5.0 → mpflash-0.6.0}/mpflash/cli_main.py +7 -3
- mpflash-0.6.0/mpflash/common.py +38 -0
- {mpflash-0.5.0 → mpflash-0.6.0}/mpflash/download.py +15 -3
- mpflash-0.6.0/mpflash/downloaded.py +108 -0
- mpflash-0.6.0/mpflash/errors.py +5 -0
- mpflash-0.6.0/mpflash/flash.py +69 -0
- {mpflash-0.5.0 → mpflash-0.6.0}/mpflash/flash_esp.py +10 -15
- {mpflash-0.5.0 → mpflash-0.6.0}/mpflash/flash_stm32.py +1 -3
- {mpflash-0.5.0 → mpflash-0.6.0}/mpflash/flash_stm32_cube.py +0 -1
- {mpflash-0.5.0 → mpflash-0.6.0}/mpflash/flash_stm32_dfu.py +19 -5
- mpflash-0.5.0/mpflash/cli_list.py → mpflash-0.6.0/mpflash/list.py +11 -38
- mpflash-0.5.0/mpflash/mpboard_id/api.py → mpflash-0.6.0/mpflash/mpboard_id/__init__.py +23 -16
- mpflash-0.6.0/mpflash/mpboard_id/board_id.py +63 -0
- mpflash-0.6.0/mpflash/mpremoteboard/__init__.py +208 -0
- {mpflash-0.5.0 → mpflash-0.6.0}/mpflash/mpremoteboard/runner.py +5 -1
- mpflash-0.5.0/mpflash/common.py → mpflash-0.6.0/mpflash/vendor/versions.py +7 -56
- mpflash-0.6.0/mpflash/worklist.py +147 -0
- {mpflash-0.5.0 → mpflash-0.6.0}/pyproject.toml +1 -1
- mpflash-0.5.0/mpflash/ask_input.py +0 -163
- mpflash-0.5.0/mpflash/cli_flash.py +0 -225
- mpflash-0.5.0/mpflash/flash.py +0 -164
- mpflash-0.5.0/mpflash/mpboard_id/board_id.py +0 -45
- mpflash-0.5.0/mpflash/mpremoteboard/__init__.py +0 -138
- {mpflash-0.5.0 → mpflash-0.6.0}/LICENSE +0 -0
- {mpflash-0.5.0 → mpflash-0.6.0}/mpflash/__init__.py +0 -0
- {mpflash-0.5.0 → mpflash-0.6.0}/mpflash/cli_group.py +0 -0
- {mpflash-0.5.0 → mpflash-0.6.0}/mpflash/config.py +0 -0
- {mpflash-0.5.0 → mpflash-0.6.0}/mpflash/flash_uf2.py +0 -0
- {mpflash-0.5.0 → mpflash-0.6.0}/mpflash/flash_uf2_boardid.py +0 -0
- {mpflash-0.5.0 → mpflash-0.6.0}/mpflash/flash_uf2_linux.py +0 -0
- {mpflash-0.5.0 → mpflash-0.6.0}/mpflash/flash_uf2_windows.py +0 -0
- {mpflash-0.5.0 → mpflash-0.6.0}/mpflash/logger.py +0 -0
- {mpflash-0.5.0 → mpflash-0.6.0}/mpflash/mpboard_id/board_info.csv +0 -0
- {mpflash-0.5.0 → mpflash-0.6.0}/mpflash/mpboard_id/board_info.json +0 -0
- {mpflash-0.5.0 → mpflash-0.6.0}/mpflash/mpremoteboard/mpy_fw_info.py +0 -0
- {mpflash-0.5.0/mpflash/vendored → mpflash-0.6.0/mpflash/vendor}/dfu.py +0 -0
- {mpflash-0.5.0/mpflash/vendored → mpflash-0.6.0/mpflash/vendor}/pydfu.py +0 -0
- {mpflash-0.5.0/mpflash/vendored → mpflash-0.6.0/mpflash/vendor}/readme.md +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: mpflash
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.6.0
|
4
4
|
Summary: Flash and download tool for MicroPython firmwares
|
5
5
|
Home-page: https://github.com/Josverl/micropython-stubber/blob/main/src/mpflash/README.md
|
6
6
|
License: MIT
|
@@ -48,7 +48,7 @@ mpflash has been tested on Windows x64, Linux X64 and ARM64, but not (yet) macOS
|
|
48
48
|
1. List the connected boards including their firmware details, in a tabular or json format
|
49
49
|
2. Download MicroPython firmware for specific boards and versions.
|
50
50
|
3. Flash one or all connected MicroPython boards with a specific firmware or version.
|
51
|
-
Tested ports: rp2, samd, esp32, esp32s3, esp8266 and stm32
|
51
|
+
Tested ports: rp2, samd, esp32, esp32s3, esp8266 and stm32
|
52
52
|
|
53
53
|
## Installation
|
54
54
|
To install mpflash, you can use pip: `pip install mpflash`
|
@@ -63,10 +63,10 @@ You can use mpflash to perform various operations on your MicroPython boards. He
|
|
63
63
|
| `mpflash flash` | Flash the latest stable firmware to the connected board(s) |
|
64
64
|
|
65
65
|
|
66
|
-
## Linux permissions
|
66
|
+
## Linux permissions to access usb devices
|
67
67
|
In order to flash the firmware to the board, you need to have the correct permissions to access the USB devices.
|
68
68
|
On Windows this will not be an issue, but on Linux you can use udev rules to give non-root users access to the USB devices.
|
69
|
-
[See
|
69
|
+
[See the stm32_permissions documentation](./stm32_udev_rules.md) for more information.
|
70
70
|
|
71
71
|
|
72
72
|
## Advanced use
|
@@ -9,7 +9,7 @@ mpflash has been tested on Windows x64, Linux X64 and ARM64, but not (yet) macOS
|
|
9
9
|
1. List the connected boards including their firmware details, in a tabular or json format
|
10
10
|
2. Download MicroPython firmware for specific boards and versions.
|
11
11
|
3. Flash one or all connected MicroPython boards with a specific firmware or version.
|
12
|
-
Tested ports: rp2, samd, esp32, esp32s3, esp8266 and stm32
|
12
|
+
Tested ports: rp2, samd, esp32, esp32s3, esp8266 and stm32
|
13
13
|
|
14
14
|
## Installation
|
15
15
|
To install mpflash, you can use pip: `pip install mpflash`
|
@@ -24,10 +24,10 @@ You can use mpflash to perform various operations on your MicroPython boards. He
|
|
24
24
|
| `mpflash flash` | Flash the latest stable firmware to the connected board(s) |
|
25
25
|
|
26
26
|
|
27
|
-
## Linux permissions
|
27
|
+
## Linux permissions to access usb devices
|
28
28
|
In order to flash the firmware to the board, you need to have the correct permissions to access the USB devices.
|
29
29
|
On Windows this will not be an issue, but on Linux you can use udev rules to give non-root users access to the USB devices.
|
30
|
-
[See
|
30
|
+
[See the stm32_permissions documentation](./stm32_udev_rules.md) for more information.
|
31
31
|
|
32
32
|
|
33
33
|
## Advanced use
|
@@ -0,0 +1,234 @@
|
|
1
|
+
"""
|
2
|
+
Interactive input for mpflash.
|
3
|
+
|
4
|
+
Note: The prompts can use "{version}" and "{action}" to insert the version and action in the prompt without needing an f-string.
|
5
|
+
The values are provided from the answers dictionary.
|
6
|
+
"""
|
7
|
+
|
8
|
+
from dataclasses import dataclass, field
|
9
|
+
from pathlib import Path
|
10
|
+
from typing import Dict, List, Sequence, Tuple, Union
|
11
|
+
|
12
|
+
from loguru import logger as log
|
13
|
+
|
14
|
+
from mpflash.config import config
|
15
|
+
from mpflash.mpboard_id import known_stored_boards, local_mp_ports
|
16
|
+
from mpflash.mpremoteboard import MPRemoteBoard
|
17
|
+
from mpflash.vendor.versions import micropython_versions
|
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]
|
44
|
+
|
45
|
+
|
46
|
+
def ask_missing_params(
|
47
|
+
params: ParamType,
|
48
|
+
action: str = "download",
|
49
|
+
) -> ParamType:
|
50
|
+
"""
|
51
|
+
Asks the user for parameters that have not been supplied on the commandline and returns the updated params.
|
52
|
+
|
53
|
+
Args:
|
54
|
+
params (ParamType): The parameters to be updated.
|
55
|
+
action (str, optional): The action to be performed. Defaults to "download".
|
56
|
+
|
57
|
+
Returns:
|
58
|
+
ParamType: The updated parameters.
|
59
|
+
"""
|
60
|
+
if not config.interactive:
|
61
|
+
# no interactivity allowed
|
62
|
+
return params
|
63
|
+
# import only when needed to reduce load time
|
64
|
+
import inquirer
|
65
|
+
|
66
|
+
questions = []
|
67
|
+
answers = {"action": action}
|
68
|
+
if isinstance(params, FlashParams):
|
69
|
+
if not params.serial or "?" in params.serial:
|
70
|
+
ask_serialport(questions, action=action)
|
71
|
+
else:
|
72
|
+
answers["serial"] = params.serial
|
73
|
+
|
74
|
+
if not params.versions or "?" in params.versions:
|
75
|
+
ask_versions(questions, action=action)
|
76
|
+
else:
|
77
|
+
# versions is used to show only the boards for the selected versions
|
78
|
+
answers["versions"] = params.versions # type: ignore
|
79
|
+
|
80
|
+
if not params.boards or "?" in params.boards:
|
81
|
+
ask_port_board(questions, action=action)
|
82
|
+
|
83
|
+
answers = inquirer.prompt(questions, answers=answers)
|
84
|
+
if not answers:
|
85
|
+
# input cancelled by user
|
86
|
+
return [] # type: ignore
|
87
|
+
# print(repr(answers))
|
88
|
+
if isinstance(params, FlashParams) and "serial" in answers:
|
89
|
+
params.serial = answers["serial"]
|
90
|
+
if "port" in answers:
|
91
|
+
params.ports = [answers["port"]]
|
92
|
+
if "boards" in answers:
|
93
|
+
params.boards = answers["boards"] if isinstance(answers["boards"], list) else [answers["boards"]]
|
94
|
+
if "versions" in answers:
|
95
|
+
# make sure it is a list
|
96
|
+
params.versions = answers["versions"] if isinstance(answers["versions"], list) else [answers["versions"]]
|
97
|
+
|
98
|
+
log.debug(repr(params))
|
99
|
+
|
100
|
+
return params
|
101
|
+
|
102
|
+
|
103
|
+
def filter_matching_boards(answers: dict) -> Sequence[Tuple[str, str]]:
|
104
|
+
"""
|
105
|
+
Filters the known boards based on the selected versions and returns the filtered boards.
|
106
|
+
|
107
|
+
Args:
|
108
|
+
answers (dict): The user's answers.
|
109
|
+
|
110
|
+
Returns:
|
111
|
+
Sequence[Tuple[str, str]]: The filtered boards.
|
112
|
+
"""
|
113
|
+
# if version is not asked ; then need to get the version from the inputs
|
114
|
+
if "versions" in answers:
|
115
|
+
_versions = list(answers["versions"])
|
116
|
+
if "stable" in _versions:
|
117
|
+
_versions.remove("stable")
|
118
|
+
_versions.append(micropython_versions()[-2]) # latest stable
|
119
|
+
if "preview" in _versions:
|
120
|
+
_versions.remove("preview")
|
121
|
+
_versions.extend((micropython_versions()[-1], micropython_versions()[-2])) # latest preview and stable
|
122
|
+
|
123
|
+
some_boards = known_stored_boards(answers["port"], _versions) # or known_mp_boards(answers["port"])
|
124
|
+
else:
|
125
|
+
some_boards = known_stored_boards(answers["port"])
|
126
|
+
|
127
|
+
if some_boards:
|
128
|
+
# Create a dictionary where the keys are the second elements of the tuples
|
129
|
+
# This will automatically remove duplicates because dictionaries cannot have duplicate keys
|
130
|
+
unique_dict = {item[1]: item for item in some_boards}
|
131
|
+
# Get the values of the dictionary, which are the unique items from the original list
|
132
|
+
some_boards = list(unique_dict.values())
|
133
|
+
else:
|
134
|
+
some_boards = [("No boards found", "")]
|
135
|
+
return some_boards
|
136
|
+
|
137
|
+
|
138
|
+
def ask_port_board(questions: list, *, action: str):
|
139
|
+
"""
|
140
|
+
Asks the user for the port and board selection.
|
141
|
+
|
142
|
+
Args:
|
143
|
+
questions (list): The list of questions to be asked.
|
144
|
+
action (str): The action to be performed.
|
145
|
+
|
146
|
+
Returns:
|
147
|
+
None
|
148
|
+
"""
|
149
|
+
# import only when needed to reduce load time
|
150
|
+
import inquirer
|
151
|
+
|
152
|
+
# TODO: if action = flash, Use Inquirer.List for boards
|
153
|
+
inquirer_ux = inquirer.Checkbox if action == "download" else inquirer.List
|
154
|
+
questions.extend(
|
155
|
+
(
|
156
|
+
inquirer.List(
|
157
|
+
"port",
|
158
|
+
message="Which port do you want to {action} " + "to {serial} ?" if action == "flash" else "?",
|
159
|
+
choices=local_mp_ports(),
|
160
|
+
autocomplete=True,
|
161
|
+
),
|
162
|
+
inquirer_ux(
|
163
|
+
"boards",
|
164
|
+
message=(
|
165
|
+
"Which {port} board firmware do you want to {action} " + "to {serial} ?"
|
166
|
+
if action == "flash"
|
167
|
+
else "?"
|
168
|
+
),
|
169
|
+
choices=filter_matching_boards,
|
170
|
+
validate=lambda _, x: True if x else "Please select at least one board", # type: ignore
|
171
|
+
),
|
172
|
+
)
|
173
|
+
)
|
174
|
+
|
175
|
+
|
176
|
+
def ask_versions(questions: list, *, action: str):
|
177
|
+
"""
|
178
|
+
Asks the user for the version selection.
|
179
|
+
|
180
|
+
Args:
|
181
|
+
questions (list): The list of questions to be asked.
|
182
|
+
action (str): The action to be performed.
|
183
|
+
|
184
|
+
Returns:
|
185
|
+
None
|
186
|
+
"""
|
187
|
+
# import only when needed to reduce load time
|
188
|
+
import inquirer
|
189
|
+
|
190
|
+
input_ux = inquirer.Checkbox if action == "download" else inquirer.List
|
191
|
+
mp_versions: List[str] = micropython_versions()
|
192
|
+
mp_versions = [v for v in mp_versions if "preview" not in v]
|
193
|
+
mp_versions.append("preview")
|
194
|
+
mp_versions.reverse() # newest first
|
195
|
+
questions.append(
|
196
|
+
input_ux(
|
197
|
+
# inquirer.List(
|
198
|
+
"versions",
|
199
|
+
message="Which version(s) do you want to {action} " + ("to {serial} ?" if action == "flash" else "?"),
|
200
|
+
# Hints would be nice , but needs a hint for each and every option
|
201
|
+
# hints=["Use space to select multiple options"],
|
202
|
+
choices=mp_versions,
|
203
|
+
autocomplete=True,
|
204
|
+
validate=lambda _, x: True if x else "Please select at least one version", # type: ignore
|
205
|
+
)
|
206
|
+
)
|
207
|
+
|
208
|
+
|
209
|
+
def ask_serialport(questions: list, *, action: str):
|
210
|
+
"""
|
211
|
+
Asks the user for the serial port selection.
|
212
|
+
|
213
|
+
Args:
|
214
|
+
questions (list): The list of questions to be asked.
|
215
|
+
action (str): The action to be performed.
|
216
|
+
|
217
|
+
Returns:
|
218
|
+
None
|
219
|
+
"""
|
220
|
+
# import only when needed to reduce load time
|
221
|
+
import inquirer
|
222
|
+
|
223
|
+
serialports = MPRemoteBoard.connected_boards()
|
224
|
+
questions.append(
|
225
|
+
inquirer.List(
|
226
|
+
"serial",
|
227
|
+
message="Which serial port do you want to {action} ?",
|
228
|
+
choices=serialports,
|
229
|
+
other=True,
|
230
|
+
validate=lambda _, x: True if x else "Please select or enter a serial port", # type: ignore
|
231
|
+
)
|
232
|
+
)
|
233
|
+
|
234
|
+
return questions
|
@@ -4,8 +4,10 @@ from pathlib import Path
|
|
4
4
|
from typing import List, Tuple
|
5
5
|
|
6
6
|
import rich_click as click
|
7
|
+
from loguru import logger as log
|
7
8
|
|
8
|
-
from mpflash.
|
9
|
+
from mpflash.mpboard_id import find_stored_board
|
10
|
+
from mpflash.vendor.versions import clean_version
|
9
11
|
|
10
12
|
from .ask_input import DownloadParams, ask_missing_params
|
11
13
|
from .cli_group import cli
|
@@ -14,13 +16,6 @@ from .config import config
|
|
14
16
|
from .download import download
|
15
17
|
|
16
18
|
|
17
|
-
def connected_ports_boards() -> Tuple[List[str], List[str]]:
|
18
|
-
mpr_boards = list_mcus()
|
19
|
-
ports = list({b.port for b in mpr_boards})
|
20
|
-
boards = list({b.board for b in mpr_boards})
|
21
|
-
return ports, boards
|
22
|
-
|
23
|
-
|
24
19
|
@cli.command(
|
25
20
|
"download",
|
26
21
|
help="Download MicroPython firmware for specific ports, boards and versions.",
|
@@ -49,7 +44,7 @@ def connected_ports_boards() -> Tuple[List[str], List[str]]:
|
|
49
44
|
"-b",
|
50
45
|
"boards",
|
51
46
|
multiple=True,
|
52
|
-
default=[
|
47
|
+
default=[],
|
53
48
|
show_default=True,
|
54
49
|
help="The board(s) to download the firmware for.",
|
55
50
|
metavar="BOARD_ID or ?",
|
@@ -71,16 +66,21 @@ def cli_download(
|
|
71
66
|
**kwargs,
|
72
67
|
):
|
73
68
|
params = DownloadParams(**kwargs)
|
74
|
-
|
75
|
-
|
76
|
-
|
69
|
+
params.versions = list(params.versions)
|
70
|
+
params.boards = list(params.boards)
|
71
|
+
if params.boards:
|
72
|
+
pass
|
73
|
+
# TODO Clean board - same as in cli_flash.py
|
74
|
+
else:
|
75
|
+
# no boards specified - detect connected boards
|
77
76
|
params.ports, params.boards = connected_ports_boards()
|
78
|
-
|
77
|
+
|
79
78
|
params = ask_missing_params(params, action="download")
|
79
|
+
if not params: # Cancelled by user
|
80
|
+
exit(1)
|
81
|
+
params.versions = [clean_version(v, drop_v=True) for v in params.versions]
|
80
82
|
assert isinstance(params, DownloadParams)
|
81
83
|
|
82
|
-
params.versions = [clean_version(v, drop_v=True) for v in params.versions] # remove leading v from version
|
83
|
-
|
84
84
|
download(
|
85
85
|
params.fw_folder,
|
86
86
|
params.ports,
|
@@ -89,3 +89,19 @@ def cli_download(
|
|
89
89
|
params.force,
|
90
90
|
params.clean,
|
91
91
|
)
|
92
|
+
|
93
|
+
|
94
|
+
def connected_ports_boards() -> Tuple[List[str], List[str]]:
|
95
|
+
"""
|
96
|
+
Returns a tuple containing lists of unique ports and boards from the connected MCUs.
|
97
|
+
Boards that are physically connected, but give no tangible response are ignored.
|
98
|
+
|
99
|
+
Returns:
|
100
|
+
A tuple containing two lists:
|
101
|
+
- A list of unique ports where MCUs are connected.
|
102
|
+
- A list of unique board names of the connected MCUs.
|
103
|
+
"""
|
104
|
+
mpr_boards = [b for b in list_mcus() if b.connected]
|
105
|
+
ports = list({b.port for b in mpr_boards})
|
106
|
+
boards = list({b.board for b in mpr_boards})
|
107
|
+
return ports, boards
|
@@ -0,0 +1,165 @@
|
|
1
|
+
from pathlib import Path
|
2
|
+
|
3
|
+
import rich_click as click
|
4
|
+
from loguru import logger as log
|
5
|
+
|
6
|
+
from mpflash.errors import MPFlashError
|
7
|
+
from mpflash.mpboard_id import find_stored_board
|
8
|
+
from mpflash.vendor.versions import clean_version
|
9
|
+
|
10
|
+
from .ask_input import FlashParams, ask_missing_params
|
11
|
+
from .cli_download import connected_ports_boards
|
12
|
+
from .cli_group import cli
|
13
|
+
from .cli_list import show_mcus
|
14
|
+
from .config import config
|
15
|
+
from .flash import flash_list
|
16
|
+
from .worklist import WorkList, full_auto_worklist, manual_worklist, single_auto_worklist
|
17
|
+
|
18
|
+
# #########################################################################################################
|
19
|
+
# CLI
|
20
|
+
# #########################################################################################################
|
21
|
+
|
22
|
+
|
23
|
+
@cli.command(
|
24
|
+
"flash",
|
25
|
+
short_help="Flash one or all connected MicroPython boards with a specific firmware and version.",
|
26
|
+
)
|
27
|
+
@click.option(
|
28
|
+
"--firmware",
|
29
|
+
"-f",
|
30
|
+
"fw_folder",
|
31
|
+
type=click.Path(exists=True, file_okay=False, dir_okay=True, path_type=Path),
|
32
|
+
default=config.firmware_folder,
|
33
|
+
show_default=True,
|
34
|
+
help="The folder to retrieve the firmware from.",
|
35
|
+
)
|
36
|
+
@click.option(
|
37
|
+
"--version",
|
38
|
+
"-v",
|
39
|
+
"version", # single version
|
40
|
+
default="stable",
|
41
|
+
multiple=False,
|
42
|
+
show_default=True,
|
43
|
+
help="The version of MicroPython to flash.",
|
44
|
+
metavar="SEMVER, 'stable', 'preview' or '?'",
|
45
|
+
)
|
46
|
+
@click.option(
|
47
|
+
"--serial",
|
48
|
+
"--serial-port",
|
49
|
+
"-s",
|
50
|
+
"serial",
|
51
|
+
default="auto",
|
52
|
+
show_default=True,
|
53
|
+
help="Which serial port(s) to flash",
|
54
|
+
metavar="SERIAL_PORT",
|
55
|
+
)
|
56
|
+
@click.option(
|
57
|
+
"--port",
|
58
|
+
"-p",
|
59
|
+
"ports",
|
60
|
+
help="The MicroPython port to flash",
|
61
|
+
metavar="PORT",
|
62
|
+
default=[],
|
63
|
+
multiple=True,
|
64
|
+
)
|
65
|
+
@click.option(
|
66
|
+
"--board",
|
67
|
+
"-b",
|
68
|
+
"board", # single board
|
69
|
+
multiple=False,
|
70
|
+
help="The MicroPython board ID to flash. If not specified will try to read the BOARD_ID from the connected MCU.",
|
71
|
+
metavar="BOARD_ID or ?",
|
72
|
+
)
|
73
|
+
@click.option(
|
74
|
+
"--cpu",
|
75
|
+
"--chip",
|
76
|
+
"-c",
|
77
|
+
"cpu",
|
78
|
+
help="The CPU type to flash. If not specified will try to read the CPU from the connected MCU.",
|
79
|
+
metavar="CPU",
|
80
|
+
)
|
81
|
+
@click.option(
|
82
|
+
"--erase/--no-erase",
|
83
|
+
default=True,
|
84
|
+
show_default=True,
|
85
|
+
help="""Erase flash before writing new firmware. (Not supported on UF2 boards)""",
|
86
|
+
)
|
87
|
+
@click.option(
|
88
|
+
"--bootloader/--no-bootloader",
|
89
|
+
default=True,
|
90
|
+
is_flag=True,
|
91
|
+
show_default=True,
|
92
|
+
help="""Enter micropython bootloader mode before flashing.""",
|
93
|
+
)
|
94
|
+
def cli_flash_board(**kwargs):
|
95
|
+
# version to versions, board to boards
|
96
|
+
kwargs["versions"] = [kwargs.pop("version")] if kwargs["version"] != None else []
|
97
|
+
if kwargs["board"] is None:
|
98
|
+
kwargs["boards"] = []
|
99
|
+
kwargs.pop("board")
|
100
|
+
else:
|
101
|
+
kwargs["boards"] = [kwargs.pop("board")]
|
102
|
+
|
103
|
+
params = FlashParams(**kwargs)
|
104
|
+
if not params.boards or params.boards == []:
|
105
|
+
# nothing specified - detect connected boards
|
106
|
+
params.ports, params.boards = connected_ports_boards()
|
107
|
+
else:
|
108
|
+
for board_id in params.boards:
|
109
|
+
if board_id == "":
|
110
|
+
params.boards.remove(board_id)
|
111
|
+
continue
|
112
|
+
if " " in board_id:
|
113
|
+
try:
|
114
|
+
info = find_stored_board(board_id)
|
115
|
+
if info:
|
116
|
+
log.info(f"Resolved board description: {info['board']}")
|
117
|
+
params.boards.remove(board_id)
|
118
|
+
params.boards.append(info["board"])
|
119
|
+
except Exception as e:
|
120
|
+
log.warning(f"unable to resolve board description: {e}")
|
121
|
+
|
122
|
+
# Ask for missing input if needed
|
123
|
+
params = ask_missing_params(params, action="flash")
|
124
|
+
if not params: # Cancelled by user
|
125
|
+
exit(1)
|
126
|
+
# TODO: Just in time Download of firmware
|
127
|
+
|
128
|
+
assert isinstance(params, FlashParams)
|
129
|
+
|
130
|
+
if len(params.versions) > 1:
|
131
|
+
log.error(f"Only one version can be flashed at a time, not {params.versions}")
|
132
|
+
raise MPFlashError("Only one version can be flashed at a time")
|
133
|
+
if len(params.boards) > 1:
|
134
|
+
log.error(f"Only one board can be flashed at a time, not {params.boards}")
|
135
|
+
raise MPFlashError("Only one board can be flashed at a time")
|
136
|
+
|
137
|
+
params.versions = [clean_version(v) for v in params.versions]
|
138
|
+
worklist: WorkList = []
|
139
|
+
# if serial port == auto and there are one or more specified/detected boards
|
140
|
+
if params.serial == "auto" and params.boards:
|
141
|
+
worklist = full_auto_worklist(version=params.versions[0], fw_folder=params.fw_folder)
|
142
|
+
elif params.versions[0] and params.boards[0] and params.serial:
|
143
|
+
# A single serial port including the board / variant
|
144
|
+
worklist = manual_worklist(
|
145
|
+
params.versions[0],
|
146
|
+
params.fw_folder,
|
147
|
+
params.serial,
|
148
|
+
params.boards[0],
|
149
|
+
)
|
150
|
+
else:
|
151
|
+
# just this serial port on auto
|
152
|
+
worklist = single_auto_worklist(
|
153
|
+
serial_port=params.serial,
|
154
|
+
version=params.versions[0],
|
155
|
+
fw_folder=params.fw_folder,
|
156
|
+
)
|
157
|
+
|
158
|
+
if flashed := flash_list(
|
159
|
+
worklist,
|
160
|
+
params.fw_folder,
|
161
|
+
params.erase,
|
162
|
+
params.bootloader,
|
163
|
+
):
|
164
|
+
log.info(f"Flashed {len(flashed)} boards")
|
165
|
+
show_mcus(flashed, title="Updated boards after flashing")
|
@@ -0,0 +1,41 @@
|
|
1
|
+
import json
|
2
|
+
|
3
|
+
import rich_click as click
|
4
|
+
from rich import print
|
5
|
+
|
6
|
+
from .cli_group import cli
|
7
|
+
from .list import list_mcus, show_mcus
|
8
|
+
from .logger import make_quiet
|
9
|
+
|
10
|
+
|
11
|
+
@cli.command("list", help="List the connected MCU boards.")
|
12
|
+
@click.option(
|
13
|
+
"--json",
|
14
|
+
"-j",
|
15
|
+
"as_json",
|
16
|
+
is_flag=True,
|
17
|
+
default=False,
|
18
|
+
show_default=True,
|
19
|
+
help="""Output in json format""",
|
20
|
+
)
|
21
|
+
@click.option(
|
22
|
+
"--progress/--no-progress",
|
23
|
+
"progress",
|
24
|
+
is_flag=True,
|
25
|
+
default=True,
|
26
|
+
show_default=True,
|
27
|
+
help="""Show progress""",
|
28
|
+
)
|
29
|
+
def cli_list_mcus(as_json: bool, progress: bool = True):
|
30
|
+
"""List the connected MCU boards, and output in a nice table or json."""
|
31
|
+
if as_json:
|
32
|
+
# avoid noise in json output
|
33
|
+
make_quiet()
|
34
|
+
|
35
|
+
conn_mcus = list_mcus()
|
36
|
+
if as_json:
|
37
|
+
print(json.dumps([mcu.__dict__ for mcu in conn_mcus], indent=4))
|
38
|
+
progress = False
|
39
|
+
if progress:
|
40
|
+
show_mcus(conn_mcus, refresh=False)
|
41
|
+
return conn_mcus
|
@@ -15,8 +15,12 @@ def mpflash():
|
|
15
15
|
cli.add_command(cli_list_mcus)
|
16
16
|
cli.add_command(cli_download)
|
17
17
|
# cli(auto_envvar_prefix="MPFLASH")
|
18
|
-
|
18
|
+
try:
|
19
|
+
exit(cli())
|
20
|
+
except AttributeError as e:
|
21
|
+
print(f"Error: {e}")
|
22
|
+
exit(-1)
|
19
23
|
|
20
24
|
|
21
|
-
|
22
|
-
mpflash()
|
25
|
+
if __name__ == "__main__":
|
26
|
+
mpflash()
|
@@ -0,0 +1,38 @@
|
|
1
|
+
import os
|
2
|
+
import time
|
3
|
+
from typing import TypedDict
|
4
|
+
|
5
|
+
from github import Auth, Github
|
6
|
+
from rich.progress import track
|
7
|
+
|
8
|
+
from mpflash.errors import MPFlashError
|
9
|
+
# from mpflash.mpremoteboard import MPRemoteBoard
|
10
|
+
|
11
|
+
PORT_FWTYPES = {
|
12
|
+
"stm32": [".dfu"], # need .dfu for pydfu.py - .hex for cube cli/GUI
|
13
|
+
"esp32": [".bin"],
|
14
|
+
"esp8266": [".bin"],
|
15
|
+
"rp2": [".uf2"],
|
16
|
+
"samd": [".uf2"],
|
17
|
+
"mimxrt": [".hex"],
|
18
|
+
"nrf": [".uf2"],
|
19
|
+
"renesas-ra": [".hex"],
|
20
|
+
}
|
21
|
+
|
22
|
+
# Token with no permissions to avoid throttling
|
23
|
+
# 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
|
24
|
+
PAT_NO_ACCESS = (
|
25
|
+
"github_pat" + "_11AAHPVFQ0qAkDnSUaMKSp" + "_ZkDl5NRRwBsUN6EYg9ahp1Dvj4FDDONnXVgimxC2EtpY7Q7BUKBoQ0Jq72X"
|
26
|
+
)
|
27
|
+
PAT = os.environ.get("GITHUB_TOKEN") or PAT_NO_ACCESS
|
28
|
+
GH_CLIENT = Github(auth=Auth.Token(PAT))
|
29
|
+
|
30
|
+
|
31
|
+
class FWInfo(TypedDict):
|
32
|
+
filename: str
|
33
|
+
port: str
|
34
|
+
board: str
|
35
|
+
variant: str
|
36
|
+
preview: bool
|
37
|
+
version: str
|
38
|
+
build: str
|
@@ -46,6 +46,9 @@ def get_board_urls(page_url: str) -> List[Dict[str, str]]:
|
|
46
46
|
"""
|
47
47
|
Get the urls to all the board pages listed on this page.
|
48
48
|
Assumes that all links to firmware have "class": "board-card"
|
49
|
+
|
50
|
+
Args:
|
51
|
+
page_url (str): The url of the page to get the board urls from.
|
49
52
|
"""
|
50
53
|
downloads_html = get_page(page_url)
|
51
54
|
soup = BeautifulSoup(downloads_html, "html.parser")
|
@@ -57,8 +60,17 @@ def get_board_urls(page_url: str) -> List[Dict[str, str]]:
|
|
57
60
|
return [{"board": board, "url": page_url + board} for board in boards]
|
58
61
|
|
59
62
|
|
60
|
-
def
|
61
|
-
"""
|
63
|
+
def board_firmware_urls(board_url: str, base_url: str, ext: str) -> List[str]:
|
64
|
+
"""
|
65
|
+
Get the urls to all the firmware files for a board.
|
66
|
+
Args:
|
67
|
+
page_url (str): The url of the page to get the board urls from.
|
68
|
+
??? base_url (str): The base url to join the relative urls to.
|
69
|
+
ext (str): The extension of the firmware files to get. (with or withouth leading .)
|
70
|
+
|
71
|
+
the urls are relative urls to the site root
|
72
|
+
|
73
|
+
"""
|
62
74
|
html = get_page(board_url)
|
63
75
|
soup = BeautifulSoup(html, "html.parser")
|
64
76
|
# get all the a tags:
|
@@ -109,7 +121,7 @@ def get_boards(ports: List[str], boards: List[str], clean: bool) -> List[Firmwar
|
|
109
121
|
# add a board to the list for each firmware found
|
110
122
|
firmwares = []
|
111
123
|
for ext in PORT_FWTYPES[port]:
|
112
|
-
firmwares +=
|
124
|
+
firmwares += board_firmware_urls(board["url"], MICROPYTHON_ORG_URL, ext)
|
113
125
|
|
114
126
|
for _url in firmwares:
|
115
127
|
board["firmware"] = _url
|