mirror of
https://github.com/liquidraver/ZephCore.git
synced 2026-09-02 16:43:42 +00:00
After broadcasting a group message, wait 5s to see if any neighbor
repeated the flood and use the outcome to mark the on-device entry
and the BLE-app mirror.
- ContentionTracker gets extractDupeCount(hash): finds the tracked
entry, captures dupe_count, finalizes (folds into EMA, marks
inactive), returns the count or -1.
- BaseChatMesh::sendGroupMessage gains an optional out_hash param;
when set, the FNV-1a packet hash is also pre-registered with the
contention tracker so heard retransmits get counted (originated
floods weren't tracked before, only relays).
- Mesh::getContentionTracker() promoted to public so the UI can
query after the feedback window.
JoystickUITask grows a 4-slot pending-channel table with per-slot
k_timer (5s one-shot). startPendingChannel() broadcasts, adds the
local _ch_previews entry with path_len = OUT_PATH_SENT, and starts
the feedback timer. The timer ISR sets a feedback_due flag; the
loop's processPendingChannelFeedback() picks it up, calls
extractDupeCount(), and rewrites the preview's path_len to
OUT_PATH_SENT_HEARD (0xFD) or OUT_PATH_SENT_UNHEARD (0xFC) — which
formatHopCount renders as "sent+" / "sent?".
The deferred BLE-app mirror queues only on outcome with body prefix
"(>>✓) " (heard) or "(>>✗) " (not heard). queueLocalSentChannelMessage
gains a heard_repeat parameter for the selection.
Both channel send entry points (sendComposedMessage's channel branch
and sendChannelMessage) now route through startPendingChannel().
89 lines
3.0 KiB
C++
89 lines
3.0 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);
|
|
|
|
/* Capture the dupe count for the given hash and remove the entry from
|
|
* active tracking (also folds the sample into the EMA exactly once,
|
|
* same as natural finalization would have). Returns the dupe count, or
|
|
* -1 if the entry isn't found (already finalized, or never tracked).
|
|
* Used by the joystick UI to ask "did my channel send get repeated?"
|
|
* a few seconds after sending. */
|
|
int extractDupeCount(uint32_t hash32);
|
|
|
|
/* 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;
|
|
|
|
/* Saturating curve: MIN + (MAX-MIN) * est/(est+HALFPOINT).
|
|
* 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.40f; /* sparse mesh baseline */
|
|
static constexpr float MAX_FLOOD_FACTOR = 1.00f; /* dense mesh ceiling */
|
|
static constexpr float FLOOD_EST_HALFPOINT = 4.0f; /* midpoint: factor=0.60 at est=4 */
|
|
static constexpr float DEFAULT_BACKOFF_MULT = 0.2f; /* airtime*0.2 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 */
|