fix(radio): keep the radio serviced while an e-ink refresh blocks the loop

GxEPD2 polls the panel's BUSY pin for the whole refresh (up to ~1-2s),
stalling the main loop. A TX finishing in that window left the SX126x in
standby -- deaf -- until the refresh ended; packets received meanwhile could
be lost (only the latest is readable, and a later CRC failure rejects a good
one still pending). Hook GxEPD2's busy callback to finish TX / drain RX into
a small staging queue and re-arm right away, without touching dispatch/UI.
Wired only where the display driver supports it (GxEPD2: the two e-ink Solo
boards); OLED and sim builds are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jakub
2026-09-24 15:21:38 +02:00
co-authored by Claude Opus 5.5
parent 81cee3846f
commit 22eba3c244
13 changed files with 184 additions and 16 deletions
+16
View File
@@ -30,6 +30,14 @@ protected:
unsigned long _marquee_next_at = 0;
DisplayDriver(int w, int h) { _w = w; _h = h; }
void setDimensions(int w, int h) { _w = w; _h = h; }
// Registered via setBusyPumpFn() below; drivers whose underlying vendor
// library exposes a hook for its blocking hardware wait (e.g. GxEPD2's
// setBusyCallback()) call this from it. Default no-op, so any driver/board
// that never wires one up behaves exactly as before.
void (*_busy_pump_fn)(void*) = nullptr;
void* _busy_pump_ctx = nullptr;
void callBusyPump() { if (_busy_pump_fn) _busy_pump_fn(_busy_pump_ctx); }
public:
enum Color { DARK=0, LIGHT, RED, GREEN, BLUE, YELLOW, ORANGE }; // on b/w screen, colors will be !=0 synonym of light
@@ -499,6 +507,14 @@ public:
virtual void setFullRefreshInterval(uint8_t n) { } // e-ink: do full refresh every n partial refreshes (0=never)
virtual void endFrame() = 0;
// Called from board setup to run safe background work during a blocking
// hardware wait (currently: an e-ink panel's BUSY-pin poll, which can run
// for over a second on a full refresh and would otherwise stall the whole
// main loop -- see RadioLibWrapper::pumpRecvDuringBlockingWait(), the
// motivating use). fn must not touch this display or any UI state: it can
// run reentrantly, nested inside the very call it's servicing.
void setBusyPumpFn(void (*fn)(void*), void* ctx) { _busy_pump_fn = fn; _busy_pump_ctx = ctx; }
#ifdef ENABLE_SCREENSHOT
// Screenshot support — return raw framebuffer and its size in bytes.
// 0=OLED (page-based, column-major), 1=e-ink (row-major, MSB-first, 1=white/0=black).