Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/simple_repeater/MyMesh.h
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
void removeNeighbor(const uint8_t* pubkey, int key_len) override;
void formatStatsReply(char *reply) override;
void formatRadioStatsReply(char *reply) override;
mesh::Radio* getRadio() override { return _radio; }
void formatPacketStatsReply(char *reply) override;
void startRegionsLoad() override;
bool saveRegions() override;
Expand Down
1 change: 1 addition & 0 deletions examples/simple_room_server/MyMesh.h
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
}
void formatStatsReply(char *reply) override;
void formatRadioStatsReply(char *reply) override;
mesh::Radio* getRadio() override { return _radio; }
void formatPacketStatsReply(char *reply) override;
void startRegionsLoad() override;
bool saveRegions() override;
Expand Down
1 change: 1 addition & 0 deletions examples/simple_sensor/SensorMesh.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ class SensorMesh : public mesh::Mesh, public CommonCLICallbacks {
}
void formatStatsReply(char *reply) override;
void formatRadioStatsReply(char *reply) override;
mesh::Radio* getRadio() override { return _radio; }
void formatPacketStatsReply(char *reply) override;
mesh::LocalIdentity& getSelfId() override { return self_id; }
void saveIdentity(const mesh::LocalIdentity& new_id) override;
Expand Down
8 changes: 8 additions & 0 deletions src/Dispatcher.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,14 @@ class Radio {

virtual void setCADEnabled(bool enable) { }

/**
* \brief query CAD auto-calibration state.
* \param peak_offset current detPeak offset above the radio's default
* \param hits/count ambient CAD detections in the last evaluated probe window
* \returns false if this radio doesn't support CAD detPeak tuning.
*/
virtual bool getCADCalibState(uint8_t& peak_offset, uint8_t& hits, uint8_t& count) const { return false; }

virtual void resetAGC() { }

virtual bool isInRecvMode() const = 0;
Expand Down
2 changes: 1 addition & 1 deletion src/Mesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ uint8_t Mesh::getExtraAckTransmitCount() const {
}

uint32_t Mesh::getCADFailRetryDelay() const {
return _rng->nextInt(1, 4)*120;
return 120 + _rng->nextInt(0, 241); // continuous 120..360ms, so nearby nodes don't retry in lock-step
}

int Mesh::searchPeersByHash(const uint8_t* hash) {
Expand Down
8 changes: 7 additions & 1 deletion src/helpers/CommonCLI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -811,7 +811,13 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep
} else if (memcmp(config, "int.thresh", 10) == 0) {
sprintf(reply, "> %d", (uint32_t) _prefs->interference_threshold);
} else if (memcmp(config, "cad", 3) == 0) {
sprintf(reply, "> %s", _prefs->cad_enabled ? "on" : "off");
uint8_t peak_offset, hits, count;
mesh::Radio* radio = _callbacks->getRadio();
if (_prefs->cad_enabled && radio != NULL && radio->getCADCalibState(peak_offset, hits, count)) {
sprintf(reply, "> on (detPeak +%d, ambient %d/%d)", (int)peak_offset, (int)hits, (int)count);
} else {
sprintf(reply, "> %s", _prefs->cad_enabled ? "on" : "off");
}
} else if (memcmp(config, "agc.reset.interval", 18) == 0) {
sprintf(reply, "> %d", ((uint32_t) _prefs->agc_reset_interval) * 4);
} else if (memcmp(config, "multi.acks", 10) == 0) {
Expand Down
1 change: 1 addition & 0 deletions src/helpers/CommonCLI.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ class CommonCLICallbacks {
virtual void eraseLogFile() = 0;
virtual void dumpLogFile() = 0;
virtual void setTxPower(int8_t power_dbm) = 0;
virtual mesh::Radio* getRadio() { return NULL; }
virtual void formatNeighborsReply(char *reply) = 0;
virtual void removeNeighbor(const uint8_t* pubkey, int key_len) {
// no op by default
Expand Down
1 change: 1 addition & 0 deletions src/helpers/radiolib/CustomLLCC68Wrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class CustomLLCC68Wrapper : public RadioLibWrapper {
return packetScoreInt(snr, sf, packet_len);
}
uint8_t getSpreadingFactor() const override { return ((CustomLLCC68 *)_radio)->spreadingFactor; }
uint8_t getCADDetPeakBase() const override { return getSpreadingFactor() + 13; } // Semtech DS.SX1261-2 recommended detPeak

void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); }

Expand Down
6 changes: 6 additions & 0 deletions src/helpers/radiolib/CustomLR1110Wrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ class CustomLR1110Wrapper : public RadioLibWrapper {
float getLastSNR() const override { return ((CustomLR1110 *)_radio)->getSNR(); }

uint8_t getSpreadingFactor() const override { return ((CustomLR1110 *)_radio)->getSpreadingFactor(); }
uint8_t getCADDetPeakBase() const override {
// RadioLib LR11x0 default detPeak per SF5..12 (different scale than SX126x)
static const uint8_t detPeakValues[8] = { 48, 48, 50, 55, 55, 59, 61, 65 };
uint8_t sf = getSpreadingFactor();
return (sf >= 5 && sf <= 12) ? detPeakValues[sf - 5] : 0;
}

bool setRxBoostedGainMode(bool en) override {
return ((CustomLR1110 *)_radio)->setRxBoostedGainMode(en) == RADIOLIB_ERR_NONE;
Expand Down
1 change: 1 addition & 0 deletions src/helpers/radiolib/CustomSTM32WLxWrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class CustomSTM32WLxWrapper : public RadioLibWrapper {
return packetScoreInt(snr, sf, packet_len);
}
uint8_t getSpreadingFactor() const override { return ((CustomSTM32WLx *)_radio)->spreadingFactor; }
uint8_t getCADDetPeakBase() const override { return getSpreadingFactor() + 13; } // Semtech DS.SX1261-2 recommended detPeak

void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); }
};
1 change: 1 addition & 0 deletions src/helpers/radiolib/CustomSX1262Wrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class CustomSX1262Wrapper : public RadioLibWrapper {
return packetScoreInt(snr, sf, packet_len);
}
uint8_t getSpreadingFactor() const override { return ((CustomSX1262 *)_radio)->spreadingFactor; }
uint8_t getCADDetPeakBase() const override { return getSpreadingFactor() + 13; } // Semtech DS.SX1261-2 recommended detPeak
virtual void powerOff() override {
((CustomSX1262 *)_radio)->sleep(false);
}
Expand Down
1 change: 1 addition & 0 deletions src/helpers/radiolib/CustomSX1268Wrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class CustomSX1268Wrapper : public RadioLibWrapper {
return packetScoreInt(snr, sf, packet_len);
}
uint8_t getSpreadingFactor() const override { return ((CustomSX1268 *)_radio)->spreadingFactor; }
uint8_t getCADDetPeakBase() const override { return getSpreadingFactor() + 13; } // Semtech DS.SX1261-2 recommended detPeak

void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); }

