mpflash 1.25.0.post1__py3-none-any.whl → 1.25.0.post2__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 +43 -16
- mpflash/ask_input.py +4 -4
- mpflash/basicgit.py +1 -1
- mpflash/bootloader/manual.py +1 -1
- mpflash/cli_download.py +8 -5
- mpflash/cli_flash.py +31 -35
- mpflash/cli_group.py +3 -0
- mpflash/cli_list.py +8 -3
- mpflash/cli_main.py +4 -0
- mpflash/common.py +2 -38
- mpflash/config.py +21 -0
- mpflash/db/__init__.py +2 -0
- mpflash/db/core.py +61 -0
- mpflash/db/gather_boards.py +112 -0
- mpflash/db/loader.py +122 -0
- mpflash/db/meta.py +78 -0
- mpflash/db/micropython_boards.zip +0 -0
- mpflash/db/models.py +93 -0
- mpflash/db/tools.py +27 -0
- mpflash/download/__init__.py +46 -64
- mpflash/download/from_web.py +26 -36
- mpflash/download/fwinfo.py +41 -0
- mpflash/download/jid.py +56 -0
- mpflash/downloaded.py +79 -93
- mpflash/flash/__init__.py +7 -3
- mpflash/flash/esp.py +2 -1
- mpflash/flash/stm32.py +1 -1
- mpflash/flash/uf2/windows.py +3 -1
- mpflash/flash/worklist.py +16 -28
- mpflash/list.py +3 -3
- mpflash/logger.py +43 -9
- mpflash/mpboard_id/__init__.py +3 -9
- mpflash/mpboard_id/alternate.py +56 -0
- mpflash/mpboard_id/board_id.py +11 -94
- mpflash/mpboard_id/known.py +45 -57
- mpflash/mpboard_id/resolve.py +19 -0
- mpflash/mpremoteboard/__init__.py +4 -3
- mpflash/mpremoteboard/mpy_fw_info.py +1 -0
- mpflash/mpremoteboard/runner.py +5 -2
- mpflash/vendor/pydfu.py +33 -6
- mpflash/versions.py +3 -0
- {mpflash-1.25.0.post1.dist-info → mpflash-1.25.0.post2.dist-info}/METADATA +49 -12
- mpflash-1.25.0.post2.dist-info/RECORD +69 -0
- mpflash/db/boards.py +0 -63
- mpflash/db/downloads.py +0 -87
- mpflash/mpboard_id/add_boards.py +0 -260
- mpflash/mpboard_id/board.py +0 -40
- mpflash/mpboard_id/store.py +0 -47
- mpflash-1.25.0.post1.dist-info/RECORD +0 -62
- {mpflash-1.25.0.post1.dist-info → mpflash-1.25.0.post2.dist-info}/LICENSE +0 -0
- {mpflash-1.25.0.post1.dist-info → mpflash-1.25.0.post2.dist-info}/WHEEL +0 -0
- {mpflash-1.25.0.post1.dist-info → mpflash-1.25.0.post2.dist-info}/entry_points.txt +0 -0
mpflash/add_firmware.py
CHANGED
@@ -5,18 +5,29 @@ from typing import Union
|
|
5
5
|
import jsonlines
|
6
6
|
import requests
|
7
7
|
from loguru import logger as log
|
8
|
-
|
9
8
|
# re-use logic from mpremote
|
10
9
|
from mpremote.mip import _rewrite_url as rewrite_url # type: ignore
|
10
|
+
from pytest import Session
|
11
11
|
|
12
|
-
from mpflash.common import FWInfo
|
13
12
|
from mpflash.config import config
|
13
|
+
from mpflash.db.core import Session
|
14
|
+
from mpflash.db.models import Firmware
|
14
15
|
from mpflash.versions import get_preview_mp_version, get_stable_mp_version
|
15
16
|
|
17
|
+
# github.com/<owner>/<repo>@<branch>#<commit>
|
18
|
+
# $remote_url = git remote get-url origin
|
19
|
+
# $branch = git rev-parse --abbrev-ref HEAD
|
20
|
+
# $commit = git rev-parse --short HEAD
|
21
|
+
# if ($remote_url -match "github.com[:/](.+)/(.+?)(\.git)?$") {
|
22
|
+
# $owner = $matches[1]
|
23
|
+
# $repo = $matches[2]
|
24
|
+
# "github.com/$owner/$repo@$branch#$commit"
|
25
|
+
# }
|
26
|
+
|
16
27
|
|
17
28
|
def add_firmware(
|
18
29
|
source: Union[Path, str],
|
19
|
-
new_fw:
|
30
|
+
new_fw: Firmware,
|
20
31
|
*,
|
21
32
|
force: bool = False,
|
22
33
|
custom: bool = False,
|
@@ -25,11 +36,11 @@ def add_firmware(
|
|
25
36
|
"""Add a firmware to the firmware folder.
|
26
37
|
|
27
38
|
stored in the port folder, with the same filename as the source.
|
28
|
-
|
29
39
|
"""
|
30
40
|
# Check minimal info needed
|
31
|
-
|
32
|
-
|
41
|
+
|
42
|
+
if not new_fw.board_id or not new_fw.board or not new_fw.port:
|
43
|
+
log.error("board_id, board and port are required")
|
33
44
|
return False
|
34
45
|
if not isinstance(source, Path) and not source.startswith("http"):
|
35
46
|
log.error(f"Invalid source {source}")
|
@@ -37,31 +48,47 @@ def add_firmware(
|
|
37
48
|
|
38
49
|
# use sensible defaults
|
39
50
|
source_2 = Path(source)
|
40
|
-
new_fw.
|
41
|
-
new_fw.variant = new_fw.variant or new_fw.board
|
51
|
+
# new_fw.variant = new_fw.variant or new_fw.board
|
42
52
|
new_fw.custom = new_fw.custom or custom
|
43
|
-
new_fw.description = new_fw.description or description
|
44
53
|
if not new_fw.version:
|
45
54
|
# TODO: Get version from filename
|
46
55
|
# or use the last preview version
|
47
|
-
new_fw.version = get_preview_mp_version()
|
56
|
+
new_fw.version = get_preview_mp_version()
|
48
57
|
|
49
58
|
config.firmware_folder.mkdir(exist_ok=True)
|
50
59
|
|
51
60
|
fw_filename = config.firmware_folder / new_fw.port / source_2.name
|
52
61
|
|
53
|
-
new_fw.
|
54
|
-
new_fw.
|
62
|
+
new_fw.firmware_file = str(fw_filename.relative_to(config.firmware_folder))
|
63
|
+
new_fw.source = source.as_uri() if isinstance(source, Path) else source
|
55
64
|
|
56
65
|
if not copy_firmware(source, fw_filename, force):
|
57
66
|
log.error(f"Failed to copy {source} to {fw_filename}")
|
58
67
|
return False
|
59
68
|
# add to inventory
|
60
|
-
with
|
61
|
-
|
62
|
-
|
69
|
+
with Session() as session:
|
70
|
+
# check if the firmware already exists
|
71
|
+
existing_fw = (
|
72
|
+
session.query(Firmware)
|
73
|
+
.filter(
|
74
|
+
Firmware.board_id == new_fw.board_id,
|
75
|
+
Firmware.version == new_fw.version,
|
76
|
+
Firmware.port == new_fw.port,
|
77
|
+
)
|
78
|
+
.first()
|
79
|
+
)
|
80
|
+
if existing_fw:
|
81
|
+
log.warning(f"Firmware {existing_fw} already exists")
|
82
|
+
if not force:
|
83
|
+
return False
|
84
|
+
# update the existing firmware
|
85
|
+
existing_fw.firmware_file = new_fw.firmware_file
|
86
|
+
existing_fw.source = new_fw.source
|
87
|
+
existing_fw.custom = custom
|
88
|
+
existing_fw.description = description
|
89
|
+
else:
|
90
|
+
session.add(new_fw)
|
63
91
|
|
64
|
-
writer.write(new_fw.to_dict())
|
65
92
|
return True
|
66
93
|
|
67
94
|
|
mpflash/ask_input.py
CHANGED
@@ -11,7 +11,7 @@ from loguru import logger as log
|
|
11
11
|
|
12
12
|
from .common import DownloadParams, FlashParams, ParamType
|
13
13
|
from .config import config
|
14
|
-
from .mpboard_id import get_known_boards_for_port,
|
14
|
+
from .mpboard_id import get_known_boards_for_port, known_ports, known_stored_boards
|
15
15
|
from .mpremoteboard import MPRemoteBoard
|
16
16
|
from .versions import micropython_versions
|
17
17
|
|
@@ -106,7 +106,7 @@ def filter_matching_boards(answers: dict) -> Sequence[Tuple[str, str]]:
|
|
106
106
|
Returns:
|
107
107
|
Sequence[Tuple[str, str]]: The filtered boards.
|
108
108
|
"""
|
109
|
-
versions =
|
109
|
+
versions = []
|
110
110
|
# if version is not asked ; then need to get the version from the inputs
|
111
111
|
if "versions" in answers:
|
112
112
|
versions = list(answers["versions"])
|
@@ -151,7 +151,7 @@ def ask_port_board(*, multi_select: bool, action: str):
|
|
151
151
|
inquirer.List(
|
152
152
|
"port",
|
153
153
|
message="Which port do you want to {action} " + "to {serial} ?" if action == "flash" else "?",
|
154
|
-
choices=
|
154
|
+
choices=known_ports(),
|
155
155
|
# autocomplete=True,
|
156
156
|
),
|
157
157
|
inquirer_ux(
|
@@ -226,7 +226,7 @@ def ask_serialport(*, multi_select: bool = False, bluetooth: bool = False):
|
|
226
226
|
# import only when needed to reduce load time
|
227
227
|
import inquirer
|
228
228
|
|
229
|
-
comports = MPRemoteBoard.connected_boards(bluetooth=bluetooth, description=True)
|
229
|
+
comports = MPRemoteBoard.connected_boards(bluetooth=bluetooth, description=True) + ["auto"]
|
230
230
|
return inquirer.List(
|
231
231
|
"serial",
|
232
232
|
message="Which serial port do you want to {action} ?",
|
mpflash/basicgit.py
CHANGED
@@ -24,6 +24,7 @@ from mpflash.config import config
|
|
24
24
|
|
25
25
|
# GH_CLIENT = None
|
26
26
|
|
27
|
+
|
27
28
|
def _run_local_git(
|
28
29
|
cmd: List[str],
|
29
30
|
repo: Optional[Union[Path, str]] = None,
|
@@ -180,7 +181,6 @@ def checkout_tag(tag: str, repo: Optional[Union[str, Path]] = None) -> bool:
|
|
180
181
|
return True
|
181
182
|
|
182
183
|
|
183
|
-
|
184
184
|
def checkout_commit(commit_hash: str, repo: Optional[Union[Path, str]] = None) -> bool:
|
185
185
|
"""
|
186
186
|
Checkout a specific commit
|
mpflash/bootloader/manual.py
CHANGED
@@ -55,7 +55,7 @@ def enter_bootloader_manual(mcu: MPRemoteBoard, timeout: int = 10):
|
|
55
55
|
message: str
|
56
56
|
if mcu.port == "rp2":
|
57
57
|
message = f"""\
|
58
|
-
Please put your {" ".join([mcu.port,mcu.board])} device into bootloader mode by either:
|
58
|
+
Please put your {" ".join([mcu.port, mcu.board])} device into bootloader mode by either:
|
59
59
|
Method 1:
|
60
60
|
1. Unplug the USB cable,
|
61
61
|
2. Press and hold the BOOTSEL button on the device,
|
mpflash/cli_download.py
CHANGED
@@ -6,8 +6,10 @@ import rich_click as click
|
|
6
6
|
from loguru import logger as log
|
7
7
|
|
8
8
|
from mpflash.connected import connected_ports_boards
|
9
|
+
from mpflash.downloaded import clean_downloaded_firmwares
|
9
10
|
from mpflash.errors import MPFlashError
|
10
11
|
from mpflash.mpboard_id import find_known_board
|
12
|
+
from mpflash.mpboard_id.alternate import add_renamed_boards
|
11
13
|
from mpflash.versions import clean_version
|
12
14
|
|
13
15
|
from .ask_input import ask_missing_params
|
@@ -26,8 +28,8 @@ from .download import download
|
|
26
28
|
"-d",
|
27
29
|
"fw_folder",
|
28
30
|
type=click.Path(file_okay=False, dir_okay=True, path_type=Path),
|
29
|
-
default=
|
30
|
-
show_default=
|
31
|
+
default=None,
|
32
|
+
show_default=False,
|
31
33
|
help="The folder to download the firmware to.",
|
32
34
|
)
|
33
35
|
@click.option(
|
@@ -92,7 +94,8 @@ def cli_download(**kwargs) -> int:
|
|
92
94
|
params.boards = list(params.boards)
|
93
95
|
params.serial = list(params.serial)
|
94
96
|
params.ignore = list(params.ignore)
|
95
|
-
|
97
|
+
if params.fw_folder:
|
98
|
+
config.firmware_folder = Path(params.fw_folder)
|
96
99
|
# all_boards: List[MPRemoteBoard] = []
|
97
100
|
if params.boards:
|
98
101
|
if not params.ports:
|
@@ -116,13 +119,13 @@ def cli_download(**kwargs) -> int:
|
|
116
119
|
|
117
120
|
try:
|
118
121
|
download(
|
119
|
-
params.fw_folder,
|
120
122
|
params.ports,
|
121
|
-
params.boards,
|
123
|
+
add_renamed_boards(params.boards),
|
122
124
|
params.versions,
|
123
125
|
params.force,
|
124
126
|
params.clean,
|
125
127
|
)
|
128
|
+
clean_downloaded_firmwares()
|
126
129
|
return 0
|
127
130
|
except MPFlashError as e:
|
128
131
|
log.error(f"{e}")
|
mpflash/cli_flash.py
CHANGED
@@ -4,6 +4,8 @@ from typing import List
|
|
4
4
|
import rich_click as click
|
5
5
|
from loguru import logger as log
|
6
6
|
|
7
|
+
import mpflash.download.jid as jid
|
8
|
+
import mpflash.mpboard_id as mpboard_id
|
7
9
|
from mpflash.ask_input import ask_missing_params
|
8
10
|
from mpflash.cli_download import connected_ports_boards
|
9
11
|
from mpflash.cli_group import cli
|
@@ -13,7 +15,6 @@ from mpflash.config import config
|
|
13
15
|
from mpflash.errors import MPFlashError
|
14
16
|
from mpflash.flash import flash_list
|
15
17
|
from mpflash.flash.worklist import WorkList, full_auto_worklist, manual_worklist, single_auto_worklist
|
16
|
-
from mpflash.mpboard_id import find_known_board
|
17
18
|
from mpflash.mpremoteboard import MPRemoteBoard
|
18
19
|
from mpflash.versions import clean_version
|
19
20
|
|
@@ -28,11 +29,11 @@ from mpflash.versions import clean_version
|
|
28
29
|
)
|
29
30
|
@click.option(
|
30
31
|
"--firmware",
|
31
|
-
"
|
32
|
+
"--ff",
|
32
33
|
"fw_folder",
|
33
34
|
type=click.Path(file_okay=False, dir_okay=True, path_type=Path),
|
34
|
-
default=
|
35
|
-
show_default=
|
35
|
+
default=None,
|
36
|
+
show_default=False,
|
36
37
|
help="The folder to retrieve the firmware from.",
|
37
38
|
)
|
38
39
|
@click.option(
|
@@ -69,7 +70,7 @@ from mpflash.versions import clean_version
|
|
69
70
|
)
|
70
71
|
@click.option(
|
71
72
|
"--bluetooth/--no-bluetooth",
|
72
|
-
"-
|
73
|
+
"--bt/--no-bt",
|
73
74
|
is_flag=True,
|
74
75
|
default=False,
|
75
76
|
show_default=True,
|
@@ -94,7 +95,7 @@ from mpflash.versions import clean_version
|
|
94
95
|
)
|
95
96
|
@click.option(
|
96
97
|
"--variant",
|
97
|
-
"
|
98
|
+
"--var",
|
98
99
|
"variant", # single board
|
99
100
|
multiple=False,
|
100
101
|
help="The board VARIANT to flash or '-'. If not specified will try to read the variant from the connected MCU.",
|
@@ -116,7 +117,7 @@ from mpflash.versions import clean_version
|
|
116
117
|
)
|
117
118
|
@click.option(
|
118
119
|
"--bootloader",
|
119
|
-
"
|
120
|
+
"--bl",
|
120
121
|
"bootloader",
|
121
122
|
type=click.Choice([e.value for e in BootloaderMethod]),
|
122
123
|
default="auto",
|
@@ -124,7 +125,16 @@ from mpflash.versions import clean_version
|
|
124
125
|
help="""How to enter the (MicroPython) bootloader before flashing.""",
|
125
126
|
)
|
126
127
|
@click.option(
|
127
|
-
"--
|
128
|
+
"--force",
|
129
|
+
"-f",
|
130
|
+
default=False,
|
131
|
+
is_flag=True,
|
132
|
+
show_default=True,
|
133
|
+
help="""Force download of firmware even if it already exists.""",
|
134
|
+
)
|
135
|
+
@click.option(
|
136
|
+
"--flash_mode",
|
137
|
+
"--fm",
|
128
138
|
type=click.Choice(["keep", "qio", "qout", "dio", "dout"]),
|
129
139
|
default="keep",
|
130
140
|
show_default=True,
|
@@ -137,7 +147,7 @@ def cli_flash_board(**kwargs) -> int:
|
|
137
147
|
kwargs["boards"] = []
|
138
148
|
kwargs.pop("board")
|
139
149
|
else:
|
140
|
-
kwargs["boards"] = [kwargs.pop("board")]
|
150
|
+
kwargs["boards"] = [kwargs.pop("board")]
|
141
151
|
|
142
152
|
params = FlashParams(**kwargs)
|
143
153
|
params.versions = list(params.versions)
|
@@ -148,9 +158,14 @@ def cli_flash_board(**kwargs) -> int:
|
|
148
158
|
params.bootloader = BootloaderMethod(params.bootloader)
|
149
159
|
|
150
160
|
# make it simple for the user to flash one board by asking for the serial port if not specified
|
151
|
-
if params.boards == ["?"]
|
161
|
+
if params.boards == ["?"] or params.serial == "?":
|
152
162
|
params.serial = ["?"]
|
163
|
+
if params.boards == ["*"]:
|
164
|
+
# No bard specified
|
165
|
+
params.boards = ["?"]
|
153
166
|
|
167
|
+
if params.fw_folder:
|
168
|
+
config.firmware_folder = Path(params.fw_folder)
|
154
169
|
# Detect connected boards if not specified,
|
155
170
|
# and ask for input if boards cannot be detected
|
156
171
|
all_boards: List[MPRemoteBoard] = []
|
@@ -167,14 +182,12 @@ def cli_flash_board(**kwargs) -> int:
|
|
167
182
|
# assume manual mode if no board is detected
|
168
183
|
params.bootloader = BootloaderMethod("manual")
|
169
184
|
else:
|
170
|
-
resolve_board_ids(params)
|
185
|
+
mpboard_id.resolve_board_ids(params)
|
171
186
|
|
172
187
|
# Ask for missing input if needed
|
173
188
|
params = ask_missing_params(params)
|
174
189
|
if not params: # Cancelled by user
|
175
190
|
return 2
|
176
|
-
# TODO: Just in time Download of firmware
|
177
|
-
|
178
191
|
assert isinstance(params, FlashParams)
|
179
192
|
|
180
193
|
if len(params.versions) > 1:
|
@@ -183,6 +196,7 @@ def cli_flash_board(**kwargs) -> int:
|
|
183
196
|
|
184
197
|
params.versions = [clean_version(v) for v in params.versions]
|
185
198
|
worklist: WorkList = []
|
199
|
+
|
186
200
|
# if serial port == auto and there are one or more specified/detected boards
|
187
201
|
if params.serial == ["*"] and params.boards:
|
188
202
|
if not all_boards:
|
@@ -191,37 +205,33 @@ def cli_flash_board(**kwargs) -> int:
|
|
191
205
|
# if variant id provided on the cmdline, treat is as an override
|
192
206
|
if params.variant:
|
193
207
|
for b in all_boards:
|
194
|
-
b.variant = params.variant if (params.variant.lower() not in {"-","none"}) else ""
|
208
|
+
b.variant = params.variant if (params.variant.lower() not in {"-", "none"}) else ""
|
195
209
|
|
196
210
|
worklist = full_auto_worklist(
|
197
211
|
all_boards=all_boards,
|
198
212
|
version=params.versions[0],
|
199
|
-
fw_folder=params.fw_folder,
|
200
213
|
include=params.serial,
|
201
214
|
ignore=params.ignore,
|
202
215
|
)
|
203
216
|
elif params.versions[0] and params.boards[0] and params.serial:
|
204
|
-
# A one or more
|
217
|
+
# A one or more serial port including the board / variant
|
205
218
|
worklist = manual_worklist(
|
206
219
|
params.serial[0],
|
207
220
|
board_id=params.boards[0],
|
208
221
|
version=params.versions[0],
|
209
|
-
fw_folder=params.fw_folder,
|
210
222
|
)
|
211
223
|
else:
|
212
224
|
# just this serial port on auto
|
213
225
|
worklist = single_auto_worklist(
|
214
226
|
serial=params.serial[0],
|
215
227
|
version=params.versions[0],
|
216
|
-
fw_folder=params.fw_folder,
|
217
228
|
)
|
218
|
-
|
229
|
+
jid.ensure_firmware_downloaded(worklist, version=params.versions[0], force=params.force)
|
219
230
|
if flashed := flash_list(
|
220
231
|
worklist,
|
221
|
-
params.fw_folder,
|
222
232
|
params.erase,
|
223
233
|
params.bootloader,
|
224
|
-
flash_mode
|
234
|
+
flash_mode=params.flash_mode,
|
225
235
|
):
|
226
236
|
log.info(f"Flashed {len(flashed)} boards")
|
227
237
|
show_mcus(flashed, title="Updated boards after flashing")
|
@@ -231,17 +241,3 @@ def cli_flash_board(**kwargs) -> int:
|
|
231
241
|
return 1
|
232
242
|
|
233
243
|
|
234
|
-
def resolve_board_ids(params: Params):
|
235
|
-
"""Resolve board descriptions to board_id, and remove empty strings from list of boards"""
|
236
|
-
for board_id in params.boards:
|
237
|
-
if board_id == "":
|
238
|
-
params.boards.remove(board_id)
|
239
|
-
continue
|
240
|
-
if " " in board_id:
|
241
|
-
try:
|
242
|
-
if info := find_known_board(board_id):
|
243
|
-
log.info(f"Resolved board description: {info.board_id}")
|
244
|
-
params.boards.remove(board_id)
|
245
|
-
params.boards.append(info.board_id)
|
246
|
-
except Exception as e:
|
247
|
-
log.warning(f"Unable to resolve board description: {e}")
|
mpflash/cli_group.py
CHANGED
@@ -10,6 +10,9 @@ from mpflash.vendor.click_aliases import ClickAliasedGroup
|
|
10
10
|
from .config import __version__, config
|
11
11
|
from .logger import log, make_quiet, set_loglevel
|
12
12
|
|
13
|
+
# default log level
|
14
|
+
set_loglevel("INFO")
|
15
|
+
config.verbose = False
|
13
16
|
|
14
17
|
def cb_verbose(ctx, param, value):
|
15
18
|
"""Callback to set the log level to DEBUG if verbose is set"""
|
mpflash/cli_list.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
import json
|
2
|
+
import time
|
2
3
|
from typing import List
|
3
4
|
|
4
5
|
import rich_click as click
|
@@ -48,7 +49,7 @@ from .logger import make_quiet
|
|
48
49
|
)
|
49
50
|
@click.option(
|
50
51
|
"--bluetooth/--no-bluetooth",
|
51
|
-
"-
|
52
|
+
"--bt/--no-bt",
|
52
53
|
is_flag=True,
|
53
54
|
default=False,
|
54
55
|
show_default=True,
|
@@ -77,10 +78,14 @@ def cli_list_mcus(serial: List[str], ignore: List[str], bluetooth: bool, as_json
|
|
77
78
|
conn_mcus = [item for item in conn_mcus if not (item.toml.get("mpflash", {}).get("ignore", False))]
|
78
79
|
if as_json:
|
79
80
|
print(json.dumps([mcu.to_dict() for mcu in conn_mcus], indent=4))
|
80
|
-
|
81
|
+
|
81
82
|
if progress:
|
82
83
|
show_mcus(conn_mcus, refresh=False)
|
83
84
|
for mcu in conn_mcus:
|
84
85
|
# reset the board so it can continue to whatever it was running before
|
85
|
-
mcu.
|
86
|
+
if mcu.family == "circuitpython":
|
87
|
+
# CircuitPython boards need a special reset command
|
88
|
+
mcu.run_command(["exec", "--no-follow", "import microcontroller,time;time.sleep(0.01);microcontroller.reset()"], resume=False)
|
89
|
+
else:
|
90
|
+
mcu.run_command("reset")
|
86
91
|
return 0 if conn_mcus else 1
|
mpflash/cli_main.py
CHANGED
@@ -9,9 +9,13 @@ from .cli_download import cli_download
|
|
9
9
|
from .cli_flash import cli_flash_board
|
10
10
|
from .cli_group import cli
|
11
11
|
from .cli_list import cli_list_mcus
|
12
|
+
from .db.core import migrate_database
|
12
13
|
|
13
14
|
|
14
15
|
def mpflash():
|
16
|
+
"""Main entry point for the mpflash CLI."""
|
17
|
+
migrate_database(boards=True, firmwares=True)
|
18
|
+
|
15
19
|
cli.add_command(cli_list_mcus)
|
16
20
|
cli.add_command(cli_download)
|
17
21
|
cli.add_command(cli_flash_board)
|
mpflash/common.py
CHANGED
@@ -10,7 +10,6 @@ from typing import List, Optional, Union
|
|
10
10
|
from serial.tools import list_ports
|
11
11
|
from serial.tools.list_ports_common import ListPortInfo
|
12
12
|
|
13
|
-
|
14
13
|
# from mpflash.flash.esp import FlashMode
|
15
14
|
from .logger import log
|
16
15
|
|
@@ -28,41 +27,6 @@ PORT_FWTYPES = {
|
|
28
27
|
|
29
28
|
UF2_PORTS = [port for port, exts in PORT_FWTYPES.items() if ".uf2" in exts]
|
30
29
|
|
31
|
-
@dataclass
|
32
|
-
class FWInfo:
|
33
|
-
"""
|
34
|
-
Downloaded Firmware information
|
35
|
-
is somewhat related to the BOARD class in the mpboard_id module
|
36
|
-
"""
|
37
|
-
|
38
|
-
port: str # MicroPython port
|
39
|
-
board: str # MicroPython board
|
40
|
-
filename: str = field(default="") # relative filename of the firmware image
|
41
|
-
firmware: str = field(default="") # url or path to original firmware image
|
42
|
-
variant: str = field(default="") # MicroPython variant
|
43
|
-
preview: bool = field(default=False) # True if the firmware is a preview version
|
44
|
-
version: str = field(default="") # MicroPython version (NO v prefix)
|
45
|
-
url: str = field(default="") # url to the firmware image download folder
|
46
|
-
build: str = field(default="0") # The build = number of commits since the last release
|
47
|
-
ext: str = field(default="") # the file extension of the firmware
|
48
|
-
family: str = field(default="micropython") # The family of the firmware
|
49
|
-
custom: bool = field(default=False) # True if the firmware is a custom build
|
50
|
-
description: str = field(default="") # Description used by this firmware (custom only)
|
51
|
-
|
52
|
-
def to_dict(self) -> dict:
|
53
|
-
"""Convert the object to a dictionary"""
|
54
|
-
return self.__dict__
|
55
|
-
|
56
|
-
@classmethod
|
57
|
-
def from_dict(cls, data: dict) -> "FWInfo":
|
58
|
-
"""Create a FWInfo object from a dictionary"""
|
59
|
-
# add missing keys
|
60
|
-
if "ext" not in data:
|
61
|
-
data["ext"] = Path(data["firmware"]).suffix
|
62
|
-
if "family" not in data:
|
63
|
-
data["family"] = "micropython"
|
64
|
-
return cls(**data)
|
65
|
-
|
66
30
|
|
67
31
|
@dataclass
|
68
32
|
class Params:
|
@@ -72,10 +36,11 @@ class Params:
|
|
72
36
|
boards: List[str] = field(default_factory=list)
|
73
37
|
variant: str = ""
|
74
38
|
versions: List[str] = field(default_factory=list)
|
75
|
-
fw_folder: Path =
|
39
|
+
fw_folder: Optional[Path] = None
|
76
40
|
serial: List[str] = field(default_factory=list)
|
77
41
|
ignore: List[str] = field(default_factory=list)
|
78
42
|
bluetooth: bool = False
|
43
|
+
force: bool = False
|
79
44
|
|
80
45
|
|
81
46
|
@dataclass
|
@@ -83,7 +48,6 @@ class DownloadParams(Params):
|
|
83
48
|
"""Parameters for downloading firmware"""
|
84
49
|
|
85
50
|
clean: bool = False
|
86
|
-
force: bool = False
|
87
51
|
|
88
52
|
|
89
53
|
class BootloaderMethod(Enum):
|
mpflash/config.py
CHANGED
@@ -31,6 +31,7 @@ class MPFlashConfig:
|
|
31
31
|
# No interactions in CI
|
32
32
|
if os.getenv("GITHUB_ACTIONS") == "true":
|
33
33
|
from mpflash.logger import log
|
34
|
+
|
34
35
|
log.warning("Disabling interactive mode in CI")
|
35
36
|
return False
|
36
37
|
return self._interactive
|
@@ -44,18 +45,38 @@ class MPFlashConfig:
|
|
44
45
|
"""The folder where firmware files are stored"""
|
45
46
|
if not self._firmware_folder:
|
46
47
|
self._firmware_folder = platformdirs.user_downloads_path() / "firmware"
|
48
|
+
# allow testing in CI
|
49
|
+
if Path(os.getenv("GITHUB_ACTIONS", "")).as_posix().lower() == "true":
|
50
|
+
workspace = os.getenv("GITHUB_WORKSPACE")
|
51
|
+
if workspace:
|
52
|
+
ws_path = Path(workspace) / "firmware"
|
53
|
+
ws_path.mkdir(parents=True, exist_ok=True)
|
54
|
+
print(f"Detected GitHub Actions environment. Using workspace path: {ws_path}")
|
55
|
+
self._firmware_folder = ws_path
|
47
56
|
return self._firmware_folder
|
48
57
|
|
58
|
+
@firmware_folder.setter
|
59
|
+
def firmware_folder(self, value: Path):
|
60
|
+
"""Set the firmware folder"""
|
61
|
+
if value.exists() and value.is_dir():
|
62
|
+
self._firmware_folder = value
|
63
|
+
else:
|
64
|
+
raise ValueError(f"Invalid firmware folder: {value}. It must be a valid directory.")
|
65
|
+
|
49
66
|
@property
|
50
67
|
def db_path(self) -> Path:
|
51
68
|
"""The path to the database file"""
|
52
69
|
return self.firmware_folder / "mpflash.db"
|
70
|
+
@property
|
71
|
+
def db_version(self) -> str:
|
72
|
+
return "1.24.1"
|
53
73
|
|
54
74
|
@property
|
55
75
|
def gh_client(self):
|
56
76
|
"""The gh client to use"""
|
57
77
|
if not self._gh_client:
|
58
78
|
from github import Auth, Github
|
79
|
+
|
59
80
|
# Token with no permissions to avoid throttling
|
60
81
|
# 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
|
61
82
|
PAT_NO_ACCESS = "github_pat_" + "11AAHPVFQ0G4NTaQ73Bw5J" + "_fAp7K9sZ1qL8VFnI9g78eUlCdmOXHB3WzSdj2jtEYb4XF3N7PDJBl32qIxq"
|
mpflash/db/__init__.py
ADDED
mpflash/db/core.py
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
from pathlib import Path
|
2
|
+
from sqlite3 import DatabaseError, OperationalError
|
3
|
+
|
4
|
+
from loguru import logger as log
|
5
|
+
from sqlalchemy import create_engine
|
6
|
+
from sqlalchemy.orm import sessionmaker
|
7
|
+
|
8
|
+
from mpflash.config import config
|
9
|
+
from mpflash.errors import MPFlashError
|
10
|
+
|
11
|
+
# TODO: lazy import to avoid slowdowns ?
|
12
|
+
from .models import Base
|
13
|
+
|
14
|
+
TRACE = False
|
15
|
+
connect_str = f"sqlite:///{config.db_path.as_posix()}"
|
16
|
+
engine = create_engine(connect_str, echo=TRACE)
|
17
|
+
Session = sessionmaker(bind=engine)
|
18
|
+
|
19
|
+
|
20
|
+
def migrate_database(boards: bool = True, firmwares: bool = True):
|
21
|
+
"""Migrate from 1.24.x to 1.25.x"""
|
22
|
+
# Move import here to avoid circular import
|
23
|
+
from .loader import load_jsonl_to_db, update_boards
|
24
|
+
|
25
|
+
# get the location of the database from the session
|
26
|
+
with Session() as session:
|
27
|
+
db_location = session.get_bind().url.database # type: ignore
|
28
|
+
log.debug(f"Database location: {Path(db_location)}") # type: ignore
|
29
|
+
|
30
|
+
try:
|
31
|
+
create_database()
|
32
|
+
except (DatabaseError, OperationalError) as e:
|
33
|
+
log.error(f"Error creating database: {e}")
|
34
|
+
log.error("Database might already exist, trying to migrate.")
|
35
|
+
raise MPFlashError("Database migration failed. Please check the logs for more details.") from e
|
36
|
+
if boards:
|
37
|
+
update_boards()
|
38
|
+
if firmwares:
|
39
|
+
jsonl_file = config.firmware_folder / "firmware.jsonl"
|
40
|
+
if jsonl_file.exists():
|
41
|
+
log.info(f"Migrating JSONL data {jsonl_file} to SQLite database.")
|
42
|
+
load_jsonl_to_db(jsonl_file)
|
43
|
+
# rename the jsonl file to jsonl.bak
|
44
|
+
log.info(f"Renaming {jsonl_file} to {jsonl_file.with_suffix('.jsonl.bak')}")
|
45
|
+
try:
|
46
|
+
jsonl_file.rename(jsonl_file.with_suffix(".jsonl.bak"))
|
47
|
+
except OSError as e:
|
48
|
+
for i in range(1, 10):
|
49
|
+
try:
|
50
|
+
jsonl_file.rename(jsonl_file.with_suffix(f".jsonl.{i}.bak"))
|
51
|
+
break
|
52
|
+
except OSError:
|
53
|
+
continue
|
54
|
+
|
55
|
+
|
56
|
+
def create_database():
|
57
|
+
"""
|
58
|
+
Create the SQLite database and tables if they don't exist.
|
59
|
+
"""
|
60
|
+
# Create the database and tables if they don't exist
|
61
|
+
Base.metadata.create_all(engine)
|