mirror of
https://github.com/liquidraver/ZephCore.git
synced 2026-09-02 02:39:30 +00:00
reactive backoff tuning
This commit is contained in:
@@ -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.0–2.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.0–2.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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user