diff --git a/src/mesh/StreamAPI.cpp b/src/mesh/StreamAPI.cpp index 5dd1ef99b..f0bb1c455 100644 --- a/src/mesh/StreamAPI.cpp +++ b/src/mesh/StreamAPI.cpp @@ -13,7 +13,9 @@ int32_t StreamAPI::runOncePart() { auto result = readStream(); - writeStream(); + // More to send: come straight back instead of sleeping out readStream's idle delay. + if (writeStream()) + result = 0; checkConnectionTimeout(); return result; } @@ -22,7 +24,8 @@ int32_t StreamAPI::runOncePart() int32_t StreamAPI::runOncePart(char *buf, uint16_t bufLen) { auto result = readStream(buf, bufLen); - writeStream(); + if (writeStream()) + result = 0; checkConnectionTimeout(); return result; } @@ -44,25 +47,29 @@ int32_t StreamAPI::readStream(const char *buf, uint16_t bufLen) } } -/** - * call getFromRadio() and deliver encapsulated packets to the Stream - */ -void StreamAPI::writeStream() +/// Emit a slice of pending output. True means "more to send, come straight back"; false covers +/// both a drained queue and backpressure, where retrying at once would only spin. +bool StreamAPI::writeStream() { - if (canWrite) { - // A transport that retained a short frame must complete it before - // getFromRadio() advances the PhoneAPI state to the next packet. - if (!finishPendingFrame()) - return; + if (!canWrite) + return false; - uint32_t len; - do { - // Send every packet we can - len = getFromRadio(txBuf + HEADER_LEN); - if (len != 0 && !emitTxBuffer(len)) - break; - } while (len); - } + // A retained short frame must complete before getFromRadio() advances the PhoneAPI state. + if (!finishPendingFrame()) + return false; + + // Draining a full dump in one call never returns to loop(), so the 8s hardware watchdog + // fires mid-dump. PhoneAPI is resumable, so stop at the budget and continue next dispatch. + uint32_t len; + uint32_t started = millis(); + do { + // Send every packet we can, up to this slice's budget + len = getFromRadio(txBuf + HEADER_LEN); + if (len != 0 && !emitTxBuffer(len)) + return false; + } while (len && Throttle::isWithinTimespanMs(started, STREAM_WRITE_BUDGET_MSEC)); + + return len != 0; } /// Parse supplied bytes through the framed ToRadio receive state machine. diff --git a/src/mesh/StreamAPI.h b/src/mesh/StreamAPI.h index 705460532..c91da4d02 100644 --- a/src/mesh/StreamAPI.h +++ b/src/mesh/StreamAPI.h @@ -9,6 +9,10 @@ // A To/FromRadio packet + our 32 bit header #define MAX_STREAM_BUF_SIZE (MAX_TO_FROM_RADIO_SIZE + sizeof(uint32_t)) +// Cap on one writeStream() slice: an uncapped dump never reaches loop(), so a board with a +// hardware watchdog (RP2350 arms 8s) resets mid-dump. +#define STREAM_WRITE_BUDGET_MSEC 100 + /** * A version of our 'phone' API that talks over a Stream. So therefore well suited to use with serial links * or TCP connections. @@ -64,10 +68,9 @@ class StreamAPI : public PhoneAPI int32_t readStream(const char *buf, uint16_t bufLen); int32_t handleRecStream(const char *buf, uint16_t bufLen); - /** - * call getFromRadio() and deliver encapsulated packets to the Stream - */ - void writeStream(); + /// Emit a slice of pending output. True asks the caller to come straight back; false covers + /// both a drained queue and backpressure, so it does not mean the queue is empty. + bool writeStream(); protected: /**