micropython-stubber 1.23.1__py3-none-any.whl → 1.23.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 (153) hide show
  1. {micropython_stubber-1.23.1.dist-info → micropython_stubber-1.23.2.dist-info}/LICENSE +30 -30
  2. {micropython_stubber-1.23.1.dist-info → micropython_stubber-1.23.2.dist-info}/METADATA +32 -15
  3. micropython_stubber-1.23.2.dist-info/RECORD +158 -0
  4. micropython_stubber-1.23.2.dist-info/entry_points.txt +5 -0
  5. mpflash/README.md +220 -194
  6. mpflash/libusb_flash.ipynb +203 -203
  7. mpflash/mpflash/add_firmware.py +98 -98
  8. mpflash/mpflash/ask_input.py +236 -236
  9. mpflash/mpflash/basicgit.py +284 -284
  10. mpflash/mpflash/bootloader/__init__.py +2 -2
  11. mpflash/mpflash/bootloader/activate.py +60 -60
  12. mpflash/mpflash/bootloader/detect.py +82 -82
  13. mpflash/mpflash/bootloader/manual.py +101 -101
  14. mpflash/mpflash/bootloader/micropython.py +12 -12
  15. mpflash/mpflash/bootloader/touch1200.py +36 -36
  16. mpflash/mpflash/cli_download.py +129 -129
  17. mpflash/mpflash/cli_flash.py +224 -219
  18. mpflash/mpflash/cli_group.py +111 -111
  19. mpflash/mpflash/cli_list.py +87 -81
  20. mpflash/mpflash/cli_main.py +39 -39
  21. mpflash/mpflash/common.py +210 -165
  22. mpflash/mpflash/config.py +44 -44
  23. mpflash/mpflash/connected.py +96 -78
  24. mpflash/mpflash/download.py +364 -364
  25. mpflash/mpflash/downloaded.py +130 -130
  26. mpflash/mpflash/errors.py +9 -9
  27. mpflash/mpflash/flash/__init__.py +55 -55
  28. mpflash/mpflash/flash/esp.py +59 -59
  29. mpflash/mpflash/flash/stm32.py +19 -19
  30. mpflash/mpflash/flash/stm32_dfu.py +104 -104
  31. mpflash/mpflash/flash/uf2/__init__.py +88 -88
  32. mpflash/mpflash/flash/uf2/boardid.py +15 -15
  33. mpflash/mpflash/flash/uf2/linux.py +136 -130
  34. mpflash/mpflash/flash/uf2/macos.py +42 -42
  35. mpflash/mpflash/flash/uf2/uf2disk.py +12 -12
  36. mpflash/mpflash/flash/uf2/windows.py +43 -43
  37. mpflash/mpflash/flash/worklist.py +170 -170
  38. mpflash/mpflash/list.py +106 -99
  39. mpflash/mpflash/logger.py +41 -41
  40. mpflash/mpflash/mpboard_id/__init__.py +93 -93
  41. mpflash/mpflash/mpboard_id/add_boards.py +251 -251
  42. mpflash/mpflash/mpboard_id/board.py +37 -37
  43. mpflash/mpflash/mpboard_id/board_id.py +86 -86
  44. mpflash/mpflash/mpboard_id/store.py +43 -43
  45. mpflash/mpflash/mpremoteboard/__init__.py +266 -222
  46. mpflash/mpflash/mpremoteboard/mpy_fw_info.py +141 -141
  47. mpflash/mpflash/mpremoteboard/runner.py +140 -140
  48. mpflash/mpflash/vendor/click_aliases.py +91 -91
  49. mpflash/mpflash/vendor/dfu.py +165 -165
  50. mpflash/mpflash/vendor/pydfu.py +605 -605
  51. mpflash/mpflash/vendor/readme.md +2 -2
  52. mpflash/mpflash/versions.py +135 -135
  53. mpflash/poetry.lock +1599 -1599
  54. mpflash/pyproject.toml +65 -65
  55. mpflash/stm32_udev_rules.md +62 -62
  56. stubber/__init__.py +3 -3
  57. stubber/board/board_info.csv +193 -193
  58. stubber/board/boot.py +34 -34
  59. stubber/board/createstubs.py +1004 -986
  60. stubber/board/createstubs_db.py +826 -825
  61. stubber/board/createstubs_db_min.py +332 -331
  62. stubber/board/createstubs_db_mpy.mpy +0 -0
  63. stubber/board/createstubs_lvgl.py +741 -741
  64. stubber/board/createstubs_lvgl_min.py +741 -741
  65. stubber/board/createstubs_mem.py +767 -766
  66. stubber/board/createstubs_mem_min.py +307 -306
  67. stubber/board/createstubs_mem_mpy.mpy +0 -0
  68. stubber/board/createstubs_min.py +295 -294
  69. stubber/board/createstubs_mpy.mpy +0 -0
  70. stubber/board/fw_info.py +141 -141
  71. stubber/board/info.py +183 -183
  72. stubber/board/main.py +19 -19
  73. stubber/board/modulelist.txt +247 -247
  74. stubber/board/pyrightconfig.json +34 -34
  75. stubber/bulk/mcu_stubber.py +437 -454
  76. stubber/codemod/_partials/__init__.py +48 -48
  77. stubber/codemod/_partials/db_main.py +147 -147
  78. stubber/codemod/_partials/lvgl_main.py +77 -77
  79. stubber/codemod/_partials/modules_reader.py +80 -80
  80. stubber/codemod/add_comment.py +53 -53
  81. stubber/codemod/add_method.py +65 -65
  82. stubber/codemod/board.py +317 -317
  83. stubber/codemod/enrich.py +151 -145
  84. stubber/codemod/merge_docstub.py +284 -284
  85. stubber/codemod/modify_list.py +54 -54
  86. stubber/codemod/utils.py +56 -56
  87. stubber/commands/build_cmd.py +94 -94
  88. stubber/commands/cli.py +49 -55
  89. stubber/commands/clone_cmd.py +78 -78
  90. stubber/commands/config_cmd.py +29 -29
  91. stubber/commands/enrich_folder_cmd.py +71 -71
  92. stubber/commands/get_core_cmd.py +71 -71
  93. stubber/commands/get_docstubs_cmd.py +92 -89
  94. stubber/commands/get_frozen_cmd.py +117 -114
  95. stubber/commands/get_mcu_cmd.py +102 -61
  96. stubber/commands/merge_cmd.py +66 -66
  97. stubber/commands/publish_cmd.py +118 -118
  98. stubber/commands/stub_cmd.py +31 -31
  99. stubber/commands/switch_cmd.py +62 -62
  100. stubber/commands/variants_cmd.py +48 -48
  101. stubber/cst_transformer.py +178 -178
  102. stubber/data/board_info.csv +193 -193
  103. stubber/data/board_info.json +1729 -1729
  104. stubber/data/micropython_tags.csv +15 -15
  105. stubber/data/requirements-core-micropython.txt +38 -38
  106. stubber/data/requirements-core-pycopy.txt +39 -39
  107. stubber/downloader.py +37 -36
  108. stubber/freeze/common.py +72 -68
  109. stubber/freeze/freeze_folder.py +69 -69
  110. stubber/freeze/freeze_manifest_2.py +126 -113
  111. stubber/freeze/get_frozen.py +131 -127
  112. stubber/get_cpython.py +112 -101
  113. stubber/get_lobo.py +59 -59
  114. stubber/minify.py +423 -419
  115. stubber/publish/bump.py +86 -86
  116. stubber/publish/candidates.py +275 -256
  117. stubber/publish/database.py +18 -18
  118. stubber/publish/defaults.py +40 -40
  119. stubber/publish/enums.py +24 -24
  120. stubber/publish/helpers.py +29 -29
  121. stubber/publish/merge_docstubs.py +136 -130
  122. stubber/publish/missing_class_methods.py +51 -49
  123. stubber/publish/package.py +150 -146
  124. stubber/publish/pathnames.py +51 -51
  125. stubber/publish/publish.py +120 -120
  126. stubber/publish/pypi.py +42 -38
  127. stubber/publish/stubpackage.py +1055 -1027
  128. stubber/rst/__init__.py +9 -9
  129. stubber/rst/classsort.py +78 -77
  130. stubber/rst/lookup.py +533 -530
  131. stubber/rst/output_dict.py +401 -401
  132. stubber/rst/reader.py +814 -814
  133. stubber/rst/report_return.py +77 -69
  134. stubber/rst/rst_utils.py +541 -540
  135. stubber/stubber.py +38 -38
  136. stubber/stubs_from_docs.py +90 -90
  137. stubber/tools/manifestfile.py +654 -654
  138. stubber/tools/readme.md +6 -6
  139. stubber/update_fallback.py +117 -117
  140. stubber/update_module_list.py +123 -123
  141. stubber/utils/__init__.py +6 -6
  142. stubber/utils/config.py +137 -125
  143. stubber/utils/makeversionhdr.py +54 -54
  144. stubber/utils/manifest.py +90 -90
  145. stubber/utils/post.py +80 -79
  146. stubber/utils/repos.py +156 -150
  147. stubber/utils/stubmaker.py +139 -139
  148. stubber/utils/typed_config_toml.py +80 -77
  149. stubber/variants.py +106 -106
  150. micropython_stubber-1.23.1.dist-info/RECORD +0 -159
  151. micropython_stubber-1.23.1.dist-info/entry_points.txt +0 -3
  152. mpflash/basicgit.py +0 -288
  153. {micropython_stubber-1.23.1.dist-info → micropython_stubber-1.23.2.dist-info}/WHEEL +0 -0
