Bound failed RX retries without blocking power-saving sleep
This commit is contained in:
1 parent
862260de17
commit
339079c439
14 files changed
+463
-12
No files matched your search
+18
-1
@@ -584,10 +584,18 @@ bool Dispatcher::getNextQueueWakeDelay(uint32_t& delay_millis) const {
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t receive_retry_delay = 0;
|
||||
const bool receive_retry = radio_available
|
||||
&& _radio->getReceiveRetryDelay(now, receive_retry_delay);
|
||||
if (outbound != NULL) {
|
||||
if (outbound_radio_retry_pending) {
|
||||
int32_t signed_retry_delay = (int32_t)(outbound_radio_retry_at - now);
|
||||
delay_millis = signed_retry_delay > 0 ? (uint32_t)signed_retry_delay : 0;
|
||||
// This backoff branch services checkRecv(), but returns before AGC.
|
||||
// Wake for a failed RX retry without making overdue AGC a hot poll.
|
||||
if (receive_retry && receive_retry_delay < delay_millis) {
|
||||
delay_millis = receive_retry_delay;
|
||||
}
|
||||
} else {
|
||||
// TX completion/timeout still needs the normal fast lifecycle path.
|
||||
delay_millis = 0;
|
||||
@@ -595,6 +603,11 @@ bool Dispatcher::getNextQueueWakeDelay(uint32_t& delay_millis) const {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (receive_retry) {
|
||||
if (!found || receive_retry_delay < shortest_delay) shortest_delay = receive_retry_delay;
|
||||
found = true;
|
||||
}
|
||||
|
||||
if (outbound_restore_cr) {
|
||||
const int32_t restore_delay = (int32_t)(outbound_restore_retry_at - now);
|
||||
const uint32_t delay = restore_delay > 0 ? (uint32_t)restore_delay : 0;
|
||||
@@ -854,7 +867,11 @@ void Dispatcher::loop() {
|
||||
agc_reset.noteAttempt(_ms->getMillis());
|
||||
} else {
|
||||
// Ownership/calibration deferral is not a reset or health evidence.
|
||||
agc_reset.noteDeferred(_ms->getMillis());
|
||||
const uint32_t deferred_at = _ms->getMillis();
|
||||
uint32_t retry_delay = 50, receive_delay;
|
||||
if (adaptive && _radio->getReceiveRetryDelay(deferred_at, receive_delay)
|
||||
&& receive_delay > retry_delay) retry_delay = receive_delay;
|
||||
agc_reset.noteDeferred(deferred_at, retry_delay);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -215,6 +215,11 @@ public:
|
||||
return getNoiseFloorCalibrationSecondsRemaining();
|
||||
}
|
||||
virtual bool isCalibratingNoiseFloor() const { return false; }
|
||||
// A failed receive start may need a later retry without keeping the MCU
|
||||
// awake. Transports without a deferred receive retry retain their cadence.
|
||||
virtual bool getReceiveRetryDelay(uint32_t now, uint32_t& delay_ms) const {
|
||||
(void)now; (void)delay_ms; return false;
|
||||
}
|
||||
|
||||
virtual void triggerNoiseFloorCalibrate(int threshold) { }
|
||||
|
||||
|
||||
@@ -54,8 +54,9 @@ public:
|
||||
|
||||
// A busy operation changes only its retry deadline, never the reset cadence
|
||||
// or adaptive stage. This permits sleep instead of spinning on a due reset.
|
||||
void noteDeferred(uint32_t now) {
|
||||
if (_state) _next = now + 50;
|
||||
void noteDeferred(uint32_t now, uint32_t retry_delay_ms = 50) {
|
||||
if (retry_delay_ms == 0 || retry_delay_ms > INT32_MAX) retry_delay_ms = 50;
|
||||
if (_state) _next = now + retry_delay_ms;
|
||||
}
|
||||
|
||||
// Call only after an actual accepted reset sequence, not a busy/deferred one.
|
||||
|
||||
@@ -207,6 +207,9 @@ void RadioLibWrapper::begin() {
|
||||
}
|
||||
#endif
|
||||
state = STATE_IDLE;
|
||||
_rx_retry_at = 0;
|
||||
_rx_retry_delay_ms = 0;
|
||||
_rx_retry_pending = false;
|
||||
|
||||
if (_board->getStartupReason() == BD_STARTUP_RX_PACKET) { // received a LoRa packet (while in deep sleep)
|
||||
_loop_event_pending = true; // Main-owned wake event; ISR remains sole publisher
|
||||
@@ -1877,6 +1880,8 @@ void RadioLibWrapper::startRecv() {
|
||||
// Keep the partial block, but wait for the frontend before its next sample.
|
||||
_nf_sample_from = millis() + NF_CALIB_SETTLE_MS;
|
||||
_startrx_fails = 0;
|
||||
_rx_retry_pending = false;
|
||||
_rx_retry_delay_ms = 0;
|
||||
if (_rx_ps_armed) {
|
||||
// (re)base the duty-cycle watchdog on the freshly armed cycle
|
||||
_wd_last_busy = isChipBusy();
|
||||
@@ -1895,6 +1900,15 @@ void RadioLibWrapper::startRecv() {
|
||||
if (_wd_observe_ms > 1500) _wd_observe_ms = 1500;
|
||||
}
|
||||
} else {
|
||||
state = STATE_IDLE;
|
||||
_rx_ps_armed = false;
|
||||
// Recover a transient failure promptly, then permit whole-second timed
|
||||
// sleep under persistent failure. Direct retune/CAD/packet rearms remain
|
||||
// immediate; only recvRaw's repeated idle fallback observes this deadline.
|
||||
_rx_retry_delay_ms = _rx_retry_delay_ms == 0 ? 20
|
||||
: (_rx_retry_delay_ms < 1000 ? _rx_retry_delay_ms * 2 : 2000);
|
||||
_rx_retry_at = millis() + _rx_retry_delay_ms;
|
||||
_rx_retry_pending = true;
|
||||
if (_startrx_fails < 255) _startrx_fails++;
|
||||
MESH_DEBUG_PRINTLN("RadioLibWrapper: error: startReceiveMode(%d)", err);
|
||||
}
|
||||
@@ -1933,6 +1947,15 @@ bool RadioLibWrapper::isInRecvMode() const {
|
||||
return state == STATE_RX;
|
||||
}
|
||||
|
||||
bool RadioLibWrapper::getReceiveRetryDelay(uint32_t now, uint32_t& delay_ms) const {
|
||||
#if MESH_ENABLE_TX_BEACONS
|
||||
if (_beacon_owned) return false;
|
||||
#endif
|
||||
if (!_rx_retry_pending || state != STATE_IDLE || _cw_active) return false;
|
||||
delay_ms = static_cast<int32_t>(now - _rx_retry_at) >= 0 ? 0 : _rx_retry_at - now;
|
||||
return true;
|
||||
}
|
||||
|
||||
// RX PowerSaving
|
||||
bool RadioLibWrapper::setRxPowerSaving(bool enabled, uint32_t rx_us, uint32_t sleep_us) {
|
||||
#if MESH_ENABLE_TX_BEACONS
|
||||
@@ -1965,7 +1988,7 @@ int RadioLibWrapper::recvRaw(uint8_t* bytes, int sz) {
|
||||
#if MESH_ENABLE_TX_BEACONS
|
||||
if (_beacon_owned) return 0;
|
||||
#endif
|
||||
if (_cw_active) return 0;
|
||||
if (_cw_active || state == STATE_TX_WAIT) return 0;
|
||||
int len = 0;
|
||||
#if defined(USE_LR2021)
|
||||
bool receiver_stopped = false;
|
||||
@@ -2005,6 +2028,8 @@ int RadioLibWrapper::recvRaw(uint8_t* bytes, int sz) {
|
||||
// Serial.print(" readData() -> "); Serial.println(len);
|
||||
n_recv++;
|
||||
last_recv_millis = millis();
|
||||
_rx_retry_pending = false;
|
||||
_rx_retry_delay_ms = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2022,6 +2047,9 @@ int RadioLibWrapper::recvRaw(uint8_t* bytes, int sz) {
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
// Serviced receive events retain the normal immediate rearm, even if an
|
||||
// earlier idle start failed. A pending IRQ probe above still owns its FIFO.
|
||||
_rx_retry_pending = false;
|
||||
state = STATE_IDLE; // need another startReceive()
|
||||
}
|
||||
|
||||
@@ -2033,16 +2061,32 @@ int RadioLibWrapper::recvRaw(uint8_t* bytes, int sz) {
|
||||
int err = _radio->startReceive();
|
||||
if (err == RADIOLIB_ERR_NONE) {
|
||||
state = STATE_RX;
|
||||
_rx_retry_pending = false;
|
||||
_rx_retry_delay_ms = 0;
|
||||
if (_nf_calib_active) {
|
||||
_nf_sample_from = millis() + NF_CALIB_SETTLE_MS;
|
||||
}
|
||||
} else {
|
||||
_rx_retry_delay_ms = 20; // the delivered packet just proved RX was working
|
||||
_rx_retry_at = millis() + _rx_retry_delay_ms;
|
||||
_rx_retry_pending = true;
|
||||
MESH_DEBUG_PRINTLN("RadioLibWrapper: error: startReceive after packet (%d)", err);
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
if (state != STATE_RX) {
|
||||
if (_rx_retry_pending) {
|
||||
const uint32_t now = millis();
|
||||
if (static_cast<int32_t>(now - _rx_retry_at) < 0) return len;
|
||||
// A command error can leave hardware receiving despite the idle software
|
||||
// state. Preserve that owner and move the retry deadline forward instead
|
||||
// of keeping a permanently due wake or issuing SPI while BUSY is high.
|
||||
if (isChipBusy() || isPacketPendingOrReceiving() || hasPendingRadioInterrupt()) {
|
||||
_rx_retry_at = now + 2000;
|
||||
return len;
|
||||
}
|
||||
}
|
||||
startRecv();
|
||||
}
|
||||
return len;
|
||||
@@ -2164,6 +2208,7 @@ void RadioLibWrapper::onSendFinished() {
|
||||
claimRadioInterrupt();
|
||||
_irq_probe_failures = 0;
|
||||
_board->onAfterTransmit();
|
||||
_rx_retry_pending = false; // TX retirement gets its usual immediate RX rearm
|
||||
state = STATE_IDLE;
|
||||
}
|
||||
|
||||
|
||||
@@ -58,6 +58,11 @@ protected:
|
||||
NoiseFloorEstimator _secondary_floor_estimator;
|
||||
uint32_t _rx_mode_checked_at = 0;
|
||||
uint8_t _rx_mode_failures = 0;
|
||||
// Independent of calibration and watchdog counters: repeated failed starts
|
||||
// must retain their backoff through an AGC reset or radio recovery attempt.
|
||||
uint32_t _rx_retry_at = 0;
|
||||
uint16_t _rx_retry_delay_ms = 0;
|
||||
bool _rx_retry_pending = false;
|
||||
unsigned long last_recv_millis;
|
||||
unsigned long last_radio_interrupt_millis; // updated on any ISR event, even CRC errors
|
||||
bool _rx_ps_enabled;
|
||||
@@ -530,10 +535,11 @@ public:
|
||||
// true while a noise-floor batch needs prompt loop service; the app's
|
||||
// hasPendingWork() keeps the MCU awake for the bounded spaced-sample window.
|
||||
bool isCalibratingNoiseFloor() const override {
|
||||
return _nf_calib_active
|
||||
return isInRecvMode() && !_rx_ps_armed && (_nf_calib_active
|
||||
|| (_nf_refresh_requested
|
||||
&& (!_rx_ps_enabled || _rx_ps_continuous_fallback));
|
||||
&& (!_rx_ps_enabled || _rx_ps_continuous_fallback)));
|
||||
}
|
||||
bool getReceiveRetryDelay(uint32_t now, uint32_t& delay_ms) const override;
|
||||
void resetStats() { n_recv = n_sent = n_recv_errors = 0; }
|
||||
|
||||
uint8_t getRadioState() const override;
|
||||
|
||||
Reference in new issue
Block a user