Reuse the confirmed LoRa modem only inside acknowledged warm continuous-RX hops and revoke that capability before modem initialization or after failures. Opt the tested T-Deck into IQ workaround reuse and an exact 8,888,888 Hz SPI setting. Keep the existing BUSY, packet-ownership and recovery checks; other boards retain their modem-cache default.
184 lines
8.0 KiB
Python
184 lines
8.0 KiB
Python
"""Execute boot selection, regulator errors, Morse resets and radio recovery."""
|
|
from pathlib import Path
|
|
import unittest
|
|
from test_morse_tx import compile_run
|
|
from test_radio_receive_contract import method
|
|
from radio_regulator_harness import regulator_members
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
|
|
HARNESS = r'''
|
|
#include <cassert>
|
|
#include <cstdint>
|
|
#include <cstddef>
|
|
#include <initializer_list>
|
|
#include <helpers/radiolib/RadioRegulatorState.h>
|
|
#define RADIOLIB_ERR_NONE 0
|
|
#define RADIOLIB_ERR_UNKNOWN -16
|
|
#define RADIOLIB_SX126X_SYNC_WORD_PRIVATE 0x12
|
|
#define RADIOLIB_LR11X0_LORA_SYNC_WORD_PRIVATE 0x12
|
|
#define RADIOLIB_LR2021_LORA_SYNC_WORD_PRIVATE 0x12
|
|
#define LORA_FREQ 909.5f
|
|
#define LORA_BW 500.0f
|
|
#define LORA_SF 5
|
|
#define LORA_CR 5
|
|
#define LORA_TX_POWER 2
|
|
#define MESH_DEBUG_PRINTLN(...) ((void)0)
|
|
struct SPIClass {};
|
|
struct Module {};
|
|
namespace mesh {
|
|
struct Logger { void print(const char*) {} void println(int) {} };
|
|
inline Logger& usbLoggingPort() { static Logger logger;return logger; }
|
|
struct MorseRx { static constexpr float ReceiverBitRateKbps=4.8f; };
|
|
namespace lr2021 { inline bool isHighBand(float f) { return f>1900; } }
|
|
}
|
|
struct Native {
|
|
int fail=0,spi=0,regulator_writes=0,modem_resets=0;
|
|
bool useRegulatorLDO=true,standbyXOSC=true;
|
|
float tcxoVoltage=1.6f;
|
|
uint8_t syncWordLength=4,preambleDetLength=4,gainModeLf=1,gainModeHf=3;
|
|
int step(int phase) { ++spi;return fail==phase ? -100-phase : 0; }
|
|
int nativeReset() { ++modem_resets;useRegulatorLDO=true;return step(1); }
|
|
int16_t begin(float,float,uint8_t,uint8_t,uint8_t,int8_t,uint16_t,float) {
|
|
return nativeReset();
|
|
}
|
|
int16_t begin(float,float,uint8_t,uint8_t,uint8_t,int8_t,uint16_t,float,bool ldo) {
|
|
assert(!standbyXOSC);
|
|
int result=nativeReset();
|
|
if(!result) { ++regulator_writes;result=step(2);if(!result)useRegulatorLDO=ldo; }
|
|
return result;
|
|
}
|
|
virtual int16_t beginFSK(float,float,float,float,int8_t,uint16_t,float,bool ldo) {
|
|
int result=nativeReset();
|
|
if(!result) { ++regulator_writes;result=step(2);if(!result)useRegulatorLDO=ldo; }
|
|
return result;
|
|
}
|
|
int16_t beginGFSK(float,float,float,float,int8_t,uint16_t,float) { return nativeReset(); }
|
|
int16_t setRegulatorDCDC() {
|
|
++regulator_writes;int result=step(2);if(!result)useRegulatorLDO=false;return result;
|
|
}
|
|
int16_t setRegulatorLDO() {
|
|
++regulator_writes;int result=step(2);if(!result)useRegulatorLDO=true;return result;
|
|
}
|
|
int16_t applyMeshCoreTcxoDelay() { return step(3); }
|
|
int16_t setCRC(int) { return step(4); }
|
|
int16_t setRxBoostedGainMode(int) { return step(5); }
|
|
int16_t setSyncWord(uint8_t*,size_t) { return step(6); }
|
|
int16_t explicitHeader() { return step(7); }
|
|
int16_t setDioAsRfSwitch(uint8_t,uint8_t,uint8_t,uint8_t,uint8_t,uint8_t,uint8_t,uint8_t) { return step(8); }
|
|
struct Profile { void invalidate() {} };
|
|
struct RfSwitch {
|
|
template<class Callback> int restore(Callback callback) {
|
|
const uint8_t modes[]={0,0,0,0,0,0,0};return callback(0,modes);
|
|
}
|
|
};
|
|
};
|
|
using @CHIP@=Native;
|
|
class Custom:public @CHIP@ {
|
|
@POLICY@
|
|
bool _coldStandby=false,_tcxoWakePending=false,_profilePacketDirty=true,_rx_boosted=false;
|
|
Profile _profileSwitch;
|
|
RfSwitch _morse_rf_switch;
|
|
public:
|
|
int restoreRfSwitchTable() { return 0; }
|
|
@INIT@
|
|
@MORSE@
|
|
int init() { return @INIT_CALL@; }
|
|
int morse() { return @MORSE_CALL@; }
|
|
};
|
|
@WRAPPER@
|
|
int main() {
|
|
using mesh::power::RegulatorMode;
|
|
constexpr bool supported_dcdc=@DCDC@;
|
|
for (auto requested:{RegulatorMode::Default,RegulatorMode::Ldo,RegulatorMode::Dcdc}) {
|
|
Custom radio;Wrapper wrapper{&radio};
|
|
assert(wrapper.activeRegulatorMode()==RegulatorMode::Default && radio.spi==0);
|
|
assert(!wrapper.configureBootRegulator(static_cast<RegulatorMode>(3)));
|
|
assert(wrapper.regulatorModes()==radio.regulatorModes());
|
|
const bool supported=requested!=RegulatorMode::Dcdc || supported_dcdc;
|
|
assert(wrapper.configureBootRegulator(requested)==supported);
|
|
assert(radio.spi==0); // boot configuration cannot send commands mid-RX/TX
|
|
if(!supported) continue;
|
|
const auto expected=requested==RegulatorMode::Default ? wrapper.defaultRegulatorMode() : requested;
|
|
assert(radio.init()==0 && wrapper.activeRegulatorMode()==expected);
|
|
assert(radio.useRegulatorLDO==(expected==RegulatorMode::Ldo));
|
|
assert(!wrapper.configureBootRegulator(RegulatorMode::Default));
|
|
// Both transitions reset the native modem; the saved choice survives each.
|
|
assert(radio.morse()==0 && wrapper.activeRegulatorMode()==expected);
|
|
assert(radio.useRegulatorLDO==(expected==RegulatorMode::Ldo));
|
|
assert(radio.init()==0 && wrapper.activeRegulatorMode()==expected);
|
|
assert(radio.useRegulatorLDO==(expected==RegulatorMode::Ldo));
|
|
for(int fault:{1,2}) {
|
|
if(fault==2 && @SKIP_LDO_WRITE@) continue;
|
|
radio.fail=fault;
|
|
assert(radio.morse()!=0);
|
|
assert(wrapper.activeRegulatorMode()==RegulatorMode::Default);
|
|
assert(!wrapper.configureBootRegulator(RegulatorMode::Ldo));
|
|
radio.fail=0;
|
|
assert(radio.init()==0 && wrapper.activeRegulatorMode()==expected);
|
|
assert(radio.useRegulatorLDO==(expected==RegulatorMode::Ldo));
|
|
radio.fail=fault;
|
|
assert(radio.init()!=0 && wrapper.activeRegulatorMode()==RegulatorMode::Default);
|
|
radio.fail=0;
|
|
assert(radio.init()==0 && wrapper.activeRegulatorMode()==expected);
|
|
}
|
|
}
|
|
// A failed first init must lock the boot policy as firmly as a successful one.
|
|
Custom failed;
|
|
assert(failed.configureBootRegulator(RegulatorMode::Ldo));failed.fail=1;
|
|
assert(failed.init()!=0 && !failed.configureBootRegulator(RegulatorMode::Default));
|
|
}
|
|
'''
|
|
|
|
|
|
class RadioRegulatorTests(unittest.TestCase):
|
|
def exercise(self, chip, flags=(), dcdc=True):
|
|
source = (ROOT / f'src/helpers/radiolib/Custom{chip}.h').read_text()
|
|
if chip == 'LR2021':
|
|
initialize = method(source, 'bool std_init(')
|
|
init_call = 'std_init() ? 0 : -1'
|
|
else:
|
|
initialize = method(source, 'int16_t begin(float')
|
|
init_call = 'begin()'
|
|
if chip == 'SX1262':
|
|
morse = method(source, 'int16_t beginFSK(float')
|
|
morse_call = 'beginFSK()'
|
|
else:
|
|
morse = method(source, 'int16_t beginMorseReceive(')
|
|
morse_call = 'beginMorseReceive(902.1f)'
|
|
wrapper_source = (ROOT / f'src/helpers/radiolib/Custom{chip}Wrapper.h').read_text()
|
|
delegates = '\n'.join(method(wrapper_source, signature).replace(' override', '')
|
|
for signature in ('uint8_t regulatorModes(',
|
|
'mesh::power::RegulatorMode defaultRegulatorMode(',
|
|
'bool configureBootRegulator(', 'mesh::power::RegulatorMode activeRegulatorMode('))
|
|
wrapper = f'using Custom{chip}=Custom;\nstruct Wrapper {{ Custom* _radio;\n{delegates}\n}};'
|
|
code = HARNESS.replace('@WRAPPER@', wrapper)
|
|
for marker, value in {'@CHIP@': chip, '@POLICY@': regulator_members(source),
|
|
'@INIT@': initialize, '@MORSE@': morse, '@INIT_CALL@': init_call,
|
|
'@MORSE_CALL@': morse_call, '@DCDC@': str(dcdc).lower(),
|
|
'@SKIP_LDO_WRITE@': str(chip == 'LR2021' and not dcdc).lower()}.items():
|
|
code = code.replace(marker, value)
|
|
compile_run(self, code, flags=('-Wno-unused-parameter', *flags))
|
|
|
|
def test_sx1262_boot_selection_morse_failures_and_recovery(self):
|
|
self.exercise('SX1262')
|
|
|
|
def test_sx1262_forced_ldo_recipe_cannot_enable_missing_dcdc_network(self):
|
|
self.exercise('SX1262', ('-DSX126X_USE_REGULATOR_LDO=1',), dcdc=False)
|
|
|
|
def test_lr1110_boot_selection_morse_failures_and_recovery(self):
|
|
self.exercise('LR1110')
|
|
|
|
def test_lr1121_boot_selection_morse_failures_and_recovery(self):
|
|
self.exercise('LR1121')
|
|
|
|
def test_lr2021_verified_layout_boot_selection_morse_failures_and_recovery(self):
|
|
self.exercise('LR2021', ('-DLR2021_USE_DCDC=1',))
|
|
|
|
def test_lr2021_unverified_layout_cannot_enable_simo(self):
|
|
self.exercise('LR2021', ('-DLR2021_USE_DCDC=0',), dcdc=False)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|