Files
ZephCore/zephcore/include/mesh/ContentionTracker.h
T
liquidraver ba4b86752d acw: airtime-scale jitter caps, companion surroundings awareness
- flood retransmit jitter now capped at min(2000ms, 6·airtime) instead of
  fixed 2000ms — spreads tighter at SF7, unchanged at SF8
- reactive per-dupe backoff cap now min(2000ms, 12·airtime), keeps
  semantic of "push past ~12 relay slots"
- contention ring 16 → 24 for 50-neighbor hilltops
- companions passively track heard floods (warms EMA without forwarding)
  and spread their own TX by up to min(1000ms, 3·airtime), hopefully
  fixing repeaters missing companion's first transmission

config cleanup:
- move BLE TX buffer bumps (ACL_TX=12 etc.) from zephcore_common.conf to
  esp32_common.conf — the Espressif blob needs them, nRF doesn't, and
  the bumps were overflowing nRF52840 RAM
- remove CONFIG_ZEPHCORE_MAX_CONTACTS=510 overrides from 5 nRF52840
  companion boards; Kconfig default of 350 fits with comfortable margin
  (wio prod: 91% → 79% RAM)
2026-04-17 10:24:12 +02:00

81 lines
2.7 KiB
C++

/*
* SPDX-License-Identifier: Apache-2.0
* Adaptive Contention Window — EMA-based flood retransmit delay
*
* Counts neighbor retransmit dupes within a 10s window per packet.
* Dupe counts feed a rolling EMA that drives an adaptive delay factor.
*/
#pragma once
#include <stdint.h>
namespace mesh {
class Packet;
class ContentionTracker {
public:
ContentionTracker();
/* FNV-1a 32-bit hash for ring buffer correlation (not dedup SHA256). */
static uint32_t computePacketHash32(const Packet *pkt);
void trackRetransmit(uint32_t hash32, uint32_t now_ms);
/* Returns true if packet matched a tracked retransmit (dupe recorded). */
bool recordDupeIfTracked(uint32_t hash32, uint32_t now_ms);
/* Returns backoff_multiplier * airtime, clamped by remaining headroom.
* Returns 0 when hard cap reached or backoff disabled. */
uint16_t getReactiveHeadroom(uint32_t hash32, uint32_t airtime_ms) const;
void addReactiveExtension(uint32_t hash32, uint16_t added_ms);
/* Finalize expired entries into EMA. */
void tick(uint32_t now_ms);
float getContentionEstimate() const;
/* sqrt curve: MIN_FLOOD_FACTOR + FLOOD_SCALE * sqrt(est), cap 2.0.
* Returns 0.5 during warmup. */
float getFloodDelayFactor() const;
bool isWarmedUp() const { return _finalized_count >= WARMUP_PACKETS; }
void setBackoffMultiplier(float m) { _backoff_multiplier = m; }
float getBackoffMultiplier() const { return _backoff_multiplier; }
private:
static constexpr int RING_SIZE = 24; /* max concurrent tracked retransmits */
static constexpr uint32_t WINDOW_MS = 10000; /* dupe observation window; covers SF12 2-hop */
static constexpr int EMA_SHIFT = 3; /* alpha = 1/8 */
static constexpr int WARMUP_PACKETS = 4; /* min samples before EMA is trusted */
static constexpr float MIN_FLOOD_FACTOR = 0.05f; /* floor: near-zero delay in quiet networks */
static constexpr float FLOOD_SCALE = 0.170f; /* (0.5 - 0.05) / sqrt(15) */
static constexpr float MAX_FLOOD_FACTOR = 2.0f; /* ceiling: 2x base airtime */
static constexpr float DEFAULT_BACKOFF_MULT = 0.5f; /* half-airtime per dupe heard */
static constexpr uint32_t REACTIVE_HARD_CAP_MS = 2000; /* max cumulative reactive extension */
static constexpr uint32_t STALE_MS = 300000; /* 5 min: reset EMA if no traffic */
struct Entry {
uint32_t hash32;
uint32_t first_seen_ms;
uint8_t dupe_count;
uint16_t reactive_added_ms;
bool active;
};
Entry _ring[RING_SIZE];
int _next_idx;
uint32_t _ema_x256;
int _finalized_count;
uint32_t _last_retransmit_ms;
float _backoff_multiplier;
void finalizeEntry(int idx);
int findEntry(uint32_t hash32) const;
};
} /* namespace mesh */