mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-11-04 20:17:53 +00:00
Integrate the ESP32 power-saving core and RXPS recovery/status updates while preserving radio2, telemetry and saved settings.
299 lines
12 KiB
C++
299 lines
12 KiB
C++
#include <gtest/gtest.h>
|
|
#include <cmath>
|
|
#include <helpers/radiolib/RXPowerSaving.h>
|
|
#include <helpers/radiolib/RXPowerSavingCLI.h>
|
|
#include <helpers/CLICommandUtils.h>
|
|
|
|
TEST(RxPowerSaving, WirePreamblesStayCompatibleRegardlessOfLocalPowerSaving) {
|
|
EXPECT_EQ(32U, rxPowerSavingPreambleForParams(7, 500));
|
|
EXPECT_EQ(32U, rxPowerSavingPreambleForParams(6, 250));
|
|
EXPECT_EQ(32U, rxPowerSavingPreambleForParams(5, 125));
|
|
EXPECT_EQ(64U, rxPowerSavingPreambleForParams(6, 500));
|
|
EXPECT_EQ(64U, rxPowerSavingPreambleForParams(5, 250));
|
|
EXPECT_EQ(128U, rxPowerSavingPreambleForParams(5, 500));
|
|
EXPECT_EQ(16U, rxPowerSavingPreambleForParams(9, 500));
|
|
}
|
|
|
|
TEST(RxPowerSaving, GuardedProfilesCoverActualWireHeaderAndPromisedCatch) {
|
|
for (float bw : {7.8f, 15.6f, 31.25f, 62.5f, 125.0f, 250.0f, 500.0f}) {
|
|
for (uint8_t sf = 5; sf <= 12; ++sf) {
|
|
for (uint32_t transition : {1000U, 2600U, 6000U}) {
|
|
for (uint8_t requested = 1; requested <= 8; ++requested) {
|
|
for (uint8_t assumption : {16, 32, 64, 128}) {
|
|
SCOPED_TRACE(::testing::Message() << "sf=" << int(sf) << " bw=" << bw
|
|
<< " level=" << int(requested) << " preamble=" << int(assumption)
|
|
<< " transition=" << transition);
|
|
uint32_t rx, sleep;
|
|
uint8_t level, preamble;
|
|
ASSERT_TRUE(calcRxPowerSavingLevelAtOrAbove(requested, sf, bw, assumption,
|
|
transition, &rx, &sleep, &level, &preamble));
|
|
if (rxPowerSavingUsesContinuousFallback(rx, sleep)) {
|
|
EXPECT_EQ(0, level);
|
|
EXPECT_EQ(0, preamble);
|
|
continue;
|
|
}
|
|
EXPECT_GE(level, requested);
|
|
EXPECT_LE(level, RX_POWERSAVING_MAX_LEVEL);
|
|
EXPECT_LE(preamble, rxPowerSavingPreambleForParams(sf, bw));
|
|
EXPECT_GE(sleep, transition + RX_POWERSAVING_MIN_SLEEP_MARGIN_US);
|
|
EXPECT_TRUE(canStartRxPowerSavingDutyCycle(rx, sleep, transition));
|
|
const double symbol = 1000.0 * (1U << sf) / bw;
|
|
EXPECT_GE(preamble - sleep / symbol,
|
|
rxPowerSavingLevelCatch(level, preamble));
|
|
const double actual_rx = (rx * 8U / 125U) * 15.625;
|
|
const double actual_sleep = ((sleep - transition) * 8U / 125U) * 15.625;
|
|
const double required = (rxPowerSavingPreambleForParams(sf, bw)
|
|
+ (sf <= 6 ? 6.25 : 4.25) + 8.0 + 1.0) * symbol;
|
|
EXPECT_GE(2 * actual_rx + actual_sleep + 0.01, required);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST(RxPowerSaving, AutomaticAdjustmentNeverSelectsExperimentalLevels) {
|
|
uint32_t rx, sleep;
|
|
uint8_t level, preamble;
|
|
// The previous 6 ms TCXO budget cannot fit a guarded P32 at this tuple.
|
|
ASSERT_TRUE(calcRxPowerSavingLevelAtOrAbove(1, 7, 500, 16, 6000,
|
|
&rx, &sleep, &level, &preamble));
|
|
EXPECT_TRUE(rxPowerSavingUsesContinuousFallback(rx, sleep));
|
|
ASSERT_TRUE(calcRxPowerSavingLevelAtOrAbove(1, 7, 500, 16, 2600,
|
|
&rx, &sleep, &level, &preamble));
|
|
EXPECT_FALSE(rxPowerSavingUsesContinuousFallback(rx, sleep));
|
|
EXPECT_LE(level, 8);
|
|
EXPECT_EQ(32, preamble);
|
|
}
|
|
|
|
TEST(RxPowerSaving, FastTuplesRetainLongPreambleSupport) {
|
|
uint32_t rx, sleep;
|
|
uint8_t level, preamble;
|
|
for (uint8_t sf : {5, 6}) {
|
|
ASSERT_TRUE(calcRxPowerSavingLevelAtOrAbove(8, sf, 500, 16, 6000,
|
|
&rx, &sleep, &level, &preamble));
|
|
EXPECT_EQ(8, level);
|
|
EXPECT_EQ(sf == 5 ? 128 : 64, preamble);
|
|
EXPECT_FALSE(rxPowerSavingUsesContinuousFallback(rx, sleep));
|
|
}
|
|
}
|
|
|
|
TEST(RxPowerSaving, NamedProfilesAndExplicitExperimentalModes) {
|
|
EXPECT_EQ(3, RX_POWERSAVING_CONSERVATIVE_LEVEL);
|
|
EXPECT_EQ(6, RX_POWERSAVING_BALANCED_LEVEL);
|
|
EXPECT_EQ(8, RX_POWERSAVING_MAX_LEVEL);
|
|
uint32_t rx9, sleep9, rx10, sleep10;
|
|
ASSERT_TRUE(calcRxPowerSavingLevel(9, 8, 62.5f, 32, &rx9, &sleep9));
|
|
ASSERT_TRUE(calcRxPowerSavingLevel(10, 8, 62.5f, 32, &rx10, &sleep10));
|
|
EXPECT_LT(rx10, rx9);
|
|
EXPECT_GT(sleep10, sleep9);
|
|
EXPECT_FALSE(isRxPowerSavingUnguardedLevel(8));
|
|
EXPECT_TRUE(isRxPowerSavingUnguardedLevel(9));
|
|
}
|
|
|
|
TEST(RxPowerSaving, BothFamiliesShareGuardedGeometry) {
|
|
for (uint8_t level = 1; level <= 8; ++level) {
|
|
uint32_t sx_rx, sx_sleep, lr_rx, lr_sleep;
|
|
ASSERT_TRUE(calcRxPowerSavingLevel(level, 8, 62.5f, 32, &sx_rx, &sx_sleep, 6, 2600));
|
|
ASSERT_TRUE(calcRxPowerSavingLevel(level, 8, 62.5f, 32, &lr_rx, &lr_sleep, 8, 2600));
|
|
EXPECT_EQ(sx_rx, lr_rx);
|
|
EXPECT_EQ(sx_sleep, lr_sleep);
|
|
}
|
|
}
|
|
|
|
TEST(RxPowerSaving, ExperimentalProfilesAvoidKnownBadSx126xTicks) {
|
|
for (uint32_t tick : {320U, 640U}) {
|
|
EXPECT_EQ(tick + 1, avoidBadRxPowerSavingTick(rxPowerSavingTickToUs(tick)) * 8U / 125U);
|
|
}
|
|
}
|
|
|
|
TEST(RxPowerSaving, RetuningPreservesManualTimingsAndRestoresRequestedLevel) {
|
|
uint32_t rx = 12345, sleep = 67890;
|
|
EXPECT_FALSE(recalcRxPowerSavingFromLevel(0, 8, 62.5f, 16, &rx, &sleep));
|
|
EXPECT_EQ(12345U, rx);
|
|
EXPECT_EQ(67890U, sleep);
|
|
uint8_t level, preamble;
|
|
ASSERT_TRUE(recalcRxPowerSavingFromLevel(3, 5, 500, 16, &rx, &sleep, &level, &preamble));
|
|
ASSERT_TRUE(recalcRxPowerSavingFromLevel(3, 9, 62.5f, 16, &rx, &sleep, &level, &preamble));
|
|
EXPECT_EQ(3, level);
|
|
EXPECT_EQ(16, preamble);
|
|
}
|
|
|
|
TEST(RxPowerSaving, RejectsInvalidInputs) {
|
|
uint32_t rx, sleep;
|
|
EXPECT_FALSE(calcRxPowerSavingLevel(0, 8, 62.5f, 16, &rx, &sleep));
|
|
EXPECT_FALSE(calcRxPowerSavingLevel(11, 8, 62.5f, 16, &rx, &sleep));
|
|
EXPECT_FALSE(calcRxPowerSavingLevel(5, 4, 62.5f, 16, &rx, &sleep));
|
|
EXPECT_FALSE(calcRxPowerSavingLevel(5, 8, 0, 16, &rx, &sleep));
|
|
EXPECT_FALSE(calcRxPowerSavingLevel(5, 8, NAN, 16, &rx, &sleep));
|
|
EXPECT_FALSE(calcRxPowerSavingLevel(5, 8, INFINITY, 16, &rx, &sleep));
|
|
EXPECT_FALSE(calcRxPowerSavingLevel(5, 8, 62.5f, 24, &rx, &sleep));
|
|
EXPECT_FALSE(calcRxPowerSavingLevel(5, 8, 62.5f, 16, nullptr, &sleep));
|
|
}
|
|
|
|
TEST(RxPowerSavingCLI, ParsesNewNamesAndRejectsTrailingJunk) {
|
|
mesh::cli::RxPowerSavingArguments args = {};
|
|
for (const char* name : {"max", "overdrive", "riskyWorkingMax"}) {
|
|
ASSERT_TRUE(mesh::cli::parseRxPowerSavingArgumentsStrict(name, args));
|
|
EXPECT_EQ(mesh::cli::rxPowerSavingNamedLevel(name), args.level);
|
|
EXPECT_EQ(16U, args.preamble);
|
|
}
|
|
ASSERT_TRUE(mesh::cli::parseRxPowerSavingArgumentsStrict("max preamble 32", args));
|
|
EXPECT_EQ(8U, args.level);
|
|
EXPECT_EQ(32U, args.preamble);
|
|
for (const char* text : {"maxx", "max extra", "max preamble 32 extra", "riskyworkingmax",
|
|
"level 4294967296", "overdrive preamble nope"}) {
|
|
EXPECT_FALSE(mesh::cli::parseRxPowerSavingArgumentsStrict(text, args)) << text;
|
|
}
|
|
}
|
|
|
|
namespace {
|
|
class RxPowerSavingTestControl : public RxPowerSavingControl {
|
|
public:
|
|
bool accept = true;
|
|
bool enabled = false;
|
|
bool config_error = false;
|
|
uint32_t transition_us = 1000;
|
|
float capture_cost = 8;
|
|
uint32_t rx_us = 0, sleep_us = 0;
|
|
RxPowerSavingStatus status;
|
|
RxPowerSavingStatus getRxPowerSavingStatus() const override { return status; }
|
|
bool setRxPowerSaving(bool on, uint32_t rx, uint32_t sleep) override {
|
|
if (!accept) return false;
|
|
enabled = on; rx_us = rx; sleep_us = sleep;
|
|
return true;
|
|
}
|
|
void setRxPowerSavingConfigError(bool invalid) override { config_error = invalid; }
|
|
float rxPowerSavingCaptureCostSymbols() const override { return capture_cost; }
|
|
uint32_t rxPowerSavingTransitionUs() const override { return transition_us; }
|
|
};
|
|
}
|
|
|
|
TEST(RxPowerSaving, RepeatedArmFailuresStopUntilResetOrSuccess) {
|
|
RxPowerSavingArmRetryState retry;
|
|
for (unsigned i = 0; i < 3; ++i) {
|
|
EXPECT_TRUE(retry.canAttempt());
|
|
retry.recordFailure();
|
|
}
|
|
EXPECT_FALSE(retry.canAttempt());
|
|
retry.recordFailure();
|
|
EXPECT_EQ(3, retry.consecutiveFailures());
|
|
retry.reset();
|
|
EXPECT_TRUE(retry.canAttempt());
|
|
retry.recordFailure();
|
|
retry.recordSuccess();
|
|
EXPECT_EQ(0, retry.consecutiveFailures());
|
|
}
|
|
|
|
TEST(RxPowerSaving, ConfigUsesRuntimeRadioTimingAndKeepsWirePreamble) {
|
|
RxPowerSavingTestControl control;
|
|
RxPowerSavingConfig config;
|
|
config.enabled = 1;
|
|
ASSERT_TRUE(applyRxPowerSavingConfig(&config, 6, 500, &control));
|
|
EXPECT_TRUE(control.enabled);
|
|
EXPECT_FALSE(control.config_error);
|
|
EXPECT_FALSE(rxPowerSavingUsesContinuousFallback(control.rx_us, control.sleep_us));
|
|
EXPECT_EQ(16, config.preamble);
|
|
uint32_t rx = 0, sleep = 0;
|
|
ASSERT_TRUE(recalcRxPowerSavingFromLevel(config.level, 6, 500, 16, &rx, &sleep,
|
|
nullptr, nullptr, control.transition_us, control.capture_cost));
|
|
EXPECT_EQ(rx, control.rx_us);
|
|
EXPECT_EQ(sleep, control.sleep_us);
|
|
}
|
|
|
|
TEST(RxPowerSaving, InvalidConfigFallsBackWithoutOverwritingRequestedPolicy) {
|
|
RxPowerSavingTestControl control;
|
|
RxPowerSavingConfig config;
|
|
config.enabled = 1;
|
|
config.level = 0;
|
|
config.rx_us = 500;
|
|
EXPECT_FALSE(applyRxPowerSavingConfig(&config, 8, 62.5f, &control));
|
|
EXPECT_EQ(1, config.enabled);
|
|
EXPECT_EQ(500U, config.rx_us);
|
|
EXPECT_FALSE(control.enabled);
|
|
EXPECT_TRUE(control.config_error);
|
|
EXPECT_FALSE(normalizeRxPowerSavingConfig(&config, 8, NAN));
|
|
}
|
|
|
|
TEST(RxPowerSavingCLI, RejectedCommandsNeverOverwriteSavedConfig) {
|
|
RxPowerSavingTestControl control;
|
|
RxPowerSavingConfig config;
|
|
char reply[160];
|
|
for (const char* invalid : {"max extra", "level 4294967296", "level 256", "500 1000",
|
|
"max preamble 65552", "riskyworkingmax", "level 8 preamble 24"}) {
|
|
EXPECT_FALSE(RXPowerSavingCLI::set(invalid, 8, 62.5f, &config, &control, reply, sizeof(reply))) << invalid;
|
|
EXPECT_EQ(0, config.enabled);
|
|
EXPECT_EQ(RX_POWERSAVING_BALANCED_LEVEL, config.level);
|
|
}
|
|
control.accept = false;
|
|
EXPECT_FALSE(RXPowerSavingCLI::set("max", 8, 62.5f, &config, &control, reply, sizeof(reply)));
|
|
EXPECT_EQ(0, config.enabled);
|
|
}
|
|
|
|
TEST(RxPowerSavingCLI, OffPreservesRequestedLevelAndManualPeriods) {
|
|
RxPowerSavingTestControl control;
|
|
RxPowerSavingConfig config;
|
|
char reply[160];
|
|
ASSERT_TRUE(RXPowerSavingCLI::set("max preamble 64", 5, 500, &config, &control, reply, sizeof(reply)));
|
|
EXPECT_EQ(8, config.level);
|
|
EXPECT_EQ(64, config.preamble);
|
|
ASSERT_TRUE(RXPowerSavingCLI::set("off", 5, 500, &config, &control, reply, sizeof(reply)));
|
|
EXPECT_EQ(0, config.enabled);
|
|
EXPECT_EQ(8, config.level);
|
|
ASSERT_TRUE(RXPowerSavingCLI::set("12000 5000", 8, 62.5f, &config, &control, reply, sizeof(reply)));
|
|
EXPECT_EQ(0, config.level);
|
|
EXPECT_EQ(12000U, config.rx_us);
|
|
EXPECT_EQ(5000U, config.sleep_us);
|
|
}
|
|
|
|
TEST(RxPowerSavingCLI, StatusUsesLiveWirePreambleAndReportsHardwareClamp) {
|
|
RxPowerSavingTestControl control;
|
|
control.status.supported = control.status.armed = true;
|
|
control.status.effective_rx_us = 78000;
|
|
control.status.effective_sleep_us = 60000;
|
|
RxPowerSavingConfig config;
|
|
config.enabled = 1;
|
|
char reply[160];
|
|
RXPowerSavingCLI::get(&config, &control, 8, 62.5f, reply, sizeof(reply), 32);
|
|
EXPECT_NE(nullptr, strstr(reply, "effective=armed"));
|
|
EXPECT_NE(nullptr, strstr(reply, "catch=17.4"));
|
|
EXPECT_NE(nullptr, strstr(reply, "erx=78000,eslp=60000"));
|
|
control.status.armed = false;
|
|
RXPowerSavingCLI::get(&config, &control, 8, 62.5f, reply, sizeof(reply), 32);
|
|
EXPECT_EQ(nullptr, strstr(reply, "catch="));
|
|
}
|
|
|
|
TEST(RxPowerSavingCLI, StatusCatchRejectsInvalidBandwidthAndBoundsMaximumPeriods) {
|
|
RxPowerSavingTestControl control;
|
|
control.status.supported = control.status.armed = true;
|
|
RxPowerSavingConfig config;
|
|
config.sleep_us = config.rx_us = RX_POWERSAVING_MAX_PERIOD_US;
|
|
control.status.effective_rx_us = config.rx_us;
|
|
control.status.effective_sleep_us = config.sleep_us;
|
|
char reply[160];
|
|
for (float bw : {NAN, INFINITY, 0.0f, -1.0f, 501.0f}) {
|
|
RXPowerSavingCLI::get(&config, &control, 5, bw, reply, sizeof(reply), 65535);
|
|
EXPECT_EQ(nullptr, strstr(reply, "catch="));
|
|
}
|
|
RXPowerSavingCLI::get(&config, &control, 5, 500.0f, reply, sizeof(reply), 65535);
|
|
EXPECT_NE(nullptr, strstr(reply, "catch=-403215.0"));
|
|
EXPECT_LT(strlen(reply), sizeof(reply));
|
|
}
|
|
|
|
TEST(RxPowerSavingCLI, CompactStatusKeepsExperimentalWarningAndFullSetterNames) {
|
|
RxPowerSavingTestControl control;
|
|
RxPowerSavingConfig config;
|
|
char reply[160];
|
|
for (const char* name : {"overdrive", "riskyWorkingMax"}) {
|
|
ASSERT_TRUE(RXPowerSavingCLI::set(name, 8, 62.5f, &config, &control, reply, sizeof(reply)));
|
|
EXPECT_NE(nullptr, strstr(reply, name));
|
|
RXPowerSavingCLI::get(&config, &control, 8, 62.5f, reply, sizeof(reply));
|
|
EXPECT_NE(nullptr, strstr(reply, config.level == 9 ? "level=9(!)" : "level=10(!)"));
|
|
}
|
|
}
|
|
|
|
int main(int argc, char** argv) {
|
|
::testing::InitGoogleTest(&argc, argv);
|
|
return RUN_ALL_TESTS();
|
|
}
|