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 <noreply@anthropic.com>
This commit is contained in:
rootandClaude Fable 5 committed 2026-08-14 20:13:36 +02:00
1 parent d929643524
commit ab492ba187
3 files changed
+43 -5

No files matched your search

+21
View File
@@ -49,6 +49,11 @@ MultiSerialInterface interface_manager;
#if defined(ENABLE_USB_INTERFACE)
#include <helpers/ArduinoSerialInterface.h>
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
+3 -1
View File
@@ -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;
}
}
+19 -4
View File
@@ -4,23 +4,38 @@
#include <Arduino.h>
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;