mpflash 1.24.7__py3-none-any.whl → 1.25.0__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/ask_input.py +7 -7
- mpflash/basicgit.py +26 -59
- mpflash/bootloader/__init__.py +0 -2
- mpflash/bootloader/detect.py +1 -2
- mpflash/bootloader/manual.py +0 -1
- mpflash/bootloader/touch1200.py +2 -2
- mpflash/cli_flash.py +28 -5
- mpflash/cli_group.py +1 -0
- mpflash/cli_list.py +7 -8
- mpflash/cli_main.py +2 -2
- mpflash/common.py +6 -14
- mpflash/config.py +30 -6
- mpflash/connected.py +6 -14
- mpflash/db/boards.py +63 -0
- mpflash/db/downloads.py +87 -0
- mpflash/download/__init__.py +221 -0
- mpflash/download/from_web.py +204 -0
- mpflash/downloaded.py +9 -34
- mpflash/flash/__init__.py +33 -18
- mpflash/flash/esp.py +39 -8
- mpflash/flash/uf2/linux.py +4 -9
- mpflash/flash/uf2/macos.py +1 -1
- mpflash/flash/uf2/windows.py +1 -1
- mpflash/flash/worklist.py +10 -5
- mpflash/list.py +17 -6
- mpflash/logger.py +1 -3
- mpflash/mpboard_id/__init__.py +6 -87
- mpflash/mpboard_id/add_boards.py +3 -8
- mpflash/mpboard_id/board.py +5 -2
- mpflash/mpboard_id/board_id.py +67 -7
- mpflash/mpboard_id/board_info.json +30974 -0
- mpflash/mpboard_id/board_info.zip +0 -0
- mpflash/mpboard_id/known.py +108 -0
- mpflash/mpboard_id/store.py +2 -3
- mpflash/mpremoteboard/__init__.py +85 -17
- mpflash/mpremoteboard/mpy_fw_info.py +23 -22
- mpflash/py.typed +0 -0
- mpflash/vendor/board_database.py +86 -1
- mpflash/vendor/click_aliases.py +64 -0
- mpflash/vendor/dfu.py +2 -8
- mpflash/vendor/pydfu.py +3 -14
- mpflash/versions.py +16 -6
- {mpflash-1.24.7.dist-info → mpflash-1.25.0.dist-info}/METADATA +71 -13
- mpflash-1.25.0.dist-info/RECORD +62 -0
- {mpflash-1.24.7.dist-info → mpflash-1.25.0.dist-info}/WHEEL +1 -1
- mpflash/download.py +0 -364
- mpflash-1.24.7.dist-info/RECORD +0 -56
- {mpflash-1.24.7.dist-info → mpflash-1.25.0.dist-info}/LICENSE +0 -0
- {mpflash-1.24.7.dist-info → mpflash-1.25.0.dist-info}/entry_points.txt +0 -0
Binary file
|
@@ -0,0 +1,108 @@
|
|
1
|
+
"""
|
2
|
+
KNOWN ports and boards are sourced from the micropython repo,
|
3
|
+
this info is stored in the board_info.json file
|
4
|
+
and is used to identify the board and port for flashing.
|
5
|
+
This module provides access to the board info and the known ports and boards."""
|
6
|
+
|
7
|
+
from functools import lru_cache
|
8
|
+
from typing import List, Optional, Tuple
|
9
|
+
|
10
|
+
from mpflash.db.boards import find_board_id, find_board_info
|
11
|
+
from mpflash.errors import MPFlashError
|
12
|
+
from mpflash.versions import clean_version
|
13
|
+
from mpflash.logger import log
|
14
|
+
|
15
|
+
from .board import Board
|
16
|
+
from .store import read_known_boardinfo
|
17
|
+
|
18
|
+
|
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 get_known_boards_for_port(port: Optional[str] = "", versions: Optional[List[str]] = None) -> List[Board]:
|
29
|
+
"""
|
30
|
+
Returns a list of boards for the given port and version(s)
|
31
|
+
|
32
|
+
port: The Micropython port to filter for
|
33
|
+
versions: Optional, The Micropython versions to filter for (actual versions required)
|
34
|
+
"""
|
35
|
+
mp_boards = read_known_boardinfo()
|
36
|
+
if versions:
|
37
|
+
preview_or_stable = "preview" in versions or "stable" in versions
|
38
|
+
else:
|
39
|
+
preview_or_stable = False
|
40
|
+
|
41
|
+
# filter for 'preview' as they are not in the board_info.json
|
42
|
+
# instead use stable version
|
43
|
+
versions = versions or []
|
44
|
+
if "preview" in versions:
|
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]]:
|
66
|
+
"""
|
67
|
+
Returns a list of tuples with the description and board name for the given port and version
|
68
|
+
|
69
|
+
port : str : The Micropython port to filter for
|
70
|
+
versions : List[str] : The Micropython versions to filter for (actual versions required)
|
71
|
+
"""
|
72
|
+
mp_boards = get_known_boards_for_port(port, versions)
|
73
|
+
|
74
|
+
boards = set({(f"{board.version} {board.description}", board.board_id) for board in mp_boards})
|
75
|
+
return sorted(list(boards))
|
76
|
+
|
77
|
+
|
78
|
+
@lru_cache(maxsize=20)
|
79
|
+
def find_known_board(board_id: str, version ="") -> Board:
|
80
|
+
"""Find the board for the given BOARD_ID or 'board description' and return the board info as a Board object"""
|
81
|
+
# Some functional overlap with:
|
82
|
+
# mpboard_id\board_id.py _find_board_id_by_description
|
83
|
+
# TODO: Refactor to search the SQLite DB instead of the JSON file
|
84
|
+
board_ids = find_board_id(board_id = board_id, version = version or "%")
|
85
|
+
boards = []
|
86
|
+
for board_id in board_ids:
|
87
|
+
# if we have a board_id, use it to find the board info
|
88
|
+
boards += [Board.from_dict(dict(r)) for r in find_board_info(board_id = board_id)]
|
89
|
+
|
90
|
+
|
91
|
+
# if board_ids:
|
92
|
+
# # if we have a board_id, use it to find the board info
|
93
|
+
# board_id = board_ids[0]
|
94
|
+
# info = read_known_boardinfo()
|
95
|
+
# for board_info in info:
|
96
|
+
# if board_id in (
|
97
|
+
# board_info.board_id,
|
98
|
+
# board_info.description,
|
99
|
+
# ) or board_info.description.startswith(board_id):
|
100
|
+
# if not board_info.cpu:
|
101
|
+
# # failsafe for older board_info.json files
|
102
|
+
# print(f"Board {board_id} has no CPU info, using port as CPU")
|
103
|
+
# if " with " in board_info.description:
|
104
|
+
# board_info.cpu = board_info.description.split(" with ")[-1]
|
105
|
+
# else:
|
106
|
+
# board_info.cpu = board_info.port
|
107
|
+
# return board_info
|
108
|
+
raise MPFlashError(f"Board {board_id} not found")
|
mpflash/mpboard_id/store.py
CHANGED
@@ -2,6 +2,7 @@ import functools
|
|
2
2
|
import zipfile
|
3
3
|
from pathlib import Path
|
4
4
|
from typing import Final, List, Optional
|
5
|
+
from mpflash.logger import log
|
5
6
|
|
6
7
|
import jsons
|
7
8
|
|
@@ -19,7 +20,6 @@ def write_boardinfo_json(board_list: List[Board], *, folder: Optional[Path] = No
|
|
19
20
|
board_list (List[Board]): The list of Board objects.
|
20
21
|
folder (Path): The folder where the compressed JSON file will be saved.
|
21
22
|
"""
|
22
|
-
import zipfile
|
23
23
|
|
24
24
|
if not folder:
|
25
25
|
folder = HERE
|
@@ -33,8 +33,7 @@ def write_boardinfo_json(board_list: List[Board], *, folder: Optional[Path] = No
|
|
33
33
|
@functools.lru_cache(maxsize=20)
|
34
34
|
def read_known_boardinfo(board_info: Optional[Path] = None) -> List[Board]:
|
35
35
|
"""Reads the board information from a JSON file in a zip file."""
|
36
|
-
|
37
|
-
import zipfile
|
36
|
+
log.warning("read_known_boardinfo() is deprecated")
|
38
37
|
|
39
38
|
if not board_info:
|
40
39
|
board_info = HERE / "board_info.zip"
|
@@ -2,6 +2,7 @@
|
|
2
2
|
Module to run mpremote commands, and retry on failure or timeout
|
3
3
|
"""
|
4
4
|
|
5
|
+
import contextlib
|
5
6
|
import sys
|
6
7
|
import time
|
7
8
|
from pathlib import Path
|
@@ -33,7 +34,9 @@ RETRIES = 3
|
|
33
34
|
class MPRemoteBoard:
|
34
35
|
"""Class to run mpremote commands"""
|
35
36
|
|
36
|
-
def __init__(
|
37
|
+
def __init__(
|
38
|
+
self, serialport: str = "", update: bool = False, *, location: str = ""
|
39
|
+
):
|
37
40
|
"""
|
38
41
|
Initialize MPRemoteBoard object.
|
39
42
|
|
@@ -41,6 +44,8 @@ class MPRemoteBoard:
|
|
41
44
|
- serialport (str): The serial port to connect to. Default is an empty string.
|
42
45
|
- update (bool): Whether to update the MCU information. Default is False.
|
43
46
|
"""
|
47
|
+
self._board_id = ""
|
48
|
+
|
44
49
|
self.serialport: str = serialport
|
45
50
|
self.firmware = {}
|
46
51
|
|
@@ -50,7 +55,6 @@ class MPRemoteBoard:
|
|
50
55
|
self.description = ""
|
51
56
|
self.version = ""
|
52
57
|
self.port = ""
|
53
|
-
self.board = ""
|
54
58
|
self.cpu = ""
|
55
59
|
self.arch = ""
|
56
60
|
self.mpy = ""
|
@@ -60,17 +64,46 @@ class MPRemoteBoard:
|
|
60
64
|
if update:
|
61
65
|
self.get_mcu_info()
|
62
66
|
|
67
|
+
###################################
|
68
|
+
# board_id := board[-variant]
|
69
|
+
@property
|
70
|
+
def board_id(self) -> str:
|
71
|
+
return self._board_id
|
72
|
+
|
73
|
+
@board_id.setter
|
74
|
+
def board_id(self, value: str) -> None:
|
75
|
+
self._board_id = value.rstrip("-")
|
76
|
+
|
77
|
+
@property
|
78
|
+
def board(self) -> str:
|
79
|
+
return self._board_id.split("-")[0]
|
80
|
+
|
81
|
+
@board.setter
|
82
|
+
def board(self, value: str) -> None:
|
83
|
+
self.board_id = f"{value}-{self.variant}" if self.variant else value
|
84
|
+
|
85
|
+
@property
|
86
|
+
def variant(self) -> str:
|
87
|
+
return self._board_id.split("-")[1] if "-" in self._board_id else ""
|
88
|
+
|
89
|
+
@variant.setter
|
90
|
+
def variant(self, value: str) -> None:
|
91
|
+
self.board_id = f"{self.board}-{value}"
|
92
|
+
|
93
|
+
###################################
|
63
94
|
def __str__(self):
|
64
95
|
"""
|
65
96
|
Return a string representation of the MPRemoteBoard object.
|
66
97
|
|
67
98
|
Returns:
|
68
|
-
- str:
|
99
|
+
- str: A human readable representation of the MCU.
|
69
100
|
"""
|
70
|
-
return f"MPRemoteBoard({self.serialport}, {self.family} {self.port}, {self.board}, {self.version})"
|
101
|
+
return f"MPRemoteBoard({self.serialport}, {self.family} {self.port}, {self.board}{f'-{self.variant}' if self.variant else ''}, {self.version})"
|
71
102
|
|
72
103
|
@staticmethod
|
73
|
-
def connected_boards(
|
104
|
+
def connected_boards(
|
105
|
+
bluetooth: bool = False, description: bool = False
|
106
|
+
) -> List[str]:
|
74
107
|
# TODO: rename to connected_comports
|
75
108
|
"""
|
76
109
|
Get a list of connected comports.
|
@@ -98,7 +131,10 @@ class MPRemoteBoard:
|
|
98
131
|
if sys.platform == "win32":
|
99
132
|
# Windows sort of comports by number - but fallback to device name
|
100
133
|
return sorted(
|
101
|
-
output,
|
134
|
+
output,
|
135
|
+
key=lambda x: int(x.split()[0][3:])
|
136
|
+
if x.split()[0][3:].isdigit()
|
137
|
+
else x,
|
102
138
|
)
|
103
139
|
# sort by device name
|
104
140
|
return sorted(output)
|
@@ -120,11 +156,11 @@ class MPRemoteBoard:
|
|
120
156
|
timeout=timeout,
|
121
157
|
resume=False, # Avoid restarts
|
122
158
|
)
|
123
|
-
if rc:
|
159
|
+
if rc not in (0, 1): ## WORKAROUND - SUDDEN RETURN OF 1 on success
|
124
160
|
log.debug(f"rc: {rc}, result: {result}")
|
125
161
|
raise ConnectionError(f"Failed to get mcu_info for {self.serialport}")
|
126
162
|
# Ok we have the info, now parse it
|
127
|
-
raw_info = result[0].strip()
|
163
|
+
raw_info = result[0].strip() if result else ""
|
128
164
|
if raw_info.startswith("{") and raw_info.endswith("}"):
|
129
165
|
info = eval(raw_info)
|
130
166
|
self.family = info["family"]
|
@@ -137,14 +173,19 @@ class MPRemoteBoard:
|
|
137
173
|
self.description = descr = info["board"]
|
138
174
|
pos = descr.rfind(" with")
|
139
175
|
short_descr = descr[:pos].strip() if pos != -1 else ""
|
140
|
-
if
|
141
|
-
|
142
|
-
|
143
|
-
self.board = board_name
|
176
|
+
if info.get("board_id", None):
|
177
|
+
# we have a board_id - so use that to get the board name
|
178
|
+
self.board_id = info["board_id"]
|
144
179
|
else:
|
145
|
-
self.
|
180
|
+
self.board_id = f"{info['board']}-{info.get('variant', '')}"
|
181
|
+
board_name = find_board_id_by_description(
|
182
|
+
descr, short_descr, version=self.version
|
183
|
+
)
|
184
|
+
self.board_id = board_name or "UNKNOWN_BOARD"
|
185
|
+
# TODO: Get the variant as well
|
146
186
|
# get the board_info.toml
|
147
187
|
self.get_board_info_toml()
|
188
|
+
# TODO: get board_id from the toml file if it exists
|
148
189
|
# now we know the board is connected
|
149
190
|
self.connected = True
|
150
191
|
|
@@ -168,7 +209,9 @@ class MPRemoteBoard:
|
|
168
209
|
log_errors=False,
|
169
210
|
)
|
170
211
|
except Exception as e:
|
171
|
-
raise ConnectionError(
|
212
|
+
raise ConnectionError(
|
213
|
+
f"Failed to get board_info.toml for {self.serialport}:"
|
214
|
+
) from e
|
172
215
|
# this is optional - so only parse if we got the file
|
173
216
|
self.toml = {}
|
174
217
|
if rc in [OK]: # sometimes we get an -9 ???
|
@@ -264,8 +307,33 @@ class MPRemoteBoard:
|
|
264
307
|
total=timeout,
|
265
308
|
):
|
266
309
|
time.sleep(1)
|
267
|
-
|
310
|
+
with contextlib.suppress(ConnectionError, MPFlashError):
|
268
311
|
self.get_mcu_info()
|
269
312
|
break
|
270
|
-
|
271
|
-
|
313
|
+
|
314
|
+
def to_dict(self) -> dict:
|
315
|
+
"""
|
316
|
+
Serialize the MPRemoteBoard object to JSON, including all attributes and readable properties.
|
317
|
+
|
318
|
+
Returns:
|
319
|
+
- str: A JSON string representation of the object.
|
320
|
+
"""
|
321
|
+
|
322
|
+
def get_properties(obj):
|
323
|
+
"""Helper function to get all readable properties."""
|
324
|
+
return {
|
325
|
+
name: getattr(obj, name)
|
326
|
+
for name in dir(obj.__class__)
|
327
|
+
if isinstance(getattr(obj.__class__, name, None), property)
|
328
|
+
}
|
329
|
+
|
330
|
+
# Combine instance attributes, readable properties, and private attributes
|
331
|
+
data = {**self.__dict__, **get_properties(self)}
|
332
|
+
|
333
|
+
# remove the path and firmware attibutes from the json output as they are always empty
|
334
|
+
del data["_board_id"] # dup of board_id
|
335
|
+
del data["connected"]
|
336
|
+
del data["path"]
|
337
|
+
del data["firmware"]
|
338
|
+
|
339
|
+
return data
|
@@ -1,9 +1,9 @@
|
|
1
|
-
#
|
1
|
+
# pragma: no cover
|
2
2
|
import os
|
3
3
|
import sys
|
4
4
|
|
5
5
|
|
6
|
-
def
|
6
|
+
def get_build(s):
|
7
7
|
# extract build from sys.version or os.uname().version if available
|
8
8
|
# sys.version: 'MicroPython v1.23.0-preview.6.g3d0b6276f'
|
9
9
|
# sys.implementation.version: 'v1.13-103-gb137d064e'
|
@@ -11,11 +11,11 @@ def _build(s):
|
|
11
11
|
return ""
|
12
12
|
s = s.split(" on ", 1)[0] if " on " in s else s
|
13
13
|
if s.startswith("v"):
|
14
|
-
if
|
14
|
+
if "-" not in s:
|
15
15
|
return ""
|
16
16
|
b = s.split("-")[1]
|
17
17
|
return b
|
18
|
-
if
|
18
|
+
if "-preview" not in s:
|
19
19
|
return ""
|
20
20
|
b = s.split("-preview")[1].split(".")[1]
|
21
21
|
return b
|
@@ -36,10 +36,9 @@ def _info(): # type:() -> dict[str, str]
|
|
36
36
|
"version": "",
|
37
37
|
"build": "",
|
38
38
|
"ver": "",
|
39
|
-
"port": (
|
40
|
-
"stm32" if sys.platform.startswith("pyb") else sys.platform
|
41
|
-
), # port: esp32 / win32 / linux / stm32
|
39
|
+
"port": ("stm32" if sys.platform.startswith("pyb") else sys.platform), # port: esp32 / win32 / linux / stm32
|
42
40
|
"board": "GENERIC",
|
41
|
+
"_build": "",
|
43
42
|
"cpu": "",
|
44
43
|
"mpy": "",
|
45
44
|
"arch": "",
|
@@ -50,29 +49,32 @@ def _info(): # type:() -> dict[str, str]
|
|
50
49
|
except AttributeError:
|
51
50
|
pass
|
52
51
|
try:
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
info["
|
52
|
+
_machine = sys.implementation._machine if "_machine" in dir(sys.implementation) else os.uname().machine # type: ignore
|
53
|
+
info["board"] = _machine.strip()
|
54
|
+
si_build = sys.implementation._build if "_build" in dir(sys.implementation) else ""
|
55
|
+
if si_build:
|
56
|
+
info["board"] = si_build.split("-")[0]
|
57
|
+
info["variant"] = si_build.split("-")[1] if "-" in si_build else ""
|
58
|
+
info["board_id"] = si_build
|
59
|
+
info["cpu"] = _machine.split("with")[-1].strip() if "with" in _machine else ""
|
60
60
|
info["mpy"] = (
|
61
61
|
sys.implementation._mpy
|
62
62
|
if "_mpy" in dir(sys.implementation)
|
63
|
-
else sys.implementation.mpy
|
63
|
+
else sys.implementation.mpy
|
64
|
+
if "mpy" in dir(sys.implementation)
|
65
|
+
else ""
|
64
66
|
)
|
65
67
|
except (AttributeError, IndexError):
|
66
68
|
pass
|
67
69
|
|
68
70
|
try:
|
69
71
|
if hasattr(sys, "version"):
|
70
|
-
info["build"] =
|
72
|
+
info["build"] = get_build(sys.version)
|
71
73
|
elif hasattr(os, "uname"):
|
72
|
-
info["build"] =
|
74
|
+
info["build"] = get_build(os.uname()[3]) # type: ignore
|
73
75
|
if not info["build"]:
|
74
76
|
# extract build from uname().release if available
|
75
|
-
info["build"] =
|
77
|
+
info["build"] = get_build(os.uname()[2]) # type: ignore
|
76
78
|
except (AttributeError, IndexError):
|
77
79
|
pass
|
78
80
|
# avoid build hashes
|
@@ -81,7 +83,7 @@ def _info(): # type:() -> dict[str, str]
|
|
81
83
|
|
82
84
|
if info["version"] == "" and sys.platform not in ("unix", "win32"):
|
83
85
|
try:
|
84
|
-
u = os.uname()
|
86
|
+
u = os.uname() # type: ignore
|
85
87
|
info["version"] = u.release
|
86
88
|
except (IndexError, AttributeError, TypeError):
|
87
89
|
pass
|
@@ -106,8 +108,7 @@ def _info(): # type:() -> dict[str, str]
|
|
106
108
|
if (
|
107
109
|
info["version"]
|
108
110
|
and info["version"].endswith(".0")
|
109
|
-
and info["version"]
|
110
|
-
>= "1.10.0" # versions from 1.10.0 to 1.20.0 do not have a micro .0
|
111
|
+
and info["version"] >= "1.10.0" # versions from 1.10.0 to 1.20.0 do not have a micro .0
|
111
112
|
and info["version"] <= "1.19.9"
|
112
113
|
):
|
113
114
|
# drop the .0 for newer releases
|
@@ -149,4 +150,4 @@ def _info(): # type:() -> dict[str, str]
|
|
149
150
|
|
150
151
|
|
151
152
|
print(_info())
|
152
|
-
del _info,
|
153
|
+
del _info, get_build, _version_str
|
mpflash/py.typed
ADDED
File without changes
|
mpflash/vendor/board_database.py
CHANGED
@@ -37,7 +37,9 @@ from __future__ import annotations
|
|
37
37
|
import json
|
38
38
|
from dataclasses import dataclass, field
|
39
39
|
from glob import glob
|
40
|
+
from os import path
|
40
41
|
from pathlib import Path
|
42
|
+
import re
|
41
43
|
|
42
44
|
|
43
45
|
@dataclass(order=True)
|
@@ -52,6 +54,15 @@ class Variant:
|
|
52
54
|
"""
|
53
55
|
board: Board = field(repr=False)
|
54
56
|
|
57
|
+
@property
|
58
|
+
def description(self) -> str:
|
59
|
+
"""
|
60
|
+
Description of the board, if available.
|
61
|
+
Example: "Pyboard v1.1 with STM32F4"
|
62
|
+
"""
|
63
|
+
return description_from_source(self.board.path, self.name) or self.board.description
|
64
|
+
# f"{self.board.description}-{self.name}"
|
65
|
+
|
55
66
|
|
56
67
|
@dataclass(order=True)
|
57
68
|
class Board:
|
@@ -93,6 +104,12 @@ class Board:
|
|
93
104
|
"""
|
94
105
|
port: Port | None = field(default=None, compare=False)
|
95
106
|
|
107
|
+
path: str = ""
|
108
|
+
"""
|
109
|
+
the relative path to the boards files.
|
110
|
+
Example: "ports/stm32/boards/PYBV11"
|
111
|
+
"""
|
112
|
+
|
96
113
|
@staticmethod
|
97
114
|
def factory(filename_json: Path) -> Board:
|
98
115
|
with filename_json.open() as f:
|
@@ -101,18 +118,27 @@ class Board:
|
|
101
118
|
board = Board(
|
102
119
|
name=filename_json.parent.name,
|
103
120
|
variants=[],
|
104
|
-
url=board_json["url"],
|
121
|
+
url=board_json["url"] if "url" in board_json else "", # fix missing url
|
105
122
|
mcu=board_json["mcu"],
|
106
123
|
product=board_json["product"],
|
107
124
|
vendor=board_json["vendor"],
|
108
125
|
images=board_json["images"],
|
109
126
|
deploy=board_json["deploy"],
|
127
|
+
path=filename_json.parent.as_posix(),
|
110
128
|
)
|
111
129
|
board.variants.extend(
|
112
130
|
sorted([Variant(*v, board) for v in board_json.get("variants", {}).items()]) # type: ignore
|
113
131
|
)
|
114
132
|
return board
|
115
133
|
|
134
|
+
@property
|
135
|
+
def description(self) -> str:
|
136
|
+
"""
|
137
|
+
Description of the board, if available.
|
138
|
+
Example: "Pyboard v1.1 with STM32F4"
|
139
|
+
"""
|
140
|
+
return description_from_source(self.path, "") or self.name
|
141
|
+
|
116
142
|
|
117
143
|
@dataclass(order=True)
|
118
144
|
class Port:
|
@@ -140,6 +166,8 @@ class Database:
|
|
140
166
|
|
141
167
|
def __post_init__(self) -> None:
|
142
168
|
mpy_dir = self.mpy_root_directory
|
169
|
+
if not mpy_dir.is_dir():
|
170
|
+
raise ValueError(f"Invalid path to micropython directory: {mpy_dir}")
|
143
171
|
# Take care to avoid using Path.glob! Performance was 15x slower.
|
144
172
|
for p in glob(f"{mpy_dir}/ports/**/boards/**/board.json"):
|
145
173
|
filename_json = Path(p)
|
@@ -176,6 +204,7 @@ class Database:
|
|
176
204
|
"",
|
177
205
|
[],
|
178
206
|
[],
|
207
|
+
path=path.as_posix(),
|
179
208
|
)
|
180
209
|
board.variants = [Variant(v, "", board) for v in variant_names]
|
181
210
|
port = Port(special_port_name, {special_port_name: board})
|
@@ -183,3 +212,59 @@ class Database:
|
|
183
212
|
|
184
213
|
self.ports[special_port_name] = port
|
185
214
|
self.boards[board.name] = board
|
215
|
+
|
216
|
+
|
217
|
+
# look for all mpconfigboard.h files and extract the board name
|
218
|
+
# from the #define MICROPY_HW_BOARD_NAME "PYBD_SF6"
|
219
|
+
# and the #define MICROPY_HW_MCU_NAME "STM32F767xx"
|
220
|
+
RE_H_MICROPY_HW_BOARD_NAME = re.compile(r"#define\s+MICROPY_HW_BOARD_NAME\s+\"(.+)\"")
|
221
|
+
RE_H_MICROPY_HW_MCU_NAME = re.compile(r"#define\s+MICROPY_HW_MCU_NAME\s+\"(.+)\"")
|
222
|
+
# find boards and variants in the mpconfigboard*.cmake files
|
223
|
+
RE_CMAKE_MICROPY_HW_BOARD_NAME = re.compile(r"MICROPY_HW_BOARD_NAME\s?=\s?\"(?P<variant>[\w\s\S]*)\"")
|
224
|
+
RE_CMAKE_MICROPY_HW_MCU_NAME = re.compile(r"MICROPY_HW_MCU_NAME\s?=\s?\"(?P<variant>[\w\s\S]*)\"")
|
225
|
+
|
226
|
+
|
227
|
+
def description_from_source(board_path: str | Path, variant: str = "") -> str:
|
228
|
+
"""Get the board's description from the header or make files."""
|
229
|
+
return description_from_header(board_path, variant) or description_from_cmake(board_path, variant)
|
230
|
+
|
231
|
+
|
232
|
+
def description_from_header(board_path: str | Path, variant: str = "") -> str:
|
233
|
+
"""Get the board's description from the mpconfigboard.h file."""
|
234
|
+
|
235
|
+
mpconfig_path = path.join(board_path, f"mpconfigboard_{variant}.h" if variant else "mpconfigboard.h")
|
236
|
+
if not path.exists(mpconfig_path):
|
237
|
+
return f""
|
238
|
+
|
239
|
+
with open(mpconfig_path, "r") as f:
|
240
|
+
board_name = mcu_name = "-"
|
241
|
+
found = 0
|
242
|
+
for line in f:
|
243
|
+
if match := RE_H_MICROPY_HW_BOARD_NAME.match(line):
|
244
|
+
board_name = match[1]
|
245
|
+
found += 1
|
246
|
+
elif match := RE_H_MICROPY_HW_MCU_NAME.match(line):
|
247
|
+
mcu_name = match[1]
|
248
|
+
found += 1
|
249
|
+
if found == 2:
|
250
|
+
return f"{board_name} with {mcu_name}" if mcu_name != "-" else board_name
|
251
|
+
return board_name if found == 1 else ""
|
252
|
+
|
253
|
+
|
254
|
+
def description_from_cmake(board_path: str | Path, variant: str = "") -> str:
|
255
|
+
"""Get the board's description from the mpconfig[board|variant].cmake file."""
|
256
|
+
|
257
|
+
cmake_path = path.join(board_path, f"mpconfigvariant_{variant}.cmake" if variant else "mpconfigboard.cmake")
|
258
|
+
if not path.exists(cmake_path):
|
259
|
+
return f""
|
260
|
+
with open(cmake_path, "r") as f:
|
261
|
+
board_name = mcu_name = "-"
|
262
|
+
for line in f:
|
263
|
+
line = line.strip()
|
264
|
+
if match := RE_CMAKE_MICROPY_HW_BOARD_NAME.match(line):
|
265
|
+
description = match["variant"]
|
266
|
+
return description
|
267
|
+
elif match := RE_CMAKE_MICROPY_HW_MCU_NAME.match(line):
|
268
|
+
description = match["variant"]
|
269
|
+
return description
|
270
|
+
return ""
|
mpflash/vendor/click_aliases.py
CHANGED
@@ -10,6 +10,7 @@
|
|
10
10
|
# The above copyright notice and this permission notice shall be included in all
|
11
11
|
# copies or substantial portions of the Software.
|
12
12
|
# ------------------------------------------------------------------------------------
|
13
|
+
# Jos Verlinde - 2024
|
13
14
|
# modified to avoid conflcts with rich_click
|
14
15
|
|
15
16
|
# sourcery skip: assign-if-exp, use-named-expression
|
@@ -20,12 +21,36 @@ _click7 = click.__version__[0] >= "7"
|
|
20
21
|
|
21
22
|
|
22
23
|
class ClickAliasedGroup(click.RichGroup):
|
24
|
+
"""
|
25
|
+
A subclass of click.RichGroup that adds support for command aliases.
|
26
|
+
|
27
|
+
This class allows defining aliases for commands and groups, enabling users
|
28
|
+
to invoke commands using alternative names.
|
29
|
+
"""
|
30
|
+
|
23
31
|
def __init__(self, *args, **kwargs):
|
32
|
+
"""
|
33
|
+
Initialize the ClickAliasedGroup instance.
|
34
|
+
|
35
|
+
Args:
|
36
|
+
*args: Positional arguments passed to the superclass.
|
37
|
+
**kwargs: Keyword arguments passed to the superclass.
|
38
|
+
"""
|
24
39
|
super().__init__(*args, **kwargs)
|
25
40
|
self._commands = {}
|
26
41
|
self._aliases = {}
|
27
42
|
|
28
43
|
def add_command(self, *args, **kwargs):
|
44
|
+
"""
|
45
|
+
Add a command to the group, optionally with aliases.
|
46
|
+
|
47
|
+
Args:
|
48
|
+
*args: Positional arguments, typically the command instance and optionally its name.
|
49
|
+
**kwargs: Keyword arguments, may include 'aliases' as a list of alternative names.
|
50
|
+
|
51
|
+
Raises:
|
52
|
+
TypeError: If the command has no name.
|
53
|
+
"""
|
29
54
|
aliases = kwargs.pop("aliases", [])
|
30
55
|
super().add_command(*args, **kwargs)
|
31
56
|
if aliases:
|
@@ -40,6 +65,16 @@ class ClickAliasedGroup(click.RichGroup):
|
|
40
65
|
self._aliases[alias] = cmd.name
|
41
66
|
|
42
67
|
def command(self, *args, **kwargs):
|
68
|
+
"""
|
69
|
+
Decorator to define a new command with optional aliases.
|
70
|
+
|
71
|
+
Args:
|
72
|
+
*args: Positional arguments passed to the superclass decorator.
|
73
|
+
**kwargs: Keyword arguments, may include 'aliases' as a list of alternative names.
|
74
|
+
|
75
|
+
Returns:
|
76
|
+
Callable: A decorator function that registers the command and its aliases.
|
77
|
+
"""
|
43
78
|
aliases = kwargs.pop("aliases", [])
|
44
79
|
decorator = super().command(*args, **kwargs)
|
45
80
|
if not aliases:
|
@@ -56,6 +91,16 @@ class ClickAliasedGroup(click.RichGroup):
|
|
56
91
|
return _decorator
|
57
92
|
|
58
93
|
def group(self, *args, **kwargs):
|
94
|
+
"""
|
95
|
+
Decorator to define a new command group with optional aliases.
|
96
|
+
|
97
|
+
Args:
|
98
|
+
*args: Positional arguments passed to the superclass decorator.
|
99
|
+
**kwargs: Keyword arguments, may include 'aliases' as a list of alternative names.
|
100
|
+
|
101
|
+
Returns:
|
102
|
+
Callable: A decorator function that registers the group and its aliases.
|
103
|
+
"""
|
59
104
|
aliases = kwargs.pop("aliases", [])
|
60
105
|
decorator = super().group(*args, **kwargs)
|
61
106
|
if not aliases:
|
@@ -72,11 +117,30 @@ class ClickAliasedGroup(click.RichGroup):
|
|
72
117
|
return _decorator
|
73
118
|
|
74
119
|
def resolve_alias(self, cmd_name):
|
120
|
+
"""
|
121
|
+
Resolve a command alias to its original command name.
|
122
|
+
|
123
|
+
Args:
|
124
|
+
cmd_name (str): The command name or alias to resolve.
|
125
|
+
|
126
|
+
Returns:
|
127
|
+
str: The original command name if an alias is provided; otherwise, the input name.
|
128
|
+
"""
|
75
129
|
if cmd_name in self._aliases:
|
76
130
|
return self._aliases[cmd_name]
|
77
131
|
return cmd_name
|
78
132
|
|
79
133
|
def get_command(self, ctx, cmd_name):
|
134
|
+
"""
|
135
|
+
Retrieve a command by name or alias.
|
136
|
+
|
137
|
+
Args:
|
138
|
+
ctx (click.Context): The Click context object.
|
139
|
+
cmd_name (str): The command name or alias to retrieve.
|
140
|
+
|
141
|
+
Returns:
|
142
|
+
click.Command or None: The command object if found; otherwise, None.
|
143
|
+
"""
|
80
144
|
cmd_name = self.resolve_alias(cmd_name)
|
81
145
|
command = super().get_command(ctx, cmd_name)
|
82
146
|
if command:
|