Harden fast RXPS timing and board behavior

This commit is contained in:
mikecarper committed 2026-08-24 15:10:54 -07:00
1 parent 1a43123d2c
commit b984fdcb4c
21 files changed
+607 -30

No files matched your search

+23 -1
View File
@@ -658,7 +658,11 @@ capacity. Remote use follows the existing administrator-command permissions.
**Default:** `869.525,250,11,5`
**Note:** Requires reboot to apply
**Note:** Requires reboot to apply. If RXPS is enabled and the saved minimum
level/preamble cannot safely cover the new radio timing, the command reply
reports the effective level and preamble, or `RXPS continuous-fast` when no
level through 10 is safe. Slower settings recalculate from the saved RXPS
minimum and return to it exactly when it is safe.
---
@@ -721,6 +725,24 @@ capacity. Remote use follows the existing administrator-command permissions.
- `del radioat` and `del tempradioat` delete all entries when `n` is omitted.
- Each queue supports 3 entries. Scheduled entries are not saved across reboot.
- `radioat` saves the new radio preferences when it fires. `tempradioat` applies temporarily, then reverts to the saved radio preferences.
- A successful scheduling reply notes any RXPS effective-level/preamble change
required by the scheduled tuple. `RXPS continuous-fast` means the tuple is
accepted but will use continuous receive because no safe duty-cycle level is
available. Returning to slower settings recalculates from the saved RXPS
minimum.
On SX1262+TCXO boards, the tested fast-setting boundaries are:
| SF | BW (kHz) | Effective preamble | Minimum effective RXPS level | Result |
|---:|---------:|-------------------:|-----------------------------:|:-------|
| 7 | 500 | 32 | 7 | RXPS duty cycling |
| 6 | 250 | 32 | 7 | RXPS duty cycling |
| 5 | 125 | 32 | 7 | RXPS duty cycling |
| 5 | 62.5 | 16 | 10 | RXPS duty cycling |
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.
---
+30 -1
View File
@@ -107,7 +107,36 @@ 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.
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.
If neither adjustment leaves enough time for the radio to wake, RXPS stays
logically enabled but receives continuously instead of rejecting the radio
setting or starting an invalid duty cycle. `get radio.rxps` reports this as
`mode=continuous-fast` and reports any `effective-level` or
`effective-preamble` adjustment. On SX1262 boards with a TCXO, SF5/BW125 works
at effective level 7 with preamble 32; SF5/BW250 and SF5/BW500 use the
continuous fallback. A later compatible SF/BW change resumes duty cycling
without another RXPS command.
For the SX1262+TCXO boards tested here, these fast combinations are the useful
RXPS boundary profiles (CR does not change the RXPS preamble timing):
| SF | BW (kHz) | Effective preamble | Minimum effective level | RX / sleep |
|---:|---------:|-------------------:|------------------------:|-----------:|
| 7 | 500 | 32 | 7 | 2731 / 6101 us |
| 6 | 250 | 32 | 7 | 2731 / 6101 us |
| 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`.
```
get wifi.powersave
+81 -3
View File
@@ -1790,6 +1790,7 @@ bool MyMesh::scheduleTempRadio(float freq, float bw, uint8_t sf, uint8_t cr,
_temp_radio_failures = 0;
snprintf(reply, reply_size, "OK - temp params for %lu mins",
(unsigned long)timeout_mins);
appendRxPowerSavingAdjustmentNote(reply, reply_size, sf, bw);
return true;
}
@@ -4108,6 +4109,46 @@ bool MyMesh::applyAndSaveRxPowerSaving(const char* value, char* reply) {
return true;
}
void MyMesh::appendRxPowerSavingAdjustmentNote(char* reply, size_t reply_size,
uint8_t sf, float bw) const {
if (!reply || reply_size == 0 || !_prefs.rx_powersaving_enabled
|| _prefs.rx_ps_level < 1 || _prefs.rx_ps_level > 10) {
return;
}
uint32_t rx_us = 0;
uint32_t sleep_us = 0;
uint8_t effective_level = 0;
uint8_t effective_preamble = 0;
if (!recalcRxPowerSavingFromLevel(
_prefs.rx_ps_level, sf, bw, _prefs.rx_ps_preamble,
&rx_us, &sleep_us, &effective_level, &effective_preamble)) {
return;
}
const size_t used = strlen(reply);
if (used >= reply_size - 1) return;
if (rxPowerSavingUsesContinuousFallback(rx_us, sleep_us)) {
snprintf(reply + used, reply_size - used,
"; RXPS continuous-fast (no safe level %u-10)",
(unsigned)_prefs.rx_ps_level);
return;
}
const uint8_t requested_preamble = _prefs.rx_ps_preamble == 0
? rxPowerSavingPreambleForSF(sf) : _prefs.rx_ps_preamble;
if (effective_level == _prefs.rx_ps_level
&& effective_preamble == requested_preamble) {
return;
}
snprintf(reply + used, reply_size - used,
"; RXPS effective level %u, preamble %u (saved minimum %u)",
(unsigned)effective_level, (unsigned)effective_preamble,
(unsigned)_prefs.rx_ps_level);
}
#ifdef ENABLE_USB_INTERFACE
Stream& MyMesh::terminalOutput() {
return _terminal_output != NULL ? *_terminal_output : Serial;
@@ -4953,12 +4994,49 @@ void MyMesh::handleTerminalCommand(char* command) {
if (!radio_driver.supportsRxPowerSaving()) {
terminalOutput().print(" ERROR: RX power saving is unsupported on this radio\r\n");
} else {
terminalOutput().printf(" radio.rxps %s,level=%u,preamble=%u,rx=%lu,sleep=%lu\r\n",
uint8_t effective_level = 0;
uint8_t effective_preamble = 0;
uint32_t effective_rx_us = _prefs.rx_ps_rx_us;
uint32_t effective_sleep_us = _prefs.rx_ps_sleep_us;
uint8_t active_sf = _prefs.sf;
float active_bw = _prefs.bw;
#if defined(COMPANION_RADIO_FULL)
if (isTempRadioActive()) {
active_sf = _temp_radio_sf;
active_bw = _temp_radio_bw;
}
#endif
if (_prefs.rx_ps_level != 0) {
recalcRxPowerSavingFromLevel(
_prefs.rx_ps_level, active_sf, active_bw,
_prefs.rx_ps_preamble, &effective_rx_us, &effective_sleep_us,
&effective_level, &effective_preamble);
}
char effective_suffix[48] = {};
const bool adjusted_level = effective_level != 0
&& effective_level != _prefs.rx_ps_level;
const bool adjusted_preamble = effective_preamble != 0
&& effective_preamble != _prefs.rx_ps_preamble;
if (adjusted_level && adjusted_preamble) {
snprintf(effective_suffix, sizeof(effective_suffix),
",effective-level=%u,effective-preamble=%u",
(unsigned)effective_level, (unsigned)effective_preamble);
} else if (adjusted_level) {
snprintf(effective_suffix, sizeof(effective_suffix),
",effective-level=%u", (unsigned)effective_level);
} else if (adjusted_preamble) {
snprintf(effective_suffix, sizeof(effective_suffix),
",effective-preamble=%u", (unsigned)effective_preamble);
}
terminalOutput().printf(" radio.rxps %s,level=%u,preamble=%u,rx=%lu,sleep=%lu%s%s\r\n",
_prefs.rx_powersaving_enabled ? "on" : "off",
(unsigned)_prefs.rx_ps_level,
(unsigned)_prefs.rx_ps_preamble,
(unsigned long)_prefs.rx_ps_rx_us,
(unsigned long)_prefs.rx_ps_sleep_us);
(unsigned long)effective_rx_us,
(unsigned long)effective_sleep_us,
radio_driver.isRxPowerSavingContinuousFallback()
? ",mode=continuous-fast" : "",
effective_suffix);
}
#if defined(ESP32) && defined(WIFI_SSID)
} else if (strcmp(command, "get wifi.powersave") == 0) {
+2
View File
@@ -302,6 +302,8 @@ private:
bool applyAndSaveRxBoostedGain(bool enabled);
bool applyAndSavePowerSaving(const char* value, char* reply);
bool applyAndSaveRxPowerSaving(const char* value, char* reply);
void appendRxPowerSavingAdjustmentNote(char* reply, size_t reply_size,
uint8_t sf, float bw) const;
#if defined(ESP32) && defined(WIFI_SSID)
bool applyAndSaveWiFiPowerSaving(const char* value, char* reply,
size_t reply_size);
+29 -4
View File
@@ -109,7 +109,9 @@ class HomeScreen : public UIScreen {
#if UI_SENSORS_PAGE == 1
SENSORS,
#endif
#ifndef UI_NO_HIBERNATE
SHUTDOWN,
#endif
Count // keep as last
};
@@ -165,9 +167,18 @@ class HomeScreen : public UIScreen {
}
display.setTextSize(1);
bool charging = board.isExternalPowered();
#ifdef PIN_BUZZER
bool muted = _task->isBuzzerQuiet();
#endif
int uptimeWidth = display.getTextWidth(uptime);
int spaceWidth = display.getTextWidth(" ");
display.setCursor(iconX - uptimeWidth - spaceWidth, iconY);
int statusWidth = charging ? 9 : 0;
#ifdef PIN_BUZZER
if (muted) statusWidth += 9;
#endif
display.setCursor(iconX - statusWidth - uptimeWidth - spaceWidth, iconY);
display.print(uptime);
// battery outline
@@ -180,11 +191,21 @@ class HomeScreen : public UIScreen {
int fillWidth = (batteryPercentage * (iconWidth - 4)) / 100;
display.fillRect(iconX + 2, iconY + 2, fillWidth, iconHeight - 4);
// show muted icon if buzzer is muted
// Show external power beside the battery. Most boards have no
// charge-complete signal, so use a high percentage band for the plug.
if (charging) {
static constexpr int BATT_FULL_PCT = 95;
const uint8_t* symbol =
batteryPercentage >= BATT_FULL_PCT ? plug_icon : charging_icon;
display.setColor(UIColor::title_txt);
display.drawXbm(iconX - 9, iconY + 1, symbol, 8, 8);
}
// Keep the mute icon left of the charging icon when both are present.
#ifdef PIN_BUZZER
if (_task->isBuzzerQuiet()) {
if (muted) {
display.setColor(UIColor::warning_txt);
display.drawXbm(iconX - 9, iconY + 1, muted_icon, 8, 8);
display.drawXbm(iconX - (charging ? 18 : 9), iconY + 1, muted_icon, 8, 8);
}
#endif
}
@@ -487,6 +508,7 @@ public:
if (sensors_scroll) sensors_scroll_offset = (sensors_scroll_offset+1)%sensors_nb;
else sensors_scroll_offset = 0;
#endif
#ifndef UI_NO_HIBERNATE
} else if (_page == HomePage::SHUTDOWN) {
display.setColor(UIColor::corp_blue);
display.setTextSize(1);
@@ -498,6 +520,7 @@ public:
display.drawXbm((display.width() - 32) / 2, 18, power_icon, 32, 32);
display.drawTextCentered(display.width() / 2, 64 - 11, "hibernate:" PRESS_LABEL);
}
#endif
}
return 5000; // next render after 5000 ms
}
@@ -544,10 +567,12 @@ public:
return true;
}
#endif
#ifndef UI_NO_HIBERNATE
if (c == KEY_ENTER && _page == HomePage::SHUTDOWN) {
_shutdown_init = true; // need to wait for button to be released
return true;
}
#endif
return false;
}
};
+11 -1
View File
@@ -119,4 +119,14 @@ static const uint8_t advert_icon[] = {
static const uint8_t muted_icon[] = {
0x20, 0x6a, 0xea, 0xe4, 0xe4, 0xea, 0x6a, 0x20
};
};
// Small lightning bolt, 8x8px, shown while externally powered and charging.
static const uint8_t charging_icon[] = {
0x18, 0x30, 0x60, 0xFC, 0x18, 0x30, 0x60, 0xC0
};
// Small power plug, 8x8px, shown once the battery reaches the full band.
static const uint8_t plug_icon[] = {
0x24, 0x24, 0x7E, 0x7E, 0x7E, 0x3C, 0x18, 0x18
};
-4
View File
@@ -125,10 +125,6 @@ struct RepeaterStats {
uint32_t n_recv_errors;
};
#ifndef MAX_CLIENTS
#define MAX_CLIENTS 32
#endif
struct NeighbourInfo {
mesh::Identity id;
uint32_t advert_timestamp;
+1
View File
@@ -116,6 +116,7 @@ public:
virtual bool supportsRxPowerSaving() const { return false; }
virtual bool setRxPowerSaving(bool enabled, uint32_t rx_us, uint32_t sleep_us) { return !enabled; }
virtual bool isRxPowerSavingContinuousFallback() const { return false; }
virtual bool supportsRxPowerSavingRfRxDisable() const { return false; }
virtual bool setRxPowerSavingRfRxDisabled(bool disabled) {
(void)disabled;
+3
View File
@@ -415,6 +415,9 @@ void BaseChatMesh::handleReturnPathRetry(const ContactInfo& contact, const uint8
int BaseChatMesh::searchChannelsByHash(const uint8_t* hash, mesh::GroupChannel dest[], int max_matches) {
int n = 0;
for (int i = 0; i < MAX_GROUP_CHANNELS && n < max_matches; i++) {
// Empty slots have an all-zero secret and can otherwise validate null-key
// traffic as if it belonged to a configured local channel.
if (channels[i].name[0] == 0) continue;
if (channels[i].channel.hash[0] == hash[0]) {
dest[n++] = channels[i].channel;
}
+1 -1
View File
@@ -43,7 +43,7 @@ struct ClientInfo {
};
#ifndef MAX_CLIENTS
#define MAX_CLIENTS 20
#define MAX_CLIENTS 32
#endif
class ClientACL {
+56 -3
View File
@@ -453,7 +453,50 @@ bool CommonCLI::calculateRxPowerSavingLevel(uint32_t level, uint8_t sf, float bw
if (level < 1 || level > 10 || (preamble != 16 && preamble != 32)) {
return false;
}
return calcRxPowerSavingLevel((uint8_t)level, sf, bw, (uint8_t)preamble, rx_us, sleep_us);
return calcRxPowerSavingLevelAtOrAbove(
(uint8_t)level, sf, bw, (uint8_t)preamble,
rxPowerSavingDefaultTransitionUs(), rx_us, sleep_us);
}
static void appendRxPowerSavingAdjustmentNote(char* reply, const NodePrefs* prefs,
uint8_t sf, float bw) {
if (!reply || !prefs || !prefs->rx_powersaving_enabled
|| prefs->rx_ps_level < 1 || prefs->rx_ps_level > 10) {
return;
}
uint32_t rx_us = 0;
uint32_t sleep_us = 0;
uint8_t effective_level = 0;
uint8_t effective_preamble = 0;
if (!recalcRxPowerSavingFromLevel(
prefs->rx_ps_level, sf, bw, prefs->rx_ps_preamble,
&rx_us, &sleep_us, &effective_level, &effective_preamble)) {
return;
}
char note[96] = {};
if (rxPowerSavingUsesContinuousFallback(rx_us, sleep_us)) {
snprintf(note, sizeof(note),
"; RXPS continuous-fast (no safe level %u-10)",
(unsigned)prefs->rx_ps_level);
} else {
const uint8_t requested_preamble = prefs->rx_ps_preamble == 0
? rxPowerSavingPreambleForSF(sf) : prefs->rx_ps_preamble;
if (effective_level == prefs->rx_ps_level
&& effective_preamble == requested_preamble) {
return;
}
snprintf(note, sizeof(note),
"; RXPS effective level %u, preamble %u (saved minimum %u)",
(unsigned)effective_level, (unsigned)effective_preamble,
(unsigned)prefs->rx_ps_level);
}
const size_t used = strlen(reply);
if (used < 159) {
snprintf(reply + used, 160 - used, "%s", note);
}
}
// Recomputes rx_ps_rx_us/rx_ps_sleep_us from the stored level and the current
@@ -2409,6 +2452,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* re
&& isValidLoRaBandwidth(bw) && temp_timeout_mins > 0) {
_callbacks->applyTempRadioParams(freq, bw, sf, cr, temp_timeout_mins);
sprintf(reply, "OK - temp params for %d mins", temp_timeout_mins);
appendRxPowerSavingAdjustmentNote(reply, _prefs, sf, bw);
} else {
strcpy(reply, "Error, invalid params");
}
@@ -3464,6 +3508,8 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep
(unsigned long)rx_us,
(unsigned long)sleep_us,
(unsigned long)preamble);
appendRxPowerSavingAdjustmentNote(
reply, _prefs, _prefs->sf, _prefs->bw);
} else {
sprintf(reply, "OK - %s,%lu,%lu", enable ? "on" : "off",
(unsigned long)rx_us, (unsigned long)sleep_us);
@@ -3495,11 +3541,12 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep
_prefs->bw = bw;
// Retune level-based RX powersaving to the new SF/BW. Persist only; the
// radio itself is "reboot to apply", and begin() re-arms the timings then.
bool rxps_retuned = recalcRxPowerSavingFromLevel(
recalcRxPowerSavingFromLevel(
_prefs->rx_ps_level, _prefs->sf, _prefs->bw, _prefs->rx_ps_preamble, &_prefs->rx_ps_rx_us,
&_prefs->rx_ps_sleep_us); // retune level-based timings to the loaded SF/BW
_callbacks->savePrefs();
strcpy(reply, rxps_retuned ? "OK - reboot to apply (rxps retuned)" : "OK - reboot to apply");
strcpy(reply, "OK - reboot to apply");
appendRxPowerSavingAdjustmentNote(reply, _prefs, sf, bw);
} else {
strcpy(reply, "Error, invalid radio params");
}
@@ -3513,6 +3560,9 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep
strcpy(reply, "Error, invalid radio params");
} else {
_callbacks->addScheduledRadioParams(false, freq, bw, sf, cr, start_time, end_time, reply);
if (strncmp(reply, "OK", 2) == 0) {
appendRxPowerSavingAdjustmentNote(reply, _prefs, sf, bw);
}
}
} else if (memcmp(config, "tempradioat ", 12) == 0) {
float freq, bw;
@@ -3524,6 +3574,9 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep
strcpy(reply, "Error, invalid radio params");
} else {
_callbacks->addScheduledRadioParams(true, freq, bw, sf, cr, start_time, end_time, reply);
if (strncmp(reply, "OK", 2) == 0) {
appendRxPowerSavingAdjustmentNote(reply, _prefs, sf, bw);
}
}
} else if (memcmp(config, "lat ", 4) == 0) {
float latitude = 0.0f;
@@ -99,9 +99,17 @@ protected:
if (!_rx_ps_enabled || _nf_calib_active) {
// plain continuous RX: powersaving off, or a periodic noise-floor
// calibration window is in progress
if (!_rx_ps_enabled) _rx_ps_continuous_fallback = false;
return _radio->startReceive();
}
if (rxPowerSavingUsesContinuousFallback(_rx_ps_rx_us, _rx_ps_sleep_us)) {
_rx_ps_continuous_fallback = true;
return _radio->startReceive();
}
_rx_ps_continuous_fallback = false;
const RadioLibIrqFlags_t irqFlags = RADIOLIB_IRQ_RX_DEFAULT_FLAGS;
// route RX timeout (false preamble detect) and error IRQs to the IRQ pin
// as well, so the wrapper re-arms RX instead of waiting forever
@@ -117,6 +125,7 @@ protected:
return err;
}
_rx_ps_continuous_fallback = true;
MESH_DEBUG_PRINTLN("CustomLR1110Wrapper: error: startReceiveDutyCycle(%d), falling back to continuous RX", err);
return _radio->startReceive();
}
+14
View File
@@ -2,6 +2,7 @@
#include <RadioLib.h>
#include "MeshCore.h"
#include "RXPowerSaving.h"
#ifndef SX126X_TX_BUSY_TIMEOUT_MS
#define SX126X_TX_BUSY_TIMEOUT_MS 1000UL
@@ -13,6 +14,7 @@ class CustomSX1262 : public SX1262 {
uint32_t _activityAt = 0;
bool _headerSeen = false;
bool _rx_ps_rf_rx_disabled = false;
uint32_t _rxDutyCycleTransitionUs = 1000;
public:
CustomSX1262(Module *mod) : SX1262(mod) { }
@@ -100,6 +102,11 @@ class CustomSX1262 : public SX1262 {
mesh::usbLoggingPort().println(status);
return false; // fail
}
// RadioLib's default DIO3 TCXO delay is 5 ms and its RX duty-cycle
// command adds another 1 ms for sleep/wake transitions. If begin()
// fell back to a crystal, only the fixed 1 ms transition remains.
_rxDutyCycleTransitionUs = tcxo > 0.0f ? 6000UL : 1000UL;
setCRC(1);
@@ -268,6 +275,7 @@ class CustomSX1262 : public SX1262 {
}
return true;
}
if (preamble) {
if (_activityAt == 0) _activityAt = now;
if (now - _activityAt > _preambleMillis) {
@@ -283,6 +291,12 @@ class CustomSX1262 : public SX1262 {
return false;
}
bool canUseRxPowerSavingDutyCycle(uint32_t rx_us, uint32_t sleep_us) const {
return !rxPowerSavingUsesContinuousFallback(rx_us, sleep_us)
&& canStartRxPowerSavingDutyCycle(
rx_us, sleep_us, _rxDutyCycleTransitionUs);
}
void setPreambleMillis(uint32_t preambleMillis) {
_preambleMillis = preambleMillis;
MESH_DEBUG_PRINTLN("Set _preambleMillis=%u", _preambleMillis);
@@ -113,9 +113,18 @@ protected:
if (!_rx_ps_enabled || _nf_calib_active) {
// plain continuous RX: powersaving off, or a periodic noise-floor
// calibration window is in progress
if (!_rx_ps_enabled) _rx_ps_continuous_fallback = false;
return _radio->startReceive();
}
if (!((CustomSX1262 *)_radio)->canUseRxPowerSavingDutyCycle(
_rx_ps_rx_us, _rx_ps_sleep_us)) {
_rx_ps_continuous_fallback = true;
return _radio->startReceive();
}
_rx_ps_continuous_fallback = false;
const RadioLibIrqFlags_t irqFlags = RADIOLIB_IRQ_RX_DEFAULT_FLAGS;
const RadioLibIrqFlags_t irqMask =
(1UL << RADIOLIB_IRQ_RX_DONE) |
@@ -129,6 +138,7 @@ protected:
return err;
}
_rx_ps_continuous_fallback = true;
MESH_DEBUG_PRINTLN("CustomSX1262Wrapper: error: startReceiveDutyCycle(%d), falling back to continuous RX", err);
return _radio->startReceive();
}
+118 -5
View File
@@ -6,6 +6,13 @@
#define RX_POWERSAVING_MIN_PERIOD_US 1000UL
#define RX_POWERSAVING_MAX_PERIOD_US 30000000UL
// 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
// timings. Using an in-range pair lets the existing radio-parameter API carry
// the fallback without persisting invalid sub-millisecond periods.
#define RX_POWERSAVING_CONTINUOUS_FALLBACK_US RX_POWERSAVING_MIN_PERIOD_US
// The named profiles are level presets pinned to a 16-symbol preamble: most
// deployed senders still transmit 16-symbol preambles regardless of the newer
// SF-based rule (32 for SF <= 8). Revisit once the field has largely migrated.
@@ -59,6 +66,31 @@ inline bool isValidRxPowerSavingPeriod(uint32_t us) {
return us >= RX_POWERSAVING_MIN_PERIOD_US && us <= RX_POWERSAVING_MAX_PERIOD_US;
}
inline bool rxPowerSavingUsesContinuousFallback(uint32_t rx_us, uint32_t sleep_us) {
return rx_us == RX_POWERSAVING_CONTINUOUS_FALLBACK_US
&& sleep_us == RX_POWERSAVING_CONTINUOUS_FALLBACK_US;
}
inline uint32_t rxPowerSavingDefaultTransitionUs() {
#if defined(SX126X_DIO3_TCXO_VOLTAGE)
// RadioLib uses a 5 ms DIO3 TCXO delay plus 1 ms for sleep/wake.
return 6000UL;
#else
// Crystal and non-SX126x implementations still need transition headroom.
return 1000UL;
#endif
}
// Mirrors the SX126x/RF duty-cycle timer conversion: both encoded periods must
// contain at least one 15.625 us tick after the radio/TCXO transition time has
// been removed from the sleep period.
inline bool canStartRxPowerSavingDutyCycle(uint32_t rx_us, uint32_t sleep_us,
uint32_t transition_us) {
if (sleep_us <= transition_us) return false;
return ((rx_us * 8UL) / 125UL) != 0
&& (((sleep_us - transition_us) * 8UL) / 125UL) != 0;
}
// MeshCore preamble convention used for the RX powersaving timing calculation.
// Must stay in sync with RadioLibWrapper::preambleLengthForSF() (the value the
// radio actually transmits); kept local here because CommonCLI is radio-agnostic.
@@ -82,7 +114,8 @@ 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 (level < 1 || level > 10 || sf < 5 || sf > 12 || bw <= 0.0f || (preamble != 16 && preamble != 32)) {
if (!rx_us || !sleep_us || level < 1 || level > 10 || sf < 5 || sf > 12
|| bw <= 0.0f || (preamble != 16 && preamble != 32)) {
return false;
}
@@ -96,8 +129,83 @@ inline bool calcRxPowerSavingLevel(uint8_t level, uint8_t sf, float bw, uint8_t
const float rx_symbols = rx_start_symbols + amount * (rx_edge_symbols - rx_start_symbols);
const float sleep_symbols = sleep_start_symbols + amount * (sleep_edge_symbols - sleep_start_symbols);
*rx_us = ceilPositiveFloat(rx_symbols * symbol_us);
*sleep_us = (uint32_t)(sleep_symbols * symbol_us);
uint32_t calculated_rx_us = ceilPositiveFloat(rx_symbols * symbol_us);
uint32_t calculated_sleep_us = (uint32_t)(sleep_symbols * symbol_us);
// At high-rate settings (notably SF5/BW500), the entire preamble can be
// shorter than the radio's sleep/wake transition. Scaling either period up
// would break the preamble-overlap guarantee and silently lose packets.
// Canonicalize that case to continuous RX instead; the wrapper will resume
// a real duty cycle automatically when slower parameters are restored.
if (calculated_rx_us < RX_POWERSAVING_MIN_PERIOD_US
|| calculated_sleep_us < RX_POWERSAVING_MIN_PERIOD_US) {
calculated_rx_us = RX_POWERSAVING_CONTINUOUS_FALLBACK_US;
calculated_sleep_us = RX_POWERSAVING_CONTINUOUS_FALLBACK_US;
}
*rx_us = calculated_rx_us;
*sleep_us = calculated_sleep_us;
return true;
}
// Treat requested_level as the operator's minimum. Faster tuples shorten each
// LoRa symbol, so progressively more aggressive levels are tried until the
// sleep window is long enough to cover the radio/TCXO transition. The
// configured level itself is not changed; callers persist only the effective
// timings, allowing slower tuples to return to the requested level later.
// effective_level and effective_preamble are 0 when no combination can safely
// duty-cycle and continuous RX is required for this tuple.
inline bool calcRxPowerSavingLevelAtOrAbove(
uint8_t requested_level, uint8_t sf, float bw, uint8_t preamble,
uint32_t transition_us, uint32_t *rx_us, uint32_t *sleep_us,
uint8_t *effective_level = nullptr,
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)) {
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;
for (uint8_t pass = 0; pass < pass_count; pass++) {
const uint8_t candidate_preamble = pass == 0 ? preamble : 32;
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;
const float rx_start_symbols = candidate_preamble == 16 ? 12.0f : 16.0f;
const float sleep_start_symbols = candidate_preamble == 16 ? 2.0f : 15.0f;
const float rx_edge_symbols = 8.0f;
const float sleep_edge_symbols = (float)candidate_preamble + 4.25f - 8.0f;
const float rx_symbols = rx_start_symbols
+ amount * (rx_edge_symbols - rx_start_symbols);
const float sleep_symbols = sleep_start_symbols
+ amount * (sleep_edge_symbols - sleep_start_symbols);
const uint32_t candidate_rx_us = ceilPositiveFloat(rx_symbols * symbol_us);
const uint32_t candidate_sleep_us = (uint32_t)(sleep_symbols * symbol_us);
if (isValidRxPowerSavingPeriod(candidate_rx_us)
&& isValidRxPowerSavingPeriod(candidate_sleep_us)
&& canStartRxPowerSavingDutyCycle(
candidate_rx_us, candidate_sleep_us, transition_us)) {
*rx_us = candidate_rx_us;
*sleep_us = candidate_sleep_us;
if (effective_level) *effective_level = level;
if (effective_preamble) *effective_preamble = candidate_preamble;
return true;
}
}
}
*rx_us = RX_POWERSAVING_CONTINUOUS_FALLBACK_US;
*sleep_us = RX_POWERSAVING_CONTINUOUS_FALLBACK_US;
if (effective_level) *effective_level = 0;
if (effective_preamble) *effective_preamble = 0;
return true;
}
@@ -115,7 +223,10 @@ inline void ensureRxPowerSavingDefaults(uint32_t *rx_ps_rx_us, uint32_t *rx_ps_s
// radio SF/BW. No-op (returns false) for manual timings (rx_ps_level == 0).
// Lets level-based RX powersaving auto-retune when SF/BW change.
inline bool recalcRxPowerSavingFromLevel(uint8_t level, uint8_t sf, float bw, uint8_t preamble,
uint32_t *rx_ps_rx_us, uint32_t *rx_ps_sleep_us) {
uint32_t *rx_ps_rx_us, uint32_t *rx_ps_sleep_us,
uint8_t *effective_level = nullptr,
uint8_t *effective_preamble = nullptr,
uint32_t transition_us = rxPowerSavingDefaultTransitionUs()) {
if (level < 1 || level > 10) {
return false; // manual: nothing to recompute
}
@@ -125,7 +236,9 @@ inline bool recalcRxPowerSavingFromLevel(uint8_t level, uint8_t sf, float bw, ui
}
uint32_t rx_us, sleep_us;
if (!calcRxPowerSavingLevel(level, sf, bw, preamble, &rx_us, &sleep_us)) {
if (!calcRxPowerSavingLevelAtOrAbove(
level, sf, bw, preamble, transition_us, &rx_us, &sleep_us,
effective_level, effective_preamble)) {
return false;
}
+13 -5
View File
@@ -150,6 +150,8 @@ mesh::RadioParamApplyResult RadioLibWrapper::trySetParams(float freq, float bw,
_rx_ps_enabled = true;
_rx_ps_rx_us = rx_ps_timings[0];
_rx_ps_sleep_us = rx_ps_timings[1];
_rx_ps_continuous_fallback = rxPowerSavingUsesContinuousFallback(
_rx_ps_rx_us, _rx_ps_sleep_us);
}
} else {
bool restored = had_previous_params
@@ -424,14 +426,16 @@ void RadioLibWrapper::endNoiseFloorCalib(unsigned long now) {
}
void RadioLibWrapper::loop() {
if (_rx_ps_enabled) {
if (_rx_ps_enabled && !_rx_ps_continuous_fallback) {
rxPsWatchdogCheck();
}
unsigned long now = millis();
if (_nf_calib_active || _nf_refresh_requested) {
noiseFloorCalibCheck(now);
}
if (_nf_refresh_requested && !_rx_ps_enabled && _nf_calib_deadline == 0
if (_nf_refresh_requested
&& (!_rx_ps_enabled || _rx_ps_continuous_fallback)
&& _nf_calib_deadline == 0
&& state == STATE_RX) {
// Measure the awake-time bound from actual continuous RX, not from begin()
// or a request made while the radio is idle, transmitting, or starting up.
@@ -467,7 +471,9 @@ void RadioLibWrapper::loop() {
return;
}
if (_nf_refresh_requested && !_rx_ps_enabled && _nf_calib_deadline != 0
if (_nf_refresh_requested
&& (!_rx_ps_enabled || _rx_ps_continuous_fallback)
&& _nf_calib_deadline != 0
&& (long)(now - _nf_calib_deadline) >= 0) {
// A continuously busy channel can reject every candidate sample. Do not
// keep the MCU awake indefinitely; retain the previous floor and let the
@@ -567,6 +573,8 @@ bool RadioLibWrapper::setRxPowerSaving(bool enabled, uint32_t rx_us, uint32_t sl
_rx_ps_enabled = enabled;
_rx_ps_rx_us = rx_us;
_rx_ps_sleep_us = sleep_us;
_rx_ps_continuous_fallback = enabled
&& rxPowerSavingUsesContinuousFallback(rx_us, sleep_us);
endReconfigure(resume_rx);
return true;
}
@@ -607,7 +615,7 @@ int RadioLibWrapper::recvRaw(uint8_t* bytes, int sz) {
#endif
}
if (len > 0 && _rx_ps_enabled) {
if (len > 0 && _rx_ps_enabled && !_rx_ps_continuous_fallback) {
// Dispatcher still needs the just-cached RSSI/SNR. Keep the receiver in
// ordinary continuous mode until it has parsed and scored this packet,
// then onReceiveProcessed() restores the duty cycle.
@@ -644,7 +652,7 @@ void RadioLibWrapper::finishReceiveProcessing() {
}
_rx_hold_continuous = false;
if (!_rx_ps_enabled || _nf_calib_active) return;
if (!_rx_ps_enabled || _rx_ps_continuous_fallback || _nf_calib_active) return;
state = STATE_IDLE;
startRecv();
+9 -2
View File
@@ -4,6 +4,7 @@
#include <RadioLib.h>
#include "CadTiming.h"
#include "RadioAirtime.h"
#include "RXPowerSaving.h"
// Fallback RX powersaving timings, only used until setRxPowerSaving() is called
// (begin() always applies the persisted values). The authoritative defaults live
@@ -40,6 +41,7 @@ protected:
uint8_t _preamble_sf;
bool _rx_ps_enabled;
bool _rx_ps_armed; // radio is currently in RX duty-cycle mode
bool _rx_ps_continuous_fallback; // requested RXPS is receiving continuously for this tuple
bool _rx_hold_continuous; // keep plain RX active until Dispatcher consumes cached metadata
uint32_t _rx_ps_rx_us;
uint32_t _rx_ps_sleep_us;
@@ -119,7 +121,7 @@ public:
RadioLibWrapper(PhysicalLayer& radio, mesh::MainBoard& board)
: _radio(&radio), _board(&board), _noise_floor_valid(false), _nf_refresh_requested(true),
_preamble_sf(0), _rx_ps_enabled(false), _rx_ps_armed(false),
_rx_hold_continuous(false),
_rx_ps_continuous_fallback(false), _rx_hold_continuous(false),
_rx_ps_rx_us(RX_PS_FALLBACK_RX_US), _rx_ps_sleep_us(RX_PS_FALLBACK_SLEEP_US),
_wd_last_busy(false), _wd_stage(0), _wd_strikes(0), _startrx_fails(0), _wd_last_transition(0),
_wd_stuck_thresh(0), _wd_observe_until(0), _wd_observe_ms(0),
@@ -142,6 +144,9 @@ public:
void onSendFinished() override;
bool isInRecvMode() const override;
bool setRxPowerSaving(bool enabled, uint32_t rx_us, uint32_t sleep_us) override;
bool isRxPowerSavingContinuousFallback() const override {
return _rx_ps_enabled && _rx_ps_continuous_fallback;
}
virtual bool supportsRxPowerSavingRfRxDisable() const { return false; }
virtual bool setRxPowerSavingRfRxDisabled(bool) { return false; }
virtual bool isRxPowerSavingRfRxDisabled() const { return false; }
@@ -196,7 +201,9 @@ public:
// true while a noise-floor batch needs prompt loop service; the app's
// hasPendingWork() keeps the MCU awake only for this short sample burst.
bool isCalibratingNoiseFloor() const {
return _nf_calib_active || (_nf_refresh_requested && !_rx_ps_enabled);
return _nf_calib_active
|| (_nf_refresh_requested
&& (!_rx_ps_enabled || _rx_ps_continuous_fallback));
}
void resetStats() { n_recv = n_sent = n_recv_errors = 0; }
@@ -924,11 +924,16 @@ bool EnvironmentSensorManager::gpsIsAwake(uint8_t ioPin) {
}
#endif
// RAK3401 shares WB_IO2 with the radio PA supply. Probing a GPS must never
// pulse that rail low or turn the pin back into an input.
#ifndef RAK_3401
// set initial waking state
pinMode(ioPin, OUTPUT);
digitalWrite(ioPin, LOW);
delay(500);
digitalWrite(ioPin, HIGH);
#endif
// give the receiver time to become responsive
delay(500);
// Try to init RAK12500 on I2C
@@ -961,7 +966,9 @@ bool EnvironmentSensorManager::gpsIsAwake(uint8_t ioPin) {
return true;
}
#ifndef RAK_3401
pinMode(ioPin, INPUT);
#endif
MESH_DEBUG_PRINTLN("GPS did not init with this IO pin... try the next");
return false;
}
+1
View File
@@ -63,6 +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_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 |
@@ -0,0 +1,187 @@
#include <gtest/gtest.h>
#include <helpers/radiolib/RXPowerSaving.h>
TEST(RxPowerSaving, BalancedNormalProfileKeepsDerivedDutyCycle) {
uint32_t rx_us = 0;
uint32_t sleep_us = 0;
ASSERT_TRUE(calcRxPowerSavingLevel(5, 7, 62.5f, 16, &rx_us, &sleep_us));
EXPECT_EQ(20936U, rx_us);
EXPECT_EQ(13425U, sleep_us);
EXPECT_FALSE(rxPowerSavingUsesContinuousFallback(rx_us, sleep_us));
}
TEST(RxPowerSaving, Sf5Bw500FallsBackToContinuousReceive) {
uint32_t rx_us = 0;
uint32_t sleep_us = 0;
ASSERT_TRUE(recalcRxPowerSavingFromLevel(5, 5, 500.0f, 16,
&rx_us, &sleep_us));
EXPECT_EQ(RX_POWERSAVING_CONTINUOUS_FALLBACK_US, rx_us);
EXPECT_EQ(RX_POWERSAVING_CONTINUOUS_FALLBACK_US, sleep_us);
EXPECT_TRUE(rxPowerSavingUsesContinuousFallback(rx_us, sleep_us));
}
TEST(RxPowerSaving, ReturningToSlowerProfileResumesDutyCycle) {
uint32_t rx_us = 0;
uint32_t sleep_us = 0;
ASSERT_TRUE(recalcRxPowerSavingFromLevel(5, 5, 500.0f, 16,
&rx_us, &sleep_us));
ASSERT_TRUE(rxPowerSavingUsesContinuousFallback(rx_us, sleep_us));
ASSERT_TRUE(recalcRxPowerSavingFromLevel(5, 9, 500.0f, 16,
&rx_us, &sleep_us));
EXPECT_EQ(10468U, rx_us);
EXPECT_EQ(6712U, sleep_us);
EXPECT_FALSE(rxPowerSavingUsesContinuousFallback(rx_us, sleep_us));
}
TEST(RxPowerSaving, TcxoThresholdSeparatesFastestUsableProfiles) {
constexpr uint32_t sx1262_tcxo_transition_us = 6000;
uint32_t rx_us = 0;
uint32_t sleep_us = 0;
ASSERT_TRUE(calcRxPowerSavingLevel(6, 7, 500.0f, 32,
&rx_us, &sleep_us));
EXPECT_FALSE(canStartRxPowerSavingDutyCycle(
rx_us, sleep_us, sx1262_tcxo_transition_us));
ASSERT_TRUE(calcRxPowerSavingLevel(7, 7, 500.0f, 32,
&rx_us, &sleep_us));
EXPECT_TRUE(canStartRxPowerSavingDutyCycle(
rx_us, sleep_us, sx1262_tcxo_transition_us));
}
TEST(RxPowerSaving, RequestedLevelIsRaisedOnlyToRequiredMinimum) {
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, 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_TRUE(canStartRxPowerSavingDutyCycle(
rx_us, sleep_us, sx1262_tcxo_transition_us));
ASSERT_TRUE(calcRxPowerSavingLevelAtOrAbove(
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_TRUE(canStartRxPowerSavingDutyCycle(
rx_us, sleep_us, sx1262_tcxo_transition_us));
}
TEST(RxPowerSaving, Sf5Bw500StillFallsBackWithPreamble32) {
constexpr uint32_t sx1262_tcxo_transition_us = 6000;
uint32_t rx_us = 0;
uint32_t sleep_us = 0;
uint8_t effective_level = 99;
uint8_t effective_preamble = 99;
ASSERT_TRUE(calcRxPowerSavingLevelAtOrAbove(
1, 5, 500.0f, 16, sx1262_tcxo_transition_us,
&rx_us, &sleep_us, &effective_level, &effective_preamble));
EXPECT_EQ(0U, effective_level);
EXPECT_EQ(0U, effective_preamble);
EXPECT_TRUE(rxPowerSavingUsesContinuousFallback(rx_us, sleep_us));
}
TEST(RxPowerSaving, Sf5Bw250StillFallsBackWithPreamble32OnTcxoRadio) {
constexpr uint32_t sx1262_tcxo_transition_us = 6000;
uint32_t rx_us = 0;
uint32_t sleep_us = 0;
uint8_t effective_level = 99;
uint8_t effective_preamble = 99;
// Even the maximum level only provides a 3.616 ms sleep window. RadioLib
// must reserve 6 ms for the SX1262 TCXO and sleep/wake transitions.
ASSERT_TRUE(calcRxPowerSavingLevel(10, 5, 250.0f, 32,
&rx_us, &sleep_us));
EXPECT_EQ(1024U, rx_us);
EXPECT_EQ(3616U, sleep_us);
EXPECT_FALSE(canStartRxPowerSavingDutyCycle(
rx_us, sleep_us, sx1262_tcxo_transition_us));
ASSERT_TRUE(calcRxPowerSavingLevelAtOrAbove(
1, 5, 250.0f, 16, sx1262_tcxo_transition_us,
&rx_us, &sleep_us, &effective_level, &effective_preamble));
EXPECT_EQ(0U, effective_level);
EXPECT_EQ(0U, effective_preamble);
EXPECT_TRUE(rxPowerSavingUsesContinuousFallback(rx_us, sleep_us));
}
TEST(RxPowerSaving, Sf5Bw125UsesPreamble32AtEffectiveLevel7) {
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, 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(2731U, rx_us);
EXPECT_EQ(6101U, sleep_us);
EXPECT_TRUE(canStartRxPowerSavingDutyCycle(
rx_us, sleep_us, sx1262_tcxo_transition_us));
}
TEST(RxPowerSaving, Sf6Bw250UsesPreamble32AtEffectiveLevel7) {
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, 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(2731U, rx_us);
EXPECT_EQ(6101U, sleep_us);
}
TEST(RxPowerSaving, Sf5Bw62Point5UsesPreamble16AtEffectiveLevel10) {
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, 62.5f, 16, sx1262_tcxo_transition_us,
&rx_us, &sleep_us, &effective_level, &effective_preamble));
EXPECT_EQ(10U, effective_level);
EXPECT_EQ(16U, effective_preamble);
EXPECT_EQ(4096U, rx_us);
EXPECT_EQ(6272U, sleep_us);
}
TEST(RxPowerSaving, RejectsInvalidInputs) {
uint32_t rx_us = 123;
uint32_t sleep_us = 456;
EXPECT_FALSE(calcRxPowerSavingLevel(0, 7, 500.0f, 32,
&rx_us, &sleep_us));
EXPECT_FALSE(calcRxPowerSavingLevel(5, 4, 500.0f, 32,
&rx_us, &sleep_us));
EXPECT_FALSE(calcRxPowerSavingLevel(5, 7, 0.0f, 32,
&rx_us, &sleep_us));
EXPECT_FALSE(calcRxPowerSavingLevel(5, 7, 500.0f, 24,
&rx_us, &sleep_us));
}
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
+2
View File
@@ -93,6 +93,7 @@ build_flags = ${WioTrackerL1.build_flags}
-D MAX_GROUP_CHANNELS=40
-D DISPLAY_CLASS=SH1106Display
-D UI_HAS_JOYSTICK=1
-D UI_NO_HIBERNATE
-D OFFLINE_QUEUE_SIZE=256
-D PIN_BUZZER=12
-D QSPIFLASH=1
@@ -125,6 +126,7 @@ build_flags = ${WioTrackerL1.build_flags}
-D OFFLINE_QUEUE_SIZE=256
-D DISPLAY_CLASS=SH1106Display
-D UI_HAS_JOYSTICK=1
-D UI_NO_HIBERNATE
-D PIN_BUZZER=12
-D QSPIFLASH=1
-D ADVERT_NAME='"@@MAC"'