diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 7ef5063f..b95f3a47 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -258,6 +258,10 @@ float MyMesh::getAirtimeBudgetFactor() const { return _prefs.airtime_factor; } +bool MyMesh::getCADEnabled() const { + return true; // hardware CAD before TX (no CLI toggle on companion; enabled by default) +} + int MyMesh::getInterferenceThreshold() const { return 0; // disabled for now, until currentRSSI() problem is resolved } diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index 43d3950b..f4190f30 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -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; diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 3fd7ec00..672e8ea8 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -979,6 +979,8 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc _prefs.rx_boosted_gain = 1; // enabled by default; #endif #endif + _prefs.radio_fem_rxgain = 1; // LoRa FEM RX gain on by default (FEM boards) + _prefs.cad_enabled = 0; // hardware CAD before TX (off by default; 'set cad on') pending_discover_tag = 0; pending_discover_until = 0; diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 853e8a34..d2a45b81 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -152,6 +152,10 @@ protected: return _prefs.airtime_factor; } + bool getCADEnabled() const override { + return _prefs.cad_enabled; + } + bool allowPacketForward(const mesh::Packet* packet) override; const char* getLogDateTime() override; void logRxRaw(float snr, float rssi, const uint8_t raw[], int len) override; diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index 892008d1..69fd99fb 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -672,6 +672,8 @@ 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.radio_fem_rxgain = 1; // LoRa FEM RX gain on by default (FEM boards) + _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 diff --git a/examples/simple_room_server/MyMesh.h b/examples/simple_room_server/MyMesh.h index 96186f0a..cd556be3 100644 --- a/examples/simple_room_server/MyMesh.h +++ b/examples/simple_room_server/MyMesh.h @@ -143,6 +143,10 @@ protected: return _prefs.airtime_factor; } + bool getCADEnabled() const override { + return _prefs.cad_enabled; + } + void logRxRaw(float snr, float rssi, const uint8_t raw[], int len) override; void logRx(mesh::Packet* pkt, int len, float score) override; void logTx(mesh::Packet* pkt, int len) override; diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index 434e06b9..bdd35fc0 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -301,6 +301,10 @@ float SensorMesh::getAirtimeBudgetFactor() const { return _prefs.airtime_factor; } +bool SensorMesh::getCADEnabled() const { + return _prefs.cad_enabled; +} + bool SensorMesh::allowPacketForward(const mesh::Packet* packet) { if (_prefs.disable_fwd) return false; if (packet->isRouteFlood() && packet->getPathHashCount() >= _prefs.flood_max) return false; @@ -726,6 +730,8 @@ SensorMesh::SensorMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::Millise _prefs.disable_fwd = true; _prefs.flood_max = 64; _prefs.interference_threshold = 0; // disabled + _prefs.radio_fem_rxgain = 1; // LoRa FEM RX gain on by default (FEM boards) + _prefs.cad_enabled = 0; // hardware CAD before TX (off by default; 'set cad on') // GPS defaults _prefs.gps_enabled = 0; diff --git a/examples/simple_sensor/SensorMesh.h b/examples/simple_sensor/SensorMesh.h index c9f135f6..1d65b877 100644 --- a/examples/simple_sensor/SensorMesh.h +++ b/examples/simple_sensor/SensorMesh.h @@ -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; diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index e11c17b5..9d832f93 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -150,8 +150,15 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { // MQTT gap here followed by a trailing observer block; detect that layout by the // extra length, skip the gap, and recover the tail so those settings survive // the upgrade (the file is rewritten in the new layout by loadPrefs afterwards). + // Defaults for the trailing fields that older/shorter files may not contain. + // (upstream defaults: FEM RX gain on, CAD off) — overwritten below if present. + _prefs->radio_fem_rxgain = 1; + _prefs->cad_enabled = 0; + // New-format tail is 5 bytes (rx_boosted_gain, flood_max_unscoped/advert, + // radio_fem_rxgain, cad_enabled). A larger `extra` means an old fork file with + // the legacy MQTT gap; detect and recover it below. size_t extra = file.available(); - if (extra > 3) { + if (extra > 5) { _com_prefs_needs_upgrade = true; size_t gap = 0; if (extra > LEGACY_MQTT_GAP_6SLOT && extra <= LEGACY_MQTT_GAP_6SLOT + LEGACY_OBS_TAIL_MAX) { @@ -256,6 +263,12 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { if (file.available() >= (int)sizeof(_prefs->flood_max_advert)) { file.read((uint8_t *)&_prefs->flood_max_advert, sizeof(_prefs->flood_max_advert)); } + if (file.available() >= (int)sizeof(_prefs->radio_fem_rxgain)) { // 293 + file.read((uint8_t *)&_prefs->radio_fem_rxgain, sizeof(_prefs->radio_fem_rxgain)); + } + if (file.available() >= (int)sizeof(_prefs->cad_enabled)) { // 294 + file.read((uint8_t *)&_prefs->cad_enabled, sizeof(_prefs->cad_enabled)); + } } // sanitise bad pref values @@ -272,6 +285,8 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { _prefs->adc_multiplier = constrain(_prefs->adc_multiplier, 0.0f, 10.0f); _prefs->path_hash_mode = constrain(_prefs->path_hash_mode, 0, 2); // NOTE: mode 3 reserved for future _prefs->loop_detect = constrain(_prefs->loop_detect, 0, 3); // LOOP_DETECT_OFF..LOOP_DETECT_STRICT + _prefs->radio_fem_rxgain = constrain(_prefs->radio_fem_rxgain, 0, 1); // boolean + _prefs->cad_enabled = constrain(_prefs->cad_enabled, 0, 1); // boolean // sanitise bad bridge pref values _prefs->bridge_enabled = constrain(_prefs->bridge_enabled, 0, 1); @@ -348,9 +363,11 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) { file.write((uint8_t *)_prefs->owner_info, sizeof(_prefs->owner_info)); // 170 // MQTT/observer settings are stored in /mqtt_prefs, not here. No zero-gap is // written anymore — /com_prefs holds only the (non-observer) fields below. - file.write((uint8_t *)&_prefs->rx_boosted_gain, sizeof(_prefs->rx_boosted_gain)); - file.write((uint8_t *)&_prefs->flood_max_unscoped, sizeof(_prefs->flood_max_unscoped)); - file.write((uint8_t *)&_prefs->flood_max_advert, sizeof(_prefs->flood_max_advert)); + file.write((uint8_t *)&_prefs->rx_boosted_gain, sizeof(_prefs->rx_boosted_gain)); // 290 + 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 + file.write((uint8_t *)&_prefs->cad_enabled, sizeof(_prefs->cad_enabled)); // 294 file.close(); } @@ -963,6 +980,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(); @@ -1249,6 +1270,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) { diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index b5240571..604ca77f 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -66,6 +66,12 @@ struct NodePrefs { // persisted to file uint8_t loop_detect; + // Restored from upstream (dropped by the 22eb9b87 revert). Persisted at the same + // /com_prefs offsets upstream uses (293, 294) so the file stays upstream-aligned. + uint8_t radio_fem_rxgain; // LoRa FEM RX-gain (LNA); default on. Hardware driving is + // wired per-board in the FEM-restore change; persisted here. + uint8_t cad_enabled; // hardware Channel Activity Detection before TX; default off + // NOTE: observer settings (MQTT/WiFi/timezone/SNMP/alert) were moved out of // NodePrefs into MQTTPrefs (persisted to /mqtt_prefs) so this struct stays // aligned with upstream. See struct MQTTPrefs below. diff --git a/src/helpers/radiolib/RadioLibWrappers.cpp b/src/helpers/radiolib/RadioLibWrappers.cpp index b6a82764..07259bfc 100644 --- a/src/helpers/radiolib/RadioLibWrappers.cpp +++ b/src/helpers/radiolib/RadioLibWrappers.cpp @@ -180,10 +180,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; } uint8_t RadioLibWrapper::getRadioState() const { diff --git a/src/helpers/radiolib/RadioLibWrappers.h b/src/helpers/radiolib/RadioLibWrappers.h index 5dc0e0d9..ea7fdb80 100644 --- a/src/helpers/radiolib/RadioLibWrappers.h +++ b/src/helpers/radiolib/RadioLibWrappers.h @@ -14,9 +14,11 @@ protected: unsigned long last_recv_millis; unsigned long last_radio_interrupt_millis; // updated on any ISR event, even CRC errors uint8_t _preamble_sf; + bool _cad_enabled; void idle() override; void startRecv() override; + int16_t performChannelScan(); float packetScoreInt(float snr, int sf, int packet_len); virtual bool isReceivingPacket() =0; virtual void doResetAGC(); @@ -26,6 +28,7 @@ public: n_recv = n_sent = n_recv_errors = 0; last_recv_millis = 0; last_radio_interrupt_millis = 0; + _cad_enabled = false; } void begin() override; @@ -55,6 +58,7 @@ public: 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;