Improve repeater direct retry handling
This commit is contained in:
1 parent
ea3f7acb4d
commit
674beb0e47
8 files changed
+491
-262
No files matched your search
+50
-11
@@ -128,8 +128,10 @@ This document provides an overview of CLI commands that can be sent to MeshCore
|
||||
- `page`: 1-based page number
|
||||
|
||||
**Notes:**
|
||||
- `set` is rejected when the prefix already exists in neighbors.
|
||||
- Rows are shown newest-first.
|
||||
- `set` stores or updates the prefix in the recent repeater table.
|
||||
- Rows are sorted by prefix width (3-byte, 2-byte, 1-byte), then SNR descending.
|
||||
- A full direct retry failure lowers the stored SNR by `0.25 dB`.
|
||||
- If a full failure has no row yet, it first seeds the row at the active retry cutoff + `2.5 dB`, then applies the `0.25 dB` penalty.
|
||||
- Serial CLI prints all rows (no paging).
|
||||
- Over LoRa remote CLI, page size is fixed at `4` rows; choose page with `get recent.repeater <page>`.
|
||||
|
||||
@@ -534,11 +536,13 @@ This document provides an overview of CLI commands that can be sent to MeshCore
|
||||
**Parameters:**
|
||||
- `value`: Direct transmit delay factor (0-2)
|
||||
|
||||
**Default:** `0.2`
|
||||
**Default:** `0.3`
|
||||
|
||||
**Note:** Direct retry waits include the same airtime-based randomized delay calculation as direct retransmits, so this factor also controls retry echo windows.
|
||||
|
||||
---
|
||||
|
||||
#### View or change whether direct retries can fall back to the recently-heard repeater list
|
||||
#### View or change whether direct retries use the recent repeater blacklist
|
||||
**Usage:**
|
||||
- `get direct.retry.heard`
|
||||
- `set direct.retry.heard <state>`
|
||||
@@ -548,7 +552,7 @@ This document provides an overview of CLI commands that can be sent to MeshCore
|
||||
|
||||
**Default:** `on`
|
||||
|
||||
**Note:** When enabled, a repeater can use recently-heard non-duplicate repeater prefixes as a fallback for direct retry eligibility when no suitable neighbor entry is available.
|
||||
**Note:** When enabled, the recent repeater table is the only direct retry eligibility gate. Prefixes missing from the table are assumed reachable; prefixes in the table below the active SNR gate are blocked. Neighbor data is not used.
|
||||
|
||||
---
|
||||
|
||||
@@ -558,11 +562,30 @@ This document provides an overview of CLI commands that can be sent to MeshCore
|
||||
- `set direct.retry.margin <value>`
|
||||
|
||||
**Parameters:**
|
||||
- `value`: Margin in dB above the SF-specific receive floor (minimum `0`, maximum `40`, quarter-dB precision, default `2.5`)
|
||||
- `value`: Rooftop preset margin in dB above the SF-specific receive floor (minimum `0`, maximum `40`, quarter-dB precision, default `5.0`)
|
||||
|
||||
**Default:** `2.5`
|
||||
**Default:** `5.0`
|
||||
|
||||
**Note:** The retry gate uses the active SF floor of `SF5=-2.5`, `SF6=-5`, `SF7=-7.5`, `SF8=-10`, `SF9=-12.5`, `SF10=-15`, `SF11=-17.5`, `SF12=-20`, then adds this margin.
|
||||
**Note:** `get direct.retry.margin` returns the active preset's effective margin. The retry gate uses the active SF floor of `SF5=-2.5`, `SF6=-5`, `SF7=-7.5`, `SF8=-10`, `SF9=-12.5`, `SF10=-15`, `SF11=-17.5`, `SF12=-20`, then adds this margin.
|
||||
|
||||
---
|
||||
|
||||
#### View or change the direct retry timing preset
|
||||
**Usage:**
|
||||
- `get direct.retry.preset`
|
||||
- `set direct.retry.preset <value>`
|
||||
|
||||
**Parameters:**
|
||||
- `value`: `infra`|`rooftop`|`mobile` or `0`|`1`|`2`
|
||||
|
||||
**Default:** `rooftop` (`1`)
|
||||
|
||||
**Presets:**
|
||||
- `infra` (`0`): `275 ms` base wait, `4` retries, `150 ms` added per retry, SNR gate is SF floor + `15 dB`
|
||||
- `rooftop` (`1`): `175 ms` base wait, `15` retries, `100 ms` added per retry, SNR gate is SF floor + `5 dB`
|
||||
- `mobile` (`2`): `175 ms` base wait, `15` retries, `50 ms` added per retry, SNR gate is the SF floor
|
||||
|
||||
**Note:** Selecting a preset copies those values into the retry settings. You can refine `direct.retry.margin`, `direct.retry.count`, `direct.retry.base`, or `direct.retry.step` afterward. Retry delay is `direct.txdelay` jitter + base wait + packet-length airtime wait + per-attempt step.
|
||||
|
||||
---
|
||||
|
||||
@@ -572,10 +595,12 @@ This document provides an overview of CLI commands that can be sent to MeshCore
|
||||
- `set direct.retry.count <value>`
|
||||
|
||||
**Parameters:**
|
||||
- `value`: Retry attempts after initial TX (`1`-`15`)
|
||||
- `value`: Maximum retry attempts after initial TX (`1`-`15`)
|
||||
|
||||
**Default:** `15`
|
||||
|
||||
**Note:** The effective value is capped by total direct path length: paths of `3` hops or less use at most `8` retries, `4` hops use at most `12`, and `5+` hops use at most `15`. A queued resend is canceled early when the next-hop echo is heard.
|
||||
|
||||
---
|
||||
|
||||
#### View or change the base direct retry wait (milliseconds)
|
||||
@@ -586,9 +611,23 @@ This document provides an overview of CLI commands that can be sent to MeshCore
|
||||
**Parameters:**
|
||||
- `value`: Base wait in milliseconds (`10`-`5000`)
|
||||
|
||||
**Default:** `200`
|
||||
**Default:** `175`
|
||||
|
||||
**Note:** The actual first retry wait is `base + computed_echo_wait_from_live_phy`.
|
||||
**Note:** The configured base is added to packet-length airtime and `direct.txdelay` jitter. Preset defaults are already reduced to account for the added `direct.txdelay` component.
|
||||
|
||||
---
|
||||
|
||||
#### View or change the direct retry per-attempt add time (milliseconds)
|
||||
**Usage:**
|
||||
- `get direct.retry.step`
|
||||
- `set direct.retry.step <value>`
|
||||
|
||||
**Parameters:**
|
||||
- `value`: Milliseconds added per retry attempt (`0`-`5000`)
|
||||
|
||||
**Default:** `100`
|
||||
|
||||
**Note:** This controls the linear add after the first retry wait. For example, `base=300` and `step=150` adds `0`, `150`, `300`, ... ms across retry attempts.
|
||||
|
||||
---
|
||||
|
||||
|
||||
+158
-101
@@ -63,29 +63,6 @@
|
||||
|
||||
#define LAZY_CONTACTS_WRITE_DELAY 5000
|
||||
|
||||
const NeighbourInfo* MyMesh::findNeighbourByHash(const uint8_t* hash, uint8_t hash_len) const {
|
||||
#if MAX_NEIGHBOURS
|
||||
for (int i = 0; i < MAX_NEIGHBOURS; i++) {
|
||||
if (neighbours[i].heard_timestamp > 0 && neighbours[i].id.isHashMatch(hash, hash_len)) {
|
||||
return &neighbours[i];
|
||||
}
|
||||
}
|
||||
#else
|
||||
(void)hash;
|
||||
(void)hash_len;
|
||||
#endif
|
||||
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;
|
||||
}
|
||||
|
||||
static void formatRecentRepeaterPrefix(const SimpleMeshTables::RecentRepeaterInfo* info, char* out, size_t out_len) {
|
||||
if (out == NULL || out_len == 0) {
|
||||
return;
|
||||
@@ -124,6 +101,45 @@ static void formatRecentRepeaterSnrX4(int8_t snr_x4, char* out, size_t out_len)
|
||||
}
|
||||
}
|
||||
|
||||
static int buildSortedRecentRepeaterView(SimpleMeshTables* tables,
|
||||
const SimpleMeshTables::RecentRepeaterInfo** out,
|
||||
int out_cap) {
|
||||
if (tables == NULL || out == NULL || out_cap <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int total = tables->getRecentRepeaterCount();
|
||||
if (total > out_cap) {
|
||||
total = out_cap;
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
for (int i = 0; i < total; i++) {
|
||||
const auto* info = tables->getRecentRepeaterNewestByIdx(i);
|
||||
if (info != NULL) {
|
||||
out[count++] = info;
|
||||
}
|
||||
}
|
||||
|
||||
std::stable_sort(out, out + count, [](const SimpleMeshTables::RecentRepeaterInfo* a,
|
||||
const SimpleMeshTables::RecentRepeaterInfo* b) {
|
||||
uint8_t a_len = a->prefix_len;
|
||||
uint8_t b_len = b->prefix_len;
|
||||
if (a_len > MAX_ROUTE_HASH_BYTES) a_len = MAX_ROUTE_HASH_BYTES;
|
||||
if (b_len > MAX_ROUTE_HASH_BYTES) b_len = MAX_ROUTE_HASH_BYTES;
|
||||
|
||||
if (a_len != b_len) {
|
||||
return a_len > b_len; // 3-byte first, then 2-byte, then 1-byte
|
||||
}
|
||||
if (a->snr_x4 != b->snr_x4) {
|
||||
return a->snr_x4 > b->snr_x4; // highest SNR first within each prefix size
|
||||
}
|
||||
return false; // keep original newest-first order for ties
|
||||
});
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static uint8_t decodeTraceHashSize(uint8_t flags, uint8_t route_bytes) {
|
||||
uint8_t code = flags & 0x03;
|
||||
uint8_t size_pow2 = (uint8_t)(1U << code); // legacy TRACE interpretation
|
||||
@@ -641,7 +657,7 @@ void MyMesh::logTxFail(mesh::Packet *pkt, int len) {
|
||||
}
|
||||
}
|
||||
|
||||
void MyMesh::onDirectRetryEvent(const char* event, const mesh::Packet* packet, uint32_t delay_millis) {
|
||||
void MyMesh::onDirectRetryEvent(const char* event, const mesh::Packet* packet, uint32_t delay_millis, uint8_t retry_attempt) {
|
||||
if (packet == NULL) {
|
||||
return;
|
||||
}
|
||||
@@ -656,92 +672,103 @@ void MyMesh::onDirectRetryEvent(const char* event, const mesh::Packet* packet, u
|
||||
mesh::Utils::toHex(next_hop_hex, prefix, prefix_len);
|
||||
}
|
||||
const char* next_hop = (has_prefix && prefix_len > 0) ? next_hop_hex : "unknown";
|
||||
char hop_text[24];
|
||||
if (packet->isRouteDirect() && packet->getPayloadType() == PAYLOAD_TYPE_TRACE && packet->payload_len >= 9) {
|
||||
uint8_t route_bytes = packet->payload_len - 9;
|
||||
uint8_t hash_size = decodeTraceHashSize(packet->payload[8], route_bytes);
|
||||
uint8_t total_hops = (hash_size > 0) ? (route_bytes / hash_size) : 0;
|
||||
if (total_hops > 0) {
|
||||
snprintf(hop_text, sizeof(hop_text), "%u/%u", (unsigned int)packet->path_len, (unsigned int)total_hops);
|
||||
} else {
|
||||
snprintf(hop_text, sizeof(hop_text), "unknown");
|
||||
}
|
||||
} else if (packet->isRouteDirect()) {
|
||||
snprintf(hop_text, sizeof(hop_text), "remaining:%u", (unsigned int)packet->getPathHashCount());
|
||||
} else {
|
||||
snprintf(hop_text, sizeof(hop_text), "unknown");
|
||||
}
|
||||
|
||||
// Direct-retry events are TX-side and usually have no trustworthy RX SNR.
|
||||
// Cap event SNR at fixed SF floor + 10 dB so trace-start retries can't inflate table SNR.
|
||||
uint8_t sf = constrain(active_sf, (uint8_t)5, (uint8_t)12);
|
||||
int16_t fallback_snr_x4_raw = direct_retry_floor_x4[sf - 5] + 40;
|
||||
int8_t fallback_snr_x4 = (int8_t)constrain(fallback_snr_x4_raw, -128, 127);
|
||||
bool is_success_event = (strcmp(event, "good") == 0 || strcmp(event, "canceled_echo") == 0);
|
||||
bool is_success_event = strcmp(event, "good") == 0;
|
||||
bool updates_quality = strcmp(event, "good") == 0;
|
||||
int8_t retry_event_snr_x4;
|
||||
const char* snr_src;
|
||||
if (is_success_event && packet->_snr != 0) {
|
||||
// On success, Mesh.cpp injects echo RX SNR for TRACE retries.
|
||||
if (is_success_event) {
|
||||
// On success, Mesh.cpp injects echo RX SNR for the downstream retry target.
|
||||
retry_event_snr_x4 = packet->_snr;
|
||||
snr_src = "packet";
|
||||
} else if (existing != NULL) {
|
||||
retry_event_snr_x4 = existing->snr_x4;
|
||||
snr_src = "table";
|
||||
} else {
|
||||
retry_event_snr_x4 = fallback_snr_x4;
|
||||
snr_src = "fallback";
|
||||
}
|
||||
char snr_used_text[12];
|
||||
char snr_pkt_text[12];
|
||||
char snr_table_text[12];
|
||||
snprintf(snr_used_text, sizeof(snr_used_text), "%s", StrHelper::ftoa(((float)retry_event_snr_x4) / 4.0f));
|
||||
|
||||
if (has_prefix && updates_quality) {
|
||||
// Refresh SNR only once per successful echo/progress event, not on queued/resent bookkeeping.
|
||||
tables->setRecentRepeater(prefix, prefix_len, retry_event_snr_x4, false, true);
|
||||
}
|
||||
|
||||
if (strcmp(event, "failed_all_tries") == 0) {
|
||||
if (has_prefix) {
|
||||
if (existing == NULL) {
|
||||
int16_t seed_snr_x4 = (int16_t)getDirectRetryMinSNRX4() + 10; // +2.5 dB over the active retry cutoff.
|
||||
tables->setRecentRepeater(prefix, prefix_len, (int8_t)constrain(seed_snr_x4, -128, 127), false, true);
|
||||
}
|
||||
// SNR is stored in quarter-dB units, so 1 lowers quality by 0.25 dB.
|
||||
tables->decrementRecentRepeaterSnrX4(prefix, prefix_len, 1);
|
||||
}
|
||||
}
|
||||
|
||||
snprintf(snr_pkt_text, sizeof(snr_pkt_text), "%s", StrHelper::ftoa(((float)packet->_snr) / 4.0f));
|
||||
if (existing != NULL) {
|
||||
snprintf(snr_table_text, sizeof(snr_table_text), "%s", StrHelper::ftoa(((float)existing->snr_x4) / 4.0f));
|
||||
const auto* log_existing = has_prefix ? tables->findRecentRepeaterByHash(prefix, prefix_len) : NULL;
|
||||
if (log_existing != NULL) {
|
||||
snprintf(snr_table_text, sizeof(snr_table_text), "%s", StrHelper::ftoa(((float)log_existing->snr_x4) / 4.0f));
|
||||
} else {
|
||||
snprintf(snr_table_text, sizeof(snr_table_text), "na");
|
||||
}
|
||||
|
||||
if (has_prefix && is_success_event) {
|
||||
// Refresh SNR only on successful echo/progress events, not on queued/resent bookkeeping.
|
||||
tables->setRecentRepeater(prefix, prefix_len, retry_event_snr_x4, false, true);
|
||||
const char* time_label = "time_ms";
|
||||
if (strcmp(event, "queued") == 0 || strcmp(event, "dropped_queue_full") == 0) {
|
||||
time_label = "wait_ms";
|
||||
} else if (strcmp(event, "resent") == 0 || strcmp(event, "failed_all_tries") == 0 || strcmp(event, "failure") == 0) {
|
||||
time_label = "elapsed_ms";
|
||||
} else if (strcmp(event, "good") == 0) {
|
||||
time_label = "echo_ms";
|
||||
}
|
||||
|
||||
if (strcmp(event, "resent") == 0) {
|
||||
if (has_prefix) {
|
||||
// Retry stats should be visible even when the prefix was never learned into recent.repeater.
|
||||
tables->incrementRecentRepeaterRetryCount(prefix, prefix_len, true, retry_event_snr_x4, true);
|
||||
}
|
||||
} else if (strcmp(event, "failed_all_tries") == 0) {
|
||||
if (has_prefix) {
|
||||
// A failed_all_tries event means all retry attempts for this packet failed.
|
||||
// Count failures by retry-attempts so fail% reflects failed retries, not just failed sessions.
|
||||
uint8_t give_up_retries = getDirectRetryMaxAttempts(packet);
|
||||
uint8_t failed_retries = give_up_retries;
|
||||
if (failed_retries < 1) {
|
||||
failed_retries = 1;
|
||||
}
|
||||
for (uint8_t i = 0; i < failed_retries; i++) {
|
||||
tables->incrementRecentRepeaterFailCount(prefix, prefix_len, true, retry_event_snr_x4, true);
|
||||
}
|
||||
if (failed_retries >= give_up_retries && give_up_retries > 0) {
|
||||
// If all configured retry attempts still fail, slightly degrade stored path quality.
|
||||
tables->decrementRecentRepeaterSnrX4(prefix, prefix_len, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MESH_DEBUG_PRINTLN("%s direct retry %s (type=%d, route=%s, payload_len=%d, next_hop=%s, snr=%s, snr_src=%s, pkt_snr=%s, table_snr=%s, delay=%lu)",
|
||||
MESH_DEBUG_PRINTLN("%s direct retry %s (retry=%u, type=%d, route=%s, payload_len=%d, hop=%s, next_hop=%s, pkt=%s, tbl=%s, %s=%lu)",
|
||||
getLogDateTime(),
|
||||
event,
|
||||
(unsigned int)retry_attempt,
|
||||
(uint32_t)packet->getPayloadType(),
|
||||
packet->isRouteDirect() ? "D" : "F",
|
||||
(uint32_t)packet->payload_len,
|
||||
hop_text,
|
||||
next_hop,
|
||||
snr_used_text,
|
||||
snr_src,
|
||||
snr_pkt_text,
|
||||
snr_table_text,
|
||||
time_label,
|
||||
(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, next_hop=%s, snr=%s, snr_src=%s, pkt_snr=%s, table_snr=%s, delay=%lu)\n",
|
||||
f.printf(": DIRECT RETRY %s (retry=%u, type=%d, route=%s, payload_len=%d, hop=%s, next_hop=%s, pkt=%s, tbl=%s, %s=%lu)\n",
|
||||
event,
|
||||
(unsigned int)retry_attempt,
|
||||
(uint32_t)packet->getPayloadType(),
|
||||
packet->isRouteDirect() ? "D" : "F",
|
||||
(uint32_t)packet->payload_len,
|
||||
hop_text,
|
||||
next_hop,
|
||||
snr_used_text,
|
||||
snr_src,
|
||||
snr_pkt_text,
|
||||
snr_table_text,
|
||||
time_label,
|
||||
(unsigned long)delay_millis);
|
||||
f.close();
|
||||
}
|
||||
@@ -764,7 +791,8 @@ uint32_t MyMesh::getDirectRetransmitDelay(const mesh::Packet *packet) {
|
||||
int8_t MyMesh::getDirectRetryMinSNRX4() const {
|
||||
// Use the live SF so `tempradio` changes immediately affect the retry threshold.
|
||||
uint8_t sf = constrain(active_sf, (uint8_t)5, (uint8_t)12);
|
||||
int16_t threshold = direct_retry_floor_x4[sf - 5] + (int16_t)_prefs.direct_retry_snr_margin_db;
|
||||
int16_t margin_x4 = (int16_t)_prefs.direct_retry_snr_margin_db;
|
||||
int16_t threshold = direct_retry_floor_x4[sf - 5] + margin_x4;
|
||||
return (int8_t)constrain(threshold, -128, 127);
|
||||
}
|
||||
bool MyMesh::extractDirectRetryPrefix(const mesh::Packet* packet, uint8_t* prefix, uint8_t& prefix_len) const {
|
||||
@@ -808,18 +836,30 @@ bool MyMesh::allowDirectRetry(const mesh::Packet* packet, const uint8_t* next_ho
|
||||
|
||||
int8_t min_snr_x4 = getDirectRetryMinSNRX4();
|
||||
if (_prefs.direct_retry_recent_enabled) {
|
||||
// Prefer the 64-entry recent-prefix cache first, then fall back to neighbours.
|
||||
const auto* recent = ((const SimpleMeshTables *)getTables())->findRecentRepeaterByHash(next_hop_hash, next_hop_hash_len);
|
||||
if (recent != NULL && recent->snr_x4 >= min_snr_x4) {
|
||||
return true;
|
||||
}
|
||||
return recent == NULL || recent->snr_x4 >= min_snr_x4;
|
||||
}
|
||||
|
||||
const NeighbourInfo* neighbour = findNeighbourByHash(next_hop_hash, next_hop_hash_len);
|
||||
return neighbour != NULL && neighbour->snr >= min_snr_x4;
|
||||
return true;
|
||||
}
|
||||
uint8_t MyMesh::getDirectRetryPreset() const {
|
||||
if (_prefs.direct_retry_preset <= DIRECT_RETRY_PRESET_MOBILE) {
|
||||
return _prefs.direct_retry_preset;
|
||||
}
|
||||
return DIRECT_RETRY_PRESET_ROOFTOP;
|
||||
}
|
||||
uint8_t MyMesh::getDirectRetryConfiguredMaxAttempts() const {
|
||||
return constrain(_prefs.direct_retry_attempts, (uint8_t)1, (uint8_t)15);
|
||||
}
|
||||
uint32_t MyMesh::getDirectRetryAttemptStepMillis() const {
|
||||
return constrain((uint32_t)_prefs.direct_retry_step_ms, (uint32_t)0, (uint32_t)5000);
|
||||
}
|
||||
uint32_t MyMesh::getDirectRetryEchoDelay(const mesh::Packet* packet) const {
|
||||
uint32_t base_wait_millis = constrain((uint32_t)_prefs.direct_retry_base_ms, (uint32_t)10, (uint32_t)5000);
|
||||
if (packet == NULL) {
|
||||
return base_wait_millis;
|
||||
}
|
||||
|
||||
// Approximate LoRa line rate in kilobits/sec from the live radio params the repeater is using now.
|
||||
float kbps = (((float) active_sf) * active_bw * ((float) active_cr)) / ((float) (1UL << active_sf));
|
||||
if (kbps <= 0.0f) {
|
||||
@@ -832,8 +872,36 @@ uint32_t MyMesh::getDirectRetryEchoDelay(const mesh::Packet* packet) const {
|
||||
return base_wait_millis + scaled_wait_millis;
|
||||
}
|
||||
uint8_t MyMesh::getDirectRetryMaxAttempts(const mesh::Packet* packet) const {
|
||||
(void)packet;
|
||||
return constrain(_prefs.direct_retry_attempts, (uint8_t)1, (uint8_t)15);
|
||||
uint8_t configured_attempts = getDirectRetryConfiguredMaxAttempts();
|
||||
uint8_t total_hops = 0;
|
||||
|
||||
if (packet != NULL) {
|
||||
if (packet->isRouteDirect() && packet->getPayloadType() == PAYLOAD_TYPE_TRACE && packet->payload_len >= 9) {
|
||||
uint8_t route_bytes = packet->payload_len - 9;
|
||||
uint8_t hash_size = decodeTraceHashSize(packet->payload[8], route_bytes);
|
||||
if (hash_size > 0) {
|
||||
total_hops = (uint8_t)(route_bytes / hash_size);
|
||||
}
|
||||
} else {
|
||||
total_hops = packet->getPathHashCount();
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t path_cap = 15;
|
||||
if (total_hops <= 3) {
|
||||
path_cap = 8;
|
||||
} else if (total_hops == 4) {
|
||||
path_cap = 12;
|
||||
}
|
||||
|
||||
return configured_attempts < path_cap ? configured_attempts : path_cap;
|
||||
}
|
||||
uint32_t MyMesh::getDirectRetryAttemptDelay(const mesh::Packet* packet, uint8_t attempt_idx) {
|
||||
uint32_t retry_delay = getDirectRetryEchoDelay(packet) + ((uint32_t)attempt_idx * getDirectRetryAttemptStepMillis());
|
||||
if (packet == NULL) {
|
||||
return retry_delay;
|
||||
}
|
||||
return getDirectRetransmitDelay(packet) + retry_delay;
|
||||
}
|
||||
|
||||
bool MyMesh::filterRecvFloodPacket(mesh::Packet* pkt) {
|
||||
@@ -1166,9 +1234,11 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
|
||||
_prefs.tx_delay_factor = 0.5f; // was 0.25f
|
||||
_prefs.direct_tx_delay_factor = 0.3f; // was 0.2
|
||||
_prefs.direct_retry_recent_enabled = 1;
|
||||
_prefs.direct_retry_snr_margin_db = 10; // 2.5 dB stored in x4 units
|
||||
_prefs.direct_retry_attempts = 15;
|
||||
_prefs.direct_retry_base_ms = 200;
|
||||
_prefs.direct_retry_snr_margin_db = DIRECT_RETRY_ROOFTOP_MARGIN_X4;
|
||||
_prefs.direct_retry_attempts = DIRECT_RETRY_ROOFTOP_COUNT;
|
||||
_prefs.direct_retry_base_ms = DIRECT_RETRY_ROOFTOP_BASE_MS;
|
||||
_prefs.direct_retry_step_ms = DIRECT_RETRY_ROOFTOP_STEP_MS;
|
||||
_prefs.direct_retry_preset = DIRECT_RETRY_PRESET_ROOFTOP;
|
||||
StrHelper::strncpy(_prefs.node_name, ADVERT_NAME, sizeof(_prefs.node_name));
|
||||
_prefs.node_lat = ADVERT_LAT;
|
||||
_prefs.node_lon = ADVERT_LON;
|
||||
@@ -1213,8 +1283,6 @@ 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) {
|
||||
@@ -1612,21 +1680,22 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply
|
||||
if (tables->setRecentRepeater(prefix, MAX_ROUTE_HASH_BYTES, (int8_t)snr_x4, true)) {
|
||||
strcpy(reply, "OK");
|
||||
} else {
|
||||
strcpy(reply, "Err - prefix is already in neighbors");
|
||||
strcpy(reply, "Err - unable to store prefix");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
int total = tables->getRecentRepeaterCount();
|
||||
const SimpleMeshTables::RecentRepeaterInfo* sorted_recent[MAX_RECENT_REPEATERS];
|
||||
int total = buildSortedRecentRepeaterView(tables, sorted_recent, MAX_RECENT_REPEATERS);
|
||||
if (total <= 0) {
|
||||
strcpy(reply, "> none");
|
||||
} else {
|
||||
if (sender_timestamp == 0) {
|
||||
// Serial CLI: print all entries (no paging).
|
||||
Serial.printf("Recent repeater table (newest first, total=%d):\n", total);
|
||||
Serial.printf("Recent repeater table (3-byte,2-byte,1-byte; SNR desc, total=%d):\n", total);
|
||||
for (int i = 0; i < total; i++) {
|
||||
const auto* info = tables->getRecentRepeaterNewestByIdx(i);
|
||||
const auto* info = sorted_recent[i];
|
||||
if (info == NULL) {
|
||||
continue;
|
||||
}
|
||||
@@ -1635,18 +1704,9 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply
|
||||
formatRecentRepeaterPrefix(info, hex, sizeof(hex));
|
||||
char snr_text[12];
|
||||
formatRecentRepeaterSnrX4(info->snr_x4, snr_text, sizeof(snr_text));
|
||||
uint32_t fail_pct_x10 = 0;
|
||||
if (info->retry_count > 0) {
|
||||
fail_pct_x10 = (((uint32_t)info->fail_count * 1000UL) + (info->retry_count / 2U)) / (uint32_t)info->retry_count;
|
||||
}
|
||||
Serial.printf("%03d: %s,%s,fp=%lu.%01lu%%,r=%u,f=%u%s\n",
|
||||
i + 1,
|
||||
Serial.printf("%s,%s%s\n",
|
||||
hex,
|
||||
snr_text,
|
||||
(unsigned long)(fail_pct_x10 / 10U),
|
||||
(unsigned long)(fail_pct_x10 % 10U),
|
||||
(uint32_t)info->retry_count,
|
||||
(uint32_t)info->fail_count,
|
||||
info->snr_locked ? ",l" : "");
|
||||
}
|
||||
sprintf(reply, "> n=%d/%d", total, total);
|
||||
@@ -1692,7 +1752,7 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply
|
||||
|
||||
for (int i = 0; i < limit; i++) {
|
||||
int idx = offset + i;
|
||||
const auto* info = tables->getRecentRepeaterNewestByIdx(idx);
|
||||
const auto* info = sorted_recent[idx];
|
||||
if (info == NULL) {
|
||||
continue;
|
||||
}
|
||||
@@ -1707,12 +1767,9 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply
|
||||
formatRecentRepeaterSnrX4(info->snr_x4, snr_text, sizeof(snr_text));
|
||||
int n = snprintf(reply + written,
|
||||
160 - written,
|
||||
"\n%03d:%s,%s,r=%u,f=%u%s",
|
||||
idx + 1,
|
||||
"\n%s,%s%s",
|
||||
hex,
|
||||
snr_text,
|
||||
(uint32_t)info->retry_count,
|
||||
(uint32_t)info->fail_count,
|
||||
info->snr_locked ? ",l" : "");
|
||||
if (n < 0 || n >= (160 - written)) {
|
||||
truncated = true;
|
||||
|
||||
@@ -122,10 +122,11 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
|
||||
ESPNowBridge bridge;
|
||||
#endif
|
||||
|
||||
const NeighbourInfo* findNeighbourByHash(const uint8_t* hash, uint8_t hash_len) const;
|
||||
bool extractDirectRetryPrefix(const mesh::Packet* packet, uint8_t* prefix, uint8_t& prefix_len) const;
|
||||
static bool allowRecentRepeaterPrefixStore(const uint8_t* prefix, uint8_t prefix_len, void* ctx);
|
||||
int8_t getDirectRetryMinSNRX4() const;
|
||||
uint8_t getDirectRetryPreset() const;
|
||||
uint8_t getDirectRetryConfiguredMaxAttempts() const;
|
||||
uint32_t getDirectRetryAttemptStepMillis() 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);
|
||||
uint8_t handleAnonRegionsReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data);
|
||||
@@ -156,7 +157,8 @@ protected:
|
||||
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;
|
||||
uint8_t getDirectRetryMaxAttempts(const mesh::Packet* packet) const override;
|
||||
void onDirectRetryEvent(const char* event, const mesh::Packet* packet, uint32_t delay_millis) override;
|
||||
uint32_t getDirectRetryAttemptDelay(const mesh::Packet* packet, uint8_t attempt_idx) override;
|
||||
void onDirectRetryEvent(const char* event, const mesh::Packet* packet, uint32_t delay_millis, uint8_t retry_attempt) override;
|
||||
|
||||
int getInterferenceThreshold() const override {
|
||||
return _prefs.interference_threshold;
|
||||
|
||||
+74
-39
@@ -6,10 +6,32 @@ namespace mesh {
|
||||
static const uint8_t DIRECT_RETRY_MAX_ATTEMPTS_DEFAULT = 15;
|
||||
static const uint8_t DIRECT_RETRY_MAX_ATTEMPTS_HARD_MAX = 15;
|
||||
|
||||
static uint8_t decodeTraceHashSize(uint8_t flags, uint8_t route_bytes) {
|
||||
uint8_t code = flags & 0x03;
|
||||
uint8_t size_pow2 = (uint8_t)(1U << code); // legacy TRACE interpretation
|
||||
uint8_t size_linear = (uint8_t)(code + 1U); // packed-size interpretation (1..4)
|
||||
|
||||
bool pow2_ok = size_pow2 > 0 && (route_bytes % size_pow2) == 0;
|
||||
bool linear_ok = size_linear > 0 && (route_bytes % size_linear) == 0;
|
||||
|
||||
if (pow2_ok && !linear_ok) {
|
||||
return size_pow2;
|
||||
}
|
||||
if (linear_ok && !pow2_ok) {
|
||||
return size_linear;
|
||||
}
|
||||
if (pow2_ok) {
|
||||
return size_pow2;
|
||||
}
|
||||
return size_linear;
|
||||
}
|
||||
|
||||
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_started_at = 0;
|
||||
_direct_retries[i].echo_wait_started_at = 0;
|
||||
_direct_retries[i].retry_at = 0;
|
||||
_direct_retries[i].retry_delay = 0;
|
||||
_direct_retries[i].retry_attempts_sent = 0;
|
||||
@@ -60,7 +82,7 @@ uint32_t Mesh::getDirectRetryEchoDelay(const Packet* packet) const {
|
||||
uint8_t Mesh::getDirectRetryMaxAttempts(const Packet* packet) const {
|
||||
return DIRECT_RETRY_MAX_ATTEMPTS_DEFAULT;
|
||||
}
|
||||
uint32_t Mesh::getDirectRetryAttemptDelay(const Packet* packet, uint8_t attempt_idx) const {
|
||||
uint32_t Mesh::getDirectRetryAttemptDelay(const Packet* packet, uint8_t attempt_idx) {
|
||||
uint32_t base = getDirectRetryEchoDelay(packet);
|
||||
// Keep the historical linear spacing while allowing the base wait to vary by platform/profile.
|
||||
return base + ((uint32_t)attempt_idx * 100UL);
|
||||
@@ -102,13 +124,14 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
|
||||
uint32_t auth_code;
|
||||
memcpy(&auth_code, &pkt->payload[i], 4); i += 4;
|
||||
uint8_t flags = pkt->payload[i++];
|
||||
uint8_t path_sz = flags & 0x03; // NEW v1.11+: lower 2 bits is path hash size
|
||||
|
||||
uint8_t len = pkt->payload_len - i;
|
||||
uint8_t offset = pkt->path_len << path_sz;
|
||||
uint8_t hash_size = decodeTraceHashSize(flags, len);
|
||||
uint16_t offset = (uint16_t)pkt->path_len * (uint16_t)hash_size;
|
||||
if (offset >= len) { // TRACE has reached end of given path
|
||||
onTraceRecv(pkt, trace_tag, auth_code, flags, pkt->path, &pkt->payload[i], len);
|
||||
} else if (self_id.isHashMatch(&pkt->payload[i + offset], 1 << path_sz) && allowPacketForward(pkt) && !_tables->hasSeen(pkt)) {
|
||||
} else if (hash_size > 0 && offset + hash_size <= len
|
||||
&& self_id.isHashMatch(&pkt->payload[i + offset], hash_size)
|
||||
&& allowPacketForward(pkt) && !_tables->hasSeen(pkt)) {
|
||||
// append SNR (Not hash!)
|
||||
pkt->path[pkt->path_len++] = (int8_t) (pkt->getSNR()*4);
|
||||
|
||||
@@ -449,6 +472,8 @@ void Mesh::routeDirectRecvAcks(Packet* packet, uint32_t delay_millis) {
|
||||
void Mesh::clearDirectRetrySlot(int idx) {
|
||||
_direct_retries[idx].packet = NULL;
|
||||
_direct_retries[idx].trigger_packet = NULL;
|
||||
_direct_retries[idx].retry_started_at = 0;
|
||||
_direct_retries[idx].echo_wait_started_at = 0;
|
||||
_direct_retries[idx].retry_at = 0;
|
||||
_direct_retries[idx].retry_delay = 0;
|
||||
_direct_retries[idx].retry_attempts_sent = 0;
|
||||
@@ -490,13 +515,16 @@ bool Mesh::cancelDirectRetryOnEcho(const Packet* packet) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int8_t echo_snr_x4 = packet->_snr;
|
||||
if (_direct_retries[i].queued) {
|
||||
if (_direct_retries[i].expect_path_growth
|
||||
&& _direct_retries[i].packet != NULL
|
||||
&& _direct_retries[i].progress_marker < packet->path_len) {
|
||||
// For retry-good quality, use the received echo packet SNR (return-link quality).
|
||||
_direct_retries[i].packet->_snr = packet->_snr;
|
||||
if (_direct_retries[i].packet != NULL) {
|
||||
// Success quality comes from the received downstream echo, not the original upstream RX.
|
||||
_direct_retries[i].packet->_snr = echo_snr_x4;
|
||||
}
|
||||
uint32_t echo_millis = _direct_retries[i].echo_wait_started_at == 0
|
||||
? 0
|
||||
: (uint32_t)(_ms->getMillis() - _direct_retries[i].echo_wait_started_at);
|
||||
onDirectRetryEvent("good", _direct_retries[i].packet, echo_millis, _direct_retries[i].retry_attempts_sent + 1);
|
||||
for (int j = 0; j < _mgr->getOutboundTotal(); j++) {
|
||||
if (_mgr->getOutboundByIdx(j) == _direct_retries[i].packet) {
|
||||
Packet* pending = _mgr->removeOutboundByIdx(j);
|
||||
@@ -506,18 +534,15 @@ 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 {
|
||||
if (_direct_retries[i].expect_path_growth
|
||||
&& _direct_retries[i].trigger_packet != NULL
|
||||
&& _direct_retries[i].progress_marker < packet->path_len) {
|
||||
// For retry-good quality, use the received echo packet SNR (return-link quality).
|
||||
_direct_retries[i].trigger_packet->_snr = packet->_snr;
|
||||
if (_direct_retries[i].trigger_packet != NULL) {
|
||||
_direct_retries[i].trigger_packet->_snr = echo_snr_x4;
|
||||
}
|
||||
onDirectRetryEvent("canceled_echo", _direct_retries[i].trigger_packet, 0);
|
||||
onDirectRetryEvent("good", _direct_retries[i].trigger_packet, 0);
|
||||
uint32_t echo_millis = _direct_retries[i].echo_wait_started_at == 0
|
||||
? 0
|
||||
: (uint32_t)(_ms->getMillis() - _direct_retries[i].echo_wait_started_at);
|
||||
onDirectRetryEvent("good", _direct_retries[i].trigger_packet, echo_millis, _direct_retries[i].retry_attempts_sent + 1);
|
||||
clearDirectRetrySlot(i);
|
||||
}
|
||||
cleared = true;
|
||||
@@ -535,7 +560,11 @@ 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.
|
||||
onDirectRetryEvent("resent", packet, 0);
|
||||
uint32_t elapsed_millis = _direct_retries[i].retry_started_at == 0
|
||||
? 0
|
||||
: (uint32_t)(_ms->getMillis() - _direct_retries[i].retry_started_at);
|
||||
onDirectRetryEvent("resent", packet, elapsed_millis, _direct_retries[i].retry_attempts_sent + 1);
|
||||
_direct_retries[i].echo_wait_started_at = _ms->getMillis();
|
||||
_direct_retries[i].retry_attempts_sent++;
|
||||
uint8_t max_attempts = getDirectRetryMaxAttempts(packet);
|
||||
if (max_attempts < 1) {
|
||||
@@ -544,16 +573,16 @@ void Mesh::armDirectRetryOnSendComplete(const Packet* packet) {
|
||||
max_attempts = DIRECT_RETRY_MAX_ATTEMPTS_HARD_MAX;
|
||||
}
|
||||
if (_direct_retries[i].retry_attempts_sent >= max_attempts) {
|
||||
onDirectRetryEvent("failed_all_tries", packet, 0);
|
||||
onDirectRetryEvent("failure", packet, 0);
|
||||
onDirectRetryEvent("failed_all_tries", packet, elapsed_millis, _direct_retries[i].retry_attempts_sent);
|
||||
onDirectRetryEvent("failure", packet, elapsed_millis, _direct_retries[i].retry_attempts_sent);
|
||||
clearDirectRetrySlot(i);
|
||||
continue;
|
||||
}
|
||||
|
||||
Packet* retry = obtainNewPacket();
|
||||
if (retry == NULL) {
|
||||
onDirectRetryEvent("dropped_no_packet", packet, 0);
|
||||
onDirectRetryEvent("failure", packet, 0);
|
||||
onDirectRetryEvent("dropped_no_packet", packet, elapsed_millis, _direct_retries[i].retry_attempts_sent + 1);
|
||||
onDirectRetryEvent("failure", packet, elapsed_millis, _direct_retries[i].retry_attempts_sent + 1);
|
||||
clearDirectRetrySlot(i);
|
||||
continue;
|
||||
}
|
||||
@@ -565,10 +594,10 @@ void Mesh::armDirectRetryOnSendComplete(const Packet* packet) {
|
||||
_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);
|
||||
onDirectRetryEvent("queued", retry, retry_delay, _direct_retries[i].retry_attempts_sent + 1);
|
||||
} else {
|
||||
onDirectRetryEvent("dropped_queue_full", retry, retry_delay);
|
||||
onDirectRetryEvent("failure", retry, 0);
|
||||
onDirectRetryEvent("dropped_queue_full", retry, retry_delay, _direct_retries[i].retry_attempts_sent + 1);
|
||||
onDirectRetryEvent("failure", retry, elapsed_millis, _direct_retries[i].retry_attempts_sent + 1);
|
||||
clearDirectRetrySlot(i);
|
||||
}
|
||||
}
|
||||
@@ -582,8 +611,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);
|
||||
onDirectRetryEvent("dropped_no_packet", packet, _direct_retries[i].retry_delay, 1);
|
||||
onDirectRetryEvent("failure", packet, 0, 1);
|
||||
clearDirectRetrySlot(i);
|
||||
continue;
|
||||
}
|
||||
@@ -593,14 +622,17 @@ void Mesh::armDirectRetryOnSendComplete(const Packet* packet) {
|
||||
// Start the echo wait only after the initial direct transmission actually completed.
|
||||
sendPacket(retry, _direct_retries[i].priority, _direct_retries[i].retry_delay);
|
||||
if (isDirectRetryQueued(retry)) {
|
||||
unsigned long now = _ms->getMillis();
|
||||
_direct_retries[i].packet = retry;
|
||||
_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);
|
||||
_direct_retries[i].retry_started_at = now;
|
||||
_direct_retries[i].echo_wait_started_at = now;
|
||||
onDirectRetryEvent("queued", retry, _direct_retries[i].retry_delay, 1);
|
||||
} else {
|
||||
onDirectRetryEvent("dropped_queue_full", retry, _direct_retries[i].retry_delay);
|
||||
onDirectRetryEvent("failure", retry, 0);
|
||||
onDirectRetryEvent("dropped_queue_full", retry, _direct_retries[i].retry_delay, 1);
|
||||
onDirectRetryEvent("failure", retry, 0, 1);
|
||||
clearDirectRetrySlot(i);
|
||||
}
|
||||
}
|
||||
@@ -615,16 +647,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);
|
||||
onDirectRetryEvent("dropped_send_fail", packet, 0, _direct_retries[i].retry_attempts_sent + 1);
|
||||
onDirectRetryEvent("failure", packet, 0, _direct_retries[i].retry_attempts_sent + 1);
|
||||
clearDirectRetrySlot(i);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_direct_retries[i].trigger_packet == packet) {
|
||||
onDirectRetryEvent("dropped_send_fail", packet, 0);
|
||||
onDirectRetryEvent("failure", packet, 0);
|
||||
onDirectRetryEvent("dropped_send_fail", packet, 0, 1);
|
||||
onDirectRetryEvent("failure", packet, 0, 1);
|
||||
clearDirectRetrySlot(i);
|
||||
}
|
||||
}
|
||||
@@ -665,9 +697,9 @@ bool Mesh::getDirectRetryTarget(const Packet* packet, const uint8_t*& next_hop_h
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t hash_size = 1 << (packet->payload[8] & 0x03);
|
||||
uint8_t route_bytes = packet->payload_len - 9;
|
||||
uint8_t offset = packet->path_len * hash_size;
|
||||
uint8_t hash_size = decodeTraceHashSize(packet->payload[8], route_bytes);
|
||||
uint16_t offset = (uint16_t)packet->path_len * (uint16_t)hash_size;
|
||||
if (offset + hash_size > route_bytes) {
|
||||
return false;
|
||||
}
|
||||
@@ -705,6 +737,8 @@ void Mesh::maybeScheduleDirectRetry(const Packet* packet, uint8_t priority) {
|
||||
}
|
||||
}
|
||||
if (slot_idx < 0) {
|
||||
onDirectRetryEvent("dropped_no_slot", packet, 0, 0);
|
||||
onDirectRetryEvent("failure", packet, 0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -713,6 +747,8 @@ void Mesh::maybeScheduleDirectRetry(const Packet* packet, uint8_t priority) {
|
||||
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_started_at = 0;
|
||||
_direct_retries[slot_idx].echo_wait_started_at = 0;
|
||||
_direct_retries[slot_idx].retry_at = 0;
|
||||
_direct_retries[slot_idx].retry_delay = retry_delay;
|
||||
_direct_retries[slot_idx].retry_attempts_sent = 0;
|
||||
@@ -721,7 +757,6 @@ void Mesh::maybeScheduleDirectRetry(const Packet* packet, uint8_t priority) {
|
||||
_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) {
|
||||
|
||||
+4
-2
@@ -32,6 +32,8 @@ class Mesh : public Dispatcher {
|
||||
struct DirectRetryEntry {
|
||||
Packet* packet;
|
||||
Packet* trigger_packet;
|
||||
unsigned long retry_started_at;
|
||||
unsigned long echo_wait_started_at;
|
||||
unsigned long retry_at;
|
||||
uint32_t retry_delay;
|
||||
uint8_t retry_attempts_sent;
|
||||
@@ -115,7 +117,7 @@ protected:
|
||||
/**
|
||||
* \returns delay before a specific retry attempt, where attempt_idx=0 is the first retry.
|
||||
*/
|
||||
virtual uint32_t getDirectRetryAttemptDelay(const Packet* packet, uint8_t attempt_idx) const;
|
||||
virtual uint32_t getDirectRetryAttemptDelay(const Packet* packet, uint8_t attempt_idx);
|
||||
|
||||
/**
|
||||
* \returns number of extra (Direct) ACK transmissions wanted.
|
||||
@@ -125,7 +127,7 @@ protected:
|
||||
/**
|
||||
* \brief Optional hook for logging direct-retry lifecycle events.
|
||||
*/
|
||||
virtual void onDirectRetryEvent(const char* event, const Packet* packet, uint32_t delay_millis) { }
|
||||
virtual void onDirectRetryEvent(const char* event, const Packet* packet, uint32_t delay_millis, uint8_t retry_attempt) { }
|
||||
|
||||
/**
|
||||
* \brief Perform search of local DB of peers/contacts.
|
||||
|
||||
+167
-11
@@ -3,6 +3,7 @@
|
||||
#include "TxtDataHelpers.h"
|
||||
#include "AdvertDataHelpers.h"
|
||||
#include <RTClib.h>
|
||||
#include <string.h>
|
||||
#define STR_HELPER(x) #x
|
||||
#define STR(x) STR_HELPER(x)
|
||||
|
||||
@@ -14,17 +15,21 @@
|
||||
#define DIRECT_RETRY_PREFS_MAGIC_0 0xD4
|
||||
#define DIRECT_RETRY_PREFS_MAGIC_1 0x52
|
||||
#define DIRECT_RETRY_RECENT_DEFAULT 1
|
||||
#define DIRECT_RETRY_SNR_MARGIN_DB_DEFAULT_X4 10
|
||||
#define DIRECT_RETRY_SNR_MARGIN_DB_DEFAULT_X4 DIRECT_RETRY_ROOFTOP_MARGIN_X4
|
||||
#define DIRECT_RETRY_SNR_MARGIN_DB_MAX 40
|
||||
#define DIRECT_RETRY_SNR_MARGIN_X4_MAX (DIRECT_RETRY_SNR_MARGIN_DB_MAX * 4)
|
||||
#define DIRECT_RETRY_TIMING_MAGIC_0 0xD5
|
||||
#define DIRECT_RETRY_TIMING_MAGIC_1 0x54
|
||||
#define DIRECT_RETRY_COUNT_DEFAULT 15
|
||||
#define DIRECT_RETRY_COUNT_DEFAULT DIRECT_RETRY_ROOFTOP_COUNT
|
||||
#define DIRECT_RETRY_COUNT_MIN 1
|
||||
#define DIRECT_RETRY_COUNT_MAX 15
|
||||
#define DIRECT_RETRY_BASE_MS_DEFAULT 200
|
||||
#define DIRECT_RETRY_BASE_MS_DEFAULT DIRECT_RETRY_ROOFTOP_BASE_MS
|
||||
#define DIRECT_RETRY_BASE_MS_MIN 10
|
||||
#define DIRECT_RETRY_BASE_MS_MAX 5000
|
||||
#define DIRECT_RETRY_STEP_MS_DEFAULT DIRECT_RETRY_ROOFTOP_STEP_MS
|
||||
#define DIRECT_RETRY_STEP_MS_MIN 0
|
||||
#define DIRECT_RETRY_STEP_MS_MAX 5000
|
||||
#define DIRECT_RETRY_PRESET_DEFAULT DIRECT_RETRY_PRESET_ROOFTOP
|
||||
|
||||
// Believe it or not, this std C function is busted on some platforms!
|
||||
static uint32_t _atoi(const char* sp) {
|
||||
@@ -45,6 +50,98 @@ static float directRetryMarginX4ToDb(uint8_t margin_x4) {
|
||||
return ((float)margin_x4) / 4.0f;
|
||||
}
|
||||
|
||||
static uint8_t directRetryPresetOrDefault(uint8_t preset) {
|
||||
if (preset <= DIRECT_RETRY_PRESET_MOBILE) {
|
||||
return preset;
|
||||
}
|
||||
return DIRECT_RETRY_PRESET_DEFAULT;
|
||||
}
|
||||
|
||||
static const char* directRetryPresetName(uint8_t preset) {
|
||||
switch (directRetryPresetOrDefault(preset)) {
|
||||
case DIRECT_RETRY_PRESET_INFRA:
|
||||
return "infra";
|
||||
case DIRECT_RETRY_PRESET_MOBILE:
|
||||
return "mobile";
|
||||
case DIRECT_RETRY_PRESET_ROOFTOP:
|
||||
default:
|
||||
return "rooftop";
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t directRetryEffectiveCount(const NodePrefs* prefs) {
|
||||
return constrain(prefs->direct_retry_attempts, (uint8_t)DIRECT_RETRY_COUNT_MIN, (uint8_t)DIRECT_RETRY_COUNT_MAX);
|
||||
}
|
||||
|
||||
static uint16_t directRetryEffectiveBaseMs(const NodePrefs* prefs) {
|
||||
return constrain(prefs->direct_retry_base_ms, (uint16_t)DIRECT_RETRY_BASE_MS_MIN, (uint16_t)DIRECT_RETRY_BASE_MS_MAX);
|
||||
}
|
||||
|
||||
static uint16_t directRetryEffectiveStepMs(const NodePrefs* prefs) {
|
||||
return constrain(prefs->direct_retry_step_ms, (uint16_t)DIRECT_RETRY_STEP_MS_MIN, (uint16_t)DIRECT_RETRY_STEP_MS_MAX);
|
||||
}
|
||||
|
||||
static uint8_t directRetryEffectiveMarginX4(const NodePrefs* prefs) {
|
||||
return constrain(prefs->direct_retry_snr_margin_db, (uint8_t)0, (uint8_t)DIRECT_RETRY_SNR_MARGIN_X4_MAX);
|
||||
}
|
||||
|
||||
static uint16_t directRetryPresetStepDefault(uint8_t preset) {
|
||||
switch (directRetryPresetOrDefault(preset)) {
|
||||
case DIRECT_RETRY_PRESET_INFRA:
|
||||
return DIRECT_RETRY_INFRA_STEP_MS;
|
||||
case DIRECT_RETRY_PRESET_MOBILE:
|
||||
return DIRECT_RETRY_MOBILE_STEP_MS;
|
||||
case DIRECT_RETRY_PRESET_ROOFTOP:
|
||||
default:
|
||||
return DIRECT_RETRY_ROOFTOP_STEP_MS;
|
||||
}
|
||||
}
|
||||
|
||||
static void applyDirectRetryPreset(NodePrefs* prefs, uint8_t preset) {
|
||||
prefs->direct_retry_preset = directRetryPresetOrDefault(preset);
|
||||
switch (prefs->direct_retry_preset) {
|
||||
case DIRECT_RETRY_PRESET_INFRA:
|
||||
prefs->direct_retry_base_ms = DIRECT_RETRY_INFRA_BASE_MS;
|
||||
prefs->direct_retry_attempts = DIRECT_RETRY_INFRA_COUNT;
|
||||
prefs->direct_retry_step_ms = DIRECT_RETRY_INFRA_STEP_MS;
|
||||
prefs->direct_retry_snr_margin_db = DIRECT_RETRY_INFRA_MARGIN_X4;
|
||||
break;
|
||||
case DIRECT_RETRY_PRESET_MOBILE:
|
||||
prefs->direct_retry_base_ms = DIRECT_RETRY_MOBILE_BASE_MS;
|
||||
prefs->direct_retry_attempts = DIRECT_RETRY_MOBILE_COUNT;
|
||||
prefs->direct_retry_step_ms = DIRECT_RETRY_MOBILE_STEP_MS;
|
||||
prefs->direct_retry_snr_margin_db = DIRECT_RETRY_MOBILE_MARGIN_X4;
|
||||
break;
|
||||
case DIRECT_RETRY_PRESET_ROOFTOP:
|
||||
default:
|
||||
prefs->direct_retry_base_ms = DIRECT_RETRY_ROOFTOP_BASE_MS;
|
||||
prefs->direct_retry_attempts = DIRECT_RETRY_ROOFTOP_COUNT;
|
||||
prefs->direct_retry_step_ms = DIRECT_RETRY_ROOFTOP_STEP_MS;
|
||||
prefs->direct_retry_snr_margin_db = DIRECT_RETRY_ROOFTOP_MARGIN_X4;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static bool parseDirectRetryPreset(const char* value, uint8_t& preset) {
|
||||
if (value == NULL) {
|
||||
return false;
|
||||
}
|
||||
if (strcmp(value, "0") == 0 || strcmp(value, "infra") == 0
|
||||
|| strcmp(value, "infa") == 0 || strcmp(value, "infrastructure") == 0) {
|
||||
preset = DIRECT_RETRY_PRESET_INFRA;
|
||||
return true;
|
||||
}
|
||||
if (strcmp(value, "1") == 0 || strcmp(value, "rooftop") == 0) {
|
||||
preset = DIRECT_RETRY_PRESET_ROOFTOP;
|
||||
return true;
|
||||
}
|
||||
if (strcmp(value, "2") == 0 || strcmp(value, "mobile") == 0) {
|
||||
preset = DIRECT_RETRY_PRESET_MOBILE;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool isValidName(const char *n) {
|
||||
while (*n) {
|
||||
if (*n == '[' || *n == ']' || *n == '\\' || *n == ':' || *n == ',' || *n == '?' || *n == '*') return false;
|
||||
@@ -127,6 +224,9 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
|
||||
file.read((uint8_t *)&_prefs->direct_retry_timing_magic[0], sizeof(_prefs->direct_retry_timing_magic)); // 294
|
||||
size_t radio_fem_rxgain_read = file.read((uint8_t *)&_prefs->radio_fem_rxgain,
|
||||
sizeof(_prefs->radio_fem_rxgain)); // 296
|
||||
file.read((uint8_t *)&_prefs->direct_retry_preset, sizeof(_prefs->direct_retry_preset)); // 297
|
||||
size_t retry_step_read = file.read((uint8_t *)&_prefs->direct_retry_step_ms,
|
||||
sizeof(_prefs->direct_retry_step_ms)); // 298
|
||||
// PowerSaving-only prefs stored radio_fem_rxgain at 291, before direct retry timing existed.
|
||||
if (radio_fem_rxgain_read != sizeof(_prefs->radio_fem_rxgain)
|
||||
&& legacy_retry_attempts_read == sizeof(legacy_retry_attempts_or_radio_fem_rxgain)
|
||||
@@ -134,7 +234,7 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
|
||||
|| _prefs->direct_retry_timing_magic[1] != DIRECT_RETRY_TIMING_MAGIC_1)) {
|
||||
_prefs->radio_fem_rxgain = constrain(legacy_retry_attempts_or_radio_fem_rxgain, 0, 1);
|
||||
}
|
||||
// next: 297
|
||||
// next: 298
|
||||
|
||||
// sanitise bad pref values
|
||||
_prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0, 20.0f);
|
||||
@@ -182,6 +282,12 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
|
||||
// sanitise settings
|
||||
_prefs->rx_boosted_gain = constrain(_prefs->rx_boosted_gain, 0, 1); // boolean
|
||||
_prefs->radio_fem_rxgain = constrain(_prefs->radio_fem_rxgain, 0, 1); // boolean
|
||||
_prefs->direct_retry_preset = directRetryPresetOrDefault(_prefs->direct_retry_preset);
|
||||
if (retry_step_read != sizeof(_prefs->direct_retry_step_ms)) {
|
||||
_prefs->direct_retry_step_ms = directRetryPresetStepDefault(_prefs->direct_retry_preset);
|
||||
} else {
|
||||
_prefs->direct_retry_step_ms = constrain(_prefs->direct_retry_step_ms, DIRECT_RETRY_STEP_MS_MIN, DIRECT_RETRY_STEP_MS_MAX);
|
||||
}
|
||||
|
||||
file.close();
|
||||
}
|
||||
@@ -252,7 +358,9 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) {
|
||||
uint8_t retry_timing_magic[2] = { DIRECT_RETRY_TIMING_MAGIC_0, DIRECT_RETRY_TIMING_MAGIC_1 };
|
||||
file.write(retry_timing_magic, sizeof(retry_timing_magic)); // 294
|
||||
file.write((uint8_t *)&_prefs->radio_fem_rxgain, sizeof(_prefs->radio_fem_rxgain)); // 296
|
||||
// next: 297
|
||||
file.write((uint8_t *)&_prefs->direct_retry_preset, sizeof(_prefs->direct_retry_preset)); // 297
|
||||
file.write((uint8_t *)&_prefs->direct_retry_step_ms, sizeof(_prefs->direct_retry_step_ms)); // 298
|
||||
// next: 300
|
||||
|
||||
file.close();
|
||||
}
|
||||
@@ -413,11 +521,17 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* re
|
||||
} else if (memcmp(config, "direct.retry.heard", 18) == 0) {
|
||||
sprintf(reply, "> %s", _prefs->direct_retry_recent_enabled ? "on" : "off");
|
||||
} else if (memcmp(config, "direct.retry.margin", 19) == 0) {
|
||||
sprintf(reply, "> %s", StrHelper::ftoa(directRetryMarginX4ToDb(_prefs->direct_retry_snr_margin_db)));
|
||||
sprintf(reply, "> %s", StrHelper::ftoa(directRetryMarginX4ToDb(directRetryEffectiveMarginX4(_prefs))));
|
||||
} else if (memcmp(config, "direct.retry.preset", 19) == 0) {
|
||||
sprintf(reply, "> %d,%s",
|
||||
(uint32_t)directRetryPresetOrDefault(_prefs->direct_retry_preset),
|
||||
directRetryPresetName(_prefs->direct_retry_preset));
|
||||
} else if (memcmp(config, "direct.retry.count", 18) == 0) {
|
||||
sprintf(reply, "> %d", (uint32_t)_prefs->direct_retry_attempts);
|
||||
sprintf(reply, "> %d", (uint32_t)directRetryEffectiveCount(_prefs));
|
||||
} else if (memcmp(config, "direct.retry.base", 17) == 0) {
|
||||
sprintf(reply, "> %d", (uint32_t)_prefs->direct_retry_base_ms);
|
||||
sprintf(reply, "> %d", (uint32_t)directRetryEffectiveBaseMs(_prefs));
|
||||
} else if (memcmp(config, "direct.retry.step", 17) == 0) {
|
||||
sprintf(reply, "> %d", (uint32_t)directRetryEffectiveStepMs(_prefs));
|
||||
} else if (memcmp(config, "owner.info", 10) == 0) {
|
||||
*reply++ = '>';
|
||||
*reply++ = ' ';
|
||||
@@ -688,6 +802,15 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* re
|
||||
} else {
|
||||
sprintf(reply, "Error, min 0 and max %d", DIRECT_RETRY_SNR_MARGIN_DB_MAX);
|
||||
}
|
||||
} else if (memcmp(config, "direct.retry.preset ", 20) == 0) {
|
||||
uint8_t preset;
|
||||
if (parseDirectRetryPreset(&config[20], preset)) {
|
||||
applyDirectRetryPreset(_prefs, preset);
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
} else {
|
||||
strcpy(reply, "Error, must be infra, rooftop, mobile, 0, 1, or 2");
|
||||
}
|
||||
} else if (memcmp(config, "direct.retry.count ", 19) == 0) {
|
||||
int count = atoi(&config[19]);
|
||||
if (count >= DIRECT_RETRY_COUNT_MIN && count <= DIRECT_RETRY_COUNT_MAX) {
|
||||
@@ -706,6 +829,15 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* re
|
||||
} else {
|
||||
sprintf(reply, "Error, min %d and max %d", DIRECT_RETRY_BASE_MS_MIN, DIRECT_RETRY_BASE_MS_MAX);
|
||||
}
|
||||
} else if (memcmp(config, "direct.retry.step ", 18) == 0) {
|
||||
int delay_ms = atoi(&config[18]);
|
||||
if (delay_ms >= DIRECT_RETRY_STEP_MS_MIN && delay_ms <= DIRECT_RETRY_STEP_MS_MAX) {
|
||||
_prefs->direct_retry_step_ms = (uint16_t)delay_ms;
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
} else {
|
||||
sprintf(reply, "Error, min %d and max %d", DIRECT_RETRY_STEP_MS_MIN, DIRECT_RETRY_STEP_MS_MAX);
|
||||
}
|
||||
} else if (memcmp(config, "owner.info ", 11) == 0) {
|
||||
config += 11;
|
||||
char *dp = _prefs->owner_info;
|
||||
@@ -1224,6 +1356,15 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep
|
||||
} else {
|
||||
sprintf(reply, "Error, min 0 and max %d", DIRECT_RETRY_SNR_MARGIN_DB_MAX);
|
||||
}
|
||||
} else if (memcmp(config, "direct.retry.preset ", 20) == 0) {
|
||||
uint8_t preset;
|
||||
if (parseDirectRetryPreset(&config[20], preset)) {
|
||||
applyDirectRetryPreset(_prefs, preset);
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
} else {
|
||||
strcpy(reply, "Error, must be infra, rooftop, mobile, 0, 1, or 2");
|
||||
}
|
||||
} else if (memcmp(config, "direct.retry.count ", 19) == 0) {
|
||||
int count = atoi(&config[19]);
|
||||
if (count >= DIRECT_RETRY_COUNT_MIN && count <= DIRECT_RETRY_COUNT_MAX) {
|
||||
@@ -1242,6 +1383,15 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep
|
||||
} else {
|
||||
sprintf(reply, "Error, min %d and max %d", DIRECT_RETRY_BASE_MS_MIN, DIRECT_RETRY_BASE_MS_MAX);
|
||||
}
|
||||
} else if (memcmp(config, "direct.retry.step ", 18) == 0) {
|
||||
int delay_ms = atoi(&config[18]);
|
||||
if (delay_ms >= DIRECT_RETRY_STEP_MS_MIN && delay_ms <= DIRECT_RETRY_STEP_MS_MAX) {
|
||||
_prefs->direct_retry_step_ms = (uint16_t)delay_ms;
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
} else {
|
||||
sprintf(reply, "Error, min %d and max %d", DIRECT_RETRY_STEP_MS_MIN, DIRECT_RETRY_STEP_MS_MAX);
|
||||
}
|
||||
} else if (memcmp(config, "owner.info ", 11) == 0) {
|
||||
config += 11;
|
||||
char *dp = _prefs->owner_info;
|
||||
@@ -1420,11 +1570,17 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep
|
||||
} else if (memcmp(config, "direct.retry.heard", 18) == 0) {
|
||||
sprintf(reply, "> %s", _prefs->direct_retry_recent_enabled ? "on" : "off");
|
||||
} else if (memcmp(config, "direct.retry.margin", 19) == 0) {
|
||||
sprintf(reply, "> %s", StrHelper::ftoa(directRetryMarginX4ToDb(_prefs->direct_retry_snr_margin_db)));
|
||||
sprintf(reply, "> %s", StrHelper::ftoa(directRetryMarginX4ToDb(directRetryEffectiveMarginX4(_prefs))));
|
||||
} else if (memcmp(config, "direct.retry.preset", 19) == 0) {
|
||||
sprintf(reply, "> %d,%s",
|
||||
(uint32_t)directRetryPresetOrDefault(_prefs->direct_retry_preset),
|
||||
directRetryPresetName(_prefs->direct_retry_preset));
|
||||
} else if (memcmp(config, "direct.retry.count", 18) == 0) {
|
||||
sprintf(reply, "> %d", (uint32_t)_prefs->direct_retry_attempts);
|
||||
sprintf(reply, "> %d", (uint32_t)directRetryEffectiveCount(_prefs));
|
||||
} else if (memcmp(config, "direct.retry.base", 17) == 0) {
|
||||
sprintf(reply, "> %d", (uint32_t)_prefs->direct_retry_base_ms);
|
||||
sprintf(reply, "> %d", (uint32_t)directRetryEffectiveBaseMs(_prefs));
|
||||
} else if (memcmp(config, "direct.retry.step", 17) == 0) {
|
||||
sprintf(reply, "> %d", (uint32_t)directRetryEffectiveStepMs(_prefs));
|
||||
} else if (memcmp(config, "owner.info", 10) == 0) {
|
||||
*reply++ = '>';
|
||||
*reply++ = ' ';
|
||||
|
||||
@@ -19,6 +19,25 @@
|
||||
#define LOOP_DETECT_MODERATE 2
|
||||
#define LOOP_DETECT_STRICT 3
|
||||
|
||||
#define DIRECT_RETRY_PRESET_INFRA 0
|
||||
#define DIRECT_RETRY_PRESET_ROOFTOP 1
|
||||
#define DIRECT_RETRY_PRESET_MOBILE 2
|
||||
|
||||
#define DIRECT_RETRY_INFRA_BASE_MS 275
|
||||
#define DIRECT_RETRY_INFRA_COUNT 4
|
||||
#define DIRECT_RETRY_INFRA_STEP_MS 150
|
||||
#define DIRECT_RETRY_INFRA_MARGIN_X4 60
|
||||
|
||||
#define DIRECT_RETRY_ROOFTOP_BASE_MS 175
|
||||
#define DIRECT_RETRY_ROOFTOP_COUNT 15
|
||||
#define DIRECT_RETRY_ROOFTOP_STEP_MS 100
|
||||
#define DIRECT_RETRY_ROOFTOP_MARGIN_X4 20
|
||||
|
||||
#define DIRECT_RETRY_MOBILE_BASE_MS 175
|
||||
#define DIRECT_RETRY_MOBILE_COUNT 15
|
||||
#define DIRECT_RETRY_MOBILE_STEP_MS 50
|
||||
#define DIRECT_RETRY_MOBILE_MARGIN_X4 0
|
||||
|
||||
struct NodePrefs { // persisted to file
|
||||
float airtime_factor;
|
||||
char node_name[32];
|
||||
@@ -67,6 +86,8 @@ struct NodePrefs { // persisted to file
|
||||
uint8_t direct_retry_attempts;
|
||||
uint16_t direct_retry_base_ms;
|
||||
uint8_t direct_retry_timing_magic[2];
|
||||
uint8_t direct_retry_preset;
|
||||
uint16_t direct_retry_step_ms;
|
||||
};
|
||||
|
||||
class CommonCLICallbacks {
|
||||
|
||||
@@ -9,8 +9,10 @@
|
||||
#define MAX_PACKET_HASHES 128
|
||||
#define MAX_PACKET_ACKS 64
|
||||
#ifndef MAX_RECENT_REPEATERS
|
||||
// Two defaults. Can be overridden with -D MAX_RECENT_REPEATERS=<n>.
|
||||
#if defined(ESP32)
|
||||
// Platform defaults. Can be overridden with -D MAX_RECENT_REPEATERS=<n>.
|
||||
#if defined(ESP32) || defined(ESP32_PLATFORM)
|
||||
#define MAX_RECENT_REPEATERS 2048
|
||||
#elif defined(NRF52_PLATFORM)
|
||||
#define MAX_RECENT_REPEATERS 512
|
||||
#else
|
||||
#define MAX_RECENT_REPEATERS 64
|
||||
@@ -23,9 +25,7 @@ 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.
|
||||
uint16_t retry_count;
|
||||
uint16_t fail_count;
|
||||
// Identity and link quality for a next-hop path prefix.
|
||||
uint8_t prefix[MAX_ROUTE_HASH_BYTES];
|
||||
uint8_t prefix_len;
|
||||
int8_t snr_x4;
|
||||
@@ -80,8 +80,8 @@ private:
|
||||
|
||||
int8_t weightedSnrX4RoundUp(int8_t curr_snr_x4, int8_t new_snr_x4) const {
|
||||
// Keep existing SNR heavier than a single new sample: 75% existing + 25% new.
|
||||
int16_t weighted_sum = ((int16_t)curr_snr_x4 * 3) + (int16_t)new_snr_x4;
|
||||
int16_t blended = weighted_sum / 4; // truncates toward zero
|
||||
int32_t weighted_sum = ((int32_t)curr_snr_x4 * 3) + (int32_t)new_snr_x4;
|
||||
int32_t blended = weighted_sum / 4; // truncates toward zero
|
||||
// "Round up" means ceil(), which only differs from truncation for positive remainders.
|
||||
if (weighted_sum > 0 && (weighted_sum % 4) != 0) {
|
||||
blended++;
|
||||
@@ -160,8 +160,11 @@ public:
|
||||
f.read((uint8_t *) &_next_idx, sizeof(_next_idx));
|
||||
f.read((uint8_t *) &_acks[0], sizeof(_acks));
|
||||
f.read((uint8_t *) &_next_ack_idx, sizeof(_next_ack_idx));
|
||||
f.read((uint8_t *) &_recent_repeaters[0], sizeof(_recent_repeaters));
|
||||
f.read((uint8_t *) &_next_recent_repeater_idx, sizeof(_next_recent_repeater_idx));
|
||||
// Recent repeater entries are intentionally not restored across boots.
|
||||
// This avoids struct-layout migration issues and keeps stale path quality
|
||||
// stats from persisting indefinitely.
|
||||
memset(_recent_repeaters, 0, sizeof(_recent_repeaters));
|
||||
_next_recent_repeater_idx = 0;
|
||||
}
|
||||
void saveTo(File f) {
|
||||
f.write(_hashes, sizeof(_hashes));
|
||||
@@ -321,96 +324,10 @@ public:
|
||||
memcpy(slot.prefix, prefix, prefix_len);
|
||||
slot.prefix_len = prefix_len;
|
||||
slot.snr_x4 = snr_x4;
|
||||
slot.retry_count = 0;
|
||||
slot.fail_count = 0;
|
||||
slot.snr_locked = snr_locked ? 1 : 0;
|
||||
_next_recent_repeater_idx = (slot_idx + 1) % MAX_RECENT_REPEATERS;
|
||||
return true;
|
||||
}
|
||||
bool incrementRecentRepeaterRetryCount(const uint8_t* prefix, uint8_t prefix_len,
|
||||
bool create_if_missing = false, int8_t seed_snr_x4 = 0,
|
||||
bool bypass_allow_filter = false) {
|
||||
if (prefix == NULL || prefix_len == 0) {
|
||||
return false;
|
||||
}
|
||||
if (prefix_len > MAX_ROUTE_HASH_BYTES) {
|
||||
prefix_len = MAX_ROUTE_HASH_BYTES;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
if (existing.retry_count < 0xFFFF) {
|
||||
existing.retry_count++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!create_if_missing || !setRecentRepeater(prefix, prefix_len, seed_snr_x4, false, bypass_allow_filter)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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 (existing.retry_count < 0xFFFF) {
|
||||
existing.retry_count++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool incrementRecentRepeaterFailCount(const uint8_t* prefix, uint8_t prefix_len,
|
||||
bool create_if_missing = false, int8_t seed_snr_x4 = 0,
|
||||
bool bypass_allow_filter = false) {
|
||||
if (prefix == NULL || prefix_len == 0) {
|
||||
return false;
|
||||
}
|
||||
if (prefix_len > MAX_ROUTE_HASH_BYTES) {
|
||||
prefix_len = MAX_ROUTE_HASH_BYTES;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
if (existing.fail_count < 0xFFFF) {
|
||||
existing.fail_count++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!create_if_missing || !setRecentRepeater(prefix, prefix_len, seed_snr_x4, false, bypass_allow_filter)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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 (existing.fail_count < 0xFFFF) {
|
||||
existing.fail_count++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool decrementRecentRepeaterSnrX4(const uint8_t* prefix, uint8_t prefix_len, uint8_t amount_x4 = 1) {
|
||||
if (prefix == NULL || prefix_len == 0 || amount_x4 == 0) {
|
||||
return false;
|
||||
|
||||
Reference in new issue
Block a user