feat(radio): restore CAD support + radio_fem_rxgain persistence (upstream parity)
Continues restoring features dropped by the 22eb9b87 revert. Both were
upstream-tested code, not intentional fork removals.
CAD (hardware Channel Activity Detection / listen-before-talk before TX),
fully restored and functional:
- NodePrefs.cad_enabled + `set cad on/off` / `get cad` CLI (default off)
- RadioLibWrapper: _cad_enabled + setCADEnabled() + the scanChannel()/CAD
branch in isChannelActive() (Phase 1 already restored the Dispatcher hook)
- getCADEnabled() overrides in the repeater/room/sensor apps (return the
pref) and companion (always on, matching upstream)
radio_fem_rxgain: the NodePrefs field + /com_prefs persistence are restored
here at upstream's exact offsets (293 fem, 294 cad), which makes /com_prefs
byte-identical to upstream through the tail. The field is persisted and
defaults on (upstream default), but the per-board LNA *driving* + the
`radio.fem.rxgain` CLI are deferred to the FEM-hardware change (they depend
on board methods and want per-board bench testing).
The new-format /com_prefs tail grows from 3 to 5 bytes; the old-format
detection threshold and the host migration harness are updated accordingly
(all scenarios pass, incl. the non-MQTT-build variant).
Builds: Heltec_v3 repeater, repeater-observer, room-observer. (sensor /
plain-room / companion fail only on the pre-existing Timezone.h include
issue, unrelated to these changes.)
This commit is contained in:
1 parent
7076ff5b31
commit
df90673d18
12 files changed
+80
-7
No files matched your search
@@ -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
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in new issue
Block a user