mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-08-28 13:54:39 +00:00
Fix LR2021 side detectors and W12 shutdown
This commit is contained in:
@@ -4,6 +4,9 @@
|
||||
#include <helpers/CLICommandUtils.h>
|
||||
#include <helpers/ClockSyncUtils.h>
|
||||
#include <helpers/FloodFilterPolicy.h>
|
||||
#if defined(USE_LR2021)
|
||||
#include <helpers/radiolib/LR2021SideDetectorConfig.h>
|
||||
#endif
|
||||
#include <helpers/radiolib/RXPowerSaving.h>
|
||||
#include <helpers/RxReservePacketManager.h>
|
||||
#ifdef WITH_WEBCONFIG
|
||||
@@ -3418,7 +3421,30 @@ bool MyMesh::applyRadioParams(float freq, float bw, uint8_t sf, uint8_t cr) {
|
||||
}
|
||||
|
||||
bool MyMesh::applySavedRadioParams() {
|
||||
return applyRadioParams(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
|
||||
#if defined(USE_LR2021)
|
||||
uint8_t extra_sf_count = 0;
|
||||
const bool extra_sf_valid = mesh::lr2021::storedSideDetectorCount(
|
||||
_prefs.extra_sf, extra_sf_count)
|
||||
&& mesh::lr2021::validateSideDetectorSFs(
|
||||
_prefs.extra_sf, extra_sf_count, _prefs.sf, _prefs.bw);
|
||||
if (!extra_sf_valid) {
|
||||
// A permanent radio-profile change can make the old detector list invalid.
|
||||
// Clear the live/cache state before applying the new primary modulation so
|
||||
// the radio is not trapped retrying an impossible combination forever.
|
||||
if (!radio_driver.configSideDetectors(nullptr, 0, _prefs.bw)) return false;
|
||||
memset(_prefs.extra_sf, 0, sizeof(_prefs.extra_sf));
|
||||
extra_sf_count = 0;
|
||||
savePrefs();
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!applyRadioParams(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr)) return false;
|
||||
|
||||
#if defined(USE_LR2021)
|
||||
return radio_driver.configSideDetectors(_prefs.extra_sf, extra_sf_count, _prefs.bw);
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
void MyMesh::queueSavedRadioApply() {
|
||||
|
||||
+31
-15
@@ -1,6 +1,7 @@
|
||||
#include <Arduino.h>
|
||||
#include "CommonCLI.h"
|
||||
#include "CLICommandUtils.h"
|
||||
#include "radiolib/LR2021SideDetectorConfig.h"
|
||||
#include "TxtDataHelpers.h"
|
||||
#include "AdvertDataHelpers.h"
|
||||
#include "AlertReporter.h" // for alertReporterBannedChannelMatch()
|
||||
@@ -872,6 +873,7 @@ void CommonCLI::loadPrefs(FILESYSTEM* fs) {
|
||||
// The hardware main-loop watchdog is opt-out. Older preference files do not
|
||||
// contain its appended byte, so they safely inherit the enabled default.
|
||||
_prefs->system_watchdog_enabled = 1;
|
||||
memset(_prefs->extra_sf, 0, sizeof(_prefs->extra_sf));
|
||||
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
bool node_prefs_needs_migration = false;
|
||||
@@ -1068,6 +1070,7 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
|
||||
// Build-profile defaults - overwritten below when the saved field is present.
|
||||
_prefs->radio_fem_rxgain = 1;
|
||||
_prefs->cad_enabled = DEFAULT_CAD_ENABLED;
|
||||
memset(_prefs->extra_sf, 0, sizeof(_prefs->extra_sf));
|
||||
_prefs->rx_powersaving_enabled = 0;
|
||||
_prefs->rx_ps_rx_us = RX_POWERSAVING_DEFAULT_RX_US;
|
||||
_prefs->rx_ps_sleep_us = RX_POWERSAVING_DEFAULT_SLEEP_US;
|
||||
@@ -1347,6 +1350,12 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
|
||||
file.read((uint8_t *)&_prefs->system_watchdog_enabled,
|
||||
sizeof(_prefs->system_watchdog_enabled));
|
||||
}
|
||||
if (file.available() >= (int)sizeof(_prefs->extra_sf)) {
|
||||
file.read((uint8_t *)_prefs->extra_sf, sizeof(_prefs->extra_sf));
|
||||
} else if (file.available() > 0) {
|
||||
// Never accept a torn append as a partial detector list.
|
||||
_com_prefs_needs_upgrade = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Sanitize non-finite values before constrain(), whose comparisons leave
|
||||
@@ -1383,6 +1392,13 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
|
||||
_prefs->bw = isValidLoRaBandwidth(_prefs->bw) ? _prefs->bw : defaultLoRaBandwidth();
|
||||
_prefs->sf = constrain(_prefs->sf, 5, 12);
|
||||
_prefs->cr = constrain(_prefs->cr, 5, 8);
|
||||
uint8_t extra_sf_count = 0;
|
||||
if (!mesh::lr2021::storedSideDetectorCount(_prefs->extra_sf, extra_sf_count)
|
||||
|| !mesh::lr2021::validateSideDetectorSFs(
|
||||
_prefs->extra_sf, extra_sf_count, _prefs->sf, _prefs->bw)) {
|
||||
memset(_prefs->extra_sf, 0, sizeof(_prefs->extra_sf));
|
||||
_com_prefs_needs_upgrade = true;
|
||||
}
|
||||
_prefs->tx_power_dbm = constrain(_prefs->tx_power_dbm, -9, 30);
|
||||
_prefs->multi_acks = constrain(_prefs->multi_acks, 0, 1);
|
||||
_prefs->adc_multiplier = constrain(_prefs->adc_multiplier, 0.0f, 10.0f);
|
||||
@@ -1608,6 +1624,7 @@ static bool writeCommonPrefsImage(Writer& writer, NodePrefs* prefs) {
|
||||
WRITE_COMMON_PREFS(&prefs->flood_retry_group_max_path); // 853
|
||||
WRITE_COMMON_PREFS(&prefs->rx_watchdog_enabled); // 854
|
||||
WRITE_COMMON_PREFS(&prefs->system_watchdog_enabled); // 855
|
||||
WRITE_COMMON_PREFS(&prefs->extra_sf); // 856
|
||||
|
||||
#undef WRITE_COMMON_PREFS_BYTES
|
||||
#undef WRITE_COMMON_PREFS
|
||||
@@ -1753,7 +1770,8 @@ void CommonCLI::savePrefs(FILESYSTEM* fs, bool save_mqtt) {
|
||||
sizeof(_prefs->rx_watchdog_enabled)); // 854
|
||||
file.write((uint8_t *)&_prefs->system_watchdog_enabled,
|
||||
sizeof(_prefs->system_watchdog_enabled)); // 855
|
||||
// next: 856
|
||||
file.write((uint8_t *)_prefs->extra_sf, sizeof(_prefs->extra_sf)); // 856
|
||||
// next: 860
|
||||
|
||||
#if defined(NRF52_PLATFORM)
|
||||
if (!file.commit()) {
|
||||
@@ -4327,22 +4345,20 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep
|
||||
sprintf(reply, "OK - reboot.interval set to %d", _prefs->reboot_interval);
|
||||
}
|
||||
#if defined(USE_LR2021)
|
||||
} else if (memcmp(config, "extra.sf ", 9) == 0) {
|
||||
strcpy(tmp, &config[9]);
|
||||
const char *parts[4];
|
||||
uint8_t sideDetSFs[4];
|
||||
int num = mesh::Utils::parseTextParts(tmp, parts, 4);
|
||||
if (num > 3) {
|
||||
sprintf(reply, "Invalid extra SF config");
|
||||
} else if (strcmp(config, "extra.sf") == 0 || memcmp(config, "extra.sf ", 9) == 0) {
|
||||
uint8_t sideDetSFs[mesh::lr2021::STORED_SIDE_DETECTOR_BYTES] = {};
|
||||
uint8_t num = 0;
|
||||
const char* value = config[8] == '\0' ? "" : &config[9];
|
||||
if (strcmp(value, "none") == 0 || strcmp(value, "off") == 0) value = "";
|
||||
if (!mesh::lr2021::parseSideDetectorSFList(value, sideDetSFs, num)
|
||||
|| !mesh::lr2021::validateSideDetectorSFs(
|
||||
sideDetSFs, num, _prefs->sf, _prefs->bw)) {
|
||||
strcpy(reply, "Invalid extra SF config");
|
||||
} else {
|
||||
for (int i = 0; i < num; i++) {
|
||||
sideDetSFs[i] = atoi(parts[i]);
|
||||
}
|
||||
sideDetSFs[num] = 0;
|
||||
if (_callbacks->configSideDetectors(sideDetSFs, num, _prefs->bw)) {
|
||||
for (int i = 0; i <= num; i++) _prefs->extra_sf[i] = sideDetSFs[i];
|
||||
memcpy(_prefs->extra_sf, sideDetSFs, sizeof(_prefs->extra_sf));
|
||||
savePrefs();
|
||||
strcpy(reply, "OK - extra SFs set");
|
||||
strcpy(reply, num == 0 ? "OK - extra SFs cleared" : "OK - extra SFs set");
|
||||
} else {
|
||||
strcpy(reply, "Invalid extra SF config");
|
||||
}
|
||||
@@ -4770,7 +4786,7 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep
|
||||
} else {
|
||||
sprintf(reply, "> %d", (uint8_t)_prefs->reboot_interval);
|
||||
}
|
||||
} else if (memcmp(config, "extra.sf", 8) == 0) {
|
||||
} else if (strcmp(config, "extra.sf") == 0) {
|
||||
char* tmp = reply;
|
||||
for (int i = 0; i < 3 && _prefs->extra_sf[i] != 0; i++) {
|
||||
tmp += sprintf(tmp, "%s%d", (i == 0) ? "" : ",", _prefs->extra_sf[i]);
|
||||
|
||||
@@ -163,8 +163,8 @@ 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)
|
||||
// LR2021 side-detector SFs are runtime-only in upstream/dev. Keep the field
|
||||
// here without shifting the established /com_prefs binary layout.
|
||||
// LR2021 side-detector SFs are appended at /com_prefs offset 856. Their
|
||||
// in-memory placement here does not shift the established binary layout.
|
||||
uint8_t extra_sf[4] = {};
|
||||
uint8_t retry_preset;
|
||||
uint8_t direct_retry_attempts;
|
||||
|
||||
@@ -5,6 +5,14 @@
|
||||
|
||||
class CustomLR2021 : public LR2021 {
|
||||
bool _rx_boosted = false;
|
||||
uint32_t _rf_switch_pins[Module::RFSWITCH_MAX_PINS] = {};
|
||||
const Module::RfSwitchMode_t* _rf_switch_table = nullptr;
|
||||
|
||||
void restoreRfSwitchTable() {
|
||||
if (_rf_switch_table != nullptr) {
|
||||
LR2021::setRfSwitchTable(_rf_switch_pins, _rf_switch_table);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
CustomLR2021(Module *mod) : LR2021(mod) { irqDioNum = LR2021_IRQ_DIO; }
|
||||
@@ -58,9 +66,33 @@ class CustomLR2021 : public LR2021 {
|
||||
setRxBoostedGainMode(LR2021_RX_BOOSTED_GAIN);
|
||||
#endif
|
||||
|
||||
// LR2021::begin() performs a hardware reset. Preserve the board-specific
|
||||
// DIO/FEM routing across watchdog and SPI-timeout recovery resets.
|
||||
restoreRfSwitchTable();
|
||||
|
||||
return true; // success
|
||||
}
|
||||
|
||||
void setRfSwitchTable(const uint32_t (&pins)[Module::RFSWITCH_MAX_PINS],
|
||||
const Module::RfSwitchMode_t table[]) {
|
||||
for (size_t i = 0; i < Module::RFSWITCH_MAX_PINS; i++) {
|
||||
_rf_switch_pins[i] = pins[i];
|
||||
}
|
||||
_rf_switch_table = table;
|
||||
LR2021::setRfSwitchTable(pins, table);
|
||||
}
|
||||
|
||||
int16_t setSideDetector(const LR2021LoRaSideDetector_t* cfg, size_t numDetectors) {
|
||||
if (numDetectors > 0 && cfg == nullptr) return RADIOLIB_ERR_INVALID_SIDE_DETECT;
|
||||
if (numDetectors == 0) {
|
||||
// RadioLib 7.7.1 returns success without issuing this command for a
|
||||
// zero-length list. The LR2021 command encodes detector count in its
|
||||
// payload length, so the empty command is the actual disable operation.
|
||||
return setLoRaSideDetConfig(nullptr, 0);
|
||||
}
|
||||
return LR2021::setSideDetector(cfg, numDetectors);
|
||||
}
|
||||
|
||||
float getFreqMHz() const { return freqMHz; }
|
||||
|
||||
int16_t setRxBoostedGainMode(uint8_t level) {
|
||||
@@ -70,6 +102,7 @@ class CustomLR2021 : public LR2021 {
|
||||
}
|
||||
|
||||
bool getRxBoostedGainMode() const { return _rx_boosted; }
|
||||
float getBandwidthKhz() const { return bandwidthKhz; }
|
||||
|
||||
bool isChipBusy() {
|
||||
uint32_t busy = this->mod->getGpio();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "CustomLR2021.h"
|
||||
#include "LR2021SideDetectorConfig.h"
|
||||
#include "RadioLibWrappers.h"
|
||||
|
||||
#ifndef USE_LR2021
|
||||
@@ -24,7 +25,7 @@ protected:
|
||||
&& ((CustomLR2021 *)_radio)->setBandwidth(bw) == RADIOLIB_ERR_NONE
|
||||
&& ((CustomLR2021 *)_radio)->setCodingRate(cr) == RADIOLIB_ERR_NONE
|
||||
&& updatePreamble(sf)
|
||||
&& applySideDetectorConfig();
|
||||
&& applySideDetectorConfig(sf, bw);
|
||||
}
|
||||
|
||||
public:
|
||||
@@ -33,37 +34,36 @@ public:
|
||||
}
|
||||
|
||||
bool configSideDetectors(const uint8_t* sideDetSFs, uint8_t num, float bw) override {
|
||||
if (num > 3 || (num > 0 && sideDetSFs == nullptr)) return false;
|
||||
|
||||
LR2021LoRaSideDetector_t tmp[3];
|
||||
uint8_t sf = getSpreadingFactor();
|
||||
|
||||
if (sf >= 10 && num > 1) { return false; } // only 1 side detector allowed when primary SF >= 10
|
||||
for (int i = 0; i < num; i++) {
|
||||
if (sideDetSFs[i] > 12 || sideDetSFs[i] < 5) { return false; } // must be valid SF
|
||||
if (sideDetSFs[i] <= sf) { return false; } // must be greater than the primary SF
|
||||
if (sideDetSFs[i] > sf + 4) { return false; } // span must not be > 4
|
||||
|
||||
tmp[i].sf = sideDetSFs[i];
|
||||
float tSym = calcTsym(tmp[i].sf, bw);
|
||||
if (tSym >= 16.0f) {
|
||||
tmp[i].ldro = true;
|
||||
} else {
|
||||
tmp[i].ldro = false;
|
||||
}
|
||||
tmp[i].invertIQ = false;
|
||||
tmp[i].syncWord = RADIOLIB_LR2021_LORA_SYNC_WORD_PRIVATE;
|
||||
const uint8_t primary_sf = getSpreadingFactor();
|
||||
float active_bw = _params_valid ? _cur_bw : ((CustomLR2021 *)_radio)->getBandwidthKhz();
|
||||
if (active_bw <= 0.0f) active_bw = bw;
|
||||
if (!mesh::lr2021::validateSideDetectorSFs(sideDetSFs, num, primary_sf, active_bw)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
LR2021LoRaSideDetector_t tmp[mesh::lr2021::MAX_SIDE_DETECTORS] = {};
|
||||
buildSideDetectorConfig(sideDetSFs, num, active_bw, tmp);
|
||||
|
||||
uint8_t resume_rx = beginReconfigure();
|
||||
if (resume_rx > 1) return false;
|
||||
|
||||
int16_t status = ((CustomLR2021 *)_radio)->setSideDetector(tmp, num);
|
||||
int16_t status = ((CustomLR2021 *)_radio)->setSideDetector(num > 0 ? tmp : nullptr, num);
|
||||
MESH_DEBUG_PRINTLN("setSideDetector() returned %d", status);
|
||||
|
||||
if (status == RADIOLIB_ERR_NONE) {
|
||||
for (int i = 0; i < num; i++) { _sideDet[i] = tmp[i]; }
|
||||
for (uint8_t i = 0; i < mesh::lr2021::MAX_SIDE_DETECTORS; i++) {
|
||||
_sideDetSFs[i] = i < num ? sideDetSFs[i] : 0;
|
||||
}
|
||||
_numSideDet = num;
|
||||
} else {
|
||||
// Setting side detectors writes the detector bytes and sync words in two
|
||||
// separate commands. If the second command fails, restore the cached
|
||||
// configuration so a rejected CLI change cannot leave partial hardware state.
|
||||
bool restored = applySideDetectorConfig(primary_sf, active_bw);
|
||||
if (!restored) restored = restoreAfterDeepInit();
|
||||
if (!restored) {
|
||||
MESH_DEBUG_PRINTLN("LR2021: failed to restore side detectors after config error");
|
||||
}
|
||||
}
|
||||
|
||||
endReconfigure(resume_rx);
|
||||
@@ -71,13 +71,81 @@ public:
|
||||
}
|
||||
|
||||
protected:
|
||||
bool applySideDetectorConfig() {
|
||||
return ((CustomLR2021 *)_radio)->setSideDetector(_sideDet, _numSideDet)
|
||||
== RADIOLIB_ERR_NONE;
|
||||
static void buildSideDetectorConfig(const uint8_t* sideDetSFs, size_t num, float bw,
|
||||
LR2021LoRaSideDetector_t* config) {
|
||||
for (size_t i = 0; i < num; i++) {
|
||||
config[i].sf = sideDetSFs[i];
|
||||
config[i].ldro = mesh::lr2021::sideDetectorLDRO(sideDetSFs[i], bw);
|
||||
config[i].invertIQ = false;
|
||||
config[i].syncWord = RADIOLIB_LR2021_LORA_SYNC_WORD_PRIVATE;
|
||||
}
|
||||
}
|
||||
|
||||
float calcTsym(uint8_t sf, float bw) {
|
||||
return (float)(uint32_t(1) << sf) / bw;
|
||||
bool applySideDetectorConfig(uint8_t primary_sf, float bw) {
|
||||
if (!mesh::lr2021::validateSideDetectorSFs(_sideDetSFs, _numSideDet, primary_sf, bw)) {
|
||||
return false;
|
||||
}
|
||||
LR2021LoRaSideDetector_t config[mesh::lr2021::MAX_SIDE_DETECTORS] = {};
|
||||
buildSideDetectorConfig(_sideDetSFs, _numSideDet, bw, config);
|
||||
return ((CustomLR2021 *)_radio)->setSideDetector(_numSideDet > 0 ? config : nullptr,
|
||||
_numSideDet) == RADIOLIB_ERR_NONE;
|
||||
}
|
||||
|
||||
int16_t performChannelScan() override {
|
||||
if (_numSideDet == 0) return RadioLibWrapper::performChannelScan();
|
||||
|
||||
CustomLR2021* radio = (CustomLR2021 *)_radio;
|
||||
const uint8_t rx_primary_sf = getSpreadingFactor();
|
||||
float bw = _params_valid ? _cur_bw : radio->getBandwidthKhz();
|
||||
if (bw <= 0.0f) bw = static_cast<float>(LORA_BW);
|
||||
|
||||
int16_t status = radio->standby();
|
||||
if (status != RADIOLIB_ERR_NONE) return status;
|
||||
|
||||
// RadioLib's LR2021 multi-SF CAD support is unfinished and CAD requires
|
||||
// the inverse primary/side-SF ordering from RX. Scan every configured SF
|
||||
// through the supported primary detector instead, then restore RX atomically.
|
||||
status = radio->setSideDetector(nullptr, 0);
|
||||
if (status != RADIOLIB_ERR_NONE) {
|
||||
bool restored = applySideDetectorConfig(rx_primary_sf, bw);
|
||||
if (!restored) restored = restoreAfterDeepInit();
|
||||
return restored ? status : RADIOLIB_ERR_UNKNOWN;
|
||||
}
|
||||
|
||||
uint8_t scan_sfs[mesh::lr2021::STORED_SIDE_DETECTOR_BYTES] = {};
|
||||
scan_sfs[0] = rx_primary_sf;
|
||||
for (size_t i = 0; i < _numSideDet; i++) scan_sfs[i + 1] = _sideDetSFs[i];
|
||||
|
||||
int16_t scan_result = RADIOLIB_CHANNEL_FREE;
|
||||
for (size_t i = 0; i <= _numSideDet; i++) {
|
||||
if (i > 0) {
|
||||
status = radio->standby();
|
||||
if (status != RADIOLIB_ERR_NONE) {
|
||||
scan_result = status;
|
||||
break;
|
||||
}
|
||||
}
|
||||
status = radio->setSpreadingFactor(scan_sfs[i]);
|
||||
if (status != RADIOLIB_ERR_NONE) {
|
||||
scan_result = status;
|
||||
break;
|
||||
}
|
||||
scan_result = performChannelScanWithTimeout(
|
||||
mesh::calculateCadScanTimeoutMillis(scan_sfs[i], bw));
|
||||
// Each individual CAD can finish inside the helper's one-second service
|
||||
// cadence while the combined multi-SF pass still exceeds it.
|
||||
_board->serviceWatchdog();
|
||||
if (scan_result != RADIOLIB_CHANNEL_FREE) break;
|
||||
}
|
||||
|
||||
bool restored = radio->standby() == RADIOLIB_ERR_NONE
|
||||
&& radio->setSpreadingFactor(rx_primary_sf) == RADIOLIB_ERR_NONE
|
||||
&& applySideDetectorConfig(rx_primary_sf, bw);
|
||||
if (!restored && !restoreAfterDeepInit()) {
|
||||
MESH_DEBUG_PRINTLN("LR2021: failed to restore RX side detectors after CAD");
|
||||
return RADIOLIB_ERR_UNKNOWN;
|
||||
}
|
||||
return scan_result;
|
||||
}
|
||||
|
||||
public:
|
||||
@@ -126,7 +194,7 @@ protected:
|
||||
== RADIOLIB_ERR_NONE;
|
||||
}
|
||||
|
||||
LR2021LoRaSideDetector_t _sideDet[3];
|
||||
uint8_t _sideDetSFs[mesh::lr2021::MAX_SIDE_DETECTORS] = {};
|
||||
size_t _numSideDet = 0;
|
||||
|
||||
};
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
namespace mesh {
|
||||
namespace lr2021 {
|
||||
|
||||
static constexpr uint8_t MAX_SIDE_DETECTORS = 3;
|
||||
static constexpr uint8_t STORED_SIDE_DETECTOR_BYTES = MAX_SIDE_DETECTORS + 1;
|
||||
|
||||
// Parse the CLI's comma-separated SF list without copying it into CommonCLI's
|
||||
// shared scratch buffer. An empty string deliberately means "disable".
|
||||
inline bool parseSideDetectorSFList(const char* text,
|
||||
uint8_t (&side_sfs)[STORED_SIDE_DETECTOR_BYTES],
|
||||
uint8_t& num) {
|
||||
memset(side_sfs, 0, sizeof(side_sfs));
|
||||
num = 0;
|
||||
if (text == nullptr) return false;
|
||||
if (*text == '\0') return true;
|
||||
|
||||
const char* cursor = text;
|
||||
while (*cursor != '\0') {
|
||||
if (num >= MAX_SIDE_DETECTORS) return false;
|
||||
|
||||
uint16_t value = 0;
|
||||
uint8_t digits = 0;
|
||||
while (*cursor >= '0' && *cursor <= '9') {
|
||||
value = static_cast<uint16_t>(value * 10U + static_cast<uint8_t>(*cursor - '0'));
|
||||
if (value > 12U) return false;
|
||||
cursor++;
|
||||
digits++;
|
||||
}
|
||||
if (digits == 0 || value < 5U) return false;
|
||||
side_sfs[num++] = static_cast<uint8_t>(value);
|
||||
|
||||
if (*cursor == '\0') break;
|
||||
if (*cursor != ',') return false;
|
||||
cursor++;
|
||||
if (*cursor == '\0') return false; // reject a trailing comma
|
||||
}
|
||||
|
||||
side_sfs[num] = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool storedSideDetectorCount(
|
||||
const uint8_t (&stored)[STORED_SIDE_DETECTOR_BYTES], uint8_t& num) {
|
||||
for (uint8_t i = 0; i < STORED_SIDE_DETECTOR_BYTES; i++) {
|
||||
if (stored[i] == 0) {
|
||||
num = i;
|
||||
return i <= MAX_SIDE_DETECTORS;
|
||||
}
|
||||
}
|
||||
num = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool validateSideDetectorSFs(const uint8_t* side_sfs, uint8_t num,
|
||||
uint8_t primary_sf, float bandwidth_khz) {
|
||||
if (num > MAX_SIDE_DETECTORS || (num > 0 && side_sfs == nullptr)) return false;
|
||||
if (num == 0) return true;
|
||||
if (primary_sf < 5 || primary_sf > 12 || bandwidth_khz <= 0.0f) return false;
|
||||
|
||||
// These limits mirror LR2021::setSideDetector(). A primary SF of 10 or
|
||||
// greater permits two side detectors, not one.
|
||||
if ((primary_sf >= 10 || bandwidth_khz > 500.0f) && num > 2) return false;
|
||||
|
||||
uint32_t detector_factor_sum = 0;
|
||||
for (uint8_t i = 0; i < num; i++) {
|
||||
const uint8_t sf = side_sfs[i];
|
||||
if (sf < 5 || sf > 12 || sf <= primary_sf || sf > primary_sf + 4) return false;
|
||||
for (uint8_t j = 0; j < i; j++) {
|
||||
if (side_sfs[j] == sf) return false;
|
||||
}
|
||||
detector_factor_sum += 10U + static_cast<uint32_t>(((sf - 5U) >> 1U) << 1U);
|
||||
}
|
||||
|
||||
return static_cast<float>(detector_factor_sum) * bandwidth_khz < 32000.0f;
|
||||
}
|
||||
|
||||
inline bool sideDetectorLDRO(uint8_t sf, float bandwidth_khz) {
|
||||
if (sf < 5 || sf > 12 || bandwidth_khz <= 0.0f) return false;
|
||||
return static_cast<float>(uint32_t(1) << sf) / bandwidth_khz >= 16.0f;
|
||||
}
|
||||
|
||||
} // namespace lr2021
|
||||
} // namespace mesh
|
||||
@@ -675,13 +675,16 @@ void RadioLibWrapper::onSendFinished() {
|
||||
}
|
||||
|
||||
int16_t RadioLibWrapper::performChannelScan() {
|
||||
return performChannelScanWithTimeout(cadScanTimeoutMillis());
|
||||
}
|
||||
|
||||
int16_t RadioLibWrapper::performChannelScanWithTimeout(unsigned long timeout_ms) {
|
||||
// RadioLib's blocking scanChannel() waits forever if the radio never raises
|
||||
// its CAD-done IRQ. Keep CAD bounded so a missing IRQ cannot starve the main
|
||||
// loop until the MCU watchdog resets the node.
|
||||
int16_t result = _radio->startChannelScan();
|
||||
if (result != RADIOLIB_ERR_NONE) return result;
|
||||
|
||||
const unsigned long timeout_ms = cadScanTimeoutMillis();
|
||||
const unsigned long started = millis();
|
||||
unsigned long last_watchdog_service = started;
|
||||
while (millis() - started < timeout_ms) {
|
||||
|
||||
@@ -88,6 +88,7 @@ protected:
|
||||
const float bw = _params_valid ? _cur_bw : static_cast<float>(LORA_BW);
|
||||
return mesh::calculateCadScanTimeoutMillis(sf, bw);
|
||||
}
|
||||
int16_t performChannelScanWithTimeout(unsigned long timeout_ms);
|
||||
virtual int startReceiveMode();
|
||||
virtual void stopReceiveDutyCycle();
|
||||
virtual bool isPacketReady();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <helpers/radiolib/CadTiming.h>
|
||||
#include <helpers/radiolib/LR2021SideDetectorConfig.h>
|
||||
|
||||
TEST(CadTiming, UsesShortDeadlineForCascadeProfile) {
|
||||
EXPECT_EQ(mesh::calculateCadScanTimeoutMillis(7, 62.5f), 100UL);
|
||||
@@ -17,6 +18,64 @@ TEST(CadTiming, BoundsInvalidAndExtremeInputs) {
|
||||
EXPECT_EQ(mesh::calculateCadScanTimeoutMillis(12, 1.0f), 3500UL);
|
||||
}
|
||||
|
||||
TEST(LR2021SideDetectors, ParsesStrictBoundedListsAndDisable) {
|
||||
uint8_t side_sfs[mesh::lr2021::STORED_SIDE_DETECTOR_BYTES] = {99, 99, 99, 99};
|
||||
uint8_t num = 99;
|
||||
|
||||
EXPECT_TRUE(mesh::lr2021::parseSideDetectorSFList("", side_sfs, num));
|
||||
EXPECT_EQ(0, num);
|
||||
EXPECT_EQ(0, side_sfs[0]);
|
||||
EXPECT_TRUE(mesh::lr2021::validateSideDetectorSFs(nullptr, 0, 0, 0.0f));
|
||||
EXPECT_FALSE(mesh::lr2021::validateSideDetectorSFs(nullptr, 1, 8, 62.5f));
|
||||
|
||||
EXPECT_TRUE(mesh::lr2021::parseSideDetectorSFList("9,10,12", side_sfs, num));
|
||||
EXPECT_EQ(3, num);
|
||||
EXPECT_EQ(9, side_sfs[0]);
|
||||
EXPECT_EQ(10, side_sfs[1]);
|
||||
EXPECT_EQ(12, side_sfs[2]);
|
||||
EXPECT_EQ(0, side_sfs[3]);
|
||||
|
||||
EXPECT_FALSE(mesh::lr2021::parseSideDetectorSFList("9,10,11,12", side_sfs, num));
|
||||
EXPECT_FALSE(mesh::lr2021::parseSideDetectorSFList("9,", side_sfs, num));
|
||||
EXPECT_FALSE(mesh::lr2021::parseSideDetectorSFList("9junk", side_sfs, num));
|
||||
EXPECT_FALSE(mesh::lr2021::parseSideDetectorSFList(
|
||||
"999999999999999999999999999999999999999999999999999999", side_sfs, num));
|
||||
}
|
||||
|
||||
TEST(LR2021SideDetectors, AcceptsTwoDetectorsAtPrimarySf10) {
|
||||
const uint8_t valid[] = {11, 12};
|
||||
EXPECT_TRUE(mesh::lr2021::validateSideDetectorSFs(valid, 2, 10, 62.5f));
|
||||
|
||||
const uint8_t too_many[] = {10, 11, 12};
|
||||
EXPECT_FALSE(mesh::lr2021::validateSideDetectorSFs(too_many, 3, 9, 812.5f));
|
||||
}
|
||||
|
||||
TEST(LR2021SideDetectors, RejectsInvalidRxRelationships) {
|
||||
const uint8_t duplicate[] = {9, 9};
|
||||
EXPECT_FALSE(mesh::lr2021::validateSideDetectorSFs(duplicate, 2, 8, 62.5f));
|
||||
|
||||
const uint8_t below_primary[] = {8};
|
||||
EXPECT_FALSE(mesh::lr2021::validateSideDetectorSFs(below_primary, 1, 8, 62.5f));
|
||||
|
||||
const uint8_t excessive_span[] = {12};
|
||||
EXPECT_FALSE(mesh::lr2021::validateSideDetectorSFs(excessive_span, 1, 7, 62.5f));
|
||||
}
|
||||
|
||||
TEST(LR2021SideDetectors, RecomputesLdroWhenBandwidthChanges) {
|
||||
EXPECT_TRUE(mesh::lr2021::sideDetectorLDRO(10, 62.5f));
|
||||
EXPECT_FALSE(mesh::lr2021::sideDetectorLDRO(10, 125.0f));
|
||||
}
|
||||
|
||||
TEST(LR2021SideDetectors, RequiresStoredTerminator) {
|
||||
uint8_t count = 0;
|
||||
const uint8_t valid[] = {9, 10, 11, 0};
|
||||
EXPECT_TRUE(mesh::lr2021::storedSideDetectorCount(valid, count));
|
||||
EXPECT_EQ(3, count);
|
||||
|
||||
const uint8_t torn[] = {9, 10, 11, 12};
|
||||
EXPECT_FALSE(mesh::lr2021::storedSideDetectorCount(torn, count));
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
void MeshnologyW12Board::begin() {
|
||||
ESP32Board::begin();
|
||||
|
||||
rtc_gpio_hold_dis((gpio_num_t)P_LORA_LF_PA_POWER);
|
||||
rtc_gpio_hold_dis((gpio_num_t)P_LORA_HF_PA_POWER);
|
||||
pinMode(PIN_ADC_CTRL, OUTPUT);
|
||||
digitalWrite(PIN_ADC_CTRL, LOW); // Disable battery sense until required
|
||||
|
||||
@@ -59,7 +61,14 @@ void MeshnologyW12Board::begin() {
|
||||
}
|
||||
|
||||
void MeshnologyW12Board::powerOff() {
|
||||
enterDeepSleep(0);
|
||||
// A shutdown must not retain the packet-wake deep-sleep behavior above.
|
||||
// Disable both external FEM rails, then use the base shutdown path so the
|
||||
// display, GPS, radio, serial buffers, and stale wake sources are handled.
|
||||
digitalWrite(P_LORA_LF_PA_POWER, LOW);
|
||||
digitalWrite(P_LORA_HF_PA_POWER, LOW);
|
||||
rtc_gpio_hold_en((gpio_num_t)P_LORA_LF_PA_POWER);
|
||||
rtc_gpio_hold_en((gpio_num_t)P_LORA_HF_PA_POWER);
|
||||
ESP32Board::powerOff();
|
||||
}
|
||||
|
||||
uint16_t MeshnologyW12Board::getBattMilliVolts() {
|
||||
|
||||
Reference in New Issue
Block a user