From 9a8609097825980ea74e72409a345f6ea89cef44 Mon Sep 17 00:00:00 2001 From: mikecarper Date: Fri, 8 May 2026 17:04:47 -0700 Subject: [PATCH] Update direct retry CR controls --- docs/cli_commands.md | 8 +-- examples/simple_repeater/MyMesh.cpp | 34 +++++++++--- src/helpers/CommonCLI.cpp | 84 ++++++++++++++++++++--------- src/helpers/CommonCLI.h | 2 + 4 files changed, 92 insertions(+), 36 deletions(-) diff --git a/docs/cli_commands.md b/docs/cli_commands.md index f9dcc16a..ab6af502 100644 --- a/docs/cli_commands.md +++ b/docs/cli_commands.md @@ -559,18 +559,18 @@ This document provides an overview of CLI commands that can be sent to MeshCore #### View or change adaptive coding rate for direct retry packets **Usage:** - `get direct.retry.cr` -- `set direct.retry.cr ,,` -- `set direct.retry.cr ,,,` +- `set direct.retry.cr ,,,` +- `set direct.retry.cr off` **Parameters:** - `cr4_min`: SNR in dB where retry packets use `CR4` - `cr5_min`: SNR in dB where retry packets use `CR5` +- `cr7_min`: SNR in dB where retry packets use `CR7` - `cr8_max`: SNR in dB where retry packets use `CR8` -- `low`: optional repeated low boundary; both low values must match **Default:** `10.0,7.5,2.5,2.5` -**Note:** DM retry packets with a recent repeater table entry use that entry's SNR to pick a local transmit coding rate. With the default, SNR `10.0 dB` and up uses `CR4`, SNR `7.5 dB` and up uses `CR5`, SNR `2.5 dB` and down uses `CR8`, and the middle band uses `CR7`. `CR6` is never selected. The shorter form `set direct.retry.cr 10.0,7.5,2.5` is equivalent to `set direct.retry.cr 10.0,7.5,2.5,2.5`. +**Note:** DM retry packets use the next-hop SNR from a recent repeater table entry to pick a local transmit coding rate; if no recent entry is available, retry packets use `CR5`. With the default, SNR `10.0 dB` and up uses `CR4`, SNR `7.5 dB` and up uses `CR5`, SNR `2.5 dB` and down uses `CR8`, and the middle band uses `CR7`. `CR6` is never selected. Use `set direct.retry.cr off` to disable adaptive coding-rate overrides. If adaptive selection chooses `CR4`, retries after the third attempt use `CR5`. --- diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 24e5c81d..89bd6020 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -744,7 +744,8 @@ void MyMesh::onDirectRetryEvent(const char* event, const mesh::Packet* packet, u time_label = "echo_ms"; } - 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)", + uint8_t log_cr = (packet->tx_cr >= 4 && packet->tx_cr <= 8) ? packet->tx_cr : active_cr; + 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, cr=%u, %s=%lu)", getLogDateTime(), event, (unsigned int)retry_attempt, @@ -755,6 +756,7 @@ void MyMesh::onDirectRetryEvent(const char* event, const mesh::Packet* packet, u next_hop, snr_pkt_text, snr_table_text, + (unsigned int)log_cr, time_label, (unsigned long)delay_millis); @@ -762,7 +764,7 @@ void MyMesh::onDirectRetryEvent(const char* event, const mesh::Packet* packet, u File f = openAppend(PACKET_LOG_FILE); if (f) { f.print(getLogDateTime()); - 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", + f.printf(": DIRECT RETRY %s (retry=%u, type=%d, route=%s, payload_len=%d, hop=%s, next_hop=%s, pkt=%s, tbl=%s, cr=%u, %s=%lu)\n", event, (unsigned int)retry_attempt, (uint32_t)packet->getPayloadType(), @@ -772,6 +774,7 @@ void MyMesh::onDirectRetryEvent(const char* event, const mesh::Packet* packet, u next_hop, snr_pkt_text, snr_table_text, + (unsigned int)log_cr, time_label, (unsigned long)delay_millis); f.close(); @@ -847,6 +850,12 @@ bool MyMesh::allowDirectRetry(const mesh::Packet* packet, const uint8_t* next_ho return true; } uint8_t MyMesh::getDirectRetryCodingRateForSNR(int8_t snr_x4) const { + if (_prefs.direct_retry_cr4_snr_x4 == 0 + && _prefs.direct_retry_cr5_snr_x4 == 0 + && _prefs.direct_retry_cr7_snr_x4 == 0 + && _prefs.direct_retry_cr8_snr_x4 == 0) { + return 0; + } if (snr_x4 >= _prefs.direct_retry_cr4_snr_x4) { return 4; } @@ -856,11 +865,13 @@ uint8_t MyMesh::getDirectRetryCodingRateForSNR(int8_t snr_x4) const { if (snr_x4 <= _prefs.direct_retry_cr8_snr_x4) { return 8; } + if (snr_x4 >= _prefs.direct_retry_cr7_snr_x4) { + return 7; + } return 7; } void MyMesh::configureDirectRetryPacket(mesh::Packet* retry, const mesh::Packet* original, uint8_t retry_attempt) { (void) original; - (void) retry_attempt; if (retry == NULL || !retry->isRouteDirect()) { return; @@ -873,6 +884,7 @@ void MyMesh::configureDirectRetryPacket(mesh::Packet* retry, const mesh::Packet* case PAYLOAD_TYPE_RESPONSE: case PAYLOAD_TYPE_TXT_MSG: case PAYLOAD_TYPE_ANON_REQ: + case PAYLOAD_TYPE_TRACE: case PAYLOAD_TYPE_MULTIPART: break; default: @@ -885,13 +897,20 @@ void MyMesh::configureDirectRetryPacket(mesh::Packet* retry, const mesh::Packet* return; } - const auto* recent = ((const SimpleMeshTables *)getTables())->findRecentRepeaterByHash(prefix, prefix_len); - if (recent == NULL) { + if (_prefs.direct_retry_cr4_snr_x4 == 0 + && _prefs.direct_retry_cr5_snr_x4 == 0 + && _prefs.direct_retry_cr7_snr_x4 == 0 + && _prefs.direct_retry_cr8_snr_x4 == 0) { return; } - uint8_t retry_cr = getDirectRetryCodingRateForSNR(recent->snr_x4); - if (retry_cr >= 4 && retry_cr <= 8 && retry_cr != active_cr) { + const auto* recent = ((const SimpleMeshTables *)getTables())->findRecentRepeaterByHash(prefix, prefix_len); + uint8_t retry_cr = (recent != NULL) ? getDirectRetryCodingRateForSNR(recent->snr_x4) : 5; + if (retry_cr == 4 && retry_attempt > 3) { + retry_cr = 5; + } + + if (retry_cr >= 4 && retry_cr <= 8 && retry_cr != 6 && retry_cr != active_cr) { retry->tx_cr = retry_cr; } } @@ -1294,6 +1313,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc _prefs.direct_retry_preset = DIRECT_RETRY_PRESET_ROOFTOP; _prefs.direct_retry_cr4_snr_x4 = DIRECT_RETRY_CR4_MIN_SNR_X4_DEFAULT; _prefs.direct_retry_cr5_snr_x4 = DIRECT_RETRY_CR5_MIN_SNR_X4_DEFAULT; + _prefs.direct_retry_cr7_snr_x4 = DIRECT_RETRY_CR7_MIN_SNR_X4_DEFAULT; _prefs.direct_retry_cr8_snr_x4 = DIRECT_RETRY_CR8_MAX_SNR_X4_DEFAULT; StrHelper::strncpy(_prefs.node_name, ADVERT_NAME, sizeof(_prefs.node_name)); _prefs.node_lat = ADVERT_LAT; diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 42b41f29..87126629 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -92,29 +92,53 @@ static float directRetryCrX4ToDb(int8_t snr_x4) { static void setDirectRetryCrDefaults(NodePrefs* prefs) { prefs->direct_retry_cr4_snr_x4 = DIRECT_RETRY_CR4_MIN_SNR_X4_DEFAULT; prefs->direct_retry_cr5_snr_x4 = DIRECT_RETRY_CR5_MIN_SNR_X4_DEFAULT; + prefs->direct_retry_cr7_snr_x4 = DIRECT_RETRY_CR7_MIN_SNR_X4_DEFAULT; prefs->direct_retry_cr8_snr_x4 = DIRECT_RETRY_CR8_MAX_SNR_X4_DEFAULT; } -static bool directRetryCrThresholdsAreValid(int8_t cr4_snr_x4, int8_t cr5_snr_x4, int8_t cr8_snr_x4) { - return (cr4_snr_x4 != 0 || cr5_snr_x4 != 0 || cr8_snr_x4 != 0) - && cr4_snr_x4 >= cr5_snr_x4 - && cr5_snr_x4 >= cr8_snr_x4; +static void setDirectRetryCrOff(NodePrefs* prefs) { + prefs->direct_retry_cr4_snr_x4 = 0; + prefs->direct_retry_cr5_snr_x4 = 0; + prefs->direct_retry_cr7_snr_x4 = 0; + prefs->direct_retry_cr8_snr_x4 = 0; +} + +static bool directRetryCrThresholdsAreOff(int8_t cr4_snr_x4, int8_t cr5_snr_x4, int8_t cr7_snr_x4, int8_t cr8_snr_x4) { + return cr4_snr_x4 == 0 && cr5_snr_x4 == 0 && cr7_snr_x4 == 0 && cr8_snr_x4 == 0; +} + +static bool directRetryCrThresholdsAreValid(int8_t cr4_snr_x4, int8_t cr5_snr_x4, int8_t cr7_snr_x4, int8_t cr8_snr_x4) { + return directRetryCrThresholdsAreOff(cr4_snr_x4, cr5_snr_x4, cr7_snr_x4, cr8_snr_x4) + || ((cr4_snr_x4 != 0 || cr5_snr_x4 != 0 || cr7_snr_x4 != 0 || cr8_snr_x4 != 0) + && cr4_snr_x4 >= cr5_snr_x4 + && cr5_snr_x4 >= cr7_snr_x4 + && cr7_snr_x4 >= cr8_snr_x4); } static void sanitizeDirectRetryCrThresholds(NodePrefs* prefs) { if (!directRetryCrThresholdsAreValid(prefs->direct_retry_cr4_snr_x4, prefs->direct_retry_cr5_snr_x4, + prefs->direct_retry_cr7_snr_x4, prefs->direct_retry_cr8_snr_x4)) { setDirectRetryCrDefaults(prefs); } } static void formatDirectRetryCrThresholds(const NodePrefs* prefs, char* reply) { - char cr4[12], cr5[12], cr8[12]; + if (directRetryCrThresholdsAreOff(prefs->direct_retry_cr4_snr_x4, + prefs->direct_retry_cr5_snr_x4, + prefs->direct_retry_cr7_snr_x4, + prefs->direct_retry_cr8_snr_x4)) { + strcpy(reply, "> off"); + return; + } + + char cr4[12], cr5[12], cr7[12], cr8[12]; strcpy(cr4, StrHelper::ftoa(directRetryCrX4ToDb(prefs->direct_retry_cr4_snr_x4))); strcpy(cr5, StrHelper::ftoa(directRetryCrX4ToDb(prefs->direct_retry_cr5_snr_x4))); + strcpy(cr7, StrHelper::ftoa(directRetryCrX4ToDb(prefs->direct_retry_cr7_snr_x4))); strcpy(cr8, StrHelper::ftoa(directRetryCrX4ToDb(prefs->direct_retry_cr8_snr_x4))); - sprintf(reply, "> %s,%s,%s,%s", cr4, cr5, cr8, cr8); + sprintf(reply, "> %s,%s,%s,%s", cr4, cr5, cr7, cr8); } static uint16_t directRetryPresetStepDefault(uint8_t preset) { @@ -198,35 +222,36 @@ static bool parseDirectRetryCrThresholds(char* value, NodePrefs* prefs) { if (value == NULL || prefs == NULL) { return false; } + if (strcmp(value, "off") == 0) { + setDirectRetryCrOff(prefs); + return true; + } - const char* parts[4]; - int num = mesh::Utils::parseTextParts(value, parts, 4); - if (num != 3 && num != 4) { + const char* parts[5]; + int num = mesh::Utils::parseTextParts(value, parts, 5); + if (num != 4) { return false; } int8_t cr4_snr_x4; int8_t cr5_snr_x4; + int8_t cr7_snr_x4; int8_t cr8_snr_x4; if (!parseDirectRetryCrDb(parts[0], cr4_snr_x4) || !parseDirectRetryCrDb(parts[1], cr5_snr_x4) - || !parseDirectRetryCrDb(parts[num == 4 ? 3 : 2], cr8_snr_x4)) { + || !parseDirectRetryCrDb(parts[2], cr7_snr_x4) + || !parseDirectRetryCrDb(parts[3], cr8_snr_x4)) { return false; } - if (num == 4) { - int8_t repeated_low_snr_x4; - if (!parseDirectRetryCrDb(parts[2], repeated_low_snr_x4) || repeated_low_snr_x4 != cr8_snr_x4) { - return false; - } - } - - if (!directRetryCrThresholdsAreValid(cr4_snr_x4, cr5_snr_x4, cr8_snr_x4)) { + if (directRetryCrThresholdsAreOff(cr4_snr_x4, cr5_snr_x4, cr7_snr_x4, cr8_snr_x4) + || !directRetryCrThresholdsAreValid(cr4_snr_x4, cr5_snr_x4, cr7_snr_x4, cr8_snr_x4)) { return false; } prefs->direct_retry_cr4_snr_x4 = cr4_snr_x4; prefs->direct_retry_cr5_snr_x4 = cr5_snr_x4; + prefs->direct_retry_cr7_snr_x4 = cr7_snr_x4; prefs->direct_retry_cr8_snr_x4 = cr8_snr_x4; return true; } @@ -323,8 +348,10 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { sizeof(_prefs->direct_retry_cr4_snr_x4)); // 300 retry_cr_read += file.read((uint8_t *)&_prefs->direct_retry_cr5_snr_x4, sizeof(_prefs->direct_retry_cr5_snr_x4)); // 301 + retry_cr_read += file.read((uint8_t *)&_prefs->direct_retry_cr7_snr_x4, + sizeof(_prefs->direct_retry_cr7_snr_x4)); // 302 retry_cr_read += file.read((uint8_t *)&_prefs->direct_retry_cr8_snr_x4, - sizeof(_prefs->direct_retry_cr8_snr_x4)); // 302 + sizeof(_prefs->direct_retry_cr8_snr_x4)); // 303 // 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) @@ -332,7 +359,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: 303 + // next: 304 // sanitise bad pref values _prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0, 20.0f); @@ -386,8 +413,14 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { } else { _prefs->direct_retry_step_ms = constrain(_prefs->direct_retry_step_ms, DIRECT_RETRY_STEP_MS_MIN, DIRECT_RETRY_STEP_MS_MAX); } - if (retry_cr_read != sizeof(_prefs->direct_retry_cr4_snr_x4) + if (retry_cr_read == sizeof(_prefs->direct_retry_cr4_snr_x4) + sizeof(_prefs->direct_retry_cr5_snr_x4) + + sizeof(_prefs->direct_retry_cr7_snr_x4)) { + _prefs->direct_retry_cr8_snr_x4 = _prefs->direct_retry_cr7_snr_x4; + sanitizeDirectRetryCrThresholds(_prefs); + } else if (retry_cr_read != sizeof(_prefs->direct_retry_cr4_snr_x4) + + sizeof(_prefs->direct_retry_cr5_snr_x4) + + sizeof(_prefs->direct_retry_cr7_snr_x4) + sizeof(_prefs->direct_retry_cr8_snr_x4)) { setDirectRetryCrDefaults(_prefs); } else { @@ -467,8 +500,9 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) { file.write((uint8_t *)&_prefs->direct_retry_step_ms, sizeof(_prefs->direct_retry_step_ms)); // 298 file.write((uint8_t *)&_prefs->direct_retry_cr4_snr_x4, sizeof(_prefs->direct_retry_cr4_snr_x4)); // 300 file.write((uint8_t *)&_prefs->direct_retry_cr5_snr_x4, sizeof(_prefs->direct_retry_cr5_snr_x4)); // 301 - file.write((uint8_t *)&_prefs->direct_retry_cr8_snr_x4, sizeof(_prefs->direct_retry_cr8_snr_x4)); // 302 - // next: 303 + file.write((uint8_t *)&_prefs->direct_retry_cr7_snr_x4, sizeof(_prefs->direct_retry_cr7_snr_x4)); // 302 + file.write((uint8_t *)&_prefs->direct_retry_cr8_snr_x4, sizeof(_prefs->direct_retry_cr8_snr_x4)); // 303 + // next: 304 file.close(); } @@ -954,7 +988,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* re savePrefs(); formatDirectRetryCrThresholds(_prefs, reply); } else { - strcpy(reply, "Error, expected cr4,cr5,cr8 or cr4,cr5,low,low"); + strcpy(reply, "Error, expected off or cr4,cr5,cr7,cr8"); } } else if (memcmp(config, "owner.info ", 11) == 0) { config += 11; @@ -1516,7 +1550,7 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep savePrefs(); formatDirectRetryCrThresholds(_prefs, reply); } else { - strcpy(reply, "Error, expected cr4,cr5,cr8 or cr4,cr5,low,low"); + strcpy(reply, "Error, expected off or cr4,cr5,cr7,cr8"); } } else if (memcmp(config, "owner.info ", 11) == 0) { config += 11; diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index 43e8e6d7..92401b38 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -40,6 +40,7 @@ #define DIRECT_RETRY_CR4_MIN_SNR_X4_DEFAULT 40 // 10.0 dB and up => CR4 #define DIRECT_RETRY_CR5_MIN_SNR_X4_DEFAULT 30 // 7.5 dB and up => CR5 +#define DIRECT_RETRY_CR7_MIN_SNR_X4_DEFAULT 10 // 2.5 dB and up => CR7 #define DIRECT_RETRY_CR8_MAX_SNR_X4_DEFAULT 10 // 2.5 dB and down => CR8 #define DIRECT_RETRY_CR_SNR_X4_MIN -128 #define DIRECT_RETRY_CR_SNR_X4_MAX 127 @@ -96,6 +97,7 @@ struct NodePrefs { // persisted to file uint16_t direct_retry_step_ms; int8_t direct_retry_cr4_snr_x4; int8_t direct_retry_cr5_snr_x4; + int8_t direct_retry_cr7_snr_x4; int8_t direct_retry_cr8_snr_x4; };