mpflash 0.7.7__py3-none-any.whl → 0.8.1__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 +98 -0
- mpflash/ask_input.py +74 -111
- mpflash/cli_download.py +31 -21
- mpflash/cli_flash.py +64 -34
- mpflash/cli_group.py +5 -24
- mpflash/cli_list.py +39 -3
- mpflash/cli_main.py +16 -9
- mpflash/common.py +125 -12
- mpflash/connected.py +74 -0
- mpflash/download.py +82 -46
- mpflash/downloaded.py +9 -9
- mpflash/flash.py +2 -2
- mpflash/flash_esp.py +1 -1
- mpflash/flash_uf2.py +5 -5
- mpflash/flash_uf2_linux.py +1 -1
- mpflash/flash_uf2_macos.py +0 -2
- mpflash/list.py +4 -45
- mpflash/mpboard_id/__init__.py +28 -38
- mpflash/mpboard_id/add_boards.py +255 -0
- mpflash/mpboard_id/board.py +37 -0
- mpflash/mpboard_id/board_id.py +18 -23
- mpflash/mpboard_id/board_info.zip +0 -0
- mpflash/mpboard_id/store.py +42 -0
- mpflash/vendor/basicgit.py +288 -0
- mpflash/vendor/dfu.py +1 -0
- mpflash/vendor/versions.py +7 -3
- mpflash/worklist.py +70 -47
- {mpflash-0.7.7.dist-info → mpflash-0.8.1.dist-info}/METADATA +19 -7
- mpflash-0.8.1.dist-info/RECORD +47 -0
- mpflash/mpboard_id/Untitled-1.ipynb +0 -406
- mpflash/mpboard_id/board_info.csv +0 -2213
- mpflash/mpboard_id/board_info.json +0 -19910
- mpflash-0.7.7.dist-info/RECORD +0 -43
- {mpflash-0.7.7.dist-info → mpflash-0.8.1.dist-info}/LICENSE +0 -0
- {mpflash-0.7.7.dist-info → mpflash-0.8.1.dist-info}/WHEEL +0 -0
- {mpflash-0.7.7.dist-info → mpflash-0.8.1.dist-info}/entry_points.txt +0 -0
mpflash/mpboard_id/board_id.py
CHANGED
@@ -3,17 +3,14 @@ Translate board description to board designator
|
|
3
3
|
"""
|
4
4
|
|
5
5
|
import functools
|
6
|
-
import json
|
7
6
|
from pathlib import Path
|
8
7
|
from typing import Optional
|
9
8
|
|
10
9
|
from mpflash.errors import MPFlashError
|
10
|
+
from mpflash.logger import log
|
11
|
+
from mpflash.mpboard_id.store import read_known_boardinfo
|
11
12
|
from mpflash.vendor.versions import clean_version, get_stable_mp_version
|
12
13
|
|
13
|
-
###############################################################################################
|
14
|
-
HERE = Path(__file__).parent
|
15
|
-
###############################################################################################
|
16
|
-
|
17
14
|
|
18
15
|
def find_board_id_by_description(
|
19
16
|
descr: str,
|
@@ -31,45 +28,43 @@ def find_board_id_by_description(
|
|
31
28
|
board_info=board_info,
|
32
29
|
version=clean_version(version) if version else None,
|
33
30
|
)
|
34
|
-
return boards[-1]
|
31
|
+
return boards[-1].board_id
|
35
32
|
except MPFlashError:
|
36
33
|
return "UNKNOWN_BOARD"
|
37
34
|
|
38
35
|
|
39
36
|
@functools.lru_cache(maxsize=20)
|
40
37
|
def _find_board_id_by_description(
|
41
|
-
*,
|
38
|
+
*,
|
39
|
+
descr: str,
|
40
|
+
short_descr: str,
|
41
|
+
version: Optional[str] = None,
|
42
|
+
board_info: Optional[Path] = None,
|
42
43
|
):
|
43
44
|
"""
|
44
45
|
Find the MicroPython BOARD_ID based on the description in the firmware
|
45
46
|
using the pre-built board_info.json file
|
46
47
|
"""
|
47
|
-
if not board_info:
|
48
|
-
board_info = HERE / "board_info.json"
|
49
|
-
if not board_info.exists():
|
50
|
-
raise FileNotFoundError(f"Board info file not found: {board_info}")
|
51
48
|
|
52
|
-
candidate_boards =
|
49
|
+
candidate_boards = read_known_boardinfo(board_info)
|
53
50
|
|
54
51
|
if version:
|
55
52
|
# filter for matching version
|
56
53
|
if version in ("preview", "stable"):
|
57
54
|
# match last stable
|
58
55
|
version = get_stable_mp_version()
|
59
|
-
|
56
|
+
known_versions = sorted({b.version for b in candidate_boards})
|
57
|
+
if version not in known_versions:
|
58
|
+
# FIXME if latest stable is newer than the last version in the boardlist this will fail
|
59
|
+
log.trace(f"Version {version} not found in board info, using latest known version {known_versions[-1]}")
|
60
|
+
version = known_versions[-1]
|
61
|
+
version_matches = [b for b in candidate_boards if b.version.startswith(version)]
|
60
62
|
if not version_matches:
|
61
63
|
raise MPFlashError(f"No board info found for version {version}")
|
62
64
|
candidate_boards = version_matches
|
63
|
-
matches = [b for b in candidate_boards if b
|
65
|
+
matches = [b for b in candidate_boards if b.description == descr]
|
64
66
|
if not matches and short_descr:
|
65
|
-
matches = [b for b in candidate_boards if b
|
67
|
+
matches = [b for b in candidate_boards if b.description == short_descr]
|
66
68
|
if not matches:
|
67
69
|
raise MPFlashError(f"No board info found for description '{descr}' or '{short_descr}'")
|
68
|
-
return sorted(matches, key=lambda x: x
|
69
|
-
|
70
|
-
|
71
|
-
@functools.lru_cache(maxsize=20)
|
72
|
-
def _read_board_info(board_info):
|
73
|
-
with open(board_info, "r") as file:
|
74
|
-
info = json.load(file)
|
75
|
-
return info
|
70
|
+
return sorted(matches, key=lambda x: x.version)
|
Binary file
|
@@ -0,0 +1,42 @@
|
|
1
|
+
import functools
|
2
|
+
import zipfile
|
3
|
+
from pathlib import Path
|
4
|
+
from typing import List, Optional
|
5
|
+
|
6
|
+
import jsons
|
7
|
+
|
8
|
+
from mpflash.mpboard_id.board import Board
|
9
|
+
|
10
|
+
###############################################################################################
|
11
|
+
HERE = Path(__file__).parent
|
12
|
+
###############################################################################################
|
13
|
+
|
14
|
+
|
15
|
+
def write_boardinfo_json(board_list: List[Board], *, folder: Path):
|
16
|
+
"""Writes the board information to JSON and CSV files.
|
17
|
+
|
18
|
+
Args:
|
19
|
+
board_list (List[Board]): The list of Board objects.
|
20
|
+
"""
|
21
|
+
import zipfile
|
22
|
+
|
23
|
+
# create a zip file with the json file
|
24
|
+
with zipfile.ZipFile(folder / "board_info.zip", "w", compression=zipfile.ZIP_DEFLATED) as zipf:
|
25
|
+
# write the list to json file inside the zip
|
26
|
+
with zipf.open("board_info.json", "w") as fp:
|
27
|
+
fp.write(jsons.dumps(board_list, jdkwargs={"indent": 4}).encode())
|
28
|
+
|
29
|
+
|
30
|
+
@functools.lru_cache(maxsize=20)
|
31
|
+
def read_known_boardinfo(board_info: Optional[Path] = None) -> List[Board]:
|
32
|
+
|
33
|
+
if not board_info:
|
34
|
+
board_info = HERE / "board_info.zip"
|
35
|
+
if not board_info.exists():
|
36
|
+
raise FileNotFoundError(f"Board info file not found: {board_info}")
|
37
|
+
|
38
|
+
with zipfile.ZipFile(board_info, "r") as zf:
|
39
|
+
with zf.open("board_info.json", "r") as file:
|
40
|
+
info = jsons.loads(file.read().decode(encoding="utf-8"), List[Board])
|
41
|
+
|
42
|
+
return info
|
@@ -0,0 +1,288 @@
|
|
1
|
+
"""
|
2
|
+
Simple Git module, where needed via powershell
|
3
|
+
|
4
|
+
Some of the functions are based on the PyGithub module
|
5
|
+
"""
|
6
|
+
|
7
|
+
import os
|
8
|
+
import subprocess
|
9
|
+
from pathlib import Path
|
10
|
+
from typing import List, Optional, Union
|
11
|
+
|
12
|
+
import cachetools.func
|
13
|
+
from github import Auth, Github
|
14
|
+
from loguru import logger as log
|
15
|
+
from packaging.version import parse
|
16
|
+
|
17
|
+
# from stubber.utils.versions import SET_PREVIEW
|
18
|
+
|
19
|
+
# Token with no permissions to avoid throttling
|
20
|
+
# 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
|
21
|
+
PAT_NO_ACCESS = (
|
22
|
+
"github_pat" + "_11AAHPVFQ0qAkDnSUaMKSp" + "_ZkDl5NRRwBsUN6EYg9ahp1Dvj4FDDONnXVgimxC2EtpY7Q7BUKBoQ0Jq72X"
|
23
|
+
)
|
24
|
+
PAT = os.environ.get("GITHUB_TOKEN") or PAT_NO_ACCESS
|
25
|
+
GH_CLIENT = Github(auth=Auth.Token(PAT))
|
26
|
+
|
27
|
+
|
28
|
+
def _run_local_git(
|
29
|
+
cmd: List[str],
|
30
|
+
repo: Optional[Union[Path, str]] = None,
|
31
|
+
expect_stderr=False,
|
32
|
+
capture_output=True,
|
33
|
+
echo_output=True,
|
34
|
+
):
|
35
|
+
"run a external (git) command in the repo's folder and deal with some of the errors"
|
36
|
+
try:
|
37
|
+
if repo:
|
38
|
+
if isinstance(repo, str):
|
39
|
+
repo = Path(repo)
|
40
|
+
result = subprocess.run(
|
41
|
+
cmd, capture_output=capture_output, check=True, cwd=repo.absolute().as_posix(), encoding="utf-8"
|
42
|
+
)
|
43
|
+
else:
|
44
|
+
result = subprocess.run(cmd, capture_output=capture_output, check=True, encoding="utf-8")
|
45
|
+
except (NotADirectoryError, FileNotFoundError) as e: # pragma: no cover
|
46
|
+
return None
|
47
|
+
except subprocess.CalledProcessError as e: # pragma: no cover
|
48
|
+
# add some logging for github actions
|
49
|
+
log.error(f"{str(e)} : { e.stderr}")
|
50
|
+
return None
|
51
|
+
if result.stderr and result.stderr != b"":
|
52
|
+
stderr = result.stderr
|
53
|
+
if "cloning into" in stderr.lower():
|
54
|
+
# log.info(stderr)
|
55
|
+
expect_stderr = True
|
56
|
+
if "warning" in stderr.lower():
|
57
|
+
log.warning(stderr)
|
58
|
+
expect_stderr = True
|
59
|
+
elif capture_output and echo_output: # pragma: no cover
|
60
|
+
log.info(stderr)
|
61
|
+
if not expect_stderr:
|
62
|
+
raise ChildProcessError(stderr)
|
63
|
+
|
64
|
+
if result.returncode and result.returncode < 0:
|
65
|
+
raise ChildProcessError(result.stderr)
|
66
|
+
return result
|
67
|
+
|
68
|
+
|
69
|
+
def clone(remote_repo: str, path: Path, shallow: bool = False, tag: Optional[str] = None) -> bool:
|
70
|
+
"""git clone [--depth 1] [--branch <tag_name>] <remote> <directory>"""
|
71
|
+
cmd = ["git", "clone"]
|
72
|
+
if shallow:
|
73
|
+
cmd += ["--depth", "1"]
|
74
|
+
if tag in {"preview", "latest", "master"}:
|
75
|
+
tag = None
|
76
|
+
cmd += [remote_repo, "--branch", tag, str(path)] if tag else [remote_repo, str(path)]
|
77
|
+
if result := _run_local_git(cmd, expect_stderr=True, capture_output=False):
|
78
|
+
return result.returncode == 0
|
79
|
+
else:
|
80
|
+
return False
|
81
|
+
|
82
|
+
|
83
|
+
def get_local_tag(repo: Optional[Union[str, Path]] = None, abbreviate: bool = True) -> Union[str, None]:
|
84
|
+
"""
|
85
|
+
get the most recent git version tag of a local repo
|
86
|
+
repo Path should be in the form of : repo = "./repo/micropython"
|
87
|
+
|
88
|
+
returns the tag or None
|
89
|
+
"""
|
90
|
+
if not repo:
|
91
|
+
repo = Path(".")
|
92
|
+
elif isinstance(repo, str):
|
93
|
+
repo = Path(repo)
|
94
|
+
|
95
|
+
result = _run_local_git(
|
96
|
+
# ["git", "describe", "--tags"],
|
97
|
+
["git", "describe", "--tags", "--dirty", "--always", "--match", "v[1-9].*"],
|
98
|
+
repo=repo.as_posix(),
|
99
|
+
expect_stderr=True,
|
100
|
+
)
|
101
|
+
if not result:
|
102
|
+
return None
|
103
|
+
tag: str = result.stdout
|
104
|
+
tag = tag.replace("\r", "").replace("\n", "")
|
105
|
+
if not abbreviate or "-" not in tag:
|
106
|
+
return tag
|
107
|
+
if "-preview" in tag:
|
108
|
+
tag = tag.split("-preview")[0] + "-preview"
|
109
|
+
return tag
|
110
|
+
|
111
|
+
|
112
|
+
def get_local_tags(repo: Optional[Path] = None, minver: Optional[str] = None) -> List[str]:
|
113
|
+
"""
|
114
|
+
get list of all tags of a local repo
|
115
|
+
"""
|
116
|
+
if not repo:
|
117
|
+
repo = Path(".")
|
118
|
+
|
119
|
+
result = _run_local_git(["git", "tag", "-l"], repo=repo.as_posix(), expect_stderr=True)
|
120
|
+
if not result or result.returncode != 0:
|
121
|
+
return []
|
122
|
+
tags = result.stdout.replace("\r", "").split("\n")
|
123
|
+
tags = [tag for tag in tags if tag.startswith("v")]
|
124
|
+
if minver:
|
125
|
+
tags = [tag for tag in tags if parse(tag) >= parse(minver)]
|
126
|
+
return sorted(tags)
|
127
|
+
|
128
|
+
|
129
|
+
@cachetools.func.ttl_cache(maxsize=16, ttl=60) # 60 seconds
|
130
|
+
def get_tags(repo: str, minver: Optional[str] = None) -> List[str]:
|
131
|
+
"""
|
132
|
+
Get list of tag of a repote github repo
|
133
|
+
"""
|
134
|
+
if not repo or not isinstance(repo, str) or "/" not in repo: # type: ignore
|
135
|
+
return []
|
136
|
+
try:
|
137
|
+
gh_repo = GH_CLIENT.get_repo(repo)
|
138
|
+
except ConnectionError as e:
|
139
|
+
# TODO: unable to capture the exeption
|
140
|
+
log.warning(f"Unable to get tags - {e}")
|
141
|
+
return []
|
142
|
+
tags = [tag.name for tag in gh_repo.get_tags()]
|
143
|
+
if minver:
|
144
|
+
tags = [tag for tag in tags if parse(tag) >= parse(minver)]
|
145
|
+
return sorted(tags)
|
146
|
+
|
147
|
+
|
148
|
+
def checkout_tag(tag: str, repo: Optional[Union[str, Path]] = None) -> bool:
|
149
|
+
"""
|
150
|
+
checkout a specific git tag
|
151
|
+
"""
|
152
|
+
cmd = ["git", "checkout", tag, "--quiet", "--force"]
|
153
|
+
result = _run_local_git(cmd, repo=repo, expect_stderr=True, capture_output=True)
|
154
|
+
if not result:
|
155
|
+
return False
|
156
|
+
# actually a good result
|
157
|
+
msg = {result.stdout}
|
158
|
+
if msg != {""}:
|
159
|
+
log.warning(f"git message: {msg}")
|
160
|
+
return True
|
161
|
+
|
162
|
+
|
163
|
+
def sync_submodules(repo: Optional[Union[Path, str]] = None) -> bool:
|
164
|
+
"""
|
165
|
+
make sure any submodules are in sync
|
166
|
+
"""
|
167
|
+
cmds = [
|
168
|
+
["git", "submodule", "sync", "--quiet"],
|
169
|
+
# ["git", "submodule", "update", "--quiet"],
|
170
|
+
["git", "submodule", "update", "--init", "lib/micropython-lib"],
|
171
|
+
]
|
172
|
+
for cmd in cmds:
|
173
|
+
if result := _run_local_git(cmd, repo=repo, expect_stderr=True):
|
174
|
+
# actually a good result
|
175
|
+
log.debug(result.stderr)
|
176
|
+
else:
|
177
|
+
return False
|
178
|
+
return True
|
179
|
+
|
180
|
+
|
181
|
+
def checkout_commit(commit_hash: str, repo: Optional[Union[Path, str]] = None) -> bool:
|
182
|
+
"""
|
183
|
+
Checkout a specific commit
|
184
|
+
"""
|
185
|
+
cmd = ["git", "checkout", commit_hash, "--quiet", "--force"]
|
186
|
+
result = _run_local_git(cmd, repo=repo, expect_stderr=True)
|
187
|
+
if not result:
|
188
|
+
return False
|
189
|
+
# actually a good result
|
190
|
+
log.debug(result.stderr)
|
191
|
+
return True
|
192
|
+
|
193
|
+
|
194
|
+
def switch_tag(tag: Union[str, Path], repo: Optional[Union[Path, str]] = None) -> bool:
|
195
|
+
"""
|
196
|
+
switch to the specified version tag of a local repo
|
197
|
+
repo should be in the form of : path/.git
|
198
|
+
repo = '../micropython/.git'
|
199
|
+
returns None
|
200
|
+
"""
|
201
|
+
|
202
|
+
cmd = ["git", "switch", "--detach", tag, "--quiet", "--force"]
|
203
|
+
result = _run_local_git(cmd, repo=repo, expect_stderr=True)
|
204
|
+
if not result:
|
205
|
+
return False
|
206
|
+
# actually a good result
|
207
|
+
log.debug(result.stderr)
|
208
|
+
return True
|
209
|
+
|
210
|
+
|
211
|
+
def switch_branch(branch: str, repo: Optional[Union[Path, str]] = None) -> bool:
|
212
|
+
"""
|
213
|
+
switch to the specified branch in a local repo"
|
214
|
+
repo should be in the form of : path/.git
|
215
|
+
repo = '../micropython/.git'
|
216
|
+
returns None
|
217
|
+
"""
|
218
|
+
cmd = ["git", "switch", branch, "--quiet", "--force"]
|
219
|
+
result = _run_local_git(cmd, repo=repo, expect_stderr=True)
|
220
|
+
if not result:
|
221
|
+
return False
|
222
|
+
# actually a good result
|
223
|
+
log.debug(result.stderr)
|
224
|
+
return True
|
225
|
+
|
226
|
+
|
227
|
+
def fetch(repo: Union[Path, str]) -> bool:
|
228
|
+
"""
|
229
|
+
fetches a repo
|
230
|
+
repo should be in the form of : path/.git
|
231
|
+
repo = '../micropython/.git'
|
232
|
+
returns True on success
|
233
|
+
"""
|
234
|
+
if not repo:
|
235
|
+
raise NotADirectoryError
|
236
|
+
|
237
|
+
cmd = ["git", "fetch", "--all", "--tags", "--quiet"]
|
238
|
+
result = _run_local_git(cmd, repo=repo, echo_output=False)
|
239
|
+
return result.returncode == 0 if result else False
|
240
|
+
|
241
|
+
|
242
|
+
def pull(repo: Union[Path, str], branch: str = "main") -> bool:
|
243
|
+
"""
|
244
|
+
pull a repo origin into main
|
245
|
+
repo should be in the form of : path/.git
|
246
|
+
repo = '../micropython/.git'
|
247
|
+
returns True on success
|
248
|
+
"""
|
249
|
+
if not repo:
|
250
|
+
raise NotADirectoryError
|
251
|
+
repo = Path(repo)
|
252
|
+
# first checkout HEAD
|
253
|
+
cmd = ["git", "checkout", branch, "--quiet", "--force"]
|
254
|
+
result = _run_local_git(cmd, repo=repo, expect_stderr=True)
|
255
|
+
if not result:
|
256
|
+
log.error("error during git checkout main", result)
|
257
|
+
return False
|
258
|
+
|
259
|
+
cmd = ["git", "pull", "origin", branch, "--quiet", "--autostash"]
|
260
|
+
result = _run_local_git(cmd, repo=repo, expect_stderr=True)
|
261
|
+
if not result:
|
262
|
+
log.error("error durign pull", result)
|
263
|
+
return False
|
264
|
+
return result.returncode == 0
|
265
|
+
|
266
|
+
|
267
|
+
def get_git_describe(folder: Optional[str] = None):
|
268
|
+
"""
|
269
|
+
Based on MicroPython makeversionhdr.py
|
270
|
+
returns : current git tag, commits ,commit hash : "v1.19.1-841-g3446"
|
271
|
+
"""
|
272
|
+
# Note: git describe doesn't work if no tag is available
|
273
|
+
try:
|
274
|
+
git_describe = subprocess.check_output(
|
275
|
+
["git", "describe", "--tags", "--dirty", "--always", "--match", "v[1-9].*"],
|
276
|
+
stderr=subprocess.STDOUT,
|
277
|
+
universal_newlines=True,
|
278
|
+
cwd=folder,
|
279
|
+
).strip()
|
280
|
+
except subprocess.CalledProcessError as er:
|
281
|
+
if er.returncode == 128:
|
282
|
+
# git exit code of 128 means no repository found
|
283
|
+
return None
|
284
|
+
git_describe = ""
|
285
|
+
except OSError:
|
286
|
+
return None
|
287
|
+
# format
|
288
|
+
return git_describe
|
mpflash/vendor/dfu.py
CHANGED
mpflash/vendor/versions.py
CHANGED
@@ -9,6 +9,8 @@ from functools import lru_cache
|
|
9
9
|
from loguru import logger as log
|
10
10
|
from packaging.version import parse
|
11
11
|
|
12
|
+
from mpflash.common import GH_CLIENT
|
13
|
+
|
12
14
|
V_PREVIEW = "preview"
|
13
15
|
"Latest preview version"
|
14
16
|
|
@@ -67,12 +69,12 @@ def clean_version(
|
|
67
69
|
|
68
70
|
|
69
71
|
@lru_cache(maxsize=10)
|
70
|
-
def micropython_versions(minver: str = "v1.20"):
|
72
|
+
def micropython_versions(minver: str = "v1.20", reverse: bool = False):
|
71
73
|
"""Get the list of micropython versions from github tags"""
|
72
74
|
try:
|
73
75
|
gh_client = GH_CLIENT
|
74
76
|
repo = gh_client.get_repo("micropython/micropython")
|
75
|
-
versions = [tag.name for tag in repo.get_tags()
|
77
|
+
versions = [tag.name for tag in repo.get_tags()]
|
76
78
|
except Exception:
|
77
79
|
versions = [
|
78
80
|
"v9.99.9-preview",
|
@@ -94,7 +96,9 @@ def micropython_versions(minver: str = "v1.20"):
|
|
94
96
|
"v1.11",
|
95
97
|
"v1.10",
|
96
98
|
]
|
97
|
-
|
99
|
+
versions = [v for v in versions if parse(v) >= parse(minver)]
|
100
|
+
# remove all but the most recent (preview) version
|
101
|
+
versions = versions[:1] + [v for v in versions if "preview" not in v]
|
98
102
|
return sorted(versions)
|
99
103
|
|
100
104
|
|
mpflash/worklist.py
CHANGED
@@ -3,10 +3,9 @@ from typing import Dict, List, Optional, Tuple
|
|
3
3
|
|
4
4
|
from loguru import logger as log
|
5
5
|
|
6
|
-
from mpflash.common import FWInfo
|
6
|
+
from mpflash.common import FWInfo, filtered_comports
|
7
7
|
from mpflash.errors import MPFlashError
|
8
8
|
|
9
|
-
from .config import config
|
10
9
|
from .downloaded import find_downloaded_firmware
|
11
10
|
from .list import show_mcus
|
12
11
|
from .mpboard_id import find_known_board
|
@@ -60,14 +59,53 @@ def auto_update(
|
|
60
59
|
|
61
60
|
# just use the last firmware
|
62
61
|
fw_info = board_firmwares[-1]
|
63
|
-
log.info(f"Found {target_version} firmware {fw_info
|
62
|
+
log.info(f"Found {target_version} firmware {fw_info.filename} for {mcu.board} on {mcu.serialport}.")
|
64
63
|
wl.append((mcu, fw_info))
|
65
64
|
return wl
|
66
65
|
|
67
66
|
|
67
|
+
def manual_worklist(
|
68
|
+
serial: str,
|
69
|
+
*,
|
70
|
+
board_id: str,
|
71
|
+
version: str,
|
72
|
+
fw_folder: Path,
|
73
|
+
) -> WorkList:
|
74
|
+
"""Create a worklist for a single board specified manually.
|
75
|
+
|
76
|
+
Args:
|
77
|
+
serial (str): Serial port of the board
|
78
|
+
board (str): Board_ID
|
79
|
+
version (str): Firmware version
|
80
|
+
fw_folder (Path): Path to the firmware folder
|
81
|
+
|
82
|
+
Returns:
|
83
|
+
WorkList: List of boards and firmware information to update
|
84
|
+
"""
|
85
|
+
log.trace(f"Manual updating {serial} to {board_id} {version}")
|
86
|
+
mcu = MPRemoteBoard(serial)
|
87
|
+
# Lookup the matching port and cpu in board_info based in the board name
|
88
|
+
try:
|
89
|
+
info = find_known_board(board_id)
|
90
|
+
mcu.port = info.port
|
91
|
+
# need the CPU type for the esptool
|
92
|
+
mcu.cpu = info.cpu
|
93
|
+
except (LookupError, MPFlashError) as e:
|
94
|
+
log.error(f"Board {board_id} not found in board_info.zip")
|
95
|
+
log.exception(e)
|
96
|
+
return []
|
97
|
+
mcu.board = board_id
|
98
|
+
firmwares = find_downloaded_firmware(fw_folder=fw_folder, board_id=board_id, version=version, port=mcu.port)
|
99
|
+
if not firmwares:
|
100
|
+
log.error(f"No firmware found for {mcu.port} {board_id} version {version}")
|
101
|
+
return []
|
102
|
+
# use the most recent matching firmware
|
103
|
+
return [(mcu, firmwares[-1])] # type: ignore
|
104
|
+
|
105
|
+
|
68
106
|
def single_auto_worklist(
|
107
|
+
serial: str,
|
69
108
|
*,
|
70
|
-
serial_port: str,
|
71
109
|
version: str,
|
72
110
|
fw_folder: Path,
|
73
111
|
) -> WorkList:
|
@@ -81,13 +119,16 @@ def single_auto_worklist(
|
|
81
119
|
Returns:
|
82
120
|
WorkList: List of boards and firmware information to update
|
83
121
|
"""
|
84
|
-
|
122
|
+
log.trace(f"Auto updating {serial} to {version}")
|
123
|
+
conn_boards = [MPRemoteBoard(serial)]
|
85
124
|
todo = auto_update(conn_boards, version, fw_folder) # type: ignore # List / list
|
86
125
|
show_mcus(conn_boards) # type: ignore
|
87
126
|
return todo
|
88
127
|
|
89
128
|
|
90
|
-
def full_auto_worklist(
|
129
|
+
def full_auto_worklist(
|
130
|
+
all_boards: List[MPRemoteBoard], *, include: List[str], ignore: List[str], version: str, fw_folder: Path
|
131
|
+
) -> WorkList:
|
91
132
|
"""
|
92
133
|
Create a worklist for all connected micropython boards based on the information retrieved from the board.
|
93
134
|
This allows the firmware version of one or moae boards to be changed without needing to specify the port or board_id manually.
|
@@ -99,49 +140,31 @@ def full_auto_worklist(*, version: str, fw_folder: Path) -> WorkList:
|
|
99
140
|
Returns:
|
100
141
|
WorkList: List of boards and firmware information to update
|
101
142
|
"""
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
except ConnectionError as e:
|
107
|
-
log.error(f"Error connecting to boards: {e}")
|
143
|
+
log.trace(f"Auto updating all boards to {version}")
|
144
|
+
if selected_boards := filter_boards(all_boards, include=include, ignore=ignore):
|
145
|
+
return auto_update(selected_boards, version, fw_folder)
|
146
|
+
else:
|
108
147
|
return []
|
109
|
-
return auto_update(conn_boards, version, fw_folder) # type: ignore
|
110
|
-
|
111
|
-
|
112
|
-
def manual_worklist(
|
113
|
-
version: str,
|
114
|
-
fw_folder: Path,
|
115
|
-
serial_port: str,
|
116
|
-
board: str,
|
117
|
-
# port: str,
|
118
|
-
) -> WorkList:
|
119
|
-
"""Create a worklist for a single board specified manually.
|
120
148
|
|
121
|
-
Args:
|
122
|
-
version (str): Firmware version
|
123
|
-
fw_folder (Path): Path to the firmware folder
|
124
|
-
serial_port (str): Serial port of the board
|
125
|
-
board (str): Board name
|
126
149
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
150
|
+
def filter_boards(
|
151
|
+
all_boards: List[MPRemoteBoard],
|
152
|
+
*,
|
153
|
+
include: List[str],
|
154
|
+
ignore: List[str],
|
155
|
+
):
|
133
156
|
try:
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
157
|
+
comports = [
|
158
|
+
p.device
|
159
|
+
for p in filtered_comports(
|
160
|
+
ignore=ignore,
|
161
|
+
include=include,
|
162
|
+
bluetooth=False,
|
163
|
+
)
|
164
|
+
]
|
165
|
+
selected_boards = [b for b in all_boards if b.serialport in comports]
|
166
|
+
# [MPRemoteBoard(port.device, update=True) for port in comports]
|
167
|
+
except ConnectionError as e:
|
168
|
+
log.error(f"Error connecting to boards: {e}")
|
145
169
|
return []
|
146
|
-
|
147
|
-
return [(mcu, firmwares[-1])] # type: ignore
|
170
|
+
return selected_boards # type: ignore
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: mpflash
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.8.1
|
4
4
|
Summary: Flash and download tool for MicroPython firmwares
|
5
5
|
Home-page: https://github.com/Josverl/micropython-stubber/blob/main/src/mpflash/README.md
|
6
6
|
License: MIT
|
@@ -19,6 +19,7 @@ Classifier: Topic :: Software Development :: Build Tools
|
|
19
19
|
Requires-Dist: beautifulsoup4 (>=4.12.3,<5.0.0)
|
20
20
|
Requires-Dist: bincopy (>=20.0.0,<21.0.0)
|
21
21
|
Requires-Dist: blkinfo (>=0.2.0,<0.3.0)
|
22
|
+
Requires-Dist: cachetools (>=5.3.0,<6.0.0)
|
22
23
|
Requires-Dist: esptool (>=4.7.0,<5.0.0)
|
23
24
|
Requires-Dist: inquirer (>=3.2.4,<4.0.0)
|
24
25
|
Requires-Dist: jsonlines (>=4.0.0,<5.0.0)
|
@@ -32,20 +33,32 @@ Requires-Dist: psutil (>=5.9.8,<6.0.0)
|
|
32
33
|
Requires-Dist: pygithub (>=2.1.1,<3.0.0)
|
33
34
|
Requires-Dist: pyusb (>=1.2.1,<2.0.0)
|
34
35
|
Requires-Dist: requests (>=2.31.0,<3.0.0)
|
35
|
-
Requires-Dist: rich-click (>=1.
|
36
|
+
Requires-Dist: rich-click (>=1.8.1,<2.0.0)
|
36
37
|
Requires-Dist: tenacity (==8.2.3)
|
37
38
|
Project-URL: Repository, https://github.com/Josverl/micropython-stubber
|
38
39
|
Description-Content-Type: text/markdown
|
39
40
|
|
40
41
|
# MPFLASH
|
42
|
+
[](https://pypi.org/project/mpflash/)
|
43
|
+
[](https://badgen.net/pypi/python/mpflash)
|
44
|
+
[](https://pepy.tech/project/mpflash)
|
45
|
+
|
41
46
|
|
42
47
|
`mpflash` is a command-line tool for working with MicroPython firmware. It provides features to help you flash and update Micropython on one or more .
|
43
48
|
|
44
|
-
This tool was initially created to be used in a CI/CD pipeline to automate the process of downloading and flashing MicroPython firmware to multiple boards, but it has been extend with a TUI to
|
49
|
+
This tool was initially created to be used in a CI/CD pipeline to automate the process of downloading and flashing MicroPython firmware to multiple boards, but it has been extend with a TUI to be used for manual downloadig, flashing and development.
|
45
50
|
|
46
|
-
`mpflash` has been tested on
|
47
|
-
|
51
|
+
`mpflash` has been tested on:
|
52
|
+
- OS: Windows x64, Linux X64, but not (yet) macOS.
|
53
|
+
- Micropython (hardware) ports:
|
54
|
+
- `rp2`, using `.uf2`, using filecopy (macos not tested yet)
|
55
|
+
- `samd`, using ` .uf2`, using filecopy (macos not tested yet)
|
56
|
+
- `esp32`, using `.bin`, using esptool,
|
57
|
+
- `esp8266`, using `.bin`, using esptool
|
58
|
+
- `stm32`, using ` .dfu`, using pydfu
|
48
59
|
|
60
|
+
Not yet implemented: `nrf`, `cc3200`, `mimxrt`
|
61
|
+
|
49
62
|
## Features
|
50
63
|
1. List the connected boards including their firmware details, in a tabular or json format
|
51
64
|
2. Download MicroPython firmware for versions, and matching a specified board or matches your current attached board.
|
@@ -73,8 +86,7 @@ On Windows this will not be an issue, but on Linux you can use udev rules to gi
|
|
73
86
|
## Detailed usage
|
74
87
|
You can list the connected boards using the following command:
|
75
88
|
```bash
|
76
|
-
|
77
|
-
D:\MyPython\micropython-stubber> mpflash list
|
89
|
+
$> mpflash list
|
78
90
|
Connected boards
|
79
91
|
┏━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━┓
|
80
92
|
┃ Serial ┃Family ┃Port ┃Board ┃CPU ┃Version ┃build ┃
|