Files
MeshCore/test/fixtures/noise_floor_sleep.cpp
T

233 lines
11 KiB
C++

static const char* scenario;
#define CHECK(expression, diagnostic) do { if (!(expression)) { \
std::fprintf(stderr, "FAIL %s: %s (line %d)\n", scenario, diagnostic, __LINE__); \
return 1; } } while (0)
static void prepare(RadioLibWrapper& w, unsigned mode, uint32_t start = 100) {
now_ms = start;
w._rx_ps_enabled = mode != 0;
w._rx_ps_continuous_fallback = mode == 2;
w._rx_ps_armed = mode == 1;
w._noise_floor_valid = false;
w._nf_refresh_requested = true;
w._nf_last_calib = 0;
w.startRecv();
w.loop();
if (w.state == STATE_IDLE) w.startRecv();
}
int main(int argc, char** argv) {
if (argc != 2) return 2;
scenario = argv[1];
if (!std::strcmp(scenario, "quiet")) {
for (unsigned mode : {0U, 1U, 2U}) {
RadioLibWrapper w;
prepare(w, mode);
const uint32_t start = now_ms;
uint32_t eligible_ms = 0;
unsigned iterations = 0;
while (w._nf_refresh_requested || w._nf_calib_active) {
CHECK(++iterations < 200, "deadline service must remain bounded");
uint32_t wait = UINT32_MAX;
CHECK(w.getNoiseFloorWakeDelay(now_ms, wait), "active batch lost wake deadline");
CHECK(wait > 0 || w._floor_estimator.complete(),
"pending batch must permit idle between samples");
CHECK(wait <= NoiseFloorEstimator::SAMPLE_INTERVAL_MS,
"sample deadline exceeded original spacing");
eligible_ms += wait;
now_ms += wait;
w.loop();
if (w.state == STATE_IDLE) w.startRecv();
}
CHECK(w.reads == NoiseFloorEstimator::SAMPLE_COUNT,
"idle scheduling changed accepted sample count");
CHECK(w._noise_floor_valid && w._noise_floor_centi_dbm == -10000,
"idle scheduling changed published estimator value");
CHECK(w._rx_ps_armed == (mode == 1), "calibration did not restore receive policy");
CHECK(eligible_ms >= 3150 && uint32_t(now_ms - start) < 3300,
"quiet collection must retain original quality and bounded cadence");
}
} else if (!std::strcmp(scenario, "rollover")) {
for (uint32_t start : {100U, UINT32_MAX - 3U, uint32_t(0U - NF_CALIB_SETTLE_MS)}) {
RadioLibWrapper w;
prepare(w, 0, start);
uint32_t wait = 0;
CHECK(w.reads == 0, "RX settling must precede every first sample");
CHECK(w.getNoiseFloorWakeDelay(now_ms, wait) && wait == NF_CALIB_SETTLE_MS,
"settle deadline must survive rollover, including timestamp zero");
now_ms += wait - 1;
w.loop();
CHECK(w.reads == 0 && w.getNoiseFloorWakeDelay(now_ms, wait) && wait == 1,
"sample accepted before settle deadline");
++now_ms;
CHECK(w.getNoiseFloorWakeDelay(now_ms, wait) && wait == 0,
"exact sample boundary must request service");
w.loop();
CHECK(w.reads == 1 && w.getNoiseFloorWakeDelay(now_ms, wait) && wait == 50,
"next sample spacing must survive rollover");
now_ms += 49;
w.loop();
CHECK(w.reads == 1, "early sample changed original sample spacing");
++now_ms;
w.loop();
CHECK(w.reads == 2, "next sample deadline not serviced");
}
} else if (!std::strcmp(scenario, "queries")) {
RadioLibWrapper w;
prepare(w, 1);
uint32_t wait = 0;
const unsigned reads = w.reads, modes = w.mode_reads;
const unsigned busy = w.busy_probes, packets = w.packet_probes;
const auto count = w._floor_estimator.count();
for (unsigned i = 0; i < 100; ++i)
CHECK(w.getNoiseFloorWakeDelay(now_ms, wait) && wait > 0,
"query must preserve pending sample deadline");
CHECK(w.reads == reads && w.mode_reads == modes && w.busy_probes == busy
&& w.packet_probes == packets && w._floor_estimator.count() == count,
"deadline query must not access hardware or mutate samples");
w.events.record();
CHECK(w.getNoiseFloorWakeDelay(now_ms, wait) && w.hasPendingRadioInterrupt(),
"deadline query consumed radio IRQ");
now_ms += NF_CALIB_SETTLE_MS;
w.loop();
CHECK(w.hasPendingRadioInterrupt() && w.reads == reads,
"pending packet IRQ must retain ownership during sampling service");
w.claimRadioInterrupt();
now_ms += NoiseFloorEstimator::SAMPLE_INTERVAL_MS;
w.inject_mode_irq = true;
w.loop();
CHECK(w.hasPendingRadioInterrupt() && w.reads == reads,
"IRQ during status probe must prevent RSSI read and remain pending");
} else if (!std::strcmp(scenario, "rejected")) {
for (bool busy : {false, true}) for (uint32_t start : {100U, UINT32_MAX - 17U}) {
RadioLibWrapper w;
prepare(w, 1, start);
w._noise_floor_valid = true;
w.packet = !busy;
w.busy = busy;
unsigned iterations = 0;
while (w._nf_refresh_requested || w._nf_calib_active) {
CHECK(++iterations < 1000, "rejected-sample retries must not hot poll");
uint32_t wait = 0;
CHECK(w.getNoiseFloorWakeDelay(now_ms, wait), "rejected sample lost timeout wake");
CHECK(wait > 0, "rejected sample must permit bounded idle before retry");
now_ms += wait;
w.loop();
if (w.state == STATE_IDLE) w.startRecv();
}
CHECK(w.reads == 0 && w._noise_floor_centi_dbm == -10500,
"rejected reads must retain published floor");
CHECK(uint32_t(now_ms - start) <= NoiseFloorEstimator::WINDOW_TIMEOUT_MS + 50,
"idle must not extend calibration timeout");
}
} else if (!std::strcmp(scenario, "lazy")) {
for (unsigned path : {0U, 1U, 2U}) for (bool accepted : {false, true})
for (uint32_t start : {1000U, UINT32_MAX - 3149U}) {
RadioLibWrapper w;
now_ms = start;
for (unsigned i = 0; i < NoiseFloorEstimator::SAMPLE_COUNT; ++i) {
now_ms = start + i * NoiseFloorEstimator::SAMPLE_INTERVAL_MS;
CHECK(w._floor_estimator.add(accepted ? -105 : -70, now_ms),
"policy fixture must use original complete spaced blocks");
}
w.loop();
CHECK(!w._nf_refresh_requested && w._nf_refresh_attempted,
"complete publish attempt must retire calibration");
CHECK(w._nf_lazy_refresh == accepted && w._noise_floor_valid,
"held rise must not earn slow refresh merely because old floor is valid");
const uint32_t finish = now_ms;
const uint32_t interval = accepted ? NF_LAZY_REFRESH_INTERVAL_MS : NF_CALIB_INTERVAL_MS;
for (uint32_t age : {1U, interval - 1}) {
now_ms = finish + age;
if (path == 0) w.triggerNoiseFloorCalibrate(7);
else if (path == 1) w.isReceivingPassive(7);
else w.requestPeriodicNoiseFloorRefresh();
CHECK(!w._nf_refresh_requested, "routine caller ignored lazy/retry cooldown");
}
now_ms = finish + interval;
if (path == 0) w.triggerNoiseFloorCalibrate(7);
else if (path == 1) w.isReceivingPassive(7);
else w.requestPeriodicNoiseFloorRefresh();
CHECK(w._nf_refresh_requested, "routine refresh missed its selected cadence");
w._nf_refresh_requested = false;
w._nf_lazy_refresh = true;
w._nf_last_calib = now_ms;
w.recalibrateNoiseFloor();
CHECK(w._nf_refresh_requested && !w._nf_lazy_refresh && !w._noise_floor_valid,
"forced tuning/gain baseline must bypass lazy routine cadence");
}
} else if (!std::strcmp(scenario, "agc-idle")) {
for (unsigned mode : {0U, 1U, 2U}) for (bool lazy : {false, true})
for (uint32_t last : {0U, 100U, UINT32_MAX - 2000U}) {
RadioLibWrapper w;
w._rx_ps_enabled = mode != 0;
w._rx_ps_continuous_fallback = mode == 2;
w._rx_ps_armed = mode == 1;
w._noise_floor_valid = true;
w._nf_refresh_requested = w._nf_calib_active = false;
w._nf_lazy_refresh = lazy;
w._nf_refresh_attempted = true;
w._nf_last_calib = last;
const uint32_t interval = lazy ? NF_LAZY_REFRESH_INTERVAL_MS : NF_CALIB_INTERVAL_MS;
for (uint32_t age : {1000U, 2000U, interval - 1}) {
now_ms = last + age;
w.requestPeriodicNoiseFloorRefresh();
CHECK(w.tryResetAGC(), "idle maintenance must run the reset sequence");
CHECK(!w._nf_refresh_requested && w._noise_floor_valid
&& w._nf_lazy_refresh == lazy && w._nf_last_calib == last,
"maintenance must preserve the floor and original refresh deadline");
w.startRecv();
CHECK(w._rx_ps_armed == (mode == 1) && !w._nf_calib_active,
"maintenance must return immediately to the selected RX policy");
}
now_ms = last + interval;
w.requestPeriodicNoiseFloorRefresh();
CHECK(w._nf_refresh_requested, "maintenance postponed a routine refresh or failed retry");
}
for (unsigned reason : {0U, 1U, 2U, 3U, 4U, 5U}) {
RadioLibWrapper w;
w._nf_refresh_requested = w._nf_calib_active = false;
w._noise_floor_valid = true;
w._nf_last_calib = now_ms;
if (reason == 0) w._noise_floor_valid = false;
if (reason == 1) w._nf_refresh_requested = true;
if (reason == 2) w._nf_calib_active = true;
if (reason == 3) w._profiles.secondary.mode = mesh::RadioProfileMode::RxTx;
if (reason == 5) { w._rx_boosted_gain_valid = true; w.gain_apply_success = false; }
if (reason == 4) w.resetAGC(); // Recovery/legacy uses the forced API.
else CHECK(w.tryResetAGC(), "idle reset was unexpectedly refused");
CHECK(w._nf_refresh_requested && w._nf_last_calib == 0,
"startup, pending, dual-profile, failed gain and recovery floors must still refresh");
}
} else if (!std::strcmp(scenario, "timeout-zero")) {
RadioLibWrapper w;
// A legitimate 30-second timeout lands exactly on uint32_t zero.
const uint32_t start = uint32_t(0U - NoiseFloorEstimator::WINDOW_TIMEOUT_MS);
prepare(w, 0, start);
w.packet = true;
w._noise_floor_valid = false;
CHECK(w._nf_window_armed && uint32_t(w._nf_calib_deadline) == 0,
"wrapped timeout must retain explicit armed state");
now_ms = UINT32_MAX;
w.loop();
uint32_t wait = 0;
CHECK(w._nf_refresh_requested && w.getNoiseFloorWakeDelay(now_ms, wait) && wait == 1,
"last timeout millisecond must remain idle eligible");
now_ms = 0;
CHECK(w.getNoiseFloorWakeDelay(now_ms, wait) && wait == 0,
"wrapped timeout must request exact-boundary service");
w.loop();
CHECK(!w._nf_refresh_requested && w._nf_refresh_attempted && w._nf_last_calib == 0,
"timeout at timestamp zero must retire failed attempt");
w.packet = false;
now_ms = NF_CALIB_INTERVAL_MS - 1;
w.requestPeriodicNoiseFloorRefresh();
CHECK(!w._nf_refresh_requested, "timestamp-zero failure must not masquerade as startup");
++now_ms;
w.requestPeriodicNoiseFloorRefresh();
CHECK(w._nf_refresh_requested, "timestamp-zero failure missed bounded retry");
} else return 2;
std::printf("PASS: %s\n", scenario);
}