#pragma once #include "../BaseSerialInterface.h" #include "../ArduinoSerialInterface.h" #include "SerialBLEInterface.h" // Wraps BLE + USB serial interfaces: BLE takes priority when connected, // USB is always ready as a fallback. Both state machines run continuously. class DualSerialInterface : public BaseSerialInterface { SerialBLEInterface _ble; ArduinoSerialInterface _usb; uint8_t _ble_buf[MAX_FRAME_SIZE]; uint8_t _usb_buf[MAX_FRAME_SIZE]; public: void begin(const char* ble_prefix, char* node_name, uint32_t pin_code, Stream& usb_stream) { _ble.begin(ble_prefix, node_name, pin_code); _usb.begin(usb_stream); } void enable() override { _ble.enable(); _usb.enable(); } void disable() override { _ble.disable(); _usb.disable(); } bool isEnabled() const override { return _ble.isEnabled() || _usb.isEnabled(); } // Always true — USB is always available as fallback, so the mesh can send. bool isConnected() const override { return true; } // True only when a BLE companion app is paired and connected. bool isBLEConnected() const override { return _ble.isConnected(); } bool isWriteBusy() const override { return _ble.isConnected() ? _ble.isWriteBusy() : _usb.isWriteBusy(); } size_t writeFrame(const uint8_t src[], size_t len) override { return _ble.isConnected() ? _ble.writeFrame(src, len) : _usb.writeFrame(src, len); } size_t checkRecvFrame(uint8_t dest[]) override { // Always pump both state machines: BLE needs this for TX queue drain and // advertising watchdog; USB needs it to drain its input buffer. size_t ble_len = _ble.checkRecvFrame(_ble_buf); size_t usb_len = _usb.checkRecvFrame(_usb_buf); if (_ble.isConnected()) { if (ble_len > 0) { memcpy(dest, _ble_buf, ble_len); return ble_len; } } else { if (usb_len > 0) { memcpy(dest, _usb_buf, usb_len); return usb_len; } } return 0; } };