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/list.py
CHANGED
@@ -16,7 +16,7 @@ def show_mcus(
|
|
16
16
|
refresh: bool = True,
|
17
17
|
*,
|
18
18
|
title_style="magenta",
|
19
|
-
header_style="bold magenta",
|
19
|
+
header_style="bold magenta",
|
20
20
|
):
|
21
21
|
console.print(mcu_table(conn_mcus, title, refresh, title_style=title_style, header_style=header_style))
|
22
22
|
|
@@ -38,7 +38,7 @@ def mcu_table(
|
|
38
38
|
):
|
39
39
|
"""
|
40
40
|
builds a rich table with the connected boards information
|
41
|
-
The columns of the table are adjusted to the terminal width > 90
|
41
|
+
The columns of the table are adjusted to the terminal width > 90
|
42
42
|
the columns are :
|
43
43
|
Narrow Wide
|
44
44
|
- Serial Yes Yes
|
@@ -83,7 +83,7 @@ def mcu_table(
|
|
83
83
|
table.add_column("Port")
|
84
84
|
table.add_column("Board", overflow="fold")
|
85
85
|
if needs_variant:
|
86
|
-
table.add_column("Variant")
|
86
|
+
table.add_column("Variant")
|
87
87
|
if is_wide:
|
88
88
|
table.add_column("CPU")
|
89
89
|
table.add_column("Version", overflow="fold", min_width=5, max_width=16)
|
mpflash/logger.py
CHANGED
@@ -1,4 +1,12 @@
|
|
1
|
-
"""
|
1
|
+
"""
|
2
|
+
Logger setup for CLI tools with Unicode-safe output.
|
3
|
+
|
4
|
+
Ensures log messages are compatible with the current console encoding.
|
5
|
+
Removes or replaces Unicode icons if the encoding is not UTF-8.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import functools
|
9
|
+
import sys
|
2
10
|
|
3
11
|
from loguru import logger as log
|
4
12
|
from rich.console import Console
|
@@ -7,12 +15,26 @@ from .config import config
|
|
7
15
|
|
8
16
|
console = Console()
|
9
17
|
|
18
|
+
# Detect if the output encoding supports Unicode (UTF-8)
|
19
|
+
@functools.lru_cache(maxsize=1)
|
20
|
+
def _is_utf8_encoding() -> bool:
|
21
|
+
try:
|
22
|
+
encoding = getattr(sys.stdout, "encoding", None)
|
23
|
+
if encoding is None:
|
24
|
+
return False
|
25
|
+
return encoding.lower().replace("-", "") == "utf8"
|
26
|
+
except BaseException:
|
27
|
+
return False
|
10
28
|
|
11
29
|
def _log_formatter(record: dict) -> str:
|
12
|
-
"""
|
30
|
+
"""
|
31
|
+
Log message formatter for loguru and rich.
|
32
|
+
|
33
|
+
Removes Unicode icons if console encoding is not UTF-8.
|
34
|
+
"""
|
13
35
|
color_map = {
|
14
|
-
"TRACE": "
|
15
|
-
"DEBUG": "
|
36
|
+
"TRACE": "cyan",
|
37
|
+
"DEBUG": "orange",
|
16
38
|
"INFO": "bold",
|
17
39
|
"SUCCESS": "bold green",
|
18
40
|
"WARNING": "yellow",
|
@@ -20,11 +42,21 @@ def _log_formatter(record: dict) -> str:
|
|
20
42
|
"CRITICAL": "bold white on red",
|
21
43
|
}
|
22
44
|
lvl_color = color_map.get(record["level"].name, "cyan")
|
23
|
-
|
45
|
+
# Remove icon if not UTF-8
|
46
|
+
if _is_utf8_encoding():
|
47
|
+
icon = record["level"].icon
|
48
|
+
else:
|
49
|
+
icon = record["level"].name # fallback to text
|
50
|
+
# Insert color directly using f-string
|
51
|
+
return f"[not bold green]{{time:HH:mm:ss}}[/not bold green] | {icon} [{lvl_color}]{record['message']}[/{lvl_color}]"
|
52
|
+
|
24
53
|
|
54
|
+
def set_loglevel(loglevel: str) -> None:
|
55
|
+
"""
|
56
|
+
Set the log level for the logger.
|
25
57
|
|
26
|
-
|
27
|
-
"""
|
58
|
+
Ensures Unicode safety for log output.
|
59
|
+
"""
|
28
60
|
try:
|
29
61
|
log.remove()
|
30
62
|
except ValueError:
|
@@ -32,8 +64,10 @@ def set_loglevel(loglevel: str):
|
|
32
64
|
log.add(console.print, level=loglevel.upper(), colorize=False, format=_log_formatter) # type: ignore
|
33
65
|
|
34
66
|
|
35
|
-
def make_quiet():
|
36
|
-
"""
|
67
|
+
def make_quiet() -> None:
|
68
|
+
"""
|
69
|
+
Make the logger quiet.
|
70
|
+
"""
|
37
71
|
config.quiet = True
|
38
72
|
console.quiet = True
|
39
73
|
set_loglevel("CRITICAL")
|
mpflash/mpboard_id/__init__.py
CHANGED
@@ -3,15 +3,9 @@ Access to the micropython port and board information that is stored in the board
|
|
3
3
|
that is included in the module.
|
4
4
|
|
5
5
|
"""
|
6
|
-
|
7
|
-
from functools import lru_cache
|
8
|
-
from typing import List, Optional, Tuple
|
9
|
-
import importlib
|
10
6
|
from mpflash.errors import MPFlashError
|
11
|
-
from .board import Board
|
12
|
-
|
13
7
|
from mpflash.versions import clean_version
|
14
|
-
from .store import read_known_boardinfo
|
15
|
-
from .known import get_known_ports, get_known_boards_for_port
|
16
|
-
from .known import known_stored_boards, find_known_board
|
17
8
|
|
9
|
+
from .known import (find_known_board, get_known_boards_for_port, known_ports,
|
10
|
+
known_stored_boards)
|
11
|
+
from .resolve import resolve_board_ids
|
@@ -0,0 +1,56 @@
|
|
1
|
+
from typing import Dict, List, Optional
|
2
|
+
|
3
|
+
from loguru import logger as log
|
4
|
+
|
5
|
+
|
6
|
+
def alternate_board_names(board_id, port="") -> List[str]:
|
7
|
+
more = [board_id]
|
8
|
+
|
9
|
+
log.debug("try for renamed board_id")
|
10
|
+
|
11
|
+
if board_id.startswith("PICO"):
|
12
|
+
more.append(board_id.replace("PICO", "RPI_PICO"))
|
13
|
+
elif board_id.startswith("RPI_"):
|
14
|
+
more.append(board_id.replace("RPI_", ""))
|
15
|
+
elif board_id.startswith("GENERIC"):
|
16
|
+
if port:
|
17
|
+
more.append(board_id.replace("GENERIC", f"{port.upper()}_GENERIC"))
|
18
|
+
else:
|
19
|
+
# just add both of them
|
20
|
+
more.append(board_id.replace("GENERIC", f"ESP32_GENERIC"))
|
21
|
+
more.append(board_id.replace("GENERIC", f"ESP8266_GENERIC"))
|
22
|
+
elif board_id.startswith("ESP32_"):
|
23
|
+
more.append(board_id.replace("ESP32_", ""))
|
24
|
+
elif board_id.startswith("ESP8266_"):
|
25
|
+
more.append(board_id.replace("ESP8266_", ""))
|
26
|
+
|
27
|
+
# VARIANT
|
28
|
+
variant_suffixes = ["SPIRAM", "THREAD"]
|
29
|
+
for board in more:
|
30
|
+
if any(suffix in board for suffix in variant_suffixes):
|
31
|
+
for suffix in variant_suffixes:
|
32
|
+
if board.endswith(f"_{suffix}"):
|
33
|
+
more.append(board.replace(f"_{suffix}", ""))
|
34
|
+
# more.append(board.replace(f"_{suffix}", f"-{suffix}"))
|
35
|
+
break # first one found
|
36
|
+
|
37
|
+
return more
|
38
|
+
|
39
|
+
|
40
|
+
def add_renamed_boards(boards: List[str]) -> List[str]:
|
41
|
+
"""
|
42
|
+
Adds the renamed boards to the list of boards.
|
43
|
+
|
44
|
+
Args:
|
45
|
+
boards : The list of boards to add the renamed boards to.
|
46
|
+
|
47
|
+
Returns:
|
48
|
+
List[str]: The list of boards with the renamed boards added.
|
49
|
+
"""
|
50
|
+
|
51
|
+
_boards = boards.copy()
|
52
|
+
for board in boards:
|
53
|
+
_boards.extend(alternate_board_names(board))
|
54
|
+
if board != board.upper():
|
55
|
+
_boards.extend(alternate_board_names(board.upper()))
|
56
|
+
return _boards
|
mpflash/mpboard_id/board_id.py
CHANGED
@@ -2,18 +2,14 @@
|
|
2
2
|
Translate board description to board designator
|
3
3
|
"""
|
4
4
|
|
5
|
-
|
6
|
-
import re
|
7
|
-
import sqlite3
|
8
|
-
from pathlib import Path
|
5
|
+
|
9
6
|
from typing import List, Optional
|
10
7
|
|
11
|
-
from mpflash.
|
8
|
+
from mpflash.db.core import Session
|
9
|
+
from mpflash.db.models import Board
|
12
10
|
from mpflash.errors import MPFlashError
|
13
11
|
from mpflash.logger import log
|
14
|
-
from mpflash.
|
15
|
-
from mpflash.mpboard_id.store import read_known_boardinfo
|
16
|
-
from mpflash.versions import clean_version, get_preview_mp_version, get_stable_mp_version
|
12
|
+
from mpflash.versions import clean_version
|
17
13
|
|
18
14
|
|
19
15
|
def find_board_id_by_description(
|
@@ -21,7 +17,6 @@ def find_board_id_by_description(
|
|
21
17
|
short_descr: str,
|
22
18
|
*,
|
23
19
|
version: str,
|
24
|
-
board_info: Optional[Path] = None,
|
25
20
|
) -> Optional[str]:
|
26
21
|
"""Find the MicroPython BOARD_ID based on the description in the firmware"""
|
27
22
|
version = clean_version(version) if version else ""
|
@@ -29,7 +24,6 @@ def find_board_id_by_description(
|
|
29
24
|
boards = _find_board_id_by_description(
|
30
25
|
descr=descr,
|
31
26
|
short_descr=short_descr,
|
32
|
-
db_path=board_info,
|
33
27
|
version=version,
|
34
28
|
)
|
35
29
|
if not boards:
|
@@ -37,7 +31,6 @@ def find_board_id_by_description(
|
|
37
31
|
boards = _find_board_id_by_description(
|
38
32
|
descr=descr,
|
39
33
|
short_descr=short_descr,
|
40
|
-
db_path=board_info,
|
41
34
|
version="%", # any version
|
42
35
|
)
|
43
36
|
return boards[0].board_id if boards else None
|
@@ -51,7 +44,6 @@ def _find_board_id_by_description(
|
|
51
44
|
short_descr: str,
|
52
45
|
version: Optional[str] = None,
|
53
46
|
variant: str = "",
|
54
|
-
db_path: Optional[Path] = None,
|
55
47
|
):
|
56
48
|
short_descr = short_descr or ""
|
57
49
|
boards: List[Board] = []
|
@@ -63,90 +55,15 @@ def _find_board_id_by_description(
|
|
63
55
|
descriptions.append(descr[8:])
|
64
56
|
descriptions.append(short_descr[8:])
|
65
57
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
*
|
74
|
-
FROM board_downloaded
|
75
|
-
WHERE
|
76
|
-
board_id IN (
|
77
|
-
SELECT DISTINCT board_id
|
78
|
-
FROM board_downloaded
|
79
|
-
WHERE description IN {str(tuple(descriptions))}
|
80
|
-
)
|
81
|
-
AND version like '{version}'
|
82
|
-
AND variant like '{variant}'
|
83
|
-
"""
|
84
|
-
cursor.execute(qry)
|
85
|
-
rows = cursor.fetchall()
|
86
|
-
for row in rows:
|
87
|
-
r = dict(row)
|
58
|
+
with Session() as session:
|
59
|
+
qry = session.query(Board).filter(
|
60
|
+
Board.description.in_(descriptions),
|
61
|
+
Board.version.like(version),
|
62
|
+
Board.variant.like(variant),
|
63
|
+
)
|
64
|
+
boards = qry.all()
|
88
65
|
|
89
|
-
boards.append(Board.from_dict(dict(row)))
|
90
|
-
except sqlite3.OperationalError as e:
|
91
|
-
raise MPFlashError("Database error") from e
|
92
66
|
if not boards:
|
93
67
|
raise MPFlashError(f"No board info found for description '{descr}' or '{short_descr}'")
|
94
68
|
return boards
|
95
69
|
|
96
|
-
|
97
|
-
@functools.lru_cache(maxsize=20)
|
98
|
-
def _find_board_id_by_description_xx(
|
99
|
-
*,
|
100
|
-
descr: str,
|
101
|
-
short_descr: str,
|
102
|
-
version: Optional[str] = None,
|
103
|
-
board_info: Optional[Path] = None,
|
104
|
-
):
|
105
|
-
"""
|
106
|
-
Find the MicroPython BOARD_ID based on the description in the firmware
|
107
|
-
using the pre-built board_info.json file
|
108
|
-
|
109
|
-
Parameters:
|
110
|
-
descr: str
|
111
|
-
Description of the board
|
112
|
-
short_descr: str
|
113
|
-
Short description of the board (optional)
|
114
|
-
version: str
|
115
|
-
Version of the MicroPython firmware
|
116
|
-
board_info: Path
|
117
|
-
Path to the board_info.json file (optional)
|
118
|
-
|
119
|
-
"""
|
120
|
-
# Some functional overlap with
|
121
|
-
# src\mpflash\mpflash\mpboard_id\__init__.py find_known_board
|
122
|
-
|
123
|
-
candidate_boards = read_known_boardinfo(board_info)
|
124
|
-
if not short_descr and " with " in descr:
|
125
|
-
short_descr = descr.split(" with ")[0]
|
126
|
-
if version:
|
127
|
-
# filter for matching version
|
128
|
-
if version in ("stable"):
|
129
|
-
version = get_stable_mp_version()
|
130
|
-
if version in ("preview", "master"):
|
131
|
-
version = get_preview_mp_version()
|
132
|
-
known_versions = sorted({b.version for b in candidate_boards})
|
133
|
-
if version not in known_versions:
|
134
|
-
log.trace(known_versions)
|
135
|
-
log.debug(f"Version {version} not found in board info, using latest stable version {get_stable_mp_version()}")
|
136
|
-
version = ".".join(get_stable_mp_version().split(".")[:2]) # take only major.minor
|
137
|
-
if version_matches := [b for b in candidate_boards if b.version.startswith(version)]:
|
138
|
-
candidate_boards = version_matches
|
139
|
-
else:
|
140
|
-
raise MPFlashError(f"No board info found for version {version}")
|
141
|
-
# First try full match on description, then partial match
|
142
|
-
matches = [b for b in candidate_boards if b.description == descr]
|
143
|
-
if not matches and short_descr:
|
144
|
-
matches = [b for b in candidate_boards if b.description == short_descr]
|
145
|
-
if not matches:
|
146
|
-
# partial match (for added VARIANT)
|
147
|
-
matches = [b for b in candidate_boards if b.description.startswith(descr)]
|
148
|
-
if not matches and short_descr:
|
149
|
-
matches = [b for b in candidate_boards if b.description.startswith(short_descr)]
|
150
|
-
if not matches:
|
151
|
-
raise MPFlashError(f"No board info found for description '{descr}' or '{short_descr}'")
|
152
|
-
return sorted(matches, key=lambda x: x.version)
|
mpflash/mpboard_id/known.py
CHANGED
@@ -7,62 +7,50 @@ This module provides access to the board info and the known ports and boards."""
|
|
7
7
|
from functools import lru_cache
|
8
8
|
from typing import List, Optional, Tuple
|
9
9
|
|
10
|
-
from
|
10
|
+
from sqlalchemy import text
|
11
|
+
|
12
|
+
from mpflash.db.core import Session
|
13
|
+
from mpflash.db.models import Board
|
11
14
|
from mpflash.errors import MPFlashError
|
12
|
-
from mpflash.versions import clean_version
|
13
15
|
from mpflash.logger import log
|
16
|
+
from mpflash.versions import clean_version
|
14
17
|
|
15
|
-
from .board import Board
|
16
|
-
from .store import read_known_boardinfo
|
17
18
|
|
19
|
+
def known_ports(version: str = "") -> list[str]:
|
20
|
+
"""Return a list of known ports for a given version."""
|
21
|
+
version = clean_version(version) if version else "%%"
|
22
|
+
with Session() as session:
|
23
|
+
qry = text("SELECT distinct port FROM boards WHERE version like :version ORDER BY port;")
|
24
|
+
ports = session.execute(qry, {"version": version}).columns("port").fetchall()
|
25
|
+
return [row.port for row in ports]
|
18
26
|
|
19
|
-
def get_known_ports() -> List[str]:
|
20
|
-
# TODO: Filter for Version
|
21
|
-
log.warning("get_known_ports() is deprecated")
|
22
|
-
mp_boards = read_known_boardinfo()
|
23
|
-
# select the unique ports from info
|
24
|
-
ports = set({board.port for board in mp_boards if board.port})
|
25
|
-
return sorted(list(ports))
|
26
27
|
|
28
|
+
def known_versions(port: str = "") -> list[str]:
|
29
|
+
"""Return a list of known versions for a given port."""
|
30
|
+
port = port.strip() if port else "%%"
|
31
|
+
with Session() as session:
|
32
|
+
qry = text("SELECT distinct version FROM boards WHERE port like :port ORDER BY version;")
|
33
|
+
versions = session.execute(qry, {"port": port}).columns("version").fetchall()
|
34
|
+
return [row.version for row in versions]
|
27
35
|
|
28
|
-
|
36
|
+
|
37
|
+
def get_known_boards_for_port(port: str = "", versions: List[str] = []):
|
29
38
|
"""
|
30
39
|
Returns a list of boards for the given port and version(s)
|
31
40
|
|
32
41
|
port: The Micropython port to filter for
|
33
42
|
versions: Optional, The Micropython versions to filter for (actual versions required)
|
34
43
|
"""
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
versions.remove("preview")
|
46
|
-
versions.append("stable")
|
47
|
-
# filter for the port
|
48
|
-
if port:
|
49
|
-
mp_boards = [board for board in mp_boards if board.port == port]
|
50
|
-
if versions:
|
51
|
-
# make sure of the v prefix
|
52
|
-
versions = [clean_version(v) for v in versions]
|
53
|
-
# filter for the version(s)
|
54
|
-
mp_boards = [board for board in mp_boards if board.version in versions]
|
55
|
-
if not mp_boards and preview_or_stable:
|
56
|
-
# nothing found - perhaps there is a newer version for which we do not have the board info yet
|
57
|
-
# use the latest known version from the board info
|
58
|
-
mp_boards = read_known_boardinfo()
|
59
|
-
last_known_version = sorted({b.version for b in mp_boards})[-1]
|
60
|
-
mp_boards = [board for board in mp_boards if board.version == last_known_version]
|
61
|
-
|
62
|
-
return mp_boards
|
63
|
-
|
64
|
-
|
65
|
-
def known_stored_boards(port: str, versions: Optional[List[str]] = None) -> List[Tuple[str, str]]:
|
44
|
+
versions = [clean_version(v) for v in versions] if versions else []
|
45
|
+
with Session() as session:
|
46
|
+
qry = session.query(Board).filter(Board.port.like(port))
|
47
|
+
if versions:
|
48
|
+
qry = qry.filter(Board.version.in_(versions))
|
49
|
+
boards = qry.all()
|
50
|
+
return boards
|
51
|
+
|
52
|
+
|
53
|
+
def known_stored_boards(port: str, versions: List[str] = []) -> List[Tuple[str, str]]:
|
66
54
|
"""
|
67
55
|
Returns a list of tuples with the description and board name for the given port and version
|
68
56
|
|
@@ -71,22 +59,22 @@ def known_stored_boards(port: str, versions: Optional[List[str]] = None) -> List
|
|
71
59
|
"""
|
72
60
|
mp_boards = get_known_boards_for_port(port, versions)
|
73
61
|
|
74
|
-
boards = set({(f"{board.version} {board.description}", board.board_id) for board in mp_boards})
|
62
|
+
boards = set({(f"{board.version} {board.board_id:<30} {board.description}", board.board_id) for board in mp_boards})
|
75
63
|
return sorted(list(boards))
|
76
64
|
|
77
65
|
|
78
|
-
|
79
|
-
def find_known_board(board_id: str, version ="") -> Board:
|
66
|
+
def find_known_board(board_id: str, version="") -> Board:
|
80
67
|
"""Find the board for the given BOARD_ID or 'board description' and return the board info as a Board object"""
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
68
|
+
with Session() as session:
|
69
|
+
qry = session.query(Board).filter(Board.board_id == board_id)
|
70
|
+
if version:
|
71
|
+
qry = qry.filter(Board.version == version)
|
72
|
+
board = qry.first()
|
73
|
+
if not board:
|
74
|
+
# if no board found, try to find it by description
|
75
|
+
qry = session.query(Board).filter(Board.description == board_id)
|
76
|
+
if version:
|
77
|
+
qry = qry.filter(Board.version == version)
|
78
|
+
if board:
|
79
|
+
return board
|
92
80
|
raise MPFlashError(f"Board {board_id} not found")
|
@@ -0,0 +1,19 @@
|
|
1
|
+
from logging import log
|
2
|
+
from mpflash.common import Params
|
3
|
+
from mpflash.mpboard_id import find_known_board
|
4
|
+
|
5
|
+
|
6
|
+
def resolve_board_ids(params: Params):
|
7
|
+
"""Resolve board descriptions to board_id, and remove empty strings from list of boards"""
|
8
|
+
for board_id in params.boards:
|
9
|
+
if board_id == "":
|
10
|
+
params.boards.remove(board_id)
|
11
|
+
continue
|
12
|
+
if " " in board_id:
|
13
|
+
try:
|
14
|
+
if info := find_known_board(board_id):
|
15
|
+
log.info(f"Resolved board description: {info.board_id}")
|
16
|
+
params.boards.remove(board_id)
|
17
|
+
params.boards.append(info.board_id)
|
18
|
+
except Exception as e:
|
19
|
+
log.warning(f"Unable to resolve board description: {e}")
|
@@ -170,7 +170,7 @@ class MPRemoteBoard:
|
|
170
170
|
self.cpu = info["cpu"]
|
171
171
|
self.arch = info["arch"]
|
172
172
|
self.mpy = info["mpy"]
|
173
|
-
self.description = descr = info["board"]
|
173
|
+
self.description = descr = info["description"] if 'description' in info else info["board"]
|
174
174
|
pos = descr.rfind(" with")
|
175
175
|
short_descr = descr[:pos].strip() if pos != -1 else ""
|
176
176
|
if info.get("board_id", None):
|
@@ -216,9 +216,10 @@ class MPRemoteBoard:
|
|
216
216
|
self.toml = {}
|
217
217
|
if rc in [OK]: # sometimes we get an -9 ???
|
218
218
|
try:
|
219
|
-
|
219
|
+
log.trace(result)
|
220
|
+
#Ok we have the info, now parse it
|
220
221
|
self.toml = tomllib.loads("".join(result))
|
221
|
-
log.debug(f"board_info.toml: {self.toml}")
|
222
|
+
log.debug(f"board_info.toml: {self.toml["description"]}")
|
222
223
|
except Exception as e:
|
223
224
|
log.error(f"Failed to parse board_info.toml: {e}")
|
224
225
|
else:
|
@@ -51,6 +51,7 @@ def _info(): # type:() -> dict[str, str]
|
|
51
51
|
try:
|
52
52
|
_machine = sys.implementation._machine if "_machine" in dir(sys.implementation) else os.uname().machine # type: ignore
|
53
53
|
info["board"] = _machine.strip()
|
54
|
+
info["description"] = _machine.strip()
|
54
55
|
si_build = sys.implementation._build if "_build" in dir(sys.implementation) else ""
|
55
56
|
if si_build:
|
56
57
|
info["board"] = si_build.split("-")[0]
|
mpflash/mpremoteboard/runner.py
CHANGED
@@ -91,11 +91,12 @@ def run(
|
|
91
91
|
)
|
92
92
|
except FileNotFoundError as e:
|
93
93
|
raise FileNotFoundError(f"Failed to start {cmd[0]}") from e
|
94
|
-
|
94
|
+
_timed_out = False
|
95
95
|
def timed_out():
|
96
|
-
|
96
|
+
_timed_out = True
|
97
97
|
if log_warnings:
|
98
98
|
log.warning(f"Command {cmd} timed out after {timeout} seconds")
|
99
|
+
proc.kill()
|
99
100
|
|
100
101
|
timer = Timer(timeout, timed_out)
|
101
102
|
try:
|
@@ -135,6 +136,8 @@ def run(
|
|
135
136
|
log.error(f"Failed to decode output: {e}")
|
136
137
|
finally:
|
137
138
|
timer.cancel()
|
139
|
+
if _timed_out:
|
140
|
+
raise TimeoutError(f"Command {cmd} timed out after {timeout} seconds")
|
138
141
|
|
139
142
|
proc.wait(timeout=1)
|
140
143
|
return proc.returncode or 0, output
|
mpflash/vendor/pydfu.py
CHANGED
@@ -338,7 +338,7 @@ def read_dfu_file(filename):
|
|
338
338
|
# I uint32_t size Size of the DFU file (without suffix)
|
339
339
|
# B uint8_t targets Number of targets
|
340
340
|
dfu_prefix, data = consume("<5sBIB", data, "signature version size targets")
|
341
|
-
print(" %(signature)s v%(version)d, image size: %(size)d,
|
341
|
+
print(" %(signature)s v%(version)d, image size: %(size)d, targets: %(targets)d" % dfu_prefix)
|
342
342
|
for target_idx in range(dfu_prefix["targets"]):
|
343
343
|
# Decode the Image Prefix
|
344
344
|
#
|
@@ -350,15 +350,14 @@ def read_dfu_file(filename):
|
|
350
350
|
# 255s char[255] name Name of the target
|
351
351
|
# I uint32_t size Size of image (without prefix)
|
352
352
|
# I uint32_t elements Number of elements in the image
|
353
|
-
img_prefix, data = consume("<6sBI255s2I", data, "signature altsetting named name
|
353
|
+
img_prefix, data = consume("<6sBI255s2I", data, "signature altsetting named name size elements")
|
354
354
|
img_prefix["num"] = target_idx
|
355
355
|
if img_prefix["named"]:
|
356
356
|
img_prefix["name"] = cstring(img_prefix["name"])
|
357
357
|
else:
|
358
358
|
img_prefix["name"] = ""
|
359
359
|
print(
|
360
|
-
|
361
|
-
'name: "%(name)s", size: %(size)d, elements: %(elements)d' % img_prefix
|
360
|
+
' %(signature)s %(num)d, alt setting: %(altsetting)s, name: "%(name)s", size: %(size)d, elements: %(elements)d' % img_prefix
|
362
361
|
)
|
363
362
|
|
364
363
|
target_size = img_prefix["size"]
|
@@ -395,7 +394,7 @@ def read_dfu_file(filename):
|
|
395
394
|
# B uint8_t len 16
|
396
395
|
# I uint32_t crc32 Checksum
|
397
396
|
dfu_suffix = named(struct.unpack("<4H3sBI", data[:16]), "device product vendor dfu ufd len crc")
|
398
|
-
print(" usb: %(vendor)04x:%(product)04x, device: 0x%(device)04x,
|
397
|
+
print(" usb: %(vendor)04x:%(product)04x, device: 0x%(device)04x, dfu: 0x%(dfu)04x, %(ufd)s, %(len)d, 0x%(crc)08x" % dfu_suffix)
|
399
398
|
if crc != dfu_suffix["crc"]:
|
400
399
|
print("CRC ERROR: computed crc32 is 0x%08x" % crc)
|
401
400
|
return
|
@@ -407,6 +406,20 @@ def read_dfu_file(filename):
|
|
407
406
|
return elements
|
408
407
|
|
409
408
|
|
409
|
+
def read_bin_file(filename, address):
|
410
|
+
"""Reads binary file(.bin) and stores it as single
|
411
|
+
element in element array just like read_dfu_file() would.
|
412
|
+
"""
|
413
|
+
element = {}
|
414
|
+
print("File: {}".format(filename))
|
415
|
+
with open(filename, "rb") as fin:
|
416
|
+
element["data"] = fin.read()
|
417
|
+
element["size"] = len(element["data"])
|
418
|
+
element["num"] = 0
|
419
|
+
element["addr"] = address
|
420
|
+
return [element]
|
421
|
+
|
422
|
+
|
410
423
|
class FilterDFU(object):
|
411
424
|
"""Class for filtering USB devices to identify devices which are in DFU
|
412
425
|
mode.
|
@@ -542,6 +555,13 @@ def main():
|
|
542
555
|
parser.add_argument("--pid", help="USB Product ID", type=lambda x: int(x, 0), default=None)
|
543
556
|
parser.add_argument("-m", "--mass-erase", help="mass erase device", action="store_true", default=False)
|
544
557
|
parser.add_argument("-u", "--upload", help="read file from DFU device", dest="path", default=False)
|
558
|
+
parser.add_argument(
|
559
|
+
"-a",
|
560
|
+
"--address",
|
561
|
+
help="specify target memory address(hex or dec) when uploading .bin files",
|
562
|
+
type=lambda x: int(x, 0),
|
563
|
+
default=None,
|
564
|
+
)
|
545
565
|
parser.add_argument("-x", "--exit", help="Exit DFU", action="store_true", default=False)
|
546
566
|
parser.add_argument("-v", "--verbose", help="increase output verbosity", action="store_true", default=False)
|
547
567
|
args = parser.parse_args()
|
@@ -568,7 +588,14 @@ def main():
|
|
568
588
|
command_run = True
|
569
589
|
|
570
590
|
if args.path:
|
571
|
-
|
591
|
+
if str(args.path).endswith(".bin"):
|
592
|
+
if args.address is None:
|
593
|
+
raise ValueError("Address must be specified using -a when uploading binary")
|
594
|
+
|
595
|
+
elements = read_bin_file(args.path, args.addr)
|
596
|
+
else:
|
597
|
+
elements = read_dfu_file(args.path)
|
598
|
+
|
572
599
|
if not elements:
|
573
600
|
print("No data in dfu file")
|
574
601
|
return
|
mpflash/versions.py
CHANGED
@@ -78,6 +78,7 @@ def is_version(version: str):
|
|
78
78
|
"""Check if the version is a valid version string"""
|
79
79
|
# Just in time import
|
80
80
|
from packaging.version import Version
|
81
|
+
|
81
82
|
return Version._regex.search(version) is not None
|
82
83
|
|
83
84
|
|
@@ -86,6 +87,7 @@ def micropython_versions(minver: str = "v1.20", reverse: bool = False, cache_it=
|
|
86
87
|
"""Get the list of micropython versions from github tags"""
|
87
88
|
# Just in time import
|
88
89
|
from packaging.version import parse
|
90
|
+
|
89
91
|
try:
|
90
92
|
gh_client = config.gh_client
|
91
93
|
repo = gh_client.get_repo("micropython/micropython")
|
@@ -126,6 +128,7 @@ def checkedout_version(path: Path, flat: bool = False) -> str:
|
|
126
128
|
"""Get the checked-out version of the repo"""
|
127
129
|
# Just in time import
|
128
130
|
import mpflash.basicgit as git
|
131
|
+
|
129
132
|
version = git.get_local_tag(path.as_posix())
|
130
133
|
if not version:
|
131
134
|
raise ValueError("No valid Tag found")
|