mpflash 1.26.2__py3-none-any.whl → 1.26.4__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/flash/worklist.py CHANGED
@@ -1,5 +1,30 @@
1
- """Worklist for updating boards"""
1
+ """Worklist for updating boards.
2
2
 
3
+ This module provides functionality for creating worklists - collections of board-firmware
4
+ pairs that need to be flashed.
5
+
6
+ The API provides a clean, maintainable interface:
7
+
8
+ ```python
9
+ from mpflash.flash.worklist import create_worklist, WorklistConfig
10
+
11
+ # Simple auto-detection
12
+ config = WorklistConfig.for_auto_detection("1.22.0")
13
+ tasks = create_auto_worklist(connected_comports, config)
14
+
15
+ # Or use the high-level function
16
+ tasks = create_worklist("1.22.0", connected_comports=boards)
17
+
18
+ # Manual board specification
19
+ tasks = create_worklist("1.22.0", serial_ports=["COM1"], board_id="ESP32_GENERIC")
20
+
21
+ # Filtered boards
22
+ tasks = create_worklist("1.22.0", connected_comports=all_boards, include_ports=["COM*"])
23
+ ```
24
+
25
+ """
26
+
27
+ from dataclasses import dataclass
3
28
  from typing import List, Optional, Tuple
4
29
 
5
30
  from loguru import logger as log
@@ -15,170 +40,312 @@ from mpflash.mpboard_id import find_known_board
15
40
  from mpflash.mpremoteboard import MPRemoteBoard
16
41
 
17
42
  # #########################################################################################################
18
- FlashItem: TypeAlias = Tuple[MPRemoteBoard, Optional[Firmware]]
19
- WorkList: TypeAlias = List[FlashItem]
43
+
44
+
45
+ @dataclass
46
+ class FlashTask:
47
+ """Represents a single board-firmware flashing task."""
48
+
49
+ board: MPRemoteBoard
50
+ firmware: Optional[Firmware]
51
+
52
+ @property
53
+ def is_valid(self) -> bool:
54
+ """Check if the task has both board and firmware."""
55
+ return self.firmware is not None
56
+
57
+ @property
58
+ def board_id(self) -> str:
59
+ """Get the board ID for this task."""
60
+ return self.board.board_id
61
+
62
+ @property
63
+ def firmware_version(self) -> str:
64
+ """Get the firmware version for this task."""
65
+ return self.firmware.version if self.firmware else "unknown"
66
+
67
+
68
+ @dataclass
69
+ class WorklistConfig:
70
+ """Configuration for creating worklists."""
71
+
72
+ version: str
73
+ include_ports: Optional[List[str]] = None
74
+ ignore_ports: Optional[List[str]] = None
75
+ board_id: Optional[str] = None
76
+ custom_firmware: bool = False
77
+
78
+ def __post_init__(self):
79
+ if self.include_ports is None:
80
+ self.include_ports = []
81
+ if self.ignore_ports is None:
82
+ self.ignore_ports = []
83
+
84
+ @classmethod
85
+ def for_auto_detection(cls, version: str) -> "WorklistConfig":
86
+ """Create config for automatic board detection."""
87
+ return cls(version=version)
88
+
89
+ @classmethod
90
+ def for_manual_boards(cls, version: str, board_id: str, custom_firmware: bool = False) -> "WorklistConfig":
91
+ """Create config for manually specified boards."""
92
+ return cls(version=version, board_id=board_id, custom_firmware=custom_firmware)
93
+
94
+ @classmethod
95
+ def for_filtered_boards(
96
+ cls, version: str, include_ports: Optional[List[str]] = None, ignore_ports: Optional[List[str]] = None
97
+ ) -> "WorklistConfig":
98
+ """Create config for filtered board selection."""
99
+ return cls(version=version, include_ports=include_ports or [], ignore_ports=ignore_ports or [])
100
+
101
+
102
+ FlashTaskList: TypeAlias = List[FlashTask]
103
+
20
104
  # #########################################################################################################
