[core] Warn on crystal frequency mismatch during serial upload (#14582)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
J. Nick Koston
2026-03-10 20:42:38 +00:00
committed by GitHub
co-authored by Claude Opus 4.6
parent 8d988723cd
commit 6356e3def9
4 changed files with 391 additions and 11 deletions
+36 -9
View File
@@ -125,7 +125,12 @@ ANSI_ESCAPE = re.compile(r"\033[@-_][0-?]*[ -/]*[@-~]")
class RedirectText:
def __init__(self, out, filter_lines=None):
def __init__(
self,
out,
filter_lines: list[str] | None = None,
line_callbacks: list[Callable[[str], str | None]] | None = None,
) -> None:
self._out = out
if filter_lines is None:
self._filter_pattern = None
@@ -133,6 +138,7 @@ class RedirectText:
pattern = r"|".join(r"(?:" + pattern + r")" for pattern in filter_lines)
self._filter_pattern = re.compile(pattern)
self._line_buffer = ""
self._line_callbacks = line_callbacks or []
def __getattr__(self, item):
return getattr(self._out, item)
@@ -157,7 +163,7 @@ class RedirectText:
if not isinstance(s, str):
s = s.decode()
if self._filter_pattern is not None:
if self._filter_pattern is not None or self._line_callbacks:
self._line_buffer += s
lines = self._line_buffer.splitlines(True)
for line in lines:
@@ -169,7 +175,10 @@ class RedirectText:
line_without_ansi = ANSI_ESCAPE.sub("", line)
line_without_end = line_without_ansi.rstrip()
if self._filter_pattern.match(line_without_end) is not None:
if (
self._filter_pattern is not None
and self._filter_pattern.match(line_without_end) is not None
):
# Filter pattern matched, ignore the line
continue
@@ -181,6 +190,9 @@ class RedirectText:
and (help_msg := get_esp32_arduino_flash_error_help())
):
self._write_color_replace(help_msg)
for callback in self._line_callbacks:
if msg := callback(line_without_end):
self._write_color_replace(msg)
else:
self._write_color_replace(s)
@@ -194,7 +206,11 @@ class RedirectText:
def run_external_command(
func, *cmd, capture_stdout: bool = False, filter_lines: str = None
func,
*cmd,
capture_stdout: bool = False,
filter_lines: list[str] | None = None,
line_callbacks: list[Callable[[str], str | None]] | None = None,
) -> int | str:
"""
Run a function from an external package that acts like a main method.
@@ -204,7 +220,9 @@ def run_external_command(
:param func: Function to execute
:param cmd: Command to run as (eg first element of sys.argv)
:param capture_stdout: Capture text from stdout and return that.
:param filter_lines: Regular expression used to filter captured output.
Note: line_callbacks are not invoked when capture_stdout is True.
:param filter_lines: Regular expressions used to filter captured output.
:param line_callbacks: Callbacks invoked per line; non-None returns are written to output.
:return: str if `capture_stdout` is set else int exit code.
"""
@@ -218,9 +236,13 @@ def run_external_command(
_LOGGER.debug("Running: %s", full_cmd)
orig_stdout = sys.stdout
sys.stdout = RedirectText(sys.stdout, filter_lines=filter_lines)
sys.stdout = RedirectText(
sys.stdout, filter_lines=filter_lines, line_callbacks=line_callbacks
)
orig_stderr = sys.stderr
sys.stderr = RedirectText(sys.stderr, filter_lines=filter_lines)
sys.stderr = RedirectText(
sys.stderr, filter_lines=filter_lines, line_callbacks=line_callbacks
)
if capture_stdout:
cap_stdout = sys.stdout = io.StringIO()
@@ -254,14 +276,19 @@ def run_external_process(*cmd: str, **kwargs: Any) -> int | str:
full_cmd = " ".join(shlex_quote(x) for x in cmd)
_LOGGER.debug("Running: %s", full_cmd)
filter_lines = kwargs.get("filter_lines")
line_callbacks = kwargs.get("line_callbacks")
capture_stdout = kwargs.get("capture_stdout", False)
if capture_stdout:
sub_stdout = subprocess.PIPE
else:
sub_stdout = RedirectText(sys.stdout, filter_lines=filter_lines)
sub_stdout = RedirectText(
sys.stdout, filter_lines=filter_lines, line_callbacks=line_callbacks
)
sub_stderr = RedirectText(sys.stderr, filter_lines=filter_lines)
sub_stderr = RedirectText(
sys.stderr, filter_lines=filter_lines, line_callbacks=line_callbacks
)
try:
proc = subprocess.run(