mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-10-23 18:02:29 +00:00
Repair failed profile retunes before receiving packets
This commit is contained in:
1 parent
8038715ffe
commit
8e35f477c0
13 files changed
+189
-21
No files matched your search
@@ -590,6 +590,7 @@ jobs:
|
||||
python3 -B test/test_radio_reply_mutation.py
|
||||
python3 -B test/test_radio_profile_scan.py
|
||||
python3 -B test/test_radio_profile_clock.py -v
|
||||
python3 -B test/test_radio_profile_repair.py -v
|
||||
python3 -B test/test_radio_profile_runtime_recommendation.py -v
|
||||
python3 -B test/test_radio_profile_idle_dispatcher.py -v
|
||||
python3 -B test/test_radio_rxps_rejection.py -v
|
||||
|
||||
@@ -315,6 +315,21 @@ uint8_t RadioLibWrapper::beginReconfigure() {
|
||||
#endif
|
||||
if (_cw_active) return 2;
|
||||
const uint8_t base_state = state;
|
||||
if (_profile_repair_required) {
|
||||
// The last transaction failed after changing the chip. Its old FIFO and
|
||||
// activity flags have no trustworthy modulation/profile ownership.
|
||||
if (base_state == STATE_TX_WAIT || isChipBusy()) return 2;
|
||||
claimRadioInterrupt();
|
||||
_rx_hold_continuous = false;
|
||||
if (_radio->standby() != RADIOLIB_ERR_NONE) {
|
||||
state = STATE_IDLE;
|
||||
return 3;
|
||||
}
|
||||
_rx_ps_armed = false;
|
||||
claimRadioInterrupt();
|
||||
state = STATE_IDLE;
|
||||
return 1; // A repair must verify RX re-entry even if software was idle.
|
||||
}
|
||||
// On SX126x/LR11xx duty-cycle RX, BUSY may remain asserted during the sleep
|
||||
// side of the cycle. Do not issue an IRQ/preamble query over SPI then; due
|
||||
// scheduled work will retry as soon as the next safe listen window opens.
|
||||
@@ -324,10 +339,13 @@ uint8_t RadioLibWrapper::beginReconfigure() {
|
||||
}
|
||||
|
||||
const bool resume_rx = base_state == STATE_RX;
|
||||
if (_rx_ps_armed) {
|
||||
stopReceiveDutyCycle();
|
||||
} else if (resume_rx) {
|
||||
_radio->standby();
|
||||
if (_rx_ps_armed || resume_rx) {
|
||||
if (_radio->standby() != RADIOLIB_ERR_NONE) {
|
||||
state = STATE_IDLE;
|
||||
_profile_refresh_required = _profile_repair_required = true;
|
||||
return 3;
|
||||
}
|
||||
_rx_ps_armed = false;
|
||||
}
|
||||
// An RX completion during standby still owns its FIFO and old profile.
|
||||
// Defer reconfiguration until recvRaw consumes it, rather than rearming and
|
||||
@@ -368,6 +386,7 @@ bool RadioLibWrapper::restoreAfterDeepInit() {
|
||||
restored = applyRxBoostedGainMode(_cur_rx_boosted_gain) && restored;
|
||||
}
|
||||
recalibrateNoiseFloor();
|
||||
if (restored) _profile_repair_required = false;
|
||||
return restored;
|
||||
}
|
||||
|
||||
@@ -378,7 +397,8 @@ mesh::RadioParamApplyResult RadioLibWrapper::trySetParams(float freq, float bw,
|
||||
}
|
||||
|
||||
uint8_t resume_rx = beginReconfigure();
|
||||
if (resume_rx > 1) return mesh::RadioParamApplyResult::BUSY;
|
||||
if (resume_rx > 1) return resume_rx == 2
|
||||
? mesh::RadioParamApplyResult::BUSY : mesh::RadioParamApplyResult::FAILED;
|
||||
|
||||
const bool had_previous_params = _params_valid;
|
||||
const float previous_freq = _cur_freq;
|
||||
@@ -418,6 +438,7 @@ mesh::RadioParamApplyResult RadioLibWrapper::trySetParams(float freq, float bw,
|
||||
_rx_ps_continuous_fallback = rxPowerSavingUsesContinuousFallback(
|
||||
_rx_ps_rx_us, _rx_ps_sleep_us);
|
||||
}
|
||||
_profile_repair_required = false;
|
||||
endReconfigure(resume_rx);
|
||||
success = !resume_rx || isInRecvMode();
|
||||
}
|
||||
@@ -453,8 +474,12 @@ mesh::RadioParamApplyResult RadioLibWrapper::trySetParams(float freq, float bw,
|
||||
#endif
|
||||
|
||||
if (!restored) restored = restoreAfterDeepInit();
|
||||
if (restored) endReconfigure(resume_rx);
|
||||
if (!restored || (resume_rx && !isInRecvMode())) _profile_refresh_required = true;
|
||||
if (restored) {
|
||||
_profile_repair_required = false;
|
||||
endReconfigure(resume_rx);
|
||||
}
|
||||
if (!restored || (resume_rx && !isInRecvMode()))
|
||||
_profile_refresh_required = _profile_repair_required = true;
|
||||
|
||||
if (!restored) {
|
||||
MESH_DEBUG_PRINTLN("RadioLibWrapper: failed to restore radio parameters after apply failure");
|
||||
@@ -518,7 +543,7 @@ mesh::RadioParamApplyResult RadioLibWrapper::tuneProfile(uint8_t profile) {
|
||||
if (profile > 1 || (profile == 1 && !_profiles.enabled())) return mesh::RadioParamApplyResult::FAILED;
|
||||
const auto& p = _profiles.params(profile);
|
||||
const uint16_t preamble = profilePreamble(profile);
|
||||
if (!_profile_refresh_required && _active_profile == profile && _profile_generation == _profiles.generation[profile]
|
||||
if (!_profile_refresh_required && !_profile_repair_required && _active_profile == profile && _profile_generation == _profiles.generation[profile]
|
||||
&& _physical_preamble == preamble) return mesh::RadioParamApplyResult::APPLIED;
|
||||
if (!validateProfile(p)) return mesh::RadioParamApplyResult::FAILED;
|
||||
const bool dual_profile = _profiles.enabled();
|
||||
@@ -526,8 +551,8 @@ mesh::RadioParamApplyResult RadioLibWrapper::tuneProfile(uint8_t profile) {
|
||||
beginProfileRetune(isInRecvMode() && !_rx_ps_armed && !_rx_ps_enabled);
|
||||
const uint8_t resume = beginReconfigure();
|
||||
if (resume > 1) {
|
||||
endProfileRetune(true); // guard deferred before changing hardware
|
||||
return mesh::RadioParamApplyResult::BUSY;
|
||||
endProfileRetune(resume == 2); // Only a BUSY guard deferred before changing hardware.
|
||||
return resume == 2 ? mesh::RadioParamApplyResult::BUSY : mesh::RadioParamApplyResult::FAILED;
|
||||
}
|
||||
const float old_freq = _cur_freq, old_bw = _cur_bw;
|
||||
const uint8_t old_sf = _cur_sf, old_cr = _cur_cr, old_profile = _active_profile;
|
||||
@@ -553,6 +578,7 @@ mesh::RadioParamApplyResult RadioLibWrapper::tuneProfile(uint8_t profile) {
|
||||
_active_profile = profile;
|
||||
_profile_generation = _profiles.generation[profile];
|
||||
_profile_refresh_required = false;
|
||||
_profile_repair_required = false;
|
||||
endReconfigure(resume);
|
||||
applied = !resume || isInRecvMode();
|
||||
}
|
||||
@@ -579,8 +605,12 @@ mesh::RadioParamApplyResult RadioLibWrapper::tuneProfile(uint8_t profile) {
|
||||
restored = restored && applyProtocol(old_protocol);
|
||||
#endif
|
||||
if (!restored) restored = restoreAfterDeepInit();
|
||||
if (restored) endReconfigure(resume);
|
||||
if (!restored || (resume && !isInRecvMode())) _profile_refresh_required = true;
|
||||
if (restored) {
|
||||
_profile_repair_required = false;
|
||||
endReconfigure(resume);
|
||||
}
|
||||
if (!restored || (resume && !isInRecvMode()))
|
||||
_profile_refresh_required = _profile_repair_required = true;
|
||||
}
|
||||
_profile_visit_stamp = profileTimestamp(dual_profile);
|
||||
// A profile retune re-enters RX on a different tuple. Do not read its
|
||||
@@ -669,7 +699,7 @@ void RadioLibWrapper::cacheBeaconRestoreParams() {
|
||||
mesh::RadioParamApplyResult RadioLibWrapper::prepareBeaconTransmit(
|
||||
const mesh::RadioProfileParams& p, int8_t power) {
|
||||
using Result = mesh::RadioParamApplyResult;
|
||||
if (_beacon_owned || _cw_active) return Result::BUSY;
|
||||
if (_beacon_owned || _cw_active || _profile_repair_required) return Result::BUSY;
|
||||
#if defined(MESH_HIL_TX_FREQ_MHZ)
|
||||
_hil_beacon_tx_frequency_mhz = 0;
|
||||
if (!mesh::hilTransmitFrequencyAllowed(p.freq)) return Result::FAILED;
|
||||
@@ -753,6 +783,7 @@ mesh::RadioParamApplyResult RadioLibWrapper::restoreBeaconReceive() {
|
||||
}
|
||||
claimRadioInterrupt();
|
||||
if (restored) {
|
||||
_profile_repair_required = false; // Full normal tuple/protocol/power/IRQ restore succeeded.
|
||||
state = STATE_IDLE;
|
||||
startRecv();
|
||||
restored = isInRecvMode();
|
||||
@@ -2035,7 +2066,7 @@ void RadioLibWrapper::startRecv() {
|
||||
#if MESH_ENABLE_TX_BEACONS
|
||||
if (_beacon_owned && !_beacon_restoring) return;
|
||||
#endif
|
||||
if (_cw_active) return;
|
||||
if (_cw_active || _profile_repair_required) return;
|
||||
int err = startReceiveMode();
|
||||
if (err == RADIOLIB_ERR_NONE) {
|
||||
// A very short frame may complete while startReceiveMode() returns.
|
||||
@@ -2190,7 +2221,7 @@ int RadioLibWrapper::recvRaw(uint8_t* bytes, int sz) {
|
||||
#if MESH_ENABLE_TX_BEACONS
|
||||
if (_beacon_owned) return 0;
|
||||
#endif
|
||||
if (_cw_active || state == STATE_TX_WAIT) return 0;
|
||||
if (_cw_active || state == STATE_TX_WAIT || _profile_repair_required) return 0;
|
||||
int len = 0;
|
||||
#if defined(USE_LR2021)
|
||||
bool receiver_stopped = false;
|
||||
|
||||
@@ -114,6 +114,9 @@ protected:
|
||||
bool _profile_rxps_suspended = false;
|
||||
bool _profile_saved_rxps = false;
|
||||
bool _profile_refresh_required = false;
|
||||
// A failed parameter transaction left the physical tuple untrusted. Unlike
|
||||
// an ordinary refresh, its FIFO and activity flags cannot identify a packet.
|
||||
bool _profile_repair_required = false;
|
||||
#if defined(NRF52_PLATFORM)
|
||||
// Published by the scanner before the final sleep predicates. The late
|
||||
// query uses integers only: floating-point work can leave a stale FPU event.
|
||||
|
||||
+13
@@ -351,6 +351,19 @@ static void recoveryOwnershipAdmission() {
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if (argc == 2) {
|
||||
if (!strcmp(argv[1], "profile-repair")) {
|
||||
RadioLibWrapper w; uint8_t bytes[8];
|
||||
w._profile_repair_required=true; w.events.record(); w.radio.packet_length=1;
|
||||
const auto arms=w.arms;
|
||||
assert(w.recvRaw(bytes,sizeof(bytes))==0);
|
||||
w.startRecv();
|
||||
assert(w.radio.fifo_reads==0 && w.arms==arms && w.hasPendingRadioInterrupt());
|
||||
// A trusted configuration refresh does not revoke old FIFO ownership.
|
||||
w._profile_repair_required=false;
|
||||
assert(w.recvRaw(bytes,sizeof(bytes))==1 && w.radio.fifo_reads==1);
|
||||
std::puts("PASS: untrusted profile RX waits for repair");
|
||||
return 0;
|
||||
}
|
||||
assert(!strcmp(argv[1], "recovery"));
|
||||
recoveryOwnershipAdmission();
|
||||
return 0;
|
||||
|
||||
@@ -76,6 +76,7 @@ struct RadioLibWrapper {
|
||||
PhysicalLayer* _radio;
|
||||
mesh::MainBoard* _board;
|
||||
bool _params_valid = true, _rx_ps_armed = true, _dbm_valid = false, _rx_boosted_gain_valid = false;
|
||||
bool _profile_repair_required = false;
|
||||
int state = 1;
|
||||
float _cur_freq = 906.875f, _cur_bw = 250;
|
||||
uint8_t _cur_sf = 11, _cur_cr = 5;
|
||||
@@ -136,14 +137,19 @@ static void failures_and_recovery() {
|
||||
assert(chip.sync == 0x12 && chip.crc && chip.explicit_header && !chip.iq);
|
||||
assert(chip.cached_crc && !chip.cached_implicit && !chip.cached_iq);
|
||||
wrapper._physical_protocol = Protocol::Meshtext;
|
||||
wrapper._profile_repair_required = true;
|
||||
assert(wrapper.restoreAfterDeepInit());
|
||||
assert(!wrapper._profile_repair_required);
|
||||
assert(chip.sync == 0x2b && chip.crc && chip.explicit_header && !chip.iq);
|
||||
assert(!wrapper._rx_ps_armed && wrapper.state == STATE_IDLE);
|
||||
chip.fail_at = fail;
|
||||
wrapper._profile_repair_required = true;
|
||||
assert(!wrapper.restoreAfterDeepInit());
|
||||
assert(wrapper._profile_repair_required);
|
||||
chip.fail_at = 0;
|
||||
wrapper._physical_protocol = Protocol::MeshCore;
|
||||
assert(wrapper.restoreAfterDeepInit());
|
||||
assert(!wrapper._profile_repair_required);
|
||||
assert(chip.sync == 0x12 && chip.crc && chip.explicit_header && !chip.iq);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ struct RadioLibWrapper {
|
||||
uint32_t _beacon_restore_at = 0, _beacon_cad_timeout = 0;
|
||||
uint8_t state = STATE_RX, _active_profile = 0;
|
||||
uint32_t _profile_generation = 1, _profile_visit_stamp = 0;
|
||||
bool _profile_refresh_required = false;
|
||||
bool _profile_refresh_required = false, _profile_repair_required = false;
|
||||
uint16_t _physical_preamble = 32;
|
||||
mesh::RadioProfileProtocol _physical_protocol = mesh::RadioProfileProtocol::MeshCore;
|
||||
float _cur_freq = 906, _cur_bw = 62.5;
|
||||
@@ -219,29 +219,56 @@ int main() {
|
||||
assert(w.recalibrations == 0 && w.deep_calls == 0);
|
||||
++cases;
|
||||
}
|
||||
for (unsigned reason = 0; reason < 5; ++reason) {
|
||||
for (unsigned reason = 0; reason < 6; ++reason) {
|
||||
RadioLibWrapper w;
|
||||
if (reason == 0) w.pending = true;
|
||||
if (reason == 1) w.packet = true;
|
||||
if (reason == 2) w.busy = true;
|
||||
if (reason == 3) w._cw_active = true;
|
||||
if (reason == 4) w.calibrating = true;
|
||||
if (reason == 5) w._profile_repair_required = true;
|
||||
assert(w.prepareBeaconTransmit(beacon(), 10) == Result::BUSY);
|
||||
assert(!w._beacon_owned && w.applications == 0 && w.power_sets == 0);
|
||||
++cases;
|
||||
}
|
||||
for (unsigned failure = 0; failure < 4; ++failure) {
|
||||
for (unsigned failure = 0; failure < 3; ++failure) {
|
||||
RadioLibWrapper w;
|
||||
if (failure == 0) w.modem.param_fail = true;
|
||||
if (failure == 1) w.protocol_fail = true;
|
||||
if (failure == 2) w.power_fail = true;
|
||||
if (failure == 3) w.modem.standby_fail = true;
|
||||
assert(w.prepareBeaconTransmit(beacon(true), 10) == Result::FAILED && w._beacon_owned);
|
||||
w.repair_on_deep = true;
|
||||
assert(w.restoreBeaconReceive() == Result::APPLIED && !w._beacon_owned);
|
||||
assert(w.modem.freq == 906 && w.modem.protocol == mesh::RadioProfileProtocol::MeshCore && w.modem.power == 20);
|
||||
++cases;
|
||||
}
|
||||
{
|
||||
RadioLibWrapper w;
|
||||
w.modem.standby_fail = true;
|
||||
assert(w.prepareBeaconTransmit(beacon(true), 10) == Result::BUSY);
|
||||
// Refused initial standby never admits a beacon lease or mutates PA/params.
|
||||
assert(!w._beacon_owned && w._profile_repair_required && w.state == STATE_IDLE);
|
||||
assert(w.applications == 0 && w.power_sets == 0 && w.protocols == 0);
|
||||
assert(w.modem.standbys == 1 && !w.board.before && !w.arms);
|
||||
w.startRecv();
|
||||
assert(w.arms == 0 && w.state == STATE_IDLE);
|
||||
assert(w.prepareBeaconTransmit(beacon(), 10) == Result::BUSY && w.modem.standbys == 1);
|
||||
w.repair_on_deep = true;
|
||||
assert(w.restoreAfterDeepInit() && !w._profile_repair_required);
|
||||
assert(w.prepareBeaconTransmit(beacon(true), 10) == Result::APPLIED && w._beacon_owned);
|
||||
assert(w.restoreBeaconReceive() == Result::APPLIED && !w._beacon_owned);
|
||||
++cases;
|
||||
}
|
||||
{
|
||||
RadioLibWrapper w;
|
||||
assert(w.prepareBeaconTransmit(beacon(true), 10) == Result::APPLIED);
|
||||
w._profile_repair_required = true;
|
||||
w.deep_ok = false; // A skipped first RX arm cannot be hidden by deep recovery.
|
||||
assert(w.restoreBeaconReceive() == Result::APPLIED && !w._beacon_owned);
|
||||
assert(!w._profile_repair_required && w.state == STATE_RX && w.arms == 1);
|
||||
assert(w.deep_calls == 0);
|
||||
++cases;
|
||||
}
|
||||
for (unsigned changed = 0; changed < 2; ++changed) {
|
||||
RadioLibWrapper w;
|
||||
w._profiles.secondary.params = w._profiles.primary;
|
||||
@@ -386,6 +413,10 @@ class BeaconRadioTest(unittest.TestCase):
|
||||
def test_ownership_and_snapshot_negative_controls(self):
|
||||
source = self.source()
|
||||
mutations = [
|
||||
("if (_beacon_owned || _cw_active || _profile_repair_required) return Result::BUSY;",
|
||||
"if (_beacon_owned || _cw_active) return Result::BUSY;"),
|
||||
("_profile_repair_required = false; // Full normal tuple/protocol/power/IRQ restore succeeded.",
|
||||
"/* omit repair retirement before ordinary RX */"),
|
||||
("if (_beacon_owned && !_beacon_restoring) return 2;", "/* remove ownership guard */"),
|
||||
("if (_beacon_owned && !_beacon_restoring) return;", "/* permit RX on beacon tuple */"),
|
||||
("bool applied = _radio->standby() == RADIOLIB_ERR_NONE",
|
||||
|
||||
@@ -117,6 +117,7 @@ struct RadioLibWrapper : mesh::Radio {
|
||||
uint8_t state = STATE_RX;
|
||||
RadioInterruptEvents events;
|
||||
bool hasPendingRadioInterrupt() const { return events.pending(); }
|
||||
bool claimRadioInterrupt() { return events.claim(); }
|
||||
unsigned register_failures=0;
|
||||
bool registerInterruptAction() {
|
||||
if (register_failures) { --register_failures; return false; }
|
||||
@@ -148,7 +149,7 @@ struct RadioLibWrapper : mesh::Radio {
|
||||
if (rx_gain_apply_ok) physical_rx_gain=enabled;
|
||||
return rx_gain_apply_ok;
|
||||
}
|
||||
bool _nf_calib_active=true, _profile_refresh_required=false;
|
||||
bool _nf_calib_active=true, _profile_refresh_required=false, _profile_repair_required=false;
|
||||
unsigned long _wd_observe_until=0;
|
||||
bool packet=false, busy=false, reset_ok=false, supported=true, deep_supported=true;
|
||||
unsigned rtc_stops=0, tunes=0, rx_starts=0, calibrations=0, resets=0;
|
||||
@@ -270,7 +271,7 @@ int main() {
|
||||
assert(w.setCarrierWave(profile,1250)==Result::APPLIED);
|
||||
assert(w.frequency==(profile ? 911.5f : 909.5f));
|
||||
assert(w.chip.carrier && !w.chip.callback && w.board.before==1 && w.board.awake);
|
||||
assert(w.rtc_stops==1 && !w._rx_ps_armed && !w._nf_calib_active);
|
||||
assert(w.chip.standby_calls==2 && !w._rx_ps_armed && !w._nf_calib_active);
|
||||
assert(w.beginReconfigure()==2);
|
||||
advance(1249); assert(w.serviceCarrierWave() && w.chip.carrier);
|
||||
advance(1); assert(w.serviceCarrierWave() && !w.chip.carrier);
|
||||
|
||||
@@ -315,6 +315,7 @@ struct CustomSX1262: SX1262 {
|
||||
};
|
||||
struct RadioLibWrapper {
|
||||
CustomSX1262 chip;CustomSX1262* _radio=&chip;
|
||||
bool _profile_repair_required=false;
|
||||
bool supported=true;
|
||||
bool supportsMeshtext() const { return supported; }
|
||||
bool _rx_ps_armed=true,_params_valid=true,_dbm_valid=false,_rx_boosted_gain_valid=false;
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
"""Execute failed transaction recovery without erasing fork feature ownership."""
|
||||
from pathlib import Path
|
||||
import os
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
from cpp_source import body
|
||||
from test_radio_profile_scan import HARNESS, ROOT
|
||||
|
||||
|
||||
class ProfileRepairTests(unittest.TestCase):
|
||||
def test_failed_apply_rollback_standby_and_trusted_refresh(self):
|
||||
source = (ROOT / 'src/helpers/radiolib/RadioLibWrappers.cpp').read_text()
|
||||
methods = '\n'.join(body(source, signature) for signature in (
|
||||
'uint8_t RadioLibWrapper::beginReconfigure(',
|
||||
'void RadioLibWrapper::endReconfigure(',
|
||||
'mesh::RadioParamApplyResult RadioLibWrapper::tuneProfile(',
|
||||
'mesh::RadioParamApplyResult RadioLibWrapper::trySetParams('))
|
||||
harness = HARNESS[:HARNESS.index('int main() {')]
|
||||
harness = harness.replace('bool standbyXOSC=false; int standby() { return 0; }',
|
||||
'bool standbyXOSC=false; int result=0; int standby() { return result; }')
|
||||
harness = harness.replace(' bool _cw_active=false;',
|
||||
' bool _cw_active=false, _beacon_owned=false, _beacon_restoring=false, fail_all=false;')
|
||||
harness = harness.replace('return !fail || f==909.5f;', 'return !fail_all && (!fail || f==909.5f);')
|
||||
harness = harness.replace('bool restoreAfterDeepInit() { return true; }',
|
||||
'bool restoreAfterDeepInit() { return false; }')
|
||||
harness = harness.replace(' void startRecv() {',
|
||||
' void startRecv() { if (_profile_repair_required) return;')
|
||||
harness += r'''
|
||||
int main() {
|
||||
using Result=mesh::RadioParamApplyResult;
|
||||
RadioLibWrapper w; w._rx_ps_armed=w._rx_ps_enabled=false;
|
||||
auto second=w._profiles.primary;second.freq=910.5;
|
||||
w._profiles.secondary.params=second;w._profiles.secondary.mode=mesh::RadioProfileMode::RxTx;
|
||||
w.fail_all=true;
|
||||
assert(w.tuneProfile(1)==Result::FAILED && w._profile_repair_required);
|
||||
assert(w._active_profile==0 && w._profile_refresh_required);
|
||||
w.fail_all=false;w.packet=true;w.events.record();
|
||||
assert(w.tuneProfile(0)==Result::APPLIED); // Untrusted stale activity cannot block repair.
|
||||
assert(!w._profile_repair_required && w.state==STATE_RX && !w.hasPendingRadioInterrupt());
|
||||
w._profile_refresh_required=true;w.events.record();
|
||||
const auto applies=w.applies;
|
||||
assert(w.tuneProfile(0)==Result::BUSY && w.applies==applies && w.hasPendingRadioInterrupt());
|
||||
w.events.claim();w.packet=false;w._profile_repair_required=true;
|
||||
w._beacon_owned=true;assert(w.beginReconfigure()==2);w._beacon_owned=false;
|
||||
w._cw_active=true;assert(w.beginReconfigure()==2);w._cw_active=false;
|
||||
w.state=STATE_TX_WAIT;assert(w.beginReconfigure()==2);w.state=STATE_IDLE;
|
||||
w.busy=true;assert(w.beginReconfigure()==2);w.busy=false;
|
||||
w.chip.result=-7;assert(w.tuneProfile(0)==Result::FAILED && w._profile_repair_required);
|
||||
w.chip.result=0;assert(w.tuneProfile(0)==Result::APPLIED && w.state==STATE_RX);
|
||||
w.fail_all=true;
|
||||
assert(w.trySetParams(911.5,125,8,5)==Result::FAILED && w._profile_repair_required);
|
||||
w.fail_all=false;assert(w.tuneProfile(0)==Result::APPLIED && !w._profile_repair_required);
|
||||
}
|
||||
'''
|
||||
harness = harness.replace('@METHODS@', methods)
|
||||
with tempfile.TemporaryDirectory(prefix='profile-repair-') as folder:
|
||||
cpp, exe = Path(folder)/'test.cpp', Path(folder)/'test.exe'
|
||||
(Path(folder)/'Arduino.h').write_text('#pragma once\n#include <cstdint>\n#include <cmath>\n')
|
||||
cpp.write_text(harness)
|
||||
flags=[] if os.name=='nt' else ['-fsanitize=address,undefined','-fno-pie','-no-pie']
|
||||
built=subprocess.run([os.environ.get('CXX','g++'),'-std=c++17',*flags,
|
||||
'-DMESH_ENABLE_TX_BEACONS=1','-I',folder,'-I',str(ROOT/'src'),str(cpp),'-o',str(exe)],
|
||||
capture_output=True,text=True,timeout=60)
|
||||
self.assertEqual(built.returncode,0,built.stderr)
|
||||
ran=subprocess.run([str(exe)],capture_output=True,text=True,timeout=10)
|
||||
self.assertEqual(ran.returncode,0,ran.stderr)
|
||||
|
||||
|
||||
if __name__=='__main__': unittest.main()
|
||||
@@ -40,6 +40,7 @@ struct RadioLibWrapper {
|
||||
Chip chip; Chip* _radio = &chip;
|
||||
mesh::RadioProfiles _profiles;
|
||||
bool _params_valid=true, packet=false, busy=false, fail=false;
|
||||
bool _profile_repair_required=false, _rx_hold_continuous=false;
|
||||
bool _rx_ps_enabled=true, _rx_ps_armed=true, _rx_ps_continuous_fallback=false;
|
||||
bool _profile_saved_rxps=false, _profile_rxps_suspended=false;
|
||||
bool _profile_standby_held=false, _saved_standby_xosc=false;
|
||||
|
||||
@@ -65,6 +65,7 @@ struct RadioLibWrapper {
|
||||
bool hasPendingRadioInterrupt() const { return events.pending(); }
|
||||
bool claimRadioInterrupt() { return events.claim(); }
|
||||
bool _cw_active = false;
|
||||
bool _profile_repair_required = false;
|
||||
bool serviceCarrierWave() { return false; } // dedicated CW harness owns this path
|
||||
mesh::RadioProfiles _profiles;
|
||||
uint32_t _profile_visit_stamp = 0;
|
||||
|
||||
@@ -185,6 +185,12 @@ class RadioReceiveRetryTests(unittest.TestCase):
|
||||
self.assertIn('PASS: receive retry lifecycle', result.stdout)
|
||||
print(result.stdout, end='')
|
||||
|
||||
def test_failed_profile_transaction_blocks_fifo_delivery_and_rx_rearm(self):
|
||||
with tempfile.TemporaryDirectory(prefix='meshcore-profile-repair-rx-') as folder:
|
||||
result = self.run_probe(self.build(folder), 'profile-repair')
|
||||
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
|
||||
self.assertIn('PASS: untrusted profile RX waits for repair', result.stdout)
|
||||
|
||||
def test_old_hot_poll_and_pending_calibration_veto_negative_controls(self):
|
||||
for mutation in ('unpaced', 'pending_veto', 'agc_fast'):
|
||||
with self.subTest(mutation=mutation), tempfile.TemporaryDirectory(
|
||||
|
||||
@@ -45,6 +45,8 @@ struct Driver {
|
||||
uint8_t state = STATE_IDLE;
|
||||
bool receiving = false, pending_irq = false, busy = false, supported = true;
|
||||
bool _rx_ps_armed = false, _cw_active = false;
|
||||
bool _profile_repair_required = false, _profile_refresh_required = false;
|
||||
bool _rx_hold_continuous = false;
|
||||
bool _dbm_valid = true, _rx_boosted_gain_valid = false;
|
||||
int8_t _cur_dbm = 7;
|
||||
bool _cur_rx_boosted_gain = false;
|
||||
@@ -53,6 +55,7 @@ struct Driver {
|
||||
std::deque<bool> gain_results;
|
||||
Chip* _radio = &chip;
|
||||
bool hasPendingRadioInterrupt() { return pending_irq; }
|
||||
bool claimRadioInterrupt() { const bool pending = pending_irq; pending_irq = false; return pending; }
|
||||
bool isChipBusy() { return busy; }
|
||||
bool isReceivingPacket() { return receiving; }
|
||||
void stopReceiveDutyCycle() { _rx_ps_armed = false; }
|
||||
|
||||
Reference in new issue
Block a user