mirror of
https://github.com/agessaman/MeshCore.git
synced 2026-07-21 09:01:09 +00:00
Retry 3 times with a 200ms,300ms,400ms backoff.
This commit is contained in:
@@ -98,6 +98,19 @@
|
||||
|
||||
---
|
||||
|
||||
### Get or set recent repeater fallback prefix/SNR
|
||||
**Usage:**
|
||||
- `recent.repeater`
|
||||
- `recent.repeater <prefix_hex> <snr_db>`
|
||||
|
||||
**Parameters:**
|
||||
- `prefix_hex`: 1-3 bytes of next-hop prefix (hex)
|
||||
- `snr_db`: SNR in dB (supports decimals; stored at x4 precision)
|
||||
|
||||
**Note:** `set` is rejected when the prefix already exists in neighbors.
|
||||
|
||||
---
|
||||
|
||||
## Statistics
|
||||
|
||||
### Clear Stats
|
||||
|
||||
@@ -77,6 +77,15 @@ const NeighbourInfo* MyMesh::findNeighbourByHash(const uint8_t* hash, uint8_t ha
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool MyMesh::allowRecentRepeaterPrefixStore(const uint8_t* prefix, uint8_t prefix_len, void* ctx) {
|
||||
if (ctx == NULL || prefix == NULL || prefix_len == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const MyMesh* self = (const MyMesh*) ctx;
|
||||
return self->findNeighbourByHash(prefix, prefix_len) == NULL;
|
||||
}
|
||||
|
||||
void MyMesh::putNeighbour(const mesh::Identity &id, uint32_t timestamp, float snr) {
|
||||
#if MAX_NEIGHBOURS // check if neighbours enabled
|
||||
// find existing neighbour, else use least recently updated
|
||||
@@ -510,6 +519,34 @@ void MyMesh::logTxFail(mesh::Packet *pkt, int len) {
|
||||
}
|
||||
}
|
||||
|
||||
void MyMesh::onDirectRetryEvent(const char* event, const mesh::Packet* packet, uint32_t delay_millis) {
|
||||
if (packet == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
MESH_DEBUG_PRINTLN("%s direct retry %s (type=%d, route=%s, payload_len=%d, delay=%lu)",
|
||||
getLogDateTime(),
|
||||
event,
|
||||
(uint32_t)packet->getPayloadType(),
|
||||
packet->isRouteDirect() ? "D" : "F",
|
||||
(uint32_t)packet->payload_len,
|
||||
(unsigned long)delay_millis);
|
||||
|
||||
if (_logging) {
|
||||
File f = openAppend(PACKET_LOG_FILE);
|
||||
if (f) {
|
||||
f.print(getLogDateTime());
|
||||
f.printf(": DIRECT RETRY %s (type=%d, route=%s, payload_len=%d, delay=%lu)\n",
|
||||
event,
|
||||
(uint32_t)packet->getPayloadType(),
|
||||
packet->isRouteDirect() ? "D" : "F",
|
||||
(uint32_t)packet->payload_len,
|
||||
(unsigned long)delay_millis);
|
||||
f.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int MyMesh::calcRxDelay(float score, uint32_t air_time) const {
|
||||
if (_prefs.rx_delay_base <= 0.0f) return 0;
|
||||
return (int)((pow(_prefs.rx_delay_base, 0.85f - score) - 1.0) * air_time);
|
||||
@@ -880,6 +917,8 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
|
||||
active_bw = _prefs.bw;
|
||||
active_sf = _prefs.sf;
|
||||
active_cr = _prefs.cr;
|
||||
|
||||
((SimpleMeshTables *)getTables())->setRecentRepeaterAllowFilter(&MyMesh::allowRecentRepeaterPrefixStore, this);
|
||||
}
|
||||
|
||||
void MyMesh::begin(FILESYSTEM *fs) {
|
||||
@@ -901,6 +940,7 @@ void MyMesh::begin(FILESYSTEM *fs) {
|
||||
active_bw = _prefs.bw;
|
||||
active_sf = _prefs.sf;
|
||||
active_cr = _prefs.cr;
|
||||
((SimpleMeshTables *)getTables())->setRecentRepeaterMinSNRX4(getDirectRetryMinSNRX4());
|
||||
radio_set_tx_power(_prefs.tx_power_dbm);
|
||||
|
||||
updateAdvertTimer();
|
||||
@@ -1250,6 +1290,48 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply
|
||||
} else {
|
||||
strcpy(reply, "Err - ??");
|
||||
}
|
||||
} else if (memcmp(command, "recent.repeater", 15) == 0) {
|
||||
const char* sub = command + 15;
|
||||
while (*sub == ' ') sub++;
|
||||
auto* tables = (SimpleMeshTables*)getTables();
|
||||
if (*sub == 0) {
|
||||
const auto* info = tables->getLatestRecentRepeater();
|
||||
if (info == NULL) {
|
||||
strcpy(reply, "> none");
|
||||
} else {
|
||||
char hex[(MAX_ROUTE_HASH_BYTES * 2) + 1];
|
||||
mesh::Utils::toHex(hex, info->prefix, info->prefix_len);
|
||||
sprintf(reply, "> %s,%s", hex, StrHelper::ftoa(((float)info->snr_x4) / 4.0f));
|
||||
}
|
||||
} else {
|
||||
char* params = (char*) sub;
|
||||
char* arg_snr = strchr(params, ' ');
|
||||
if (arg_snr == NULL) {
|
||||
strcpy(reply, "Err - usage: recent.repeater <prefix_hex> <snr_db>");
|
||||
} else {
|
||||
*arg_snr++ = 0;
|
||||
while (*arg_snr == ' ') arg_snr++;
|
||||
if (*arg_snr == 0) {
|
||||
strcpy(reply, "Err - usage: recent.repeater <prefix_hex> <snr_db>");
|
||||
} else {
|
||||
int hex_len = strlen(params);
|
||||
int prefix_len = hex_len / 2;
|
||||
uint8_t prefix[MAX_ROUTE_HASH_BYTES] = {0};
|
||||
if ((hex_len % 2) != 0 || prefix_len <= 0 || prefix_len > MAX_ROUTE_HASH_BYTES || !mesh::Utils::fromHex(prefix, prefix_len, params)) {
|
||||
strcpy(reply, "Err - prefix must be 1-3 bytes hex");
|
||||
} else {
|
||||
float snr_db = strtof(arg_snr, nullptr);
|
||||
int snr_x4 = (int)(snr_db * 4.0f + (snr_db >= 0.0f ? 0.5f : -0.5f));
|
||||
snr_x4 = constrain(snr_x4, -128, 127);
|
||||
if (tables->setRecentRepeater(prefix, (uint8_t)prefix_len, (int8_t)snr_x4)) {
|
||||
strcpy(reply, "OK");
|
||||
} else {
|
||||
strcpy(reply, "Err - prefix is already in neighbors");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else{
|
||||
_cli.handleCommand(sender_timestamp, command, reply); // common CLI commands
|
||||
}
|
||||
@@ -1293,6 +1375,9 @@ void MyMesh::loop() {
|
||||
MESH_DEBUG_PRINTLN("Radio params restored");
|
||||
}
|
||||
|
||||
// Keep recent-prefix learning aligned with the live retry SNR gate.
|
||||
((SimpleMeshTables *)getTables())->setRecentRepeaterMinSNRX4(getDirectRetryMinSNRX4());
|
||||
|
||||
// is pending dirty contacts write needed?
|
||||
if (dirty_contacts_expiry && millisHasNowPassed(dirty_contacts_expiry)) {
|
||||
acl.save(_fs);
|
||||
|
||||
@@ -119,6 +119,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
|
||||
#endif
|
||||
|
||||
const NeighbourInfo* findNeighbourByHash(const uint8_t* hash, uint8_t hash_len) const;
|
||||
static bool allowRecentRepeaterPrefixStore(const uint8_t* prefix, uint8_t prefix_len, void* ctx);
|
||||
int8_t getDirectRetryMinSNRX4() const;
|
||||
void putNeighbour(const mesh::Identity& id, uint32_t timestamp, float snr);
|
||||
uint8_t handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data, bool is_flood);
|
||||
@@ -148,6 +149,7 @@ protected:
|
||||
uint32_t getDirectRetransmitDelay(const mesh::Packet* packet) override;
|
||||
bool allowDirectRetry(const mesh::Packet* packet, const uint8_t* next_hop_hash, uint8_t next_hop_hash_len) const override;
|
||||
uint32_t getDirectRetryEchoDelay(const mesh::Packet* packet) const override;
|
||||
void onDirectRetryEvent(const char* event, const mesh::Packet* packet, uint32_t delay_millis) override;
|
||||
|
||||
int getInterferenceThreshold() const override {
|
||||
return _prefs.interference_threshold;
|
||||
|
||||
@@ -161,6 +161,8 @@ protected:
|
||||
virtual uint32_t getCADFailMaxDuration() const;
|
||||
virtual int getInterferenceThreshold() const { return 0; } // disabled by default
|
||||
virtual int getAGCResetInterval() const { return 0; } // disabled by default
|
||||
virtual unsigned long getDutyCycleWindowMs() const { return 3600000; }
|
||||
const Packet* getOutboundInFlight() const { return outbound; }
|
||||
|
||||
public:
|
||||
void begin();
|
||||
|
||||
+53
-2
@@ -3,12 +3,16 @@
|
||||
|
||||
namespace mesh {
|
||||
|
||||
static const uint8_t DIRECT_RETRY_MAX_ATTEMPTS = 3;
|
||||
static const uint32_t DIRECT_RETRY_BACKOFF_MS[DIRECT_RETRY_MAX_ATTEMPTS] = { 200, 300, 400 };
|
||||
|
||||
void Mesh::begin() {
|
||||
for (int i = 0; i < MAX_DIRECT_RETRY_SLOTS; i++) {
|
||||
_direct_retries[i].packet = NULL;
|
||||
_direct_retries[i].trigger_packet = NULL;
|
||||
_direct_retries[i].retry_at = 0;
|
||||
_direct_retries[i].retry_delay = 0;
|
||||
_direct_retries[i].retry_attempts_sent = 0;
|
||||
_direct_retries[i].priority = 0;
|
||||
_direct_retries[i].progress_marker = 0;
|
||||
_direct_retries[i].expect_path_growth = false;
|
||||
@@ -27,6 +31,9 @@ void Mesh::loop() {
|
||||
}
|
||||
|
||||
if (!isDirectRetryQueued(_direct_retries[i].packet)) {
|
||||
if (_direct_retries[i].packet == getOutboundInFlight()) {
|
||||
continue; // currently transmitting; keep slot until onSendComplete/onSendFail emits event
|
||||
}
|
||||
clearDirectRetrySlot(i);
|
||||
}
|
||||
}
|
||||
@@ -443,6 +450,7 @@ void Mesh::clearDirectRetrySlot(int idx) {
|
||||
_direct_retries[idx].trigger_packet = NULL;
|
||||
_direct_retries[idx].retry_at = 0;
|
||||
_direct_retries[idx].retry_delay = 0;
|
||||
_direct_retries[idx].retry_attempts_sent = 0;
|
||||
_direct_retries[idx].priority = 0;
|
||||
_direct_retries[idx].progress_marker = 0;
|
||||
_direct_retries[idx].expect_path_growth = false;
|
||||
@@ -491,8 +499,12 @@ bool Mesh::cancelDirectRetryOnEcho(const Packet* packet) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
onDirectRetryEvent("canceled_echo", _direct_retries[i].packet, 0);
|
||||
onDirectRetryEvent("good", _direct_retries[i].packet, 0);
|
||||
clearDirectRetrySlot(i);
|
||||
} else {
|
||||
onDirectRetryEvent("canceled_echo", _direct_retries[i].trigger_packet, 0);
|
||||
onDirectRetryEvent("good", _direct_retries[i].trigger_packet, 0);
|
||||
clearDirectRetrySlot(i);
|
||||
}
|
||||
cleared = true;
|
||||
@@ -510,7 +522,35 @@ void Mesh::armDirectRetryOnSendComplete(const Packet* packet) {
|
||||
if (_direct_retries[i].queued) {
|
||||
if (_direct_retries[i].packet == packet) {
|
||||
// The retry packet itself just finished transmitting; Dispatcher will release it after this hook.
|
||||
clearDirectRetrySlot(i);
|
||||
onDirectRetryEvent("resent", packet, 0);
|
||||
_direct_retries[i].retry_attempts_sent++;
|
||||
if (_direct_retries[i].retry_attempts_sent >= DIRECT_RETRY_MAX_ATTEMPTS) {
|
||||
onDirectRetryEvent("failure", packet, 0);
|
||||
clearDirectRetrySlot(i);
|
||||
continue;
|
||||
}
|
||||
|
||||
Packet* retry = obtainNewPacket();
|
||||
if (retry == NULL) {
|
||||
onDirectRetryEvent("dropped_no_packet", packet, 0);
|
||||
onDirectRetryEvent("failure", packet, 0);
|
||||
clearDirectRetrySlot(i);
|
||||
continue;
|
||||
}
|
||||
|
||||
*retry = *packet;
|
||||
uint32_t retry_delay = DIRECT_RETRY_BACKOFF_MS[_direct_retries[i].retry_attempts_sent];
|
||||
sendPacket(retry, _direct_retries[i].priority, retry_delay);
|
||||
if (isDirectRetryQueued(retry)) {
|
||||
_direct_retries[i].packet = retry;
|
||||
_direct_retries[i].retry_delay = retry_delay;
|
||||
_direct_retries[i].retry_at = futureMillis(retry_delay);
|
||||
onDirectRetryEvent("queued", retry, retry_delay);
|
||||
} else {
|
||||
onDirectRetryEvent("dropped_queue_full", retry, retry_delay);
|
||||
onDirectRetryEvent("failure", retry, 0);
|
||||
clearDirectRetrySlot(i);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -522,6 +562,8 @@ void Mesh::armDirectRetryOnSendComplete(const Packet* packet) {
|
||||
// Allocate the retry packet only after TX-complete so busy repeaters do not reserve pool slots early.
|
||||
Packet* retry = obtainNewPacket();
|
||||
if (retry == NULL) {
|
||||
onDirectRetryEvent("dropped_no_packet", packet, _direct_retries[i].retry_delay);
|
||||
onDirectRetryEvent("failure", packet, 0);
|
||||
clearDirectRetrySlot(i);
|
||||
continue;
|
||||
}
|
||||
@@ -535,7 +577,10 @@ void Mesh::armDirectRetryOnSendComplete(const Packet* packet) {
|
||||
_direct_retries[i].trigger_packet = NULL;
|
||||
_direct_retries[i].queued = true;
|
||||
_direct_retries[i].retry_at = futureMillis(_direct_retries[i].retry_delay);
|
||||
onDirectRetryEvent("queued", retry, _direct_retries[i].retry_delay);
|
||||
} else {
|
||||
onDirectRetryEvent("dropped_queue_full", retry, _direct_retries[i].retry_delay);
|
||||
onDirectRetryEvent("failure", retry, 0);
|
||||
clearDirectRetrySlot(i);
|
||||
}
|
||||
}
|
||||
@@ -550,12 +595,16 @@ void Mesh::clearPendingDirectRetryOnSendFail(const Packet* packet) {
|
||||
if (_direct_retries[i].queued) {
|
||||
if (_direct_retries[i].packet == packet) {
|
||||
// The queued retry itself failed; Dispatcher will release it after this hook.
|
||||
onDirectRetryEvent("dropped_send_fail", packet, 0);
|
||||
onDirectRetryEvent("failure", packet, 0);
|
||||
clearDirectRetrySlot(i);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_direct_retries[i].trigger_packet == packet) {
|
||||
onDirectRetryEvent("dropped_send_fail", packet, 0);
|
||||
onDirectRetryEvent("failure", packet, 0);
|
||||
clearDirectRetrySlot(i);
|
||||
}
|
||||
}
|
||||
@@ -638,17 +687,19 @@ void Mesh::maybeScheduleDirectRetry(const Packet* packet, uint8_t priority) {
|
||||
}
|
||||
|
||||
// Only store retry metadata here; allocate the retry packet after the initial TX really completes.
|
||||
uint32_t retry_delay = getDirectRetryEchoDelay(packet);
|
||||
uint32_t retry_delay = DIRECT_RETRY_BACKOFF_MS[0];
|
||||
calculateDirectRetryKey(packet, _direct_retries[slot_idx].retry_key);
|
||||
_direct_retries[slot_idx].packet = NULL;
|
||||
_direct_retries[slot_idx].trigger_packet = const_cast<Packet*>(packet);
|
||||
_direct_retries[slot_idx].retry_at = 0;
|
||||
_direct_retries[slot_idx].retry_delay = retry_delay;
|
||||
_direct_retries[slot_idx].retry_attempts_sent = 0;
|
||||
_direct_retries[slot_idx].priority = priority;
|
||||
_direct_retries[slot_idx].progress_marker = progress_marker;
|
||||
_direct_retries[slot_idx].expect_path_growth = expect_path_growth;
|
||||
_direct_retries[slot_idx].queued = false;
|
||||
_direct_retries[slot_idx].active = true;
|
||||
onDirectRetryEvent("armed", packet, retry_delay);
|
||||
}
|
||||
|
||||
Packet* Mesh::createAdvert(const LocalIdentity& id, const uint8_t* app_data, size_t app_data_len) {
|
||||
|
||||
@@ -34,6 +34,7 @@ class Mesh : public Dispatcher {
|
||||
Packet* trigger_packet;
|
||||
unsigned long retry_at;
|
||||
uint32_t retry_delay;
|
||||
uint8_t retry_attempts_sent;
|
||||
uint8_t retry_key[MAX_HASH_SIZE];
|
||||
uint8_t priority;
|
||||
uint8_t progress_marker;
|
||||
@@ -111,6 +112,11 @@ protected:
|
||||
*/
|
||||
virtual uint8_t getExtraAckTransmitCount() const;
|
||||
|
||||
/**
|
||||
* \brief Optional hook for logging direct-retry lifecycle events.
|
||||
*/
|
||||
virtual void onDirectRetryEvent(const char* event, const Packet* packet, uint32_t delay_millis) { }
|
||||
|
||||
/**
|
||||
* \brief Perform search of local DB of peers/contacts.
|
||||
* \returns Number of peers with matching hash
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
|
||||
class SimpleMeshTables : public mesh::MeshTables {
|
||||
public:
|
||||
typedef bool (*RecentRepeaterAllowFn)(const uint8_t* prefix, uint8_t prefix_len, void* ctx);
|
||||
|
||||
struct RecentRepeaterInfo {
|
||||
// Just enough identity to match a next-hop path prefix plus the SNR that heard it.
|
||||
uint8_t prefix[MAX_ROUTE_HASH_BYTES];
|
||||
@@ -28,6 +30,9 @@ private:
|
||||
uint32_t _direct_dups, _flood_dups;
|
||||
RecentRepeaterInfo _recent_repeaters[MAX_RECENT_REPEATERS];
|
||||
int _next_recent_repeater_idx;
|
||||
int8_t _recent_repeater_min_snr_x4;
|
||||
RecentRepeaterAllowFn _recent_repeater_allow_fn;
|
||||
void* _recent_repeater_allow_ctx;
|
||||
|
||||
bool hasSeenAck(uint32_t ack) const {
|
||||
for (int i = 0; i < MAX_PACKET_ACKS; i++) {
|
||||
@@ -58,6 +63,11 @@ private:
|
||||
_next_idx = (_next_idx + 1) % MAX_PACKET_HASHES;
|
||||
}
|
||||
|
||||
bool prefixesOverlap(const uint8_t* a, uint8_t a_len, const uint8_t* b, uint8_t b_len) const {
|
||||
uint8_t n = a_len < b_len ? a_len : b_len;
|
||||
return n > 0 && memcmp(a, b, n) == 0;
|
||||
}
|
||||
|
||||
bool extractRecentRepeater(const mesh::Packet* packet, uint8_t* prefix, uint8_t& prefix_len) const {
|
||||
// Learn repeater prefixes only from packet shapes that expose a trustworthy repeater ID.
|
||||
if (packet->getPayloadType() == PAYLOAD_TYPE_ADVERT && packet->payload_len >= PUB_KEY_SIZE) {
|
||||
@@ -96,14 +106,10 @@ private:
|
||||
if (!extractRecentRepeater(packet, prefix, prefix_len) || prefix_len == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ring buffer is enough here; retry fallback only needs a recent prefix->SNR observation.
|
||||
RecentRepeaterInfo& slot = _recent_repeaters[_next_recent_repeater_idx];
|
||||
memset(slot.prefix, 0, sizeof(slot.prefix));
|
||||
memcpy(slot.prefix, prefix, prefix_len);
|
||||
slot.prefix_len = prefix_len;
|
||||
slot.snr_x4 = packet->_snr;
|
||||
_next_recent_repeater_idx = (_next_recent_repeater_idx + 1) % MAX_RECENT_REPEATERS;
|
||||
if (packet->_snr < _recent_repeater_min_snr_x4) {
|
||||
return;
|
||||
}
|
||||
setRecentRepeater(prefix, prefix_len, packet->_snr);
|
||||
}
|
||||
|
||||
public:
|
||||
@@ -115,6 +121,9 @@ public:
|
||||
_direct_dups = _flood_dups = 0;
|
||||
memset(_recent_repeaters, 0, sizeof(_recent_repeaters));
|
||||
_next_recent_repeater_idx = 0;
|
||||
_recent_repeater_min_snr_x4 = -128;
|
||||
_recent_repeater_allow_fn = NULL;
|
||||
_recent_repeater_allow_ctx = NULL;
|
||||
}
|
||||
|
||||
#ifdef ESP32
|
||||
@@ -216,19 +225,74 @@ public:
|
||||
uint32_t getNumDirectDups() const { return _direct_dups; }
|
||||
uint32_t getNumFloodDups() const { return _flood_dups; }
|
||||
|
||||
void setRecentRepeaterMinSNRX4(int8_t min_snr_x4) {
|
||||
_recent_repeater_min_snr_x4 = min_snr_x4;
|
||||
}
|
||||
void setRecentRepeaterAllowFilter(RecentRepeaterAllowFn fn, void* ctx) {
|
||||
_recent_repeater_allow_fn = fn;
|
||||
_recent_repeater_allow_ctx = ctx;
|
||||
}
|
||||
bool setRecentRepeater(const uint8_t* prefix, uint8_t prefix_len, int8_t snr_x4) {
|
||||
if (prefix == NULL || prefix_len == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (prefix_len > MAX_ROUTE_HASH_BYTES) {
|
||||
prefix_len = MAX_ROUTE_HASH_BYTES;
|
||||
}
|
||||
|
||||
if (_recent_repeater_allow_fn != NULL && !_recent_repeater_allow_fn(prefix, prefix_len, _recent_repeater_allow_ctx)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Keep one slot for overlapping prefixes so 1/2/3-byte paths share the same entry.
|
||||
for (int i = 0; i < MAX_RECENT_REPEATERS; i++) {
|
||||
RecentRepeaterInfo& existing = _recent_repeaters[i];
|
||||
if (existing.prefix_len == 0 || !prefixesOverlap(existing.prefix, existing.prefix_len, prefix, prefix_len)) {
|
||||
continue;
|
||||
}
|
||||
if (prefix_len > existing.prefix_len) {
|
||||
memset(existing.prefix, 0, sizeof(existing.prefix));
|
||||
memcpy(existing.prefix, prefix, prefix_len);
|
||||
existing.prefix_len = prefix_len;
|
||||
}
|
||||
existing.snr_x4 = snr_x4;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Ring buffer is enough here; retry fallback only needs a recent prefix->SNR observation.
|
||||
RecentRepeaterInfo& slot = _recent_repeaters[_next_recent_repeater_idx];
|
||||
memset(slot.prefix, 0, sizeof(slot.prefix));
|
||||
memcpy(slot.prefix, prefix, prefix_len);
|
||||
slot.prefix_len = prefix_len;
|
||||
slot.snr_x4 = snr_x4;
|
||||
_next_recent_repeater_idx = (_next_recent_repeater_idx + 1) % MAX_RECENT_REPEATERS;
|
||||
return true;
|
||||
}
|
||||
const RecentRepeaterInfo* getLatestRecentRepeater() const {
|
||||
for (int i = 0; i < MAX_RECENT_REPEATERS; i++) {
|
||||
int idx = (_next_recent_repeater_idx - 1 - i + MAX_RECENT_REPEATERS) % MAX_RECENT_REPEATERS;
|
||||
const RecentRepeaterInfo* info = &_recent_repeaters[idx];
|
||||
if (info->prefix_len > 0) {
|
||||
return info;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const RecentRepeaterInfo* findRecentRepeaterByHash(const uint8_t* hash, uint8_t hash_len) const {
|
||||
if (hash == NULL || hash_len == 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Search newest-to-oldest so the retry gate prefers the freshest SNR sample for a prefix.
|
||||
// Search newest-to-oldest and allow 1/2/3-byte prefixes to overlap-match.
|
||||
for (int i = 0; i < MAX_RECENT_REPEATERS; i++) {
|
||||
int idx = (_next_recent_repeater_idx - 1 - i + MAX_RECENT_REPEATERS) % MAX_RECENT_REPEATERS;
|
||||
const RecentRepeaterInfo* info = &_recent_repeaters[idx];
|
||||
if (info->prefix_len < hash_len || info->prefix_len == 0) {
|
||||
if (info->prefix_len == 0) {
|
||||
continue;
|
||||
}
|
||||
if (memcmp(info->prefix, hash, hash_len) == 0) {
|
||||
if (prefixesOverlap(info->prefix, info->prefix_len, hash, hash_len)) {
|
||||
return info;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user