#pragma once #include "BaseSerialInterface.h" #include "CompanionFrameQueue.h" #include "WirelessControl.h" #ifndef MAX_INTERFACES // ble, usb, wifi, ethernet #define MAX_INTERFACES 4 #endif enum class InterfaceType : uint8_t { NONE, Bluetooth, USB, WiFi, Ethernet, HardwareSerial }; class MultiSerialInterface : public BaseSerialInterface { private: struct RegisteredInterface { InterfaceType type = InterfaceType::NONE; BaseSerialInterface* instance = nullptr; }; bool _enabled = false; RegisteredInterface _interfaces[MAX_INTERFACES] = {}; BaseSerialInterface* _lastRxInterface = nullptr; BaseSerialInterface* _lockedReplyInterface = nullptr; bool isAvailableReplyTarget(BaseSerialInterface* target) const { if (target == nullptr) return false; for (auto iface : _interfaces) { if (iface.instance == target) { return target->isEnabled() && target->isConnected(); } } return false; } void clearReplyRouteFor(BaseSerialInterface* target) { // Keep a locked pointer until the response producer observes that its // target is unavailable and aborts the transaction. Clearing it here would // make the remaining frames fall back to broadcast on another interface. if (_lockedReplyInterface == target) return; if (_lastRxInterface == target) _lastRxInterface = nullptr; } BaseSerialInterface* replyTarget() const { return _lockedReplyInterface != nullptr ? _lockedReplyInterface : _lastRxInterface; } public: bool addInterface(InterfaceType type, BaseSerialInterface* iface) { // make sure an interface was provided if(iface == nullptr){ return false; } // put it in the first free slot for(int i = 0; i < MAX_INTERFACES; i++){ if(_interfaces[i].instance == nullptr){ _interfaces[i].instance = iface; _interfaces[i].type = type; return true; } } // no free slots available return false; } bool removeInterface(BaseSerialInterface* iface) { // make sure an interface was provided if(iface == nullptr){ return false; } // find and remove interface for(int i = 0; i < MAX_INTERFACES; i++){ if(_interfaces[i].instance == iface){ clearReplyRouteFor(iface); _interfaces[i] = {}; return true; } } // interface not found return false; } void enableBluetooth() { if (mesh::wireless::control().blocked(mesh::wireless::Bluetooth)) return; for(auto iface : _interfaces){ if(iface.instance && iface.type == InterfaceType::Bluetooth){ iface.instance->enable(); } } } void disableBluetooth() { for(auto iface : _interfaces){ if(iface.instance && iface.type == InterfaceType::Bluetooth){ iface.instance->disable(); clearReplyRouteFor(iface.instance); } } } bool isBluetoothEnabled() { for(auto iface : _interfaces){ if(iface.instance && iface.type == InterfaceType::Bluetooth){ return iface.instance->isEnabled(); } } return false; } bool isBluetoothConnected() const { return isInterfaceConnected(InterfaceType::Bluetooth); } bool isInterfaceConnected(InterfaceType type) const { if (!_enabled) return false; for (auto iface : _interfaces) { if (iface.instance && iface.type == type && iface.instance->isEnabled() && iface.instance->isConnected()) { return true; } } return false; } // enable all interfaces void enable() override { _enabled = true; _lastRxInterface = nullptr; _lockedReplyInterface = nullptr; for(auto iface : _interfaces){ if(iface.instance){ iface.instance->enable(); } } } // disable all interfaces void disable() override { _enabled = false; _lastRxInterface = nullptr; _lockedReplyInterface = nullptr; for(auto iface : _interfaces){ if(iface.instance){ iface.instance->disable(); } } } bool isEnabled() const override { return _enabled; } bool isConnected() const override { // not connected when disabled if(!_enabled){ return false; } // A locked multi-frame reply belongs to one client. Treat losing that // client as a disconnect even if another transport remains connected, so // the producer aborts instead of leaking the remainder to somebody else. if (_lockedReplyInterface != nullptr) { return isAvailableReplyTarget(_lockedReplyInterface); } // check if any enabled interface is connected for(auto iface : _interfaces){ if(iface.instance && iface.instance->isEnabled() && iface.instance->isConnected()) { return true; } } // nothing connected return false; } // loop all interfaces void loop() override { for(auto iface : _interfaces){ if(iface.instance){ iface.instance->loop(); } } } bool isWriteBusy() const override { // not busy when disabled if(!_enabled){ return false; } // Pace a response stream against its destination. A slow inactive BLE or // WiFi client must not stall a contact sync running over USB (or vice versa). BaseSerialInterface* target = replyTarget(); if (isAvailableReplyTarget(target)) return target->isWriteBusy(); // With no requester yet, preserve the aggregate behavior used for pushes. for(auto iface : _interfaces){ if(iface.instance && iface.instance->isEnabled() && iface.instance->isWriteBusy()){ return true; } } // nothing busy return false; } bool isReadBusy() const override { if (!_enabled) return false; for (auto iface : _interfaces) { if (iface.instance && iface.instance->isEnabled() && iface.instance->isReadBusy()) { return true; } } return false; } bool hasPendingIO() const override { if (!_enabled) return false; for (auto iface : _interfaces) { if (iface.instance && iface.instance->isEnabled() && iface.instance->hasPendingIO()) { return true; } } return false; } bool takePairingRequest() override { for (auto iface : _interfaces) { if (iface.instance && iface.type == InterfaceType::Bluetooth && iface.instance->isEnabled() && iface.instance->takePairingRequest()) { return true; } } return false; } // Exact transport-session resets can arrive independently of // isConnected() polling (for example, a close/reopen between two loop // iterations). Let the application abort only work which belongs to that // transport, without disturbing an active BLE/WiFi response transaction. bool isReplyRouteFor(BaseSerialInterface* target) const { return target != nullptr && replyTarget() == target; } BaseSerialInterface* captureReplyRoute() override { return replyTarget(); } bool isReplyRouteAvailable(BaseSerialInterface* route) const override { return _enabled && isAvailableReplyTarget(route); } bool isReplyRouteWriteBusy(BaseSerialInterface* route) const override { return !_enabled || !isAvailableReplyTarget(route) || route->isWriteBusy(); } size_t writeFrameToRoute(BaseSerialInterface* route, const uint8_t src[], size_t len) override { if (!_enabled || src == nullptr || len == 0 || !isAvailableReplyTarget(route)) { return 0; } return route->writeFrame(src, len); } // Call this only after the response producer for target has been stopped. // Forgetting both pointers prevents a new host on the same transport from // inheriting the prior host's reply destination. void forgetReplyRouteForDisconnected(BaseSerialInterface* target) { if (target == nullptr) return; if (_lockedReplyInterface == target) _lockedReplyInterface = nullptr; if (_lastRxInterface == target) _lastRxInterface = nullptr; } void lockReplyRoute() override { if (isAvailableReplyTarget(_lastRxInterface)) { _lockedReplyInterface = _lastRxInterface; } } void unlockReplyRoute() override { _lockedReplyInterface = nullptr; } size_t writeFrame(const uint8_t src[], size_t len) override { // don't write when disabled or nothing provided if(!_enabled || src == nullptr || len == 0){ return 0; } // Responses and command-completion pushes belong to their requester. // Unsolicited pushes remain broadcast regardless of queue priority so // passive connected apps can keep their view fresh. if (mesh::companionFrameUsesRequesterRoute(src, len)) { BaseSerialInterface* target = replyTarget(); if (target != nullptr) { if (!isAvailableReplyTarget(target)) return 0; return target->writeFrame(src, len); } } // Before any command establishes a reply route, or for best-effort pushes, // write the frame to all enabled interfaces. bool allSuccessful = true; for(auto iface : _interfaces){ if(iface.instance && iface.instance->isEnabled()){ if(iface.instance->writeFrame(src, len) != len){ allSuccessful = false; } } } // report success if all writes completed successfully return allSuccessful ? len : 0; } size_t checkRecvFrame(uint8_t dest[]) override { // don't read when disabled if(!_enabled || dest == nullptr){ return 0; } // Keep a multi-frame response transaction on its originating transport. // Other interfaces retain their input until the producer unlocks the route. if (_lockedReplyInterface != nullptr) { if (!isAvailableReplyTarget(_lockedReplyInterface)) return 0; size_t frameSize = _lockedReplyInterface->checkRecvFrame(dest); if (frameSize > 0) _lastRxInterface = _lockedReplyInterface; return frameSize; } if (_lastRxInterface != nullptr && !isAvailableReplyTarget(_lastRxInterface)) { _lastRxInterface = nullptr; } // try to read a frame from any enabled interface for(auto iface : _interfaces){ if(iface.instance && iface.instance->isEnabled()){ size_t frameSize = iface.instance->checkRecvFrame(dest); if(frameSize > 0){ _lastRxInterface = iface.instance; return frameSize; } } } // no frame received return 0; } };