mpflash 1.0.0__py3-none-any.whl → 1.0.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. mpflash/add_firmware.py +98 -98
  2. mpflash/ask_input.py +236 -236
  3. mpflash/basicgit.py +284 -284
  4. mpflash/bootloader/__init__.py +2 -2
  5. mpflash/bootloader/activate.py +60 -60
  6. mpflash/bootloader/detect.py +82 -82
  7. mpflash/bootloader/manual.py +101 -101
  8. mpflash/bootloader/micropython.py +12 -12
  9. mpflash/bootloader/touch1200.py +36 -36
  10. mpflash/cli_download.py +129 -129
  11. mpflash/cli_flash.py +224 -216
  12. mpflash/cli_group.py +111 -111
  13. mpflash/cli_list.py +87 -87
  14. mpflash/cli_main.py +39 -39
  15. mpflash/common.py +210 -166
  16. mpflash/config.py +44 -44
  17. mpflash/connected.py +104 -77
  18. mpflash/download.py +364 -364
  19. mpflash/downloaded.py +130 -130
  20. mpflash/errors.py +9 -9
  21. mpflash/flash/__init__.py +55 -55
  22. mpflash/flash/esp.py +59 -59
  23. mpflash/flash/stm32.py +19 -19
  24. mpflash/flash/stm32_dfu.py +104 -104
  25. mpflash/flash/uf2/__init__.py +88 -88
  26. mpflash/flash/uf2/boardid.py +15 -15
  27. mpflash/flash/uf2/linux.py +136 -130
  28. mpflash/flash/uf2/macos.py +42 -42
  29. mpflash/flash/uf2/uf2disk.py +12 -12
  30. mpflash/flash/uf2/windows.py +43 -43
  31. mpflash/flash/worklist.py +170 -170
  32. mpflash/list.py +106 -106
  33. mpflash/logger.py +41 -41
  34. mpflash/mpboard_id/__init__.py +93 -93
  35. mpflash/mpboard_id/add_boards.py +251 -251
  36. mpflash/mpboard_id/board.py +37 -37
  37. mpflash/mpboard_id/board_id.py +86 -86
  38. mpflash/mpboard_id/store.py +43 -43
  39. mpflash/mpremoteboard/__init__.py +266 -266
  40. mpflash/mpremoteboard/mpy_fw_info.py +141 -141
  41. mpflash/mpremoteboard/runner.py +140 -140
  42. mpflash/vendor/click_aliases.py +91 -91
  43. mpflash/vendor/dfu.py +165 -165
  44. mpflash/vendor/pydfu.py +605 -605
  45. mpflash/vendor/readme.md +2 -2
  46. mpflash/versions.py +135 -135
  47. {mpflash-1.0.0.dist-info → mpflash-1.0.2.dist-info}/LICENSE +20 -20
  48. {mpflash-1.0.0.dist-info → mpflash-1.0.2.dist-info}/METADATA +1 -1
  49. mpflash-1.0.2.dist-info/RECORD +53 -0
  50. mpflash-1.0.0.dist-info/RECORD +0 -53
  51. {mpflash-1.0.0.dist-info → mpflash-1.0.2.dist-info}/WHEEL +0 -0
  52. {mpflash-1.0.0.dist-info → mpflash-1.0.2.dist-info}/entry_points.txt +0 -0
