From 4d2f304f63976cc4d2a4b3e67f3eb2df3111723d Mon Sep 17 00:00:00 2001 From: mikecarper Date: Sat, 27 Jun 2026 01:51:23 -0700 Subject: [PATCH] Tune mesh retry behavior and builds --- build.sh | 90 ++++++++++++++++++++++-- docs/cli_commands.md | 4 +- docs/halo_keymind_settings.md | 10 +-- examples/companion_radio/MyMesh.h | 1 + examples/simple_repeater/MyMesh.cpp | 52 +++++++++++--- examples/simple_repeater/MyMesh.h | 4 ++ examples/simple_room_server/MyMesh.h | 3 + examples/simple_secure_chat/main.cpp | 4 ++ examples/simple_sensor/SensorMesh.h | 3 + src/Mesh.cpp | 100 ++++++++++++++++++++++++--- src/Mesh.h | 11 ++- src/helpers/SimpleMeshTables.h | 32 ++++++++- 12 files changed, 278 insertions(+), 36 deletions(-) diff --git a/build.sh b/build.sh index a497256d..521bfff4 100755 --- a/build.sh +++ b/build.sh @@ -9,6 +9,7 @@ SELECTED_TARGET="" SELECTED_COMMAND_ARGS=() MESHDEBUG_OVERRIDE="" PACKET_LOGGING_OVERRIDE="" +FIRMWARE_VERSION_SUFFIX="" RADIO_SETTINGS_API_URL="https://api.meshcore.nz/api/v1/config" RADIO_SETTING_TITLE="" RADIO_FREQ_OVERRIDE="" @@ -52,6 +53,7 @@ Commands: list|-l: List firmwares available to build. build-firmware : Build the firmware for the given build target. build-firmwares: Build all firmwares for all targets. + build-firmwares-logging-matrix: Build all firmwares twice, first with MESH_DEBUG/MESH_PACKET_LOGGING off and then with both on. build-matching-firmwares : Build all firmwares for build targets containing the string given for . build-companion-firmwares: Build all companion firmwares for all build targets. build-repeater-firmwares: Build all repeater firmwares for all build targets. @@ -67,6 +69,9 @@ $ bash build.sh Build all firmwares for device targets containing the string "RAK_4631" $ bash build.sh build-matching-firmwares +Build all firmwares twice, with logging-off artifacts using the base version and logging-on artifacts using a "-logging" version suffix: +$ bash build.sh build-firmwares-logging-matrix + Build all companion firmwares $ bash build.sh build-companion-firmwares @@ -257,6 +262,7 @@ prompt_for_build_mode() { local options=( "Build one firmware target" "Build all firmwares" + "Build all firmwares twice (logging off, then MESH_DEBUG + MESH_PACKET_LOGGING on)" "Build all repeater firmwares" "Build all companion firmwares" "Build all chat room server firmwares" @@ -282,14 +288,18 @@ prompt_for_build_mode() { return 0 ;; 3) - SELECTED_COMMAND_ARGS=(build-repeater-firmwares) + SELECTED_COMMAND_ARGS=(build-firmwares-logging-matrix) return 0 ;; 4) - SELECTED_COMMAND_ARGS=(build-companion-firmwares) + SELECTED_COMMAND_ARGS=(build-repeater-firmwares) return 0 ;; 5) + SELECTED_COMMAND_ARGS=(build-companion-firmwares) + return 0 + ;; + 6) SELECTED_COMMAND_ARGS=(build-room-server-firmwares) return 0 ;; @@ -308,6 +318,10 @@ prompt_for_debug_build_settings() { echo "Using debug options: meshdebug=${MESHDEBUG_OVERRIDE}, packet_logging=${PACKET_LOGGING_OVERRIDE}" } +is_logging_matrix_command() { + [ "$1" == "build-firmwares-logging-matrix" ] +} + clear_radio_overrides() { RADIO_SETTING_TITLE="" RADIO_FREQ_OVERRIDE="" @@ -983,7 +997,7 @@ is_supported_build_env() { disable_debug_flags() { if [ "$DISABLE_DEBUG" == "1" ]; then - export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -UMESH_DEBUG -UBLE_DEBUG_LOGGING -UWIFI_DEBUG_LOGGING -UBRIDGE_DEBUG -UGPS_NMEA_DEBUG -UCORE_DEBUG_LEVEL -UESPNOW_DEBUG_LOGGING -UDEBUG_RP2040_WIRE -UDEBUG_RP2040_SPI -UDEBUG_RP2040_CORE -UDEBUG_RP2040_PORT -URADIOLIB_DEBUG_SPI -UCFG_DEBUG -URADIOLIB_DEBUG_BASIC -URADIOLIB_DEBUG_PROTOCOL" + export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -UMESH_DEBUG -UMESH_PACKET_LOGGING -UBLE_DEBUG_LOGGING -UWIFI_DEBUG_LOGGING -UBRIDGE_DEBUG -UGPS_NMEA_DEBUG -UCORE_DEBUG_LEVEL -UESPNOW_DEBUG_LOGGING -UDEBUG_RP2040_WIRE -UDEBUG_RP2040_SPI -UDEBUG_RP2040_CORE -UDEBUG_RP2040_PORT -URADIOLIB_DEBUG_SPI -DCFG_DEBUG=0 -URADIOLIB_DEBUG_BASIC -URADIOLIB_DEBUG_PROTOCOL" fi } @@ -1021,6 +1035,25 @@ apply_firmware_profile_overrides() { esac } +append_firmware_version_suffix() { + local firmware_version=$1 + local suffix=${2#-} + + if [ -z "$suffix" ]; then + echo "$firmware_version" + return 0 + fi + + case "$firmware_version" in + *-"$suffix") + echo "$firmware_version" + ;; + *) + echo "${firmware_version}-${suffix}" + ;; + esac +} + print_build_flags() { local env_name=$1 @@ -1180,6 +1213,7 @@ build_firmware() { fi firmware_version_string="${firmware_version}-${commit_hash}" + firmware_version_string=$(append_firmware_version_suffix "$firmware_version_string" "$FIRMWARE_VERSION_SUFFIX") firmware_filename="${env_name}-${firmware_version_string}" if [ "${PLATFORMIO_BUILD_FLAGS+x}" ]; then @@ -1238,6 +1272,9 @@ get_bulk_build_resolver_name() { build-firmwares) echo "resolve_all_firmwares" ;; + build-firmwares-logging-matrix) + echo "resolve_all_firmwares" + ;; build-companion-firmwares) echo "resolve_companion_firmwares" ;; @@ -1356,6 +1393,42 @@ run_resolved_build_targets() { return "$build_status" } +run_logging_matrix_build_targets() { + local targets=("$@") + local original_meshdebug_override=$MESHDEBUG_OVERRIDE + local original_packet_logging_override=$PACKET_LOGGING_OVERRIDE + local original_firmware_version_suffix=$FIRMWARE_VERSION_SUFFIX + local build_status=0 + + if [ ${#targets[@]} -eq 0 ]; then + echo "No build targets resolved." + return 1 + fi + + echo "Building ${#targets[@]} target(s) with MESH_DEBUG=off and MESH_PACKET_LOGGING=off." + MESHDEBUG_OVERRIDE="off" + PACKET_LOGGING_OVERRIDE="off" + FIRMWARE_VERSION_SUFFIX="" + run_resolved_build_targets "${targets[@]}" + build_status=$? + + if [ "$build_status" -eq 0 ]; then + echo "Building ${#targets[@]} target(s) with MESH_DEBUG=on and MESH_PACKET_LOGGING=on." + echo "Logging-on artifacts use firmware version suffix: -logging" + MESHDEBUG_OVERRIDE="on" + PACKET_LOGGING_OVERRIDE="on" + FIRMWARE_VERSION_SUFFIX="logging" + run_resolved_build_targets "${targets[@]}" + build_status=$? + fi + + MESHDEBUG_OVERRIDE=$original_meshdebug_override + PACKET_LOGGING_OVERRIDE=$original_packet_logging_override + FIRMWARE_VERSION_SUFFIX=$original_firmware_version_suffix + + return "$build_status" +} + validate_command() { case "$1" in build-firmware) @@ -1383,6 +1456,11 @@ validate_command() { run_command() { # All build commands share execution after validation resolves their target list. + if is_logging_matrix_command "$1"; then + run_logging_matrix_build_targets "${RESOLVED_BUILD_TARGETS[@]}" + return $? + fi + if is_build_command "$1"; then run_resolved_build_targets "${RESOLVED_BUILD_TARGETS[@]}" return $? @@ -1419,7 +1497,11 @@ main() { fi prompt_for_build_mode - prompt_for_debug_build_settings + if is_logging_matrix_command "${SELECTED_COMMAND_ARGS[0]}"; then + echo "Skipping debug option prompts; this action builds logging off and logging on passes." + else + prompt_for_debug_build_settings + fi prompt_for_radio_build_settings prompt_for_firmware_profile_settings set -- "${SELECTED_COMMAND_ARGS[@]}" diff --git a/docs/cli_commands.md b/docs/cli_commands.md index 43ab975f..48b76245 100644 --- a/docs/cli_commands.md +++ b/docs/cli_commands.md @@ -1396,7 +1396,9 @@ set direct.retry.margin 10 **Explanation:** - Higher SNR uses faster coding rates. - Lower SNR uses more robust coding rates. -- CR6 is intentionally skipped. +- Repeater retry attempts escalate from the adaptive starting CR. CR4 starts as CR4, CR5, CR7, CR7, then CR8. CR5 starts as CR5, CR7, CR7, then CR8. CR7 gets two attempts, then CR8. +- Repeater adaptive CR selection intentionally skips CR6. +- Non-repeater retry packets start at the current radio CR and follow the same escalation pattern, clamped at CR8. With the normal CR5 radio setting this is CR5, CR7, CR7, then CR8. - `off` disables per-packet retry CR overrides and uses the current radio CR. - Direct path retry packets sent at CR4 or CR5 temporarily use a shorter 16-symbol preamble, then restore the radio's default preamble. - Unknown repeaters start at `+3.00 dB` for adaptive CR selection. diff --git a/docs/halo_keymind_settings.md b/docs/halo_keymind_settings.md index 863285eb..826753a2 100644 --- a/docs/halo_keymind_settings.md +++ b/docs/halo_keymind_settings.md @@ -177,15 +177,17 @@ Direct retry applies to direct-routed packets. A queued resend is canceled when | `direct.retry.count` | Maximum direct retry attempts after initial TX. | `get direct.retry.count`, `set direct.retry.count <1-15>` | `set direct.retry.count 15` | | `direct.retry.base` | Base wait in milliseconds before retry; non-TRACE paths under 6 remaining hops scale by `hops / 6`, TRACE paths under 16 by `hops / 16`. | `get direct.retry.base`, `set direct.retry.base <10-5000>` | `set direct.retry.base 175` | | `direct.retry.step` | Milliseconds added per retry attempt before the same short-path scaling. | `get direct.retry.step`, `set direct.retry.step <0-5000>` | `set direct.retry.step 100` | -| `direct.retry.cr` | Adaptive coding-rate thresholds for direct retry packets. Uses `CR4`, `CR5`, `CR7`, or `CR8`; `CR6` is never selected. | `get direct.retry.cr`, `set direct.retry.cr ,,,`, `set direct.retry.cr off` | `set direct.retry.cr 10.0,7.5,2.5,0` | +| `direct.retry.cr` | Adaptive coding-rate thresholds for repeater direct retry packets. Repeaters use `CR4`, `CR5`, `CR7`, or `CR8`, then escalate by attempt: CR4, CR5, CR7, CR7, then CR8 from a CR4 start; CR5, CR7, CR7, then CR8 from a CR5 start. Non-repeaters start at the current radio CR and follow the same escalation pattern, clamped at CR8. | `get direct.retry.cr`, `set direct.retry.cr ,,,`, `set direct.retry.cr off` | `set direct.retry.cr 10.0,7.5,2.5,0` | The default adaptive coding-rate profile is `10.0,7.5,2.5,2.5`. SNR `10.0 dB` and up uses `CR4`, `7.5 dB` and up uses `CR5`, `2.5 dB` and down uses `CR8`, and the middle band uses `CR7`. If no recent repeater table entry is available, retry packets use `CR5`. Use -`set direct.retry.cr off` to disable adaptive coding-rate overrides. If -adaptive selection chooses `CR4`, retries after the third attempt use -`CR5`. +`set direct.retry.cr off` to disable adaptive coding-rate overrides. Repeater +attempts escalate from the adaptive starting CR: `CR4`, `CR5`, `CR7`, `CR7`, +then `CR8` from a `CR4` start; `CR5`, `CR7`, `CR7`, then `CR8` from a `CR5` +start. Non-repeaters use the current radio CR as the first retry CR and follow +the same pattern up to `CR8`. Preset details: diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index 0d687cfd..1a048791 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -109,6 +109,7 @@ protected: int calcRxDelay(float score, uint32_t air_time) const override; uint32_t getRetransmitDelay(const mesh::Packet *packet) override; uint32_t getDirectRetransmitDelay(const mesh::Packet *packet) override; + uint8_t getDefaultTxCodingRate() const override { return _prefs.cr; } uint8_t getExtraAckTransmitCount() const override; bool filterRecvFloodPacket(mesh::Packet* packet) override; bool allowPacketForward(const mesh::Packet* packet) override; diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index fbbec54a..4486c886 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -913,7 +913,6 @@ bool MyMesh::allowDirectRetry(const mesh::Packet* packet, const uint8_t* next_ho } void MyMesh::configureDirectRetryPacket(mesh::Packet* retry, const mesh::Packet* original, uint8_t retry_attempt) { - (void)retry_attempt; int8_t snr_x4 = 12; // unknown repeaters start at +3.00 dB const SimpleMeshTables* tables = static_cast(getTables()); if (tables != NULL) { @@ -927,7 +926,7 @@ void MyMesh::configureDirectRetryPacket(mesh::Packet* retry, const mesh::Packet* } } - retry->tx_cr = getDirectRetryCodingRateForSNR(snr_x4); + retry->tx_cr = getDirectRetryCodingRateForAttempt(getDirectRetryCodingRateForSNR(snr_x4), retry_attempt); } uint32_t MyMesh::getDirectRetryEchoDelay(const mesh::Packet* packet) const { @@ -964,6 +963,26 @@ static void formatDirectRetryTarget(char* dest, size_t dest_len, const uint8_t* dest[hex_len] = 0; } +static uint8_t getRetryLogCodingRate(const mesh::Packet* packet, uint8_t default_cr) { + if (packet != NULL && packet->tx_cr >= 4 && packet->tx_cr <= 8) { + return packet->tx_cr; + } + return default_cr; +} + +static uint16_t getRetryLogPreambleLength(const mesh::Packet* packet, uint16_t default_preamble_len) { + if (packet == NULL || default_preamble_len <= 16 || !(packet->tx_cr == 4 || packet->tx_cr == 5)) { + return default_preamble_len; + } + + bool has_direct_path = packet->getPathHashCount() > 0 + || (packet->getPayloadType() == PAYLOAD_TYPE_TRACE && packet->payload_len > 9); + if (packet->isRouteDirect() && has_direct_path) { + return 16; + } + return default_preamble_len; +} + void MyMesh::onDirectRetryEvent(const char* event, const mesh::Packet* packet, uint32_t delay_millis, uint8_t retry_attempt, const uint8_t* target_hash, uint8_t target_hash_len, int16_t payload_type) { char type_label[8]; @@ -977,27 +996,33 @@ void MyMesh::onDirectRetryEvent(const char* event, const mesh::Packet* packet, u strcpy(type_label, "?"); } formatDirectRetryTarget(target_label, sizeof(target_label), target_hash, target_hash_len); + uint8_t log_cr = getRetryLogCodingRate(packet, getDefaultTxCodingRate()); + uint16_t log_preamble_len = getRetryLogPreambleLength(packet, radio_driver.getDefaultPreambleLength()); #if MESH_DEBUG - MESH_DEBUG_PRINTLN("direct retry %s attempt=%u delay=%lu type=%s route=%s target=%s", + MESH_DEBUG_PRINTLN("direct retry %s attempt=%u delay=%lu type=%s route=%s target=%s cr=%u preamble_len=%u", event ? event : "?", (uint32_t)retry_attempt, (unsigned long)delay_millis, type_label, route_label, - target_label); + target_label, + (uint32_t)log_cr, + (uint32_t)log_preamble_len); #endif if (_logging) { File f = openAppend(PACKET_LOG_FILE); if (f) { f.print(getLogDateTime()); - f.printf(": direct retry %s attempt=%u delay=%lu type=%s route=%s target=%s\n", + f.printf(": direct retry %s attempt=%u delay=%lu type=%s route=%s target=%s cr=%u preamble_len=%u\n", event ? event : "?", (uint32_t)retry_attempt, (unsigned long)delay_millis, type_label, route_label, - target_label); + target_label, + (uint32_t)log_cr, + (uint32_t)log_preamble_len); f.close(); } } @@ -1497,9 +1522,10 @@ void MyMesh::onFloodRetryEvent(const char* event, const mesh::Packet* packet, ui refreshFloodRetryHeardRecent(packet); snprintf(heard_suffix, sizeof(heard_suffix), ", heard=%s", heard_log); } + uint8_t log_cr = getRetryLogCodingRate(packet, getDefaultTxCodingRate()); + uint16_t log_preamble_len = getRetryLogPreambleLength(packet, radio_driver.getDefaultPreambleLength()); - MESH_DEBUG_PRINTLN("%s flood retry %s (retry=%u, type=%d, route=%s, payload_len=%d, hop=%u, path=%s%s, %s=%lu)", - getLogDateTime(), + MESH_DEBUG_PRINTLN("flood retry %s (retry=%u, type=%d, route=%s, payload_len=%d, hop=%u, path=%s%s, %s=%lu, cr=%u, preamble_len=%u)", event, (unsigned int)retry_attempt, (uint32_t)packet->getPayloadType(), @@ -1509,13 +1535,15 @@ void MyMesh::onFloodRetryEvent(const char* event, const mesh::Packet* packet, ui path_log, heard_suffix, time_label, - (unsigned long)delay_millis); + (unsigned long)delay_millis, + (uint32_t)log_cr, + (uint32_t)log_preamble_len); if (_logging) { File f = openAppend(PACKET_LOG_FILE); if (f) { f.print(getLogDateTime()); - f.printf(": FLOOD RETRY %s (retry=%u, type=%d, route=%s, payload_len=%d, hop=%u, path=%s%s, %s=%lu)\n", + f.printf(": FLOOD RETRY %s (retry=%u, type=%d, route=%s, payload_len=%d, hop=%u, path=%s%s, %s=%lu, cr=%u, preamble_len=%u)\n", event, (unsigned int)retry_attempt, (uint32_t)packet->getPayloadType(), @@ -1525,7 +1553,9 @@ void MyMesh::onFloodRetryEvent(const char* event, const mesh::Packet* packet, ui path_log, heard_suffix, time_label, - (unsigned long)delay_millis); + (unsigned long)delay_millis, + (uint32_t)log_cr, + (uint32_t)log_preamble_len); f.close(); } } diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index dc0580d5..6e7a2a87 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -5,6 +5,10 @@ #include #include +#ifndef MESH_ENABLE_RECENT_REPEATERS + #define MESH_ENABLE_RECENT_REPEATERS 1 +#endif + #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) #include #elif defined(RP2040_PLATFORM) diff --git a/examples/simple_room_server/MyMesh.h b/examples/simple_room_server/MyMesh.h index 98a79bb8..c57b2056 100644 --- a/examples/simple_room_server/MyMesh.h +++ b/examples/simple_room_server/MyMesh.h @@ -153,6 +153,9 @@ protected: uint8_t getExtraAckTransmitCount() const override { return _prefs.multi_acks; } + uint8_t getDefaultTxCodingRate() const override { + return set_radio_at == 0 && revert_radio_at != 0 ? pending_cr : _prefs.cr; + } bool filterRecvFloodPacket(mesh::Packet* pkt) override; diff --git a/examples/simple_secure_chat/main.cpp b/examples/simple_secure_chat/main.cpp index d93810ed..ba8d03e9 100644 --- a/examples/simple_secure_chat/main.cpp +++ b/examples/simple_secure_chat/main.cpp @@ -202,6 +202,10 @@ protected: return true; } + uint8_t getDefaultTxCodingRate() const override { + return LORA_CR; + } + void onDiscoveredContact(ContactInfo& contact, bool is_new, uint8_t path_len, const uint8_t* path) override { // TODO: if not in favs, prompt to add as fav(?) diff --git a/examples/simple_sensor/SensorMesh.h b/examples/simple_sensor/SensorMesh.h index 1d65b877..779b9bc0 100644 --- a/examples/simple_sensor/SensorMesh.h +++ b/examples/simple_sensor/SensorMesh.h @@ -122,6 +122,9 @@ protected: int getInterferenceThreshold() const override; bool getCADEnabled() const override; int getAGCResetInterval() const override; + uint8_t getDefaultTxCodingRate() const override { + return set_radio_at == 0 && revert_radio_at != 0 ? pending_cr : _prefs.cr; + } void onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, const mesh::Identity& sender, uint8_t* data, size_t len) override; int searchPeersByHash(const uint8_t* hash) override; void getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) override; diff --git a/src/Mesh.cpp b/src/Mesh.cpp index 45eb031f..eaff68c0 100644 --- a/src/Mesh.cpp +++ b/src/Mesh.cpp @@ -5,8 +5,13 @@ 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 uint8_t FLOOD_RETRY_MAX_ATTEMPTS_DEFAULT = 3; +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; static uint8_t decodeTraceHashSize(uint8_t flags, uint8_t route_bytes) { uint8_t code = flags & 0x03; @@ -63,6 +68,74 @@ 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; + } + + if (retry_attempt < 1) { + retry_attempt = 1; + } + + if (start_cr >= 8) { + return 8; + } + if (start_cr >= 7) { + return retry_attempt <= 2 ? 7 : 8; + } + if (start_cr <= 4) { + if (retry_attempt == 1) return 4; + if (retry_attempt == 2) return 5; + if (retry_attempt <= 4) return 7; + return 8; + } + + if (retry_attempt == 1) return start_cr; + if (retry_attempt <= 3) return 7; + return 8; +} + +void Mesh::configureDirectRetryPacket(Packet* retry, const Packet* original, uint8_t retry_attempt) { + (void)original; + if (retry == NULL) { + return; + } + + uint8_t default_cr = getDefaultTxCodingRate(); + if (default_cr < 4 || default_cr > 8) { + return; + } + + retry->tx_cr = getDirectRetryCodingRateForAttempt(default_cr, retry_attempt); +} + void Mesh::begin() { for (int i = 0; i < MAX_DIRECT_RETRY_SLOTS; i++) { _direct_retries[i].packet = NULL; @@ -181,30 +254,38 @@ uint32_t Mesh::getDirectRetransmitDelay(const Packet* packet) { return 0; // by default, no delay } bool Mesh::allowDirectRetry(const Packet* packet, const uint8_t* next_hop_hash, uint8_t next_hop_hash_len) const { - return false; + (void)packet; + (void)next_hop_hash; + (void)next_hop_hash_len; + return true; } uint32_t Mesh::getDirectRetryEchoDelay(const Packet* packet) const { - // Keep the base fallback aligned with the repeater's minimum retry wait. - return 200; + (void)packet; + return DIRECT_RETRY_BASE_MS_DEFAULT; } 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) { 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); + uint32_t delay_ms = base + ((uint32_t)attempt_idx * DIRECT_RETRY_STEP_MS_DEFAULT); + return scaleDirectRetryDelayForPath(packet, delay_ms); } bool Mesh::allowFloodRetry(const Packet* packet) const { + (void)packet; return true; } bool Mesh::hasFloodRetryTargetPrefix(const Packet* packet) const { + (void)packet; return false; } uint8_t Mesh::getFloodRetryMaxPathLength(const Packet* packet) const { - return 2; + (void)packet; + return FLOOD_RETRY_MAX_PATH_DEFAULT; } uint8_t Mesh::getFloodRetryMaxAttempts(const Packet* packet) const { + (void)packet; return FLOOD_RETRY_MAX_ATTEMPTS_DEFAULT; } uint32_t Mesh::getFloodRetryAttemptDelay(const Packet* packet, uint8_t attempt_idx) { @@ -892,9 +973,6 @@ bool Mesh::getDirectRetryTarget(const Packet* packet, const uint8_t*& next_hop_h if (offset + hash_size > route_bytes) { return false; } - if (offset + (2 * hash_size) > route_bytes) { - return false; // no downstream repeater means there will be no forward echo to overhear. - } next_hop_hash = &packet->payload[9 + offset]; next_hop_hash_len = hash_size; @@ -1514,6 +1592,7 @@ void Mesh::sendFlood(Packet* packet, uint32_t delay_millis, uint8_t path_hash_si } else { pri = 1; } + maybeScheduleFloodRetry(packet, pri); sendPacket(packet, pri, delay_millis); } @@ -1543,6 +1622,7 @@ void Mesh::sendFlood(Packet* packet, uint16_t* transport_codes, uint32_t delay_m } else { pri = 1; } + maybeScheduleFloodRetry(packet, pri); sendPacket(packet, pri, delay_millis); } diff --git a/src/Mesh.h b/src/Mesh.h index c306a30f..1dfbde6e 100644 --- a/src/Mesh.h +++ b/src/Mesh.h @@ -134,7 +134,7 @@ protected: virtual uint32_t getDirectRetransmitDelay(const Packet* packet); /** - * \brief Decide whether a DIRECT packet should get one delayed retry if the next hop echo is not overheard. + * \brief Decide whether a DIRECT packet should retry if the next hop echo is not overheard. * Sub-classes can use recent repeater or other link-quality data to opt in selectively. */ virtual bool allowDirectRetry(const Packet* packet, const uint8_t* next_hop_hash, uint8_t next_hop_hash_len) const; @@ -145,7 +145,7 @@ protected: virtual bool maybeShortCircuitDirect(Packet* packet) { return false; } /** - * \returns milliseconds to wait for the next-hop echo before queueing one retry of the DIRECT packet. + * \returns milliseconds to wait for the next-hop echo before queueing a retry of the DIRECT packet. */ virtual uint32_t getDirectRetryEchoDelay(const Packet* packet) const; @@ -216,10 +216,15 @@ protected: */ virtual void onDirectRetrySucceeded(const uint8_t* next_hop_hash, uint8_t next_hop_hash_len, int8_t snr_x4) { } + /** + * \returns Coding rate to use for a retry attempt, starting from the current/adaptive CR. + */ + static uint8_t getDirectRetryCodingRateForAttempt(uint8_t start_cr, uint8_t retry_attempt); + /** * \brief Optional hook to set local-only transmit options on a retry packet before it is queued. */ - virtual void configureDirectRetryPacket(Packet* retry, const Packet* original, uint8_t retry_attempt) { } + virtual void configureDirectRetryPacket(Packet* retry, const Packet* original, uint8_t retry_attempt); /** * \brief Perform search of local DB of peers/contacts. diff --git a/src/helpers/SimpleMeshTables.h b/src/helpers/SimpleMeshTables.h index f2420473..d7b0477f 100644 --- a/src/helpers/SimpleMeshTables.h +++ b/src/helpers/SimpleMeshTables.h @@ -10,9 +10,15 @@ #endif #define MAX_PACKET_HASHES (128+32) +#ifndef MESH_ENABLE_RECENT_REPEATERS + #define MESH_ENABLE_RECENT_REPEATERS 0 +#endif #ifndef MAX_RECENT_REPEATERS - // Platform defaults. Can be overridden with -D MAX_RECENT_REPEATERS=. - #if defined(ESP32) || defined(ESP32_PLATFORM) + // Recent repeater history is only needed by repeater firmware. Other roles + // use unconditional retry decisions and skip this RAM-heavy cache. + #if !MESH_ENABLE_RECENT_REPEATERS + #define MAX_RECENT_REPEATERS 0 + #elif defined(ESP32) || defined(ESP32_PLATFORM) #define MAX_RECENT_REPEATERS 2048 #elif defined(NRF52_PLATFORM) #define MAX_RECENT_REPEATERS 512 @@ -20,6 +26,7 @@ #define MAX_RECENT_REPEATERS 64 #endif #endif +#define RECENT_REPEATER_STORAGE_SLOTS (MAX_RECENT_REPEATERS > 0 ? MAX_RECENT_REPEATERS : 1) #define MAX_ROUTE_HASH_BYTES 3 class SimpleMeshTables : public mesh::MeshTables { @@ -36,7 +43,7 @@ private: uint8_t _hashes[MAX_PACKET_HASHES*MAX_HASH_SIZE]; int _next_idx; uint32_t _direct_dups, _flood_dups; - RecentRepeaterInfo _recent_repeaters[MAX_RECENT_REPEATERS]; + RecentRepeaterInfo _recent_repeaters[RECENT_REPEATER_STORAGE_SLOTS]; bool hasSeenHash(const uint8_t* hash) const { const uint8_t* sp = _hashes; @@ -124,6 +131,10 @@ private: } void recordRecentRepeater(const mesh::Packet* packet) { + if (MAX_RECENT_REPEATERS == 0) { + return; + } + uint8_t prefix[MAX_ROUTE_HASH_BYTES] = {0}; uint8_t prefix_len = 0; if (!extractRecentRepeater(packet, prefix, prefix_len) || prefix_len == 0) { @@ -202,6 +213,9 @@ public: bool snr_locked = false, bool bypass_allow_filter = false) { (void)snr_locked; (void)bypass_allow_filter; + if (MAX_RECENT_REPEATERS == 0) { + return false; + } if (prefix == NULL || prefix_len == 0) { return false; } @@ -262,6 +276,9 @@ public: return true; } bool decrementRecentRepeaterSnrX4(const uint8_t* prefix, uint8_t prefix_len, uint8_t amount_x4 = 1) { + if (MAX_RECENT_REPEATERS == 0) { + return false; + } if (prefix == NULL || prefix_len == 0 || amount_x4 == 0) { return false; } @@ -284,6 +301,9 @@ public: return false; } int getRecentRepeaterCount() const { + if (MAX_RECENT_REPEATERS == 0) { + return 0; + } int count = 0; for (int i = 0; i < MAX_RECENT_REPEATERS; i++) { if (_recent_repeaters[i].prefix_len > 0) { @@ -293,6 +313,9 @@ public: return count; } const RecentRepeaterInfo* getRecentRepeaterBySortedIdx(int idx_wanted) const { + if (MAX_RECENT_REPEATERS == 0) { + return NULL; + } if (idx_wanted < 0) { return NULL; } @@ -325,6 +348,9 @@ public: } const RecentRepeaterInfo* findRecentRepeaterByHash(const uint8_t* hash, uint8_t hash_len) const { + if (MAX_RECENT_REPEATERS == 0) { + return NULL; + } if (hash == NULL || hash_len == 0) { return NULL; }