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.
202 lines
8.2 KiB
Python
202 lines
8.2 KiB
Python
"""Execute production modem-query reuse and initializer invalidation gates."""
|
|
from pathlib import Path
|
|
import unittest
|
|
|
|
from test_radio_receive_contract import method
|
|
import test_sx1262_batched_modulation as compiler
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
|
|
|
|
class ModemCacheTests(unittest.TestCase):
|
|
compile_run = compiler.BatchedModulationTests.compile_run
|
|
|
|
def test_confirmed_modem_requires_owned_warm_rx_and_survives_no_failure(self):
|
|
source = (ROOT / "src/helpers/radiolib/CustomSX1262.h").read_text()
|
|
state = (ROOT / "src/helpers/radiolib/SX1262ProfileSwitchState.h").read_text().replace("#pragma once", "")
|
|
signatures = ("void invalidateMorseModemCache(", "void setProfileSwitchOptimization(",
|
|
"int16_t begin(const ConfigLoRa_t&", "int16_t beginFSK(const ConfigFSK_t&",
|
|
"int16_t beginLRFHSS(const ConfigLRFHSS_t&", "int16_t setModem(",
|
|
"int16_t setLoRaModulationParams(")
|
|
production = "\n".join(method(source, signature).replace(" override", "") for signature in signatures)
|
|
fixture = r'''
|
|
#include <cassert>
|
|
#include <cmath>
|
|
#include <cstdint>
|
|
#include <cstddef>
|
|
@DEFINES@
|
|
@STATE@
|
|
using std::isfinite;
|
|
#define RADIOLIB_ERR_NONE 0
|
|
#define RADIOLIB_ERR_INVALID_SPREADING_FACTOR -1
|
|
#define RADIOLIB_ERR_INVALID_CODING_RATE -2
|
|
#define RADIOLIB_ERR_INVALID_BANDWIDTH -3
|
|
#define RADIOLIB_ERR_WRONG_MODEM -4
|
|
#define RADIOLIB_SX126X_PACKET_TYPE_LORA 1
|
|
#define RADIOLIB_SX126X_LORA_BW_7_8 0
|
|
#define RADIOLIB_SX126X_LORA_BW_10_4 8
|
|
#define RADIOLIB_SX126X_LORA_BW_15_6 1
|
|
#define RADIOLIB_SX126X_LORA_BW_20_8 9
|
|
#define RADIOLIB_SX126X_LORA_BW_31_25 2
|
|
#define RADIOLIB_SX126X_LORA_BW_41_7 10
|
|
#define RADIOLIB_SX126X_LORA_BW_62_5 3
|
|
#define RADIOLIB_SX126X_LORA_BW_125_0 4
|
|
#define RADIOLIB_SX126X_LORA_BW_250_0 5
|
|
#define RADIOLIB_SX126X_LORA_BW_500_0 6
|
|
struct ConfigLoRa_t {};
|
|
struct ConfigFSK_t {};
|
|
struct ConfigLRFHSS_t {};
|
|
using ModemType_t = int;
|
|
struct SX1262 {
|
|
SX1262ProfileSwitchState* cache = nullptr;
|
|
int result = 0, initializations = 0;
|
|
int init() {
|
|
// The capability must be revoked BEFORE any possibly failing base call.
|
|
assert(cache && !cache->rxValid && !cache->active);
|
|
assert(!cache->modulationValid && !cache->packetValid);
|
|
++initializations;
|
|
return result;
|
|
}
|
|
int16_t begin(const ConfigLoRa_t&) { return init(); }
|
|
virtual int16_t beginFSK(const ConfigFSK_t&) { return init(); }
|
|
virtual int16_t beginLRFHSS(const ConfigLRFHSS_t&) { return init(); }
|
|
int16_t beginFSK(float) { return beginFSK(ConfigFSK_t{}); }
|
|
int16_t beginLRFHSS(float) { return beginLRFHSS(ConfigLRFHSS_t{}); }
|
|
int16_t setModem(ModemType_t) { return init(); }
|
|
};
|
|
struct Radio : SX1262 {
|
|
SX1262ProfileSwitchState _profileSwitch{true};
|
|
bool _coldStandby = false, _tcxoWakePending = true;
|
|
uint8_t spreadingFactor = 9, bandwidth = 4, codingRate = 3, ldrOptimize = 0;
|
|
float bandwidthKhz = 125;
|
|
bool ldroAuto = true;
|
|
unsigned queries = 0, writes = 0;
|
|
uint8_t packetType = 1;
|
|
int16_t writeResult = 0;
|
|
#ifdef MC_SX1262_HIL
|
|
bool hilSkipConfirmedModemQuery = false;
|
|
uint32_t hilModemQuerySkips = 0;
|
|
#endif
|
|
using SX1262::beginFSK;
|
|
using SX1262::beginLRFHSS;
|
|
Radio() { cache = &_profileSwitch; }
|
|
uint8_t getPacketType() { ++queries; return packetType; }
|
|
int16_t setModulationParams(uint8_t sf, uint8_t bw, uint8_t cr, uint8_t ldro) {
|
|
assert(spreadingFactor == sf && bandwidth == bw && codingRate == cr);
|
|
ldrOptimize = ldroAuto ? ((1U << sf) / bandwidthKhz >= 16.0f) : ldro;
|
|
++writes;
|
|
return writeResult;
|
|
}
|
|
@PRODUCTION@
|
|
};
|
|
bool skipsModem(const Radio& r) {
|
|
#ifdef MC_SX1262_HIL
|
|
return r.hilSkipConfirmedModemQuery;
|
|
#else
|
|
(void)r;
|
|
return MC_SX1262_FAST_MODEM_CACHE != 0;
|
|
#endif
|
|
}
|
|
void selectCache(Radio& r, bool enabled) {
|
|
#ifdef MC_SX1262_HIL
|
|
r.hilSkipConfirmedModemQuery = enabled;
|
|
#else
|
|
(void)r; (void)enabled;
|
|
#endif
|
|
}
|
|
void prime(Radio& r) {
|
|
// Models the full RX path's successful live LoRa confirmation. The ordinary
|
|
// lifecycle tests separately execute startReceive's real confirmation path.
|
|
r._profileSwitch.end(true);
|
|
r._profileSwitch.rxResult(0, true);
|
|
r._profileSwitch.begin(true, true);
|
|
r._profileSwitch.standbyResult(0);
|
|
}
|
|
int main() {
|
|
for (int requested = 0; requested < 2; ++requested) {
|
|
Radio r; selectCache(r, requested);
|
|
// No existing RX context: even an opted-in image must perform the query.
|
|
assert(r.setLoRaModulationParams(125, 9, 7) == 0 && r.queries == 1);
|
|
prime(r);
|
|
assert(r.setLoRaModulationParams(125, 9, 7) == 0 && r.writes == 1);
|
|
assert(r.queries == (skipsModem(r) ? 1U : 2U));
|
|
// A changed modulation tuple still writes the complete tuple. Only the
|
|
// confirmed packet-type read may disappear.
|
|
assert(r.setLoRaModulationParams(500, 8, 5) == 0 && r.writes == 2);
|
|
assert(r.queries == (skipsModem(r) ? 1U : 3U));
|
|
#ifdef MC_SX1262_HIL
|
|
assert(r.hilModemQuerySkips == (requested ? 2U : 0U));
|
|
#endif
|
|
// Every ownership gate independently restores the live query.
|
|
for (int gate = 0; gate < 9; ++gate) {
|
|
Radio x; selectCache(x, requested); prime(x);
|
|
switch (gate) {
|
|
case 0: x._profileSwitch.end(true); break;
|
|
case 1: x._profileSwitch.begin(true, true); break; // standby missing
|
|
case 2: x._profileSwitch.begin(false, true); break; // discontinuous RX
|
|
case 3: x._profileSwitch.begin(true, false); break; // cold oscillator
|
|
case 4: x.setProfileSwitchOptimization(false); break;
|
|
case 5: x._profileSwitch.rxResult(-707, true); break;
|
|
case 6: x._profileSwitch.standbyResult(-707); break;
|
|
case 7: x._profileSwitch.end(false); break;
|
|
case 8: x.invalidateMorseModemCache(); break;
|
|
}
|
|
assert(x.setLoRaModulationParams(125, 9, 7) == 0 && x.queries == 1);
|
|
#ifdef MC_SX1262_HIL
|
|
assert(x.hilModemQuerySkips == 0);
|
|
#endif
|
|
}
|
|
// Failed modulation must revoke query reuse before the restoration path.
|
|
Radio failed; selectCache(failed, requested); prime(failed);
|
|
failed.writeResult = -707;
|
|
assert(failed.setLoRaModulationParams(500, 8, 5) == -707);
|
|
assert(!failed._profileSwitch.rxValid && failed._profileSwitch.failed());
|
|
const auto before = failed.queries;
|
|
failed.writeResult = 0;
|
|
assert(failed.setLoRaModulationParams(125, 9, 7) == 0);
|
|
assert(failed.queries == before + 1);
|
|
|
|
// A live wrong-modem/error-byte observation revokes the WHOLE context.
|
|
// Reopening it without a fresh full RX confirmation cannot restore reuse.
|
|
Radio wrong; selectCache(wrong, requested); prime(wrong);
|
|
wrong._profileSwitch.end(true); wrong.packetType = 255;
|
|
assert(wrong.setLoRaModulationParams(125, 9, 7) == -4 && wrong.queries == 1);
|
|
assert(!wrong._profileSwitch.rxValid && !wrong._profileSwitch.modulationValid);
|
|
wrong._profileSwitch.begin(true, true); wrong._profileSwitch.standbyResult(0);
|
|
wrong.packetType = 1;
|
|
assert(wrong.setLoRaModulationParams(125, 9, 7) == 0 && wrong.queries == 2);
|
|
}
|
|
// Public modem initialization calls invalidate before success AND failure,
|
|
// including inherited float overloads dispatching to the config overrides.
|
|
for (int api = 0; api < 6; ++api) for (int failure = 0; failure < 2; ++failure) {
|
|
Radio r; selectCache(r, true); prime(r);
|
|
r._profileSwitch.modulationResult(0, 9, 4, 3, 0);
|
|
r._profileSwitch.packetResult(0, 32, 1, 255, 0, 0);
|
|
r.result = failure ? -707 : 0;
|
|
int rc = 0;
|
|
switch (api) {
|
|
case 0: rc = r.begin(ConfigLoRa_t{}); break;
|
|
case 1: rc = r.beginFSK(ConfigFSK_t{}); break;
|
|
case 2: rc = r.beginLRFHSS(ConfigLRFHSS_t{}); break;
|
|
case 3: rc = r.setModem(0); break;
|
|
case 4: rc = r.beginFSK(910.0f); break;
|
|
case 5: rc = r.beginLRFHSS(910.0f); break;
|
|
}
|
|
assert(rc == r.result && r.initializations == 1);
|
|
assert(r._coldStandby && !r._tcxoWakePending);
|
|
assert(r.setLoRaModulationParams(125, 9, 7) == 0 && r.queries == 1);
|
|
}
|
|
}
|
|
'''
|
|
for policy in ("#define MC_SX1262_FAST_MODEM_CACHE 0",
|
|
"#define MC_SX1262_FAST_MODEM_CACHE 1",
|
|
"#define MC_SX1262_FAST_MODEM_CACHE 0\n#define MC_SX1262_HIL 1"):
|
|
with self.subTest(policy=policy):
|
|
self.compile_run(fixture.replace("@DEFINES@", policy)
|
|
.replace("@STATE@", state).replace("@PRODUCTION@", production))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|