From 88fec6e512dd71aa30861d46fc099a595d38884c Mon Sep 17 00:00:00 2001 From: mikecarper Date: Mon, 29 Jun 2026 12:55:58 -0700 Subject: [PATCH] Use keymind retry timing for direct paths --- examples/simple_repeater/MyMesh.cpp | 128 +++++++++++++--------------- examples/simple_repeater/MyMesh.h | 1 + src/Mesh.cpp | 39 ++------- 3 files changed, 65 insertions(+), 103 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 4486c886..a7bf17ac 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -97,10 +97,6 @@ #define LOW_BATTERY_WARN_INTERVAL (24UL * 60UL * 60UL * 1000UL) #define LOW_BATTERY_CRITICAL_INTERVAL (12UL * 60UL * 60UL * 1000UL) -#ifndef DIRECT_RETRY_TRACE_SCALE_HOPS - #define DIRECT_RETRY_TRACE_SCALE_HOPS 32U -#endif - static const char* skipLocalSpaces(const char* text) { while (text != NULL && *text == ' ') text++; return text; @@ -828,65 +824,6 @@ uint32_t MyMesh::getDirectRetryAttemptStepMillis() const { return _prefs.direct_retry_step_ms; } -static uint8_t decodeRetryTraceHashSize(uint8_t flags, uint8_t route_bytes) { - uint8_t code = flags & 0x03; - uint8_t size_pow2 = (uint8_t)(1U << code); - uint8_t size_linear = (uint8_t)(code + 1U); - - 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; -} - -static uint8_t getDirectRetryRemainingHops(const mesh::Packet* packet) { - if (packet == NULL) { - return 0; - } - if (packet->getPayloadType() != PAYLOAD_TYPE_TRACE) { - return packet->getPathHashCount(); - } - if (packet->payload_len < 9) { - return 0; - } - - uint8_t route_bytes = packet->payload_len - 9; - uint8_t hash_size = decodeRetryTraceHashSize(packet->payload[8], route_bytes); - if (hash_size == 0) { - return 0; - } - - uint8_t route_hops = route_bytes / hash_size; - if (packet->path_len >= route_hops) { - return 0; - } - return route_hops - packet->path_len; -} - -static uint32_t scaleDirectRetryDelayForPath(const mesh::Packet* packet, uint32_t delay_ms) { - uint8_t hops = getDirectRetryRemainingHops(packet); - if (hops == 0) { - return delay_ms; - } - - if (packet != NULL && packet->getPayloadType() == PAYLOAD_TYPE_TRACE) { - if (hops >= DIRECT_RETRY_TRACE_SCALE_HOPS) { - return delay_ms; - } - uint32_t scaled = ((delay_ms * hops) + (DIRECT_RETRY_TRACE_SCALE_HOPS - 1U)) / DIRECT_RETRY_TRACE_SCALE_HOPS; - return scaled > 0 ? scaled : 1; - } - - if (hops >= 6) { - return delay_ms; - } - uint32_t scaled = ((delay_ms * hops) + 5U) / 6U; - return scaled > 0 ? scaled : 1; -} - bool MyMesh::allowDirectRetry(const mesh::Packet* packet, const uint8_t* next_hop_hash, uint8_t next_hop_hash_len) const { (void)packet; if (!_prefs.direct_retry_enabled) { @@ -930,18 +867,69 @@ void MyMesh::configureDirectRetryPacket(mesh::Packet* retry, const mesh::Packet* } uint32_t MyMesh::getDirectRetryEchoDelay(const mesh::Packet* packet) const { - (void)packet; - return 200; + 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) { + return base_wait_millis; + } + + // Wait roughly long enough for our TX, the next hop's receive/forward window, and its echo back. + uint32_t bits = ((uint32_t)packet->getRawLength()) * 8; + uint32_t scaled_wait_millis = (uint32_t)((((float)bits) * 4.0f) / kbps); + return base_wait_millis + scaled_wait_millis; +} + +static uint8_t decodeDirectRetryTraceHashSize(uint8_t flags, uint8_t route_bytes) { + uint8_t code = flags & 0x03; + uint8_t size_pow2 = (uint8_t)(1U << code); + uint8_t size_linear = (uint8_t)(code + 1U); + + 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; } uint8_t MyMesh::getDirectRetryMaxAttempts(const mesh::Packet* packet) const { - (void)packet; - return getDirectRetryConfiguredMaxAttempts(); + 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 = decodeDirectRetryTraceHashSize(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 delay_ms = _prefs.direct_retry_base_ms + ((uint32_t)attempt_idx * getDirectRetryAttemptStepMillis()); - return scaleDirectRetryDelayForPath(packet, delay_ms); + uint32_t retry_delay = getDirectRetryEchoDelay(packet) + ((uint32_t)attempt_idx * getDirectRetryAttemptStepMillis()); + if (packet == NULL) { + return retry_delay; + } + return getDirectRetransmitDelay(packet) + retry_delay; } static void formatDirectRetryTarget(char* dest, size_t dest_len, const uint8_t* target_hash, uint8_t target_hash_len) { @@ -2072,6 +2060,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc last_battery_alert_sent = 0; battery_alert_sent = false; dirty_contacts_expiry = 0; + active_bw = 0.0f; active_sf = 0; active_cr = 0; memset(scheduled_radio_settings, 0, sizeof(scheduled_radio_settings)); @@ -2309,6 +2298,7 @@ void MyMesh::checkBatteryAlert() { void MyMesh::applyRadioParams(float freq, float bw, uint8_t sf, uint8_t cr) { radio_driver.setParams(freq, bw, sf, cr); + active_bw = bw; active_sf = sf; active_cr = cr; } diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 6e7a2a87..63e00cdf 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -141,6 +141,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { NeighbourInfo neighbours[MAX_NEIGHBOURS]; #endif CayenneLPP telemetry; + float active_bw; // live BW, including temporary radio overrides uint8_t active_sf; // live SF, including temporary radio overrides uint8_t active_cr; // live CR, including temporary radio overrides ScheduledRadioSetting scheduled_radio_settings[MAX_SCHEDULED_RADIO_SETTINGS]; diff --git a/src/Mesh.cpp b/src/Mesh.cpp index eaff68c0..ec886a77 100644 --- a/src/Mesh.cpp +++ b/src/Mesh.cpp @@ -5,10 +5,6 @@ namespace mesh { static const uint8_t DIRECT_RETRY_MAX_ATTEMPTS_DEFAULT = 15; static const uint8_t DIRECT_RETRY_MAX_ATTEMPTS_HARD_MAX = 15; -static const uint32_t DIRECT_RETRY_BASE_MS_DEFAULT = 175; -static const uint32_t DIRECT_RETRY_STEP_MS_DEFAULT = 50; -static const uint8_t DIRECT_RETRY_SHORT_PATH_SCALE_HOPS = 6; -static const uint8_t DIRECT_RETRY_TRACE_SCALE_HOPS = 16; static const uint8_t FLOOD_RETRY_MAX_ATTEMPTS_DEFAULT = 15; static const uint8_t FLOOD_RETRY_MAX_ATTEMPTS_HARD_MAX = 15; static const uint8_t FLOOD_RETRY_MAX_PATH_DEFAULT = 1; @@ -68,33 +64,6 @@ static uint8_t getTraceDirectPriority(const Packet* packet) { return 5; } -static uint8_t getDirectRetryRemainingHops(const Packet* packet) { - if (packet == NULL) { - return 0; - } - if (packet->getPayloadType() == PAYLOAD_TYPE_TRACE) { - return getTraceRemainingHops(packet); - } - return packet->getPathHashCount(); -} - -static uint32_t scaleDirectRetryDelayForPath(const Packet* packet, uint32_t delay_ms) { - uint8_t hops = getDirectRetryRemainingHops(packet); - if (hops == 0) { - return delay_ms; - } - - uint8_t scale_hops = packet != NULL && packet->getPayloadType() == PAYLOAD_TYPE_TRACE - ? DIRECT_RETRY_TRACE_SCALE_HOPS - : DIRECT_RETRY_SHORT_PATH_SCALE_HOPS; - if (hops >= scale_hops) { - return delay_ms; - } - - uint32_t scaled = ((delay_ms * hops) + (scale_hops - 1U)) / scale_hops; - return scaled > 0 ? scaled : 1; -} - uint8_t Mesh::getDirectRetryCodingRateForAttempt(uint8_t start_cr, uint8_t retry_attempt) { if (start_cr < 4 || start_cr > 8) { return start_cr; @@ -261,16 +230,18 @@ bool Mesh::allowDirectRetry(const Packet* packet, const uint8_t* next_hop_hash, } uint32_t Mesh::getDirectRetryEchoDelay(const Packet* packet) const { (void)packet; - return DIRECT_RETRY_BASE_MS_DEFAULT; + // Keep the base fallback aligned with the repeater's minimum retry wait. + return 200; } uint8_t Mesh::getDirectRetryMaxAttempts(const Packet* packet) const { (void)packet; return DIRECT_RETRY_MAX_ATTEMPTS_DEFAULT; } uint32_t Mesh::getDirectRetryAttemptDelay(const Packet* packet, uint8_t attempt_idx) { + (void)packet; uint32_t base = getDirectRetryEchoDelay(packet); - uint32_t delay_ms = base + ((uint32_t)attempt_idx * DIRECT_RETRY_STEP_MS_DEFAULT); - return scaleDirectRetryDelayForPath(packet, delay_ms); + // Keep the historical linear spacing while allowing the base wait to vary by platform/profile. + return base + ((uint32_t)attempt_idx * 100UL); } bool Mesh::allowFloodRetry(const Packet* packet) const { (void)packet;