feat(ui): detect USB-CDC connection for Auto mute (BLE or USB)

USB client presence IS detectable on nRF52 after all: (bool)Serial ==
tud_cdc_n_connected() (DTR — a host has the CDC port open). Add
isClientConnected() = BLE-bonded OR USB-CDC-open; DualSerialInterface
overrides it, base defaults to isConnected() (single-transport unchanged).

Wire it so each consumer gets the right signal:
- hasConnection() ← isClientConnected(): Auto buzzer mute + message-wake now
  trigger on BLE or an open USB port (PR #14's intent, done correctly), but
  not on charging-only (no host → DTR low).
- BT status indicator + pairing PIN stay on isBLEConnected() (BLE-specific).

Caveat: a plain serial monitor also asserts DTR, so it counts as connected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Jakub
2026-06-05 12:36:49 +02:00
co-authored by Claude Opus 4.8
parent 4ea67ee950
commit 3b795f7abc
4 changed files with 16 additions and 7 deletions
+5 -6
View File
@@ -2497,12 +2497,11 @@ void MyMesh::loop() {
} }
#ifdef DISPLAY_CLASS #ifdef DISPLAY_CLASS
// Use BLE-bonded state, not isConnected(): on a dual (BLE+USB) interface // "a companion app is connected over any transport" — BLE bonded or a host
// isConnected() is hardcoded true (USB is always a send fallback), and USB // holding the USB-CDC port open. Drives Auto buzzer mute + message-wake.
// client presence isn't detectable anyway. hasConnection() drives the BT // (Not isConnected(): a dual interface hardcodes that true as a send
// status indicator, message-wake and the Auto buzzer mute — all of which // fallback. The BT status indicator and pairing PIN use isBLEConnected().)
// mean "a companion app is connected", i.e. BLE bonded. if (_ui) _ui->setHasConnection(_serial->isClientConnected());
if (_ui) _ui->setHasConnection(_serial->isBLEConnected());
#endif #endif
} }
+1 -1
View File
@@ -456,7 +456,7 @@ class HomeScreen : public UIScreen {
int leftmostX = battLeftX; int leftmostX = battLeftX;
if (_task->isSerialEnabled()) { if (_task->isSerialEnabled()) {
int btX = battLeftX - ind - ind_gap; int btX = battLeftX - ind - ind_gap;
if (_task->hasConnection()) { if (_task->isBLEConnected()) { // BT icon reflects BLE link, not USB
display.setColor(DisplayDriver::LIGHT); display.setColor(DisplayDriver::LIGHT);
display.fillRect(btX, 0, ind, ind_h); display.fillRect(btX, 0, ind, ind_h);
display.setColor(DisplayDriver::DARK); display.setColor(DisplayDriver::DARK);
+6
View File
@@ -18,6 +18,12 @@ public:
// isConnected(); override in dual-interface wrappers that always report // isConnected(); override in dual-interface wrappers that always report
// isConnected()=true but still need to distinguish BLE from USB state. // isConnected()=true but still need to distinguish BLE from USB state.
virtual bool isBLEConnected() const { return isConnected(); } virtual bool isBLEConnected() const { return isConnected(); }
// True when a companion app is actually connected over *any* transport
// (BLE bonded or a host holding the USB-CDC port open). Distinct from
// isConnected(), which a dual interface hardcodes true as a send fallback.
// Used for app-connected UI (Auto buzzer mute, message-wake). Default =
// isConnected() so single-transport interfaces are unchanged.
virtual bool isClientConnected() const { return isConnected(); }
virtual bool isWriteBusy() const = 0; virtual bool isWriteBusy() const = 0;
virtual size_t writeFrame(const uint8_t src[], size_t len) = 0; virtual size_t writeFrame(const uint8_t src[], size_t len) = 0;
+4
View File
@@ -33,6 +33,10 @@ public:
bool isConnected() const override { return true; } bool isConnected() const override { return true; }
// True only when a BLE companion app is paired and connected. // True only when a BLE companion app is paired and connected.
bool isBLEConnected() const override { return _ble_enabled && _ble.isConnected(); } bool isBLEConnected() const override { return _ble_enabled && _ble.isConnected(); }
// A companion app is actually connected if BLE is bonded OR a host holds the
// USB-CDC port open. (bool)Serial == tud_cdc_n_connected() (DTR asserted) — a
// serial monitor counts too, but charging-only (no host) does not.
bool isClientConnected() const override { return isBLEConnected() || (bool)Serial; }
bool isWriteBusy() const override { bool isWriteBusy() const override {
return (_ble_enabled && _ble.isConnected()) ? _ble.isWriteBusy() : _usb.isWriteBusy(); return (_ble_enabled && _ble.isConnected()) ? _ble.isWriteBusy() : _usb.isWriteBusy();