21
105
 
22
106
 
23
- def auto_update_worklist(
24
- conn_boards: List[MPRemoteBoard],
25
- target_version: str,
26
- ) -> WorkList:
27
- """Builds a list of boards to update based on the connected boards and the firmwares available locally in the firmware folder.
107
+ def _create_flash_task(board: MPRemoteBoard, firmware: Optional[Firmware]) -> FlashTask:
108
+ """Create a FlashTask from board and firmware."""
109
+ return FlashTask(board=board, firmware=firmware)
28
110
 
29
- Args:
30
- conn_boards (List[MPRemoteBoard]): List of connected boards
31
- target_version (str): Target firmware version
32
- selector (Optional[Dict[str, str]], optional): Selector for filtering firmware. Defaults to None.
33
111
 
34
- Returns:
35
- WorkList: List of boards and firmware information to update
36
- """
37
- log.debug(f"auto_update_worklist: {len(conn_boards)} boards, target version: {target_version}")
38
- wl: WorkList = []
39
- for mcu in conn_boards:
40
- if mcu.family not in ("micropython", "unknown"):
41
- log.warning(f"Skipping flashing {mcu.family} {mcu.port} {mcu.board} on {mcu.serialport} as it is not a MicroPython firmware")
42
- continue
43
- board_firmwares = find_downloaded_firmware(
44
- board_id=f"{mcu.board}-{mcu.variant}" if mcu.variant else mcu.board,
45
- version=target_version,
46
- port=mcu.port,
47
- )
48
-
49
- if not board_firmwares:
50
- log.warning(f"No {target_version} firmware found for {mcu.board} on {mcu.serialport}.")
51
- wl.append((mcu, None))
52
- continue
112
+ def _find_firmware_for_board(board: MPRemoteBoard, version: str, custom: bool = False) -> Optional[Firmware]:
113
+ """Find appropriate firmware for a board."""
114
+ board_id = f"{board.board}-{board.variant}" if board.variant else board.board
115
+ firmwares = find_downloaded_firmware(board_id=board_id, version=version, port=board.port, custom=custom)
53
116
 
54
- if len(board_firmwares) > 1:
55
- log.warning(f"Multiple {target_version} firmwares found for {mcu.board} on {mcu.serialport}.")
117
+ if not firmwares:
118
+ log.warning(f"No {version} firmware found for {board.board} on {board.serialport}.")
119
+ return None
56
120
 
57
- # just use the last firmware
58
- fw_info = board_firmwares[-1]
59
- log.info(f"Found {target_version} firmware {fw_info.firmware_file} for {mcu.board} on {mcu.serialport}.")
60
- wl.append((mcu, fw_info))
61
- return wl
121
+ if len(firmwares) > 1:
122
+ log.warning(f"Multiple {version} firmwares found for {board.board} on {board.serialport}.")
62
123
 
124
+ # Use the most recent matching firmware
125
+ firmware = firmwares[-1]
126
+ log.info(f"Found {version} firmware {firmware.firmware_file} for {board.board} on {board.serialport}.")
127
+ return firmware
63
128
 
