reactive backoff tuning

This commit is contained in:
liquidraver
2026-03-15 21:14:21 +01:00
parent 3bec5fedb9
commit 4ad8b13756
5 changed files with 57 additions and 18 deletions
+33 -1
View File
@@ -234,7 +234,39 @@ Called every ~5 seconds from the main event loop:
2. **RX mode watchdog**: Flags error if radio stuck outside RX for >8 seconds
3. **AGC reset**: Periodic warm sleep + recalibration (configurable interval, default off)
### 4.7 Encryption
### 4.7 Adaptive Contention Window
Replaces Arduino MeshCore's static `txdelay`/`rxdelay` with two complementary mechanisms:
**EMA Delay Factor (proactive)**
`ContentionTracker` measures observed duplicates per retransmitted packet using a 16-entry ring buffer. Each entry tracks a packet (identified by FNV-1a hash) and records how many dupes arrive within a 10-second observation window. When the window closes, the entry is finalized and an EMA is updated with alpha = 1/8. The resulting estimate feeds the delay factor formula:
```
factor = 0.05 + 0.116 * sqrt(est)
```
Capped at 2.0. During warmup (fewer than 4 finalized entries), factor defaults to 0.5. Sparse nodes converge toward near-zero delay; dense nodes get proportionally higher delay. The factor scales the flood TX delay computed by `calcRxDelay()`.
**Per-Dupe Reactive Backoff**
When a duplicate of a pending outbound packet is heard, TX is rescheduled to `now + backoff_multiplier * airtime`. Each dupe triggers a full delay (not diminishing). Cumulative reactive extension is hard-capped at 2000 ms per packet; after the cap, CAD handles remaining channel activity. `backoff_multiplier` is configurable via `set backoff.multiplier X` (range 0.02.0).
**Direct Packets**
Direct (source-routed) packets bypass adaptive scaling entirely. They use minimal fixed jitter: `20 + rand(0, airtime / 10)` ms.
**CLI**
- `get txdelay` — shows current adaptive state (EMA estimate, delay factor, backoff multiplier).
- `set backoff.multiplier X` — controls per-dupe reactive delay (0.02.0).
- `txdelay`, `rxdelay`, `direct.txdelay` — accepted for prefs compatibility but ignored at runtime.
**ContentionTracker Resource Usage**
~172 bytes RAM. 16-entry ring buffer, FNV-1a packet hash, 10-second observation window, EMA with alpha = 1/8.
### 4.8 Encryption
- **Peer-to-peer**: ECDH shared secret (Curve25519) → AES-128-ECB encrypt → 2-byte HMAC-SHA256 MAC
- **Group channels**: SHA-256 of channel name → AES key
+5 -2
View File
@@ -122,8 +122,11 @@ void CommonCLI::loadPrefs(const char* path) {
_prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0.0f, 20.0f);
_prefs->tx_delay_factor = constrain(_prefs->tx_delay_factor, 0.0f, 2.0f);
_prefs->direct_tx_delay_factor = constrain(_prefs->direct_tx_delay_factor, 0.0f, 2.0f);
/* Migrate uninitialized pad bytes (0.0f or NaN) to default 0.5 */
if (_prefs->backoff_multiplier == 0.0f || _prefs->backoff_multiplier != _prefs->backoff_multiplier) {
/* Migrate uninitialized pad bytes: NaN or out-of-range → default 0.5.
* 0.0 is valid (disables reactive backoff). Old firmware upgrading
* with zeroed pad bytes will get 0.0 = disabled; user can set explicitly. */
if (_prefs->backoff_multiplier != _prefs->backoff_multiplier ||
_prefs->backoff_multiplier < 0.0f || _prefs->backoff_multiplier > 10.0f) {
_prefs->backoff_multiplier = 0.5f;
}
_prefs->backoff_multiplier = constrain(_prefs->backoff_multiplier, 0.0f, 2.0f);
+4 -2
View File
@@ -34,8 +34,9 @@ public:
* Caller should attempt reactive backoff when true. */
bool recordDupeIfTracked(uint32_t hash32, uint32_t now_ms);
/* Check if reactive extension is within cap for this entry.
* Returns the max additional ms allowed, 0 if cap reached. */
/* Per-dupe reactive delay: returns backoff_multiplier × airtime,
* clamped by hard cap minus cumulative extension so far.
* Returns 0 when hard cap reached or backoff disabled. */
uint16_t getReactiveHeadroom(uint32_t hash32, uint32_t airtime_ms) const;
/* Record that we added reactive extension to this entry. */
@@ -66,6 +67,7 @@ private:
static constexpr float FLOOD_SCALE = 0.116f; /* (0.5 - 0.05) / sqrt(15) */
static constexpr float MAX_FLOOD_FACTOR = 2.0f;
static constexpr float DEFAULT_BACKOFF_MULT = 0.5f;
static constexpr uint32_t REACTIVE_HARD_CAP_MS = 2000;
static constexpr uint32_t STALE_MS = 300000; /* 5 minutes */
struct Entry {
+8 -4
View File
@@ -108,11 +108,15 @@ uint16_t ContentionTracker::getReactiveHeadroom(uint32_t hash32, uint32_t airtim
int idx = findEntry(hash32);
if (idx < 0) return 0;
uint32_t cap = (uint32_t)(_backoff_multiplier * (float)airtime_ms);
if (_ring[idx].reactive_added_ms >= cap) return 0;
uint32_t per_dupe = (uint32_t)(_backoff_multiplier * (float)airtime_ms);
if (per_dupe == 0) return 0;
uint32_t remaining = cap - _ring[idx].reactive_added_ms;
return remaining > 0xFFFF ? 0xFFFF : (uint16_t)remaining;
/* Hard cap on total reactive extension per packet */
if (_ring[idx].reactive_added_ms >= REACTIVE_HARD_CAP_MS) return 0;
uint32_t remaining = REACTIVE_HARD_CAP_MS - _ring[idx].reactive_added_ms;
if (per_dupe > remaining) per_dupe = remaining;
return per_dupe > 0xFFFF ? 0xFFFF : (uint16_t)per_dupe;
}
void ContentionTracker::addReactiveExtension(uint32_t hash32, uint16_t added_ms)
+7 -9
View File
@@ -47,15 +47,13 @@ void Mesh::extendPendingRetransmit(uint32_t hash32)
if (pkt && pkt->isRouteFlood()
&& ContentionTracker::computePacketHash32(pkt) == hash32) {
uint32_t airtime = _radio->getEstAirtimeFor(pkt->getRawLength());
uint16_t headroom = _contention.getReactiveHeadroom(hash32, airtime);
if (headroom == 0) break;
uint32_t extra = _rng->nextInt(0, (int)headroom + 1);
/* Reschedule from NOW, not from the original EMA-based schedule.
* Hearing a dupe means the channel was just used defer from
* this moment, don't compound on top of the base delay. */
_mgr->rescheduleOutbound(i, now + extra);
_contention.addReactiveExtension(hash32, (uint16_t)extra);
notifyTxQueued(extra);
uint16_t delay = _contention.getReactiveHeadroom(hash32, airtime);
if (delay == 0) break;
/* Reschedule from NOW: heard a dupe, defer by one
* backoff_multiplier × airtime window per dupe. */
_mgr->rescheduleOutbound(i, now + delay);
_contention.addReactiveExtension(hash32, delay);
notifyTxQueued(delay);
break;
}
}