Expand Down
65 changes: 65 additions & 0 deletions src/helpers/radiolib/RadioLibWrappers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@
#define NUM_NOISE_FLOOR_SAMPLES 64
#define SAMPLING_THRESHOLD 14

// ambient CAD auto-calibration: probe CAD periodically while idle, and raise/lower detPeak
// so ambient activity (sub-decode-threshold distant traffic, interference) stops tripping LBT
#define CAD_PROBE_INTERVAL 4000 // millis between ambient CAD probes
#define CAD_PROBE_WINDOW 32 // probes per evaluation window
#define CAD_PROBE_RAISE_HITS 10 // >= ~30% ambient detect rate -> desensitize
#define CAD_PROBE_LOWER_HITS 3 // <= ~10% ambient detect rate -> re-sensitize
#define CAD_PEAK_OFFSET_MAX 10

static volatile uint8_t state = STATE_IDLE;

// this function is called when a complete packet
Expand All @@ -37,6 +45,10 @@ void RadioLibWrapper::begin() {
_noise_floor = 0;
_threshold = 0;
_cad_enabled = false;
_cad_peak_offset = 0;
_cad_probe_count = _cad_probe_hits = 0;
_cad_last_count = _cad_last_hits = 0;
_last_cad_probe = 0;

// start average out some samples
_num_floor_samples = 0;
Expand Down Expand Up @@ -102,6 +114,44 @@ void RadioLibWrapper::loop() {

MESH_DEBUG_PRINTLN("RadioLibWrapper: noise_floor = %d", (int)_noise_floor);
}

if (_cad_enabled && state == STATE_RX && getCADDetPeakBase() > 0
&& millis() - _last_cad_probe >= CAD_PROBE_INTERVAL) {
_last_cad_probe = millis();
if (!isReceivingPacket()) {
int16_t result = performChannelScan();
// CAD-done DIO interrupt sets STATE_INT_READY via setFlag() ISR. Clear it
// before restarting RX so recvRaw() doesn't try to read a non-existent packet.
state = STATE_IDLE;
startRecv();

if (result == RADIOLIB_CHANNEL_FREE) {
_cad_probe_count++;
} else if (result == RADIOLIB_LORA_DETECTED || result == RADIOLIB_PREAMBLE_DETECTED) {
_cad_probe_count++;
_cad_probe_hits++;
}
// else: scan error, don't count the sample

if (_cad_probe_hits >= CAD_PROBE_RAISE_HITS) { // ambient rate too high, don't wait out the window
if (_cad_peak_offset < CAD_PEAK_OFFSET_MAX) {
_cad_peak_offset++;
MESH_DEBUG_PRINTLN("RadioLibWrapper: CAD ambient %d/%d, detPeak offset -> %d",
(int)_cad_probe_hits, (int)_cad_probe_count, (int)_cad_peak_offset);
}
_cad_last_hits = _cad_probe_hits; _cad_last_count = _cad_probe_count;
_cad_probe_count = _cad_probe_hits = 0;
} else if (_cad_probe_count >= CAD_PROBE_WINDOW) {
if (_cad_probe_hits <= CAD_PROBE_LOWER_HITS && _cad_peak_offset > 0) {
_cad_peak_offset--;
MESH_DEBUG_PRINTLN("RadioLibWrapper: CAD ambient %d/%d, detPeak offset -> %d",
(int)_cad_probe_hits, (int)_cad_probe_count, (int)_cad_peak_offset);
}
_cad_last_hits = _cad_probe_hits; _cad_last_count = _cad_probe_count;
_cad_probe_count = _cad_probe_hits = 0;
}
}
}
}