64
- def manual_worklist(
65
- serial: List[str],
66
- *,
67
- board_id: str,
68
- version: str,
69
- custom: bool = False,
70
- ) -> WorkList:
71
- """Create a worklist for manually specified boards."""
72
- log.debug(f"manual_worklist: {len(serial)} serial ports, board_id: {board_id}, version: {version}")
73
- wl: WorkList = []
74
- for comport in serial:
75
- log.trace(f"Manual updating {comport} to {board_id} {version}")
76
- wl.append(manual_board(comport, board_id=board_id, version=version, custom=custom))
77
- return wl
78
-
79
-
80
- def manual_board(
81
- serial: str,
82
- *,
83
- board_id: str,
84
- version: str,
85
- custom: bool = False,
86
- ) -> FlashItem:
87
- """Create a Flash work item for a single board specified manually.
88
129
 
89
- Args:
90
- serial (str): Serial port of the board
91
- board (str): Board_ID
92
- version (str): Firmware version
130
+ def _create_manual_board(serial_port: str, board_id: str, version: str, custom: bool = False) -> FlashTask:
131
+ """Create a FlashTask for manually specified board parameters."""
132
+ log.debug(f"Creating manual board task: {serial_port} {board_id} {version}")
93
133
 
94
- Returns:
95
- FlashItem: Board and firmware information to update
96
- """
97
- log.debug(f"manual_board: {serial} {board_id} {version}")
98
- mcu = MPRemoteBoard(serial)
99
- # Lookup the matching port and cpu in board_info based in the board name
134
+ board = MPRemoteBoard(serial_port)
135
+
136
+ # Look up board information
100
137
  try:
101
138
  info = find_known_board(board_id)
102
- mcu.port = info.port
103
- # need the CPU type for the esptool
104
- mcu.cpu = info.mcu
139
+ board.port = info.port
140
+ board.cpu = info.mcu # Need CPU type for esptool
105
141
  except (LookupError, MPFlashError) as e:
106
142
  log.error(f"Board {board_id} not found in board database")
107
143
  log.exception(e)
108
- return (mcu, None)
109
- mcu.board = board_id
110
- firmwares = find_downloaded_firmware(board_id=board_id, version=version, port=mcu.port, custom=custom)
111
- if not firmwares:
112
- log.trace(f"No firmware found for {mcu.port} {board_id} version {version}")
113
- return (mcu, None)
114
- # use the most recent matching firmware
115
- return (mcu, firmwares[-1]) # type: ignore
144
+ return _create_flash_task(board, None)
116
145
 
146
+ board.board = board_id
147
+ firmware = _find_firmware_for_board(board, version, custom)
148
+ return _create_flash_task(board, firmware)
117
149
 
