Harden fast RXPS timing and board behavior
This commit is contained in:
1 parent
1a43123d2c
commit
b984fdcb4c
21 files changed
+607
-30
No files matched your search
+23
-1
@@ -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.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ struct ClientInfo {
|
||||
};
|
||||
|
||||
#ifndef MAX_CLIENTS
|
||||
#define MAX_CLIENTS 20
|
||||
#define MAX_CLIENTS 32
|
||||
#endif
|
||||
|
||||
class ClientACL {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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"'
|
||||
|
||||
Reference in new issue
Block a user