From 0ec9d31addabe09520c461348fbe6a13c52f961e Mon Sep 17 00:00:00 2001 From: GNB Build Date: Fri, 18 Sep 2026 17:23:26 +0200 Subject: [PATCH 1/2] Fix isExternalPowered(): use PHY-level USB detection, not CDC traffic (bool)Serial was wrong: HWCDC::isCDC_Connected() and USBCDC::operator bool() only report true once an actual terminal has exchanged data with the port (CDC TX/RX, or DTR/RTS), so the flag stays stuck false after a plain esp_restart() with no monitor attached, even though USB is genuinely powered. Confirmed on real V4.3 hardware: the charging icon vanished after WiFi-toggle reboots and only returned after a manual RST where a tool happened to be talking to the port. Two USB stacks are in play depending on ARDUINO_USB_MODE: - Native USB-Serial-JTAG (HWCDC, V4.3): Serial.isPlugged(), public static, purely SOF-frame-based (USB PHY level). - External USB-OTG via TinyUSB (USBCDC, Wireless Tracker V2): tud_mounted(), reflects real bus enumeration, independent of DTR/RTS. Co-Authored-By: Claude Sonnet 5 --- src/helpers/ESP32Board.h | 41 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/helpers/ESP32Board.h b/src/helpers/ESP32Board.h index 75428bc690..05b579af1e 100644 --- a/src/helpers/ESP32Board.h +++ b/src/helpers/ESP32Board.h @@ -17,6 +17,12 @@ #include #include +#if ARDUINO_USB_CDC_ON_BOOT && !ARDUINO_USB_MODE +// tud_mounted() for isExternalPowered() on boards using TinyUSB (USBCDC) +// instead of the native HWCDC, see below. +#include "esp32-hal-tinyusb.h" +#endif + class ESP32Board : public mesh::MainBoard { protected: uint8_t startup_reason; @@ -167,6 +173,41 @@ class ESP32Board : public mesh::MainBoard { return esp_reset_reason(); } +#if ARDUINO_USB_CDC_ON_BOOT + // MeshCore.h's own default is `return false;` and no ESP32 board in + // this codebase overrode it before this (confirmed by grep, unlike + // NRF52Board which has a real VBUS-detect register; no ESP32-S3 + // equivalent register exists). + // NOTE: (bool)Serial was tried first and is WRONG on both USB paths + // below: both HWCDC::isCDC_Connected() and USBCDC::operator bool() + // only report true once an actual terminal has dialogued with the port + // (CDC TX/RX traffic, or DTR/RTS respectively), so they stay stuck + // false across a plain esp_restart() with no monitor attached, even + // while USB is genuinely powered (confirmed on real V4.3 hardware: + // icon vanished after WiFi-toggle reboots, only returned after a manual + // RST where a tool happened to be talking to the port). Two different + // ESP32-S3 boards in this codebase use two different USB stacks + // (see ARDUINO_USB_MODE in their board .json), each needing its own + // traffic-independent, PHY/bus-level "is a host actually there" check. + #if ARDUINO_USB_MODE + // Native USB-Serial-JTAG peripheral (HWCDC), e.g. Heltec V4.3. + // isPlugged() is public/static, purely SOF-frame-based (USB PHY level), + // tracked from early boot via usb_serial_jtag_conn_status_init(). + bool isExternalPowered() override { + return Serial.isPlugged(); + } + #else + // External USB-OTG PHY via TinyUSB (USBCDC), e.g. Wireless Tracker V2. + // tud_mounted() reflects real bus enumeration, independent of DTR/RTS. + bool isExternalPowered() override { + return tud_mounted(); + } + #endif + // Common limitation either way: a "dumb" charger with no data lines + // generates no SOF traffic and never enumerates, so it won't be seen + // as external power; only an active USB host (PC, OTG phone) will. +#endif + // https://docs.espressif.com/projects/esp-idf/en/v4.4.7/esp32/api-reference/system/system.html const char* getResetReasonString(uint32_t reason) { switch (reason) { From 641bb2fb918390f9cda8cf97e243aeb9b35598a6 Mon Sep 17 00:00:00 2001 From: GNB Build Date: Fri, 18 Sep 2026 17:46:26 +0200 Subject: [PATCH 2/2] Trim explanatory comments Co-Authored-By: Claude Sonnet 5 --- src/helpers/ESP32Board.h | 29 +++-------------------------- 1 file changed, 3 insertions(+), 26 deletions(-) diff --git a/src/helpers/ESP32Board.h b/src/helpers/ESP32Board.h index 05b579af1e..932df2e6ec 100644 --- a/src/helpers/ESP32Board.h +++ b/src/helpers/ESP32Board.h @@ -18,9 +18,7 @@ #include #if ARDUINO_USB_CDC_ON_BOOT && !ARDUINO_USB_MODE -// tud_mounted() for isExternalPowered() on boards using TinyUSB (USBCDC) -// instead of the native HWCDC, see below. -#include "esp32-hal-tinyusb.h" +#include "esp32-hal-tinyusb.h" // for tud_mounted(), used below #endif class ESP32Board : public mesh::MainBoard { @@ -174,38 +172,17 @@ class ESP32Board : public mesh::MainBoard { } #if ARDUINO_USB_CDC_ON_BOOT - // MeshCore.h's own default is `return false;` and no ESP32 board in - // this codebase overrode it before this (confirmed by grep, unlike - // NRF52Board which has a real VBUS-detect register; no ESP32-S3 - // equivalent register exists). - // NOTE: (bool)Serial was tried first and is WRONG on both USB paths - // below: both HWCDC::isCDC_Connected() and USBCDC::operator bool() - // only report true once an actual terminal has dialogued with the port - // (CDC TX/RX traffic, or DTR/RTS respectively), so they stay stuck - // false across a plain esp_restart() with no monitor attached, even - // while USB is genuinely powered (confirmed on real V4.3 hardware: - // icon vanished after WiFi-toggle reboots, only returned after a manual - // RST where a tool happened to be talking to the port). Two different - // ESP32-S3 boards in this codebase use two different USB stacks - // (see ARDUINO_USB_MODE in their board .json), each needing its own - // traffic-independent, PHY/bus-level "is a host actually there" check. #if ARDUINO_USB_MODE - // Native USB-Serial-JTAG peripheral (HWCDC), e.g. Heltec V4.3. - // isPlugged() is public/static, purely SOF-frame-based (USB PHY level), - // tracked from early boot via usb_serial_jtag_conn_status_init(). + // Native USB-Serial-JTAG (HWCDC), e.g. Heltec V4.3. bool isExternalPowered() override { return Serial.isPlugged(); } #else - // External USB-OTG PHY via TinyUSB (USBCDC), e.g. Wireless Tracker V2. - // tud_mounted() reflects real bus enumeration, independent of DTR/RTS. + // External USB-OTG via TinyUSB (USBCDC), e.g. Wireless Tracker V2. bool isExternalPowered() override { return tud_mounted(); } #endif - // Common limitation either way: a "dumb" charger with no data lines - // generates no SOF traffic and never enumerates, so it won't be seen - // as external power; only an active USB host (PC, OTG phone) will. #endif // https://docs.espressif.com/projects/esp-idf/en/v4.4.7/esp32/api-reference/system/system.html