Reset radio activity tracking after successful receive startup
This commit is contained in:
1 parent
86a7a27f00
commit
e4119a1206
14 files changed
+486
-18
No files matched your search
@@ -341,6 +341,7 @@ jobs:
|
||||
python3 -B test/test_meshtracker_x1_battery.py -v
|
||||
python3 -B test/test_drv2605_power.py -v
|
||||
python3 -B test/test_radio_interrupt_recovery.py -v
|
||||
python3 -B test/test_radio_rx_activity_epoch.py -v
|
||||
python3 -B test/test_forwarding_profile_timing.py -v
|
||||
python3 -B test/test_clock_transit_profile_timing.py -v
|
||||
python3 -B test/test_bridge_recovery.py -v
|
||||
|
||||
@@ -9,9 +9,9 @@ reversibly stopping the Bluetooth host/controller, and allowing bounded MCU
|
||||
light-sleep during a settled secondary-radio receive visit. This report
|
||||
separates host/build coverage, SDK wait measurements and physical packet tests.
|
||||
|
||||
Bluetooth transitions and quiet-window SDK wait checks pass. Three strict RF trials with
|
||||
Bluetooth transitions and quiet-window SDK wait checks pass. Three earlier strict RF trials with
|
||||
power saving on and
|
||||
matched preambles received five of six packets, including the trial after the IRQ fix. They remain
|
||||
matched preambles received five of six packets, including the trial after the first IRQ fix. They remain
|
||||
failures; radio2 is not fully qualified.
|
||||
|
||||
The first V3 CPU-clock sweep stopped during setup, before any of its
|
||||
@@ -373,18 +373,131 @@ does not establish that WiFi deinitialization timed out. The raw SDK log
|
||||
bypassed disabled USB logging and broke strict Binary Companion framing.
|
||||
All three attempts restored checked settings/channels and verified a fresh
|
||||
MQTT PUBACK increase. The latest immutable diagnostic receipt is
|
||||
`v4-7d0709bf-suffix-completed.private.json`. A fix is underway; these setup
|
||||
failures supply no new V4 packet-delivery or firmware-fix qualification.
|
||||
`v4-7d0709bf-suffix-completed.private.json`. These setup failures supply no
|
||||
new V4 packet-delivery evidence.
|
||||
|
||||
## SDK logging repair and V4 scanning controls
|
||||
|
||||
The SDK logging fix in `d73599bc6` shipped in
|
||||
[`3fd9b392`](https://github.com/mikecarper/MeshCore/commit/3fd9b392c8d216e2d5dd592587085c1a71fa738c).
|
||||
It prevents raw SDK logs from bypassing the disabled USB-logging policy and
|
||||
corrupting the shared Binary Companion stream. All 101 USB host tests pass.
|
||||
The V4 application is 2,558,856 bytes, with SHA-256
|
||||
`ddfd4f40a97da40bdeda2c00fb18ebc2d4d365590d4bc63496a425d16975906d`.
|
||||
Its installation passed full readback and checked configuration preservation.
|
||||
|
||||
Two new scanning cases completed on this V4 image, with 20 new nonces per
|
||||
profile, matched 32-symbol preambles and 2 dBm peer TX:
|
||||
|
||||
| CPU | Manual CPU sleep | Primary received | Secondary received | SDK calls/returns | SDK calls in wholly contained RF-time buckets |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| 80 MHz | Off | 19/20 | 18/20 | 0/0 | 0 |
|
||||
| 80 MHz | On | 19/20 | 15/20 | 4,121/4,121 | 3,511 |
|
||||
|
||||
Both strict delivery checks failed. All 80 sends reached TX_DONE once, with
|
||||
zero retries or duplicate deliveries. SDK errors, short returns and pending
|
||||
waits were zero. The awake losses establish that CPU sleep is not required for
|
||||
this symptom; these samples do not identify the cause of individual misses.
|
||||
|
||||
The user redirected testing toward the scanner. The 160 MHz On case was
|
||||
intentionally stopped during its USB host-loss grace period, before any RF
|
||||
transmission; 160 MHz Off was not entered. This is not a completed four-case
|
||||
comparison. The interrupt receipt remains recorded in
|
||||
`v4-3fd9b392-user-steered-completed.private.json` (SHA-256
|
||||
`e102200af7e2503bb0362e65afad0d947d5c3aeb220425a589d0588032530c52`).
|
||||
Full restoration and a fresh MQTT PUBACK increase passed after that stop.
|
||||
|
||||
## Fixed-tuple controls and receive-path defect
|
||||
|
||||
Two fixed-tuple controls on the same `3fd9b392` image each received **20/20**
|
||||
unique packets with radio2 explicitly off. The primary physical radio was
|
||||
temporarily tuned first to 910.525 MHz / BW62.5 / SF7 / CR5, then separately to
|
||||
916 MHz / BW125 / SF9 / CR5. Both runs used 80 MHz, manual CPU sleep off, saved
|
||||
power-saving intent on, RX power saving off, 32-symbol preambles and 2 dBm peer
|
||||
TX. The target's USB data was absent after the same 121-second host-loss grace.
|
||||
Each run retained the original seeded 40-slot cadence, leaving the opposite
|
||||
profile's 20 slots silent. SDK calls, duplicate deliveries and RF retries were
|
||||
zero. The immutable control report remains failed because the peer's original
|
||||
22 dBm setting was not restored within three bounded busy-response attempts;
|
||||
MQTT recovery passed. A separate recovery then restored 22 dBm and verified
|
||||
full peer settings, credential hashes and channel semantics without another
|
||||
RF transmission. The original failure is retained in
|
||||
`fixed-tuples-v4-3fd9b392-completed.private.json`; successful recovery is in
|
||||
`peer-tx22-restoration-verified.private.json`.
|
||||
|
||||
These finite controls narrow the investigation toward scanning or overlapping
|
||||
traffic. Radio2 time-shares one transceiver: a valid primary reception can
|
||||
occupy it throughout a secondary packet's preamble. The idle-scan timing model
|
||||
does not guarantee zero loss under simultaneous traffic. The controls do not
|
||||
distinguish that limitation from a receive-path defect for a particular nonce.
|
||||
|
||||
A separate host regression executing the real receive functions with
|
||||
ASan/UBSan proved a stale-IRQ defect. Servicing a packet through its asserted
|
||||
physical DIO could leave a late software event. Subsequent empty RX_DONE probes
|
||||
could then restart continuous RX over a newer packet's preamble or valid
|
||||
header. The narrow ESP32 SX126x continuous-RX guard in
|
||||
[`a5293fa50`](https://github.com/mikecarper/MeshCore/commit/a5293fa50b2d957077c5fdcb9378b6e88370d9d5)
|
||||
preserves that new reception while retaining normal activity timeouts.
|
||||
All 15 interrupt-recovery, five receive-contract and six RX-retry tests pass.
|
||||
The V4 build passes all 24 capability markers and its memory gate at 2,558,936
|
||||
application bytes. Its application SHA-256 is
|
||||
`cc16e44ccd24e64724a2bd17f59f684117e411077e4c0a7d04611be328b7ccfc`.
|
||||
The application-only installation passed complete readback, with the partition
|
||||
table, OTA metadata and checked configuration preserved. The immutable receipt
|
||||
is `flash-v4-a5293fa5-completed.private.json` (SHA-256
|
||||
`ef61cf8dfc77f8907d621f7f0493949ff6888b017c277eefb309247ccf88b278`).
|
||||
|
||||
One new awake 80 MHz scanning run then sent 20 unique packets per profile on
|
||||
this changed image, with manual CPU sleep off and the same matched preambles,
|
||||
TX power and USB host-loss grace. The V4 received **19/20 primary and 20/20
|
||||
secondary packets**. All 40 sends reached TX_DONE once, with zero retries or
|
||||
duplicate deliveries. SDK calls, returns and all time buckets were zero; core
|
||||
error flags remained clear. The missing primary nonce keeps the strict RF
|
||||
result failed. Full restoration passed, including a fresh MQTT PUBACK increase.
|
||||
The immutable receipt is `v4-a5293fa5-awake-completed.private.json` (SHA-256
|
||||
`466bdf0bb08f10665007d156e1ec37c074e1c576e6ac5fc401bcd449179ee774`).
|
||||
|
||||
The stale-IRQ regression proves a receive-path bug and the new guard repairs
|
||||
that case. This physical sample does not establish which historical misses
|
||||
encountered it or show that all scanning losses are fixed. A controlled
|
||||
two-transmitter overlap diagnostic is pending; no result or cause is claimed
|
||||
for that experiment here.
|
||||
|
||||
### Receive activity across RX restarts
|
||||
|
||||
A further host regression executing the production receive methods exposed a
|
||||
separate scan hazard. Reading a completed packet and successfully rearming RX
|
||||
cleared the hardware IRQs but retained the previous packet's `_activityAt` and
|
||||
`_headerSeen` latches. At the next due scan, the detector could mistake the new
|
||||
packet's preamble without a header for the old packet's consumed header. It
|
||||
reported idle before processing that preamble, permitting a retune over the
|
||||
new packet. The regression reproduces that sequence and shows that resetting
|
||||
the latches on successful RX entry protects the next preamble.
|
||||
|
||||
The change resets those existing fields only after successful receive
|
||||
starts in `CustomSX1262`, `CustomSX1268`, `CustomLLCC68`, `CustomSTM32WLx`,
|
||||
`CustomLR1110` and `CustomLR1121`. It covers normal starts and, where supported,
|
||||
fast resumes and duty-cycle entry. Failed starts retain their prior state;
|
||||
the change adds no fields and leaves existing activity timeouts and
|
||||
receive/retune guards unchanged.
|
||||
Independent source review passed. Five new regression methods cover 24
|
||||
ASan/UBSan variants, including reset-removal controls that reproduce the
|
||||
retune over a new packet for all six handlers. All 19 affected host-fixture
|
||||
tests and 26 interrupt-recovery, receive-contract and receive-retry checks
|
||||
pass. The new suite runs in the required unit-test workflow. Physical RF
|
||||
validation remains pending; this proof does not attribute historical missing
|
||||
nonces or establish a complete RF pass. The installed versions and packet
|
||||
counts above remain unchanged.
|
||||
|
||||
## Final handback and limits
|
||||
|
||||
Both V3 and V4 now run `7d0709bf0`. Its original radio, RX power-saving, Bluetooth,
|
||||
WiFi, MQTT, logging, diagnostics and USB-watchdog settings were restored and
|
||||
checked against the frozen baseline, including credential digests. Radio2 is
|
||||
`off (automatic allowed)`, preserving its original automatic permission.
|
||||
Bluetooth is on. WiFi reconnected and MQTT resumed with a verified fresh
|
||||
broker PUBACK increase.
|
||||
The peer's original 22 dBm power and logical channel state were restored.
|
||||
V3 currently runs `7d0709bf0`; V4 runs `a5293fa50`, and the peer remains on
|
||||
`ebb59d48`. The completed V3 campaign and V4 scanning controls, including the
|
||||
new awake run, restored their checked radio, RX power-saving,
|
||||
Bluetooth, WiFi, MQTT, logging, diagnostics and USB-watchdog settings, including
|
||||
credential digests, original radio2 automatic permission and peer power/channel
|
||||
state. Fresh MQTT PUBACK increases verified recovery. The fixed-tuple cleanup
|
||||
power failure is retained, with the later peer recovery verified separately.
|
||||
No nRF52 board, hub power or other firmware was changed in this task.
|
||||
|
||||
The Bluetooth hardware checks pass. SDK light-sleep calls are observed without
|
||||
|
||||
@@ -124,10 +124,15 @@ class CustomLLCC68 : public LLCC68 {
|
||||
}
|
||||
|
||||
int16_t startReceive() override {
|
||||
return LLCC68::startReceive(
|
||||
const int16_t status = LLCC68::startReceive(
|
||||
RADIOLIB_SX126X_RX_TIMEOUT_INF,
|
||||
RADIOLIB_IRQ_RX_DEFAULT_FLAGS | (1UL << RADIOLIB_IRQ_PREAMBLE_DETECTED),
|
||||
RADIOLIB_IRQ_RX_DEFAULT_MASK, 0);
|
||||
if (status == RADIOLIB_ERR_NONE) {
|
||||
_activityAt = 0;
|
||||
_headerSeen = false;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
bool isReceiving() {
|
||||
|
||||
@@ -420,7 +420,12 @@ class CustomLR1110 : public LR1110 {
|
||||
|
||||
// Send the already converted values. RadioLib 7.7.1 converts them again
|
||||
// with 32-bit arithmetic, which overflows for periods above about 131 ms.
|
||||
return this->setRxDutyCycle(rxPeriodRaw, sleepPeriodRaw, RADIOLIB_LR11X0_RX_DUTY_CYCLE_MODE_RX);
|
||||
state = this->setRxDutyCycle(rxPeriodRaw, sleepPeriodRaw, RADIOLIB_LR11X0_RX_DUTY_CYCLE_MODE_RX);
|
||||
if (state == RADIOLIB_ERR_NONE) {
|
||||
_activityAt = 0;
|
||||
_headerSeen = false;
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
int16_t setRxBoostedGainMode(bool en) {
|
||||
@@ -479,7 +484,11 @@ class CustomLR1110 : public LR1110 {
|
||||
fastSetRxCycles += DWT->CYCCNT - segment;
|
||||
#endif
|
||||
stagedMode = RADIOLIB_RADIO_MODE_NONE;
|
||||
if (rc == RADIOLIB_ERR_NONE) ++_profileFastResumes;
|
||||
if (rc == RADIOLIB_ERR_NONE) {
|
||||
++_profileFastResumes;
|
||||
_activityAt = 0;
|
||||
_headerSeen = false;
|
||||
}
|
||||
if (rc == RADIOLIB_ERR_NONE) _profilePacketDirty = false;
|
||||
_profileSwitch.rxResult(rc, true);
|
||||
return rc;
|
||||
@@ -490,6 +499,10 @@ class CustomLR1110 : public LR1110 {
|
||||
RADIOLIB_LR11X0_RX_TIMEOUT_INF,
|
||||
RADIOLIB_IRQ_RX_DEFAULT_FLAGS | (1UL << RADIOLIB_IRQ_PREAMBLE_DETECTED),
|
||||
RADIOLIB_IRQ_RX_DEFAULT_MASK | (1UL << RADIOLIB_IRQ_HEADER_ERR), 0);
|
||||
if (rc == RADIOLIB_ERR_NONE) {
|
||||
_activityAt = 0;
|
||||
_headerSeen = false;
|
||||
}
|
||||
_profileSwitch.rxResult(rc, true);
|
||||
if (rc == RADIOLIB_ERR_NONE) _profilePacketDirty = false;
|
||||
return rc;
|
||||
|
||||
@@ -123,7 +123,12 @@ class CustomLR1121 : public LR1121 {
|
||||
|
||||
int16_t startReceive() override {
|
||||
// include the PREAMBLE_DETECTED irq bit in reported flags.
|
||||
return LR1121::startReceive(RADIOLIB_LR11X0_RX_TIMEOUT_INF, RADIOLIB_IRQ_RX_DEFAULT_FLAGS | (1UL << RADIOLIB_IRQ_PREAMBLE_DETECTED), RADIOLIB_IRQ_RX_DEFAULT_MASK, 0);
|
||||
const int16_t status = LR1121::startReceive(RADIOLIB_LR11X0_RX_TIMEOUT_INF, RADIOLIB_IRQ_RX_DEFAULT_FLAGS | (1UL << RADIOLIB_IRQ_PREAMBLE_DETECTED), RADIOLIB_IRQ_RX_DEFAULT_MASK, 0);
|
||||
if (status == RADIOLIB_ERR_NONE) {
|
||||
_activityAt = 0;
|
||||
_headerSeen = false;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
bool isReceiving() {
|
||||
|
||||
@@ -33,7 +33,12 @@ class CustomSTM32WLx : public STM32WLx {
|
||||
|
||||
int16_t startReceive() override {
|
||||
// include the PREAMBLE_DETECTED irq bit in reported flags
|
||||
return STM32WLx::startReceive(RADIOLIB_SX126X_RX_TIMEOUT_INF, RADIOLIB_IRQ_RX_DEFAULT_FLAGS | (1UL << RADIOLIB_IRQ_PREAMBLE_DETECTED), RADIOLIB_IRQ_RX_DEFAULT_MASK, 0);
|
||||
const int16_t status = STM32WLx::startReceive(RADIOLIB_SX126X_RX_TIMEOUT_INF, RADIOLIB_IRQ_RX_DEFAULT_FLAGS | (1UL << RADIOLIB_IRQ_PREAMBLE_DETECTED), RADIOLIB_IRQ_RX_DEFAULT_MASK, 0);
|
||||
if (status == RADIOLIB_ERR_NONE) {
|
||||
_activityAt = 0;
|
||||
_headerSeen = false;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
bool isReceiving() {
|
||||
|
||||
@@ -550,6 +550,10 @@ class CustomSX1262 : public SX1262 {
|
||||
// while the SX1262 alternates between its RX and sleep windows.
|
||||
this->mod->setRfSwitchState(Module::MODE_RX);
|
||||
}
|
||||
if (state == RADIOLIB_ERR_NONE) {
|
||||
_activityAt = 0;
|
||||
_headerSeen = false;
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -712,7 +716,11 @@ class CustomSX1262 : public SX1262 {
|
||||
rc = setRx(rxTimeout); // original BUSY waits and status checks
|
||||
}
|
||||
stagedMode = RADIOLIB_RADIO_MODE_NONE;
|
||||
if (rc == RADIOLIB_ERR_NONE) ++_profileFastResumes;
|
||||
if (rc == RADIOLIB_ERR_NONE) {
|
||||
++_profileFastResumes;
|
||||
_activityAt = 0;
|
||||
_headerSeen = false;
|
||||
}
|
||||
_coldStandby = rc != RADIOLIB_ERR_NONE;
|
||||
_profileSwitch.rxResult(rc, true);
|
||||
return rc;
|
||||
@@ -723,6 +731,12 @@ class CustomSX1262 : public SX1262 {
|
||||
RADIOLIB_SX126X_RX_TIMEOUT_INF,
|
||||
RADIOLIB_IRQ_RX_DEFAULT_FLAGS | (1UL << RADIOLIB_IRQ_PREAMBLE_DETECTED),
|
||||
RADIOLIB_IRQ_RX_DEFAULT_MASK, 0);
|
||||
// Acknowledged RX entry cleared the previous hardware IRQs. Its local
|
||||
// activity latches must not classify the next packet as old activity.
|
||||
if (rc == RADIOLIB_ERR_NONE) {
|
||||
_activityAt = 0;
|
||||
_headerSeen = false;
|
||||
}
|
||||
const bool reusable = rc == RADIOLIB_ERR_NONE && _profileSwitch.enabled
|
||||
&& getPacketType() == RADIOLIB_SX126X_PACKET_TYPE_LORA;
|
||||
_coldStandby = rc != RADIOLIB_ERR_NONE;
|
||||
|
||||
@@ -124,10 +124,15 @@ class CustomSX1268 : public SX1268 {
|
||||
}
|
||||
|
||||
int16_t startReceive() override {
|
||||
return SX1268::startReceive(
|
||||
const int16_t status = SX1268::startReceive(
|
||||
RADIOLIB_SX126X_RX_TIMEOUT_INF,
|
||||
RADIOLIB_IRQ_RX_DEFAULT_FLAGS | (1UL << RADIOLIB_IRQ_PREAMBLE_DETECTED),
|
||||
RADIOLIB_IRQ_RX_DEFAULT_MASK, 0);
|
||||
if (status == RADIOLIB_ERR_NONE) {
|
||||
_activityAt = 0;
|
||||
_headerSeen = false;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
bool isReceiving() {
|
||||
|
||||
@@ -104,6 +104,7 @@ struct LR1110 {
|
||||
int16_t setRx(uint32_t timeout) { assert(timeout == 0xffffff); trace += 'R'; return 0; }
|
||||
};
|
||||
struct Radio : LR1110 {
|
||||
uint32_t _activityAt=0;bool _headerSeen=false;
|
||||
LR1110ProfileSwitchState _profileSwitch{true};
|
||||
bool _profilePacketDirty = true;
|
||||
uint32_t _profileFastResumes = 0;
|
||||
|
||||
@@ -150,6 +150,7 @@ struct LR1110 {
|
||||
int16_t setRx(uint32_t timeout) { assert(timeout==0xffffff);return call('R'); }
|
||||
};
|
||||
struct Radio:LR1110 {
|
||||
uint32_t _activityAt=0;bool _headerSeen=false;
|
||||
LR1110ProfileSwitchState _profileSwitch{true};
|
||||
bool _profileStandbyWarm=false,_profilePacketDirty=false;
|
||||
uint32_t _profileFastResumes=0;
|
||||
|
||||
@@ -130,6 +130,7 @@ struct SX1262 {
|
||||
int16_t setRx(uint32_t timeout) { assert(timeout==0xffffff);return call('R'); }
|
||||
};
|
||||
struct Radio : SX1262 {
|
||||
uint32_t _activityAt=0;bool _headerSeen=false;
|
||||
SX1262ProfileSwitchState _profileSwitch{true};
|
||||
bool _coldStandby=true,_tcxoWakePending=false,_rx_ps_rf_rx_disabled=false;
|
||||
uint32_t _profileFastResumes=0;
|
||||
|
||||
@@ -0,0 +1,302 @@
|
||||
"""Execute actual RX entry, activity detection and due-scanner admission."""
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
ROOT=Path(__file__).resolve().parents[1]
|
||||
import test_radio_interrupt_recovery as irq
|
||||
compile_run=irq.compile_run
|
||||
from test_radio_receive_contract import method
|
||||
|
||||
FAMILIES=('CustomSX1262','CustomSX1268','CustomLLCC68','CustomSTM32WLx','CustomLR1110','CustomLR1121')
|
||||
|
||||
|
||||
def build_source(family='CustomSX1262',remove_reset=False,include_duty=False):
|
||||
source=irq.RadioInterruptRecoveryTests().source(sx126x=family)
|
||||
src=(ROOT/'src/helpers/radiolib/RadioLibWrappers.cpp').read_text()
|
||||
chip=(ROOT/f'src/helpers/radiolib/{family}.h').read_text()
|
||||
entry=method(chip,'int16_t startReceive()')
|
||||
if remove_reset:
|
||||
assert '_activityAt = 0;'in entry and '_headerSeen = false;'in entry
|
||||
entry=entry.replace('_activityAt = 0;','').replace('_headerSeen = false;','')
|
||||
if include_duty:
|
||||
duty=method(chip,'int16_t startReceiveDutyCycle(')
|
||||
if remove_reset:duty=duty.replace('_activityAt = 0;','').replace('_headerSeen = false;','')
|
||||
entry+='\n'+duty
|
||||
source=source.replace('#include <cassert>', '''#include <cassert>
|
||||
#include <cstdio>
|
||||
#include <RadioProfiles.h>
|
||||
#include <helpers/radiolib/NoiseFloorEstimator.h>
|
||||
#include <helpers/radiolib/SX1262ProfileSwitchState.h>
|
||||
#include <helpers/radiolib/LR1110ProfileSwitchState.h>
|
||||
#define MC_SX1262_FAST_PACKET_CACHE 0
|
||||
#define MC_SX1262_FAST_IQ_CACHE 0
|
||||
#define RADIOLIB_SX126X_RX_TIMEOUT_INF 0xffffff
|
||||
#define RADIOLIB_IRQ_RX_DEFAULT_FLAGS 1
|
||||
#define RADIOLIB_IRQ_RX_DEFAULT_MASK 2
|
||||
#define RADIOLIB_SX126X_PACKET_TYPE_LORA 1
|
||||
#define RADIOLIB_RADIO_MODE_NONE 0
|
||||
#define RADIOLIB_LR11X0_RX_TIMEOUT_INF 0xffffff
|
||||
#define RADIOLIB_LR11X0_IRQ_ALL 0xffffffff
|
||||
#define RADIOLIB_LR11X0_MAX_PACKET_LENGTH 255
|
||||
#define RADIOLIB_LR11X0_IRQ_PREAMBLE_DETECTED 16
|
||||
#define RADIOLIB_LR11X0_IRQ_SYNC_WORD_HEADER_VALID 32
|
||||
#define RADIOLIB_LR11X0_IRQ_HEADER_ERR 64
|
||||
#define RADIOLIB_IRQ_HEADER_ERR 6
|
||||
#define RADIOLIB_ERR_INVALID_SLEEP_PERIOD -98
|
||||
#define RADIOLIB_ERR_INVALID_RX_PERIOD -97
|
||||
#define RADIOLIB_LR11X0_STANDBY_RC 0
|
||||
#define RADIOLIB_LR11X0_LF_CLK_RC 1
|
||||
#define RADIOLIB_LR11X0_LF_BUSY_RELEASE_ENABLED 2
|
||||
#define RADIOLIB_RADIO_MODE_RX 1
|
||||
#define RADIOLIB_LR11X0_RX_DUTY_CYCLE_MODE_RX 0
|
||||
#define RADIOLIB_ASSERT(state) do { if((state)!=0)return(state); } while(0)
|
||||
using RadioLibIrqFlags_t=uint32_t;
|
||||
struct RadioModeConfig_t {struct {uint32_t timeout,irqFlags,irqMask;size_t len;}receive;};
|
||||
#define MAX_TRANS_UNIT 255
|
||||
#define NF_CONTINUOUS_TIMEOUT_MS 4000
|
||||
namespace mesh { enum class RadioParamApplyResult { APPLIED, BUSY, FAILED }; }
|
||||
''')
|
||||
source=source.replace('uint32_t millis(){return now_ms;}', '''uint32_t millis(){return now_ms;}
|
||||
int64_t esp_timer_get_time(){return int64_t(now_ms)*1000;}
|
||||
uint32_t profileTimestamp(bool){return now_ms*1000;}
|
||||
uint32_t profileElapsedUs(uint32_t a,uint32_t b,bool){return b-a;}''')
|
||||
source=source.replace('struct Module {', '''struct Module {
|
||||
enum {MODE_RX=1};void setRfSwitchState(int){}''')
|
||||
source=source.replace('struct Chip : LR2021 {', '''using SX1262=LR2021;using SX1268=LR2021;using LLCC68=LR2021;using STM32WLx=LR2021;using LR1110=LR2021;using LR1121=LR2021;
|
||||
struct Chip : LR2021 {
|
||||
SX1262ProfileSwitchState _profileSwitch{true};bool _coldStandby=true;
|
||||
uint32_t _profileFastResumes=0;
|
||||
bool _profilePacketDirty=false;Module* mod=&module;
|
||||
int16_t snapshotIrqAfterRxExit(uint32_t* pending){*pending=irq;return snapshot_failure;}
|
||||
int16_t clearIrqState(uint32_t flags){irq &= ~flags;rx_status=0;gpio_levels[module.irq]=false;return clear_status;}
|
||||
int16_t setPacketParamsLoRa(size_t,uint8_t,uint8_t,uint8_t,uint8_t){return packet_failure;}
|
||||
int snapshot_failure=0,packet_failure=0;
|
||||
bool _rx_ps_rf_rx_disabled=false;
|
||||
uint8_t spreadingFactor=7;float bandwidthKhz=62.5;uint32_t tcxoDelay=0;
|
||||
int lf_failure=0,stage_failure=0;
|
||||
int16_t configLfClock(int){return lf_failure;}
|
||||
int16_t stageMode(int,RadioModeConfig_t*){return stage_failure;}
|
||||
int16_t setRxDutyCycle(uint32_t,uint32_t,int){return startDuty();}
|
||||
size_t preambleLengthLoRa=32;uint8_t crcTypeLoRa=1,implicitLen=255,headerType=0,invertIQEnabled=0;
|
||||
uint32_t rxTimeout=0;int stagedMode=0;
|
||||
int16_t clearIrqStatus(){irq=0;rx_status=0;gpio_levels[module.irq]=false;return clear_status;}
|
||||
int16_t setPacketParams(size_t,int,int,int,int){return packet_failure;}
|
||||
int16_t setRx(uint32_t){if(start_failure)return start_failure;++arms;stopped=false;return 0;}
|
||||
uint8_t getPacketType(){return RADIOLIB_SX126X_PACKET_TYPE_LORA;}
|
||||
'''+entry)
|
||||
source=source.replace('bool stopped=false, inject_read_irq=false;', 'bool stopped=false, inject_read_irq=false;int start_failure=0,duty_failure=0;')
|
||||
source=source.replace('int startTransmit(uint8_t*,int)', '''int16_t standby(uint8_t){return standby();}
|
||||
int startDuty(){if(duty_failure)return duty_failure;irq=0;rx_status=0;++arms;return 0;}
|
||||
int16_t startReceiveDutyCycle(uint32_t,uint32_t,uint32_t,uint32_t){return startDuty();}
|
||||
int startTransmit(uint8_t*,int)''')
|
||||
old='int16_t startReceive(uint32_t,uint32_t,uint32_t,int){assert(stopped);++arms;stopped=false;return 0;}'
|
||||
new='''int16_t startReceive(uint32_t,uint32_t,uint32_t,int){
|
||||
++standbys;stopped=true;irq=0;rx_status=0;gpio_levels[module.irq]=false;
|
||||
if(start_failure)return start_failure;++arms;stopped=false;return 0;
|
||||
}'''
|
||||
assert old in source;source=source.replace(old,new)
|
||||
fields=r'''
|
||||
mesh::RadioProfiles _profiles;
|
||||
bool _params_valid=true,_profile_rxps_suspended=true,_profile_saved_rxps=false;
|
||||
bool _noise_floor_valid=true,_noise_floor_secondary_valid=true,_nf_refresh_requested=false;
|
||||
bool _nf_window_armed=false,_nf_lazy_refresh=false,_nf_refresh_attempted=false;
|
||||
uint32_t _profile_retry_at=0,_profile_generation=1,_profile_visit_stamp=0,_profile_scan_generation[2]={};
|
||||
uint8_t _active_profile=0,_cur_sf=7,_cur_cr=5;
|
||||
uint16_t _physical_preamble=32;
|
||||
float _cur_freq=910.525,_cur_bw=62.5;
|
||||
uint64_t _esp32_profile_visit_started_us=0,_esp32_profile_idle_stamp_us=0;
|
||||
uint32_t _esp32_profile_idle_visit_us=0,_esp32_profile_idle_switch_us=0;
|
||||
uint16_t _esp32_profile_idle_preamble=0;
|
||||
uint8_t _esp32_profile_idle_profile=0;
|
||||
uint32_t _esp32_profile_idle_generation[2]={};
|
||||
uint32_t _nf_last_calib=0,_nf_calib_deadline=0;
|
||||
int16_t _noise_floor=0;int32_t _noise_floor_centi_dbm=0,_noise_floor_secondary_centi_dbm=0;
|
||||
NoiseFloorEstimator _floor_estimator,_secondary_floor_estimator;
|
||||
uint8_t _startrx_fails=0;bool _wd_last_busy=false;
|
||||
uint32_t _wd_last_transition=0,_wd_stuck_thresh=0,_wd_observe_ms=0;
|
||||
uint32_t _rx_ps_rx_us=10000,_rx_ps_sleep_us=10000;
|
||||
unsigned applies=0;
|
||||
NoiseFloorEstimator& profileFloorEstimator(uint8_t p){return p?_secondary_floor_estimator:_floor_estimator;}
|
||||
bool isInRecvMode()const{return state==STATE_RX;}
|
||||
void beginProfileRetune(bool){}void endProfileRetune(bool){}
|
||||
bool validateProfile(const mesh::RadioProfileParams&p)const{return mesh::RadioProfiles::valid(p);}
|
||||
uint16_t profilePreamble(uint8_t p)const{return _profiles.preamble(p,32);}
|
||||
bool applyParams(float,float,uint8_t,uint8_t){++applies;return true;}
|
||||
void cacheParams(float f,float b,uint8_t s,uint8_t c){_cur_freq=f;_cur_bw=b;_cur_sf=s;_cur_cr=c;}
|
||||
void setProfileStandbyWarm(bool){}
|
||||
void recalibrateNoiseFloor(){}
|
||||
bool restoreAfterDeepInit(){return false;}
|
||||
uint32_t getEstAirtimeFor(int){return 1000;}
|
||||
int startReceiveMode(){return chip.startReceive();}
|
||||
void endReconfigure(bool);
|
||||
void serviceProfileScan();
|
||||
mesh::RadioParamApplyResult tuneProfile(uint8_t);
|
||||
'''
|
||||
source=source.replace('struct RadioLibWrapper {','struct RadioLibWrapper {'+fields)
|
||||
old='void startRecv(){if(chip.startReceive()==0)state=STATE_RX;}'
|
||||
assert old in source;source=source.replace(old,'void startRecv();')
|
||||
source+='\n'+'\n'.join(method(src,sig)for sig in (
|
||||
'void RadioLibWrapper::startRecv(', 'void RadioLibWrapper::endReconfigure(',
|
||||
'mesh::RadioParamApplyResult RadioLibWrapper::tuneProfile(',
|
||||
'void RadioLibWrapper::serviceProfileScan('))
|
||||
if family=='CustomLR1110':
|
||||
source=source.replace('SX1262ProfileSwitchState _profileSwitch','LR1110ProfileSwitchState _profileSwitch')
|
||||
if family.startswith('CustomLR'):
|
||||
source+="\n#define ACTIVITY_PREAMBLE 16\n#define ACTIVITY_HEADER 32\n#define ACTIVITY_HEADER_ERROR 64\n"
|
||||
else:
|
||||
source+="\n#define ACTIVITY_PREAMBLE 4\n#define ACTIVITY_HEADER 16\n#define ACTIVITY_HEADER_ERROR 32\n"
|
||||
return source
|
||||
|
||||
CHECKS=r'''
|
||||
int main(){
|
||||
uint8_t bytes[16]={};now_ms=100;
|
||||
auto* w=new CustomSX1262Wrapper;assert(w->registerInterruptAction());
|
||||
w->_profiles.primary={910.525,62.5,32,7,5};
|
||||
mesh::RadioProfileConfig other;other.params={916,125,32,9,5};other.mode=mesh::RadioProfileMode::RxTx;
|
||||
w->_profiles.setSecondary(other,false);
|
||||
for(unsigned n=0;n<4;++n){w->_profiles.sampleSwitch(0,1,1000,now_ms);w->_profiles.sampleSwitch(1,0,1000,now_ms);}
|
||||
assert(w->_profiles.switchTestReady());
|
||||
w->_profile_scan_generation[0]=w->_profiles.generation[0];
|
||||
w->_profile_scan_generation[1]=w->_profiles.generation[1];
|
||||
w->_profile_generation=w->_profiles.generation[0];
|
||||
// A due scan while packet A has a valid header defers and latches _headerSeen.
|
||||
w->chip.rx_status=0;w->chip.irq=ACTIVITY_HEADER;
|
||||
w->_esp32_profile_visit_started_us=0;
|
||||
w->serviceProfileScan();assert(w->chip._headerSeen&&w->_active_profile==0);
|
||||
const unsigned start_arms=w->chip.arms;
|
||||
// Actual wrapper read/actual startRecv/actual selected chip startReceive.
|
||||
w->chip.rx_status=1;w->chip.irq=ACTIVITY_HEADER|1;gpio_levels[3]=true;w->chip.callback();
|
||||
assert(w->recvRaw(bytes,sizeof(bytes))==4&&w->n_recv==1&&w->chip.arms==start_arms+1);
|
||||
assert(w->chip.irq==0&&!w->hasPendingRadioInterrupt());
|
||||
const bool retired=!w->chip._headerSeen&&w->chip._activityAt==0;
|
||||
// App/UI work may outlast one visit; then B begins its preamble before the
|
||||
// next scanner pass, with no intermediate idle activity probe.
|
||||
const uint32_t visit=w->_profiles.listenUs(0,w->profilePreamble(w->_profiles.slowerProfile()));
|
||||
now_ms+=(visit+999)/1000+1;w->chip.irq=ACTIVITY_PREAMBLE;
|
||||
const unsigned stands=w->chip.standbys;
|
||||
w->serviceProfileScan();
|
||||
const bool aborted=w->chip.standbys!=stands;
|
||||
assert(!aborted&&w->_active_profile==0&&w->chip.irq==ACTIVITY_PREAMBLE
|
||||
&&"new preamble must survive the actual due scanner");
|
||||
assert(retired&&"RX entry must retire old activity latches");
|
||||
delete w;
|
||||
}
|
||||
'''
|
||||
|
||||
|
||||
ENTRY_CHECKS=r'''
|
||||
static void oldActivity(Chip& chip){chip._headerSeen=true;chip._activityAt=37;}
|
||||
int main(){
|
||||
now_ms=100;Chip chip;
|
||||
oldActivity(chip);assert(chip.startReceive()==0);
|
||||
assert(!chip._headerSeen&&chip._activityAt==0&&"successful RX must reset activity");
|
||||
oldActivity(chip);chip.start_failure=-88;
|
||||
assert(chip.startReceive()==-88&&chip._headerSeen&&chip._activityAt==37);
|
||||
chip.start_failure=0;assert(chip.startReceive()==0);
|
||||
// A fresh false preamble retains the normal finite timeout.
|
||||
chip.irq=ACTIVITY_PREAMBLE;assert(chip.isReceiving());
|
||||
now_ms+=chip._preambleMillis;assert(chip.isReceiving());
|
||||
++now_ms;assert(!chip.isReceiving()&&!(chip.irq&ACTIVITY_PREAMBLE));
|
||||
chip.irq=ACTIVITY_HEADER;assert(chip.isReceiving());
|
||||
now_ms+=chip._maxPayloadMillis;assert(chip.isReceiving());
|
||||
++now_ms;assert(!chip.isReceiving()&&!chip._headerSeen);
|
||||
chip.irq=ACTIVITY_HEADER|ACTIVITY_HEADER_ERROR;
|
||||
assert(!chip.isReceiving()&&!chip._headerSeen&&chip._activityAt==0);
|
||||
#if MESH_RADIO_PROFILE_CHIP == 1110
|
||||
// LR1110 deliberately leaves its error IRQ for wrapper-owned recovery.
|
||||
assert(chip.irq&ACTIVITY_HEADER_ERROR);
|
||||
#else
|
||||
assert(!(chip.irq&ACTIVITY_HEADER_ERROR));
|
||||
#endif
|
||||
}
|
||||
'''
|
||||
|
||||
FAST_CHECKS=r'''
|
||||
static void fastContext(Chip& chip){
|
||||
chip._profileSwitch.rxResult(0,true);
|
||||
chip._profileSwitch.begin(true,true);chip._profileSwitch.standbyResult(0);
|
||||
assert(chip._profileSwitch.canResumeFast());
|
||||
chip._headerSeen=true;chip._activityAt=37;chip.irq=0;
|
||||
}
|
||||
int main(){
|
||||
Chip chip;fastContext(chip);
|
||||
assert(chip.startReceive()==0&&!chip._headerSeen&&chip._activityAt==0);
|
||||
assert(chip._profileFastResumes==1);
|
||||
for(unsigned failure=0;failure<3;++failure){
|
||||
fastContext(chip);
|
||||
if(failure==0){chip.irq=1;chip.clear_status=-88;}
|
||||
if(failure==1){chip.packet_failure=-88;chip._profilePacketDirty=true;}
|
||||
if(failure==2)chip.start_failure=-88;
|
||||
assert(chip.startReceive()==-88&&chip._headerSeen&&chip._activityAt==37);
|
||||
chip.clear_status=chip.packet_failure=chip.start_failure=0;
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
DUTY_CHECKS=r'''
|
||||
int main(){
|
||||
Chip chip;chip._headerSeen=true;chip._activityAt=37;
|
||||
assert(chip.startReceiveDutyCycle(100000,100000)==0);
|
||||
assert(!chip._headerSeen&&chip._activityAt==0&&"acknowledged duty-cycle RX must reset activity");
|
||||
chip._headerSeen=true;chip._activityAt=37;chip.duty_failure=-88;
|
||||
assert(chip.startReceiveDutyCycle(100000,100000)==-88);
|
||||
assert(chip._headerSeen&&chip._activityAt==37);
|
||||
chip.duty_failure=0;
|
||||
assert(chip.startReceiveDutyCycle(100000,100000)==0&&!chip._headerSeen&&chip._activityAt==0);
|
||||
#if MESH_RADIO_PROFILE_CHIP == 1110
|
||||
// Bounds and every preparatory command can reject duty-cycle entry. None
|
||||
// acknowledged a new RX epoch, so none may retire the old local activity.
|
||||
chip._headerSeen=true;chip._activityAt=37;
|
||||
assert(chip.startReceiveDutyCycle(100000,0)==RADIOLIB_ERR_INVALID_SLEEP_PERIOD);
|
||||
assert(chip._headerSeen&&chip._activityAt==37);
|
||||
for(unsigned failure=0;failure<3;++failure){
|
||||
if(failure==0)chip.standby_status=-88;
|
||||
if(failure==1)chip.lf_failure=-88;
|
||||
if(failure==2)chip.stage_failure=-88;
|
||||
assert(chip.startReceiveDutyCycle(100000,100000)==-88);
|
||||
assert(chip._headerSeen&&chip._activityAt==37);
|
||||
chip.standby_status=chip.lf_failure=chip.stage_failure=0;
|
||||
}
|
||||
#else
|
||||
chip._rx_ps_rf_rx_disabled=true;chip._headerSeen=true;chip._activityAt=37;
|
||||
assert(chip.startReceiveDutyCycle(100000,100000)==0&&!chip._headerSeen&&chip._activityAt==0);
|
||||
#endif
|
||||
}
|
||||
'''
|
||||
|
||||
class RadioRxActivityEpochTests(unittest.TestCase):
|
||||
def test_actual_receive_rearm_preserves_new_preamble_at_due_scan(self):
|
||||
for family in FAMILIES:
|
||||
with self.subTest(family=family):
|
||||
compile_run(build_source(family)+CHECKS,['-DESP32_PLATFORM=1','-DRADIO_CLASS='+family])
|
||||
|
||||
def test_removed_epoch_reset_aborts_new_packet_in_actual_due_scan(self):
|
||||
for family in FAMILIES:
|
||||
with self.subTest(family=family):
|
||||
with self.assertRaises(AssertionError)as failure:
|
||||
compile_run(build_source(family,remove_reset=True)+CHECKS,
|
||||
['-DESP32_PLATFORM=1','-DRADIO_CLASS='+family])
|
||||
self.assertIn('new preamble must survive the actual due scanner',str(failure.exception))
|
||||
|
||||
def test_actual_normal_receive_success_failure_activity_ttl_and_header_error(self):
|
||||
for family in FAMILIES:
|
||||
with self.subTest(family=family):
|
||||
compile_run(build_source(family)+ENTRY_CHECKS,['-DESP32_PLATFORM=1','-DRADIO_CLASS='+family])
|
||||
|
||||
def test_actual_fast_receive_resets_only_after_acknowledged_entry(self):
|
||||
for family in ('CustomSX1262','CustomLR1110'):
|
||||
with self.subTest(family=family):
|
||||
compile_run(build_source(family)+FAST_CHECKS,['-DESP32_PLATFORM=1','-DRADIO_CLASS='+family])
|
||||
|
||||
def test_actual_duty_cycle_entry_resets_only_after_acknowledged_entry(self):
|
||||
for family in ('CustomSX1262','CustomLR1110'):
|
||||
for remove in (False,True):
|
||||
with self.subTest(family=family,removed_reset=remove):
|
||||
source=build_source(family,remove_reset=remove,include_duty=True)
|
||||
if remove:
|
||||
with self.assertRaises(AssertionError)as failure:
|
||||
compile_run(source+DUTY_CHECKS,['-DESP32_PLATFORM=1','-DRADIO_CLASS='+family])
|
||||
self.assertIn('acknowledged duty-cycle RX must reset activity',str(failure.exception))
|
||||
else:compile_run(source+DUTY_CHECKS,['-DESP32_PLATFORM=1','-DRADIO_CLASS='+family])
|
||||
|
||||
|
||||
if __name__=='__main__':unittest.main()
|
||||
@@ -141,6 +141,7 @@ struct SX1262 {
|
||||
int16_t setRx(uint32_t timeout) { assert(timeout==0xffffff);return call('R'); }
|
||||
};
|
||||
struct Radio: SX1262 {
|
||||
uint32_t _activityAt=0;bool _headerSeen=false;
|
||||
SX1262ProfileSwitchState _profileSwitch{true};bool _rx_ps_rf_rx_disabled=false;uint32_t _profileFastResumes=0;
|
||||
bool _coldStandby=true;
|
||||
bool _tcxoWakePending=false;
|
||||
|
||||
@@ -84,6 +84,7 @@ struct SX1262 {
|
||||
uint8_t getPacketType() { call('Q');return 1; }
|
||||
};
|
||||
struct Radio: SX1262 {
|
||||
uint32_t _activityAt=0;bool _headerSeen=false;
|
||||
SX1262ProfileSwitchState _profileSwitch{true};
|
||||
bool _coldStandby=false;uint32_t _profileFastResumes=0;
|
||||
@METHODS@
|
||||
|
||||
Reference in new issue
Block a user