@@ -1,222 +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 loguru import logger as log
12
- from rich.progress import track
13
- from tenacity import retry, stop_after_attempt, wait_fixed
14
-
15
- from mpflash.errors import MPFlashError
16
- from mpflash.mpboard_id.board_id import find_board_id_by_description
17
- from mpflash.mpremoteboard.runner import run
18
-
19
- ###############################################################################################
20
- # TODO : make this a bit nicer
21
- HERE = Path(__file__).parent
22
-
23
- OK = 0
24
- ERROR = -1
25
- RETRIES = 3
26
- ###############################################################################################
27
-
28
-
29
- class MPRemoteBoard:
30
- """Class to run mpremote commands"""
31
-
32
- def __init__(self, serialport: str = "", update: bool = False, *, location: str = ""):
33
- """
34
- Initialize MPRemoteBoard object.
35
-
36
- Parameters:
37
- - serialport (str): The serial port to connect to. Default is an empty string.
38
- - update (bool): Whether to update the MCU information. Default is False.
39
- """
40
- self.serialport: str = serialport
41
- self.firmware = {}
42
-
43
- self.connected = False
44
- self.path: Optional[Path] = None
45
- self.family = "unknown"
46
- self.description = ""
47
- self.version = ""
48
- self.port = ""
49
- self.board = ""
50
- self.cpu = ""
51
- self.arch = ""
52
- self.mpy = ""
53
- self.build = ""
54
- self.location = location
55
- if update:
56
- self.get_mcu_info()
57
-
58
- def __str__(self):
59
- """
60
- Return a string representation of the MPRemoteBoard object.
61
-
62
- Returns:
63
- - str: The string representation of the object.
64
- """
65
- return f"MPRemoteBoard({self.serialport}, {self.family} {self.port}, {self.board}, {self.version})"
66
-
67
- @staticmethod
68
- def connected_boards(bluetooth: bool = False, description: bool = False) -> List[str]:
69
- # TODO: rename to connected_comports
70
- """
71
- Get a list of connected comports.
72
-
73
- Parameters:
74
- - bluetooth (bool): Whether to include Bluetooth ports. Default is False.
75
-
76
- Returns:
77
- - List[str]: A list of connected board ports.
78
- """
79
- comports = serial.tools.list_ports.comports()
80
-
81
- if not bluetooth:
82
- # filter out bluetooth ports
83
- comports = [p for p in comports if "bluetooth" not in p.description.lower()]
84
- comports = [p for p in comports if "BTHENUM" not in p.hwid]
85
- if description:
86
- output = [
87
- f"{p.device} {(p.manufacturer + ' ') if p.manufacturer and not p.description.startswith(p.manufacturer) else ''}{p.description}"
88
- for p in comports
89
- ]
90
- else:
91
- output = [p.device for p in comports]
92
-
93
- if sys.platform == "win32":
94
- # Windows sort of comports by number - but fallback to device name
95
- return sorted(output, key=lambda x: int(x.split()[0][3:]) if x.split()[0][3:].isdigit() else x)
96
- # sort by device name
97
- return sorted(output)
98
-
99
- @retry(stop=stop_after_attempt(RETRIES), wait=wait_fixed(1), reraise=True) # type: ignore ## retry_error_cls=ConnectionError,
100
- def get_mcu_info(self, timeout: int = 2):
101
- """
102
- Get MCU information from the connected board.
103
-
104
- Parameters:
105
- - timeout (int): The timeout value in seconds. Default is 2.
106
-
107
- Raises:
108
- - ConnectionError: If failed to get mcu_info for the serial port.
109
- """
110
- rc, result = self.run_command(
111
- ["run", str(HERE / "mpy_fw_info.py")],
112
- no_info=True,
113
- timeout=timeout,
114
- )
115
- if rc != OK:
116
- raise ConnectionError(f"Failed to get mcu_info for {self.serialport}")
117
- # Ok we have the info, now parse it
118
- s = result[0].strip()
119
- if s.startswith("{") and s.endswith("}"):
120
- info = eval(s)
121
- self.family = info["family"]
122
- self.version = info["version"]
123
- self.build = info["build"]
124
- self.port = info["port"]
125
- self.cpu = info["cpu"]
126
- self.arch = info["arch"]
127
- self.mpy = info["mpy"]
128
- self.description = descr = info["board"]
129
- pos = descr.rfind(" with")
130
- short_descr = descr[:pos].strip() if pos != -1 else ""
131
- if board_name := find_board_id_by_description(descr, short_descr, version=self.version):
132
- self.board = board_name
133
- else:
134
- self.board = "UNKNOWN_BOARD"
135
-
136
- def disconnect(self) -> bool:
137
- """
138
- Disconnect from a board.
139
-
140
- Returns:
141
- - bool: True if successfully disconnected, False otherwise.
142
- """
143
- if not self.connected:
144
- return True
145
- if not self.serialport:
146
- log.error("No port connected")
147
- self.connected = False
148
- return False
149
- log.info(f"Disconnecting from {self.serialport}")
150
- result = self.run_command(["disconnect"])[0] == OK
151
- self.connected = False
152
- return result
153
-
154
- @retry(stop=stop_after_attempt(RETRIES), wait=wait_fixed(2), reraise=True)
155
- def run_command(
156
- self,
157
- cmd: Union[str, List[str]],
158
- *,
159
- log_errors: bool = True,
160
- no_info: bool = False,
161
- timeout: int = 60,
162
- **kwargs,
163
- ):
164
- """
165
- Run mpremote with the given command.
166
-
167
- Parameters:
168
- - cmd (Union[str, List[str]]): The command to run, either a string or a list of strings.
169
- - log_errors (bool): Whether to log errors. Default is True.
170
- - no_info (bool): Whether to skip printing info. Default is False.
171
- - timeout (int): The timeout value in seconds. Default is 60.
172
-
173
- Returns:
174
- - bool: True if the command succeeded, False otherwise.
175
- """
176
- if isinstance(cmd, str):
177
- cmd = cmd.split(" ")
178
- prefix = [sys.executable, "-m", "mpremote"]
179
- if self.serialport:
180
- prefix += ["connect", self.serialport]
181
- # if connected add resume to keep state between commands
182
- if self.connected:
183
- prefix += ["resume"]
184
- cmd = prefix + cmd
185
- log.debug(" ".join(cmd))
186
- result = run(cmd, timeout, log_errors, no_info, **kwargs)
187
- self.connected = result[0] == OK
188
- return result
189
-
190
- @retry(stop=stop_after_attempt(RETRIES), wait=wait_fixed(1))
191
- def mip_install(self, name: str) -> bool:
192
- """
193
- Install a micropython package.
194
-
195
- Parameters:
196
- - name (str): The name of the package to install.
197
-
198
- Returns:
199
- - bool: True if the installation succeeded, False otherwise.
200
- """
201
- # install createstubs to the board
202
- cmd = ["mip", "install", name]
203
- result = self.run_command(cmd)[0] == OK
204
- self.connected = True
205
- return result
206
-
207
- def wait_for_restart(self, timeout: int = 10):
208
- """wait for the board to restart"""
209
- for _ in track(
210
- range(timeout),
211
- description=f"Waiting for the board to restart ({timeout}s)",
212
- transient=True,
213
- show_speed=False,
214
- refresh_per_second=1,
215
- total=timeout,
216
- ):
217
- time.sleep(1)
218
- try:
219
- self.get_mcu_info()
220
- break
221
- except (ConnectionError, MPFlashError):
222
- 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