Escalate coding rate on hop-zero flood retries
This commit is contained in:
1 parent
b8ecd7fcac
commit
499e5bc94a
6 files changed
+415
-3
No files matched your search
+44
-1
@@ -3344,6 +3344,49 @@ The count, path, group-data path, and advert controls work on repeater, room-ser
|
||||
firmware. Flood forwarding must also be enabled for retries to run. Prefix,
|
||||
ignore, bridge, and bucket controls are repeater-only.
|
||||
|
||||
#### Hop-zero flood retry coding rate
|
||||
|
||||
Flood packets with no path hops recorded yet use the same CR escalation ladder
|
||||
as direct retries. The starting point is the active radio CR, since a flood has
|
||||
no single next-hop repeater whose SNR can select a starting CR. Each retry uses
|
||||
its attempt number and the active radio setting; it does not restart the ladder
|
||||
from the previous retry's override. Forwarded packets with one or more recorded
|
||||
hops use the active radio CR on every retry, including bridge retries whose
|
||||
effective path length discounts ignored hops.
|
||||
|
||||
The initial transmission uses the active radio CR. This table lists retries
|
||||
after that initial transmission; any later retries remain at CR8:
|
||||
|
||||
| Starting CR | Retry 1 | Retry 2 | Retry 3 | Retry 4 | Retry 5+ |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| CR4 | CR4 | CR5 | CR7 | CR7 | CR8 |
|
||||
| CR5 | CR5 | CR7 | CR7 | CR8 | CR8 |
|
||||
| CR6 | CR6 | CR7 | CR7 | CR8 | CR8 |
|
||||
| CR7 | CR7 | CR7 | CR8 | CR8 | CR8 |
|
||||
| CR8 | CR8 | CR8 | CR8 | CR8 | CR8 |
|
||||
|
||||
The ladder is shared across presets. Presets change the retry budget, not the
|
||||
CR steps. At CR5, the maximum hop-zero schedules are:
|
||||
|
||||
| Preset | Configured flood count | Hop-zero retries | CR schedule after the initial send |
|
||||
| --- | ---: | ---: | --- |
|
||||
| `infra` | 1 | 2 | CR5, CR7 |
|
||||
| `rooftop` | 3 | 6 | CR5, CR7, CR7, CR8, CR8, CR8 |
|
||||
| `mobile` | 15 | 15 | CR5, CR7, CR7, then CR8 for retries 4-15 |
|
||||
|
||||
These budgets apply to group text and the origin login/message family
|
||||
(`RESPONSE`, `TXT_MSG`, `ANON_REQ`, `PATH`). Existing payload caps, echo
|
||||
cancellation, and retry eligibility still apply: `REQ` and OTA do not use
|
||||
generic flood retries; other payloads allow at most one retry. An eligible
|
||||
advert at CR5 therefore gets only its first CR5 retry.
|
||||
|
||||
`set flood.retry.count 0` continues to disable all automatic flood retries,
|
||||
including hop zero. It leaves the initial transmission and all direct-retry
|
||||
settings and CR schedules unchanged. Direct retry's `direct.retry.cr` switch
|
||||
and SNR thresholds continue to control direct retries only. Per-packet CR
|
||||
overrides are restored after transmission and do not change the saved radio
|
||||
setting or physical preamble.
|
||||
|
||||
#### View or change flood retry count
|
||||
|
||||
**Search terms:** flood tx retries, flood retry attempts, flood retransmissions, broadcast retries.
|
||||
@@ -3690,7 +3733,7 @@ set direct.retry.margin 10
|
||||
- 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.
|
||||
- Room-server and sensor firmware accept `on` or `off`; numeric SNR thresholds
|
||||
are repeater-only because those roles do not keep recent-repeater SNR data.
|
||||
- `off` disables per-packet retry CR overrides and uses the current radio CR.
|
||||
- `off` disables per-packet direct-retry CR overrides and uses the current radio CR.
|
||||
- Retry packets may use a different coding rate, but they keep the radio's normal physical preamble.
|
||||
- Unknown repeaters start at `+3.00 dB` for adaptive CR selection.
|
||||
- A failed unknown repeater is seeded at `+2.75 dB`.
|
||||
|
||||
@@ -332,6 +332,16 @@ The shared retry preset sets these flood defaults:
|
||||
| `rooftop` | `3` | `2` | `1` |
|
||||
| `mobile` | `15` | `1` | `1` |
|
||||
|
||||
Hop-zero flood retries use the direct-retry CR ladder starting at the active
|
||||
radio CR. With CR5, `infra` allows CR5, CR7; `rooftop` allows CR5, CR7, CR7,
|
||||
CR8, CR8, CR8; `mobile` allows CR5, CR7, CR7, then CR8 through retry 15.
|
||||
These lists exclude the initial send and remain subject to payload caps and
|
||||
echo cancellation. Floods with recorded hops keep the active CR.
|
||||
`flood.retry.count 0` still disables every automatic flood retry, including
|
||||
hop zero, without changing direct-retry settings or their CR schedule.
|
||||
See [hop-zero flood retry coding rate](cli_commands.md#hop-zero-flood-retry-coding-rate)
|
||||
for every starting CR and the payload limits.
|
||||
|
||||
Example for path-gated retry:
|
||||
|
||||
```text
|
||||
|
||||
+15
-2
@@ -145,6 +145,19 @@ void Mesh::configureDirectRetryPacket(Packet* retry, const Packet* original, uin
|
||||
retry->tx_cr = getDirectRetryCodingRateForAttempt(default_cr, retry_attempt);
|
||||
}
|
||||
|
||||
void Mesh::configureFloodRetryPacket(Packet* retry, const Packet* original, uint8_t retry_attempt) {
|
||||
if (retry == NULL) return;
|
||||
|
||||
// Always start from the active radio setting, not the previous retry's
|
||||
// override. Only the originating sender has no hops recorded yet; bridge
|
||||
// path adjustments must not make a forwarded packet eligible for this.
|
||||
const uint8_t default_cr = getDefaultTxCodingRate();
|
||||
retry->tx_cr = original != NULL && original->isRouteFlood()
|
||||
&& original->getPathHashCount() == 0
|
||||
? getDirectRetryCodingRateForAttempt(default_cr, retry_attempt)
|
||||
: default_cr;
|
||||
}
|
||||
|
||||
static bool isPrimaryOtaTraffic(const uint8_t* msg, uint16_t len) {
|
||||
return msg && len && ota::ota_is_transfer_message(msg[0]);
|
||||
}
|
||||
@@ -2367,7 +2380,7 @@ void Mesh::armFloodRetryOnSendComplete(const Packet* packet) {
|
||||
}
|
||||
|
||||
*retry = *packet;
|
||||
retry->tx_cr = getDefaultTxCodingRate();
|
||||
configureFloodRetryPacket(retry, packet, _flood_retries[i].retry_attempts_sent + 1);
|
||||
uint32_t retry_delay = getFloodRetryAttemptDelay(packet, _flood_retries[i].retry_attempts_sent);
|
||||
if (queueOutboundPacket(retry, _flood_retries[i].priority, retry_delay)) {
|
||||
_flood_retries[i].packet = retry;
|
||||
@@ -2403,7 +2416,7 @@ void Mesh::armFloodRetryOnSendComplete(const Packet* packet) {
|
||||
}
|
||||
|
||||
*retry = *packet;
|
||||
retry->tx_cr = getDefaultTxCodingRate();
|
||||
configureFloodRetryPacket(retry, packet, 1);
|
||||
if (queueOutboundPacket(retry, _flood_retries[i].priority, _flood_retries[i].retry_delay)) {
|
||||
unsigned long now = _ms->getMillis();
|
||||
_flood_retries[i].packet = retry;
|
||||
|
||||
@@ -416,6 +416,12 @@ protected:
|
||||
*/
|
||||
static uint8_t getDirectRetryCodingRateForAttempt(uint8_t start_cr, uint8_t retry_attempt);
|
||||
|
||||
/**
|
||||
* \brief Apply the direct-retry CR ladder to hop-zero flood retries, starting
|
||||
* from the active radio CR. Forwarded floods keep the active CR.
|
||||
*/
|
||||
void configureFloodRetryPacket(Packet* retry, const Packet* original, uint8_t retry_attempt);
|
||||
|
||||
/**
|
||||
* \brief Optional hook to set local-only transmit options on a retry packet before it is queued.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Execute preset application, role retry budgets, and the flood-count setter."""
|
||||
from pathlib import Path
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
from test_replay_reset_integration import extract_braced
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
class RetryCrPresetsTest(unittest.TestCase):
|
||||
def test_presets_and_disabling_flood_leave_direct_settings_unchanged(self):
|
||||
compiler = shutil.which("g++") or shutil.which("clang++")
|
||||
if compiler is None:
|
||||
self.skipTest("a host C++17 compiler is required")
|
||||
common = (ROOT / "src/helpers/CommonCLI.cpp").read_text()
|
||||
header = (ROOT / "src/helpers/CommonCLI.h").read_text()
|
||||
core = (ROOT / "src/Mesh.cpp").read_text()
|
||||
repeater = (ROOT / "examples/simple_repeater/MyMesh.cpp").read_text()
|
||||
definitions = "\n".join(re.findall(
|
||||
r"^#define (?:RETRY_PRESET_|DIRECT_RETRY_|FLOOD_RETRY_)[A-Z0-9_]+[^\n]*",
|
||||
header, re.M))
|
||||
definitions += "\n" + re.search(
|
||||
r"#define FLOOD_RETRY_PATH_GATE_DISABLED[^\n]*",
|
||||
(ROOT / "src/Mesh.h").read_text()).group()
|
||||
generated = definitions + r'''
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
#define constrain(x, low, high) ((x) < (low) ? (low) : (x) > (high) ? (high) : (x))
|
||||
struct NodePrefs {
|
||||
uint8_t retry_preset, direct_retry_attempts, direct_retry_enabled;
|
||||
uint8_t direct_retry_cr_enabled, direct_retry_recent_enabled;
|
||||
uint8_t direct_retry_prefs_magic[2], flood_retry_attempts;
|
||||
uint8_t flood_retry_max_path, flood_retry_group_max_path, disable_fwd;
|
||||
uint16_t direct_retry_base_ms, direct_retry_step_ms, direct_retry_snr_margin_x4;
|
||||
int8_t direct_retry_cr4_snr_x4, direct_retry_cr5_snr_x4;
|
||||
int8_t direct_retry_cr7_snr_x4, direct_retry_cr8_snr_x4;
|
||||
};
|
||||
namespace mesh {
|
||||
struct Packet { uint8_t hops = 0; uint8_t getPathHashCount() const { return hops; } };
|
||||
class Mesh { public:
|
||||
static uint8_t getDirectRetryCodingRateForAttempt(uint8_t, uint8_t);
|
||||
};
|
||||
}
|
||||
class MyMesh : public mesh::Mesh {
|
||||
public:
|
||||
NodePrefs _prefs{};
|
||||
unsigned direct_cancellations = 0, flood_cancellations = 0;
|
||||
uint8_t getFloodRetryMaxAttempts(const mesh::Packet*) const;
|
||||
uint8_t roomFloodAttempts(const mesh::Packet*) const;
|
||||
uint8_t sensorFloodAttempts(const mesh::Packet*) const;
|
||||
uint8_t getDirectRetryCodingRateForSNR(int8_t) const;
|
||||
void onRetryConfigChanged();
|
||||
void cancelAllDirectRetries() { ++direct_cancellations; }
|
||||
void cancelAllFloodRetries() { ++flood_cancellations; }
|
||||
};
|
||||
struct CLI {
|
||||
NodePrefs* _prefs;
|
||||
MyMesh* _callbacks;
|
||||
unsigned saves = 0;
|
||||
void savePrefs() { ++saves; }
|
||||
int _atoi(const char* value) { return atoi(value); }
|
||||
void set(const char* config, char* reply);
|
||||
};
|
||||
'''
|
||||
for signature in ("static void markDirectRetryPrefsValid(",
|
||||
"static void applyFloodRetryPreset(",
|
||||
"static void applyDirectRetryPreset(",
|
||||
"static void setDefaultDirectRetryPrefs(",
|
||||
"static bool looksUnsignedInteger("):
|
||||
generated += extract_braced(common, signature) + "\n"
|
||||
generated += "namespace mesh {\n" + extract_braced(
|
||||
core, "uint8_t Mesh::getDirectRetryCodingRateForAttempt(") + "\n}\n"
|
||||
for signature in ("uint8_t MyMesh::getFloodRetryMaxAttempts(",
|
||||
"uint8_t MyMesh::getDirectRetryCodingRateForSNR(",
|
||||
"void MyMesh::onRetryConfigChanged("):
|
||||
generated += extract_braced(repeater, signature) + "\n"
|
||||
for role, owner, method in (("simple_room_server", "MyMesh", "roomFloodAttempts"),
|
||||
("simple_sensor", "SensorMesh", "sensorFloodAttempts")):
|
||||
source = (ROOT / f"examples/{role}/{owner}.cpp").read_text()
|
||||
budget = extract_braced(source, f"uint8_t {owner}::getFloodRetryMaxAttempts(")
|
||||
generated += budget.replace(f"{owner}::getFloodRetryMaxAttempts", f"MyMesh::{method}") + "\n"
|
||||
setter = extract_braced(common, 'if (memcmp(config, "flood.retry.count ", 18) == 0)')
|
||||
generated += "void CLI::set(const char* config, char* reply) { " + setter + " }\n"
|
||||
generated += r'''
|
||||
static std::vector<int> directSettings(const NodePrefs& p) {
|
||||
return {p.direct_retry_attempts, p.direct_retry_enabled, p.direct_retry_cr_enabled,
|
||||
p.direct_retry_recent_enabled, p.direct_retry_base_ms, p.direct_retry_step_ms,
|
||||
p.direct_retry_snr_margin_x4, p.direct_retry_cr4_snr_x4, p.direct_retry_cr5_snr_x4,
|
||||
p.direct_retry_cr7_snr_x4, p.direct_retry_cr8_snr_x4};
|
||||
}
|
||||
static std::vector<uint8_t> directSchedule(const MyMesh& node, int8_t snr) {
|
||||
std::vector<uint8_t> rates;
|
||||
for (uint8_t attempt = 1; attempt <= node._prefs.direct_retry_attempts; ++attempt) {
|
||||
rates.push_back(node.getDirectRetryCodingRateForAttempt(
|
||||
node.getDirectRetryCodingRateForSNR(snr), attempt));
|
||||
}
|
||||
return rates;
|
||||
}
|
||||
int main() {
|
||||
const uint8_t presets[] = {RETRY_PRESET_INFRA, RETRY_PRESET_ROOFTOP, RETRY_PRESET_MOBILE};
|
||||
const uint8_t direct_counts[] = {4, 15, 15};
|
||||
const uint8_t origin_counts[] = {2, 6, 15};
|
||||
const uint8_t expected_cr5[] = {5, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8};
|
||||
for (unsigned preset = 0; preset < 3; ++preset) {
|
||||
for (int8_t snr : {-40, 10, 12, 30, 40}) {
|
||||
MyMesh node;
|
||||
setDefaultDirectRetryPrefs(&node._prefs);
|
||||
applyDirectRetryPreset(&node._prefs, presets[preset]);
|
||||
assert(node._prefs.direct_retry_attempts == direct_counts[preset]);
|
||||
mesh::Packet packet;
|
||||
assert(node.getFloodRetryMaxAttempts(&packet) == origin_counts[preset]);
|
||||
assert(node.roomFloodAttempts(&packet) == origin_counts[preset]);
|
||||
assert(node.sensorFloodAttempts(&packet) == origin_counts[preset]);
|
||||
for (uint8_t i = 0; i < origin_counts[preset]; ++i) {
|
||||
assert(node.getDirectRetryCodingRateForAttempt(5, i + 1) == expected_cr5[i]);
|
||||
}
|
||||
const auto settings = directSettings(node._prefs);
|
||||
const auto schedule = directSchedule(node, snr);
|
||||
CLI cli{&node._prefs, &node};
|
||||
char reply[160] = {};
|
||||
cli.set("flood.retry.count 0", reply);
|
||||
assert(strcmp(reply, "OK") == 0 && cli.saves == 1);
|
||||
assert(node._prefs.retry_preset == RETRY_PRESET_CUSTOM);
|
||||
assert(node.getFloodRetryMaxAttempts(&packet) == 0);
|
||||
assert(node.roomFloodAttempts(&packet) == 0);
|
||||
assert(node.sensorFloodAttempts(&packet) == 0);
|
||||
assert(node.flood_cancellations == 1 && node.direct_cancellations == 0);
|
||||
assert(directSettings(node._prefs) == settings);
|
||||
assert(directSchedule(node, snr) == schedule);
|
||||
}
|
||||
}
|
||||
puts("15 preset/disabled-flood schedule checks passed");
|
||||
}
|
||||
'''
|
||||
with tempfile.TemporaryDirectory(prefix=".tmp-retry-cr-", dir=ROOT) as directory:
|
||||
work = Path(directory)
|
||||
source = work / "retry-cr.cpp"
|
||||
source.write_text(generated, encoding="ascii")
|
||||
binary = work / "retry-cr"
|
||||
result = subprocess.run([compiler, "-std=c++17", "-Wall", "-Wextra",
|
||||
str(source), "-o", str(binary)],
|
||||
capture_output=True, text=True, timeout=60)
|
||||
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
|
||||
checked = subprocess.run([str(binary)], capture_output=True, text=True, timeout=10)
|
||||
self.assertEqual(checked.returncode, 0, checked.stdout + checked.stderr)
|
||||
self.assertIn("15 preset/disabled-flood schedule checks passed", checked.stdout)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <helpers/FloodAdvertCLI.h>
|
||||
#include <helpers/StaticPoolPacketManager.h>
|
||||
#include <helpers/ota/OtaFormat.h>
|
||||
#include <vector>
|
||||
|
||||
class TraceTestClock : public mesh::MillisecondClock {
|
||||
public:
|
||||
@@ -165,6 +166,186 @@ static mesh::Packet makeFloodPacket(uint8_t payload_type) {
|
||||
return packet;
|
||||
}
|
||||
|
||||
class RetryCodingRateRadio : public TraceTestRadio {
|
||||
public:
|
||||
uint8_t cr = 5;
|
||||
std::vector<uint8_t> transmitted_crs;
|
||||
|
||||
bool setCodingRate(uint8_t value) override { cr = value; return true; }
|
||||
bool startSendRaw(const uint8_t* bytes, int length) override {
|
||||
transmitted_crs.push_back(cr);
|
||||
return TraceTestRadio::startSendRaw(bytes, length);
|
||||
}
|
||||
};
|
||||
|
||||
class RetryCodingRateMesh : public TraceTestMesh {
|
||||
public:
|
||||
using TraceTestMesh::TraceTestMesh;
|
||||
uint8_t base_cr = 5;
|
||||
uint8_t flood_attempts = 15;
|
||||
uint8_t direct_attempts = 15;
|
||||
|
||||
uint8_t getDefaultTxCodingRate() const override { return base_cr; }
|
||||
uint8_t getFloodRetryMaxAttempts(const mesh::Packet*) const override { return flood_attempts; }
|
||||
uint8_t getDirectRetryMaxAttempts(const mesh::Packet*) const override { return direct_attempts; }
|
||||
bool allowDirectRetry(const mesh::Packet*, const uint8_t*, uint8_t) const override { return true; }
|
||||
|
||||
uint8_t floodCR(const mesh::Packet& packet, uint8_t attempt) {
|
||||
mesh::Packet retry = packet;
|
||||
configureFloodRetryPacket(&retry, &packet, attempt);
|
||||
return retry.tx_cr;
|
||||
}
|
||||
uint8_t directCR(uint8_t attempt) {
|
||||
mesh::Packet original, retry;
|
||||
configureDirectRetryPacket(&retry, &original, attempt);
|
||||
return retry.tx_cr;
|
||||
}
|
||||
void disableFloodRetries() {
|
||||
flood_attempts = 0;
|
||||
floodRetriesAllowed = false;
|
||||
cancelAllFloodRetries();
|
||||
}
|
||||
};
|
||||
|
||||
class RetryCodingRateTest : public testing::Test {
|
||||
protected:
|
||||
TraceTestClock clock;
|
||||
TraceTestRTC rtc;
|
||||
TraceTestRNG rng;
|
||||
RetryCodingRateRadio radio;
|
||||
TraceTestTables tables;
|
||||
StaticPoolPacketManager manager{12};
|
||||
RetryCodingRateMesh node{radio, clock, rng, rtc, manager, tables};
|
||||
|
||||
void SetUp() override { node.begin(); }
|
||||
|
||||
void transmitNext() {
|
||||
clock.now += 10000;
|
||||
node.loop();
|
||||
ASSERT_TRUE(radio.sending);
|
||||
radio.complete = true;
|
||||
++clock.now;
|
||||
node.loop();
|
||||
EXPECT_FALSE(radio.sending);
|
||||
EXPECT_EQ(node.base_cr, radio.cr); // the next packet and RX use the normal CR
|
||||
}
|
||||
|
||||
void queueFlood(bool scoped = false) {
|
||||
auto* packet = manager.allocNew();
|
||||
ASSERT_NE(nullptr, packet);
|
||||
*packet = makeFloodPacket(PAYLOAD_TYPE_GRP_TXT);
|
||||
if (scoped) {
|
||||
uint16_t codes[] = {0x1234, 0x5678};
|
||||
ASSERT_TRUE(node.sendFlood(packet, codes, 0, 3));
|
||||
} else {
|
||||
ASSERT_TRUE(node.sendFlood(packet));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(RetryCodingRateTest, HopZeroMatchesDirectLadderFromEveryRadioCR) {
|
||||
for (uint8_t cr : {4, 5, 6, 7, 8}) {
|
||||
node.base_cr = cr;
|
||||
for (uint8_t route : {ROUTE_TYPE_FLOOD, ROUTE_TYPE_TRANSPORT_FLOOD}) {
|
||||
auto packet = makeFloodPacket(PAYLOAD_TYPE_GRP_TXT);
|
||||
packet.header = route | (PAYLOAD_TYPE_GRP_TXT << PH_TYPE_SHIFT);
|
||||
packet.tx_cr = 8; // a previous retry must not advance the ladder twice
|
||||
for (uint8_t hash_size : {1, 2, 3}) {
|
||||
packet.setPathHashSizeAndCount(hash_size, 0);
|
||||
for (uint8_t attempt = 1; attempt <= 15; ++attempt) {
|
||||
SCOPED_TRACE(testing::Message() << "CR" << int(cr) << " retry " << int(attempt));
|
||||
EXPECT_EQ(node.directCR(attempt), node.floodCR(packet, attempt));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(RetryCodingRateTest, ForwardedFloodsKeepActiveCRAtEveryAttempt) {
|
||||
auto packet = makeFloodPacket(PAYLOAD_TYPE_GRP_TXT);
|
||||
packet.tx_cr = 8;
|
||||
for (uint8_t cr : {5, 6, 7, 8}) {
|
||||
node.base_cr = cr;
|
||||
for (uint8_t hash_size : {1, 2, 3}) {
|
||||
for (uint8_t hops : {1, 2, 8}) {
|
||||
packet.setPathHashSizeAndCount(hash_size, hops);
|
||||
for (uint8_t attempt = 1; attempt <= 15; ++attempt) {
|
||||
EXPECT_EQ(cr, node.floodCR(packet, attempt));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(RetryCodingRateTest, HopZeroCR5ScheduleReachesTheRadioForEachPresetBudget) {
|
||||
// infra, rooftop, mobile hop-zero budgets; the production preset/role
|
||||
// calculations are exercised separately by test_retry_cr_presets.py.
|
||||
for (uint8_t attempts : {2, 6, 15}) {
|
||||
for (bool scoped : {false, true}) {
|
||||
node.begin();
|
||||
node.flood_attempts = attempts;
|
||||
radio.transmitted_crs.clear();
|
||||
queueFlood(scoped);
|
||||
std::vector<uint8_t> expected{5}; // initial transmission
|
||||
for (uint8_t i = 0; i <= attempts; ++i) {
|
||||
if (i > 0) expected.push_back(i == 1 ? 5 : i <= 3 ? 7 : 8);
|
||||
transmitNext();
|
||||
}
|
||||
EXPECT_EQ(expected, radio.transmitted_crs);
|
||||
EXPECT_EQ(0, manager.getOutboundTotal());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(RetryCodingRateTest, DisabledFloodRetrySendsOnlyTheInitialPacket) {
|
||||
node.disableFloodRetries();
|
||||
queueFlood();
|
||||
transmitNext();
|
||||
EXPECT_EQ((std::vector<uint8_t>{5}), radio.transmitted_crs);
|
||||
EXPECT_EQ(0, manager.getOutboundTotal());
|
||||
}
|
||||
|
||||
TEST_F(RetryCodingRateTest, DisablingFloodRetryMidSequenceCancelsEscalatedCopies) {
|
||||
queueFlood();
|
||||
transmitNext();
|
||||
transmitNext();
|
||||
transmitNext();
|
||||
ASSERT_EQ(1, manager.getOutboundTotal());
|
||||
EXPECT_EQ(7, manager.getOutboundByIdx(0)->tx_cr);
|
||||
node.disableFloodRetries();
|
||||
clock.now += 10000;
|
||||
node.loop();
|
||||
EXPECT_FALSE(radio.sending);
|
||||
EXPECT_EQ(0, manager.getOutboundTotal());
|
||||
EXPECT_EQ((std::vector<uint8_t>{5, 5, 7}), radio.transmitted_crs);
|
||||
}
|
||||
|
||||
TEST_F(RetryCodingRateTest, DirectTransmissionsKeepTheirScheduleWithFloodRetryOff) {
|
||||
for (uint8_t attempts : {4, 15}) {
|
||||
for (bool flood_enabled : {true, false}) {
|
||||
node.begin();
|
||||
node.direct_attempts = attempts;
|
||||
node.floodRetriesAllowed = flood_enabled;
|
||||
node.flood_attempts = flood_enabled ? 15 : 0;
|
||||
radio.transmitted_crs.clear();
|
||||
auto* packet = node.obtainNewPacket();
|
||||
ASSERT_NE(nullptr, packet);
|
||||
packet->header = PAYLOAD_TYPE_ACK << PH_TYPE_SHIFT;
|
||||
packet->payload_len = 4;
|
||||
memset(packet->payload, 0x34, packet->payload_len);
|
||||
const uint8_t path[] = {0x12, 0x34};
|
||||
ASSERT_TRUE(node.sendDirect(packet, path, sizeof(path)));
|
||||
std::vector<uint8_t> expected{5};
|
||||
for (uint8_t i = 0; i <= attempts; ++i) {
|
||||
if (i > 0) expected.push_back(i == 1 ? 5 : i <= 3 ? 7 : 8);
|
||||
transmitNext();
|
||||
}
|
||||
EXPECT_EQ(expected, radio.transmitted_crs);
|
||||
EXPECT_EQ(0, manager.getOutboundTotal());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static mesh::Packet makeOtaManifestFragment(uint8_t format_version) {
|
||||
mesh::Packet packet = makeFloodPacket(PAYLOAD_TYPE_OTA);
|
||||
packet.payload_len = 12;
|
||||
|
||||
Reference in new issue
Block a user