Files
MeshCore/test/test_sx1262_protocol_batching.py
T

174 lines
8.3 KiB
Python

"""Host-only exercise of the actual SX1262 owned protocol/packet staging path."""
from pathlib import Path
import unittest
import test_sx1262_batched_modulation as compiler
from test_radio_receive_contract import method
ROOT = Path(__file__).resolve().parents[1]
class ProtocolBatchingTests(unittest.TestCase):
compile_run = compiler.BatchedModulationTests.compile_run
def test_owned_protocol_commit_errors_and_full_setup_fallback(self):
source = (ROOT / "src/helpers/radiolib/CustomSX1262.h").read_text()
state = (ROOT / "src/helpers/radiolib/SX1262ProfileSwitchState.h").read_text()
methods = "\n".join(method(source, signature).replace(" override", "") for signature in (
"int16_t setCRC(", "int16_t explicitHeader()", "int16_t invertIQ(",
"int16_t setProfileProtocol(", "int16_t startReceive()"))
harness = r'''
#include <cassert>
#include <cstdint>
#include <cstddef>
#include <string>
@STATE@
#define MC_SX1262_FAST_PACKET_CACHE 1
#define RADIOLIB_ERR_NONE 0
#define RADIOLIB_SX126X_RX_TIMEOUT_INF 0xffffff
#define RADIOLIB_SX126X_PACKET_TYPE_LORA 1
#define RADIOLIB_SX126X_LORA_CRC_ON 1
#define RADIOLIB_SX126X_LORA_HEADER_EXPLICIT 0
#define RADIOLIB_SX126X_LORA_IQ_STANDARD 0
#define RADIOLIB_RADIO_MODE_NONE 0
#define RADIOLIB_IRQ_RX_DEFAULT_FLAGS 1
#define RADIOLIB_IRQ_RX_DEFAULT_MASK 2
#define RADIOLIB_IRQ_PREAMBLE_DETECTED 3
struct Module {
enum {MODE_RX=1}; std::string* trace;
void setRfSwitchState(int n) { assert(n==MODE_RX);*trace+='W'; }
};
struct SX1262 {
std::string trace; char fail=0; bool standbyXOSC=true;
size_t preambleLengthLoRa=32;
uint8_t crcTypeLoRa=1, implicitLen=255, headerType=0, invertIQEnabled=0;
uint8_t physicalSync=0x12, physicalCrc=1, physicalLen=255, physicalHeader=0, physicalIq=0;
bool iqWorkaroundNormal=true;
unsigned packetWrites=0, workaroundWrites=0, syncWrites=0;
uint32_t rxTimeout=0; int stagedMode=3;
Module module{&trace};Module* getMod() { return &module; }
int16_t call(char c) { trace+=c;return fail==c ? -100:0; }
int16_t setSyncWord(uint8_t sync) {
++syncWrites;physicalSync=sync;return call('Y');
}
// Model the pinned RadioLib setPacketParams() IQ-register workaround as
// part of the packet commit, including an error before the command itself.
int16_t setPacketParams(size_t p,uint8_t crc,uint8_t len,uint8_t header,uint8_t iq) {
assert(p==preambleLengthLoRa);
++workaroundWrites;
if(call('I')) return -100;
iqWorkaroundNormal=iq==0;
++packetWrites;
if(call('P')) return -100;
physicalCrc=crc;physicalLen=len;physicalHeader=header;physicalIq=iq;return 0;
}
int16_t setCRC(uint8_t crc,uint16_t,uint16_t,bool) {
if(call('C')) return -100;crcTypeLoRa=crc;
return setPacketParams(preambleLengthLoRa,crcTypeLoRa,implicitLen,headerType,invertIQEnabled);
}
int16_t explicitHeader() {
if(call('E')) return -100;
const int rc=setPacketParams(preambleLengthLoRa,crcTypeLoRa,255,0,invertIQEnabled);
if(!rc) {implicitLen=255;headerType=0;}return rc;
}
int16_t invertIQ(bool enabled) {
if(call('i')) return -100;invertIQEnabled=enabled;
return setPacketParams(preambleLengthLoRa,crcTypeLoRa,implicitLen,headerType,invertIQEnabled);
}
int16_t clearIrqStatus() { return call('c'); }
int16_t setRx(uint32_t timeout) { assert(timeout==0xffffff);return call('R'); }
int16_t startReceive(uint32_t,uint32_t,uint32_t,size_t) {
const int rc=setPacketParams(preambleLengthLoRa,crcTypeLoRa,implicitLen,headerType,invertIQEnabled);
return rc ? rc:call('F');
}
uint8_t getPacketType() { call('Q');return 1; }
};
struct Radio: SX1262 {
SX1262ProfileSwitchState _profileSwitch{true};
bool _coldStandby=false;uint32_t _profileFastResumes=0;
@METHODS@
};
void owned(Radio& r) {
r._profileSwitch.rxResult(0,true);
r._profileSwitch.packetResult(0,r.preambleLengthLoRa,r.crcTypeLoRa,r.implicitLen,r.headerType,r.invertIQEnabled);
r._profileSwitch.begin(true,true);r._profileSwitch.standbyResult(0);
assert(r._profileSwitch.canResumeFast());r.trace.clear();
}
void restored(Radio& r) {
r._profileSwitch.end(false);r.fail=0;r.trace.clear();
assert(r.setProfileProtocol(0x12)==0);
assert(r.trace=="YCIPEIPiIP");
assert(r.physicalSync==0x12 && r.physicalCrc==1 && r.physicalLen==255
&& r.physicalHeader==0 && r.physicalIq==0 && r.iqWorkaroundNormal);
assert(r.startReceive()==0 && r._profileSwitch.rxValid);
}
int main() {
// Staging itself is sync-only; resume performs exactly one full packet
// write and one IQ workaround. An acknowledged old tuple cannot skip it.
for(uint8_t sync:{uint8_t(0x12),uint8_t(0x2b)}) {
Radio r;owned(r);assert(r.setProfileProtocol(sync)==0 && r.trace=="Y");
assert(!r._profileSwitch.packetValid && r._profileSwitch.rxValid);
assert(r.packetWrites==0 && r.workaroundWrites==0);
assert(r.startReceive()==0 && r.trace=="YcIPWR");
assert(r.packetWrites==1 && r.workaroundWrites==1 && r.syncWrites==1);
assert(r._profileSwitch.packetValid && r._profileSwitch.rxValid);
assert(r.physicalSync==sync && r.iqWorkaroundNormal && r.physicalLen==255);
}
// A nonstandard prior tuple is repaired, not merely assumed compatible.
{ Radio r;r.crcTypeLoRa=r.physicalCrc=0;r.implicitLen=r.physicalLen=16;
r.headerType=r.physicalHeader=1;r.invertIQEnabled=r.physicalIq=1;r.iqWorkaroundNormal=false;
owned(r);assert(r.setProfileProtocol(0x2b)==0);
assert(r.physicalCrc==0 && r.physicalHeader==1 && r.physicalIq==1);
assert(r.startReceive()==0 && r.packetWrites==1 && r.workaroundWrites==1);
assert(r.physicalCrc==1 && r.physicalHeader==0 && r.physicalIq==0 && r.iqWorkaroundNormal);
}
// Setup, partial/busy hops, disabled optimization, and a revoked RX context
// must execute all normal setters. No staging before acknowledged standby.
for(int gate=0;gate<5;++gate) {
Radio r;
if(gate==1) {r._profileSwitch.rxResult(0,true);r._profileSwitch.begin(true,true);}
if(gate==2) {owned(r);r._profileSwitch.enabled=false;r._profileSwitch.invalidate();}
if(gate==3) {owned(r);r._profileSwitch.invalidate();}
if(gate==4) {r._profileSwitch.rxResult(0,true);r._profileSwitch.begin(false,true);r._profileSwitch.standbyResult(0);}
r.trace.clear();assert(r.setProfileProtocol(0x2b)==0 && r.trace=="YCIPEIPiIP");
assert(r.packetWrites==3 && r.workaroundWrites==3 && !r._profileSwitch.packetValid);
}
// Sync, IRQ-clear, IQ-workaround, packet, and RX failures all revoke reuse;
// wrapper rollback can then restore old wire settings through full setup.
for(char failure:std::string("YcIPR")) {
Radio r;owned(r);r.fail=failure;
if(failure=='Y') assert(r.setProfileProtocol(0x2b)==-100);
else {assert(r.setProfileProtocol(0x2b)==0);assert(r.startReceive()==-100);}
assert(r._profileSwitch.failed() && !r._profileSwitch.rxValid && !r._profileSwitch.packetValid);
restored(r);
}
// Fallback setup errors propagate; no later setter hides an earlier error.
for(char failure:std::string("CEi")) {
Radio r;r.fail=failure;assert(r.setProfileProtocol(0x2b)==-100);
assert(r.trace.back()==failure && !r._profileSwitch.rxValid);restored(r);
}
}
'''
self.compile_run(harness.replace("@STATE@", state).replace("@METHODS@", methods))
def test_wrapper_uses_batched_protocol_and_pinned_iq_workaround(self):
wrapper = (ROOT / "src/helpers/radiolib/CustomSX1262Wrapper.h").read_text()
apply = method(wrapper, "bool applyProtocol(")
self.assertIn("setProfileProtocol(sync)", apply)
self.assertNotIn("setCRC(", apply)
radio = (ROOT / "src/helpers/radiolib/CustomSX1262.h").read_text()
resume = method(radio, "int16_t startReceive()")
self.assertIn("rc = setPacketParams(", resume)
self.assertLess(resume.index("rc = setPacketParams("), resume.index("rc = setRx("))
# The installed/pinned implementation delegates its required IQ-register
# repair to setPacketParams. Keep this assumption explicit and tested.
commands = ROOT / ".pio/libdeps/heltec_v4_2_v4_3_companion_radio_full_femon/RadioLib/src/modules/SX126x/SX126x_commands.cpp"
if commands.exists():
packet = method(commands.read_text(), "int16_t SX126x::setPacketParams(")
self.assertIn("fixInvertedIQ(invertIQ)", packet)
self.assertLess(packet.index("fixInvertedIQ("), packet.index("SPIwriteStream("))
if __name__ == "__main__":
unittest.main()