mpflash 1.25.0__py3-none-any.whl → 1.25.0rc1__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 -3
- mpflash/basicgit.py +2 -2
- mpflash/bootloader/manual.py +1 -1
- mpflash/cli_download.py +8 -5
- mpflash/cli_flash.py +17 -33
- mpflash/cli_group.py +3 -0
- mpflash/cli_list.py +2 -2
- mpflash/cli_main.py +4 -0
- mpflash/common.py +1 -36
- mpflash/config.py +21 -0
- mpflash/db/__init__.py +2 -0
- mpflash/db/core.py +56 -0
- mpflash/db/gather_boards.py +112 -0
- mpflash/db/loader.py +120 -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 +53 -0
- mpflash/downloaded.py +79 -93
- mpflash/flash/__init__.py +7 -3
- mpflash/flash/esp.py +2 -1
- mpflash/flash/worklist.py +16 -28
- mpflash/list.py +3 -3
- mpflash/logger.py +2 -2
- 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 +44 -72
- mpflash/mpboard_id/resolve.py +19 -0
- mpflash/mpremoteboard/__init__.py +1 -1
- mpflash/mpremoteboard/mpy_fw_info.py +1 -0
- mpflash/mpremoteboard/runner.py +5 -2
- mpflash/vendor/pydfu.py +4 -5
- mpflash/versions.py +3 -0
- {mpflash-1.25.0.dist-info → mpflash-1.25.0rc1.dist-info}/METADATA +2 -2
- mpflash-1.25.0rc1.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.dist-info/RECORD +0 -62
- {mpflash-1.25.0.dist-info → mpflash-1.25.0rc1.dist-info}/LICENSE +0 -0
- {mpflash-1.25.0.dist-info → mpflash-1.25.0rc1.dist-info}/WHEEL +0 -0
- {mpflash-1.25.0.dist-info → mpflash-1.25.0rc1.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,8 @@ 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,
|
15
|
+
known_stored_boards)
|
15
16
|
from .mpremoteboard import MPRemoteBoard
|
16
17
|
from .versions import micropython_versions
|
17
18
|
|
@@ -106,7 +107,7 @@ def filter_matching_boards(answers: dict) -> Sequence[Tuple[str, str]]:
|
|
106
107
|
Returns:
|
107
108
|
Sequence[Tuple[str, str]]: The filtered boards.
|
108
109
|
"""
|
109
|
-
versions =
|
110
|
+
versions = []
|
110
111
|
# if version is not asked ; then need to get the version from the inputs
|
111
112
|
if "versions" in answers:
|
112
113
|
versions = list(answers["versions"])
|
@@ -151,7 +152,7 @@ def ask_port_board(*, multi_select: bool, action: str):
|
|
151
152
|
inquirer.List(
|
152
153
|
"port",
|
153
154
|
message="Which port do you want to {action} " + "to {serial} ?" if action == "flash" else "?",
|
154
|
-
choices=
|
155
|
+
choices=known_ports(),
|
155
156
|
# autocomplete=True,
|
156
157
|
),
|
157
158
|
inquirer_ux(
|
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
|
@@ -267,7 +267,7 @@ def pull(repo: Union[Path, str], branch: str = "main") -> bool:
|
|
267
267
|
return result.returncode == 0
|
268
268
|
|
269
269
|
|
270
|
-
def get_git_describe(folder: Optional[Path
|
270
|
+
def get_git_describe(folder: Optional[Union[Path, str]] = None):
|
271
271
|
"""
|
272
272
|
Based on MicroPython makeversionhdr.py
|
273
273
|
returns : current git tag, commits ,commit hash : "v1.19.1-841-g3446"
|
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
|
|
@@ -31,8 +32,8 @@ from mpflash.versions import clean_version
|
|
31
32
|
"-f",
|
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,
|
@@ -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,8 @@ from mpflash.versions import clean_version
|
|
124
125
|
help="""How to enter the (MicroPython) bootloader before flashing.""",
|
125
126
|
)
|
126
127
|
@click.option(
|
127
|
-
"--flash_mode",
|
128
|
+
"--flash_mode",
|
129
|
+
"-fm",
|
128
130
|
type=click.Choice(["keep", "qio", "qout", "dio", "dout"]),
|
129
131
|
default="keep",
|
130
132
|
show_default=True,
|
@@ -137,7 +139,7 @@ def cli_flash_board(**kwargs) -> int:
|
|
137
139
|
kwargs["boards"] = []
|
138
140
|
kwargs.pop("board")
|
139
141
|
else:
|
140
|
-
kwargs["boards"] = [kwargs.pop("board")]
|
142
|
+
kwargs["boards"] = [kwargs.pop("board")]
|
141
143
|
|
142
144
|
params = FlashParams(**kwargs)
|
143
145
|
params.versions = list(params.versions)
|
@@ -150,7 +152,8 @@ def cli_flash_board(**kwargs) -> int:
|
|
150
152
|
# make it simple for the user to flash one board by asking for the serial port if not specified
|
151
153
|
if params.boards == ["?"] and params.serial == "*":
|
152
154
|
params.serial = ["?"]
|
153
|
-
|
155
|
+
if params.fw_folder:
|
156
|
+
config.firmware_folder = Path(params.fw_folder)
|
154
157
|
# Detect connected boards if not specified,
|
155
158
|
# and ask for input if boards cannot be detected
|
156
159
|
all_boards: List[MPRemoteBoard] = []
|
@@ -167,14 +170,12 @@ def cli_flash_board(**kwargs) -> int:
|
|
167
170
|
# assume manual mode if no board is detected
|
168
171
|
params.bootloader = BootloaderMethod("manual")
|
169
172
|
else:
|
170
|
-
resolve_board_ids(params)
|
173
|
+
mpboard_id.resolve_board_ids(params)
|
171
174
|
|
172
175
|
# Ask for missing input if needed
|
173
176
|
params = ask_missing_params(params)
|
174
177
|
if not params: # Cancelled by user
|
175
178
|
return 2
|
176
|
-
# TODO: Just in time Download of firmware
|
177
|
-
|
178
179
|
assert isinstance(params, FlashParams)
|
179
180
|
|
180
181
|
if len(params.versions) > 1:
|
@@ -183,6 +184,7 @@ def cli_flash_board(**kwargs) -> int:
|
|
183
184
|
|
184
185
|
params.versions = [clean_version(v) for v in params.versions]
|
185
186
|
worklist: WorkList = []
|
187
|
+
|
186
188
|
# if serial port == auto and there are one or more specified/detected boards
|
187
189
|
if params.serial == ["*"] and params.boards:
|
188
190
|
if not all_boards:
|
@@ -191,37 +193,33 @@ def cli_flash_board(**kwargs) -> int:
|
|
191
193
|
# if variant id provided on the cmdline, treat is as an override
|
192
194
|
if params.variant:
|
193
195
|
for b in all_boards:
|
194
|
-
b.variant = params.variant if (params.variant.lower() not in {"-","none"}) else ""
|
196
|
+
b.variant = params.variant if (params.variant.lower() not in {"-", "none"}) else ""
|
195
197
|
|
196
198
|
worklist = full_auto_worklist(
|
197
199
|
all_boards=all_boards,
|
198
200
|
version=params.versions[0],
|
199
|
-
fw_folder=params.fw_folder,
|
200
201
|
include=params.serial,
|
201
202
|
ignore=params.ignore,
|
202
203
|
)
|
203
204
|
elif params.versions[0] and params.boards[0] and params.serial:
|
204
|
-
# A one or more
|
205
|
+
# A one or more serial port including the board / variant
|
205
206
|
worklist = manual_worklist(
|
206
207
|
params.serial[0],
|
207
208
|
board_id=params.boards[0],
|
208
209
|
version=params.versions[0],
|
209
|
-
fw_folder=params.fw_folder,
|
210
210
|
)
|
211
211
|
else:
|
212
212
|
# just this serial port on auto
|
213
213
|
worklist = single_auto_worklist(
|
214
214
|
serial=params.serial[0],
|
215
215
|
version=params.versions[0],
|
216
|
-
fw_folder=params.fw_folder,
|
217
216
|
)
|
218
|
-
|
217
|
+
jid.ensure_firmware_downloaded(worklist, version=params.versions[0])
|
219
218
|
if flashed := flash_list(
|
220
219
|
worklist,
|
221
|
-
params.fw_folder,
|
222
220
|
params.erase,
|
223
221
|
params.bootloader,
|
224
|
-
flash_mode
|
222
|
+
flash_mode=params.flash_mode,
|
225
223
|
):
|
226
224
|
log.info(f"Flashed {len(flashed)} boards")
|
227
225
|
show_mcus(flashed, title="Updated boards after flashing")
|
@@ -231,17 +229,3 @@ def cli_flash_board(**kwargs) -> int:
|
|
231
229
|
return 1
|
232
230
|
|
233
231
|
|
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
@@ -48,7 +48,7 @@ from .logger import make_quiet
|
|
48
48
|
)
|
49
49
|
@click.option(
|
50
50
|
"--bluetooth/--no-bluetooth",
|
51
|
-
"-
|
51
|
+
"--bt/--no-bt",
|
52
52
|
is_flag=True,
|
53
53
|
default=False,
|
54
54
|
show_default=True,
|
@@ -77,7 +77,7 @@ def cli_list_mcus(serial: List[str], ignore: List[str], bluetooth: bool, as_json
|
|
77
77
|
conn_mcus = [item for item in conn_mcus if not (item.toml.get("mpflash", {}).get("ignore", False))]
|
78
78
|
if as_json:
|
79
79
|
print(json.dumps([mcu.to_dict() for mcu in conn_mcus], indent=4))
|
80
|
-
|
80
|
+
|
81
81
|
if progress:
|
82
82
|
show_mcus(conn_mcus, refresh=False)
|
83
83
|
for mcu in conn_mcus:
|
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
@@ -28,41 +28,6 @@ PORT_FWTYPES = {
|
|
28
28
|
|
29
29
|
UF2_PORTS = [port for port, exts in PORT_FWTYPES.items() if ".uf2" in exts]
|
30
30
|
|
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
31
|
|
67
32
|
@dataclass
|
68
33
|
class Params:
|
@@ -72,7 +37,7 @@ class Params:
|
|
72
37
|
boards: List[str] = field(default_factory=list)
|
73
38
|
variant: str = ""
|
74
39
|
versions: List[str] = field(default_factory=list)
|
75
|
-
fw_folder: Path =
|
40
|
+
fw_folder: Optional[Path] = None
|
76
41
|
serial: List[str] = field(default_factory=list)
|
77
42
|
ignore: List[str] = field(default_factory=list)
|
78
43
|
bluetooth: bool = False
|
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,56 @@
|
|
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
|
+
|
10
|
+
# TODO: lazy import to avoid slowdowns ?
|
11
|
+
from .models import Base
|
12
|
+
|
13
|
+
TRACE = False
|
14
|
+
connect_str = f"sqlite:///{config.db_path.as_posix()}"
|
15
|
+
engine = create_engine(connect_str, echo=TRACE)
|
16
|
+
Session = sessionmaker(bind=engine)
|
17
|
+
|
18
|
+
|
19
|
+
def migrate_database(boards: bool = True, firmwares: bool = True):
|
20
|
+
"""Migrate from 1.24.x to 1.25.x"""
|
21
|
+
# Move import here to avoid circular import
|
22
|
+
from .loader import load_jsonl_to_db, update_boards
|
23
|
+
|
24
|
+
# get the location of the database from the session
|
25
|
+
with Session() as session:
|
26
|
+
db_location = session.get_bind().url.database # type: ignore
|
27
|
+
log.debug(f"Database location: {Path(db_location)}") # type: ignore
|
28
|
+
|
29
|
+
create_database()
|
30
|
+
if boards:
|
31
|
+
|
32
|
+
update_boards()
|
33
|
+
if firmwares:
|
34
|
+
jsonl_file = config.firmware_folder / "firmware.jsonl"
|
35
|
+
if jsonl_file.exists():
|
36
|
+
log.info(f"Migrating JSONL data {jsonl_file} to SQLite database.")
|
37
|
+
load_jsonl_to_db(jsonl_file)
|
38
|
+
# rename the jsonl file to jsonl.bak
|
39
|
+
log.info(f"Renaming {jsonl_file} to {jsonl_file.with_suffix('.jsonl.bak')}")
|
40
|
+
try:
|
41
|
+
jsonl_file.rename(jsonl_file.with_suffix(".jsonl.bak"))
|
42
|
+
except OSError as e:
|
43
|
+
for i in range(1, 10):
|
44
|
+
try:
|
45
|
+
jsonl_file.rename(jsonl_file.with_suffix(f".jsonl.{i}.bak"))
|
46
|
+
break
|
47
|
+
except OSError:
|
48
|
+
continue
|
49
|
+
|
50
|
+
|
51
|
+
def create_database():
|
52
|
+
"""
|
53
|
+
Create the SQLite database and tables if they don't exist.
|
54
|
+
"""
|
55
|
+
# Create the database and tables if they don't exist
|
56
|
+
Base.metadata.create_all(engine)
|