@@ -1,266 +1,266 @@
1
- """
2
- Module to run mpremote commands, and retry on failure or timeout
3
- """
4
-
5
- import sys
6
- import time
7
- from pathlib import Path
8
- from typing import List, Optional, Union
9
-
10
- import serial.tools.list_ports
11
- from rich.progress import track
12
- from tenacity import retry, stop_after_attempt, wait_fixed
13
-
14
- from mpflash.errors import MPFlashError
15
- from mpflash.logger import log
16
- from mpflash.mpboard_id.board_id import find_board_id_by_description
17
- from mpflash.mpremoteboard.runner import run
18
-
19
- if sys.version_info >= (3, 11):
20
- import tomllib # type: ignore
21
- else:
22
- import tomli as tomllib # type: ignore
23
-
24
- ###############################################################################################
25
- HERE = Path(__file__).parent
26
-
27
- OK = 0
28
- ERROR = -1
29
- RETRIES = 3
30
- ###############################################################################################
31
-
32
-
33
- class MPRemoteBoard:
34
- """Class to run mpremote commands"""
35
-
36
- def __init__(self, serialport: str = "", update: bool = False, *, location: str = ""):
37
- """
38
- Initialize MPRemoteBoard object.
39
-
40
- Parameters:
41
- - serialport (str): The serial port to connect to. Default is an empty string.
42
- - update (bool): Whether to update the MCU information. Default is False.
43
- """
44
- self.serialport: str = serialport
45
- self.firmware = {}
46
-
47
- self.connected = False
48
- self.path: Optional[Path] = None
49
- self.family = "unknown"
50
- self.description = ""
51
- self.version = ""
52
- self.port = ""
53
- self.board = ""
54
- self.cpu = ""
55
- self.arch = ""
56
- self.mpy = ""
57
- self.build = ""
58
- self.location = location
59
- self.toml = {}
60
- if update:
61
- self.get_mcu_info()
62
-
63
- def __str__(self):
64
- """
65
- Return a string representation of the MPRemoteBoard object.
66
-
67
- Returns:
68
- - str: The string representation of the object.
69
- """
70
- return f"MPRemoteBoard({self.serialport}, {self.family} {self.port}, {self.board}, {self.version})"
71
-
72
- @staticmethod
73
- def connected_boards(bluetooth: bool = False, description: bool = False) -> List[str]:
74
- # TODO: rename to connected_comports
75
- """
76
- Get a list of connected comports.
77
-
78
- Parameters:
79
- - bluetooth (bool): Whether to include Bluetooth ports. Default is False.
80
-
81
- Returns:
82
- - List[str]: A list of connected board ports.
83
- """
84
- comports = serial.tools.list_ports.comports()
85
-
86
- if not bluetooth:
87
- # filter out bluetooth ports
88
- comports = [p for p in comports if "bluetooth" not in p.description.lower()]
89
- comports = [p for p in comports if "BTHENUM" not in p.hwid]
90
- if description:
91
- output = [
92
- f"{p.device} {(p.manufacturer + ' ') if p.manufacturer and not p.description.startswith(p.manufacturer) else ''}{p.description}"
93
- for p in comports
94
- ]
95
- else:
96
- output = [p.device for p in comports]
97
-
98
- if sys.platform == "win32":
99
- # Windows sort of comports by number - but fallback to device name
100
- return sorted(output, key=lambda x: int(x.split()[0][3:]) if x.split()[0][3:].isdigit() else x)
101
- # sort by device name
102
- return sorted(output)
103
-
104
- @retry(stop=stop_after_attempt(RETRIES), wait=wait_fixed(1), reraise=True) # type: ignore ## retry_error_cls=ConnectionError,
105
- def get_mcu_info(self, timeout: int = 2):
106
- """
107
- Get MCU information from the connected board.
108
-
109
- Parameters:
110
- - timeout (int): The timeout value in seconds. Default is 2.
111
-
112
- Raises:
113
- - ConnectionError: If failed to get mcu_info for the serial port.
114
- """
115
- rc, result = self.run_command(
116
- ["run", str(HERE / "mpy_fw_info.py")],
117
- no_info=True,
118
- timeout=timeout,
119
- resume=True, # Avoid restarts
120
- )
121
- if rc != OK:
122
- raise ConnectionError(f"Failed to get mcu_info for {self.serialport}")
123
- # Ok we have the info, now parse it
124
- raw_info = result[0].strip()
125
- if raw_info.startswith("{") and raw_info.endswith("}"):
126
- info = eval(raw_info)
127
- self.family = info["family"]
128
- self.version = info["version"]
129
- self.build = info["build"]
130
- self.port = info["port"]
131
- self.cpu = info["cpu"]
132
- self.arch = info["arch"]
133
- self.mpy = info["mpy"]
134
- self.description = descr = info["board"]
135
- pos = descr.rfind(" with")
136
- short_descr = descr[:pos].strip() if pos != -1 else ""
137
- if board_name := find_board_id_by_description(descr, short_descr, version=self.version):
138
- self.board = board_name
139
- else:
140
- self.board = "UNKNOWN_BOARD"
141
- # get the board_info.toml
142
- self.get_board_info_toml()
143
- # now we know the board is connected
144
- self.connected = True
145
-
146
- @retry(stop=stop_after_attempt(RETRIES), wait=wait_fixed(0.2), reraise=True) # type: ignore ## retry_error_cls=ConnectionError,
147
- def get_board_info_toml(self, timeout: int = 1):
148
- """
149
- Reads the content of the board_info.toml file from the connected board,
150
- and adds that to the board object.
151
-
152
- Parameters:
153
- - timeout (int): The timeout value in seconds.
154
-
155
- Raises:
156
- - ConnectionError: If failed to communicate with the serial port.
157
- """
158
- try:
159
- rc, result = self.run_command(
160
- ["cat", ":board_info.toml"],
161
- no_info=True,
162
- timeout=timeout,
163
- log_errors=False,
164
- )
165
- except Exception as e:
166
- raise ConnectionError(f"Failed to get board_info.toml for {self.serialport}: {e}")
167
- # this is optional - so only parse if we got the file
168
- self.toml = {}
169
- if rc in [OK]: # sometimes we get an -9 ???
170
- try:
171
- # Ok we have the info, now parse it
172
- self.toml = tomllib.loads("".join(result))
173
- log.debug(f"board_info.toml: {self.toml}")
174
- except Exception as e:
175
- log.error(f"Failed to parse board_info.toml: {e}")
176
- else:
177
- log.trace(f"Failed to read board_info.toml: {result}")
178
-
179
- def disconnect(self) -> bool:
180
- """
181
- Disconnect from a board.
182
-
183
- Returns:
184
- - bool: True if successfully disconnected, False otherwise.
185
- """
186
- if not self.connected:
187
- return True
188
- if not self.serialport:
189
- log.error("No port connected")
190
- self.connected = False
191
- return False
192
- log.info(f"Disconnecting from {self.serialport}")
193
- result = self.run_command(["disconnect"])[0] == OK
194
- self.connected = False
195
- return result
196
-
197
- @retry(stop=stop_after_attempt(RETRIES), wait=wait_fixed(2), reraise=True)
198
- def run_command(
199
- self,
200
- cmd: Union[str, List[str]],
201
- *,
202
- log_errors: bool = True,
203
- no_info: bool = False,
204
- timeout: int = 60,
205
- resume: bool = False,
206
- **kwargs,
207
- ):
208
- """
209
- Run mpremote with the given command.
210
-
211
- Parameters:
212
- - cmd (Union[str, List[str]]): The command to run, either a string or a list of strings.
213
- - log_errors (bool): Whether to log errors. Default is True.
214
- - no_info (bool): Whether to skip printing info. Default is False.
215
- - timeout (int): The timeout value in seconds. Default is 60.
216
-
217
- Returns:
218
- - bool: True if the command succeeded, False otherwise.
219
- """
220
- if isinstance(cmd, str):
221
- cmd = cmd.split(" ")
222
- prefix = [sys.executable, "-m", "mpremote"]
223
- if self.serialport:
224
- prefix += ["connect", self.serialport]
225
- # if connected add resume to keep state between commands
226
- if self.connected or resume:
227
- prefix += ["resume"]
228
- cmd = prefix + cmd
229
- log.debug(" ".join(cmd))
230
- result = run(cmd, timeout, log_errors, no_info, **kwargs)
231
- self.connected = result[0] == OK
232
- return result
233
-
234
- @retry(stop=stop_after_attempt(RETRIES), wait=wait_fixed(1))
235
- def mip_install(self, name: str) -> bool:
236
- """
237
- Install a micropython package.
238
-
239
- Parameters:
240
- - name (str): The name of the package to install.
241
-
242
- Returns:
243
- - bool: True if the installation succeeded, False otherwise.
244
- """
245
- # install createstubs to the board
246
- cmd = ["mip", "install", name]
247
- result = self.run_command(cmd)[0] == OK
248
- self.connected = True
249
- return result
250
-
251
- def wait_for_restart(self, timeout: int = 10):
252
- """wait for the board to restart"""
253
- for _ in track(
254
- range(timeout),
255
- description=f"Waiting for the board to restart ({timeout}s)",
256
- transient=True,
257
- show_speed=False,
258
- refresh_per_second=1,
259
- total=timeout,
260
- ):
261
- time.sleep(1)
262
- try:
263
- self.get_mcu_info()
264
- break
265
- except (ConnectionError, MPFlashError):
266
- pass
1
+ """
2
+ Module to run mpremote commands, and retry on failure or timeout
3
+ """
4
+
5
+ import sys
6
+ import time
7
+ from pathlib import Path
8
+ from typing import List, Optional, Union
9
+
10
+ import serial.tools.list_ports
11
+ from rich.progress import track
12
+ from tenacity import retry, stop_after_attempt, wait_fixed
13
+
14
+ from mpflash.errors import MPFlashError
15
+ from mpflash.logger import log
16
+ from mpflash.mpboard_id.board_id import find_board_id_by_description
17
+ from mpflash.mpremoteboard.runner import run
18
+
19
+ if sys.version_info >= (3, 11):
20
+ import tomllib # type: ignore
21
+ else:
22
+ import tomli as tomllib # type: ignore
23
+
24
+ ###############################################################################################
25
+ HERE = Path(__file__).parent
26
+
27
+ OK = 0
28
+ ERROR = -1
29
+ RETRIES = 3
30
+ ###############################################################################################
31
+
32
+
33
+ class MPRemoteBoard:
34
+ """Class to run mpremote commands"""
35
+
36
+ def __init__(self, serialport: str = "", update: bool = False, *, location: str = ""):
37
+ """
38
+ Initialize MPRemoteBoard object.
39
+
40
+ Parameters:
41
+ - serialport (str): The serial port to connect to. Default is an empty string.
42
+ - update (bool): Whether to update the MCU information. Default is False.
43
+ """
44
+ self.serialport: str = serialport
45
+ self.firmware = {}
46
+
47
+ self.connected = False
48
+ self.path: Optional[Path] = None
49
+ self.family = "unknown"
50
+ self.description = ""
51
+ self.version = ""
52
+ self.port = ""
53
+ self.board = ""
54
+ self.cpu = ""
55
+ self.arch = ""
56
+ self.mpy = ""
57
+ self.build = ""
58
+ self.location = location # USB location
59
+ self.toml = {}
60
+ if update:
61
+ self.get_mcu_info()
62
+
63
+ def __str__(self):
64
+ """
65
+ Return a string representation of the MPRemoteBoard object.
66
+
67
+ Returns:
68
+ - str: The string representation of the object.
69
+ """
70
+ return f"MPRemoteBoard({self.serialport}, {self.family} {self.port}, {self.board}, {self.version})"
71
+
72
+ @staticmethod
73
+ def connected_boards(bluetooth: bool = False, description: bool = False) -> List[str]:
74
+ # TODO: rename to connected_comports
75
+ """
76
+ Get a list of connected comports.
77
+
78
+ Parameters:
79
+ - bluetooth (bool): Whether to include Bluetooth ports. Default is False.
80
+
81
+ Returns:
82
+ - List[str]: A list of connected board ports.
83
+ """
84
+ comports = serial.tools.list_ports.comports()
85
+
86
+ if not bluetooth:
87
+ # filter out bluetooth ports
88
+ comports = [p for p in comports if "bluetooth" not in p.description.lower()]
89
+ comports = [p for p in comports if "BTHENUM" not in p.hwid]
90
+ if description:
91
+ output = [
92
+ f"{p.device} {(p.manufacturer + ' ') if p.manufacturer and not p.description.startswith(p.manufacturer) else ''}{p.description}"
93
+ for p in comports
94
+ ]
95
+ else:
96
+ output = [p.device for p in comports]
97
+
98
+ if sys.platform == "win32":
99
+ # Windows sort of comports by number - but fallback to device name
100
+ return sorted(output, key=lambda x: int(x.split()[0][3:]) if x.split()[0][3:].isdigit() else x)
101
+ # sort by device name
102
+ return sorted(output)
103
+
104
+ @retry(stop=stop_after_attempt(RETRIES), wait=wait_fixed(1), reraise=True) # type: ignore ## retry_error_cls=ConnectionError,
105
+ def get_mcu_info(self, timeout: int = 2):
106
+ """
107
+ Get MCU information from the connected board.
108
+
109
+ Parameters:
110
+ - timeout (int): The timeout value in seconds. Default is 2.
111
+
112
+ Raises:
113
+ - ConnectionError: If failed to get mcu_info for the serial port.
114
+ """
115
+ rc, result = self.run_command(
116
+ ["run", str(HERE / "mpy_fw_info.py")],
117
+ no_info=True,
118
+ timeout=timeout,
119
+ resume=True, # Avoid restarts
120
+ )
121
+ if rc != OK:
122
+ raise ConnectionError(f"Failed to get mcu_info for {self.serialport}")
123
+ # Ok we have the info, now parse it
124
+ raw_info = result[0].strip()
125
+ if raw_info.startswith("{") and raw_info.endswith("}"):
126
+ info = eval(raw_info)
127
+ self.family = info["family"]
128
+ self.version = info["version"]
129
+ self.build = info["build"]
130
+ self.port = info["port"]
131
+ self.cpu = info["cpu"]
132
+ self.arch = info["arch"]
133
+ self.mpy = info["mpy"]
134
+ self.description = descr = info["board"]
135
+ pos = descr.rfind(" with")
136
+ short_descr = descr[:pos].strip() if pos != -1 else ""
137
+ if board_name := find_board_id_by_description(descr, short_descr, version=self.version):
138
+ self.board = board_name
139
+ else:
140
+ self.board = "UNKNOWN_BOARD"
141
+ # get the board_info.toml
142
+ self.get_board_info_toml()
143
+ # now we know the board is connected
144
+ self.connected = True
145
+
146
+ @retry(stop=stop_after_attempt(RETRIES), wait=wait_fixed(0.2), reraise=True) # type: ignore ## retry_error_cls=ConnectionError,
147
+ def get_board_info_toml(self, timeout: int = 1):
148
+ """
149
+ Reads the content of the board_info.toml file from the connected board,
150
+ and adds that to the board object.
151
+
152
+ Parameters:
153
+ - timeout (int): The timeout value in seconds.
154
+
155
+ Raises:
156
+ - ConnectionError: If failed to communicate with the serial port.
157
+ """
158
+ try:
159
+ rc, result = self.run_command(
160
+ ["cat", ":board_info.toml"],
161
+ no_info=True,
162
+ timeout=timeout,
163
+ log_errors=False,
164
+ )
165
+ except Exception as e:
166
+ raise ConnectionError(f"Failed to get board_info.toml for {self.serialport}: {e}")
167
+ # this is optional - so only parse if we got the file
168
+ self.toml = {}
169
+ if rc in [OK]: # sometimes we get an -9 ???
170
+ try:
171
+ # Ok we have the info, now parse it
172
+ self.toml = tomllib.loads("".join(result))
173
+ log.debug(f"board_info.toml: {self.toml}")
174
+ except Exception as e:
175
+ log.error(f"Failed to parse board_info.toml: {e}")
176
+ else:
177
+ log.trace(f"Failed to read board_info.toml: {result}")
178
+
179
+ def disconnect(self) -> bool:
180
+ """
181
+ Disconnect from a board.
182
+
183
+ Returns:
184
+ - bool: True if successfully disconnected, False otherwise.
185
+ """
186
+ if not self.connected:
187
+ return True
188
+ if not self.serialport:
189
+ log.error("No port connected")
190
+ self.connected = False
191
+ return False
192
+ log.info(f"Disconnecting from {self.serialport}")
193
+ result = self.run_command(["disconnect"])[0] == OK
194
+ self.connected = False
195
+ return result
196
+
197
+ @retry(stop=stop_after_attempt(RETRIES), wait=wait_fixed(2), reraise=True)
198
+ def run_command(
199
+ self,
200
+ cmd: Union[str, List[str]],
201
+ *,
202
+ log_errors: bool = True,
203
+ no_info: bool = False,
204
+ timeout: int = 60,
205
+ resume: bool = False,
206
+ **kwargs,
207
+ ):
208
+ """
209
+ Run mpremote with the given command.
210
+
211
+ Parameters:
212
+ - cmd (Union[str, List[str]]): The command to run, either a string or a list of strings.
213
+ - log_errors (bool): Whether to log errors. Default is True.
214
+ - no_info (bool): Whether to skip printing info. Default is False.
215
+ - timeout (int): The timeout value in seconds. Default is 60.
216
+
217
+ Returns:
218
+ - bool: True if the command succeeded, False otherwise.
219
+ """
220
+ if isinstance(cmd, str):
221
+ cmd = cmd.split(" ")
222
+ prefix = [sys.executable, "-m", "mpremote"]
223
+ if self.serialport:
224
+ prefix += ["connect", self.serialport]
225
+ # if connected add resume to keep state between commands
226
+ if self.connected or resume:
227
+ prefix += ["resume"]
228
+ cmd = prefix + cmd
229
+ log.debug(" ".join(cmd))
230
+ result = run(cmd, timeout, log_errors, no_info, **kwargs)
231
+ self.connected = result[0] == OK
232
+ return result
233
+
234
+ @retry(stop=stop_after_attempt(RETRIES), wait=wait_fixed(1))
235
+ def mip_install(self, name: str) -> bool:
236
+ """
237
+ Install a micropython package.
238
+
239
+ Parameters:
240
+ - name (str): The name of the package to install.
241
+
242
+ Returns:
243
+ - bool: True if the installation succeeded, False otherwise.
244
+ """
245
+ # install createstubs to the board
246
+ cmd = ["mip", "install", name]
247
+ result = self.run_command(cmd)[0] == OK
248
+ self.connected = True
249
+ return result
250
+
251
+ def wait_for_restart(self, timeout: int = 10):
252
+ """wait for the board to restart"""
253
+ for _ in track(
254
+ range(timeout),
255
+ description=f"Waiting for the board to restart ({timeout}s)",
256
+ transient=True,
257
+ show_speed=False,
258
+ refresh_per_second=1,
259
+ total=timeout,
260
+ ):
261
+ time.sleep(1)
262
+ try:
263
+ self.get_mcu_info()
264
+ break
265
+ except (ConnectionError, MPFlashError):
266
+ pass