mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-10-22 12:42:27 +00:00
Added RX duty-cycle power saving support
This commit is contained in:
1 parent
46c729b2e9
commit
e97f0fecc6
15 files changed
+419
-14
No files matched your search
@@ -267,6 +267,50 @@ bool MyMesh::getCADEnabled() const {
|
||||
return true; // hardware CAD before TX (no CLI toggle on companion; enabled by default)
|
||||
}
|
||||
|
||||
#ifdef RXPS_FIXED_ENABLED
|
||||
static uint32_t ceilPositiveFloat(float value) {
|
||||
uint32_t rounded = (uint32_t)value;
|
||||
return value > (float)rounded ? rounded + 1 : rounded;
|
||||
}
|
||||
|
||||
static bool calcFixedRxPowerSaving(uint8_t sf, float bw, uint32_t* rx_us, uint32_t* sleep_us) {
|
||||
if (RXPS_FIXED_LEVEL < 1 || RXPS_FIXED_LEVEL > 10 || sf < 5 || sf > 12 ||
|
||||
bw <= 0.0f || (RXPS_FIXED_PREAMBLE != 16 && RXPS_FIXED_PREAMBLE != 32)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const float symbol_us = (1000.0f * (float)(1UL << sf)) / bw;
|
||||
const float amount = (float)(RXPS_FIXED_LEVEL - 1) / 9.0f;
|
||||
const float rx_start_symbols = RXPS_FIXED_PREAMBLE == 16 ? 12.0f : 16.0f;
|
||||
const float sleep_start_symbols = RXPS_FIXED_PREAMBLE == 16 ? 2.0f : 15.0f;
|
||||
const float rx_edge_symbols = 8.0f;
|
||||
const float sleep_edge_symbols = (float)RXPS_FIXED_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);
|
||||
|
||||
*rx_us = ceilPositiveFloat(rx_symbols * symbol_us);
|
||||
*sleep_us = (uint32_t)(sleep_symbols * symbol_us);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void applyFixedRxPowerSaving(uint8_t sf, float bw) {
|
||||
uint32_t rx_us, sleep_us;
|
||||
if (!calcFixedRxPowerSaving(sf, bw, &rx_us, &sleep_us)) {
|
||||
MESH_DEBUG_PRINTLN("RX Power Saving fixed profile invalid");
|
||||
return;
|
||||
}
|
||||
|
||||
bool ok = radio_driver.setRxPowerSaving(true, rx_us, sleep_us);
|
||||
MESH_DEBUG_PRINTLN("RX Power Saving fixed level %d p%d: %s (%lu/%lu us)",
|
||||
RXPS_FIXED_LEVEL,
|
||||
RXPS_FIXED_PREAMBLE,
|
||||
ok ? "Enabled" : "Unsupported",
|
||||
(unsigned long)rx_us,
|
||||
(unsigned long)sleep_us);
|
||||
}
|
||||
#endif
|
||||
|
||||
int MyMesh::calcRxDelay(float score, uint32_t air_time) const {
|
||||
if (_prefs.rx_delay_base <= 0.0f) return 0;
|
||||
return (int)((pow(_prefs.rx_delay_base, 0.85f - score) - 1.0) * air_time);
|
||||
@@ -972,6 +1016,9 @@ void MyMesh::begin(bool has_display) {
|
||||
radio_driver.setTxPower(_prefs.tx_power_dbm);
|
||||
radio_driver.setRxBoostedGainMode(_prefs.rx_boosted_gain);
|
||||
board.setLoRaFemLnaEnabled(_prefs.radio_fem_rxgain);
|
||||
#ifdef RXPS_FIXED_ENABLED
|
||||
applyFixedRxPowerSaving(_prefs.sf, _prefs.bw);
|
||||
#endif
|
||||
MESH_DEBUG_PRINTLN("RX Boosted Gain Mode: %s",
|
||||
radio_driver.getRxBoostedGainMode() ? "Enabled" : "Disabled");
|
||||
}
|
||||
@@ -1397,6 +1444,9 @@ void MyMesh::handleCmdFrame(size_t len) {
|
||||
savePrefs();
|
||||
|
||||
radio_driver.setParams(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
|
||||
#ifdef RXPS_FIXED_ENABLED
|
||||
applyFixedRxPowerSaving(_prefs.sf, _prefs.bw);
|
||||
#endif
|
||||
MESH_DEBUG_PRINTLN("OK: CMD_SET_RADIO_PARAMS: f=%d, bw=%d, sf=%d, cr=%d", freq, bw, (uint32_t)sf,
|
||||
(uint32_t)cr);
|
||||
|
||||
|
||||
@@ -896,6 +896,8 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
|
||||
_prefs.flood_max_advert = 8;
|
||||
_prefs.interference_threshold = 0; // disabled
|
||||
_prefs.cad_enabled = 0; // hardware CAD before TX (off by default; 'set cad on')
|
||||
_prefs.rx_ps_rx_us = RX_POWERSAVING_DEFAULT_RX_US;
|
||||
_prefs.rx_ps_sleep_us = RX_POWERSAVING_DEFAULT_SLEEP_US;
|
||||
|
||||
// bridge defaults
|
||||
_prefs.bridge_enabled = 1; // enabled
|
||||
@@ -970,6 +972,7 @@ void MyMesh::begin(FILESYSTEM *fs) {
|
||||
MESH_DEBUG_PRINTLN("RX Boosted Gain Mode: %s",
|
||||
radio_driver.getRxBoostedGainMode() ? "Enabled" : "Disabled");
|
||||
board.setLoRaFemLnaEnabled(_prefs.radio_fem_rxgain);
|
||||
setRxPowerSaving(_prefs.rx_powersaving_enabled, _prefs.rx_ps_rx_us, _prefs.rx_ps_sleep_us);
|
||||
|
||||
updateAdvertTimer();
|
||||
updateFloodAdvertTimer();
|
||||
@@ -1064,6 +1067,16 @@ void MyMesh::setTxPower(int8_t power_dbm) {
|
||||
radio_driver.setTxPower(power_dbm);
|
||||
}
|
||||
|
||||
bool MyMesh::setRxPowerSaving(bool enable, uint32_t rx_us, uint32_t sleep_us) {
|
||||
bool ok = radio_driver.setRxPowerSaving(enable, rx_us, sleep_us);
|
||||
MESH_DEBUG_PRINTLN("RX Power Saving: %s (%lu/%lu us)%s",
|
||||
enable ? "Enabled" : "Disabled",
|
||||
(unsigned long)rx_us,
|
||||
(unsigned long)sleep_us,
|
||||
ok ? "" : " unsupported");
|
||||
return ok;
|
||||
}
|
||||
|
||||
#if defined(USE_SX1262) || defined(USE_SX1268)
|
||||
void MyMesh::setRxBoostedGain(bool enable) {
|
||||
radio_driver.setRxBoostedGainMode(enable);
|
||||
|
||||
@@ -212,6 +212,7 @@ public:
|
||||
|
||||
void dumpLogFile() override;
|
||||
void setTxPower(int8_t power_dbm) override;
|
||||
bool setRxPowerSaving(bool enable, uint32_t rx_us, uint32_t sleep_us) override;
|
||||
void formatNeighborsReply(char *reply) override;
|
||||
void removeNeighbor(const uint8_t* pubkey, int key_len) override;
|
||||
void formatStatsReply(char *reply) override;
|
||||
|
||||
@@ -651,6 +651,8 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
|
||||
_prefs.flood_max_advert = 8;
|
||||
_prefs.interference_threshold = 0; // disabled
|
||||
_prefs.cad_enabled = 0; // hardware CAD before TX (off by default; 'set cad on')
|
||||
_prefs.rx_ps_rx_us = RX_POWERSAVING_DEFAULT_RX_US;
|
||||
_prefs.rx_ps_sleep_us = RX_POWERSAVING_DEFAULT_SLEEP_US;
|
||||
#ifdef ROOM_PASSWORD
|
||||
StrHelper::strncpy(_prefs.guest_password, ROOM_PASSWORD, sizeof(_prefs.guest_password));
|
||||
#endif
|
||||
@@ -702,6 +704,7 @@ void MyMesh::begin(FILESYSTEM *fs) {
|
||||
radio_driver.setParams(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
|
||||
radio_driver.setTxPower(_prefs.tx_power_dbm);
|
||||
board.setLoRaFemLnaEnabled(_prefs.radio_fem_rxgain);
|
||||
setRxPowerSaving(_prefs.rx_powersaving_enabled, _prefs.rx_ps_rx_us, _prefs.rx_ps_sleep_us);
|
||||
|
||||
updateAdvertTimer();
|
||||
updateFloodAdvertTimer();
|
||||
@@ -808,6 +811,10 @@ void MyMesh::setTxPower(int8_t power_dbm) {
|
||||
radio_driver.setTxPower(power_dbm);
|
||||
}
|
||||
|
||||
bool MyMesh::setRxPowerSaving(bool enable, uint32_t rx_us, uint32_t sleep_us) {
|
||||
return radio_driver.setRxPowerSaving(enable, rx_us, sleep_us);
|
||||
}
|
||||
|
||||
void MyMesh::saveIdentity(const mesh::LocalIdentity &new_id) {
|
||||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
||||
IdentityStore store(*_fs, "");
|
||||
|
||||
@@ -206,6 +206,7 @@ public:
|
||||
|
||||
void dumpLogFile() override;
|
||||
void setTxPower(int8_t power_dbm) override;
|
||||
bool setRxPowerSaving(bool enable, uint32_t rx_us, uint32_t sleep_us) override;
|
||||
|
||||
void formatNeighborsReply(char *reply) override {
|
||||
strcpy(reply, "not supported");
|
||||
|
||||
@@ -730,6 +730,8 @@ SensorMesh::SensorMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::Millise
|
||||
_prefs.flood_max = 64;
|
||||
_prefs.interference_threshold = 0; // disabled
|
||||
_prefs.cad_enabled = 0; // hardware CAD before TX (off by default; 'set cad on')
|
||||
_prefs.rx_ps_rx_us = RX_POWERSAVING_DEFAULT_RX_US;
|
||||
_prefs.rx_ps_sleep_us = RX_POWERSAVING_DEFAULT_SLEEP_US;
|
||||
|
||||
// GPS defaults
|
||||
_prefs.gps_enabled = 0;
|
||||
@@ -770,6 +772,7 @@ void SensorMesh::begin(FILESYSTEM* fs) {
|
||||
|
||||
radio_driver.setParams(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
|
||||
radio_driver.setTxPower(_prefs.tx_power_dbm);
|
||||
setRxPowerSaving(_prefs.rx_powersaving_enabled, _prefs.rx_ps_rx_us, _prefs.rx_ps_sleep_us);
|
||||
|
||||
updateAdvertTimer();
|
||||
updateFloodAdvertTimer();
|
||||
@@ -849,6 +852,10 @@ void SensorMesh::setTxPower(int8_t power_dbm) {
|
||||
radio_driver.setTxPower(power_dbm);
|
||||
}
|
||||
|
||||
bool SensorMesh::setRxPowerSaving(bool enable, uint32_t rx_us, uint32_t sleep_us) {
|
||||
return radio_driver.setRxPowerSaving(enable, rx_us, sleep_us);
|
||||
}
|
||||
|
||||
void SensorMesh::formatStatsReply(char *reply) {
|
||||
StatsFormatHelper::formatCoreStats(reply, board, *_ms, _err_flags, _mgr);
|
||||
}
|
||||
|
||||
@@ -68,6 +68,7 @@ public:
|
||||
void eraseLogFile() override { }
|
||||
void dumpLogFile() override { }
|
||||
void setTxPower(int8_t power_dbm) override;
|
||||
bool setRxPowerSaving(bool enable, uint32_t rx_us, uint32_t sleep_us) override;
|
||||
void formatNeighborsReply(char *reply) override {
|
||||
strcpy(reply, "not supported");
|
||||
}
|
||||
|
||||
@@ -71,6 +71,9 @@ public:
|
||||
|
||||
virtual bool isInRecvMode() const = 0;
|
||||
|
||||
virtual bool supportsRxPowerSaving() const { return false; }
|
||||
virtual bool setRxPowerSaving(bool enabled, uint32_t rx_us, uint32_t sleep_us) { return !enabled; }
|
||||
|
||||
/**
|
||||
* \returns true if the radio is currently mid-receive of a packet.
|
||||
*/
|
||||
|
||||
+195
-3
@@ -29,6 +29,80 @@ static bool isValidName(const char *n) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool isValidRxPowerSavingPeriod(uint32_t us) {
|
||||
return us >= RX_POWERSAVING_MIN_PERIOD_US && us <= RX_POWERSAVING_MAX_PERIOD_US;
|
||||
}
|
||||
|
||||
// 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.
|
||||
static uint16_t rxPowerSavingPreambleForSF(uint8_t sf) {
|
||||
return sf <= 8 ? 32 : 16;
|
||||
}
|
||||
|
||||
static bool isNumeric(const char* sp) {
|
||||
if (!sp || !*sp) return false;
|
||||
while (*sp) {
|
||||
if (*sp < '0' || *sp > '9') return false;
|
||||
sp++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static uint32_t ceilPositiveFloat(float value) {
|
||||
uint32_t rounded = (uint32_t)value;
|
||||
return value > (float)rounded ? rounded + 1 : rounded;
|
||||
}
|
||||
|
||||
static bool calcRxPowerSavingLevel(uint32_t level, uint8_t sf, float bw, uint32_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)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const float symbol_us = (1000.0f * (float)(1UL << sf)) / bw;
|
||||
const float amount = (float)(level - 1) / 9.0f;
|
||||
const float rx_start_symbols = preamble == 16 ? 12.0f : 16.0f;
|
||||
const float sleep_start_symbols = preamble == 16 ? 2.0f : 15.0f;
|
||||
const float rx_edge_symbols = 8.0f;
|
||||
const float sleep_edge_symbols = (float)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);
|
||||
|
||||
*rx_us = ceilPositiveFloat(rx_symbols * symbol_us);
|
||||
*sleep_us = (uint32_t)(sleep_symbols * symbol_us);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void ensureRxPowerSavingDefaults(NodePrefs* prefs) {
|
||||
if (!isValidRxPowerSavingPeriod(prefs->rx_ps_rx_us)) {
|
||||
prefs->rx_ps_rx_us = RX_POWERSAVING_DEFAULT_RX_US;
|
||||
}
|
||||
if (!isValidRxPowerSavingPeriod(prefs->rx_ps_sleep_us)) {
|
||||
prefs->rx_ps_sleep_us = RX_POWERSAVING_DEFAULT_SLEEP_US;
|
||||
}
|
||||
}
|
||||
|
||||
// Recomputes rx_ps_rx_us/rx_ps_sleep_us from the stored level and the current
|
||||
// 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.
|
||||
static bool recalcRxPowerSavingFromLevel(NodePrefs* prefs) {
|
||||
if (prefs->rx_ps_level < 1 || prefs->rx_ps_level > 10) return false; // manual: nothing to recompute
|
||||
uint32_t preamble = prefs->rx_ps_preamble ? prefs->rx_ps_preamble
|
||||
: rxPowerSavingPreambleForSF(prefs->sf);
|
||||
uint32_t rx_us, sleep_us;
|
||||
if (!calcRxPowerSavingLevel(prefs->rx_ps_level, prefs->sf, prefs->bw, preamble, &rx_us, &sleep_us)) {
|
||||
return false;
|
||||
}
|
||||
if (!isValidRxPowerSavingPeriod(rx_us) || !isValidRxPowerSavingPeriod(sleep_us)) {
|
||||
return false;
|
||||
}
|
||||
prefs->rx_ps_rx_us = rx_us;
|
||||
prefs->rx_ps_sleep_us = sleep_us;
|
||||
return true;
|
||||
}
|
||||
|
||||
void CommonCLI::loadPrefs(FILESYSTEM* fs) {
|
||||
if (fs->exists("/com_prefs")) {
|
||||
loadPrefsInt(fs, "/com_prefs"); // new filename
|
||||
@@ -96,7 +170,12 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
|
||||
file.read((uint8_t *)&_prefs->flood_max_advert, sizeof(_prefs->flood_max_advert)); // 292
|
||||
file.read((uint8_t *)&_prefs->radio_fem_rxgain, sizeof(_prefs->radio_fem_rxgain)); // 293
|
||||
file.read((uint8_t *)&_prefs->cad_enabled, sizeof(_prefs->cad_enabled)); // 294
|
||||
// next: 295
|
||||
file.read((uint8_t *)&_prefs->rx_powersaving_enabled, sizeof(_prefs->rx_powersaving_enabled)); // 295
|
||||
file.read((uint8_t *)&_prefs->rx_ps_rx_us, sizeof(_prefs->rx_ps_rx_us)); // 296
|
||||
file.read((uint8_t *)&_prefs->rx_ps_sleep_us, sizeof(_prefs->rx_ps_sleep_us)); // 300
|
||||
file.read((uint8_t *)&_prefs->rx_ps_level, sizeof(_prefs->rx_ps_level)); // 304
|
||||
file.read((uint8_t *)&_prefs->rx_ps_preamble, sizeof(_prefs->rx_ps_preamble)); // 305
|
||||
// next: 306
|
||||
|
||||
// sanitise bad pref values
|
||||
_prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0, 20.0f);
|
||||
@@ -129,6 +208,13 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
|
||||
_prefs->rx_boosted_gain = constrain(_prefs->rx_boosted_gain, 0, 1); // boolean
|
||||
_prefs->radio_fem_rxgain = constrain(_prefs->radio_fem_rxgain, 0, 1); // boolean
|
||||
_prefs->cad_enabled = constrain(_prefs->cad_enabled, 0, 1); // boolean
|
||||
_prefs->rx_powersaving_enabled = constrain(_prefs->rx_powersaving_enabled, 0, 1);
|
||||
_prefs->rx_ps_level = constrain(_prefs->rx_ps_level, 0, 10);
|
||||
if (_prefs->rx_ps_preamble != 16 && _prefs->rx_ps_preamble != 32) {
|
||||
_prefs->rx_ps_preamble = 0; // 0 = auto (derive from SF)
|
||||
}
|
||||
ensureRxPowerSavingDefaults(_prefs);
|
||||
recalcRxPowerSavingFromLevel(_prefs); // retune level-based timings to the loaded SF/BW
|
||||
|
||||
file.close();
|
||||
}
|
||||
@@ -195,7 +281,12 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) {
|
||||
file.write((uint8_t *)&_prefs->flood_max_advert, sizeof(_prefs->flood_max_advert)); // 292
|
||||
file.write((uint8_t *)&_prefs->radio_fem_rxgain, sizeof(_prefs->radio_fem_rxgain)); // 293
|
||||
file.write((uint8_t *)&_prefs->cad_enabled, sizeof(_prefs->cad_enabled)); // 294
|
||||
// next: 295
|
||||
file.write((uint8_t *)&_prefs->rx_powersaving_enabled, sizeof(_prefs->rx_powersaving_enabled)); // 295
|
||||
file.write((uint8_t *)&_prefs->rx_ps_rx_us, sizeof(_prefs->rx_ps_rx_us)); // 296
|
||||
file.write((uint8_t *)&_prefs->rx_ps_sleep_us, sizeof(_prefs->rx_ps_sleep_us)); // 300
|
||||
file.write((uint8_t *)&_prefs->rx_ps_level, sizeof(_prefs->rx_ps_level)); // 304
|
||||
file.write((uint8_t *)&_prefs->rx_ps_preamble, sizeof(_prefs->rx_ps_preamble)); // 305
|
||||
// next: 306
|
||||
|
||||
file.close();
|
||||
}
|
||||
@@ -635,6 +726,100 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep
|
||||
} else {
|
||||
strcpy(reply, "Error: state must be on or off");
|
||||
}
|
||||
} else if (memcmp(config, "radio.rxps ", 11) == 0) {
|
||||
const char* value = &config[11];
|
||||
uint8_t enable = _prefs->rx_powersaving_enabled;
|
||||
uint32_t rx_us = _prefs->rx_ps_rx_us;
|
||||
uint32_t sleep_us = _prefs->rx_ps_sleep_us;
|
||||
uint32_t level = 0;
|
||||
uint32_t preamble = rxPowerSavingPreambleForSF(_prefs->sf);
|
||||
bool level_requested = false;
|
||||
bool preamble_overridden = false;
|
||||
|
||||
ensureRxPowerSavingDefaults(_prefs);
|
||||
rx_us = _prefs->rx_ps_rx_us;
|
||||
sleep_us = _prefs->rx_ps_sleep_us;
|
||||
|
||||
if (strcmp(value, "off") == 0) {
|
||||
enable = 0;
|
||||
} else if (strcmp(value, "on") == 0 || strcmp(value, "conservative") == 0) {
|
||||
enable = 1;
|
||||
rx_us = RX_POWERSAVING_DEFAULT_RX_US;
|
||||
sleep_us = RX_POWERSAVING_DEFAULT_SLEEP_US;
|
||||
} else if (strcmp(value, "balanced") == 0) {
|
||||
enable = 1;
|
||||
rx_us = RX_POWERSAVING_DEFAULT_RX_US;
|
||||
sleep_us = RX_POWERSAVING_BALANCED_SLEEP_US;
|
||||
} else {
|
||||
StrHelper::strncpy(tmp, value, sizeof(tmp));
|
||||
const char *parts[4];
|
||||
int num = mesh::Utils::parseTextParts(tmp, parts, 4, ' ');
|
||||
if (num == 1 && isNumeric(parts[0])) {
|
||||
level = _atoi(parts[0]);
|
||||
level_requested = true;
|
||||
enable = 1;
|
||||
} else if (num == 2 && strcmp(parts[0], "level") == 0 && isNumeric(parts[1])) {
|
||||
level = _atoi(parts[1]);
|
||||
level_requested = true;
|
||||
enable = 1;
|
||||
} else if (num == 4 && strcmp(parts[0], "level") == 0 && isNumeric(parts[1]) &&
|
||||
strcmp(parts[2], "preamble") == 0 && isNumeric(parts[3])) {
|
||||
level = _atoi(parts[1]);
|
||||
preamble = _atoi(parts[3]);
|
||||
level_requested = true;
|
||||
preamble_overridden = true;
|
||||
enable = 1;
|
||||
} else if (num == 2 && isNumeric(parts[0]) && isNumeric(parts[1])) {
|
||||
rx_us = _atoi(parts[0]);
|
||||
sleep_us = _atoi(parts[1]);
|
||||
enable = 1;
|
||||
} else {
|
||||
strcpy(reply, "ERROR: use off|on|conservative|balanced|level <1-10>|<rx_us> <sleep_us>");
|
||||
return;
|
||||
}
|
||||
|
||||
if (level_requested && !calcRxPowerSavingLevel(level, _prefs->sf, _prefs->bw, preamble, &rx_us, &sleep_us)) {
|
||||
strcpy(reply, "ERROR: level range is 1-10; preamble is 16 or 32");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isValidRxPowerSavingPeriod(rx_us) || !isValidRxPowerSavingPeriod(sleep_us)) {
|
||||
sprintf(reply, "ERROR: range is %lu-%lu us",
|
||||
(unsigned long)RX_POWERSAVING_MIN_PERIOD_US,
|
||||
(unsigned long)RX_POWERSAVING_MAX_PERIOD_US);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_callbacks->setRxPowerSaving(enable, rx_us, sleep_us)) {
|
||||
strcpy(reply, "ERROR: RX powersaving unsupported");
|
||||
return;
|
||||
}
|
||||
|
||||
_prefs->rx_powersaving_enabled = enable;
|
||||
_prefs->rx_ps_rx_us = rx_us;
|
||||
_prefs->rx_ps_sleep_us = sleep_us;
|
||||
if (level_requested) {
|
||||
// Remember the intent so the timings can auto-retune when SF/BW change.
|
||||
_prefs->rx_ps_level = level;
|
||||
_prefs->rx_ps_preamble = preamble_overridden ? preamble : 0; // 0 = auto (derive from SF)
|
||||
} else if (strcmp(value, "off") != 0) {
|
||||
// on/conservative/balanced/manual timings are fixed, not level-derived.
|
||||
_prefs->rx_ps_level = 0;
|
||||
_prefs->rx_ps_preamble = 0;
|
||||
}
|
||||
savePrefs();
|
||||
if (level_requested) {
|
||||
sprintf(reply, "OK - level %lu,%s,%lu,%lu,preamble=%lu",
|
||||
(unsigned long)level,
|
||||
enable ? "on" : "off",
|
||||
(unsigned long)rx_us,
|
||||
(unsigned long)sleep_us,
|
||||
(unsigned long)preamble);
|
||||
} else {
|
||||
sprintf(reply, "OK - %s,%lu,%lu", enable ? "on" : "off",
|
||||
(unsigned long)rx_us, (unsigned long)sleep_us);
|
||||
}
|
||||
} else if (memcmp(config, "radio ", 6) == 0) {
|
||||
strcpy(tmp, &config[6]);
|
||||
const char *parts[4];
|
||||
@@ -648,8 +833,11 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep
|
||||
_prefs->cr = cr;
|
||||
_prefs->freq = freq;
|
||||
_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(_prefs);
|
||||
_callbacks->savePrefs();
|
||||
strcpy(reply, "OK - reboot to apply");
|
||||
strcpy(reply, rxps_retuned ? "OK - reboot to apply (rxps retuned)" : "OK - reboot to apply");
|
||||
} else {
|
||||
strcpy(reply, "Error, invalid radio params");
|
||||
}
|
||||
@@ -893,6 +1081,10 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep
|
||||
} else {
|
||||
sprintf(reply, "> %s", _board->isLoRaFemLnaEnabled() ? "on" : "off");
|
||||
}
|
||||
} else if (memcmp(config, "radio.rxps", 10) == 0) {
|
||||
ensureRxPowerSavingDefaults(_prefs);
|
||||
sprintf(reply, "> %s,%lu,%lu", _prefs->rx_powersaving_enabled ? "on" : "off",
|
||||
(unsigned long)_prefs->rx_ps_rx_us, (unsigned long)_prefs->rx_ps_sleep_us);
|
||||
} else if (memcmp(config, "radio", 5) == 0) {
|
||||
char freq[16], bw[16];
|
||||
strcpy(freq, StrHelper::ftoa(_prefs->freq));
|
||||
|
||||
@@ -19,6 +19,12 @@
|
||||
#define LOOP_DETECT_MODERATE 2
|
||||
#define LOOP_DETECT_STRICT 3
|
||||
|
||||
#define RX_POWERSAVING_DEFAULT_RX_US 65625UL
|
||||
#define RX_POWERSAVING_DEFAULT_SLEEP_US 60000UL
|
||||
#define RX_POWERSAVING_BALANCED_SLEEP_US 80000UL
|
||||
#define RX_POWERSAVING_MIN_PERIOD_US 1000UL
|
||||
#define RX_POWERSAVING_MAX_PERIOD_US 30000000UL
|
||||
|
||||
struct NodePrefs { // persisted to file
|
||||
float airtime_factor;
|
||||
char node_name[32];
|
||||
@@ -66,6 +72,11 @@ struct NodePrefs { // persisted to file
|
||||
uint8_t path_hash_mode; // which path mode to use when sending
|
||||
uint8_t loop_detect;
|
||||
uint8_t cad_enabled; // hardware Channel Activity Detection before TX (boolean)
|
||||
uint8_t rx_powersaving_enabled; // boolean
|
||||
uint32_t rx_ps_rx_us;
|
||||
uint32_t rx_ps_sleep_us;
|
||||
uint8_t rx_ps_level; // 0 = manual/explicit us timings; 1..10 = level-derived (auto-retunes on SF/BW change)
|
||||
uint8_t rx_ps_preamble; // 0 = auto (derive from SF); else 16 or 32 = explicit override for level calc
|
||||
};
|
||||
|
||||
class CommonCLICallbacks {
|
||||
@@ -115,6 +126,10 @@ public:
|
||||
virtual void setRxBoostedGain(bool enable) {
|
||||
// no op by default
|
||||
};
|
||||
|
||||
virtual bool setRxPowerSaving(bool enable, uint32_t rx_us, uint32_t sleep_us) {
|
||||
return !enable;
|
||||
};
|
||||
};
|
||||
|
||||
class CommonCLI {
|
||||
|
||||
@@ -24,6 +24,40 @@ class CustomLR1110 : public LR1110 {
|
||||
|
||||
float getFreqMHz() const { return freqMHz; }
|
||||
|
||||
int16_t startReceiveDutyCycle(uint32_t rxPeriod, uint32_t sleepPeriod,
|
||||
RadioLibIrqFlags_t irqFlags = RADIOLIB_IRQ_RX_DEFAULT_FLAGS,
|
||||
RadioLibIrqFlags_t irqMask = RADIOLIB_IRQ_RX_DEFAULT_MASK) {
|
||||
// RadioLib's LR11x0 duty-cycle path stages RX but does not call
|
||||
// launchMode(), where the software RF switch is normally set to RX.
|
||||
uint32_t transitionTime = this->tcxoDelay + 1000;
|
||||
sleepPeriod -= transitionTime;
|
||||
|
||||
uint32_t rxPeriodRaw = (rxPeriod * 32768UL) / 1000000UL;
|
||||
uint32_t sleepPeriodRaw = (sleepPeriod * 32768UL) / 1000000UL;
|
||||
|
||||
if ((rxPeriodRaw & 0xFF000000) || (rxPeriodRaw == 0)) {
|
||||
return RADIOLIB_ERR_INVALID_RX_PERIOD;
|
||||
}
|
||||
|
||||
if ((sleepPeriodRaw & 0xFF000000) || (sleepPeriodRaw == 0)) {
|
||||
return RADIOLIB_ERR_INVALID_SLEEP_PERIOD;
|
||||
}
|
||||
|
||||
RadioModeConfig_t cfg = {
|
||||
.receive = {
|
||||
.timeout = RADIOLIB_LR11X0_RX_TIMEOUT_INF,
|
||||
.irqFlags = irqFlags,
|
||||
.irqMask = irqMask,
|
||||
.len = 0,
|
||||
}
|
||||
};
|
||||
int16_t state = this->stageMode(RADIOLIB_RADIO_MODE_RX, &cfg);
|
||||
RADIOLIB_ASSERT(state);
|
||||
|
||||
this->mod->setRfSwitchState(Module::MODE_RX);
|
||||
return this->setRxDutyCycle(rxPeriodRaw, sleepPeriodRaw, RADIOLIB_LR11X0_RX_DUTY_CYCLE_MODE_RX);
|
||||
}
|
||||
|
||||
int16_t setRxBoostedGainMode(bool en) {
|
||||
_rx_boosted = en;
|
||||
return LR1110::setRxBoostedGainMode(en);
|
||||
@@ -38,4 +72,4 @@ class CustomLR1110 : public LR1110 {
|
||||
}
|
||||
|
||||
uint8_t getSpreadingFactor() const { return spreadingFactor; }
|
||||
};
|
||||
};
|
||||
@@ -31,6 +31,27 @@ public:
|
||||
_radio->setPreambleLength(preambleLengthForSF(getSpreadingFactor())); // overcomes weird issues with small and big pkts
|
||||
}
|
||||
|
||||
bool supportsRxPowerSaving() const override { return true; }
|
||||
|
||||
protected:
|
||||
int startReceiveMode() override {
|
||||
if (!_rx_ps_enabled) {
|
||||
return _radio->startReceive();
|
||||
}
|
||||
|
||||
const RadioLibIrqFlags_t irqFlags = RADIOLIB_IRQ_RX_DEFAULT_FLAGS;
|
||||
const RadioLibIrqFlags_t irqMask = RADIOLIB_IRQ_RX_DEFAULT_MASK;
|
||||
|
||||
int err = ((CustomLR1110 *)_radio)->startReceiveDutyCycle(_rx_ps_rx_us, _rx_ps_sleep_us, irqFlags, irqMask);
|
||||
if (err == RADIOLIB_ERR_NONE) {
|
||||
return err;
|
||||
}
|
||||
|
||||
MESH_DEBUG_PRINTLN("CustomLR1110Wrapper: error: startReceiveDutyCycle(%d), falling back to continuous RX", err);
|
||||
return _radio->startReceive();
|
||||
}
|
||||
|
||||
public:
|
||||
float getLastRSSI() const override { return ((CustomLR1110 *)_radio)->getRSSI(); }
|
||||
float getLastSNR() const override { return ((CustomLR1110 *)_radio)->getSNR(); }
|
||||
|
||||
|
||||
@@ -38,6 +38,31 @@ public:
|
||||
((CustomSX1262 *)_radio)->sleep(false);
|
||||
}
|
||||
|
||||
bool supportsRxPowerSaving() const override { return true; }
|
||||
|
||||
protected:
|
||||
int startReceiveMode() override {
|
||||
if (!_rx_ps_enabled) {
|
||||
return _radio->startReceive();
|
||||
}
|
||||
|
||||
const RadioLibIrqFlags_t irqFlags = RADIOLIB_IRQ_RX_DEFAULT_FLAGS;
|
||||
const RadioLibIrqFlags_t irqMask =
|
||||
(1UL << RADIOLIB_IRQ_RX_DONE) |
|
||||
(1UL << RADIOLIB_IRQ_TIMEOUT) |
|
||||
(1UL << RADIOLIB_IRQ_CRC_ERR) |
|
||||
(1UL << RADIOLIB_IRQ_HEADER_ERR);
|
||||
|
||||
int err = ((CustomSX1262 *)_radio)->startReceiveDutyCycle(_rx_ps_rx_us, _rx_ps_sleep_us, irqFlags, irqMask);
|
||||
if (err == RADIOLIB_ERR_NONE) {
|
||||
return err;
|
||||
}
|
||||
|
||||
MESH_DEBUG_PRINTLN("CustomSX1262Wrapper: error: startReceiveDutyCycle(%d), falling back to continuous RX", err);
|
||||
return _radio->startReceive();
|
||||
}
|
||||
|
||||
public:
|
||||
void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); }
|
||||
|
||||
void setRxBoostedGainMode(bool en) override {
|
||||
|
||||
@@ -85,7 +85,11 @@ void RadioLibWrapper::resetAGC() {
|
||||
}
|
||||
|
||||
void RadioLibWrapper::loop() {
|
||||
if (state == STATE_RX && _num_floor_samples < NUM_NOISE_FLOOR_SAMPLES) {
|
||||
// In RX duty-cycle (powersaving) mode the radio auto-sleeps between listen
|
||||
// windows, so getCurrentRSSI() would read a sleeping frontend and corrupt the
|
||||
// noise floor. Skip adaptive sampling; note this also degrades interference
|
||||
// detection (isChannelActive) while powersaving is enabled.
|
||||
if (!_rx_ps_enabled && state == STATE_RX && _num_floor_samples < NUM_NOISE_FLOOR_SAMPLES) {
|
||||
if (!isReceivingPacket()) {
|
||||
int rssi = getCurrentRSSI();
|
||||
if (rssi < _noise_floor + SAMPLING_THRESHOLD) { // only consider samples below current floor + sampling THRESHOLD
|
||||
@@ -105,18 +109,40 @@ void RadioLibWrapper::loop() {
|
||||
}
|
||||
|
||||
void RadioLibWrapper::startRecv() {
|
||||
int err = _radio->startReceive();
|
||||
int err = startReceiveMode();
|
||||
if (err == RADIOLIB_ERR_NONE) {
|
||||
state = STATE_RX;
|
||||
} else {
|
||||
MESH_DEBUG_PRINTLN("RadioLibWrapper: error: startReceive(%d)", err);
|
||||
MESH_DEBUG_PRINTLN("RadioLibWrapper: error: startReceiveMode(%d)", err);
|
||||
}
|
||||
}
|
||||
|
||||
int RadioLibWrapper::startReceiveMode() {
|
||||
return _radio->startReceive();
|
||||
}
|
||||
|
||||
bool RadioLibWrapper::isInRecvMode() const {
|
||||
return (state & ~STATE_INT_READY) == STATE_RX;
|
||||
}
|
||||
|
||||
bool RadioLibWrapper::setRxPowerSaving(bool enabled, uint32_t rx_us, uint32_t sleep_us) {
|
||||
if (enabled && !supportsRxPowerSaving()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
_rx_ps_enabled = enabled;
|
||||
_rx_ps_rx_us = rx_us;
|
||||
_rx_ps_sleep_us = sleep_us;
|
||||
// Force the next recvRaw() to arm the requested RX mode, but don't clobber a
|
||||
// completed-but-unread packet (STATE_INT_READY): recvRaw() will consume it and
|
||||
// then re-arm with the new mode. Also leave an in-flight TX alone. (Same
|
||||
// non-atomic guard style as resetAGC().)
|
||||
if ((state & STATE_INT_READY) == 0 && (state & ~STATE_INT_READY) != STATE_TX_WAIT) {
|
||||
state = STATE_IDLE;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int RadioLibWrapper::recvRaw(uint8_t* bytes, int sz) {
|
||||
int len = 0;
|
||||
if (state & STATE_INT_READY) {
|
||||
@@ -137,12 +163,7 @@ int RadioLibWrapper::recvRaw(uint8_t* bytes, int sz) {
|
||||
}
|
||||
|
||||
if (state != STATE_RX) {
|
||||
int err = _radio->startReceive();
|
||||
if (err == RADIOLIB_ERR_NONE) {
|
||||
state = STATE_RX;
|
||||
} else {
|
||||
MESH_DEBUG_PRINTLN("RadioLibWrapper: error: startReceive(%d)", err);
|
||||
}
|
||||
startRecv();
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,13 @@
|
||||
#include <Mesh.h>
|
||||
#include <RadioLib.h>
|
||||
|
||||
// Fallback RX powersaving timings, only used until setRxPowerSaving() is called
|
||||
// (begin() always applies the persisted values). The authoritative defaults live
|
||||
// in CommonCLI.h as RX_POWERSAVING_DEFAULT_RX_US / _SLEEP_US and are delivered
|
||||
// via NodePrefs; keep these mirrored.
|
||||
#define RX_PS_FALLBACK_RX_US 65625UL
|
||||
#define RX_PS_FALLBACK_SLEEP_US 60000UL
|
||||
|
||||
class RadioLibWrapper : public mesh::Radio {
|
||||
protected:
|
||||
PhysicalLayer* _radio;
|
||||
@@ -13,15 +20,21 @@ protected:
|
||||
uint16_t _num_floor_samples;
|
||||
int32_t _floor_sample_sum;
|
||||
uint8_t _preamble_sf;
|
||||
bool _rx_ps_enabled;
|
||||
uint32_t _rx_ps_rx_us;
|
||||
uint32_t _rx_ps_sleep_us;
|
||||
|
||||
void idle();
|
||||
void startRecv();
|
||||
virtual int startReceiveMode();
|
||||
float packetScoreInt(float snr, int sf, int packet_len);
|
||||
virtual bool isReceivingPacket() =0;
|
||||
virtual void doResetAGC();
|
||||
|
||||
public:
|
||||
RadioLibWrapper(PhysicalLayer& radio, mesh::MainBoard& board) : _radio(&radio), _board(&board), _preamble_sf(0) { n_recv = n_sent = 0; }
|
||||
RadioLibWrapper(PhysicalLayer& radio, mesh::MainBoard& board)
|
||||
: _radio(&radio), _board(&board), _preamble_sf(0), _rx_ps_enabled(false),
|
||||
_rx_ps_rx_us(RX_PS_FALLBACK_RX_US), _rx_ps_sleep_us(RX_PS_FALLBACK_SLEEP_US) { n_recv = n_sent = n_recv_errors = 0; }
|
||||
|
||||
void begin() override;
|
||||
virtual void powerOff() { _radio->sleep(); }
|
||||
@@ -31,6 +44,7 @@ public:
|
||||
bool isSendComplete() override;
|
||||
void onSendFinished() override;
|
||||
bool isInRecvMode() const override;
|
||||
bool setRxPowerSaving(bool enabled, uint32_t rx_us, uint32_t sleep_us) override;
|
||||
bool isChannelActive();
|
||||
|
||||
bool isReceiving() override {
|
||||
|
||||
Reference in new issue
Block a user