From ab492ba187917e68d91083fba748a7fea68b88b4 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 14 Aug 2026 20:13:36 +0200 Subject: [PATCH] Report the real USB client connection state in companion builds ArduinoSerialInterface::isConnected() always returned true, so any companion build with a USB interface believes a client is attached from boot onwards. Two user visible effects: - new message notifications never fire, because MyMesh only notifies the UI (display, buzzer) while no client is connected - on builds that also expose BLE the home screen shows '< Connected >' instead of the BLE pairing PIN, so a freshly flashed device cannot be paired at all Add an optional connection check callback and wire it up per platform: native USB-CDC (TinyUSB on nRF52/RP2040/ESP32 with USB_MODE=0) exposes real DTR through (bool)Serial. The ESP32 USB-Serial-JTAG peripheral has no DTR concept - it reports 'connected' as soon as the host enumerated the device - so fall back to frame activity there. Plain UARTs keep the previous assume-connected behaviour. Co-Authored-By: Claude Fable 5 --- examples/companion_radio/main.cpp | 21 +++++++++++++++++++++ src/helpers/ArduinoSerialInterface.cpp | 4 +++- src/helpers/ArduinoSerialInterface.h | 23 +++++++++++++++++++---- 3 files changed, 43 insertions(+), 5 deletions(-) diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 89f0e6cb..6b421550 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -49,6 +49,11 @@ MultiSerialInterface interface_manager; #if defined(ENABLE_USB_INTERFACE) #include ArduinoSerialInterface usb_serial_interface; + #ifndef USB_CLIENT_IDLE_TIMEOUT + // how long a USB client is still considered present after its last frame, + // for targets which cannot report DTR (see setConnectedCheck below) + #define USB_CLIENT_IDLE_TIMEOUT (10*60*1000UL) + #endif #endif // include ethernet interface @@ -213,6 +218,22 @@ void setup() { // add usb interface #if defined(ENABLE_USB_INTERFACE) usb_serial_interface.begin(Serial); +#if defined(ESP32) && defined(ARDUINO_USB_MODE) && ARDUINO_USB_MODE == 1 + // The ESP32 USB-Serial-JTAG peripheral (HWCDC) has no DTR concept at all: + // (bool)Serial only tells us the host has enumerated the device, which is + // already true when the cable is plugged into a powered port. Fall back to + // activity: a real client has to send us frames. + usb_serial_interface.setConnectedCheck([]() { + uint32_t last = usb_serial_interface.getLastFrameMillis(); + return (bool)Serial && last != 0 && (millis() - last) < USB_CLIENT_IDLE_TIMEOUT; + }); +#elif (defined(ESP32) && defined(ARDUINO_USB_CDC_ON_BOOT) && ARDUINO_USB_CDC_ON_BOOT) \ + || defined(NRF52_PLATFORM) || defined(RP2040_PLATFORM) + // native USB-CDC (TinyUSB): (bool)Serial reflects DTR, ie. the host really + // has the port open. A classic ESP32 behind a UART bridge has no such + // signal and keeps the assume-connected default. + usb_serial_interface.setConnectedCheck([]() { return (bool)Serial; }); +#endif interface_manager.addInterface(InterfaceType::USB, &usb_serial_interface); #endif diff --git a/src/helpers/ArduinoSerialInterface.cpp b/src/helpers/ArduinoSerialInterface.cpp index a01fa586..317e8395 100644 --- a/src/helpers/ArduinoSerialInterface.cpp +++ b/src/helpers/ArduinoSerialInterface.cpp @@ -13,7 +13,8 @@ void ArduinoSerialInterface::disable() { _isEnabled = false; } -bool ArduinoSerialInterface::isConnected() const { +bool ArduinoSerialInterface::isConnected() const { + if (_conn_check) return _conn_check(); return true; // no way of knowing, so assume yes } @@ -65,6 +66,7 @@ size_t ArduinoSerialInterface::checkRecvFrame(uint8_t dest[]) { if (_frame_len > MAX_FRAME_SIZE) _frame_len = MAX_FRAME_SIZE; // truncate memcpy(dest, rx_buf, _frame_len); _state = RECV_STATE_IDLE; // reset state, for next frame + _last_frame_ms = millis(); // a real client is talking to us return _frame_len; } } diff --git a/src/helpers/ArduinoSerialInterface.h b/src/helpers/ArduinoSerialInterface.h index c4086353..ecc05976 100644 --- a/src/helpers/ArduinoSerialInterface.h +++ b/src/helpers/ArduinoSerialInterface.h @@ -4,23 +4,38 @@ #include class ArduinoSerialInterface : public BaseSerialInterface { +public: + // reports whether a client currently has this stream open (see setConnectedCheck) + typedef bool (*ConnectedCheck)(); + +private: bool _isEnabled; uint8_t _state; uint16_t _frame_len; uint16_t rx_len; + uint32_t _last_frame_ms; Stream* _serial; + ConnectedCheck _conn_check; uint8_t rx_buf[MAX_FRAME_SIZE]; public: - ArduinoSerialInterface() { _isEnabled = false; _state = 0; } + ArduinoSerialInterface() { _isEnabled = false; _state = 0; _last_frame_ms = 0; _conn_check = NULL; } - void begin(Stream& serial) { - _serial = &serial; + void begin(Stream& serial) { + _serial = &serial; #ifdef RAK_4631 pinMode(WB_IO2, OUTPUT); - #endif + #endif } + // Optional: let the target report the real link state, e.g. USB-CDC DTR. + // Without it isConnected() assumes true, as a plain UART has no way of knowing. + void setConnectedCheck(ConnectedCheck fn) { _conn_check = fn; } + + // millis() of the last completely received frame, 0 if none since boot. + // Useful as an activity-based connection check where no DTR state exists. + uint32_t getLastFrameMillis() const { return _last_frame_ms; } + // BaseSerialInterface methods void enable() override; void disable() override;