mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-10-27 02:32:26 +00:00
Retain valid noise floors during adaptive AGC maintenance
This commit is contained in:
1 parent
55354c7fbe
commit
eb4f837969
6 files changed
+78
-13
No files matched your search
@@ -73,8 +73,11 @@ an attempt on a busy channel.
|
||||
|
||||
After a successful baseline, ordinary single-profile refresh waits five
|
||||
minutes. A rejected rise or failed attempt retains the last usable floor and
|
||||
retries after one minute. Gain, tuning and AGC changes still request immediate
|
||||
recalibration, so a configured short AGC interval can refresh more frequently.
|
||||
retries after one minute. Gain/tuning changes and radio recovery still request
|
||||
immediate recalibration. Adaptive AGC maintenance with an unchanged RF path
|
||||
retains a valid baseline and its existing refresh deadline; it does not cancel
|
||||
a pending collection or postpone a failed-sample retry. Legacy fixed-cadence
|
||||
AGC resets still request recalibration.
|
||||
Dual-profile scanning keeps its existing timing and calibration policy.
|
||||
|
||||
ESP32 timed sleep uses a millisecond timer capped at the next sample deadline.
|
||||
|
||||
@@ -1040,10 +1040,10 @@ bool RadioLibWrapper::tryResetAGC() {
|
||||
// BUSY is expected during RXPS sleep. Reuse the legacy explicit wake/reset
|
||||
// sequence there; polling only every 50 ms can miss every short RX window.
|
||||
// Outside known duty cycling, a busy SPI command still defers admission.
|
||||
return (_rx_ps_armed || !isChipBusy()) && resetAGCIfIdle();
|
||||
return (_rx_ps_armed || !isChipBusy()) && resetAGCIfIdle(false);
|
||||
}
|
||||
|
||||
bool RadioLibWrapper::resetAGCIfIdle() {
|
||||
bool RadioLibWrapper::resetAGCIfIdle(bool refresh_floor) {
|
||||
#if MESH_ENABLE_TX_BEACONS
|
||||
if (_beacon_owned) return false;
|
||||
#endif
|
||||
@@ -1053,11 +1053,20 @@ bool RadioLibWrapper::resetAGCIfIdle() {
|
||||
|
||||
doResetAGC();
|
||||
state = STATE_IDLE; // trigger a startReceive()
|
||||
if (_rx_boosted_gain_valid) applyRxBoostedGainMode(_cur_rx_boosted_gain);
|
||||
const bool gain_restored = !_rx_boosted_gain_valid
|
||||
|| applyRxBoostedGainMode(_cur_rx_boosted_gain);
|
||||
|
||||
// AGC reset discards stale samples, but the established floor remains usable
|
||||
// for LBT while the next spaced block is collected. Gain/tuning changes use
|
||||
// recalibrateNoiseFloor() directly to seed a fresh baseline.
|
||||
// Adaptive maintenance restores the same RF path. A valid, idle baseline
|
||||
// keeps its existing refresh deadline instead of spending another complete
|
||||
// continuous-RX window on every reset. Never cancel a requested collection,
|
||||
// postpone failed-sample retries, or change dual-profile scanning policy.
|
||||
if (!refresh_floor && gain_restored && _noise_floor_valid
|
||||
&& !_profiles.enabled() && !_nf_refresh_requested && !_nf_calib_active) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Recovery, legacy resets, changed/unknown paths and pending collections
|
||||
// retain prompt resampling. Do not mix samples across the analog reset.
|
||||
const bool previous_valid = _noise_floor_valid;
|
||||
const bool previous_secondary_valid = _noise_floor_secondary_valid;
|
||||
recalibrateNoiseFloor();
|
||||
|
||||
@@ -242,7 +242,7 @@ protected:
|
||||
void rxPsWatchdogCheck();
|
||||
void requestPeriodicNoiseFloorRefresh();
|
||||
void requestNoiseFloorRefresh();
|
||||
bool resetAGCIfIdle();
|
||||
bool resetAGCIfIdle(bool refresh_floor = true);
|
||||
void requestRestartRecv();
|
||||
bool isPacketPendingOrReceiving();
|
||||
void noiseFloorCalibCheck(unsigned long now);
|
||||
|
||||
Vendored
+43
@@ -157,6 +157,49 @@ int main(int argc, char** argv) {
|
||||
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.
|
||||
|
||||
@@ -39,6 +39,7 @@ class NoiseFloorSleepTests(unittest.TestCase):
|
||||
fixture = (ROOT / 'test/fixtures/noise_floor_sleep.cpp').read_text()
|
||||
variants = {
|
||||
'production': getter,
|
||||
'agc_forces_floor': getter,
|
||||
'old_batch_veto': getter[:getter.index('{') + 1] + '''
|
||||
(void)now;
|
||||
if (!isCalibratingNoiseFloor()) return false;
|
||||
@@ -55,7 +56,9 @@ class NoiseFloorSleepTests(unittest.TestCase):
|
||||
for name, implementation in variants.items():
|
||||
filename = name + '_' + family
|
||||
cpp, binary = work / (filename + '.cpp'), work / filename
|
||||
cpp.write_text((harness + implementation + fixture).replace('\u2014', '-'),
|
||||
variant_harness = harness.replace('resetAGCIfIdle(false)', 'resetAGCIfIdle()') \
|
||||
if name == 'agc_forces_floor' else harness
|
||||
cpp.write_text((variant_harness + implementation + fixture).replace('\u2014', '-'),
|
||||
encoding='ascii')
|
||||
built = subprocess.run([os.environ.get('CXX', 'g++'), '-std=c++17',
|
||||
'-O1', '-g', '-Wall', '-Wextra', '-Werror',
|
||||
@@ -96,6 +99,13 @@ class NoiseFloorSleepTests(unittest.TestCase):
|
||||
def test_timeout_timestamp_zero_is_not_an_unarmed_or_startup_sentinel(self):
|
||||
self.run_case('timeout-zero')
|
||||
|
||||
def test_adaptive_agc_keeps_valid_floor_cadence_but_not_changed_paths(self):
|
||||
self.run_case('agc-idle')
|
||||
|
||||
def test_old_agc_forced_collection_negative_control(self):
|
||||
self.run_case('agc-idle', 'agc_forces_floor',
|
||||
'maintenance must preserve the floor')
|
||||
|
||||
def test_old_whole_batch_sleep_veto_negative_control(self):
|
||||
self.run_case('quiet', 'old_batch_veto', 'pending batch must permit idle')
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ struct RadioLibWrapper {
|
||||
_noise_floor_secondary_valid = true;
|
||||
bool _nf_window_armed = false, _nf_lazy_refresh = false, _nf_refresh_attempted = false;
|
||||
bool _cad_enabled = true, packet = false, busy = false, inject_arm_irq = false;
|
||||
bool _rx_boosted_gain_valid = false, _cur_rx_boosted_gain = false;
|
||||
bool _rx_boosted_gain_valid = false, _cur_rx_boosted_gain = false, gain_apply_success = true;
|
||||
bool _wd_last_busy = false;
|
||||
uint8_t _startrx_fails = 0, _rx_mode_failures = 0;
|
||||
uint32_t _rx_retry_at = 0;
|
||||
@@ -117,7 +117,7 @@ struct RadioLibWrapper {
|
||||
unsigned long getEstAirtimeFor(int) { return 10; }
|
||||
void stopReceiveDutyCycle() { ++stops; _rx_ps_armed = false; }
|
||||
void doResetAGC() { ++agc_resets; }
|
||||
bool applyRxBoostedGainMode(bool) { ++gain_changes; return true; }
|
||||
bool applyRxBoostedGainMode(bool) { ++gain_changes; return gain_apply_success; }
|
||||
uint8_t beginReconfigure() { return 0; } // counted SPI/RX boundaries are separate
|
||||
void endReconfigure(bool) {}
|
||||
bool supportsRxPowerSaving() const { return true; }
|
||||
@@ -145,7 +145,7 @@ struct RadioLibWrapper {
|
||||
void requestNoiseFloorRefresh();
|
||||
void requestPeriodicNoiseFloorRefresh();
|
||||
void recalibrateNoiseFloor();
|
||||
bool resetAGCIfIdle();
|
||||
bool resetAGCIfIdle(bool refresh_floor = true);
|
||||
bool tryResetAGC();
|
||||
bool getNoiseFloorWakeDelay(uint32_t, uint32_t&) const;
|
||||
void resetAGC();
|
||||
|
||||
Reference in new issue
Block a user