Browse Source

Dynamic CAD sensitivity

Probe every 4 seconds to measure actual channel activity. If channel
seems busy we adjust CAD to be less sensitive. If channel seems empty
maximum CAD sensitivity.
pull/2916/head
Wessel Nieboer 3 weeks ago
parent
commit
4b02a86e9b
No known key found for this signature in database GPG Key ID: 929C8E45E33B5FD2
  1. 1
      examples/simple_repeater/MyMesh.h
  2. 1
      examples/simple_room_server/MyMesh.h
  3. 1
      examples/simple_sensor/SensorMesh.h
  4. 8
      src/Dispatcher.h
  5. 2
      src/Mesh.cpp
  6. 8
      src/helpers/CommonCLI.cpp
  7. 1
      src/helpers/CommonCLI.h
  8. 1
      src/helpers/radiolib/CustomLLCC68Wrapper.h
  9. 6
      src/helpers/radiolib/CustomLR1110Wrapper.h
  10. 1
      src/helpers/radiolib/CustomSTM32WLxWrapper.h
  11. 1
      src/helpers/radiolib/CustomSX1262Wrapper.h
  12. 1
      src/helpers/radiolib/CustomSX1268Wrapper.h
  13. 65
      src/helpers/radiolib/RadioLibWrappers.cpp
  14. 16
      src/helpers/radiolib/RadioLibWrappers.h

1
examples/simple_repeater/MyMesh.h

@ -216,6 +216,7 @@ public:
void removeNeighbor(const uint8_t* pubkey, int key_len) override; void removeNeighbor(const uint8_t* pubkey, int key_len) override;
void formatStatsReply(char *reply) override; void formatStatsReply(char *reply) override;
void formatRadioStatsReply(char *reply) override; void formatRadioStatsReply(char *reply) override;
mesh::Radio* getRadio() override { return _radio; }
void formatPacketStatsReply(char *reply) override; void formatPacketStatsReply(char *reply) override;
void startRegionsLoad() override; void startRegionsLoad() override;
bool saveRegions() override; bool saveRegions() override;

1
examples/simple_room_server/MyMesh.h

@ -212,6 +212,7 @@ public:
} }
void formatStatsReply(char *reply) override; void formatStatsReply(char *reply) override;
void formatRadioStatsReply(char *reply) override; void formatRadioStatsReply(char *reply) override;
mesh::Radio* getRadio() override { return _radio; }
void formatPacketStatsReply(char *reply) override; void formatPacketStatsReply(char *reply) override;
void startRegionsLoad() override; void startRegionsLoad() override;
bool saveRegions() override; bool saveRegions() override;

1
examples/simple_sensor/SensorMesh.h

@ -73,6 +73,7 @@ public:
} }
void formatStatsReply(char *reply) override; void formatStatsReply(char *reply) override;
void formatRadioStatsReply(char *reply) override; void formatRadioStatsReply(char *reply) override;
mesh::Radio* getRadio() override { return _radio; }
void formatPacketStatsReply(char *reply) override; void formatPacketStatsReply(char *reply) override;
mesh::LocalIdentity& getSelfId() override { return self_id; } mesh::LocalIdentity& getSelfId() override { return self_id; }
void saveIdentity(const mesh::LocalIdentity& new_id) override; void saveIdentity(const mesh::LocalIdentity& new_id) override;

8
src/Dispatcher.h

@ -67,6 +67,14 @@ public:
virtual void setCADEnabled(bool enable) { } 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 void resetAGC() { }
virtual bool isInRecvMode() const = 0; virtual bool isInRecvMode() const = 0;

2
src/Mesh.cpp

@ -27,7 +27,7 @@ uint8_t Mesh::getExtraAckTransmitCount() const {
} }
uint32_t Mesh::getCADFailRetryDelay() 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) { int Mesh::searchPeersByHash(const uint8_t* hash) {

8
src/helpers/CommonCLI.cpp

@ -811,7 +811,13 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep
} else if (memcmp(config, "int.thresh", 10) == 0) { } else if (memcmp(config, "int.thresh", 10) == 0) {
sprintf(reply, "> %d", (uint32_t) _prefs->interference_threshold); sprintf(reply, "> %d", (uint32_t) _prefs->interference_threshold);
} else if (memcmp(config, "cad", 3) == 0) { } 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) { } else if (memcmp(config, "agc.reset.interval", 18) == 0) {
sprintf(reply, "> %d", ((uint32_t) _prefs->agc_reset_interval) * 4); sprintf(reply, "> %d", ((uint32_t) _prefs->agc_reset_interval) * 4);
} else if (memcmp(config, "multi.acks", 10) == 0) { } else if (memcmp(config, "multi.acks", 10) == 0) {

1
src/helpers/CommonCLI.h

@ -81,6 +81,7 @@ public:
virtual void eraseLogFile() = 0; virtual void eraseLogFile() = 0;
virtual void dumpLogFile() = 0; virtual void dumpLogFile() = 0;
virtual void setTxPower(int8_t power_dbm) = 0; virtual void setTxPower(int8_t power_dbm) = 0;
virtual mesh::Radio* getRadio() { return NULL; }
virtual void formatNeighborsReply(char *reply) = 0; virtual void formatNeighborsReply(char *reply) = 0;
virtual void removeNeighbor(const uint8_t* pubkey, int key_len) { virtual void removeNeighbor(const uint8_t* pubkey, int key_len) {
// no op by default // no op by default

1
src/helpers/radiolib/CustomLLCC68Wrapper.h

@ -30,6 +30,7 @@ public:
return packetScoreInt(snr, sf, packet_len); return packetScoreInt(snr, sf, packet_len);
} }
uint8_t getSpreadingFactor() const override { return ((CustomLLCC68 *)_radio)->spreadingFactor; } 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); } void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); }

