platformio-core icon indicating copy to clipboard operation
platformio-core copied to clipboard

`pio device monitor` doesn't reconnect proactively when resetting ESP32

Open riywo opened this issue 6 months ago • 1 comments

Original post to the community: https://community.platformio.org/t/how-can-pio-device-monitor-detect-disconnection/48477?u=riywo


I’m looking for a way to reconnect serial monitor automatically when the device is unplugged then plugged, or pushed the RST. Especially because I want to capture logs from setup(). I’m using Seeed XIAO ESP32 S3 from Windows 11.

Although pio device monitor has auto reconnection by default (#3939), it doesn’t recognize the disconnection itself until I hit any key. Once I hit a key, it tries to send the key and raises an exception, thus reconnects:

(I hit a key)
Disconnected (ClearCommError failed (PermissionError(13, 'The device does not recognize the command.', None, 22)))
Reconnecting to COM14    Connected!

I wonder whether we have a workaround or we can add a new feature to do some keepalive check proactively so that it can reconnect immediately?

I'll try to create a PR when I have a time, but please let me know if someone has already solved this problem.

riywo avatar Aug 16 '25 09:08 riywo

I quickly analyzed the code base with a workaround (not working).

I tried a custom filter to inject a code at runtime. However, because miniterm's writer() thread blocks at console.getkey(), I couldn't stop the current terminal programmatically. (And probably serial.read() in reader() as well.)

So, probably we need to tweak Terminal not to block entirely, but it might be a larger change.

I'll also dig deeper to tweak the VSCode extension to use Microsoft's serial monitor instead since my main objective is achieving both auto-reconnection when reset and auto-disconnection (and reconnection) when upload.


import threading
import time
import sys
from platformio.public import DeviceMonitorFilterBase

class Foo(DeviceMonitorFilterBase):
    NAME = "foo"

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        print("Filter initialized", file=sys.stderr)

        # Try a simpler thread first
        self.keepalive_thread = threading.Thread(
            target=self._send_keepalive,
            daemon=True
        )
        self.keepalive_thread.start()
        print(f"Thread started: {self.keepalive_thread.is_alive()}", file=sys.stderr)

    def _send_keepalive(self):
        """Send periodic keepalive"""
        print("Thread function started", file=sys.stderr)
        alive = True
        while alive:
            terminal = self.get_running_terminal()
            if terminal:
                try:
                    terminal.serial.write(b'\x00')
                except Exception as e:
                    print(f"Keepalive error: {e}", file=sys.stderr)
                    terminal.pio_unexpected_exception = e
                    terminal.stop()
                    alive = False
                    break
            time.sleep(1)
Filter initialized
Thread function started
Thread started: True
--- Terminal on COM14 | 9600 8-N-1
--- Available filters and text transformations: colorize, debug, default, direct, esp32_exception_decoder, foo, hexlify, log2file, nocontrol, printable, send_on_enter, time
--- More details at https://bit.ly/pio-monitor-filters
--- Quit: Ctrl+C | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H
Hello, world!
Hello, world!
Hello, world!
Keepalive error: WriteFile failed (PermissionError(13, 'The device does not recognize the command.', None, 22))
# But paused here.

riywo avatar Aug 16 '25 10:08 riywo