373 lines
10 KiB
C++
373 lines
10 KiB
C++
#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;
|
|
}
|
|
|
|
};
|