Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
28a2400
MESHCORE_SIMULATOR patch
usrflo Jan 31, 2026
5a040a7
Implemented repeated sending for error correction
usrflo Jun 1, 2026
5b387ee
Added comments to explain changes related to the repeated-sending fea…
usrflo Jun 2, 2026
c8d6faf
Merge upstream/dev: resolve pref-slot conflict (flood_max at 291/292,…
usrflo Jun 7, 2026
e9ba400
Implemented special resend handling for TRACE packages and moved logi…
usrflo Jun 18, 2026
389ea15
TRACE packet handling, prevent repeated sending to the last hop as ca…
usrflo Jun 19, 2026
5096227
Reduce max resend attempts limit to 3 in CLI commands and related fil…
usrflo Jun 19, 2026
4d5a539
Merge upstream/dev into feature/repeated-sending-2
usrflo Jun 19, 2026
3856e49
Bugfix: check on path hash count > 0 to determine a packet resending
usrflo Jun 26, 2026
f1daf25
Merge branch 'pr-2670' into meshcore-dev#2670
m0urs Jul 3, 2026
0c1cebd
Update firmware version to v1.16.0.mu5BETA
m0urs Jul 3, 2026
f06f6b2
Merged meshcore-dev#2670
m0urs Jul 3, 2026
ed98b24
Fixed some compiler syntax errors
m0urs Jul 4, 2026
db6ccea
Set defaul value to OFF and add CLI description
m0urs Jul 4, 2026
31f577e
Merge upstream/dev into feature/repeated-sending-2
usrflo Jul 5, 2026
84bc3fa
Implement final hop ACK handling for direct packets to allow cancella…
usrflo Jul 5, 2026
2c48f09
Add non-invasive resend channel check and backoff mechanism for packe…
usrflo Jul 7, 2026
ab1bf53
Merge branch 'feature/repeated-sending-2' of https://github.com/usrfl…
m0urs Jul 9, 2026
270f48f
Merge branch 'usrflo-feature/repeated-sending-2' into meshcore-dev#2670
m0urs Jul 9, 2026
326833c
Merge branch 'v1.16.0mu6' into meshcore-dev#2670
m0urs Jul 9, 2026
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
12 changes: 12 additions & 0 deletions docs/cli_commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,18 @@ This document provides an overview of CLI commands that can be sent to MeshCore

---

#### View or change the maximum direct-route resend attempts
**Usage:**
- `get max.resend`
- `set max.resend <value>`

**Parameters:**
- `value`: Maximum number of resend attempts for direct-routed packets (0–3). `0` disables resending entirely.

**Default:** `2`

---

#### View or change the retransmit delay factor for flood traffic
**Usage:**
- `get txdelay`
Expand Down
11 changes: 11 additions & 0 deletions docs/mu.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ This document provides an overview of CLI commands which are specific to the MU
- [Wifi Companion Configuration via Rescue CLI](#wifi-companion-configuration-via-rescue-cli)
- [Allow repeaters to restrict Flooded REQ, ANON_REQ, and RESPONSE packets](#allow-repeaters-to-restrict-flooded-req-anon_req-and-response-packets)
- [Channel Content Filter](#channel-content-filter)
- [View or change the maximum direct-route resend attempts](#view-or-change-the-maximum-direct-route-resend-attempts)

### Automatic reboot
**Usage:**
Expand Down Expand Up @@ -162,3 +163,13 @@ Repeater only. Lets a repeater decrypt channels it holds the key for, inspect th
**Note:** `filter clear` empties the keyword and sender lists but keeps the loaded channel keys. `filter reset` wipes everything — channel keys, keywords and senders.

---
### View or change the maximum direct-route resend attempts

**Usage:**
- `get max.resend`
- `set max.resend <value>`

**Parameters:**
- `value`: Maximum number of resend attempts for direct-routed packets (0–3). `0` disables resending entirely.

**Default:** `0`
2 changes: 2 additions & 0 deletions examples/companion_radio/DataStore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no
file.read((uint8_t *)_prefs.default_scope_name, sizeof(_prefs.default_scope_name)); // 90
file.read((uint8_t *)_prefs.default_scope_key, sizeof(_prefs.default_scope_key)); // 121
file.read((uint8_t *)&_prefs.radio_fem_rxgain, sizeof(_prefs.radio_fem_rxgain)); // 122
file.read((uint8_t *)&_prefs.max_resend_attempts, sizeof(_prefs.max_resend_attempts)); // 137

file.close();
}
Expand Down Expand Up @@ -275,6 +276,7 @@ void DataStore::savePrefs(const NodePrefs& _prefs, double node_lat, double node_
file.write((uint8_t *)_prefs.default_scope_name, sizeof(_prefs.default_scope_name)); // 90
file.write((uint8_t *)_prefs.default_scope_key, sizeof(_prefs.default_scope_key)); // 121
file.write((uint8_t *)&_prefs.radio_fem_rxgain, sizeof(_prefs.radio_fem_rxgain)); // 122
file.write((uint8_t *)&_prefs.max_resend_attempts, sizeof(_prefs.max_resend_attempts)); // 137

file.close();
}
Expand Down
19 changes: 19 additions & 0 deletions examples/companion_radio/MyMesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,10 @@
#define DIRECT_SEND_PERHOP_EXTRA_MILLIS 250
#define LAZY_CONTACTS_WRITE_DELAY 5000

#ifndef RESEND_INTERFERENCE_MARGIN
#define RESEND_INTERFERENCE_MARGIN 12 // dB above noise floor that blocks a resend (non-invasive LBT)
#endif

#define PUBLIC_GROUP_PSK "izOH6cXN6mrJ5e26oRXNcg=="

// these are _pushed_ to client app at any time
Expand Down Expand Up @@ -267,6 +271,15 @@ bool MyMesh::getCADEnabled() const {
return true; // hardware CAD before TX (no CLI toggle on companion; enabled by default)
}

bool MyMesh::isResendChannelActive() {
// Non-invasive resend LBT (NO CAD, so RX stays open to overhear the downstream forward
// and cancel the resend). Block the resend when a LoRa preamble/header is being received
// (often that forward itself) OR the live channel energy is well above the noise floor
// (foreign interference). isReceivingPacket()/getCurrentRSSI() are IRQ/register reads.
int margin = (int)radio_driver.getCurrentRSSI() - _radio->getNoiseFloor();
return radio_driver.isReceivingPacket() || (margin >= RESEND_INTERFERENCE_MARGIN);
}

int MyMesh::calcRxDelay(float score, uint32_t air_time) const {
if (_prefs.rx_delay_base <= 0.0f) return 0;
return (int)((pow(_prefs.rx_delay_base, 0.85f - score) - 1.0) * air_time);
Expand All @@ -276,6 +289,7 @@ uint32_t MyMesh::getRetransmitDelay(const mesh::Packet *packet) {
uint32_t t = (_radio->getEstAirtimeFor(packet->getPathByteLen() + packet->payload_len + 2) * 0.5f);
return getRNG()->nextInt(0, 5*t + 1);
}

uint32_t MyMesh::getDirectRetransmitDelay(const mesh::Packet *packet) {
uint32_t t = (_radio->getEstAirtimeFor(packet->getPathByteLen() + packet->payload_len + 2) * 0.2f);
return getRNG()->nextInt(0, 5*t + 1);
Expand Down Expand Up @@ -883,6 +897,7 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe
_prefs.tx_power_dbm = LORA_TX_POWER;
_prefs.gps_enabled = 0; // GPS disabled by default
_prefs.gps_interval = 0; // No automatic GPS updates by default
_prefs.max_resend_attempts = 2;
//_prefs.rx_delay_base = 10.0f; enable once new algo fixed
#if defined(USE_SX1262) || defined(USE_SX1268)
#ifdef SX126X_RX_BOOSTED_GAIN
Expand Down Expand Up @@ -942,6 +957,7 @@ void MyMesh::begin(bool has_display) {
_prefs.gps_enabled = constrain(_prefs.gps_enabled, 0, 1); // Ensure boolean 0 or 1
_prefs.gps_interval = constrain(_prefs.gps_interval, 0, 86400); // Max 24 hours
_prefs.radio_fem_rxgain = constrain(_prefs.radio_fem_rxgain, 0, 1);
_prefs.max_resend_attempts = constrain(_prefs.max_resend_attempts, 0, 3);

#ifdef BLE_PIN_CODE // 123456 by default
if (_prefs.ble_pin == 0) {
Expand Down Expand Up @@ -1440,6 +1456,9 @@ void MyMesh::handleCmdFrame(size_t len) {
_prefs.advert_loc_policy = cmd_frame[3];
if (len >= 5) {
_prefs.multi_acks = cmd_frame[4];
if (len >= 6) {
_prefs.max_resend_attempts = constrain(cmd_frame[5], 0, 3);
}
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions examples/companion_radio/MyMesh.h
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,12 @@ class MyMesh : public BaseChatMesh, public DataStoreHost {
float getAirtimeBudgetFactor() const override;
int getInterferenceThreshold() const override;
bool getCADEnabled() const override;
bool isResendChannelActive() 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;
uint8_t getExtraAckTransmitCount() const override;
uint8_t getMaxResendAttempts() const override { return _prefs.max_resend_attempts; }
bool filterRecvFloodPacket(mesh::Packet* packet) override;
bool allowPacketForward(const mesh::Packet* packet) override;

Expand Down
1 change: 1 addition & 0 deletions examples/companion_radio/NodePrefs.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,5 @@ struct NodePrefs { // persisted to file
uint8_t autoadd_max_hops; // 0 = no limit, 1 = direct (0 hops), N = up to N-1 hops (max 64)
char default_scope_name[31];
uint8_t default_scope_key[16];
uint8_t max_resend_attempts; // 0 = disabled, 1-3, default 2
};
13 changes: 13 additions & 0 deletions examples/simple_repeater/MyMesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@ static bool isHexKey(const char* s) {

#define LAZY_CONTACTS_WRITE_DELAY 5000

#ifndef RESEND_INTERFERENCE_MARGIN
#define RESEND_INTERFERENCE_MARGIN 12 // dB above noise floor that blocks a resend (non-invasive LBT)
#endif

void MyMesh::putNeighbour(const mesh::Identity &id, uint32_t timestamp, float snr) {
#if MAX_NEIGHBOURS // check if neighbours enabled
// find existing neighbour, else use least recently updated
Expand Down Expand Up @@ -601,6 +605,14 @@ uint32_t MyMesh::getDirectRetransmitDelay(const mesh::Packet *packet) {
return getRNG()->nextInt(0, 5*t + 1);
}

bool MyMesh::isResendChannelActive() {
// Non-invasive resend LBT (NO CAD, so RX stays open to overhear the downstream forward
// and cancel the resend). Block when a LoRa preamble/header is being received (often that
// forward) OR the live channel energy is well above the noise floor (foreign interference).
int margin = (int)radio_driver.getCurrentRSSI() - _radio->getNoiseFloor();
return radio_driver.isReceivingPacket() || (margin >= RESEND_INTERFERENCE_MARGIN);
}

mesh::DispatcherAction MyMesh::onRecvPacket(mesh::Packet* pkt) {
if (pkt->getRouteType() == ROUTE_TYPE_TRANSPORT_FLOOD) {
recv_pkt_region = region_map.findMatch(pkt, REGION_DENY_FLOOD);
Expand Down Expand Up @@ -1251,6 +1263,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
_prefs.rx_delay_base = 3.0f;
_prefs.tx_delay_factor = 0.3f; // was 0.5f
_prefs.direct_tx_delay_factor = 0.1f; // was 0.3f
_prefs.max_resend_attempts = 0; // Default: OFF for compatibility reasons
StrHelper::strncpy(_prefs.node_name, ADVERT_NAME, sizeof(_prefs.node_name));
_prefs.node_lat = ADVERT_LAT;
_prefs.node_lon = ADVERT_LON;
Expand Down
4 changes: 4 additions & 0 deletions examples/simple_repeater/MyMesh.h
Original file line number Diff line number Diff line change
Expand Up @@ -184,12 +184,16 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
bool getCADEnabled() const override {
return _prefs.cad_enabled;
}
bool isResendChannelActive() override; // non-invasive resend LBT (preamble/RSSI, no CAD)
int getAGCResetInterval() const override {
return ((int)_prefs.agc_reset_interval) * 4000; // milliseconds
}
uint8_t getExtraAckTransmitCount() const override {
return _prefs.multi_acks;
}
uint8_t getMaxResendAttempts() const override {
return _prefs.max_resend_attempts;
}

#if ENV_INCLUDE_GPS == 1
void applyGpsPrefs() {
Expand Down
13 changes: 13 additions & 0 deletions examples/simple_room_server/MyMesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@

#define POST_SYNC_DELAY_SECS 6

#ifndef RESEND_INTERFERENCE_MARGIN
#define RESEND_INTERFERENCE_MARGIN 12 // dB above noise floor that blocks a resend (non-invasive LBT)
#endif

#define FIRMWARE_VER_LEVEL 1

#define REQ_TYPE_GET_STATUS 0x01 // same as _GET_STATS
Expand Down Expand Up @@ -280,6 +284,14 @@ uint32_t MyMesh::getDirectRetransmitDelay(const mesh::Packet *packet) {
return getRNG()->nextInt(0, 5*t + 1);
}

bool MyMesh::isResendChannelActive() {
// Non-invasive resend LBT (NO CAD, so RX stays open to overhear the downstream forward
// and cancel the resend). Block when a LoRa preamble/header is being received (often that
// forward) OR the live channel energy is well above the noise floor (foreign interference).
int margin = (int)radio_driver.getCurrentRSSI() - _radio->getNoiseFloor();
return radio_driver.isReceivingPacket() || (margin >= RESEND_INTERFERENCE_MARGIN);
}

bool MyMesh::allowPacketForward(const mesh::Packet *packet) {
if (_prefs.disable_fwd) return false;
if (packet->isRouteFlood()) {
Expand Down Expand Up @@ -637,6 +649,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
_prefs.rx_delay_base = 0.0f; // off by default, was 10.0
_prefs.tx_delay_factor = 0.5f; // was 0.25f;
_prefs.direct_tx_delay_factor = 0.2f; // was zero
_prefs.max_resend_attempts = 2;
StrHelper::strncpy(_prefs.node_name, ADVERT_NAME, sizeof(_prefs.node_name));
_prefs.node_lat = ADVERT_LAT;
_prefs.node_lon = ADVERT_LON;
Expand Down
4 changes: 4 additions & 0 deletions examples/simple_room_server/MyMesh.h
Original file line number Diff line number Diff line change
Expand Up @@ -147,12 +147,16 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
bool getCADEnabled() const override {
return _prefs.cad_enabled;
}
bool isResendChannelActive() override; // non-invasive resend LBT (preamble/RSSI, no CAD)
int getAGCResetInterval() const override {
return ((int)_prefs.agc_reset_interval) * 4000; // milliseconds
}
uint8_t getExtraAckTransmitCount() const override {
return _prefs.multi_acks;
}
uint8_t getMaxResendAttempts() const override {
return _prefs.max_resend_attempts;
}

mesh::DispatcherAction onRecvPacket(mesh::Packet* pkt) override;

Expand Down
1 change: 1 addition & 0 deletions examples/simple_sensor/SensorMesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -715,6 +715,7 @@ SensorMesh::SensorMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::Millise
_prefs.rx_delay_base = 0.0f; // turn off by default, was 10.0;
_prefs.tx_delay_factor = 0.5f; // was 0.25f
_prefs.direct_tx_delay_factor = 0.2f; // was zero
_prefs.max_resend_attempts = 2;
StrHelper::strncpy(_prefs.node_name, ADVERT_NAME, sizeof(_prefs.node_name));
_prefs.node_lat = ADVERT_LAT;
_prefs.node_lon = ADVERT_LON;
Expand Down
1 change: 1 addition & 0 deletions examples/simple_sensor/SensorMesh.h
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ class SensorMesh : public mesh::Mesh, public CommonCLICallbacks {
int getInterferenceThreshold() const override;
bool getCADEnabled() const override;
int getAGCResetInterval() const override;
uint8_t getMaxResendAttempts() const override { return _prefs.max_resend_attempts; }
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;
void getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) override;
Expand Down
Loading