Fix Cascade power saving, USB Companion, and mOTA flows
This commit is contained in:
1 parent
0d4274ceea
commit
818f610ba3
59 files changed
+3946
-2437
No files matched your search
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "BaseSerialInterface.h"
|
||||
#include "CompanionFrameQueue.h"
|
||||
|
||||
#ifndef MAX_INTERFACES
|
||||
// ble, usb, wifi, ethernet
|
||||
@@ -26,6 +27,31 @@ private:
|
||||
|
||||
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) {
|
||||
@@ -56,6 +82,7 @@ public:
|
||||
// find and remove interface
|
||||
for(int i = 0; i < MAX_INTERFACES; i++){
|
||||
if(_interfaces[i].instance == iface){
|
||||
clearReplyRouteFor(iface);
|
||||
_interfaces[i] = {};
|
||||
return true;
|
||||
}
|
||||
@@ -77,6 +104,7 @@ public:
|
||||
for(auto iface : _interfaces){
|
||||
if(iface.instance && iface.type == InterfaceType::Bluetooth){
|
||||
iface.instance->disable();
|
||||
clearReplyRouteFor(iface.instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -105,6 +133,8 @@ public:
|
||||
// enable all interfaces
|
||||
void enable() override {
|
||||
_enabled = true;
|
||||
_lastRxInterface = nullptr;
|
||||
_lockedReplyInterface = nullptr;
|
||||
for(auto iface : _interfaces){
|
||||
if(iface.instance){
|
||||
iface.instance->enable();
|
||||
@@ -115,6 +145,8 @@ public:
|
||||
// disable all interfaces
|
||||
void disable() override {
|
||||
_enabled = false;
|
||||
_lastRxInterface = nullptr;
|
||||
_lockedReplyInterface = nullptr;
|
||||
for(auto iface : _interfaces){
|
||||
if(iface.instance){
|
||||
iface.instance->disable();
|
||||
@@ -132,9 +164,17 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
// check if any interface is connected
|
||||
// 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->isConnected()) {
|
||||
if(iface.instance && iface.instance->isEnabled()
|
||||
&& iface.instance->isConnected()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -158,7 +198,12 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
// check if any interface is busy
|
||||
// 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;
|
||||
@@ -202,13 +247,35 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
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 || len == 0){
|
||||
if(!_enabled || src == nullptr || len == 0){
|
||||
return 0;
|
||||
}
|
||||
|
||||
// write frame to all enabled interfaces
|
||||
// Responses and delivery-required completion pushes belong to the client
|
||||
// which supplied the latest command. Best-effort asynchronous observations
|
||||
// remain broadcast so passive connected apps can keep their view fresh.
|
||||
if (mesh::companionFrameRequiresDelivery(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()){
|
||||
@@ -224,15 +291,30 @@ public:
|
||||
|
||||
size_t checkRecvFrame(uint8_t dest[]) override {
|
||||
// don't read when disabled
|
||||
if(!_enabled){
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user