118
- def single_auto_worklist(
119
- serial: str,
120
- *,
150
+
151
+ def _filter_connected_comports(
152
+ all_boards: List[MPRemoteBoard],
153
+ include: List[str],
154
+ ignore: List[str],
155
+ ) -> List[MPRemoteBoard]:
156
+ """Filter connected boards based on include/ignore patterns."""
157
+ try:
158
+ allowed_ports = [
159
+ p.device
160
+ for p in filtered_portinfos(
161
+ ignore=ignore,
162
+ include=include,
163
+ bluetooth=False,
164
+ )
165
+ ]
166
+ return [board for board in all_boards if board.serialport in allowed_ports]
167
+ except ConnectionError as e:
168
+ log.error(f"Error connecting to boards: {e}")
169
+ return []
170
+
171
+
172
+ # #########################################################################################################
173
+
174
+
175
+ # High-level API functions
176
+ # #########################################################################################################
177
+
178
+
179
+ def create_worklist(
121
180
  version: str,
122
- ) -> WorkList:
123
- """Create a worklist for a single serial-port.
181
+ *,
182
+ connected_comports: Optional[List[MPRemoteBoard]] = None,
183
+ serial_ports: Optional[List[str]] = None,
184
+ board_id: Optional[str] = None,
185
+ include_ports: Optional[List[str]] = None,
186
+ ignore_ports: Optional[List[str]] = None,
187
+ custom_firmware: bool = False,
188
+ ) -> FlashTaskList:
189
+ """High-level function to create a worklist based on different scenarios.
190
+
191
+ This function automatically determines the appropriate worklist creation method
192
+ based on the provided parameters.
124
193
 
125
194
  Args:
126
- serial_port (str): Serial port of the board
127
- version (str): Firmware version
195
+ version: Target firmware version
196
+ connected_comports: Pre-detected connected boards (for auto mode)
197
+ serial_ports: Specific serial ports to use (for manual mode)
198
+ board_id: Board ID to use with serial_ports (required for manual mode)
199
+ include_ports: Port patterns to include (for filtered mode)
200
+ ignore_ports: Port patterns to ignore (for filtered mode)
201
+ custom_firmware: Whether to use custom firmware
128
202
 
129
203
  Returns:
130
- WorkList: List of boards and firmware information to update
204
+ List of FlashTask objects
205
+
206
+ Raises:
207
+ ValueError: If parameters are inconsistent or missing required values
208
+
209
+ Examples:
210
+ # Auto-detect firmware for connected boards
211
+ tasks = create_worklist("1.22.0", connected_comports=boards)
212
+
213
+ # Manual specification
214
+ tasks = create_worklist("1.22.0", serial_ports=["COM1"], board_id="ESP32_GENERIC")
215
+
216
+ # Filtered boards
217
+ tasks = create_worklist("1.22.0", connected_comports=all_boards, include_ports=["COM*"])
131
218
  """
132
- log.debug(f"single_auto_worklist: {serial} version: {version}")
133
- log.trace(f"Auto updating {serial} to {version}")
134
- conn_boards = [MPRemoteBoard(serial)]
135
- todo = auto_update_worklist(conn_boards, version) # type: ignore # List / list
136
- show_mcus(conn_boards)
137
- return todo
219
+ # Manual mode: specific serial ports with board_id
220
+ if serial_ports and board_id:
221
+ config = WorklistConfig.for_manual_boards(version, board_id, custom_firmware)
222
+ return create_manual_worklist(serial_ports, config)
138
223
 
224
+ # Auto mode with filtering
225
+ if connected_comports and (include_ports or ignore_ports):
226
+ config = WorklistConfig.for_filtered_boards(version, include_ports, ignore_ports)
227
+ return create_filtered_worklist(connected_comports, config)
139
228
 
140
- def full_auto_worklist(
141
- all_boards: List[MPRemoteBoard],
142
- *,
143
- include: List[str],
144
- ignore: List[str],
145
- version: str,
146
- ) -> WorkList:
229
+ # Simple auto mode
230
+ if connected_comports:
231
+ config = WorklistConfig.for_auto_detection(version)
232
+ return create_auto_worklist(connected_comports, config)
233
+
234
+ # Error cases
235
+ if serial_ports and not board_id:
236
+ raise ValueError("board_id is required when specifying serial_ports for manual mode")
237
+
238
+ if not connected_comports and not serial_ports:
239
+ raise ValueError("Either connected_comports or serial_ports must be provided")
240
+
241
+ raise ValueError("Invalid combination of parameters")
242
+
243
+
244
+ # New, simplified API functions
245
+ # #########################################################################################################
246
+
247
+
248
+ def create_auto_worklist(
249
+ connected_comports: List[MPRemoteBoard],
250
+ config: WorklistConfig,
251
+ ) -> FlashTaskList:
252
+ """Create a worklist by automatically detecting firmware for connected boards.
253
+
254
+ Args:
255
+ connected_comports: List of connected MicroPython boards
256
+ config: Configuration for the worklist creation
257
+
258
+ Returns:
259
+ List of FlashTask objects
147
260
  """
148
- Create a worklist for all connected micropython boards based on the information retrieved from the board.
149
- This allows the firmware version of one or more boards to be changed without needing to specify the port or board_id manually.
261
+ log.debug(f"Creating auto worklist for {len(connected_comports)} boards, target version: {config.version}")
262
+
263
+ tasks: FlashTaskList = []
264
+ for board in connected_comports:
265
+ if board.family not in ("micropython", "unknown"):
266
+ log.warning(
267
+ f"Skipping flashing {board.family} {board.port} {board.board} on {board.serialport} as it is not a MicroPython firmware"
268
+ )
269
+ continue
270
+
271
+ firmware = _find_firmware_for_board(board, config.version, config.custom_firmware)
272
+ tasks.append(_create_flash_task(board, firmware))
273
+
274
+ return tasks
275
+
276
+
277
+ def create_manual_worklist(
278
+ serial_ports: List[str],
279
+ config: WorklistConfig,
280
+ ) -> FlashTaskList:
281
+ """Create a worklist for manually specified boards and firmware.
150
282
 
151
283
  Args:
152
- version (str): Firmware version
284
+ serial_ports: List of serial port identifiers
285
+ config: Configuration including board_id and version
153
286
 
154
287
  Returns:
155
- WorkList: List of boards and firmware information to update
288
+ List of FlashTask objects
156
289
  """
157
- log.debug(f"full_auto_worklist: {len(all_boards)} boards, include: {include}, ignore: {ignore}, version: {version}")
158
- if selected_boards := filter_boards(all_boards, include=include, ignore=ignore):
159
- return auto_update_worklist(selected_boards, version)
160
- else:
161
- return []
290
+ if not config.board_id:
291
+ raise ValueError("board_id must be specified for manual worklist creation")
292
+
293
+ log.debug(f"Creating manual worklist for {len(serial_ports)} ports, board_id: {config.board_id}, version: {config.version}")
294
+
295
+ tasks: FlashTaskList = []
296
+ for port in serial_ports:
297
+ log.trace(f"Manual updating {port} to {config.board_id} {config.version}")
298
+ task = _create_manual_board(port, config.board_id, config.version, config.custom_firmware)
299
+ tasks.append(task)
162
300
 
301
+ return tasks
163
302
 
164
- def filter_boards(
303
+
304
+ def create_filtered_worklist(
165
305
  all_boards: List[MPRemoteBoard],
166
- *,
167
- include: List[str],
168
- ignore: List[str],
169
- ):
170
- try:
171
- comports = [
172
- p.device
173
- for p in filtered_portinfos(
174
- ignore=ignore,
175
- include=include,
176
- bluetooth=False,
177
- )
178
- ]
179
- selected_boards = [b for b in all_boards if b.serialport in comports]
180
- # [MPRemoteBoard(port.device, update=True) for port in comports]
181
- except ConnectionError as e:
182
- log.error(f"Error connecting to boards: {e}")
306
+ config: WorklistConfig,
307
+ ) -> FlashTaskList:
308
+ """Create a worklist for filtered connected boards.
309
+
310
+ Args:
311
+ all_boards: All available connected boards
312
+ config: Configuration including include/ignore patterns and version
313
+
314
+ Returns:
315
+ List of FlashTask objects
316
+ """
317
+ log.debug(
318
+ f"Creating filtered worklist from {len(all_boards)} boards, include: {config.include_ports}, ignore: {config.ignore_ports}, version: {config.version}"
319
+ )
320
+
321
+ filtered_boards = _filter_connected_comports(all_boards, config.include_ports or [], config.ignore_ports or [])
322
+ if not filtered_boards:
323
+ log.warning("No boards match the filtering criteria")
183
324
  return []
184
- return selected_boards # type: ignore
325
+
326
+ return create_auto_worklist(filtered_boards, config)
327
+
328
+
329
+ def create_single_board_worklist(
330
+ serial_port: str,
331
+ config: WorklistConfig,
332
+ ) -> FlashTaskList:
333
+ """Create a worklist for a single serial port with automatic detection.
334
+
335
+ Args:
336
+ serial_port: Serial port identifier
337
+ config: Configuration with version information
338
+
339
+ Returns:
340
+ List of FlashTask objects (typically containing one item)
341
+ """
342
+ log.debug(f"Creating single board worklist: {serial_port} version: {config.version}")
343
+ log.trace(f"Auto updating {serial_port} to {config.version}")
344
+
345
+ connected_comports = [MPRemoteBoard(serial_port)]
346
+ tasks = create_auto_worklist(connected_comports, config)
347
+ show_mcus(connected_comports)
348
+ return tasks
349
+
350
+
351
+ # End of worklist.py module
mpflash/logger.py CHANGED
@@ -3,6 +3,14 @@ Logger setup for CLI tools with Unicode-safe output.
3
3
 
4
4
  Ensures log messages are compatible with the current console encoding.
5
5
  Removes or replaces Unicode icons if the encoding is not UTF-8.
6
+ Prevents Loguru colorization errors with angle bracket notation.
7
+
8
+ Usage for external packages:
9
+ from mpflash.logger import setup_external_logger_safety
10
+ setup_external_logger_safety()
11
+
12
+ This is particularly important when using packages like micropython-stubber
13
+ that may log messages containing angle bracket notation like <board_default>.
6
14
  """
7
15
 
8
16
  import functools
@@ -28,12 +36,23 @@ def _is_utf8_encoding() -> bool:
28
36
  return False
29
37
 
30
38
 
31
- def _log_formatter(record: dict) -> str:
39
+ def _sanitize_message(message: str) -> str:
40
+ """
41
+ Sanitize log messages to prevent Loguru colorization issues.
42
+
43
+ Escapes angle brackets that could be interpreted as color tags.
44
+ This prevents errors when logging documentation with placeholders like <board_default>.
45
+ """
46
+ # Escape angle brackets to prevent them from being interpreted as color tags
47
+ return message.replace("<", "\\<").replace(">", "\\>")
48
+
49
+
50
+ def _log_formatter(record) -> str:
32
51
  """
33
52
  Log message formatter for loguru and rich.
34
53
 
35
54
  Removes Unicode icons if console encoding is not UTF-8.
36
- Handles messages containing curly braces safely.
55
+ Handles messages containing curly braces and angle brackets safely.
37
56
  """
38
57
  color_map = {
39
58
  "TRACE": "cyan",
@@ -50,8 +69,12 @@ def _log_formatter(record: dict) -> str:
50
69
  icon = record["level"].icon
51
70
  else:
52
71
  icon = record["level"].name # fallback to text
53
- # Escape curly braces in the message to prevent format conflicts
54
- safe_message = record["message"].replace("{", "{{").replace("}", "}}")
72
+
73
+ # Sanitize the message to prevent format conflicts and colorization errors
74
+ safe_message = _sanitize_message(record["message"])
75
+ # Escape curly braces to prevent format conflicts
76
+ safe_message = safe_message.replace("{", "{{").replace("}", "}}")
77
+
55
78
  # Use string concatenation to avoid f-string format conflicts
56
79
  time_part = "[not bold green]{time:HH:mm:ss}[/not bold green]"
57
80
  message_part = f"[{lvl_color}]{safe_message}[/{lvl_color}]"
@@ -63,6 +86,7 @@ def set_loglevel(loglevel: str) -> None:
63
86
  Set the log level for the logger.
64
87
 
65
88
  Ensures Unicode safety for log output and handles format errors.
89
+ Disables colorization to prevent angle bracket interpretation issues.
66
90
  """
67
91
  try:
68
92
  log.remove()
@@ -77,7 +101,57 @@ def set_loglevel(loglevel: str) -> None:
77
101
  # Fallback to simple text output if formatting fails
78
102
  console.print(f"[LOG FORMAT ERROR] {message} (Error: {e})")
79
103
 
80
- log.add(safe_format_wrapper, level=loglevel.upper(), colorize=False, format=_log_formatter) # type: ignore
104
+ # Disable colorization completely to prevent angle bracket interpretation as color tags
105
+ log.add(
106
+ safe_format_wrapper,
107
+ level=loglevel.upper(),
108
+ colorize=False, # This prevents Loguru from parsing angle brackets as color tags
109
+ format=_log_formatter,
110
+ ) # type: ignore
111
+
112
+
113
+ # def configure_safe_logging() -> None:
114
+ # """
115
+ # Configure logging to be safe from colorization errors.
116
+
117
+ # This function helps prevent issues when external packages
118
+ # (like micropython-stubber) log messages with angle brackets
119
+ # that could be misinterpreted as color tags.
120
+ # """
121
+ # # Remove all existing handlers to start fresh
122
+ # try:
123
+ # log.remove()
124
+ # except ValueError:
125
+ # pass
126
+
127
+ # # Add a completely safe handler with no colorization
128
+ # log.add(
129
+ # sys.stderr,
130
+ # level="TRACE",
131
+ # colorize=False, # Completely disable colorization
132
+ # format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {name}:{function}:{line} - {message}",
133
+ # )
134
+
135
+
136
+ # def setup_external_logger_safety() -> None:
137
+ # """
138
+ # Setup safe logging configuration for external packages.
139
+
140
+ # Call this function before running tools that might log messages
141
+ # with angle bracket notation (like micropython-stubber) to prevent
142
+ # Loguru colorization errors.
143
+ # """
144
+ # import logging
145
+
146
+ # # Configure the root logger to be safe
147
+ # logging.basicConfig(
148
+ # level=logging.DEBUG,
149
+ # format="%(asctime)s | %(levelname)s | %(name)s:%(funcName)s:%(lineno)d - %(message)s",
150
+ # handlers=[logging.StreamHandler(sys.stderr)],
151
+ # )
152
+
153
+ # # Also configure loguru for safety
154
+ # configure_safe_logging()
81
155
 
82
156
 
83
157
  def make_quiet() -> None:
@@ -10,13 +10,12 @@ from pathlib import Path
10
10
  from typing import List, Optional, Union
11
11
 
12
12
  import serial.tools.list_ports
13
- from rich.progress import track
14
- from tenacity import retry, stop_after_attempt, wait_fixed
15
-
16
13
  from mpflash.errors import MPFlashError
17
14
  from mpflash.logger import log
18
15
  from mpflash.mpboard_id.board_id import find_board_id_by_description
19
16
  from mpflash.mpremoteboard.runner import run
17
+ from rich.progress import track
18
+ from tenacity import retry, stop_after_attempt, wait_fixed
20
19
 
21
20
  if sys.version_info >= (3, 11):
22
21
  import tomllib # type: ignore
@@ -64,13 +63,17 @@ class MPRemoteBoard:
64
63
  self.build = ""
65
64
  self.location = location # USB location
66
65
  self.toml = {}
67
- portinfo = list(serial.tools.list_ports.grep(serialport)) # type: ignore
66
+ portinfo = list(serial.tools.list_ports.grep(serialport))
68
67
  if not portinfo or len(portinfo) != 1:
69
68
  self.vid = 0x00
70
69
  self.pid = 0x00
71
70
  else:
72
- self.vid = portinfo[0].vid
73
- self.pid = portinfo[0].pid
71
+ try:
72
+ self.vid = portinfo[0].vid # type: ignore
73
+ self.pid = portinfo[0].pid # type: ignore
74
+ except Exception:
75
+ self.vid = 0x00
76
+ self.pid = 0x00
74
77
  if update:
75
78
  self.get_mcu_info()
76
79
 
@@ -125,7 +128,7 @@ class MPRemoteBoard:
125
128
  return f"MPRemoteBoard({self.serialport}, {self.family} {self.port}, {self.board}{f'-{self.variant}' if self.variant else ''}, {self.version})"
126
129
 
127
130
  @staticmethod
128
- def connected_boards(
131
+ def connected_comports(
129
132
  bluetooth: bool = False, description: bool = False
130
133
  ) -> List[str]:
131
134
  # TODO: rename to connected_comports
@@ -206,7 +209,6 @@ class MPRemoteBoard:
206
209
  descr, short_descr, version=self.version
207
210
  )
208
211
  self.board_id = board_name or "UNKNOWN_BOARD"
209
- # TODO: Get the variant as well
210
212
  # get the board_info.toml
211
213
  self.get_board_info_toml()
212
214
  # TODO: get board_id from the toml file if it exists