From cb5e2e7c3d49ef921a63ce446918958ed46ff12d Mon Sep 17 00:00:00 2001 From: mikecarper Date: Mon, 15 Jun 2026 17:42:53 -0700 Subject: [PATCH] Document direct retry CLI --- docs/cli_commands.md | 224 ++++++++++++++++++++++++++++ examples/simple_repeater/MyMesh.cpp | 7 +- src/helpers/CommonCLI.cpp | 30 +++- src/helpers/CommonCLI.h | 1 + 4 files changed, 255 insertions(+), 7 deletions(-) diff --git a/docs/cli_commands.md b/docs/cli_commands.md index c06f5e12..2d31a18b 100644 --- a/docs/cli_commands.md +++ b/docs/cli_commands.md @@ -950,6 +950,230 @@ region save - Enables flooding for all child regions automatically - Useful for global networks with specific regional rules +--- +### Direct Retry + +Direct retry resends direct-routed packets when the downstream echo is not heard. It applies to direct messages and TRACE packets. It does not change ACK handling. + +#### View or change direct retry state +**Usage:** +- `get direct.retry` +- `set direct.retry ` + +**Parameters:** +- `state`: `on`|`off` + +**Default:** `on` + +**Examples:** +``` +get direct.retry +set direct.retry on +set direct.retry off +``` + +--- + +#### View or apply a direct retry preset +**Usage:** +- `get retry.preset` +- `set retry.preset ` + +**Parameters:** +- `preset`: `infra`|`rooftop`|`mobile` + +**Notes:** +- `infra`: fewer, slower retries for stable fixed infrastructure. +- `rooftop`: default long retry window for weak rooftop links. +- `mobile`: long retry count with shorter spacing for moving or changing links. +- Changing `direct.retry.count`, `direct.retry.base`, `direct.retry.step`, or `direct.retry.margin` makes the preset report as `custom`. + +**Examples:** +``` +get retry.preset +set retry.preset infra +set retry.preset rooftop +set retry.preset mobile +``` + +--- + +#### View or change direct retry count +**Usage:** +- `get direct.retry.count` +- `set direct.retry.count ` + +**Parameters:** +- `count`: Maximum retry attempts after the original send, from `1` to `15`. + +**Default:** `15` with the `rooftop` preset + +**Examples:** +``` +get direct.retry.count +set direct.retry.count 1 +set direct.retry.count 4 +set direct.retry.count 15 +``` + +--- + +#### View or change direct retry base delay +**Usage:** +- `get direct.retry.base` +- `set direct.retry.base ` + +**Parameters:** +- `ms`: First retry wait in milliseconds, from `10` to `5000`. + +**Default:** `175` with the `rooftop` preset + +**Explanation:** +- The first retry waits `base` milliseconds after the failed echo window. +- Larger values reduce channel pressure and give slow repeaters more time. +- Smaller values recover faster but create tighter retry bursts. + +**Examples:** +``` +get direct.retry.base +set direct.retry.base 175 +set direct.retry.base 275 +set direct.retry.base 500 +``` + +--- + +#### View or change direct retry step delay +**Usage:** +- `get direct.retry.step` +- `set direct.retry.step ` + +**Parameters:** +- `ms`: Extra milliseconds added for each subsequent retry, from `0` to `5000`. + +**Default:** `100` with the `rooftop` preset + +**Explanation:** +- Retry delay is `base + attempt_index * step`. +- With `base=175` and `step=100`, retries wait about `175`, `275`, `375`, `475` ms, and so on. +- `step=0` keeps every retry at the same delay. +- Larger steps spread retries over time and are safer on busy channels. + +**Examples:** +``` +get direct.retry.step +set direct.retry.step 0 +set direct.retry.step 50 +set direct.retry.step 100 +set direct.retry.step 250 +``` + +--- + +#### View or change direct retry SNR margin +**Usage:** +- `get direct.retry.margin` +- `set direct.retry.margin ` + +**Parameters:** +- `snr_db`: Extra SNR margin above the SF receive floor, from `0` to `40`. + +**Default:** `5.00` with the `rooftop` preset + +**Notes:** +- Unknown repeaters are still retried. +- Known repeaters below the receive floor plus this margin are skipped. +- Failed attempts lower the recent repeater SNR estimate by `0.25 dB`. + +**Examples:** +``` +get direct.retry.margin +set direct.retry.margin 0 +set direct.retry.margin 2.5 +set direct.retry.margin 5 +set direct.retry.margin 10 +``` + +--- + +#### View or change adaptive direct retry coding rate +**Usage:** +- `get direct.retry.cr` +- `set direct.retry.cr off` +- `set direct.retry.cr ,,,` + +**Parameters:** +- `cr4_min`: Minimum SNR in dB to retry at CR4. +- `cr5_min`: Minimum SNR in dB to retry at CR5. +- `cr7_min`: Minimum SNR in dB to retry at CR7. +- `cr8_max`: Maximum SNR in dB that forces CR8. + +**Default:** `10.00,7.50,2.50,2.50` + +**Explanation:** +- Higher SNR uses faster coding rates. +- Lower SNR uses more robust coding rates. +- CR6 is intentionally skipped. +- `off` disables per-packet retry CR overrides and uses the current radio CR. +- Unknown repeaters start at `+2.00 dB` for adaptive CR selection. +- A failed unknown repeater is seeded at `+1.75 dB`. +- Each later failure lowers the SNR estimate by `0.25 dB`. + +**Examples:** +``` +get direct.retry.cr +set direct.retry.cr off +set direct.retry.cr 10.0,7.5,2.5,2.5 +set direct.retry.cr 12.0,8.0,4.0,1.0 +set direct.retry.cr 8.0,5.0,1.5,0 +set direct.retry.cr 6.0,3.0,0,-2.0 +set direct.retry.cr 20.0,12.0,6.0,2.0 +set direct.retry.cr 4.0,2.0,0,-4.0 +``` + +**Example profiles:** +- Conservative weak-link profile: +``` +set direct.retry.cr 12.0,8.0,4.0,1.0 +``` +- Balanced rooftop profile: +``` +set direct.retry.cr 10.0,7.5,2.5,2.5 +``` +- Faster strong-link profile: +``` +set direct.retry.cr 6.0,3.0,0,-2.0 +``` +- Very cautious noisy-link profile: +``` +set direct.retry.cr 20.0,12.0,6.0,2.0 +``` + +--- + +#### View, seed, or clear the recent repeater table +**Usage:** +- `get recent.repeater` +- `get recent.repeater ` +- `get recent.repeater page ` +- `set recent.repeater ` +- `clear recent.repeater` + +**Parameters:** +- `prefix`: Repeater path-hash prefix as hex. +- `snr_db`: SNR in dB. +- `page`: 1-based result page. + +**Examples:** +``` +get recent.repeater +get recent.repeater 2 +get recent.repeater page 3 +set recent.repeater A1B2C3 8.5 +set recent.repeater 71CE82 -3.25 +clear recent.repeater +``` + --- ### GPS (When GPS support is compiled in) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 45f81ed2..f8946e64 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -575,7 +575,8 @@ uint8_t MyMesh::getDirectRetryCodingRateForSNR(int8_t snr_x4) const { if (snr_x4 >= _prefs.direct_retry_cr4_snr_x4) return 4; if (snr_x4 >= _prefs.direct_retry_cr5_snr_x4) return 5; if (snr_x4 >= _prefs.direct_retry_cr7_snr_x4) return 7; - return 8; + if (snr_x4 <= _prefs.direct_retry_cr8_snr_x4) return 8; + return 7; } uint8_t MyMesh::getDirectRetryPreset() const { @@ -592,6 +593,9 @@ uint32_t MyMesh::getDirectRetryAttemptStepMillis() const { 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) { + return false; + } if (next_hop_hash == NULL || next_hop_hash_len == 0) { return true; } @@ -1108,6 +1112,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc _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; + _prefs.direct_retry_enabled = 1; _prefs.direct_retry_cr_enabled = 1; _prefs.direct_retry_prefs_magic[0] = DIRECT_RETRY_PREFS_MAGIC_0; _prefs.direct_retry_prefs_magic[1] = DIRECT_RETRY_PREFS_MAGIC_1; diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 687b7f7a..4dfbbf6d 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -101,6 +101,7 @@ static void setDefaultDirectRetryPrefs(NodePrefs* prefs) { 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; + prefs->direct_retry_enabled = 1; markDirectRetryPrefsValid(prefs); } @@ -209,9 +210,10 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { file.read((uint8_t *)&_prefs->direct_retry_cr5_snr_x4, sizeof(_prefs->direct_retry_cr5_snr_x4)); // 304 file.read((uint8_t *)&_prefs->direct_retry_cr7_snr_x4, sizeof(_prefs->direct_retry_cr7_snr_x4)); // 305 file.read((uint8_t *)&_prefs->direct_retry_cr8_snr_x4, sizeof(_prefs->direct_retry_cr8_snr_x4)); // 306 - file.read((uint8_t *)&_prefs->direct_retry_cr_enabled, sizeof(_prefs->direct_retry_cr_enabled)); // 307 - file.read((uint8_t *)&_prefs->direct_retry_prefs_magic, sizeof(_prefs->direct_retry_prefs_magic)); // 308 - // next: 310 + file.read((uint8_t *)&_prefs->direct_retry_enabled, sizeof(_prefs->direct_retry_enabled)); // 307 + file.read((uint8_t *)&_prefs->direct_retry_cr_enabled, sizeof(_prefs->direct_retry_cr_enabled)); // 308 + file.read((uint8_t *)&_prefs->direct_retry_prefs_magic, sizeof(_prefs->direct_retry_prefs_magic)); // 309 + // next: 311 // sanitise bad pref values _prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0, 20.0f); @@ -253,6 +255,7 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { _prefs->direct_retry_base_ms = constrain(_prefs->direct_retry_base_ms, 10, 5000); _prefs->direct_retry_step_ms = constrain(_prefs->direct_retry_step_ms, 0, 5000); _prefs->direct_retry_snr_margin_x4 = constrain(_prefs->direct_retry_snr_margin_x4, 0, 160); + _prefs->direct_retry_enabled = constrain(_prefs->direct_retry_enabled, 0, 1); _prefs->direct_retry_cr_enabled = constrain(_prefs->direct_retry_cr_enabled, 0, 1); file.close(); @@ -329,9 +332,10 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) { file.write((uint8_t *)&_prefs->direct_retry_cr5_snr_x4, sizeof(_prefs->direct_retry_cr5_snr_x4)); // 304 file.write((uint8_t *)&_prefs->direct_retry_cr7_snr_x4, sizeof(_prefs->direct_retry_cr7_snr_x4)); // 305 file.write((uint8_t *)&_prefs->direct_retry_cr8_snr_x4, sizeof(_prefs->direct_retry_cr8_snr_x4)); // 306 - file.write((uint8_t *)&_prefs->direct_retry_cr_enabled, sizeof(_prefs->direct_retry_cr_enabled)); // 307 - file.write((uint8_t *)&_prefs->direct_retry_prefs_magic, sizeof(_prefs->direct_retry_prefs_magic)); // 308 - // next: 310 + file.write((uint8_t *)&_prefs->direct_retry_enabled, sizeof(_prefs->direct_retry_enabled)); // 307 + file.write((uint8_t *)&_prefs->direct_retry_cr_enabled, sizeof(_prefs->direct_retry_cr_enabled)); // 308 + file.write((uint8_t *)&_prefs->direct_retry_prefs_magic, sizeof(_prefs->direct_retry_prefs_magic)); // 309 + // next: 311 file.close(); } @@ -833,6 +837,18 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep } else { strcpy(reply, "Error, must be infra, rooftop, or mobile"); } + } else if (memcmp(config, "direct.retry ", 13) == 0) { + if (memcmp(&config[13], "on", 2) == 0) { + _prefs->direct_retry_enabled = 1; + savePrefs(); + strcpy(reply, "OK"); + } else if (memcmp(&config[13], "off", 3) == 0) { + _prefs->direct_retry_enabled = 0; + savePrefs(); + strcpy(reply, "OK"); + } else { + strcpy(reply, "Error, must be on or off"); + } } else if (memcmp(config, "direct.retry.margin ", 20) == 0) { if (!looksNumeric(&config[20])) { strcpy(reply, "Error, must be 0-40 dB"); @@ -1106,6 +1122,8 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep sprintf(reply, "> %s", StrHelper::ftoa(_prefs->direct_tx_delay_factor)); } else if (memcmp(config, "retry.preset", 12) == 0) { sprintf(reply, "> %s", retryPresetName(_prefs->retry_preset)); + } else if (memcmp(config, "direct.retry", 12) == 0 && (config[12] == 0 || config[12] == ' ')) { + sprintf(reply, "> %s", _prefs->direct_retry_enabled ? "on" : "off"); } else if (memcmp(config, "direct.retry.margin", 19) == 0) { char margin[12]; formatSnrDbX4(margin, sizeof(margin), _prefs->direct_retry_snr_margin_x4); diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index 7fa71405..76a7cf46 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -102,6 +102,7 @@ struct NodePrefs { // persisted to file int8_t direct_retry_cr5_snr_x4; int8_t direct_retry_cr7_snr_x4; int8_t direct_retry_cr8_snr_x4; + uint8_t direct_retry_enabled; uint8_t direct_retry_cr_enabled; uint8_t direct_retry_prefs_magic[2]; };