6
src/helpers/radiolib/CustomLR1110Wrapper.h

@ -43,6 +43,12 @@ public:
float getLastSNR() const override { return ((CustomLR1110 *)_radio)->getSNR(); } float getLastSNR() const override { return ((CustomLR1110 *)_radio)->getSNR(); }
uint8_t getSpreadingFactor() const override { return ((CustomLR1110 *)_radio)->getSpreadingFactor(); } 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 { bool setRxBoostedGainMode(bool en) override {
return ((CustomLR1110 *)_radio)->setRxBoostedGainMode(en) == RADIOLIB_ERR_NONE; return ((CustomLR1110 *)_radio)->setRxBoostedGainMode(en) == RADIOLIB_ERR_NONE;

1
src/helpers/radiolib/CustomSTM32WLxWrapper.h

@ -31,6 +31,7 @@ public:
return packetScoreInt(snr, sf, packet_len); return packetScoreInt(snr, sf, packet_len);
} }
uint8_t getSpreadingFactor() const override { return ((CustomSTM32WLx *)_radio)->spreadingFactor; } 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); } void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); }
}; };

1
src/helpers/radiolib/CustomSX1262Wrapper.h

@ -37,6 +37,7 @@ public:
return packetScoreInt(snr, sf, packet_len); return packetScoreInt(snr, sf, packet_len);
} }
uint8_t getSpreadingFactor() const override { return ((CustomSX1262 *)_radio)->spreadingFactor; } 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 { virtual void powerOff() override {
((CustomSX1262 *)_radio)->sleep(false); ((CustomSX1262 *)_radio)->sleep(false);
} }

1
src/helpers/radiolib/CustomSX1268Wrapper.h

@ -34,6 +34,7 @@ public:
return packetScoreInt(snr, sf, packet_len); return packetScoreInt(snr, sf, packet_len);
} }
uint8_t getSpreadingFactor() const override { return ((CustomSX1268 *)_radio)->spreadingFactor; } 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); } void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); }

65
src/helpers/radiolib/RadioLibWrappers.cpp

@ -11,6 +11,14 @@
#define NUM_NOISE_FLOOR_SAMPLES 64 #define NUM_NOISE_FLOOR_SAMPLES 64
#define SAMPLING_THRESHOLD 14 #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; static volatile uint8_t state = STATE_IDLE;
// this function is called when a complete packet // this function is called when a complete packet
@ -37,6 +45,10 @@ void RadioLibWrapper::begin() {
_noise_floor = 0; _noise_floor = 0;
_threshold = 0; _threshold = 0;
_cad_enabled = false; _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 // start average out some samples
_num_floor_samples = 0; _num_floor_samples = 0;
@ -102,6 +114,44 @@ void RadioLibWrapper::loop() {
MESH_DEBUG_PRINTLN("RadioLibWrapper: noise_floor = %d", (int)_noise_floor); 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() { void RadioLibWrapper::startRecv() {
@ -180,6 +230,21 @@ void RadioLibWrapper::onSendFinished() {
} }
int16_t RadioLibWrapper::performChannelScan() { 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(); return _radio->scanChannel();
} }

16
src/helpers/radiolib/RadioLibWrappers.h

@ -18,6 +18,10 @@ protected:
uint16_t _num_floor_samples; uint16_t _num_floor_samples;
int32_t _floor_sample_sum; int32_t _floor_sample_sum;
uint8_t _preamble_sf; 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 idle();
void startRecv(); void startRecv();
@ -54,6 +58,18 @@ public:
void updatePreamble(uint8_t sf) { _preamble_sf = sf; _radio->setPreambleLength(preambleLengthForSF(sf)); } void updatePreamble(uint8_t sf) { _preamble_sf = sf; _radio->setPreambleLength(preambleLengthForSF(sf)); }
PacketMillis calcMaxPacketMillis(uint8_t sf, float bw, uint8_t cr, uint8_t preambleSymbols); PacketMillis calcMaxPacketMillis(uint8_t sf, float bw, uint8_t cr, uint8_t preambleSymbols);
virtual int16_t performChannelScan(); 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; } int getNoiseFloor() const override { return _noise_floor; }
void triggerNoiseFloorCalibrate(int threshold) override; void triggerNoiseFloorCalibrate(int threshold) override;

Loading…
Cancel
Save