Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 37 additions & 1 deletion docs/cli_commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -613,7 +613,7 @@ This document provides an overview of CLI commands that can be sent to MeshCore
- `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.
**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
Expand All @@ -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 <on|off>`

**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 <thr_dBm>,<sense_ms>,<maxwait_ms>,<txmax_ms>,<pause_ms>`

**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`
Expand Down
18 changes: 18 additions & 0 deletions examples/companion_radio/MyMesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
6 changes: 6 additions & 0 deletions examples/companion_radio/MyMesh.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
24 changes: 24 additions & 0 deletions examples/companion_radio/NodePrefs.h
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand All @@ -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
Expand Down Expand Up @@ -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; }
Expand Down
18 changes: 18 additions & 0 deletions examples/simple_repeater/MyMesh.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
18 changes: 18 additions & 0 deletions examples/simple_room_server/MyMesh.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
18 changes: 18 additions & 0 deletions examples/simple_sensor/SensorMesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
6 changes: 6 additions & 0 deletions examples/simple_sensor/SensorMesh.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
27 changes: 26 additions & 1 deletion src/Dispatcher.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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);
Expand Down
8 changes: 8 additions & 0 deletions src/Dispatcher.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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; }

Expand Down
24 changes: 24 additions & 0 deletions src/helpers/CommonCLI.h
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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);
Expand Down Expand Up @@ -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; }
Expand Down
39 changes: 39 additions & 0 deletions src/helpers/CommonRadioPrefs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading