diff --git a/docs/cli_commands.md b/docs/cli_commands.md index 8772b929fe..4ea744c65b 100644 --- a/docs/cli_commands.md +++ b/docs/cli_commands.md @@ -613,7 +613,7 @@ This document provides an overview of CLI commands that can be sent to MeshCore - `get cad` - `set cad ` -**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. +**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` and `rssi.lbt` — any combination may be active. **Parameters:** - `on|off`: Enable or disable hardware CAD @@ -622,6 +622,42 @@ This document provides an overview of CLI commands that can be sent to MeshCore --- +#### Enable or disable RSSI listen-before-talk +**Usage:** +- `get rssi.lbt` +- `set rssi.lbt ` + +**Description:** When enabled, the radio samples RSSI continuously for a short window before transmitting and defers if any sample exceeds an absolute threshold. Unlike `cad`, which detects a LoRa preamble, this is energy detection and so is independent of whatever modulation is on air; unlike `int.thresh`, the threshold is absolute rather than relative to the tracked noise floor. + +Runs independently of `cad` and `int.thresh` — any combination may be active. When both `cad` and `rssi.lbt` are enabled, the stricter of CAD's fixed 4000ms wait and the `rssi.lbt` `maxwait_ms` applies. + +While this is `off`, every value in `rssi.lbt.params` is ignored and the node transmits exactly as it would with this feature absent. + +**Parameters:** +- `on|off`: Enable or disable RSSI listen-before-talk + +**Default:** `off` + +--- + +#### View or change the RSSI listen-before-talk parameters +**Usage:** +- `get rssi.lbt.params` +- `set rssi.lbt.params ,,,,` + +**Description:** All five values are set together and reported together, in the same order, so the output of `get rssi.lbt.params` can be pasted straight back into `set rssi.lbt.params`. None of them have any effect while `rssi.lbt` is `off`. + +**Parameters:** +- `thr_dBm`: Absolute RSSI threshold, `-128`-`0`. A sample above this marks the channel busy. +- `sense_ms`: How long to sample RSSI for, `0`-`65535`. `0` takes a single instantaneous reading. +- `maxwait_ms`: How long a busy channel may hold off a pending transmit before it is sent anyway, `0`-`65535`. `0` means unlimited — the transmit is never forced. +- `txmax_ms`: Maximum airtime of a single transmit, `0`-`65535`. A packet whose estimated airtime exceeds this is dropped rather than sent. `0` means unlimited. +- `pause_ms`: Quiet period enforced after each transmit, `0`-`65535`. + +**Default:** `-80,5,0,4000,50` + +--- + #### View or change the AGC Reset Interval **Usage:** - `get agc.reset.interval` diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 46c8e2f60c..72629a2159 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -270,6 +270,24 @@ int MyMesh::getInterferenceThreshold() const { bool MyMesh::getCADEnabled() const { return _prefs.cad_enabled; } +bool MyMesh::getRssiLbtEnabled() const { + return _prefs.rssi_lbt_enabled; +} +int8_t MyMesh::getRssiLbtThrDbm() const { + return _prefs.rssi_lbt_thr_dbm; +} +uint16_t MyMesh::getRssiLbtSenseMs() const { + return _prefs.rssi_lbt_sense_ms; +} +uint16_t MyMesh::getRssiLbtMaxwaitMs() const { + return _prefs.rssi_lbt_maxwait_ms; +} +uint16_t MyMesh::getRssiLbtTxmaxMs() const { + return _prefs.rssi_lbt_txmax_ms; +} +uint16_t MyMesh::getRssiLbtPauseMs() const { + return _prefs.rssi_lbt_pause_ms; +} int MyMesh::calcRxDelay(float score, uint32_t air_time) const { if (_prefs.rx_delay_base <= 0.0f) return 0; diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index 3b98a4f674..bf272653d3 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -121,6 +121,12 @@ class MyMesh : public BaseChatMesh, public DataStoreHost { float getAirtimeBudgetFactor() const override; int getInterferenceThreshold() const override; bool getCADEnabled() const override; + bool getRssiLbtEnabled() const override; + int8_t getRssiLbtThrDbm() const override; + uint16_t getRssiLbtSenseMs() const override; + uint16_t getRssiLbtMaxwaitMs() const override; + uint16_t getRssiLbtTxmaxMs() const override; + uint16_t getRssiLbtPauseMs() const override; int getAGCResetInterval() const override { return ((int)_prefs.agc_reset_interval) * 4000; // milliseconds } diff --git a/examples/companion_radio/NodePrefs.h b/examples/companion_radio/NodePrefs.h index 846ff16989..3c36aa407f 100644 --- a/examples/companion_radio/NodePrefs.h +++ b/examples/companion_radio/NodePrefs.h @@ -43,6 +43,12 @@ class NodePrefs : public ConfigSerializer { // persisted to file uint8_t path_hash_mode = 0; // which path mode to use when sending uint8_t autoadd_max_hops = 0; // 0 = no limit, 1 = direct (0 hops), N = up to N-1 hops (max 64) uint8_t cad_enabled = 0; + uint8_t rssi_lbt_enabled = 0; // RSSI listen-before-talk (boolean) + int8_t rssi_lbt_thr_dbm = -80; // absolute RSSI threshold, above which the channel counts as busy + uint16_t rssi_lbt_sense_ms = 5; // how long to sample RSSI for + uint16_t rssi_lbt_maxwait_ms = 0; // max time to wait out a busy channel before forcing TX (0 = unlimited) + uint16_t rssi_lbt_txmax_ms = 4000; // max airtime of a single TX (0 = unlimited) + uint16_t rssi_lbt_pause_ms = 50; // quiet period after each TX uint8_t interference_threshold = 0; uint8_t agc_reset_interval = 0; // secs / 4 char default_scope_name[31]; @@ -59,6 +65,12 @@ class NodePrefs : public ConfigSerializer { // persisted to file def("sf", _parent->sf); def("cr", _parent->cr); def("cad", _parent->cad_enabled); + def("lbt_en", _parent->rssi_lbt_enabled); + def("lbt_thr", _parent->rssi_lbt_thr_dbm); + def("lbt_sense", _parent->rssi_lbt_sense_ms); + def("lbt_maxwait", _parent->rssi_lbt_maxwait_ms); + def("lbt_txmax", _parent->rssi_lbt_txmax_ms); + def("lbt_pause", _parent->rssi_lbt_pause_ms); def("int_thr", _parent->interference_threshold); def("rxgain", _parent->rx_boosted_gain); def("fem_rxgain", _parent->radio_fem_rxgain); // fem_rxgain WAS mapped to wrong JSON property previously @@ -88,6 +100,18 @@ class NodePrefs : public ConfigSerializer { // persisted to file void setAirtimeFactor(float af) override { _parent->airtime_factor = af; markDirty(); } bool isCadEnabled() const override { return _parent->cad_enabled; } void setCadEnabled(bool en) override { _parent->cad_enabled = en; markDirty(); } + bool isRssiLbtEnabled() const override { return _parent->rssi_lbt_enabled; } + void setRssiLbtEnabled(bool en) override { _parent->rssi_lbt_enabled = en; markDirty(); } + int8_t getRssiLbtThrDbm() const override { return _parent->rssi_lbt_thr_dbm; } + void setRssiLbtThrDbm(int8_t dbm) override { _parent->rssi_lbt_thr_dbm = dbm; markDirty(); } + uint16_t getRssiLbtSenseMs() const override { return _parent->rssi_lbt_sense_ms; } + void setRssiLbtSenseMs(uint16_t ms) override { _parent->rssi_lbt_sense_ms = ms; markDirty(); } + uint16_t getRssiLbtMaxwaitMs() const override { return _parent->rssi_lbt_maxwait_ms; } + void setRssiLbtMaxwaitMs(uint16_t ms) override { _parent->rssi_lbt_maxwait_ms = ms; markDirty(); } + uint16_t getRssiLbtTxmaxMs() const override { return _parent->rssi_lbt_txmax_ms; } + void setRssiLbtTxmaxMs(uint16_t ms) override { _parent->rssi_lbt_txmax_ms = ms; markDirty(); } + uint16_t getRssiLbtPauseMs() const override { return _parent->rssi_lbt_pause_ms; } + void setRssiLbtPauseMs(uint16_t ms) override { _parent->rssi_lbt_pause_ms = ms; markDirty(); } uint8_t getIntThresh() const override { return _parent->interference_threshold; } void setIntThresh(uint8_t t) override { _parent->interference_threshold = t; markDirty(); } uint8_t getRxGain() const override { return _parent->rx_boosted_gain; } diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index cac6c4a281..247a2acf33 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -150,6 +150,24 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { bool getCADEnabled() const override { return _prefs.cad_enabled; } + bool getRssiLbtEnabled() const override { + return _prefs.rssi_lbt_enabled; + } + int8_t getRssiLbtThrDbm() const override { + return _prefs.rssi_lbt_thr_dbm; + } + uint16_t getRssiLbtSenseMs() const override { + return _prefs.rssi_lbt_sense_ms; + } + uint16_t getRssiLbtMaxwaitMs() const override { + return _prefs.rssi_lbt_maxwait_ms; + } + uint16_t getRssiLbtTxmaxMs() const override { + return _prefs.rssi_lbt_txmax_ms; + } + uint16_t getRssiLbtPauseMs() const override { + return _prefs.rssi_lbt_pause_ms; + } int getAGCResetInterval() const override { return ((int)_prefs.agc_reset_interval) * 4000; // milliseconds } diff --git a/examples/simple_room_server/MyMesh.h b/examples/simple_room_server/MyMesh.h index 5cf949c6bd..e4f41d8a02 100644 --- a/examples/simple_room_server/MyMesh.h +++ b/examples/simple_room_server/MyMesh.h @@ -149,6 +149,24 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { bool getCADEnabled() const override { return _prefs.cad_enabled; } + bool getRssiLbtEnabled() const override { + return _prefs.rssi_lbt_enabled; + } + int8_t getRssiLbtThrDbm() const override { + return _prefs.rssi_lbt_thr_dbm; + } + uint16_t getRssiLbtSenseMs() const override { + return _prefs.rssi_lbt_sense_ms; + } + uint16_t getRssiLbtMaxwaitMs() const override { + return _prefs.rssi_lbt_maxwait_ms; + } + uint16_t getRssiLbtTxmaxMs() const override { + return _prefs.rssi_lbt_txmax_ms; + } + uint16_t getRssiLbtPauseMs() const override { + return _prefs.rssi_lbt_pause_ms; + } int getAGCResetInterval() const override { return ((int)_prefs.agc_reset_interval) * 4000; // milliseconds } diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index 23d0cdc353..5b2195137a 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -326,6 +326,24 @@ int SensorMesh::getInterferenceThreshold() const { bool SensorMesh::getCADEnabled() const { return _prefs.cad_enabled; } +bool SensorMesh::getRssiLbtEnabled() const { + return _prefs.rssi_lbt_enabled; +} +int8_t SensorMesh::getRssiLbtThrDbm() const { + return _prefs.rssi_lbt_thr_dbm; +} +uint16_t SensorMesh::getRssiLbtSenseMs() const { + return _prefs.rssi_lbt_sense_ms; +} +uint16_t SensorMesh::getRssiLbtMaxwaitMs() const { + return _prefs.rssi_lbt_maxwait_ms; +} +uint16_t SensorMesh::getRssiLbtTxmaxMs() const { + return _prefs.rssi_lbt_txmax_ms; +} +uint16_t SensorMesh::getRssiLbtPauseMs() const { + return _prefs.rssi_lbt_pause_ms; +} int SensorMesh::getAGCResetInterval() const { return ((int)_prefs.agc_reset_interval) * 4000; // milliseconds } diff --git a/examples/simple_sensor/SensorMesh.h b/examples/simple_sensor/SensorMesh.h index b5e96d5cc7..50af6a46cf 100644 --- a/examples/simple_sensor/SensorMesh.h +++ b/examples/simple_sensor/SensorMesh.h @@ -121,6 +121,12 @@ class SensorMesh : public mesh::Mesh, public CommonCLICallbacks { uint32_t getDirectRetransmitDelay(const mesh::Packet* packet) override; int getInterferenceThreshold() const override; bool getCADEnabled() const override; + bool getRssiLbtEnabled() const override; + int8_t getRssiLbtThrDbm() const override; + uint16_t getRssiLbtSenseMs() const override; + uint16_t getRssiLbtMaxwaitMs() const override; + uint16_t getRssiLbtTxmaxMs() const override; + uint16_t getRssiLbtPauseMs() 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/Dispatcher.cpp b/src/Dispatcher.cpp index c0610b7f8a..c5f2a25352 100644 --- a/src/Dispatcher.cpp +++ b/src/Dispatcher.cpp @@ -59,14 +59,28 @@ int Dispatcher::calcRxDelay(float score, uint32_t air_time) const { uint32_t Dispatcher::getCADFailRetryDelay() const { return 200; } +// a max-wait of zero means 'unlimited', which is the strictest setting of all, so normalise it +// to the largest representable duration before any comparison. +static uint32_t normMaxWait(uint16_t ms) { + return ms == 0 ? 0xFFFFFFFFUL : (uint32_t) ms; +} + uint32_t Dispatcher::getCADFailMaxDuration() const { - return 4000; // 4 seconds + // rssi.lbt is the only mechanism with a configurable max-wait; CAD's is the fixed upstream + // 4000ms. With rssi.lbt off this is byte-for-byte the original hard-coded behaviour. With + // both active, the stricter (longer) of the two wins -- CAD's fixed wait never gets to + // shorten a max-wait that rssi.lbt asked to be longer. + if (!getRssiLbtEnabled()) return 4000; // 4 seconds, upstream default, untouched + uint32_t rssi = normMaxWait(getRssiLbtMaxwaitMs()); + if (!getCADEnabled()) return rssi; + return rssi > 4000 ? rssi : 4000; } void Dispatcher::loop() { if (millisHasNowPassed(next_floor_calib_time)) { _radio->triggerNoiseFloorCalibrate(getInterferenceThreshold()); _radio->setCADEnabled(getCADEnabled()); + _radio->setRssiLbtParams(getRssiLbtEnabled(), getRssiLbtThrDbm(), getRssiLbtSenseMs(), getRssiLbtPauseMs()); next_floor_calib_time = futureMillis(NOISE_FLOOR_CALIB_INTERVAL); } _radio->loop(); @@ -324,6 +338,17 @@ void Dispatcher::checkSend() { } else { memcpy(&raw[len], outbound->payload, outbound->payload_len); len += outbound->payload_len; + uint16_t txmax = getRssiLbtTxmaxMs(); + if (getRssiLbtEnabled() && txmax != 0 && _radio->getEstAirtimeFor(len) > txmax) { + MESH_DEBUG_PRINTLN("%s Dispatcher::checkSend(): packet airtime exceeds rssi.lbt txmax, dropping, len=%d", getLogDateTime(), len); + + logTxFail(outbound, outbound->getRawLength()); + + releasePacket(outbound); // return to pool + outbound = NULL; + return; + } + uint32_t max_airtime = _radio->getEstAirtimeFor(len)*3/2; outbound_start = _ms->getMillis(); bool success = _radio->startSendRaw(raw, len); diff --git a/src/Dispatcher.h b/src/Dispatcher.h index aad6cba3ec..1f2d17f718 100644 --- a/src/Dispatcher.h +++ b/src/Dispatcher.h @@ -67,6 +67,8 @@ class Radio { virtual void setCADEnabled(bool enable) { } + virtual void setRssiLbtParams(bool enabled, int8_t thr_dbm, uint16_t sense_ms, uint16_t pause_ms) { } + virtual void resetAGC() { } virtual bool isInRecvMode() const = 0; @@ -169,6 +171,12 @@ class Dispatcher { 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 bool getRssiLbtEnabled() const { return false; } // RSSI listen-before-talk disabled by default + virtual int8_t getRssiLbtThrDbm() const { return 0; } + virtual uint16_t getRssiLbtSenseMs() const { return 0; } + virtual uint16_t getRssiLbtMaxwaitMs() const { return 0; } + virtual uint16_t getRssiLbtTxmaxMs() const { return 0; } + virtual uint16_t getRssiLbtPauseMs() const { return 0; } virtual int getAGCResetInterval() const { return 0; } // disabled by default virtual unsigned long getDutyCycleWindowMs() const { return 3600000; } diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index 8591cdc140..65f32db0be 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -71,6 +71,12 @@ class NodePrefs : public ConfigSerializer { uint8_t path_hash_mode = 0; // which path mode to use when sending uint8_t loop_detect = 0; uint8_t cad_enabled = 0; // hardware Channel Activity Detection before TX (boolean) + uint8_t rssi_lbt_enabled = 0; // RSSI listen-before-talk (boolean) + int8_t rssi_lbt_thr_dbm = -80; // absolute RSSI threshold, above which the channel counts as busy + uint16_t rssi_lbt_sense_ms = 5; // how long to sample RSSI for + uint16_t rssi_lbt_maxwait_ms = 0; // max time to wait out a busy channel before forcing TX (0 = unlimited) + uint16_t rssi_lbt_txmax_ms = 4000; // max airtime of a single TX (0 = unlimited) + uint16_t rssi_lbt_pause_ms = 50; // quiet period after each TX uint8_t extra_sf[4]; private: @@ -83,6 +89,12 @@ class NodePrefs : public ConfigSerializer { def("sf", _parent->sf); def("cr", _parent->cr); def("cad", _parent->cad_enabled); + def("lbt_en", _parent->rssi_lbt_enabled); + def("lbt_thr", _parent->rssi_lbt_thr_dbm); + def("lbt_sense", _parent->rssi_lbt_sense_ms); + def("lbt_maxwait", _parent->rssi_lbt_maxwait_ms); + def("lbt_txmax", _parent->rssi_lbt_txmax_ms); + def("lbt_pause", _parent->rssi_lbt_pause_ms); def("int_thr", _parent->interference_threshold); def("rxgain", _parent->rx_boosted_gain); def("fem_rxgain", _parent->radio_fem_rxgain); @@ -111,6 +123,18 @@ class NodePrefs : public ConfigSerializer { void setAirtimeFactor(float af) override { _parent->airtime_factor = af; markDirty(); } bool isCadEnabled() const override { return _parent->cad_enabled; } void setCadEnabled(bool en) override { _parent->cad_enabled = en; markDirty(); } + bool isRssiLbtEnabled() const override { return _parent->rssi_lbt_enabled; } + void setRssiLbtEnabled(bool en) override { _parent->rssi_lbt_enabled = en; markDirty(); } + int8_t getRssiLbtThrDbm() const override { return _parent->rssi_lbt_thr_dbm; } + void setRssiLbtThrDbm(int8_t dbm) override { _parent->rssi_lbt_thr_dbm = dbm; markDirty(); } + uint16_t getRssiLbtSenseMs() const override { return _parent->rssi_lbt_sense_ms; } + void setRssiLbtSenseMs(uint16_t ms) override { _parent->rssi_lbt_sense_ms = ms; markDirty(); } + uint16_t getRssiLbtMaxwaitMs() const override { return _parent->rssi_lbt_maxwait_ms; } + void setRssiLbtMaxwaitMs(uint16_t ms) override { _parent->rssi_lbt_maxwait_ms = ms; markDirty(); } + uint16_t getRssiLbtTxmaxMs() const override { return _parent->rssi_lbt_txmax_ms; } + void setRssiLbtTxmaxMs(uint16_t ms) override { _parent->rssi_lbt_txmax_ms = ms; markDirty(); } + uint16_t getRssiLbtPauseMs() const override { return _parent->rssi_lbt_pause_ms; } + void setRssiLbtPauseMs(uint16_t ms) override { _parent->rssi_lbt_pause_ms = ms; markDirty(); } uint8_t getIntThresh() const override { return _parent->interference_threshold; } void setIntThresh(uint8_t t) override { _parent->interference_threshold = t; markDirty(); } uint8_t getRxGain() const override { return _parent->rx_boosted_gain; } diff --git a/src/helpers/CommonRadioPrefs.cpp b/src/helpers/CommonRadioPrefs.cpp index a25df88069..e87fb6ca3e 100644 --- a/src/helpers/CommonRadioPrefs.cpp +++ b/src/helpers/CommonRadioPrefs.cpp @@ -113,6 +113,45 @@ bool CommonRadioPrefs::handleCommand(const char* command, uint32_t sender_timest return true; } + if (strcmp(command, "get rssi.lbt") == 0) { + sprintf(reply, "> %s", isRssiLbtEnabled() ? "on" : "off"); + return true; + } + if (memcmp(command, "set rssi.lbt ", 13) == 0) { + setRssiLbtEnabled(memcmp(&command[13], "on", 2) == 0); + strcpy(reply, "OK"); + return true; + } + + if (strcmp(command, "get rssi.lbt.params") == 0) { + sprintf(reply, "> %d,%d,%d,%d,%d", (int32_t) getRssiLbtThrDbm(), (uint32_t) getRssiLbtSenseMs(), + (uint32_t) getRssiLbtMaxwaitMs(), (uint32_t) getRssiLbtTxmaxMs(), (uint32_t) getRssiLbtPauseMs()); + return true; + } + if (memcmp(command, "set rssi.lbt.params ", 20) == 0) { + char tmp[132]; + strcpy(tmp, &command[20]); + const char *parts[5]; + int num = mesh::Utils::parseTextParts(tmp, parts, 5); + int32_t thr = num > 0 ? atol(parts[0]) : 1; // out of range, so a short command is rejected below + int32_t sense = num > 1 ? atol(parts[1]) : -1; + int32_t maxwait = num > 2 ? atol(parts[2]) : -1; + int32_t txmax = num > 3 ? atol(parts[3]) : -1; + int32_t pause = num > 4 ? atol(parts[4]) : -1; + if (thr >= -128 && thr <= 0 && sense >= 0 && sense <= 65535 && maxwait >= 0 && maxwait <= 65535 + && txmax >= 0 && txmax <= 65535 && pause >= 0 && pause <= 65535) { + setRssiLbtThrDbm((int8_t) thr); + setRssiLbtSenseMs((uint16_t) sense); + setRssiLbtMaxwaitMs((uint16_t) maxwait); + setRssiLbtTxmaxMs((uint16_t) txmax); + setRssiLbtPauseMs((uint16_t) pause); + strcpy(reply, "OK"); + } else { + strcpy(reply, "Error, invalid rssi.lbt params"); + } + return true; + } + if (strcmp(command, "get radio.rxgain") == 0) { sprintf(reply, "> %s", getRxGain() != 0 ? "on" : "off"); return true; diff --git a/src/helpers/CommonRadioPrefs.h b/src/helpers/CommonRadioPrefs.h index 96895bb2eb..81ccf3fbd9 100644 --- a/src/helpers/CommonRadioPrefs.h +++ b/src/helpers/CommonRadioPrefs.h @@ -29,6 +29,24 @@ class CommonRadioPrefs : public ConfigSerializer, public KeyValueStore { virtual bool isCadEnabled() const = 0; virtual void setCadEnabled(bool en) = 0; + virtual bool isRssiLbtEnabled() const = 0; + virtual void setRssiLbtEnabled(bool en) = 0; + + virtual int8_t getRssiLbtThrDbm() const = 0; + virtual void setRssiLbtThrDbm(int8_t dbm) = 0; + + virtual uint16_t getRssiLbtSenseMs() const = 0; + virtual void setRssiLbtSenseMs(uint16_t ms) = 0; + + virtual uint16_t getRssiLbtMaxwaitMs() const = 0; + virtual void setRssiLbtMaxwaitMs(uint16_t ms) = 0; + + virtual uint16_t getRssiLbtTxmaxMs() const = 0; + virtual void setRssiLbtTxmaxMs(uint16_t ms) = 0; + + virtual uint16_t getRssiLbtPauseMs() const = 0; + virtual void setRssiLbtPauseMs(uint16_t ms) = 0; + virtual uint8_t getIntThresh() const = 0; virtual void setIntThresh(uint8_t t) = 0; diff --git a/src/helpers/radiolib/RadioLibWrappers.cpp b/src/helpers/radiolib/RadioLibWrappers.cpp index e4d2ba1c27..c70e429af1 100644 --- a/src/helpers/radiolib/RadioLibWrappers.cpp +++ b/src/helpers/radiolib/RadioLibWrappers.cpp @@ -37,6 +37,10 @@ void RadioLibWrapper::begin() { _noise_floor = 0; _threshold = 0; _cad_enabled = false; + _rssi_lbt_enabled = false; + _rssi_lbt_thr_dbm = 0; + _rssi_lbt_sense_ms = 0; + _rssi_lbt_pause_ms = 0; // start average out some samples _num_floor_samples = 0; @@ -189,6 +193,11 @@ void RadioLibWrapper::onSendFinished() { _radio->finishTransmit(); _board->onAfterTransmit(); state = STATE_IDLE; + + // rssi.lbt: quiet period after every transmit + if (_rssi_lbt_enabled && _rssi_lbt_pause_ms > 0) { + delay(_rssi_lbt_pause_ms); + } } int16_t RadioLibWrapper::performChannelScan() { @@ -199,6 +208,15 @@ bool RadioLibWrapper::isChannelActive() { // int.thresh: RSSI-based interference detection (relative to noise floor) if (_threshold != 0 && getCurrentRSSI() > _noise_floor + _threshold) return true; + // rssi.lbt: energy detection against an absolute threshold, sampled continuously + // over a sensing window. Unlike CAD this is independent of the modulation on air. + if (_rssi_lbt_enabled) { + uint32_t start = millis(); + do { + if (getCurrentRSSI() > _rssi_lbt_thr_dbm) return true; + } while (millis() - start < _rssi_lbt_sense_ms); + } + // cad: hardware channel activity detection if (_cad_enabled) { int16_t result = performChannelScan(); diff --git a/src/helpers/radiolib/RadioLibWrappers.h b/src/helpers/radiolib/RadioLibWrappers.h index 77dd93116b..15fa13f872 100644 --- a/src/helpers/radiolib/RadioLibWrappers.h +++ b/src/helpers/radiolib/RadioLibWrappers.h @@ -18,6 +18,10 @@ class RadioLibWrapper : public mesh::Radio { uint32_t n_recv, n_sent, n_recv_errors; int16_t _noise_floor, _threshold; bool _cad_enabled; + bool _rssi_lbt_enabled; + int8_t _rssi_lbt_thr_dbm; + uint16_t _rssi_lbt_sense_ms; + uint16_t _rssi_lbt_pause_ms; uint16_t _num_floor_samples; int32_t _floor_sample_sum; uint8_t _preamble_sf; @@ -61,6 +65,12 @@ class RadioLibWrapper : public mesh::Radio { int getNoiseFloor() const override { return _noise_floor; } void triggerNoiseFloorCalibrate(int threshold) override; void setCADEnabled(bool enable) override { _cad_enabled = enable; } + void setRssiLbtParams(bool enabled, int8_t thr_dbm, uint16_t sense_ms, uint16_t pause_ms) override { + _rssi_lbt_enabled = enabled; + _rssi_lbt_thr_dbm = thr_dbm; + _rssi_lbt_sense_ms = sense_ms; + _rssi_lbt_pause_ms = pause_ms; + } void resetAGC() override; void loop() override;