From b4076ca23493f3b1a3a81c95e6cf21affa3850b3 Mon Sep 17 00:00:00 2001
From: Wessel Nieboer <wessel@weebl.me>
Date: Wed, 18 Feb 2026 01:16:52 +0100
Subject: [PATCH 1/4] Use hardware channel activity detection for checking
 interference

---
 examples/companion_radio/MyMesh.cpp       |  2 +-
 examples/simple_repeater/MyMesh.cpp       |  2 +-
 examples/simple_room_server/MyMesh.cpp    |  2 +-
 examples/simple_sensor/SensorMesh.cpp     |  2 +-
 src/helpers/radiolib/RadioLibWrappers.cpp | 13 ++++++++++---
 src/helpers/radiolib/RadioLibWrappers.h   |  3 ++-
 6 files changed, 16 insertions(+), 8 deletions(-)

diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp
index e8c1914bad..7d621d53b4 100644
--- a/examples/companion_radio/MyMesh.cpp
+++ b/examples/companion_radio/MyMesh.cpp
@@ -258,7 +258,7 @@ float MyMesh::getAirtimeBudgetFactor() const {
 }
 
 int MyMesh::getInterferenceThreshold() const {
-  return 0; // disabled for now, until currentRSSI() problem is resolved
+  return 1; // non-zero enables hardware CAD (Channel Activity Detection) before TX
 }
 
 int MyMesh::calcRxDelay(float score, uint32_t air_time) const {
diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp
index 666f79fc5c..13dd916892 100644
--- a/examples/simple_repeater/MyMesh.cpp
+++ b/examples/simple_repeater/MyMesh.cpp
@@ -886,7 +886,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
   _prefs.advert_interval = 1;        // default to 2 minutes for NEW installs
   _prefs.flood_advert_interval = 12; // 12 hours
   _prefs.flood_max = 64;
-  _prefs.interference_threshold = 0; // disabled
+  _prefs.interference_threshold = 1; // non-zero enables hardware CAD before TX
 
   // bridge defaults
   _prefs.bridge_enabled = 1;    // enabled
diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp
index 145fb0fd9f..78488bf569 100644
--- a/examples/simple_room_server/MyMesh.cpp
+++ b/examples/simple_room_server/MyMesh.cpp
@@ -643,7 +643,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
   _prefs.advert_interval = 1;        // default to 2 minutes for NEW installs
   _prefs.flood_advert_interval = 12; // 12 hours
   _prefs.flood_max = 64;
-  _prefs.interference_threshold = 0; // disabled
+  _prefs.interference_threshold = 1; // non-zero enables hardware CAD before TX
 #ifdef ROOM_PASSWORD
   StrHelper::strncpy(_prefs.guest_password, ROOM_PASSWORD, sizeof(_prefs.guest_password));
 #endif
diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp
index b8fe1e579c..05b2952657 100644
--- a/examples/simple_sensor/SensorMesh.cpp
+++ b/examples/simple_sensor/SensorMesh.cpp
@@ -725,7 +725,7 @@ SensorMesh::SensorMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::Millise
   _prefs.flood_advert_interval = 0;   // disabled
   _prefs.disable_fwd = true;
   _prefs.flood_max = 64;
-  _prefs.interference_threshold = 0;  // disabled
+  _prefs.interference_threshold = 1;  // non-zero enables hardware CAD before TX
 
   // GPS defaults
   _prefs.gps_enabled = 0;
diff --git a/src/helpers/radiolib/RadioLibWrappers.cpp b/src/helpers/radiolib/RadioLibWrappers.cpp
index 85f3a935db..62856cb53e 100644
--- a/src/helpers/radiolib/RadioLibWrappers.cpp
+++ b/src/helpers/radiolib/RadioLibWrappers.cpp
@@ -170,10 +170,17 @@ 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;
+  if (_threshold == 0) return false;    // interference check is disabled
+
+  int16_t result = performChannelScan();
+  // scanChannel() leaves radio in standby — restart RX regardless of result
+  startRecv();
+  return (result == RADIOLIB_LORA_DETECTED);
 }
 
 float RadioLibWrapper::getLastRSSI() const {
diff --git a/src/helpers/radiolib/RadioLibWrappers.h b/src/helpers/radiolib/RadioLibWrappers.h
index ff951aaa47..89c403927c 100644
--- a/src/helpers/radiolib/RadioLibWrappers.h
+++ b/src/helpers/radiolib/RadioLibWrappers.h
@@ -32,7 +32,7 @@ class RadioLibWrapper : public mesh::Radio {
   bool isInRecvMode() const override;
   bool isChannelActive();
 
-  bool isReceiving() override { 
+  bool isReceiving() override {
     if (isReceivingPacket()) return true;
 
     return isChannelActive();
@@ -42,6 +42,7 @@ class RadioLibWrapper : public mesh::Radio {
   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;

From 8f3b409dbe06b140e0a19557ad56d2b4fe2a0be1 Mon Sep 17 00:00:00 2001
From: Wessel Nieboer <github@weebl.me>
Date: Sun, 22 Feb 2026 16:05:18 +0100
Subject: [PATCH 2/4] Also return busy if preamble detected

---
 src/helpers/radiolib/RadioLibWrappers.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/helpers/radiolib/RadioLibWrappers.cpp b/src/helpers/radiolib/RadioLibWrappers.cpp
index 62856cb53e..d13c1fadab 100644
--- a/src/helpers/radiolib/RadioLibWrappers.cpp
+++ b/src/helpers/radiolib/RadioLibWrappers.cpp
@@ -180,7 +180,7 @@ bool RadioLibWrapper::isChannelActive() {
   int16_t result = performChannelScan();
   // scanChannel() leaves radio in standby — restart RX regardless of result
   startRecv();
-  return (result == RADIOLIB_LORA_DETECTED);
+  return (result == RADIOLIB_LORA_DETECTED || result == RADIOLIB_PREAMBLE_DETECTED);
 }
 
 float RadioLibWrapper::getLastRSSI() const {

From 78c29a0900a1b36e2a057eab0a4491b3a7e0b440 Mon Sep 17 00:00:00 2001
From: Wessel Nieboer <github@weebl.me>
Date: Sun, 22 Feb 2026 16:08:04 +0100
Subject: [PATCH 3/4] Just check for not channel free

---
 src/helpers/radiolib/RadioLibWrappers.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/helpers/radiolib/RadioLibWrappers.cpp b/src/helpers/radiolib/RadioLibWrappers.cpp
index d13c1fadab..ce9bab06a3 100644
--- a/src/helpers/radiolib/RadioLibWrappers.cpp
+++ b/src/helpers/radiolib/RadioLibWrappers.cpp
@@ -180,7 +180,7 @@ bool RadioLibWrapper::isChannelActive() {
   int16_t result = performChannelScan();
   // scanChannel() leaves radio in standby — restart RX regardless of result
   startRecv();
-  return (result == RADIOLIB_LORA_DETECTED || result == RADIOLIB_PREAMBLE_DETECTED);
+  return result != RADIOLIB_CHANNEL_FREE;
 }
 
 float RadioLibWrapper::getLastRSSI() const {

From 2db6d4ef5ded657742a436624c9fb59aaa4f5d67 Mon Sep 17 00:00:00 2001
From: Wessel Nieboer <github@weebl.me>
Date: Fri, 6 Mar 2026 04:09:05 +0100
Subject: [PATCH 4/4] Prevent packet errors from growing

---
 src/helpers/radiolib/RadioLibWrappers.cpp | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/src/helpers/radiolib/RadioLibWrappers.cpp b/src/helpers/radiolib/RadioLibWrappers.cpp
index ce9bab06a3..df5033d80c 100644
--- a/src/helpers/radiolib/RadioLibWrappers.cpp
+++ b/src/helpers/radiolib/RadioLibWrappers.cpp
@@ -178,7 +178,10 @@ bool RadioLibWrapper::isChannelActive() {
   if (_threshold == 0) return false;    // interference check is disabled
 
   int16_t result = performChannelScan();
-  // scanChannel() leaves radio in standby — restart RX regardless of result
+  // 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();
   return result != RADIOLIB_CHANNEL_FREE;
 }
