Compare commits

...

7 Commits

Author SHA1 Message Date
ripplebiz 60ea4a91bf Merge pull request #1727 from weebl2000/use-hardware-channel-activity-detection
Use hardware channel activity detection for checking interference
2026-06-15 19:51:59 +10:00
Wessel Nieboer 7c8e092457 Have CAD be a separate toggle (set cad on/off) 2026-06-14 15:20:58 +02:00
Wessel Nieboer 099ef67348 Prevent packet errors from growing 2026-06-14 15:19:20 +02:00
Wessel Nieboer 04a6c7009a Just check for not channel free 2026-06-14 15:19:19 +02:00
Wessel Nieboer 813d108c7a Also return busy if preamble detected 2026-06-14 15:19:19 +02:00
Wessel Nieboer 4f9a091671 Use hardware channel activity detection for checking interference 2026-06-14 15:19:18 +02:00
Huw Duddy 29f0a7fc4f Merge pull request #2763 from meshcore-dev/anon-contacts-fix
* fix for anon contacts when full
2026-06-14 19:11:38 +10:00
15 changed files with 71 additions and 6 deletions
+14
View File
@@ -578,6 +578,20 @@ This document provides an overview of CLI commands that can be sent to MeshCore
---
#### Enable or disable hardware Channel Activity Detection (CAD)
**Usage:**
- `get cad`
- `set cad <on|off>`
**Description:** When enabled, the radio performs a hardware Channel Activity Detection scan before transmitting and defers if the channel is busy. Runs independently of `int.thresh` — either, both, or none may be active.
**Parameters:**
- `on|off`: Enable or disable hardware CAD
**Default:** `off`
---
#### View or change the AGC Reset Interval
**Usage:**
- `get agc.reset.interval`
+3
View File
@@ -261,6 +261,9 @@ float MyMesh::getAirtimeBudgetFactor() const {
int MyMesh::getInterferenceThreshold() const {
return 0; // disabled for now, until currentRSSI() problem is resolved
}
bool MyMesh::getCADEnabled() const {
return true; // hardware CAD before TX (no CLI toggle on companion; enabled by default)
}
int MyMesh::calcRxDelay(float score, uint32_t air_time) const {
if (_prefs.rx_delay_base <= 0.0f) return 0;
+1
View File
@@ -105,6 +105,7 @@ public:
protected:
float getAirtimeBudgetFactor() const override;
int getInterferenceThreshold() const override;
bool getCADEnabled() const override;
int calcRxDelay(float score, uint32_t air_time) const override;
uint32_t getRetransmitDelay(const mesh::Packet *packet) override;
uint32_t getDirectRetransmitDelay(const mesh::Packet *packet) override;
+1
View File
@@ -893,6 +893,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
_prefs.flood_max_unscoped = 64;
_prefs.flood_max_advert = 8;
_prefs.interference_threshold = 0; // disabled
_prefs.cad_enabled = 0; // hardware CAD before TX (off by default; 'set cad on')
// bridge defaults
_prefs.bridge_enabled = 1; // enabled
+3
View File
@@ -150,6 +150,9 @@ protected:
int getInterferenceThreshold() const override {
return _prefs.interference_threshold;
}
bool getCADEnabled() const override {
return _prefs.cad_enabled;
}
int getAGCResetInterval() const override {
return ((int)_prefs.agc_reset_interval) * 4000; // milliseconds
}
+1
View File
@@ -650,6 +650,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
_prefs.flood_max_unscoped = 64;
_prefs.flood_max_advert = 8;
_prefs.interference_threshold = 0; // disabled
_prefs.cad_enabled = 0; // hardware CAD before TX (off by default; 'set cad on')
#ifdef ROOM_PASSWORD
StrHelper::strncpy(_prefs.guest_password, ROOM_PASSWORD, sizeof(_prefs.guest_password));
#endif
+3
View File
@@ -144,6 +144,9 @@ protected:
int getInterferenceThreshold() const override {
return _prefs.interference_threshold;
}
bool getCADEnabled() const override {
return _prefs.cad_enabled;
}
int getAGCResetInterval() const override {
return ((int)_prefs.agc_reset_interval) * 4000; // milliseconds
}
+4
View File
@@ -323,6 +323,9 @@ uint32_t SensorMesh::getDirectRetransmitDelay(const mesh::Packet* packet) {
int SensorMesh::getInterferenceThreshold() const {
return _prefs.interference_threshold;
}
bool SensorMesh::getCADEnabled() const {
return _prefs.cad_enabled;
}
int SensorMesh::getAGCResetInterval() const {
return ((int)_prefs.agc_reset_interval) * 4000; // milliseconds
}
@@ -726,6 +729,7 @@ SensorMesh::SensorMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::Millise
_prefs.disable_fwd = true;
_prefs.flood_max = 64;
_prefs.interference_threshold = 0; // disabled
_prefs.cad_enabled = 0; // hardware CAD before TX (off by default; 'set cad on')
// GPS defaults
_prefs.gps_enabled = 0;
+1
View File
@@ -120,6 +120,7 @@ protected:
uint32_t getRetransmitDelay(const mesh::Packet* packet) override;
uint32_t getDirectRetransmitDelay(const mesh::Packet* packet) override;
int getInterferenceThreshold() const override;
bool getCADEnabled() const override;
int getAGCResetInterval() const override;
void onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, const mesh::Identity& sender, uint8_t* data, size_t len) override;
int searchPeersByHash(const uint8_t* hash) override;
+1
View File
@@ -66,6 +66,7 @@ uint32_t Dispatcher::getCADFailMaxDuration() const {
void Dispatcher::loop() {
if (millisHasNowPassed(next_floor_calib_time)) {
_radio->triggerNoiseFloorCalibrate(getInterferenceThreshold());
_radio->setCADEnabled(getCADEnabled());
next_floor_calib_time = futureMillis(NOISE_FLOOR_CALIB_INTERVAL);
}
_radio->loop();
+3
View File
@@ -65,6 +65,8 @@ public:
virtual void triggerNoiseFloorCalibrate(int threshold) { }
virtual void setCADEnabled(bool enable) { }
virtual void resetAGC() { }
virtual bool isInRecvMode() const = 0;
@@ -166,6 +168,7 @@ protected:
virtual uint32_t getCADFailRetryDelay() const;
virtual uint32_t getCADFailMaxDuration() const;
virtual int getInterferenceThreshold() const { return 0; } // disabled by default
virtual bool getCADEnabled() const { return false; } // hardware CAD disabled by default
virtual int getAGCResetInterval() const { return 0; } // disabled by default
virtual unsigned long getDutyCycleWindowMs() const { return 3600000; }
+11 -2
View File
@@ -92,7 +92,8 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
file.read((uint8_t *)&_prefs->flood_max_unscoped, sizeof(_prefs->flood_max_unscoped)); // 291
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
// next: 294
file.read((uint8_t *)&_prefs->cad_enabled, sizeof(_prefs->cad_enabled)); // 294
// next: 295
// sanitise bad pref values
_prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0, 20.0f);
@@ -123,6 +124,7 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
// sanitise settings
_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
file.close();
}
@@ -187,7 +189,8 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) {
file.write((uint8_t *)&_prefs->flood_max_unscoped, sizeof(_prefs->flood_max_unscoped)); // 291
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
// next: 294
file.write((uint8_t *)&_prefs->cad_enabled, sizeof(_prefs->cad_enabled)); // 294
// next: 295
file.close();
}
@@ -503,6 +506,10 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep
_prefs->interference_threshold = atoi(&config[11]);
savePrefs();
strcpy(reply, "OK");
} else if (memcmp(config, "cad ", 4) == 0) {
_prefs->cad_enabled = memcmp(&config[4], "on", 2) == 0;
savePrefs();
strcpy(reply, "OK");
} else if (memcmp(config, "agc.reset.interval ", 19) == 0) {
_prefs->agc_reset_interval = atoi(&config[19]) / 4;
savePrefs();
@@ -801,6 +808,8 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep
sprintf(reply, "> %s", StrHelper::ftoa(_prefs->airtime_factor));
} 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");
} 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) {
+1
View File
@@ -64,6 +64,7 @@ struct NodePrefs { // persisted to file
uint8_t radio_fem_rxgain; // LoRa FEM RX gain setting
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)
};
class CommonCLICallbacks {
+20 -3
View File
@@ -36,6 +36,7 @@ void RadioLibWrapper::begin() {
_noise_floor = 0;
_threshold = 0;
_cad_enabled = false;
// start average out some samples
_num_floor_samples = 0;
@@ -178,10 +179,26 @@ void RadioLibWrapper::onSendFinished() {
state = STATE_IDLE;
}
int16_t RadioLibWrapper::performChannelScan() {
return _radio->scanChannel();
}
bool RadioLibWrapper::isChannelActive() {
return _threshold == 0
? false // interference check is disabled
: getCurrentRSSI() > _noise_floor + _threshold;
// int.thresh: RSSI-based interference detection (relative to noise floor)
if (_threshold != 0 && getCurrentRSSI() > _noise_floor + _threshold) return true;
// cad: hardware channel activity detection
if (_cad_enabled) {
int16_t result = performChannelScan();
// scanChannel() triggers DIO interrupt (CAD done) which sets STATE_INT_READY
// via setFlag() ISR. Clear it before restarting RX so recvRaw() doesn't
// try to read a non-existent packet and count a spurious recv error.
state = STATE_IDLE;
startRecv();
if (result != RADIOLIB_CHANNEL_FREE) return true;
}
return false;
}
float RadioLibWrapper::getLastRSSI() const {
+4 -1
View File
@@ -9,6 +9,7 @@ protected:
mesh::MainBoard* _board;
uint32_t n_recv, n_sent, n_recv_errors;
int16_t _noise_floor, _threshold;
bool _cad_enabled;
uint16_t _num_floor_samples;
int32_t _floor_sample_sum;
uint8_t _preamble_sf;
@@ -32,7 +33,7 @@ public:
bool isInRecvMode() const override;
bool isChannelActive();
bool isReceiving() override {
bool isReceiving() override {
if (isReceivingPacket()) return true;
return isChannelActive();
@@ -46,9 +47,11 @@ public:
virtual uint8_t getSpreadingFactor() const { return LORA_SF; }
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();
int getNoiseFloor() const override { return _noise_floor; }
void triggerNoiseFloorCalibrate(int threshold) override;
void setCADEnabled(bool enable) override { _cad_enabled = enable; }
void resetAGC() override;
void loop() override;