mpflash 1.24.7__py3-none-any.whl → 1.24.8__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 +23 -57
- 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 +2 -2
- mpflash/cli_main.py +1 -1
- mpflash/common.py +6 -14
- mpflash/config.py +26 -7
- mpflash/connected.py +6 -14
- mpflash/download.py +56 -23
- mpflash/downloaded.py +1 -5
- 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 +7 -2
- 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_id.py +0 -1
- mpflash/mpboard_id/board_info.json +30974 -0
- mpflash/mpboard_id/known.py +94 -0
- mpflash/mpboard_id/store.py +0 -2
- mpflash/mpremoteboard/__init__.py +13 -9
- mpflash/mpremoteboard/mpy_fw_info.py +14 -17
- mpflash/py.typed +0 -0
- mpflash/vendor/click_aliases.py +64 -0
- mpflash/vendor/dfu.py +2 -8
- mpflash/vendor/pydfu.py +3 -14
- mpflash/versions.py +9 -6
- {mpflash-1.24.7.dist-info → mpflash-1.24.8.dist-info}/METADATA +71 -13
- mpflash-1.24.8.dist-info/RECORD +59 -0
- {mpflash-1.24.7.dist-info → mpflash-1.24.8.dist-info}/WHEEL +1 -1
- mpflash-1.24.7.dist-info/RECORD +0 -56
- {mpflash-1.24.7.dist-info → mpflash-1.24.8.dist-info}/LICENSE +0 -0
- {mpflash-1.24.7.dist-info → mpflash-1.24.8.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,94 @@
|
|
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.errors import MPFlashError
|
11
|
+
from mpflash.versions import clean_version
|
12
|
+
|
13
|
+
from .board import Board
|
14
|
+
from .store import read_known_boardinfo
|
15
|
+
|
16
|
+
|
17
|
+
def get_known_ports() -> List[str]:
|
18
|
+
# TODO: Filter for Version
|
19
|
+
mp_boards = read_known_boardinfo()
|
20
|
+
# select the unique ports from info
|
21
|
+
ports = set({board.port for board in mp_boards if board.port})
|
22
|
+
return sorted(list(ports))
|
23
|
+
|
24
|
+
|
25
|
+
def get_known_boards_for_port(port: Optional[str] = "", versions: Optional[List[str]] = None) -> List[Board]:
|
26
|
+
"""
|
27
|
+
Returns a list of boards for the given port and version(s)
|
28
|
+
|
29
|
+
port: The Micropython port to filter for
|
30
|
+
versions: Optional, The Micropython versions to filter for (actual versions required)
|
31
|
+
"""
|
32
|
+
mp_boards = read_known_boardinfo()
|
33
|
+
if versions:
|
34
|
+
preview_or_stable = "preview" in versions or "stable" in versions
|
35
|
+
else:
|
36
|
+
preview_or_stable = False
|
37
|
+
|
38
|
+
# filter for 'preview' as they are not in the board_info.json
|
39
|
+
# instead use stable version
|
40
|
+
versions = versions or []
|
41
|
+
if "preview" in versions:
|
42
|
+
versions.remove("preview")
|
43
|
+
versions.append("stable")
|
44
|
+
if versions:
|
45
|
+
# make sure of the v prefix
|
46
|
+
versions = [clean_version(v) for v in versions]
|
47
|
+
# filter for the version(s)
|
48
|
+
mp_boards = [board for board in mp_boards if board.version in versions]
|
49
|
+
if not mp_boards and preview_or_stable:
|
50
|
+
# nothing found - perhaps there is a newer version for which we do not have the board info yet
|
51
|
+
# use the latest known version from the board info
|
52
|
+
mp_boards = read_known_boardinfo()
|
53
|
+
last_known_version = sorted({b.version for b in mp_boards})[-1]
|
54
|
+
mp_boards = [board for board in mp_boards if board.version == last_known_version]
|
55
|
+
|
56
|
+
# filter for the port
|
57
|
+
if port:
|
58
|
+
mp_boards = [board for board in mp_boards if board.port == port]
|
59
|
+
return mp_boards
|
60
|
+
|
61
|
+
|
62
|
+
def known_stored_boards(port: str, versions: Optional[List[str]] = None) -> List[Tuple[str, str]]:
|
63
|
+
"""
|
64
|
+
Returns a list of tuples with the description and board name for the given port and version
|
65
|
+
|
66
|
+
port : str : The Micropython port to filter for
|
67
|
+
versions : List[str] : The Micropython versions to filter for (actual versions required)
|
68
|
+
"""
|
69
|
+
mp_boards = get_known_boards_for_port(port, versions)
|
70
|
+
|
71
|
+
boards = set({(f"{board.version} {board.description}", board.board_id) for board in mp_boards})
|
72
|
+
return sorted(list(boards))
|
73
|
+
|
74
|
+
|
75
|
+
@lru_cache(maxsize=20)
|
76
|
+
def find_known_board(board_id: str) -> Board:
|
77
|
+
"""Find the board for the given BOARD_ID or 'board description' and return the board info as a Board object"""
|
78
|
+
# Some functional overlap with:
|
79
|
+
# mpboard_id\board_id.py _find_board_id_by_description
|
80
|
+
info = read_known_boardinfo()
|
81
|
+
for board_info in info:
|
82
|
+
if board_id in (
|
83
|
+
board_info.board_id,
|
84
|
+
board_info.description,
|
85
|
+
) or board_info.description.startswith(board_id):
|
86
|
+
if not board_info.cpu:
|
87
|
+
# failsafe for older board_info.json files
|
88
|
+
print(f"Board {board_id} has no CPU info, using port as CPU")
|
89
|
+
if " with " in board_info.description:
|
90
|
+
board_info.cpu = board_info.description.split(" with ")[-1]
|
91
|
+
else:
|
92
|
+
board_info.cpu = board_info.port
|
93
|
+
return board_info
|
94
|
+
raise MPFlashError(f"Board {board_id} not found")
|
mpflash/mpboard_id/store.py
CHANGED
@@ -19,7 +19,6 @@ def write_boardinfo_json(board_list: List[Board], *, folder: Optional[Path] = No
|
|
19
19
|
board_list (List[Board]): The list of Board objects.
|
20
20
|
folder (Path): The folder where the compressed JSON file will be saved.
|
21
21
|
"""
|
22
|
-
import zipfile
|
23
22
|
|
24
23
|
if not folder:
|
25
24
|
folder = HERE
|
@@ -34,7 +33,6 @@ def write_boardinfo_json(board_list: List[Board], *, folder: Optional[Path] = No
|
|
34
33
|
def read_known_boardinfo(board_info: Optional[Path] = None) -> List[Board]:
|
35
34
|
"""Reads the board information from a JSON file in a zip file."""
|
36
35
|
|
37
|
-
import zipfile
|
38
36
|
|
39
37
|
if not board_info:
|
40
38
|
board_info = HERE / "board_info.zip"
|
@@ -2,6 +2,8 @@
|
|
2
2
|
Module to run mpremote commands, and retry on failure or timeout
|
3
3
|
"""
|
4
4
|
|
5
|
+
|
6
|
+
import contextlib
|
5
7
|
import sys
|
6
8
|
import time
|
7
9
|
from pathlib import Path
|
@@ -51,6 +53,7 @@ class MPRemoteBoard:
|
|
51
53
|
self.version = ""
|
52
54
|
self.port = ""
|
53
55
|
self.board = ""
|
56
|
+
self.variant= ""
|
54
57
|
self.cpu = ""
|
55
58
|
self.arch = ""
|
56
59
|
self.mpy = ""
|
@@ -65,9 +68,9 @@ class MPRemoteBoard:
|
|
65
68
|
Return a string representation of the MPRemoteBoard object.
|
66
69
|
|
67
70
|
Returns:
|
68
|
-
- str:
|
71
|
+
- str: A human readable representation of the MCU.
|
69
72
|
"""
|
70
|
-
return f"MPRemoteBoard({self.serialport}, {self.family} {self.port}, {self.board}, {self.version})"
|
73
|
+
return f"MPRemoteBoard({self.serialport}, {self.family} {self.port}, {self.board}{f'-{self.variant}' if self.variant else ''}, {self.version})"
|
71
74
|
|
72
75
|
@staticmethod
|
73
76
|
def connected_boards(bluetooth: bool = False, description: bool = False) -> List[str]:
|
@@ -137,9 +140,12 @@ class MPRemoteBoard:
|
|
137
140
|
self.description = descr = info["board"]
|
138
141
|
pos = descr.rfind(" with")
|
139
142
|
short_descr = descr[:pos].strip() if pos != -1 else ""
|
140
|
-
if
|
141
|
-
|
142
|
-
|
143
|
+
if info["_build"]:
|
144
|
+
self.board = info["_build"].split('-')[0]
|
145
|
+
self.variant = info["_build"].split('-')[1] if '-' in info["_build"] else ""
|
146
|
+
elif board_name := find_board_id_by_description(
|
147
|
+
descr, short_descr, version=self.version
|
148
|
+
):
|
143
149
|
self.board = board_name
|
144
150
|
else:
|
145
151
|
self.board = "UNKNOWN_BOARD"
|
@@ -168,7 +174,7 @@ class MPRemoteBoard:
|
|
168
174
|
log_errors=False,
|
169
175
|
)
|
170
176
|
except Exception as e:
|
171
|
-
raise ConnectionError(f"Failed to get board_info.toml for {self.serialport}:
|
177
|
+
raise ConnectionError(f"Failed to get board_info.toml for {self.serialport}:") from e
|
172
178
|
# this is optional - so only parse if we got the file
|
173
179
|
self.toml = {}
|
174
180
|
if rc in [OK]: # sometimes we get an -9 ???
|
@@ -264,8 +270,6 @@ class MPRemoteBoard:
|
|
264
270
|
total=timeout,
|
265
271
|
):
|
266
272
|
time.sleep(1)
|
267
|
-
|
273
|
+
with contextlib.suppress(ConnectionError, MPFlashError):
|
268
274
|
self.get_mcu_info()
|
269
275
|
break
|
270
|
-
except (ConnectionError, MPFlashError):
|
271
|
-
pass
|
@@ -1,4 +1,4 @@
|
|
1
|
-
#
|
1
|
+
# pragma: no cover
|
2
2
|
import os
|
3
3
|
import sys
|
4
4
|
|
@@ -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,17 +49,16 @@ def _info(): # type:() -> dict[str, str]
|
|
50
49
|
except AttributeError:
|
51
50
|
pass
|
52
51
|
try:
|
53
|
-
machine = (
|
54
|
-
sys.implementation._machine
|
55
|
-
if "_machine" in dir(sys.implementation)
|
56
|
-
else os.uname().machine
|
57
|
-
)
|
52
|
+
machine = sys.implementation._machine if "_machine" in dir(sys.implementation) else os.uname().machine # type: ignore
|
58
53
|
info["board"] = machine.strip()
|
54
|
+
info["_build"] = sys.implementation._build if "_build" in dir(sys.implementation) else ""
|
59
55
|
info["cpu"] = machine.split("with")[-1].strip() if "with" in machine else ""
|
60
56
|
info["mpy"] = (
|
61
57
|
sys.implementation._mpy
|
62
58
|
if "_mpy" in dir(sys.implementation)
|
63
|
-
else sys.implementation.mpy
|
59
|
+
else sys.implementation.mpy
|
60
|
+
if "mpy" in dir(sys.implementation)
|
61
|
+
else ""
|
64
62
|
)
|
65
63
|
except (AttributeError, IndexError):
|
66
64
|
pass
|
@@ -69,10 +67,10 @@ def _info(): # type:() -> dict[str, str]
|
|
69
67
|
if hasattr(sys, "version"):
|
70
68
|
info["build"] = _build(sys.version)
|
71
69
|
elif hasattr(os, "uname"):
|
72
|
-
info["build"] = _build(os.uname()[3])
|
70
|
+
info["build"] = _build(os.uname()[3]) # type: ignore
|
73
71
|
if not info["build"]:
|
74
72
|
# extract build from uname().release if available
|
75
|
-
info["build"] = _build(os.uname()[2])
|
73
|
+
info["build"] = _build(os.uname()[2]) # type: ignore
|
76
74
|
except (AttributeError, IndexError):
|
77
75
|
pass
|
78
76
|
# avoid build hashes
|
@@ -81,7 +79,7 @@ def _info(): # type:() -> dict[str, str]
|
|
81
79
|
|
82
80
|
if info["version"] == "" and sys.platform not in ("unix", "win32"):
|
83
81
|
try:
|
84
|
-
u = os.uname()
|
82
|
+
u = os.uname() # type: ignore
|
85
83
|
info["version"] = u.release
|
86
84
|
except (IndexError, AttributeError, TypeError):
|
87
85
|
pass
|
@@ -106,8 +104,7 @@ def _info(): # type:() -> dict[str, str]
|
|
106
104
|
if (
|
107
105
|
info["version"]
|
108
106
|
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
|
107
|
+
and info["version"] >= "1.10.0" # versions from 1.10.0 to 1.20.0 do not have a micro .0
|
111
108
|
and info["version"] <= "1.19.9"
|
112
109
|
):
|
113
110
|
# drop the .0 for newer releases
|
mpflash/py.typed
ADDED
File without changes
|
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:
|
mpflash/vendor/dfu.py
CHANGED
@@ -46,10 +46,7 @@ def parse(file, dump_images=False):
|
|
46
46
|
tprefix["name"] = cstring(tprefix["name"])
|
47
47
|
else:
|
48
48
|
tprefix["name"] = ""
|
49
|
-
print(
|
50
|
-
'%(signature)s %(num)d, alt setting: %(altsetting)s, name: "%(name)s", size: %(size)d, elements: %(elements)d'
|
51
|
-
% tprefix
|
52
|
-
)
|
49
|
+
print('%(signature)s %(num)d, alt setting: %(altsetting)s, name: "%(name)s", size: %(size)d, elements: %(elements)d' % tprefix)
|
53
50
|
tsize = tprefix["size"]
|
54
51
|
target, data = data[:tsize], data[tsize:]
|
55
52
|
for e in range(tprefix["elements"]):
|
@@ -65,10 +62,7 @@ def parse(file, dump_images=False):
|
|
65
62
|
if len(target):
|
66
63
|
print("target %d: PARSE ERROR" % t)
|
67
64
|
suffix = named(struct.unpack("<4H3sBI", data[:16]), "device product vendor dfu ufd len crc")
|
68
|
-
print(
|
69
|
-
"usb: %(vendor)04x:%(product)04x, device: 0x%(device)04x, dfu: 0x%(dfu)04x, %(ufd)s, %(len)d, 0x%(crc)08x"
|
70
|
-
% suffix
|
71
|
-
)
|
65
|
+
print("usb: %(vendor)04x:%(product)04x, device: 0x%(device)04x, dfu: 0x%(dfu)04x, %(ufd)s, %(len)d, 0x%(crc)08x" % suffix)
|
72
66
|
if crc != suffix["crc"]:
|
73
67
|
print("CRC ERROR: computed crc32 is 0x%08x" % crc)
|
74
68
|
data = data[16:]
|
mpflash/vendor/pydfu.py
CHANGED
@@ -395,10 +395,7 @@ def read_dfu_file(filename):
|
|
395
395
|
# B uint8_t len 16
|
396
396
|
# I uint32_t crc32 Checksum
|
397
397
|
dfu_suffix = named(struct.unpack("<4H3sBI", data[:16]), "device product vendor dfu ufd len crc")
|
398
|
-
print(
|
399
|
-
" usb: %(vendor)04x:%(product)04x, device: 0x%(device)04x, "
|
400
|
-
"dfu: 0x%(dfu)04x, %(ufd)s, %(len)d, 0x%(crc)08x" % dfu_suffix
|
401
|
-
)
|
398
|
+
print(" usb: %(vendor)04x:%(product)04x, device: 0x%(device)04x, " "dfu: 0x%(dfu)04x, %(ufd)s, %(len)d, 0x%(crc)08x" % dfu_suffix)
|
402
399
|
if crc != dfu_suffix["crc"]:
|
403
400
|
print("CRC ERROR: computed crc32 is 0x%08x" % crc)
|
404
401
|
return
|
@@ -477,19 +474,11 @@ def list_dfu_devices(*args, **kwargs):
|
|
477
474
|
if not devices:
|
478
475
|
raise SystemExit("No DFU capable devices found")
|
479
476
|
for device in devices:
|
480
|
-
print(
|
481
|
-
"Bus {} Device {:03d}: ID {:04x}:{:04x}".format(
|
482
|
-
device.bus, device.address, device.idVendor, device.idProduct
|
483
|
-
)
|
484
|
-
)
|
477
|
+
print("Bus {} Device {:03d}: ID {:04x}:{:04x}".format(device.bus, device.address, device.idVendor, device.idProduct))
|
485
478
|
layout = get_memory_layout(device)
|
486
479
|
print("Memory Layout")
|
487
480
|
for entry in layout:
|
488
|
-
print(
|
489
|
-
" 0x{:x} {:2d} pages of {:3d}K bytes".format(
|
490
|
-
entry["addr"], entry["num_pages"], entry["page_size"] // 1024
|
491
|
-
)
|
492
|
-
)
|
481
|
+
print(" 0x{:x} {:2d} pages of {:3d}K bytes".format(entry["addr"], entry["num_pages"], entry["page_size"] // 1024))
|
493
482
|
|
494
483
|
|
495
484
|
def write_elements(elements, mass_erase_used, progress=None):
|
mpflash/versions.py
CHANGED
@@ -6,10 +6,8 @@ from pathlib import Path
|
|
6
6
|
|
7
7
|
from cache_to_disk import NoCacheCondition, cache_to_disk
|
8
8
|
from loguru import logger as log
|
9
|
-
from packaging.version import Version, parse
|
10
9
|
|
11
|
-
|
12
|
-
from mpflash.common import GH_CLIENT
|
10
|
+
from mpflash.config import config
|
13
11
|
|
14
12
|
OLDEST_VERSION = "1.16"
|
15
13
|
"This is the oldest MicroPython version to build the stubs on"
|
@@ -71,15 +69,18 @@ def clean_version(
|
|
71
69
|
|
72
70
|
def is_version(version: str):
|
73
71
|
"""Check if the version is a valid version string"""
|
72
|
+
# Just in time import
|
73
|
+
from packaging.version import Version
|
74
74
|
return Version._regex.search(version) is not None
|
75
75
|
|
76
76
|
|
77
77
|
@cache_to_disk(n_days_to_cache=1)
|
78
78
|
def micropython_versions(minver: str = "v1.20", reverse: bool = False, cache_it=True):
|
79
79
|
"""Get the list of micropython versions from github tags"""
|
80
|
-
|
80
|
+
# Just in time import
|
81
|
+
from packaging.version import parse
|
81
82
|
try:
|
82
|
-
gh_client =
|
83
|
+
gh_client = config.gh_client
|
83
84
|
repo = gh_client.get_repo("micropython/micropython")
|
84
85
|
tags = [tag.name for tag in repo.get_tags() if parse(tag.name) >= parse(minver)]
|
85
86
|
versions = [v for v in tags if not v.endswith(V_PREVIEW)]
|
@@ -90,7 +91,7 @@ def micropython_versions(minver: str = "v1.20", reverse: bool = False, cache_it=
|
|
90
91
|
log.error(e)
|
91
92
|
versions = []
|
92
93
|
# returns - but does not cache
|
93
|
-
raise NoCacheCondition(function_value=versions)
|
94
|
+
raise NoCacheCondition(function_value=versions) from e
|
94
95
|
# remove any duplicates and sort
|
95
96
|
versions = sorted(list(set(versions)), reverse=reverse, key=lambda s: (not is_version(s), s))
|
96
97
|
if cache_it:
|
@@ -116,6 +117,8 @@ def get_preview_mp_version(cache_it=True) -> str:
|
|
116
117
|
# Do not cache , same path will have different versions checked out
|
117
118
|
def checkedout_version(path: Path, flat: bool = False) -> str:
|
118
119
|
"""Get the checked-out version of the repo"""
|
120
|
+
# Just in time import
|
121
|
+
import mpflash.basicgit as git
|
119
122
|
version = git.get_local_tag(path.as_posix())
|
120
123
|
if not version:
|
121
124
|
raise ValueError("No valid Tag found")
|
@@ -1,17 +1,15 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: mpflash
|
3
|
-
Version: 1.24.
|
3
|
+
Version: 1.24.8
|
4
4
|
Summary: Flash and download tool for MicroPython firmwares
|
5
5
|
License: MIT
|
6
6
|
Keywords: MicroPython,firmware,flash,download,UF2,esptool
|
7
7
|
Author: Jos Verlinde
|
8
8
|
Author-email: jos_verlinde@hotmail.com
|
9
|
-
Requires-Python: >=3.9
|
9
|
+
Requires-Python: >=3.9, !=2.7.*, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*, !=3.7.*, !=3.8.*
|
10
10
|
Classifier: License :: OSI Approved :: MIT License
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
12
|
-
Classifier: Programming Language :: Python :: 3.9
|
13
12
|
Classifier: Programming Language :: Python :: 3.10
|
14
|
-
Classifier: Programming Language :: Python :: 3.11
|
15
13
|
Classifier: Programming Language :: Python :: 3.12
|
16
14
|
Classifier: Programming Language :: Python :: 3.13
|
17
15
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
@@ -32,14 +30,15 @@ Requires-Dist: mpremote (>=1.22.0,<2.0.0)
|
|
32
30
|
Requires-Dist: packaging (>=24.2,<25.0)
|
33
31
|
Requires-Dist: platformdirs (>=4.2.0,<5.0.0)
|
34
32
|
Requires-Dist: poetry (>=2.0.1,<3.0.0)
|
35
|
-
Requires-Dist: psutil (>=5.9.8,<
|
33
|
+
Requires-Dist: psutil (>=5.9.8,<8.0.0)
|
36
34
|
Requires-Dist: pygithub (>=2.1.1,<3.0.0)
|
37
35
|
Requires-Dist: pyusb (>=1.2.1,<2.0.0)
|
36
|
+
Requires-Dist: pywin32 (>=310,<311) ; sys_platform == "win32"
|
38
37
|
Requires-Dist: requests (>=2.31.0,<3.0.0)
|
39
38
|
Requires-Dist: rich-click (>=1.8.1,<2.0.0)
|
40
|
-
Requires-Dist: tenacity (==
|
41
|
-
Project-URL: Homepage, https://github.com/Josverl/
|
42
|
-
Project-URL: Repository, https://github.com/Josverl/
|
39
|
+
Requires-Dist: tenacity (==9.0.0)
|
40
|
+
Project-URL: Homepage, https://github.com/Josverl/mpflash/blob/main/README.md
|
41
|
+
Project-URL: Repository, https://github.com/Josverl/mpflash
|
43
42
|
Description-Content-Type: text/markdown
|
44
43
|
|
45
44
|
# MPFLASH
|
@@ -48,7 +47,7 @@ Description-Content-Type: text/markdown
|
|
48
47
|
[](https://pepy.tech/project/mpflash)
|
49
48
|
|
50
49
|
|
51
|
-
`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 .
|
50
|
+
`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 attached microcontrollers.
|
52
51
|
|
53
52
|
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.
|
54
53
|
|
@@ -69,7 +68,10 @@ Not yet implemented: `nrf`, `cc3200`, `mimxrt`, `renesas`
|
|
69
68
|
3. Flash one or all connected MicroPython boards with a specific firmware or version.
|
70
69
|
|
71
70
|
## Installation
|
72
|
-
To install mpflash, you can use
|
71
|
+
To install mpflash, you can use either of the following commands:
|
72
|
+
- `uv tool install mpflash`
|
73
|
+
- `pipx install mpflash`
|
74
|
+
- `pip install mpflash`
|
73
75
|
|
74
76
|
## Basic usage
|
75
77
|
You can use mpflash to perform various operations on your MicroPython boards. Here is an example of basic usage:
|
@@ -102,6 +104,7 @@ description = "Blue Norwegian actuator"
|
|
102
104
|
|
103
105
|
If you want the board to be ignored by mpflash, you can add the following to the board_info.toml file:
|
104
106
|
```toml
|
107
|
+
description = "Blue Norwegian feeder"
|
105
108
|
[mpflash]
|
106
109
|
ignore = true
|
107
110
|
```
|
@@ -110,8 +113,13 @@ ignore = true
|
|
110
113
|
## Linux permissions to access usb devices
|
111
114
|
In order to flash the firmware to the board, you need to have the correct permissions to access the USB devices.
|
112
115
|
On Windows this will not be an issue, but on Linux you can use udev rules to give non-root users access to the USB devices.
|
113
|
-
[See the stm32_permissions documentation](
|
116
|
+
[See the stm32_permissions documentation](docs/stm32_udev_rules.md) for more information.
|
114
117
|
|
118
|
+
## Use MPFlash in your own project
|
119
|
+
|
120
|
+
MPFlash can be used as a library in your own project. mpflash is used in [micropython-stubber]() to download and flash the firmware to the connected boards.
|
121
|
+
|
122
|
+
The interface is not well documented other than the code itself, but you can use the following example to get started: - docs/mpflash_api_example.ipynb
|
115
123
|
|
116
124
|
## Detailed usage
|
117
125
|
You can list the connected boards using the following command:
|
@@ -257,9 +265,59 @@ Note that if no matching firmware can be found for a board, it will be skipped.
|
|
257
265
|
(For example, the PYBV11 and ESP32_GENERIC_S3 boards in the example above.)
|
258
266
|
|
259
267
|
## Issues and bug reports
|
260
|
-
|
261
|
-
Please report any issues or bugs in the [issue tracker](https://github.com/Josverl/micropython-stubber/issues) using the MPflash feedback template.
|
268
|
+
Please report any issues or bugs in the [issue tracker](https://github.com/Josverl/mpflash/issues).
|
262
269
|
|
263
270
|
## License
|
264
271
|
mpflash is licensed under the MIT license. See the LICENSE file for more details.
|
265
272
|
|
273
|
+
# Contributions
|
274
|
+
<!-- spell-checker: disable -->
|
275
|
+
<!--
|
276
|
+
To add via the cli run the following command:
|
277
|
+
npx all-contributors-cli add user things
|
278
|
+
|
279
|
+
- bug
|
280
|
+
- tool
|
281
|
+
- stubs
|
282
|
+
- test
|
283
|
+
- doc
|
284
|
+
- code
|
285
|
+
- research
|
286
|
+
- ideas
|
287
|
+
- content
|
288
|
+
- mpflash
|
289
|
+
-->
|
290
|
+
|
291
|
+
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
292
|
+
<!-- prettier-ignore-start -->
|
293
|
+
<!-- markdownlint-disable -->
|
294
|
+
<table>
|
295
|
+
<tbody>
|
296
|
+
<tr>
|
297
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Josverl"><img src="https://avatars2.githubusercontent.com/u/981654?v=4?s=100" width="100px;" alt="Jos Verlinde"/><br /><sub><b>Jos Verlinde</b></sub></a><br /><a href="https://github.com/Josverl/mpflash/commits?author=josverl" title="Code">💻</a> <a href="#research-josverl" title="Research">🔬</a> <a href="#ideas-josverl" title="Ideas, Planning, & Feedback">🤔</a> <a href="#content-josverl" title="Content">🖋</a> <a href="#test-josverl" title="Test">✅</a> <a href="#mpflash-josverl" title="mpflash">💥</a></td>
|
298
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/shariltumin"><img src="https://avatars.githubusercontent.com/u/186120?v=4?s=100" width="100px;" alt="shariltumin"/><br /><sub><b>shariltumin</b></sub></a><br /><a href="#mpflash-shariltumin" title="mpflash">💥</a> <a href="#test-shariltumin" title="Test">✅</a></td>
|
299
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mattytrentini"><img src="https://avatars.githubusercontent.com/u/194201?v=4?s=100" width="100px;" alt="Matt Trentini"/><br /><sub><b>Matt Trentini</b></sub></a><br /><a href="#mpflash-mattytrentini" title="mpflash">💥</a> <a href="#test-mattytrentini" title="Test">✅</a></td>
|
300
|
+
<td align="center" valign="top" width="14.28%"><a href="http://scruss.com/blog/"><img src="https://avatars.githubusercontent.com/u/425706?v=4?s=100" width="100px;" alt="Stewart Russell"/><br /><sub><b>Stewart Russell</b></sub></a><br /><a href="#mpflash-scruss" title="mpflash">💥</a> <a href="#test-scruss" title="Test">✅</a></td>
|
301
|
+
<td align="center" valign="top" width="14.28%"><a href="https://www.gitlab.com/alelec"><img src="https://avatars.githubusercontent.com/u/3318786?v=4?s=100" width="100px;" alt="Andrew Leech"/><br /><sub><b>Andrew Leech</b></sub></a><br /><a href="#mpflash-andrewleech" title="mpflash">💥</a> <a href="#test-andrewleech" title="Test">✅</a></td>
|
302
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/wovo"><img src="https://avatars.githubusercontent.com/u/9039468?v=4?s=100" width="100px;" alt="Wouter van Ooijen"/><br /><sub><b>Wouter van Ooijen</b></sub></a><br /><a href="#mpflash-wovo" title="mpflash">💥</a> <a href="#test-wovo" title="Test">✅</a></td>
|
303
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/shaneapowell"><img src="https://avatars.githubusercontent.com/u/12113620?v=4?s=100" width="100px;" alt="Shane Powell"/><br /><sub><b>Shane Powell</b></sub></a><br /><a href="#mpflash-shaneapowell" title="mpflash">💥</a> <a href="#test-shaneapowell" title="Test">✅</a></td>
|
304
|
+
</tr>
|
305
|
+
<tr>
|
306
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/robert-hh"><img src="https://avatars.githubusercontent.com/u/12476868?v=4?s=100" width="100px;" alt="Robert Hammelrath"/><br /><sub><b>Robert Hammelrath</b></sub></a><br /><a href="#mpflash-robert-hh" title="mpflash">💥</a> <a href="#test-robert-hh" title="Test">✅</a></td>
|
307
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/beetlegigg"><img src="https://avatars.githubusercontent.com/u/34552737?v=4?s=100" width="100px;" alt="Bg"/><br /><sub><b>Bg</b></sub></a><br /><a href="#mpflash-beetlegigg" title="mpflash">💥</a> <a href="#test-beetlegigg" title="Test">✅</a></td>
|
308
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/rkompass"><img src="https://avatars.githubusercontent.com/u/90282516?v=4?s=100" width="100px;" alt="Raul Kompaß"/><br /><sub><b>Raul Kompaß</b></sub></a><br /><a href="#mpflash-rkompass" title="mpflash">💥</a> <a href="#test-rkompass" title="Test">✅</a></td>
|
309
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/garryp4"><img src="https://avatars.githubusercontent.com/u/96994876?v=4?s=100" width="100px;" alt="garryp4"/><br /><sub><b>garryp4</b></sub></a><br /><a href="#mpflash-garryp4" title="mpflash">💥</a> <a href="#test-garryp4" title="Test">✅</a></td>
|
310
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/shanepowell-ast"><img src="https://avatars.githubusercontent.com/u/102747617?v=4?s=100" width="100px;" alt="Shane Powell"/><br /><sub><b>Shane Powell</b></sub></a><br /><a href="#mpflash-shanepowell-ast" title="mpflash">💥</a> <a href="#test-shanepowell-ast" title="Test">✅</a></td>
|
311
|
+
<td align="center" valign="top" width="14.28%"><a href="https://andypiper.org/"><img src="https://avatars.githubusercontent.com/u/552452?v=4?s=100" width="100px;" alt="Andy Piper"/><br /><sub><b>Andy Piper</b></sub></a><br /><a href="#mpflash-andypiper" title="mpflash">💥</a> <a href="#test-andypiper" title="Test">✅</a></td>
|
312
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/DavesCodeMusings"><img src="https://avatars.githubusercontent.com/u/61114342?v=4?s=100" width="100px;" alt="David Horton"/><br /><sub><b>David Horton</b></sub></a><br /><a href="#mpflash-DavesCodeMusings" title="mpflash">💥</a> <a href="#test-DavesCodeMusings" title="Test">✅</a></td>
|
313
|
+
</tr>
|
314
|
+
</tbody>
|
315
|
+
</table>
|
316
|
+
|
317
|
+
<!-- markdownlint-restore -->
|
318
|
+
<!-- prettier-ignore-end -->
|
319
|
+
|
320
|
+
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
321
|
+
|
322
|
+
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
|
323
|
+
|