void RadioLibWrapper::startRecv() {
Expand Down Expand Up @@ -180,6 +230,21 @@ void RadioLibWrapper::onSendFinished() {
}

int16_t RadioLibWrapper::performChannelScan() {
uint8_t peak_base = getCADDetPeakBase();
if (_cad_peak_offset > 0 && peak_base > 0) {
ChannelScanConfig_t cfg = {
.cad = {
.symNum = 0xFF, // 0xFF -> radio-specific default (RADIOLIB_*_CAD_PARAM_DEFAULT)
.detPeak = (uint8_t)(peak_base + _cad_peak_offset),
.detMin = 0xFF,
.exitMode = 0xFF,
.timeout = 0,
.irqFlags = RADIOLIB_IRQ_CAD_DEFAULT_FLAGS,
.irqMask = RADIOLIB_IRQ_CAD_DEFAULT_MASK,
},
};
return _radio->scanChannel(cfg);
}
return _radio->scanChannel();
}

Expand Down
16 changes: 16 additions & 0 deletions src/helpers/radiolib/RadioLibWrappers.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ class RadioLibWrapper : public mesh::Radio {
uint16_t _num_floor_samples;
int32_t _floor_sample_sum;
uint8_t _preamble_sf;
uint8_t _cad_peak_offset;
uint8_t _cad_probe_count, _cad_probe_hits;
uint8_t _cad_last_count, _cad_last_hits;
unsigned long _last_cad_probe;

void idle();
void startRecv();
Expand Down Expand Up @@ -48,6 +52,18 @@ class RadioLibWrapper : public mesh::Radio {
static uint16_t preambleLengthForSF(uint8_t sf) { return sf <= 8 ? 32 : 16; }
void updatePreamble(uint8_t sf) { _preamble_sf = sf; _radio->setPreambleLength(preambleLengthForSF(sf)); }
virtual int16_t performChannelScan();
virtual uint8_t getCADDetPeakBase() const { return 0; } // 0 = CAD detPeak tuning not supported

bool getCADCalibState(uint8_t& peak_offset, uint8_t& hits, uint8_t& count) const override {
if (getCADDetPeakBase() == 0) return false;
peak_offset = _cad_peak_offset;
if (_cad_last_count > 0) {
hits = _cad_last_hits; count = _cad_last_count;
} else { // no probe window evaluated yet, report the one in progress
hits = _cad_probe_hits; count = _cad_probe_count;
}
return true;
}

int getNoiseFloor() const override { return _noise_floor; }
void triggerNoiseFloorCalibrate(int threshold) override;
Expand Down