mpflash 1.25.1__py3-none-any.whl → 1.25.2__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/basicgit.py +19 -0
- mpflash/cli_add.py +131 -0
- mpflash/cli_download.py +3 -2
- mpflash/cli_flash.py +20 -9
- mpflash/cli_group.py +1 -0
- mpflash/cli_main.py +7 -0
- mpflash/common.py +3 -1
- mpflash/config.py +7 -2
- mpflash/connected.py +6 -5
- mpflash/custom/__init__.py +144 -0
- mpflash/custom/naming.py +91 -0
- mpflash/db/core.py +84 -6
- mpflash/db/gather_boards.py +6 -4
- mpflash/db/loader.py +4 -3
- mpflash/db/meta.py +4 -3
- mpflash/db/micropython_boards.zip +0 -0
- mpflash/db/models.py +2 -0
- mpflash/download/__init__.py +58 -1
- mpflash/download/fwinfo.py +1 -1
- mpflash/downloaded.py +8 -4
- mpflash/flash/__init__.py +10 -1
- mpflash/flash/worklist.py +9 -4
- mpflash/list.py +2 -0
- mpflash/logger.py +20 -4
- mpflash/mpboard_id/board_id.py +13 -17
- mpflash/mpboard_id/known.py +8 -2
- mpflash/mpremoteboard/__init__.py +59 -2
- mpflash/mpremoteboard/runner.py +1 -0
- {mpflash-1.25.1.dist-info → mpflash-1.25.2.dist-info}/METADATA +2 -1
- {mpflash-1.25.1.dist-info → mpflash-1.25.2.dist-info}/RECORD +33 -31
- {mpflash-1.25.1.dist-info → mpflash-1.25.2.dist-info}/WHEEL +1 -1
- mpflash/add_firmware.py +0 -125
- {mpflash-1.25.1.dist-info → mpflash-1.25.2.dist-info}/LICENSE +0 -0
- {mpflash-1.25.1.dist-info → mpflash-1.25.2.dist-info}/entry_points.txt +0 -0
mpflash/basicgit.py
CHANGED
@@ -135,6 +135,25 @@ def get_local_tags(repo: Optional[Path] = None, minver: Optional[str] = None) ->
|
|
135
135
|
return sorted(tags)
|
136
136
|
|
137
137
|
|
138
|
+
def get_current_branch(repo: Optional[Union[Path, str]] = None) -> Optional[str]:
|
139
|
+
"""
|
140
|
+
Get the current branch name of a local repository.
|
141
|
+
|
142
|
+
Args:
|
143
|
+
repo: Path to the repository directory
|
144
|
+
|
145
|
+
Returns:
|
146
|
+
Current branch name or None if error
|
147
|
+
"""
|
148
|
+
cmd = ["git", "branch", "--show-current"]
|
149
|
+
result = _run_local_git(cmd, repo=repo, expect_stderr=True)
|
150
|
+
if not result:
|
151
|
+
return None
|
152
|
+
|
153
|
+
branch = result.stdout.strip()
|
154
|
+
return branch if branch else None
|
155
|
+
|
156
|
+
|
138
157
|
@cachetools.func.ttl_cache(maxsize=16, ttl=60) # 60 seconds
|
139
158
|
def get_tags(repo: str, minver: Optional[str] = None) -> List[str]:
|
140
159
|
"""
|
mpflash/cli_add.py
ADDED
@@ -0,0 +1,131 @@
|
|
1
|
+
"""CLI to add a custom MicroPython firmware."""
|
2
|
+
|
3
|
+
from pathlib import Path
|
4
|
+
from typing import Union
|
5
|
+
|
6
|
+
import rich_click as click
|
7
|
+
from loguru import logger as log
|
8
|
+
|
9
|
+
from mpflash.connected import connected_ports_boards_variants
|
10
|
+
from mpflash.custom import Firmware, add_firmware, custom_fw_from_path
|
11
|
+
from mpflash.downloaded import clean_downloaded_firmwares
|
12
|
+
from mpflash.errors import MPFlashError
|
13
|
+
from mpflash.mpboard_id import find_known_board
|
14
|
+
from mpflash.mpboard_id.alternate import add_renamed_boards
|
15
|
+
from mpflash.versions import clean_version
|
16
|
+
|
17
|
+
from .ask_input import ask_missing_params
|
18
|
+
from .cli_group import cli
|
19
|
+
from .config import config
|
20
|
+
from .download import download
|
21
|
+
|
22
|
+
|
23
|
+
@cli.command(
|
24
|
+
"add",
|
25
|
+
help="Add a custom MicroPython firmware.",
|
26
|
+
)
|
27
|
+
# @click.option(
|
28
|
+
# "--version",
|
29
|
+
# "-v",
|
30
|
+
# "versions",
|
31
|
+
# default=["stable"],
|
32
|
+
# multiple=False,
|
33
|
+
# show_default=True,
|
34
|
+
# help="The version of MicroPython to to download.",
|
35
|
+
# metavar="SEMVER, 'stable', 'preview' or '?'",
|
36
|
+
# )
|
37
|
+
@click.option(
|
38
|
+
"--path",
|
39
|
+
"-p",
|
40
|
+
"fw_path",
|
41
|
+
multiple=False,
|
42
|
+
default="",
|
43
|
+
show_default=False,
|
44
|
+
help="a local path to the firmware file to add.",
|
45
|
+
metavar="FIRMWARE_PATH",
|
46
|
+
)
|
47
|
+
@click.option(
|
48
|
+
"--description",
|
49
|
+
"-d",
|
50
|
+
"description",
|
51
|
+
default="",
|
52
|
+
help="An Optional description for the firmware.",
|
53
|
+
metavar="TXT",
|
54
|
+
)
|
55
|
+
# @click.option(
|
56
|
+
# "--board",
|
57
|
+
# "-b",
|
58
|
+
# "boards",
|
59
|
+
# multiple=True,
|
60
|
+
# default=[],
|
61
|
+
# show_default=True,
|
62
|
+
# help="The board(s) to download the firmware for.",
|
63
|
+
# metavar="BOARD_ID or ?",
|
64
|
+
# )
|
65
|
+
# @click.option(
|
66
|
+
# "--serial",
|
67
|
+
# "--serial-port",
|
68
|
+
# "-s",
|
69
|
+
# "serial",
|
70
|
+
# default=["*"],
|
71
|
+
# show_default=True,
|
72
|
+
# multiple=True,
|
73
|
+
# help="Which serial port(s) (or globs) to flash",
|
74
|
+
# metavar="SERIALPORT",
|
75
|
+
# )
|
76
|
+
# @click.option(
|
77
|
+
# "--ignore",
|
78
|
+
# "-i",
|
79
|
+
# is_eager=True,
|
80
|
+
# help="Serial port(s) to ignore. Defaults to MPFLASH_IGNORE.",
|
81
|
+
# multiple=True,
|
82
|
+
# default=[],
|
83
|
+
# envvar="MPFLASH_IGNORE",
|
84
|
+
# show_default=True,
|
85
|
+
# metavar="SERIALPORT",
|
86
|
+
# )
|
87
|
+
# @click.option(
|
88
|
+
# "--clean/--no-clean",
|
89
|
+
# default=True,
|
90
|
+
# show_default=True,
|
91
|
+
# help="""Remove dates and hashes from the downloaded firmware filenames.""",
|
92
|
+
# )
|
93
|
+
@click.option(
|
94
|
+
"--force",
|
95
|
+
"-f",
|
96
|
+
default=False,
|
97
|
+
is_flag=True,
|
98
|
+
show_default=True,
|
99
|
+
help="""Overwrite existing firmware.""",
|
100
|
+
)
|
101
|
+
def cli_add_custom(
|
102
|
+
fw_path: Union[Path, str],
|
103
|
+
force: bool = False,
|
104
|
+
description: str = "",
|
105
|
+
) -> int:
|
106
|
+
"""Add a custom MicroPython firmware from a local file."""
|
107
|
+
if not fw_path:
|
108
|
+
log.error("No firmware path provided. Use --path to specify a firmware file.")
|
109
|
+
return 1
|
110
|
+
fw_path = Path(fw_path).expanduser().resolve()
|
111
|
+
if not fw_path.exists():
|
112
|
+
log.error(f"Firmware file does not exist: {fw_path}")
|
113
|
+
return 1
|
114
|
+
|
115
|
+
try:
|
116
|
+
fw_dict = custom_fw_from_path(fw_path)
|
117
|
+
if description:
|
118
|
+
fw_dict["description"] = description
|
119
|
+
if add_firmware(
|
120
|
+
source=fw_path,
|
121
|
+
fw_info=fw_dict,
|
122
|
+
custom=True,
|
123
|
+
force=force,
|
124
|
+
):
|
125
|
+
log.success(f"Added custom firmware: {fw_dict['custom_id']} for {fw_dict['firmware_file']}")
|
126
|
+
return 0
|
127
|
+
else:
|
128
|
+
return 1
|
129
|
+
except MPFlashError as e:
|
130
|
+
log.error(f"{e}")
|
131
|
+
return 1
|
mpflash/cli_download.py
CHANGED
@@ -2,10 +2,11 @@
|
|
2
2
|
|
3
3
|
from pathlib import Path
|
4
4
|
|
5
|
+
from pytest import param
|
5
6
|
import rich_click as click
|
6
7
|
from loguru import logger as log
|
7
8
|
|
8
|
-
from mpflash.connected import
|
9
|
+
from mpflash.connected import connected_ports_boards_variants
|
9
10
|
from mpflash.downloaded import clean_downloaded_firmwares
|
10
11
|
from mpflash.errors import MPFlashError
|
11
12
|
from mpflash.mpboard_id import find_known_board
|
@@ -97,7 +98,7 @@ def cli_download(**kwargs) -> int:
|
|
97
98
|
log.error(f"{e}")
|
98
99
|
else:
|
99
100
|
# no boards specified - detect connected ports and boards
|
100
|
-
params.ports, params.boards, _ =
|
101
|
+
params.ports, params.boards, _ , _ = connected_ports_boards_variants(include=params.serial, ignore=params.ignore)
|
101
102
|
|
102
103
|
params = ask_missing_params(params)
|
103
104
|
if not params: # Cancelled by user
|
mpflash/cli_flash.py
CHANGED
@@ -6,7 +6,7 @@ from loguru import logger as log
|
|
6
6
|
import mpflash.download.jid as jid
|
7
7
|
import mpflash.mpboard_id as mpboard_id
|
8
8
|
from mpflash.ask_input import ask_missing_params
|
9
|
-
from mpflash.cli_download import
|
9
|
+
from mpflash.cli_download import connected_ports_boards_variants
|
10
10
|
from mpflash.cli_group import cli
|
11
11
|
from mpflash.cli_list import show_mcus
|
12
12
|
from mpflash.common import BootloaderMethod, FlashParams, filtered_comports
|
@@ -93,7 +93,6 @@ from mpflash.versions import clean_version
|
|
93
93
|
@click.option(
|
94
94
|
"--cpu",
|
95
95
|
"--chip",
|
96
|
-
"-c",
|
97
96
|
"cpu",
|
98
97
|
help="The CPU type to flash. If not specified will try to read the CPU from the connected MCU.",
|
99
98
|
metavar="CPU",
|
@@ -129,6 +128,14 @@ from mpflash.versions import clean_version
|
|
129
128
|
show_default=True,
|
130
129
|
help="""Flash mode for ESP boards. (default: keep)""",
|
131
130
|
)
|
131
|
+
@click.option(
|
132
|
+
"--custom",
|
133
|
+
"-c",
|
134
|
+
default=False,
|
135
|
+
is_flag=True,
|
136
|
+
show_default=True,
|
137
|
+
help="""Flash a custom firmware""",
|
138
|
+
)
|
132
139
|
def cli_flash_board(**kwargs) -> int:
|
133
140
|
# version to versions, board to boards
|
134
141
|
kwargs["versions"] = [kwargs.pop("version")] if kwargs["version"] is not None else []
|
@@ -158,9 +165,13 @@ def cli_flash_board(**kwargs) -> int:
|
|
158
165
|
all_boards: List[MPRemoteBoard] = []
|
159
166
|
if not params.boards:
|
160
167
|
# nothing specified - detect connected boards
|
161
|
-
params.ports, params.boards, all_boards =
|
162
|
-
include=params.ports,
|
168
|
+
params.ports, params.boards, variants, all_boards = connected_ports_boards_variants(
|
169
|
+
include=params.ports,
|
170
|
+
ignore=params.ignore,
|
171
|
+
bluetooth=params.bluetooth,
|
163
172
|
)
|
173
|
+
if variants and len(variants) >= 1:
|
174
|
+
params.variant = variants[0]
|
164
175
|
if params.boards == []:
|
165
176
|
# No MicroPython boards detected, but it could be unflashed or in bootloader mode
|
166
177
|
# Ask for serial port and board_id to flash
|
@@ -188,7 +199,7 @@ def cli_flash_board(**kwargs) -> int:
|
|
188
199
|
# A one or more serial port including the board / variant
|
189
200
|
comports = filtered_comports(
|
190
201
|
ignore=params.ignore,
|
191
|
-
include=params.
|
202
|
+
include=params.serial,
|
192
203
|
bluetooth=params.bluetooth,
|
193
204
|
)
|
194
205
|
board_id = f"{params.boards[0]}-{params.variant}" if params.variant else params.boards[0]
|
@@ -198,12 +209,13 @@ def cli_flash_board(**kwargs) -> int:
|
|
198
209
|
comports,
|
199
210
|
board_id=board_id,
|
200
211
|
version=params.versions[0],
|
212
|
+
custom=params.custom,
|
201
213
|
)
|
202
214
|
# if serial port == auto and there are one or more specified/detected boards
|
203
215
|
elif params.serial == ["*"] and params.boards:
|
204
216
|
if not all_boards:
|
205
217
|
log.trace("No boards detected yet, scanning for connected boards")
|
206
|
-
_, _, all_boards =
|
218
|
+
_, _, _, all_boards = connected_ports_boards_variants(include=params.ports, ignore=params.ignore)
|
207
219
|
# if variant id provided on the cmdline, treat is as an override
|
208
220
|
if params.variant:
|
209
221
|
for b in all_boards:
|
@@ -233,7 +245,8 @@ def cli_flash_board(**kwargs) -> int:
|
|
233
245
|
serial=params.serial[0],
|
234
246
|
version=params.versions[0],
|
235
247
|
)
|
236
|
-
|
248
|
+
if not params.custom:
|
249
|
+
jid.ensure_firmware_downloaded(worklist, version=params.versions[0], force=params.force)
|
237
250
|
if flashed := flash_list(
|
238
251
|
worklist,
|
239
252
|
params.erase,
|
@@ -246,5 +259,3 @@ def cli_flash_board(**kwargs) -> int:
|
|
246
259
|
else:
|
247
260
|
log.error("No boards were flashed")
|
248
261
|
return 1
|
249
|
-
|
250
|
-
|
mpflash/cli_group.py
CHANGED
mpflash/cli_main.py
CHANGED
@@ -5,6 +5,9 @@ import os
|
|
5
5
|
import click.exceptions as click_exceptions
|
6
6
|
from loguru import logger as log
|
7
7
|
|
8
|
+
from mpflash.errors import MPFlashError
|
9
|
+
|
10
|
+
from .cli_add import cli_add_custom
|
8
11
|
from .cli_download import cli_download
|
9
12
|
from .cli_flash import cli_flash_board
|
10
13
|
from .cli_group import cli
|
@@ -19,6 +22,7 @@ def mpflash():
|
|
19
22
|
cli.add_command(cli_list_mcus)
|
20
23
|
cli.add_command(cli_download)
|
21
24
|
cli.add_command(cli_flash_board)
|
25
|
+
cli.add_command(cli_add_custom)
|
22
26
|
|
23
27
|
# cli(auto_envvar_prefix="MPFLASH")
|
24
28
|
if False and os.environ.get("COMPUTERNAME").upper().startswith("JOSVERL"):
|
@@ -37,6 +41,9 @@ def mpflash():
|
|
37
41
|
except click_exceptions.Abort:
|
38
42
|
# Aborted - Ctrl-C
|
39
43
|
exit(-3)
|
44
|
+
except MPFlashError as e:
|
45
|
+
log.error(f"MPFlashError: {e}")
|
46
|
+
exit(-4)
|
40
47
|
|
41
48
|
|
42
49
|
if __name__ == "__main__":
|
mpflash/common.py
CHANGED
@@ -4,7 +4,6 @@ import os
|
|
4
4
|
import platform
|
5
5
|
from dataclasses import dataclass, field
|
6
6
|
from enum import Enum
|
7
|
-
from pathlib import Path
|
8
7
|
from typing import List, Optional, Union
|
9
8
|
|
10
9
|
from serial.tools import list_ports
|
@@ -65,6 +64,7 @@ class FlashParams(Params):
|
|
65
64
|
bootloader: BootloaderMethod = BootloaderMethod.NONE
|
66
65
|
cpu: str = ""
|
67
66
|
flash_mode: str = "keep" # keep, qio, qout, dio, dout
|
67
|
+
custom: bool = False
|
68
68
|
|
69
69
|
def __post_init__(self):
|
70
70
|
if isinstance(self.bootloader, str):
|
@@ -85,6 +85,7 @@ def filtered_comports(
|
|
85
85
|
"""
|
86
86
|
return [p.device for p in filtered_portinfos(ignore, include, bluetooth)]
|
87
87
|
|
88
|
+
|
88
89
|
def filtered_portinfos(
|
89
90
|
ignore: Optional[List[str]] = None,
|
90
91
|
include: Optional[List[str]] = None,
|
@@ -94,6 +95,7 @@ def filtered_portinfos(
|
|
94
95
|
Get a list of filtered comports using the include and ignore lists.
|
95
96
|
both can be globs (e.g. COM*) or exact port names (e.g. COM1)
|
96
97
|
"""
|
98
|
+
log.trace(f"filtered_portinfos: {ignore=}, {include=}, {bluetooth=}")
|
97
99
|
if not ignore:
|
98
100
|
ignore = []
|
99
101
|
elif not isinstance(ignore, list): # type: ignore
|
mpflash/config.py
CHANGED
@@ -4,6 +4,7 @@ import os
|
|
4
4
|
from importlib.metadata import version
|
5
5
|
from pathlib import Path
|
6
6
|
from typing import List, Optional
|
7
|
+
|
7
8
|
import platformdirs
|
8
9
|
|
9
10
|
from mpflash.errors import MPFlashError
|
@@ -46,14 +47,17 @@ class MPFlashConfig:
|
|
46
47
|
"""The folder where firmware files are stored"""
|
47
48
|
if not self._firmware_folder:
|
48
49
|
from mpflash.logger import log
|
50
|
+
|
49
51
|
# Check if MPFLASH_FIRMWARE environment variable is set
|
50
52
|
env_firmware_path = os.getenv("MPFLASH_FIRMWARE")
|
51
53
|
if env_firmware_path:
|
52
|
-
firmware_path = Path(env_firmware_path)
|
54
|
+
firmware_path = Path(env_firmware_path).expanduser().resolve()
|
53
55
|
if firmware_path.exists() and firmware_path.is_dir():
|
54
56
|
self._firmware_folder = firmware_path
|
55
57
|
else:
|
56
|
-
log.warning(
|
58
|
+
log.warning(
|
59
|
+
f"Environment variable MPFLASH_FIRMWARE points to invalid directory: {env_firmware_path}. Using default location."
|
60
|
+
)
|
57
61
|
# allow testing in CI
|
58
62
|
if Path(os.getenv("GITHUB_ACTIONS", "")).as_posix().lower() == "true":
|
59
63
|
workspace = os.getenv("GITHUB_WORKSPACE")
|
@@ -83,6 +87,7 @@ class MPFlashConfig:
|
|
83
87
|
def db_path(self) -> Path:
|
84
88
|
"""The path to the database file"""
|
85
89
|
return self.firmware_folder / "mpflash.db"
|
90
|
+
|
86
91
|
@property
|
87
92
|
def db_version(self) -> str:
|
88
93
|
return "1.24.1"
|
mpflash/connected.py
CHANGED
@@ -8,9 +8,9 @@ from mpflash.common import filtered_portinfos, find_serial_by_path
|
|
8
8
|
from mpflash.mpremoteboard import MPRemoteBoard
|
9
9
|
|
10
10
|
|
11
|
-
def
|
11
|
+
def connected_ports_boards_variants(
|
12
12
|
*, include: List[str], ignore: List[str], bluetooth: bool = False
|
13
|
-
) -> Tuple[List[str], List[str], List[MPRemoteBoard]]:
|
13
|
+
) -> Tuple[List[str], List[str], List[str], List[MPRemoteBoard]]:
|
14
14
|
"""
|
15
15
|
Returns a tuple containing lists of unique ports and boards from the connected MCUs.
|
16
16
|
Boards that are physically connected, but give no tangible response are ignored.
|
@@ -21,14 +21,15 @@ def connected_ports_boards(
|
|
21
21
|
- A list of unique board names of the connected MCUs.
|
22
22
|
- A list of MPRemoteBoard instances of the connected MCUs.
|
23
23
|
"""
|
24
|
-
conn_mcus = [b for b in list_mcus(include=include, ignore=ignore, bluetooth=bluetooth)]
|
24
|
+
# conn_mcus = [b for b in list_mcus(include=include, ignore=ignore, bluetooth=bluetooth)]
|
25
25
|
conn_mcus = [b for b in list_mcus(include=include, ignore=ignore, bluetooth=bluetooth) if b.connected]
|
26
26
|
# ignore boards that have the [mpflash] ignore flag set
|
27
27
|
conn_mcus = [item for item in conn_mcus if not (item.toml.get("mpflash", {}).get("ignore", False))]
|
28
28
|
|
29
29
|
ports = list({b.port for b in conn_mcus})
|
30
30
|
boards = list({b.board for b in conn_mcus})
|
31
|
-
|
31
|
+
variants = list({b.variant for b in conn_mcus if b.variant})
|
32
|
+
return (ports, boards, variants, conn_mcus)
|
32
33
|
|
33
34
|
|
34
35
|
# #########################################################################################################
|
@@ -47,7 +48,7 @@ def list_mcus(*, ignore: List[str], include: List[str], bluetooth: bool = False)
|
|
47
48
|
ConnectionError: If there is an error connecting to a board.
|
48
49
|
"""
|
49
50
|
# conn_mcus = [MPRemoteBoard(sp) for sp in MPRemoteBoard.connected_boards(bluetooth) if sp not in config.ignore_ports]
|
50
|
-
|
51
|
+
vid_pid = True
|
51
52
|
comports = filtered_portinfos(
|
52
53
|
ignore=ignore,
|
53
54
|
include=include,
|
@@ -0,0 +1,144 @@
|
|
1
|
+
import shutil
|
2
|
+
import sqlite3
|
3
|
+
from pathlib import Path
|
4
|
+
from typing import Union
|
5
|
+
|
6
|
+
import jsonlines
|
7
|
+
import requests
|
8
|
+
from loguru import logger as log
|
9
|
+
# re-use logic from mpremote
|
10
|
+
from mpremote.mip import _rewrite_url as rewrite_url # type: ignore
|
11
|
+
|
12
|
+
from mpflash.config import config
|
13
|
+
from mpflash.db.core import Session
|
14
|
+
from mpflash.db.models import Firmware
|
15
|
+
from mpflash.errors import MPFlashError
|
16
|
+
from mpflash.versions import get_preview_mp_version, get_stable_mp_version
|
17
|
+
|
18
|
+
from .naming import (custom_fw_from_path, extract_commit_count,
|
19
|
+
port_and_boardid_from_path)
|
20
|
+
|
21
|
+
#
|
22
|
+
# github.com/<owner>/<repo>@<branch>#<commit>
|
23
|
+
# $remote_url = git remote get-url origin
|
24
|
+
# $branch = git rev-parse --abbrev-ref HEAD
|
25
|
+
# $commit = git rev-parse --short HEAD
|
26
|
+
# if ($remote_url -match "github.com[:/](.+)/(.+?)(\.git)?$") {
|
27
|
+
# $owner = $matches[1]
|
28
|
+
# $repo = $matches[2]
|
29
|
+
# "github.com/$owner/$repo@$branch#$commit"
|
30
|
+
# }
|
31
|
+
|
32
|
+
|
33
|
+
# 1) local path
|
34
|
+
|
35
|
+
|
36
|
+
def add_firmware(
|
37
|
+
source: Path,
|
38
|
+
fw_info: dict,
|
39
|
+
*,
|
40
|
+
force: bool = False,
|
41
|
+
custom: bool = False,
|
42
|
+
) -> bool:
|
43
|
+
"""
|
44
|
+
Add a firmware to the database , and firmware folder.
|
45
|
+
stored in the port folder, with the filename.
|
46
|
+
|
47
|
+
fw_info is a dict with the following keys:
|
48
|
+
- board_id: str, required
|
49
|
+
- version: str, required
|
50
|
+
- port: str, required
|
51
|
+
- firmware_file: str, required, the filename to store in the firmware folder
|
52
|
+
- source: str, optional, the source of the firmware, can be a local path
|
53
|
+
- description: str, optional, a description of the firmware
|
54
|
+
- custom: bool, optional, if the firmware is a custom firmware, default False
|
55
|
+
"""
|
56
|
+
try:
|
57
|
+
source = source.expanduser().absolute()
|
58
|
+
if not source.exists() or not source.is_file():
|
59
|
+
log.error(f"Source file {source} does not exist or is not a file")
|
60
|
+
return False
|
61
|
+
with Session() as session:
|
62
|
+
# Check minimal info needed
|
63
|
+
new_fw = Firmware(**fw_info)
|
64
|
+
if custom:
|
65
|
+
new_fw.custom = True
|
66
|
+
|
67
|
+
if not new_fw.board_id:
|
68
|
+
log.error("board_id is required")
|
69
|
+
return False
|
70
|
+
|
71
|
+
# assume the the firmware_file has already been prepared
|
72
|
+
fw_filename = config.firmware_folder / new_fw.firmware_file
|
73
|
+
|
74
|
+
if not copy_firmware(source, fw_filename, force):
|
75
|
+
log.error(f"Failed to copy {source} to {fw_filename}")
|
76
|
+
return False
|
77
|
+
# add to inventory
|
78
|
+
# check if the firmware already exists
|
79
|
+
if custom:
|
80
|
+
qry = session.query(Firmware).filter(Firmware.custom_id == new_fw.custom_id)
|
81
|
+
else:
|
82
|
+
qry = session.query(Firmware).filter(Firmware.board_id == new_fw.board_id)
|
83
|
+
|
84
|
+
qry = qry.filter(
|
85
|
+
Firmware.board_id == new_fw.board_id,
|
86
|
+
Firmware.version == new_fw.version,
|
87
|
+
Firmware.port == new_fw.port,
|
88
|
+
Firmware.custom == new_fw.custom,
|
89
|
+
)
|
90
|
+
existing_fw = qry.first()
|
91
|
+
|
92
|
+
if existing_fw:
|
93
|
+
if not force:
|
94
|
+
log.warning(f"Firmware {existing_fw} already exists")
|
95
|
+
return False
|
96
|
+
# update the existing firmware
|
97
|
+
existing_fw.firmware_file = new_fw.firmware_file
|
98
|
+
existing_fw.source = new_fw.source
|
99
|
+
existing_fw.description = new_fw.description
|
100
|
+
existing_fw.custom = custom
|
101
|
+
if custom:
|
102
|
+
existing_fw.custom_id = new_fw.custom_id
|
103
|
+
else:
|
104
|
+
session.add(new_fw)
|
105
|
+
session.commit()
|
106
|
+
|
107
|
+
return True
|
108
|
+
except sqlite3.DatabaseError as e:
|
109
|
+
raise MPFlashError(
|
110
|
+
f"Failed to add firmware {fw_info['firmware_file']}: {e}"
|
111
|
+
) from e
|
112
|
+
|
113
|
+
|
114
|
+
def copy_firmware(source: Path, fw_filename: Path, force: bool = False):
|
115
|
+
"""Add a firmware to the firmware folder.
|
116
|
+
stored in the port folder, with the same filename as the source.
|
117
|
+
"""
|
118
|
+
if fw_filename.exists() and not force:
|
119
|
+
log.error(f" {fw_filename} already exists. Use --force to overwrite")
|
120
|
+
return False
|
121
|
+
fw_filename.parent.mkdir(parents=True, exist_ok=True)
|
122
|
+
if isinstance(source, Path):
|
123
|
+
if not source.exists():
|
124
|
+
log.error(f"File {source} does not exist")
|
125
|
+
return False
|
126
|
+
# file copy
|
127
|
+
log.debug(f"Copy {source} to {fw_filename}")
|
128
|
+
shutil.copy(source, fw_filename)
|
129
|
+
return True
|
130
|
+
# TODO: handle github urls
|
131
|
+
# url = rewrite_url(source)
|
132
|
+
# if str(source).startswith("http://") or str(source).startswith("https://"):
|
133
|
+
# log.debug(f"Download {url} to {fw_filename}")
|
134
|
+
# response = requests.get(url)
|
135
|
+
|
136
|
+
# if response.status_code == 200:
|
137
|
+
# with open(fw_filename, "wb") as file:
|
138
|
+
# file.write(response.content)
|
139
|
+
# log.info("File downloaded and saved successfully.")
|
140
|
+
# return True
|
141
|
+
# else:
|
142
|
+
# print("Failed to download the file.")
|
143
|
+
# return False
|
144
|
+
# return False
|
mpflash/custom/naming.py
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
import re
|
2
|
+
from pathlib import Path
|
3
|
+
from typing import Dict, List, Optional, Tuple, Union
|
4
|
+
|
5
|
+
import mpflash.basicgit as git
|
6
|
+
from mpflash.logger import log
|
7
|
+
|
8
|
+
|
9
|
+
def custom_fw_from_path(fw_path: Path) -> Dict[str, Union[str, int, bool]]:
|
10
|
+
"""Generate a custom name for the firmware file based on its path.
|
11
|
+
|
12
|
+
Args:
|
13
|
+
firmware_path: Path to firmware file
|
14
|
+
|
15
|
+
Returns:
|
16
|
+
Custom name for the firmware file
|
17
|
+
"""
|
18
|
+
repo_path = fw_path.expanduser().absolute().parent
|
19
|
+
port, board_id = port_and_boardid_from_path(fw_path)
|
20
|
+
if not port or not board_id:
|
21
|
+
raise ValueError(f"Could not extract port and board_id from path: {fw_path}")
|
22
|
+
if "wsl.localhost" in str(repo_path):
|
23
|
+
log.info("Accessing WSL path; please note that it may take a few seconds to get git info across filesystems")
|
24
|
+
version = git.get_local_tag(repo_path) or "unknown"
|
25
|
+
describe = git.get_git_describe(repo_path)
|
26
|
+
if describe:
|
27
|
+
build = extract_commit_count(describe)
|
28
|
+
else:
|
29
|
+
build = 0
|
30
|
+
branch = git.get_current_branch(repo_path)
|
31
|
+
if branch:
|
32
|
+
branch = branch.split("/")[-1] # Use only last part of the branch name (?)
|
33
|
+
build_str = f".{build}" if build > 0 else ""
|
34
|
+
branch_str = f"@{branch}" if branch else ""
|
35
|
+
new_fw_path = Path(port) / f"{board_id}{branch_str}-{version}{build_str}{fw_path.suffix}"
|
36
|
+
|
37
|
+
return {
|
38
|
+
"port": port,
|
39
|
+
"board_id": f"{board_id}",
|
40
|
+
"custom_id": f"{board_id}{branch_str}",
|
41
|
+
"version": version,
|
42
|
+
"build": build,
|
43
|
+
"custom": True,
|
44
|
+
"firmware_file": new_fw_path.as_posix(),
|
45
|
+
"source": fw_path.expanduser().absolute().as_uri() if isinstance(fw_path, Path) else fw_path, # Use URI for local files
|
46
|
+
}
|
47
|
+
|
48
|
+
|
49
|
+
def port_and_boardid_from_path(firmware_path: Path) -> Tuple[Optional[str], Optional[str]]:
|
50
|
+
"""Extract port and board_id from firmware path.
|
51
|
+
|
52
|
+
Args:
|
53
|
+
firmware_path: Path to firmware file
|
54
|
+
|
55
|
+
Returns:
|
56
|
+
Tuple of (port, board_id) or (None, None) if not found
|
57
|
+
"""
|
58
|
+
path_str = str(firmware_path).replace("\\", "/") # Normalize path for regex matching
|
59
|
+
|
60
|
+
# Pattern: /path/to/micropython/ports/{port}/build-{board_id}/firmware.ext
|
61
|
+
build_match = re.search(r"/ports/([^/]+)/build-([^/]+)/", path_str)
|
62
|
+
if build_match:
|
63
|
+
port = build_match.group(1)
|
64
|
+
board_id = build_match.group(2)
|
65
|
+
# Remove variant suffix (e.g., GENERIC_S3-SPIRAM_OCT -> GENERIC_S3)
|
66
|
+
board_id = board_id.split("-")[0]
|
67
|
+
return port, board_id
|
68
|
+
|
69
|
+
# Pattern: /path/to/micropython/ports/{port}/firmware.ext
|
70
|
+
port_match = re.search(r"/ports/([^/]+)/[^/]*firmware\.[^/]*$", path_str)
|
71
|
+
if port_match:
|
72
|
+
port = port_match.group(1)
|
73
|
+
return port, None
|
74
|
+
|
75
|
+
return None, None
|
76
|
+
|
77
|
+
|
78
|
+
def extract_commit_count(git_describe: str) -> int:
|
79
|
+
"""Extract commit count from git describe string.
|
80
|
+
|
81
|
+
Args:
|
82
|
+
git_describe: Git describe output like 'v1.26.0-preview-214-ga56a1eec7b-dirty'
|
83
|
+
|
84
|
+
Returns:
|
85
|
+
Commit count as integer or None if not found
|
86
|
+
"""
|
87
|
+
# Match patterns like v1.26.0-preview-214-g... or v1.26.0-214-g...
|
88
|
+
match = re.search(r"-(\d+)-g[a-f0-9]+", git_describe)
|
89
|
+
if match:
|
90
|
+
return int(match.group(1))
|
91
|
+
return 0
|