diff --git a/HISTORY.rst b/HISTORY.rst index d9663c0b..85709451 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -33,6 +33,7 @@ PlatformIO Core 6 * Documented `Stringification `__ – converting a macro argument into a string constant (`issue #4310 `_) * Added ``env.StringifyMacro(value)`` helper function for the `Advanced Scripting `__ * Allowed to ``Import("projenv")`` in a library extra script (`issue #4305 `_) +* Improved a serial port finder for `Black Magic Probe `__ (`issue #4023 `_) * Improved a serial port finder for a board with predefined HWIDs * Fixed an issue when the `build_unflags `__ operation ignores a flag value (`issue #4309 `_) * Fixed an issue when the `build_unflags `__ option was not applied to the ``ASPPFLAGS`` scope diff --git a/platformio/debug/config/base.py b/platformio/debug/config/base.py index c53c7d0f..fa85cd55 100644 --- a/platformio/debug/config/base.py +++ b/platformio/debug/config/base.py @@ -18,7 +18,7 @@ import os from platformio import fs, proc, util from platformio.compat import string_types from platformio.debug.exception import DebugInvalidOptionsError -from platformio.debug.helpers import reveal_debug_port +from platformio.device.finder import find_debug_port from platformio.project.config import ProjectConfig from platformio.project.helpers import load_build_metadata from platformio.project.options import ProjectOptions @@ -119,12 +119,22 @@ class DebugConfigBase: # pylint: disable=too-many-instance-attributes @property def port(self): - return reveal_debug_port( + initial_port = ( self.env_options.get("debug_port", self.tool_settings.get("port")) - or self._port, + or self._port + ) + port = find_debug_port( + initial_port, self.tool_name, self.tool_settings, ) + if port: + return port + if not self.tool_settings.get("require_debug_port"): + return None + raise DebugInvalidOptionsError( + "Please specify `debug_port` for the working environment" + ) @port.setter def port(self, value): diff --git a/platformio/debug/helpers.py b/platformio/debug/helpers.py index 35f0d483..a2d89de3 100644 --- a/platformio/debug/helpers.py +++ b/platformio/debug/helpers.py @@ -16,14 +16,12 @@ import os import re import sys import time -from fnmatch import fnmatch from hashlib import sha1 from io import BytesIO from platformio.cli import PlatformioCLI -from platformio.compat import IS_WINDOWS, is_bytes +from platformio.compat import is_bytes from platformio.debug.exception import DebugInvalidOptionsError -from platformio.device.list.util import list_serial_ports from platformio.run.cli import cli as cmd_run from platformio.run.cli import print_processing_header from platformio.test.helpers import list_test_names @@ -161,44 +159,3 @@ def is_prog_obsolete(prog_path): with open(prog_hash_path, mode="w", encoding="utf8") as fp: fp.write(new_digest) return True - - -def reveal_debug_port(env_debug_port, tool_name, tool_settings): - def _get_pattern(): - if not env_debug_port: - return None - if set(["*", "?", "[", "]"]) & set(env_debug_port): - return env_debug_port - return None - - def _is_match_pattern(port): - pattern = _get_pattern() - if not pattern: - return True - return fnmatch(port, pattern) - - def _look_for_serial_port(hwids): - for item in list_serial_ports(filter_hwid=True): - if not _is_match_pattern(item["port"]): - continue - port = item["port"] - if tool_name.startswith("blackmagic"): - if IS_WINDOWS and port.startswith("COM") and len(port) > 4: - port = "\\\\.\\%s" % port - if "GDB" in item["description"]: - return port - for hwid in hwids: - hwid_str = ("%s:%s" % (hwid[0], hwid[1])).replace("0x", "") - if hwid_str in item["hwid"]: - return port - return None - - if env_debug_port and not _get_pattern(): - return env_debug_port - if not tool_settings.get("require_debug_port"): - return None - - debug_port = _look_for_serial_port(tool_settings.get("hwids", [])) - if not debug_port: - raise DebugInvalidOptionsError("Please specify `debug_port` for environment") - return debug_port diff --git a/platformio/device/finder.py b/platformio/device/finder.py index 89d99a94..65d641c0 100644 --- a/platformio/device/finder.py +++ b/platformio/device/finder.py @@ -18,13 +18,16 @@ from fnmatch import fnmatch import click import serial -from platformio.compat import IS_WINDOWS +from platformio.compat import IS_MACOS, IS_WINDOWS from platformio.device.list.util import list_logical_devices, list_serial_ports from platformio.package.manager.platform import PlatformPackageManager from platformio.platform.factory import PlatformFactory from platformio.util import retry -KNOWN_UART_HWIDS = ( +BLACK_MAGIC_HWIDS = [ + "1D50:6018", +] +KNOWN_UART_HWIDS = BLACK_MAGIC_HWIDS + [ # Silicon Labs "10C4:EA60", # CP210X "10C4:EA61", # CP210X @@ -33,7 +36,7 @@ KNOWN_UART_HWIDS = ( "10C4:EA71", # CP2108 "10C4:EA80", # CP2110 "10C4:80A9", # CP210X -) +] def normalize_board_hwid(value): @@ -91,18 +94,44 @@ def find_serial_port( return best_port or port -def find_blackmagic_serial_port(timeout=0): +def find_blackmagic_serial_port(timeout=0, only_gdb_port=False): try: @retry(timeout=timeout) def wrapper(): - for item in list_serial_ports(): - port = item["port"] - if IS_WINDOWS and port.startswith("COM") and len(port) > 4: - port = "\\\\.\\%s" % port - if "GDB" in item["description"]: - return port - raise retry.RetryNextException() + candidates = [] + for item in list_serial_ports(filter_hwid=True): + if ( + not any(hwid in item["hwid"].upper() for hwid in BLACK_MAGIC_HWIDS) + and not "Black Magic" in item["description"] + ): + continue + if ( + IS_WINDOWS + and item["port"].startswith("COM") + and len(item["port"]) > 4 + ): + item["port"] = "\\\\.\\%s" % item["port"] + candidates.append(item) + + if not candidates: + raise retry.RetryNextException() + + for item in candidates: + if ("GDB" if only_gdb_port else "UART") in item["description"]: + return item["port"] + if IS_MACOS: + # 1 - GDB, 3 - UART + for item in candidates: + if item["port"].endswith("1" if only_gdb_port else "3"): + return item["port"] + + candidates = sorted(candidates, key=lambda item: item["port"]) + return ( + candidates[0] # first port is GDB? + if len(candidates) == 1 or only_gdb_port + else candidates[1] + )["port"] return wrapper() except retry.RetryStopException: @@ -193,3 +222,15 @@ def find_mbed_disk(initial_port): if item["name"] and any(l in item["name"].lower() for l in msdlabels): return item["path"] return None + + +def find_debug_port(initial_port, tool_name, tool_settings): + if initial_port: + if not is_pattern_port(initial_port): + return initial_port + return match_serial_port(initial_port) + if not tool_settings.get("require_debug_port"): + return None + if tool_name.startswith("blackmagic"): + return find_blackmagic_serial_port(timeout=0, only_gdb_port=True) + return None