Qualify optional 64-symbol fast preamble

This commit is contained in:
mikecarper committed 2026-08-24 16:54:35 -07:00
1 parent ba70c5b3c6
commit da74d7de22
7 files changed
+129 -35

No files matched your search

+4 -3
View File
@@ -250,9 +250,10 @@ a trusted LAN.
The WebConfig **Advanced** card exposes device power saving on WiFi Companion,
repeater, and room-server builds. It also exposes RX power saving on radio chips
that support receive duty cycling. RXPS can select continuous receive, levels
1-10, automatic or explicit 16/32-symbol preambles, or manual receive/sleep
windows. The WebConfig **WiFi** card exposes WiFi modem power saving. All three
settings are persisted across reboots.
1-10, automatic or explicit 16/32-symbol timing assumptions, or manual
receive/sleep windows. The timing assumption does not change the radio's actual
wire preamble. The WebConfig **WiFi** card exposes WiFi modem power saving. All
three settings are persisted across reboots.
The three settings are independent. A WiFi Companion keeps its transports
available while device power saving reduces CPU and GPS idle power. An
+17 -2
View File
@@ -742,7 +742,18 @@ On SX1262+TCXO boards, the tested fast-setting boundaries are:
SF7/BW500, SF6/BW250, and SF5/BW125 are timing-equivalent because each has a
256 us LoRa symbol. SF5/BW250 and SF5/BW500 use `continuous-fast` even with
preamble 32.
preamble 32 in the normal build.
The table's effective preamble is the length RXPS uses for its timing window.
The radio's actual transmitted preamble is a firmware property, not a
`radio.rxps` preference. Normal firmware transmits 32 symbols at SF5-SF8 and
16 symbols at SF9-SF12. A controlled build can set
`-D LORA_FAST_PREAMBLE=64`; this changes the actual SF5-SF8 wire preamble and
lets SF5/BW250 duty-cycle at effective level 8 (`1252 / 6424 us`). That profile
passed a 16/16 on-air qualification at 909.950 MHz. SF5/BW500 remains too fast.
Use the 64-symbol option only when every possible sender to an RXPS receiver
uses the longer preamble; treating a 32-symbol sender as if it sent 64 would
create a receive gap.
---
@@ -798,7 +809,8 @@ Station G2/G3 targets default to `off`.
**Parameters:**
- `rx_us`, `sleep_us`: Receive and sleep durations in microseconds (`1000`-`30000000`).
- `level`: A power-saving level from `1` (most conservative) to `10` (least power saving).
- `preamble`: LoRa preamble length in symbols; `16` or `32`.
- `preamble`: RXPS timing assumption in symbols; `16` or `32`. This does not
change the radio's actual transmitted preamble.
- `state`: `on` or `off`.
**Notes:**
@@ -809,6 +821,9 @@ Station G2/G3 targets default to `off`.
- `on` and `conservative` select level `1` with a 16-symbol preamble; `balanced` selects level `5` with a 16-symbol preamble.
- Fresh Cascade-profile builds start with RXPS on at level `8` and a 16-symbol preamble. Saved operator settings still take precedence after an upgrade.
- Level-based settings automatically recalculate their timings when the spreading factor or bandwidth changes. Custom `<rx_us> <sleep_us>` timings remain fixed.
- `get radio.rxps` reports both the saved timing assumption and any effective
preamble/level selected for the current tuple. A 64-symbol effective preamble
appears only in a firmware build whose actual fast preamble is 64.
- The selected mode is applied immediately, persisted, and restored after reboot.
---
+17 -10
View File
@@ -107,14 +107,16 @@ get radio.rxps
set radio.rxps {off|on|level 1-10 [preamble 16|32]|rx_us sleep_us}
```
Shows or changes LoRa receive duty cycling on supported radios. Fresh Cascade
builds select level 8 with a 16-symbol preamble. A configured level is the
minimum: when faster SF/BW settings shorten the timing window, firmware raises
the effective level only as far as needed, up to level 10. For SF5-SF8 it can
then use the radio's actual 32-symbol transmit preamble if no level is safe
under the legacy 16-symbol assumption. The configured values remain unchanged,
so every radio change recalculates from the saved minimum. A slower tuple
returns to that exact level when it is safe; otherwise firmware keeps using the
smallest safe effective level.
builds select level 8 with a 16-symbol timing assumption. The `preamble`
argument controls the RXPS calculation; it does not change the physical wire
preamble. A configured level is the minimum: when faster SF/BW settings shorten
the timing window, firmware raises the effective level only as far as needed,
up to level 10. For SF5-SF8 the normal build can then use the radio's actual
32-symbol transmit preamble if no level is safe under the legacy 16-symbol
assumption. The configured values remain unchanged, so every radio change
recalculates from the saved minimum. A slower tuple returns to that exact level
when it is safe; otherwise firmware keeps using the smallest safe effective
level.
If neither adjustment leaves enough time for the radio to wake, RXPS stays
logically enabled but receives continuously instead of rejecting the radio
@@ -135,8 +137,13 @@ RXPS boundary profiles (CR does not change the RXPS preamble timing):
| 5 | 125 | 32 | 7 | 2731 / 6101 us |
| 5 | 62.5 | 16 | 10 | 4096 / 6272 us |
SF5/BW250 and SF5/BW500 remain too fast for a true TCXO duty cycle even with
preamble 32, so they use `continuous-fast`.
SF5/BW250 and SF5/BW500 remain too fast for a true TCXO duty cycle in the
normal 32-symbol build, so they use `continuous-fast`. A controlled build with
`-D LORA_FAST_PREAMBLE=64` changes the actual SF5-SF8 wire preamble. SF5/BW250
then uses effective level 8 and `1252 / 6424 us`; that combination passed a
16/16 on-air qualification at 909.950 MHz. SF5/BW500 still uses
`continuous-fast`. Deploy a longer physical preamble consistently across all
senders that may reach an RXPS receiver.
```
get wifi.powersave
+25 -10
View File
@@ -6,6 +6,17 @@
#define RX_POWERSAVING_MIN_PERIOD_US 1000UL
#define RX_POWERSAVING_MAX_PERIOD_US 30000000UL
// Actual transmit preamble used by MeshCore at SF5-SF8. Keep 32 as the wire
// compatible default; controlled qualification builds can select 64 to test
// faster RX duty-cycle profiles without pretending the sender transmits a
// longer preamble than it really does.
#ifndef LORA_FAST_PREAMBLE
#define LORA_FAST_PREAMBLE 32
#endif
#if LORA_FAST_PREAMBLE != 32 && LORA_FAST_PREAMBLE != 64
#error "LORA_FAST_PREAMBLE must be 32 or 64"
#endif
// A level-derived timing pair equal to this value means the requested LoRa
// tuple is too fast for a safe duty cycle. Keep the operator's RXPS preference
// enabled, but receive continuously until a later SF/BW change produces usable
@@ -95,7 +106,11 @@ inline bool canStartRxPowerSavingDutyCycle(uint32_t rx_us, uint32_t sleep_us,
// Must stay in sync with RadioLibWrapper::preambleLengthForSF() (the value the
// radio actually transmits); kept local here because CommonCLI is radio-agnostic.
inline uint8_t rxPowerSavingPreambleForSF(uint8_t sf) {
return sf <= 8 ? 32 : 16;
return sf <= 8 ? LORA_FAST_PREAMBLE : 16;
}
inline bool isRxPowerSavingPreamble(uint8_t preamble) {
return preamble == 16 || preamble == 32 || preamble == 64;
}
inline bool isNumeric(const char *sp) {
@@ -115,7 +130,7 @@ inline uint32_t ceilPositiveFloat(float value) {
inline bool calcRxPowerSavingLevel(uint8_t level, uint8_t sf, float bw, uint8_t preamble, uint32_t *rx_us,
uint32_t *sleep_us) {
if (!rx_us || !sleep_us || level < 1 || level > 10 || sf < 5 || sf > 12
|| bw <= 0.0f || (preamble != 16 && preamble != 32)) {
|| bw <= 0.0f || !isRxPowerSavingPreamble(preamble)) {
return false;
}
@@ -162,19 +177,19 @@ inline bool calcRxPowerSavingLevelAtOrAbove(
uint8_t *effective_preamble = nullptr) {
if (!rx_us || !sleep_us || requested_level < 1 || requested_level > 10
|| sf < 5 || sf > 12 || bw <= 0.0f
|| (preamble != 16 && preamble != 32)) {
|| !isRxPowerSavingPreamble(preamble)) {
return false;
}
// Keep the configured/legacy preamble assumption if any level can satisfy
// it. For SF5-SF8 the radio already transmits 32 symbols, so a second pass
// may use that real preamble length when 16 symbols cannot cover the TCXO
// transition at a faster tuple.
const bool can_try_32 = preamble == 16
&& rxPowerSavingPreambleForSF(sf) == 32;
const uint8_t pass_count = can_try_32 ? 2 : 1;
// it. A second pass may use the radio's real, longer transmit preamble when
// the configured assumption cannot cover the transition at a faster tuple.
const uint8_t transmitted_preamble = rxPowerSavingPreambleForSF(sf);
const bool can_try_transmitted = transmitted_preamble > preamble;
const uint8_t pass_count = can_try_transmitted ? 2 : 1;
for (uint8_t pass = 0; pass < pass_count; pass++) {
const uint8_t candidate_preamble = pass == 0 ? preamble : 32;
const uint8_t candidate_preamble = pass == 0
? preamble : transmitted_preamble;
for (uint8_t level = requested_level; level <= 10; level++) {
const float symbol_us = (1000.0f * (float)(1UL << sf)) / bw;
const float amount = (float)(level - 1) / 9.0f;
+3 -1
View File
@@ -172,7 +172,9 @@ public:
virtual float getCurrentRSSI() =0;
virtual uint8_t getSpreadingFactor() const { return LORA_SF; }
static uint16_t preambleLengthForSF(uint8_t sf) { return sf <= 8 ? 32 : 16; }
static uint16_t preambleLengthForSF(uint8_t sf) {
return rxPowerSavingPreambleForSF(sf);
}
bool updatePreamble(uint8_t sf) {
if (_radio->setPreambleLength(preambleLengthForSF(sf)) != RADIOLIB_ERR_NONE) return false;
_preamble_sf = sf;
+1 -1
View File
@@ -63,7 +63,7 @@ does not reflect the GoogleTest count -- run the built binary directly
| `test_packet_manager` | `src/Packet.cpp`, `src/Dispatcher.cpp`, `src/helpers/StaticPoolPacketManager.cpp` | truncated-packet rejection, unavailable-radio behavior, scoped RX-delay replacement, queue/CAD scheduling, and staged radio/TX recovery |
| `test_persistent_store_format` | `src/helpers/PersistentStoreFormat.h` | contact-page headers and CRCs, dirty-page state, stable slot allocation, and bounded resumable legacy migration across power loss |
| `test_power_management` | `src/helpers/PowerManagementUtils.h` | median filtering of a brownout outlier and valid-reading requirements for the boot lock |
| `test_rx_power_saving` | `src/helpers/radiolib/RXPowerSaving.h` | level-derived timing, automatic level/preamble selection, equivalent SF7/BW500, SF6/BW250, and SF5/BW125 profiles, SF5/BW62.5 with preamble 16, SF5/BW250/BW500 continuous-RX fallback, automatic duty-cycle resume, and SX1262 TCXO timing thresholds |
| `test_rx_power_saving` | `src/helpers/radiolib/RXPowerSaving.h` | level-derived timing, automatic level/preamble selection, equivalent SF7/BW500, SF6/BW250, and SF5/BW125 profiles, SF5/BW62.5 with preamble 16, normal-preamble SF5/BW250/BW500 continuous-RX fallback, opt-in 64-symbol SF5/BW250 level-8 timing, automatic duty-cycle resume, and SX1262 TCXO timing thresholds |
| `test_region_names` | `src/helpers/RegionNameUtils.h` | canonical public-region markers while preserving distinct private and differently named regions |
| `test_routing_policy` | `src/helpers/RoutingPolicy.h` | scoped/unscoped flood hop limits and selection of direct, path-return, mirrored-scope, default-scope, or unscoped replies |
| `test_rs232_uart` | `src/helpers/bridges/RS232UartUtils.h` | stopping the active UART peripheral before reassigning its pins |
@@ -64,8 +64,8 @@ TEST(RxPowerSaving, RequestedLevelIsRaisedOnlyToRequiredMinimum) {
ASSERT_TRUE(calcRxPowerSavingLevelAtOrAbove(
1, 7, 500.0f, 16, sx1262_tcxo_transition_us,
&rx_us, &sleep_us, &effective_level, &effective_preamble));
EXPECT_EQ(7U, effective_level);
EXPECT_EQ(32U, effective_preamble);
EXPECT_EQ(LORA_FAST_PREAMBLE == 64 ? 3U : 7U, effective_level);
EXPECT_EQ((uint8_t)LORA_FAST_PREAMBLE, effective_preamble);
EXPECT_TRUE(canStartRxPowerSavingDutyCycle(
rx_us, sleep_us, sx1262_tcxo_transition_us));
@@ -73,7 +73,7 @@ TEST(RxPowerSaving, RequestedLevelIsRaisedOnlyToRequiredMinimum) {
8, 7, 500.0f, 16, sx1262_tcxo_transition_us,
&rx_us, &sleep_us, &effective_level, &effective_preamble));
EXPECT_EQ(8U, effective_level);
EXPECT_EQ(32U, effective_preamble);
EXPECT_EQ((uint8_t)LORA_FAST_PREAMBLE, effective_preamble);
EXPECT_TRUE(canStartRxPowerSavingDutyCycle(
rx_us, sleep_us, sx1262_tcxo_transition_us));
}
@@ -93,7 +93,7 @@ TEST(RxPowerSaving, Sf5Bw500StillFallsBackWithPreamble32) {
EXPECT_TRUE(rxPowerSavingUsesContinuousFallback(rx_us, sleep_us));
}
TEST(RxPowerSaving, Sf5Bw250StillFallsBackWithPreamble32OnTcxoRadio) {
TEST(RxPowerSaving, Sf5Bw250NeedsLongerTransmittedPreambleOnTcxoRadio) {
constexpr uint32_t sx1262_tcxo_transition_us = 6000;
uint32_t rx_us = 0;
uint32_t sleep_us = 0;
@@ -112,11 +112,55 @@ TEST(RxPowerSaving, Sf5Bw250StillFallsBackWithPreamble32OnTcxoRadio) {
ASSERT_TRUE(calcRxPowerSavingLevelAtOrAbove(
1, 5, 250.0f, 16, sx1262_tcxo_transition_us,
&rx_us, &sleep_us, &effective_level, &effective_preamble));
#if LORA_FAST_PREAMBLE == 64
EXPECT_EQ(8U, effective_level);
EXPECT_EQ(64U, effective_preamble);
EXPECT_EQ(1252U, rx_us);
EXPECT_EQ(6424U, sleep_us);
EXPECT_FALSE(rxPowerSavingUsesContinuousFallback(rx_us, sleep_us));
#else
EXPECT_EQ(0U, effective_level);
EXPECT_EQ(0U, effective_preamble);
EXPECT_TRUE(rxPowerSavingUsesContinuousFallback(rx_us, sleep_us));
#endif
}
TEST(RxPowerSaving, Sf5Bw250UsesPreamble64AtEffectiveLevel8) {
constexpr uint32_t sx1262_tcxo_transition_us = 6000;
uint32_t rx_us = 0;
uint32_t sleep_us = 0;
uint8_t effective_level = 0;
uint8_t effective_preamble = 0;
ASSERT_TRUE(calcRxPowerSavingLevelAtOrAbove(
1, 5, 250.0f, 64, sx1262_tcxo_transition_us,
&rx_us, &sleep_us, &effective_level, &effective_preamble));
EXPECT_EQ(8U, effective_level);
EXPECT_EQ(64U, effective_preamble);
EXPECT_EQ(1252U, rx_us);
EXPECT_EQ(6424U, sleep_us);
EXPECT_TRUE(canStartRxPowerSavingDutyCycle(
rx_us, sleep_us, sx1262_tcxo_transition_us));
}
#if LORA_FAST_PREAMBLE == 64
TEST(RxPowerSaving, FastPreamble64BuildSelectsItAutomatically) {
constexpr uint32_t sx1262_tcxo_transition_us = 6000;
uint32_t rx_us = 0;
uint32_t sleep_us = 0;
uint8_t effective_level = 0;
uint8_t effective_preamble = 0;
ASSERT_TRUE(calcRxPowerSavingLevelAtOrAbove(
1, 5, 250.0f, 16, sx1262_tcxo_transition_us,
&rx_us, &sleep_us, &effective_level, &effective_preamble));
EXPECT_EQ(8U, effective_level);
EXPECT_EQ(64U, effective_preamble);
EXPECT_EQ(1252U, rx_us);
EXPECT_EQ(6424U, sleep_us);
}
#endif
TEST(RxPowerSaving, Sf5Bw125UsesPreamble32AtEffectiveLevel7) {
constexpr uint32_t sx1262_tcxo_transition_us = 6000;
uint32_t rx_us = 0;
@@ -127,10 +171,15 @@ TEST(RxPowerSaving, Sf5Bw125UsesPreamble32AtEffectiveLevel7) {
ASSERT_TRUE(calcRxPowerSavingLevelAtOrAbove(
1, 5, 125.0f, 16, sx1262_tcxo_transition_us,
&rx_us, &sleep_us, &effective_level, &effective_preamble));
EXPECT_EQ(7U, effective_level);
EXPECT_EQ(32U, effective_preamble);
EXPECT_EQ(LORA_FAST_PREAMBLE == 64 ? 3U : 7U, effective_level);
EXPECT_EQ((uint8_t)LORA_FAST_PREAMBLE, effective_preamble);
#if LORA_FAST_PREAMBLE == 64
EXPECT_EQ(3641U, rx_us);
EXPECT_EQ(6414U, sleep_us);
#else
EXPECT_EQ(2731U, rx_us);
EXPECT_EQ(6101U, sleep_us);
#endif
EXPECT_TRUE(canStartRxPowerSavingDutyCycle(
rx_us, sleep_us, sx1262_tcxo_transition_us));
}
@@ -145,10 +194,15 @@ TEST(RxPowerSaving, Sf6Bw250UsesPreamble32AtEffectiveLevel7) {
ASSERT_TRUE(calcRxPowerSavingLevelAtOrAbove(
1, 6, 250.0f, 16, sx1262_tcxo_transition_us,
&rx_us, &sleep_us, &effective_level, &effective_preamble));
EXPECT_EQ(7U, effective_level);
EXPECT_EQ(32U, effective_preamble);
EXPECT_EQ(LORA_FAST_PREAMBLE == 64 ? 3U : 7U, effective_level);
EXPECT_EQ((uint8_t)LORA_FAST_PREAMBLE, effective_preamble);
#if LORA_FAST_PREAMBLE == 64
EXPECT_EQ(3641U, rx_us);
EXPECT_EQ(6414U, sleep_us);
#else
EXPECT_EQ(2731U, rx_us);
EXPECT_EQ(6101U, sleep_us);
#endif
}
TEST(RxPowerSaving, Sf5Bw62Point5UsesPreamble16AtEffectiveLevel10) {