mirror of
https://github.com/liquidraver/ZephCore.git
synced 2026-09-02 06:33:42 +00:00
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)
This commit is contained in:
+164
-161
@@ -1,161 +1,164 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* Adaptive Contention Window — dupe-counting based delay estimation
|
||||
*/
|
||||
|
||||
#include <mesh/ContentionTracker.h>
|
||||
#include <mesh/Packet.h>
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
|
||||
namespace mesh {
|
||||
|
||||
ContentionTracker::ContentionTracker()
|
||||
: _next_idx(0), _ema_x256(0), _finalized_count(0),
|
||||
_last_retransmit_ms(0), _backoff_multiplier(DEFAULT_BACKOFF_MULT)
|
||||
{
|
||||
memset(_ring, 0, sizeof(_ring));
|
||||
}
|
||||
|
||||
/* FNV-1a over payload_type + first 8 payload bytes */
|
||||
uint32_t ContentionTracker::computePacketHash32(const Packet *pkt)
|
||||
{
|
||||
uint32_t h = 0x811c9dc5u; /* FNV-1a offset basis */
|
||||
uint8_t t = pkt->getPayloadType();
|
||||
h = (h ^ t) * 0x01000193u;
|
||||
int n = pkt->payload_len < 8 ? pkt->payload_len : 8;
|
||||
for (int i = 0; i < n; i++) {
|
||||
h = (h ^ pkt->payload[i]) * 0x01000193u;
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
int ContentionTracker::findEntry(uint32_t hash32) const
|
||||
{
|
||||
for (int i = 0; i < RING_SIZE; i++) {
|
||||
if (_ring[i].active && _ring[i].hash32 == hash32) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void ContentionTracker::finalizeEntry(int idx)
|
||||
{
|
||||
if (!_ring[idx].active) return;
|
||||
|
||||
uint32_t sample_x256 = (uint32_t)_ring[idx].dupe_count << 8;
|
||||
|
||||
int32_t diff = (int32_t)sample_x256 - (int32_t)_ema_x256;
|
||||
|
||||
if (_finalized_count < WARMUP_PACKETS) {
|
||||
/* Warmup: seed EMA with fast convergence */
|
||||
if (_finalized_count == 0) {
|
||||
_ema_x256 = sample_x256;
|
||||
} else {
|
||||
_ema_x256 = (uint32_t)((int32_t)_ema_x256 + (diff >> 1));
|
||||
}
|
||||
} else {
|
||||
_ema_x256 = (uint32_t)((int32_t)_ema_x256 + (diff >> EMA_SHIFT));
|
||||
}
|
||||
|
||||
_finalized_count++;
|
||||
_ring[idx].active = false;
|
||||
}
|
||||
|
||||
void ContentionTracker::trackRetransmit(uint32_t hash32, uint32_t now_ms)
|
||||
{
|
||||
_last_retransmit_ms = now_ms;
|
||||
|
||||
/* Evict oldest if ring slot occupied */
|
||||
if (_ring[_next_idx].active) {
|
||||
finalizeEntry(_next_idx);
|
||||
}
|
||||
|
||||
Entry &e = _ring[_next_idx];
|
||||
e.hash32 = hash32;
|
||||
e.first_seen_ms = now_ms;
|
||||
e.dupe_count = 0;
|
||||
e.reactive_added_ms = 0;
|
||||
e.active = true;
|
||||
|
||||
_next_idx = (_next_idx + 1) % RING_SIZE;
|
||||
}
|
||||
|
||||
bool ContentionTracker::recordDupeIfTracked(uint32_t hash32, uint32_t now_ms)
|
||||
{
|
||||
int idx = findEntry(hash32);
|
||||
if (idx < 0) return false;
|
||||
|
||||
Entry &e = _ring[idx];
|
||||
|
||||
if (now_ms - e.first_seen_ms > WINDOW_MS) {
|
||||
finalizeEntry(idx);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (e.dupe_count < 255) {
|
||||
e.dupe_count++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
uint16_t ContentionTracker::getReactiveHeadroom(uint32_t hash32, uint32_t airtime_ms) const
|
||||
{
|
||||
int idx = findEntry(hash32);
|
||||
if (idx < 0) return 0;
|
||||
|
||||
uint32_t per_dupe = (uint32_t)(_backoff_multiplier * (float)airtime_ms);
|
||||
if (per_dupe == 0) return 0;
|
||||
|
||||
/* Hard cap: REACTIVE_HARD_CAP_MS total 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)
|
||||
{
|
||||
int idx = findEntry(hash32);
|
||||
if (idx < 0) return;
|
||||
|
||||
uint32_t total = (uint32_t)_ring[idx].reactive_added_ms + added_ms;
|
||||
_ring[idx].reactive_added_ms = total > 0xFFFF ? 0xFFFF : (uint16_t)total;
|
||||
}
|
||||
|
||||
void ContentionTracker::tick(uint32_t now_ms)
|
||||
{
|
||||
for (int i = 0; i < RING_SIZE; i++) {
|
||||
if (_ring[i].active && now_ms - _ring[i].first_seen_ms > WINDOW_MS) {
|
||||
finalizeEntry(i);
|
||||
}
|
||||
}
|
||||
|
||||
/* Decay EMA toward 0 if no retransmit in STALE_MS */
|
||||
if (_last_retransmit_ms != 0 && now_ms - _last_retransmit_ms > STALE_MS) {
|
||||
if (_ema_x256 > 0) {
|
||||
_ema_x256 -= _ema_x256 >> EMA_SHIFT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float ContentionTracker::getContentionEstimate() const
|
||||
{
|
||||
return (float)_ema_x256 / 256.0f;
|
||||
}
|
||||
|
||||
float ContentionTracker::getFloodDelayFactor() const
|
||||
{
|
||||
if (!isWarmedUp()) return 0.5f; /* conservative default before warmup */
|
||||
|
||||
float est = getContentionEstimate();
|
||||
if (est <= 0.0f) return MIN_FLOOD_FACTOR;
|
||||
|
||||
float factor = MIN_FLOOD_FACTOR + FLOOD_SCALE * sqrtf(est);
|
||||
if (factor > MAX_FLOOD_FACTOR) factor = MAX_FLOOD_FACTOR;
|
||||
return factor;
|
||||
}
|
||||
|
||||
} /* namespace mesh */
|
||||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* Adaptive Contention Window — dupe-counting based delay estimation
|
||||
*/
|
||||
|
||||
#include <mesh/ContentionTracker.h>
|
||||
#include <mesh/Packet.h>
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
|
||||
namespace mesh {
|
||||
|
||||
ContentionTracker::ContentionTracker()
|
||||
: _next_idx(0), _ema_x256(0), _finalized_count(0),
|
||||
_last_retransmit_ms(0), _backoff_multiplier(DEFAULT_BACKOFF_MULT)
|
||||
{
|
||||
memset(_ring, 0, sizeof(_ring));
|
||||
}
|
||||
|
||||
/* FNV-1a over payload_type + first 8 payload bytes */
|
||||
uint32_t ContentionTracker::computePacketHash32(const Packet *pkt)
|
||||
{
|
||||
uint32_t h = 0x811c9dc5u; /* FNV-1a offset basis */
|
||||
uint8_t t = pkt->getPayloadType();
|
||||
h = (h ^ t) * 0x01000193u;
|
||||
int n = pkt->payload_len < 8 ? pkt->payload_len : 8;
|
||||
for (int i = 0; i < n; i++) {
|
||||
h = (h ^ pkt->payload[i]) * 0x01000193u;
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
int ContentionTracker::findEntry(uint32_t hash32) const
|
||||
{
|
||||
for (int i = 0; i < RING_SIZE; i++) {
|
||||
if (_ring[i].active && _ring[i].hash32 == hash32) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void ContentionTracker::finalizeEntry(int idx)
|
||||
{
|
||||
if (!_ring[idx].active) return;
|
||||
|
||||
uint32_t sample_x256 = (uint32_t)_ring[idx].dupe_count << 8;
|
||||
|
||||
int32_t diff = (int32_t)sample_x256 - (int32_t)_ema_x256;
|
||||
|
||||
if (_finalized_count < WARMUP_PACKETS) {
|
||||
/* Warmup: seed EMA with fast convergence */
|
||||
if (_finalized_count == 0) {
|
||||
_ema_x256 = sample_x256;
|
||||
} else {
|
||||
_ema_x256 = (uint32_t)((int32_t)_ema_x256 + (diff >> 1));
|
||||
}
|
||||
} else {
|
||||
_ema_x256 = (uint32_t)((int32_t)_ema_x256 + (diff >> EMA_SHIFT));
|
||||
}
|
||||
|
||||
_finalized_count++;
|
||||
_ring[idx].active = false;
|
||||
}
|
||||
|
||||
void ContentionTracker::trackRetransmit(uint32_t hash32, uint32_t now_ms)
|
||||
{
|
||||
_last_retransmit_ms = now_ms;
|
||||
|
||||
/* Evict oldest if ring slot occupied */
|
||||
if (_ring[_next_idx].active) {
|
||||
finalizeEntry(_next_idx);
|
||||
}
|
||||
|
||||
Entry &e = _ring[_next_idx];
|
||||
e.hash32 = hash32;
|
||||
e.first_seen_ms = now_ms;
|
||||
e.dupe_count = 0;
|
||||
e.reactive_added_ms = 0;
|
||||
e.active = true;
|
||||
|
||||
_next_idx = (_next_idx + 1) % RING_SIZE;
|
||||
}
|
||||
|
||||
bool ContentionTracker::recordDupeIfTracked(uint32_t hash32, uint32_t now_ms)
|
||||
{
|
||||
int idx = findEntry(hash32);
|
||||
if (idx < 0) return false;
|
||||
|
||||
Entry &e = _ring[idx];
|
||||
|
||||
if (now_ms - e.first_seen_ms > WINDOW_MS) {
|
||||
finalizeEntry(idx);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (e.dupe_count < 255) {
|
||||
e.dupe_count++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
uint16_t ContentionTracker::getReactiveHeadroom(uint32_t hash32, uint32_t airtime_ms) const
|
||||
{
|
||||
int idx = findEntry(hash32);
|
||||
if (idx < 0) return 0;
|
||||
|
||||
uint32_t per_dupe = (uint32_t)(_backoff_multiplier * (float)airtime_ms);
|
||||
if (per_dupe == 0) return 0;
|
||||
|
||||
/* Effective cap: ~12 relay-slots (airtime-scaled), absolute ceiling REACTIVE_HARD_CAP_MS */
|
||||
uint32_t effective_cap = 12 * airtime_ms;
|
||||
if (effective_cap > REACTIVE_HARD_CAP_MS) effective_cap = REACTIVE_HARD_CAP_MS;
|
||||
|
||||
if (_ring[idx].reactive_added_ms >= effective_cap) return 0;
|
||||
|
||||
uint32_t remaining = effective_cap - _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)
|
||||
{
|
||||
int idx = findEntry(hash32);
|
||||
if (idx < 0) return;
|
||||
|
||||
uint32_t total = (uint32_t)_ring[idx].reactive_added_ms + added_ms;
|
||||
_ring[idx].reactive_added_ms = total > 0xFFFF ? 0xFFFF : (uint16_t)total;
|
||||
}
|
||||
|
||||
void ContentionTracker::tick(uint32_t now_ms)
|
||||
{
|
||||
for (int i = 0; i < RING_SIZE; i++) {
|
||||
if (_ring[i].active && now_ms - _ring[i].first_seen_ms > WINDOW_MS) {
|
||||
finalizeEntry(i);
|
||||
}
|
||||
}
|
||||
|
||||
/* Decay EMA toward 0 if no retransmit in STALE_MS */
|
||||
if (_last_retransmit_ms != 0 && now_ms - _last_retransmit_ms > STALE_MS) {
|
||||
if (_ema_x256 > 0) {
|
||||
_ema_x256 -= _ema_x256 >> EMA_SHIFT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float ContentionTracker::getContentionEstimate() const
|
||||
{
|
||||
return (float)_ema_x256 / 256.0f;
|
||||
}
|
||||
|
||||
float ContentionTracker::getFloodDelayFactor() const
|
||||
{
|
||||
if (!isWarmedUp()) return 0.5f; /* conservative default before warmup */
|
||||
|
||||
float est = getContentionEstimate();
|
||||
if (est <= 0.0f) return MIN_FLOOD_FACTOR;
|
||||
|
||||
float factor = MIN_FLOOD_FACTOR + FLOOD_SCALE * sqrtf(est);
|
||||
if (factor > MAX_FLOOD_FACTOR) factor = MAX_FLOOD_FACTOR;
|
||||
return factor;
|
||||
}
|
||||
|
||||
} /* namespace mesh */
|
||||
|
||||
+723
-719
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user