Skip to content

Commit 90fc0ee

Browse files
committed
Add SNR-driven dynamic coding rate selection for repeater retransmits
When a repeater forwards a direct-routed pkt, it looks up the next-hop neighbor's SNR, computes the link margin against the SF-specific demodulation floor, then determines coding rate (RT) based on SNR. Weaker links get higher CR. We apply CR on a per-packet basis and restore to default configuration after TX. Margin thresholds: <3 dB - CR 4/8 3-6 dB - CR 4/7 6-10 dB - CR 4/6 >=10 dB - CR 4/5 Flood repeats adhere to configured CR, as do forwards to unknown neighbors.
1 parent d2c2a6e commit 90fc0ee

14 files changed

Lines changed: 87 additions & 2 deletions

examples/simple_repeater/MyMesh.cpp

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "MyMesh.h"
22
#include <algorithm>
3+
#include <climits>
34

45
/* ------------------------------ Config -------------------------------- */
56

@@ -87,6 +88,42 @@ void MyMesh::putNeighbour(const mesh::Identity &id, uint32_t timestamp, float sn
8788
#endif
8889
}
8990

91+
int8_t MyMesh::findNeighbourSNR(const uint8_t* hash, uint8_t hash_size) {
92+
#if MAX_NEIGHBOURS
93+
for (int i = 0; i < MAX_NEIGHBOURS; i++) {
94+
if (neighbours[i].heard_timestamp != 0 && neighbours[i].id.isHashMatch(hash, hash_size)) {
95+
return neighbours[i].snr;
96+
}
97+
}
98+
#endif
99+
return INT8_MAX;
100+
}
101+
102+
// Approximate SNR demod floor per SF (same as RadioLibWrappers.cpp)
103+
static float cr_snr_thresholds[] = {
104+
-7.5f, // SF7
105+
-10.0f, // SF8
106+
-12.5f, // SF9
107+
-15.0f, // SF10
108+
-17.5f, // SF11
109+
-20.0f // SF12
110+
};
111+
112+
uint8_t MyMesh::selectCodingRateForPeer(const uint8_t* hash, uint8_t hash_size) {
113+
int8_t snr4 = findNeighbourSNR(hash, hash_size);
114+
if (snr4 == INT8_MAX) return 0; // unknown neighbor, use default
115+
116+
float snr = snr4 / 4.0f;
117+
float threshold = (_prefs.sf >= 7 && _prefs.sf <= 12)
118+
? cr_snr_thresholds[_prefs.sf - 7] : -15.0f;
119+
float margin = snr - threshold;
120+
121+
if (margin < 3.0f) return 8;
122+
if (margin < 6.0f) return 7;
123+
if (margin < 10.0f) return 6;
124+
return 5; // good margin, use lightest CR
125+
}
126+
90127
uint8_t MyMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data, bool is_flood) {
91128
ClientInfo* client = NULL;
92129
if (data[0] == 0) { // blank password, just check if sender is in ACL
@@ -955,6 +992,7 @@ void MyMesh::begin(FILESYSTEM *fs) {
955992

956993
radio_driver.setParams(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
957994
radio_driver.setTxPower(_prefs.tx_power_dbm);
995+
setDefaultCR(_prefs.cr);
958996

959997
radio_driver.setRxBoostedGainMode(_prefs.rx_boosted_gain);
960998
MESH_DEBUG_PRINTLN("RX Boosted Gain Mode: %s",

examples/simple_repeater/MyMesh.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
120120
#endif
121121

122122
void putNeighbour(const mesh::Identity& id, uint32_t timestamp, float snr);
123+
int8_t findNeighbourSNR(const uint8_t* hash, uint8_t hash_size);
123124
uint8_t handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data, bool is_flood);
124125
uint8_t handleAnonRegionsReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data);
125126
uint8_t handleAnonOwnerReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data);
@@ -156,6 +157,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
156157
uint8_t getExtraAckTransmitCount() const override {
157158
return _prefs.multi_acks;
158159
}
160+
uint8_t selectCodingRateForPeer(const uint8_t* hash, uint8_t hash_size) override;
159161

160162
#if ENV_INCLUDE_GPS == 1
161163
void applyGpsPrefs() {

src/Dispatcher.cpp

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,9 @@ void Dispatcher::loop() {
105105
}
106106

107107
_radio->onSendFinished();
108+
if (outbound->_tx_cr != 0 && outbound->_tx_cr != _default_cr) {
109+
_radio->setCodingRate(_default_cr);
110+
}
108111
logTx(outbound, 2 + outbound->getPathByteLen() + outbound->payload_len);
109112
if (outbound->isRouteFlood()) {
110113
n_sent_flood++;
@@ -117,6 +120,9 @@ void Dispatcher::loop() {
117120
MESH_DEBUG_PRINTLN("%s Dispatcher::loop(): WARNING: outbound packed send timed out!", getLogDateTime());
118121

119122
_radio->onSendFinished();
123+
if (outbound->_tx_cr != 0 && outbound->_tx_cr != _default_cr) {
124+
_radio->setCodingRate(_default_cr);
125+
}
120126
logTxFail(outbound, 2 + outbound->getPathByteLen() + outbound->payload_len);
121127

122128
releasePacket(outbound); // return to pool
@@ -323,14 +329,21 @@ void Dispatcher::checkSend() {
323329
} else {
324330
memcpy(&raw[len], outbound->payload, outbound->payload_len); len += outbound->payload_len;
325331

332+
if (outbound->_tx_cr != 0 && outbound->_tx_cr != _default_cr) {
333+
_radio->setCodingRate(outbound->_tx_cr);
334+
}
335+
326336
uint32_t max_airtime = _radio->getEstAirtimeFor(len)*3/2;
327337
outbound_start = _ms->getMillis();
328338
bool success = _radio->startSendRaw(raw, len);
329339
if (!success) {
330340
MESH_DEBUG_PRINTLN("%s Dispatcher::loop(): ERROR: send start failed!", getLogDateTime());
341+
if (outbound->_tx_cr != 0 && outbound->_tx_cr != _default_cr) {
342+
_radio->setCodingRate(_default_cr);
343+
}
331344

332345
logTxFail(outbound, outbound->getRawLength());
333-
346+
334347
releasePacket(outbound); // return to pool
335348
outbound = NULL;
336349
return;
@@ -359,6 +372,7 @@ Packet* Dispatcher::obtainNewPacket() {
359372
} else {
360373
pkt->payload_len = pkt->path_len = 0;
361374
pkt->_snr = 0;
375+
pkt->_tx_cr = 0;
362376
}
363377
return pkt;
364378
}

src/Dispatcher.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ class Radio {
7474
*/
7575
virtual bool isReceiving() { return false; }
7676

77+
virtual void setCodingRate(uint8_t cr) { }
78+
7779
virtual float getLastRSSI() const { return 0; }
7880
virtual float getLastSNR() const { return 0; }
7981
};
@@ -136,6 +138,8 @@ class Dispatcher {
136138
MillisecondClock* _ms;
137139
uint16_t _err_flags;
138140

141+
uint8_t _default_cr;
142+
139143
Dispatcher(Radio& radio, MillisecondClock& ms, PacketManager& mgr)
140144
: _radio(&radio), _ms(&ms), _mgr(&mgr)
141145
{
@@ -150,6 +154,7 @@ class Dispatcher {
150154
tx_budget_ms = 0;
151155
last_budget_update = 0;
152156
duty_cycle_window_ms = 3600000;
157+
_default_cr = 5;
153158
}
154159

155160
virtual DispatcherAction onRecvPacket(Packet* pkt) = 0;
@@ -184,6 +189,8 @@ class Dispatcher {
184189
uint32_t getNumSentDirect() const { return n_sent_direct; }
185190
uint32_t getNumRecvFlood() const { return n_recv_flood; }
186191
uint32_t getNumRecvDirect() const { return n_recv_direct; }
192+
void setDefaultCR(uint8_t cr) { _default_cr = cr; }
193+
187194
void resetStats() {
188195
n_sent_flood = n_sent_direct = n_recv_flood = n_recv_direct = 0;
189196
_err_flags = 0;

src/Mesh.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,10 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
9696

9797
if (!_tables->hasSeen(pkt)) {
9898
removeSelfFromPath(pkt);
99+
pkt->_tx_cr = selectCodingRateForPeer(pkt->path, pkt->getPathHashSize());
99100

100101
uint32_t d = getDirectRetransmitDelay(pkt);
101-
return ACTION_RETRANSMIT_DELAYED(0, d); // Routed traffic is HIGHEST priority
102+
return ACTION_RETRANSMIT_DELAYED(0, d); // Routed traffic is HIGHEST priority
102103
}
103104
}
104105
return ACTION_RELEASE; // this node is NOT the next hop (OR this packet has already been forwarded), so discard.

src/Mesh.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,14 @@ class Mesh : public Dispatcher {
7070
*/
7171
virtual uint8_t getExtraAckTransmitCount() const;
7272

73+
/**
74+
* \brief Select a coding rate for the next-hop peer based on link quality.
75+
* \param hash the hash of the next-hop peer (from path[0..hash_size-1])
76+
* \param hash_size bytes per hash entry
77+
* \returns 0 = use node default CR, 5-8 = override CR for this packet
78+
*/
79+
virtual uint8_t selectCodingRateForPeer(const uint8_t* hash, uint8_t hash_size) { return 0; }
80+
7381
/**
7482
* \brief Perform search of local DB of peers/contacts.
7583
* \returns Number of peers with matching hash

src/Packet.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ Packet::Packet() {
88
header = 0;
99
path_len = 0;
1010
payload_len = 0;
11+
_snr = 0;
12+
_tx_cr = 0;
1113
}
1214

1315
bool Packet::isValidPathLen(uint8_t path_len) {

src/Packet.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ class Packet {
4949
uint8_t path[MAX_PATH_SIZE];
5050
uint8_t payload[MAX_PACKET_PAYLOAD];
5151
int8_t _snr;
52+
uint8_t _tx_cr; // 0 = use node default, 5-8 = override CR for this TX (not serialized to wire)
5253

5354
/**
5455
* \brief calculate the hash of payload + type

src/helpers/radiolib/CustomLLCC68Wrapper.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ class CustomLLCC68Wrapper : public RadioLibWrapper {
2525
float getLastRSSI() const override { return ((CustomLLCC68 *)_radio)->getRSSI(); }
2626
float getLastSNR() const override { return ((CustomLLCC68 *)_radio)->getSNR(); }
2727

28+
void setCodingRate(uint8_t cr) override { ((CustomLLCC68 *)_radio)->setCodingRate(cr); }
29+
2830
float packetScore(float snr, int packet_len) override {
2931
int sf = ((CustomLLCC68 *)_radio)->spreadingFactor;
3032
return packetScoreInt(snr, sf, packet_len);

src/helpers/radiolib/CustomLR1110Wrapper.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ class CustomLR1110Wrapper : public RadioLibWrapper {
3131
_radio->setPreambleLength(preambleLengthForSF(getSpreadingFactor())); // overcomes weird issues with small and big pkts
3232
}
3333

34+
void setCodingRate(uint8_t cr) override { ((CustomLR1110 *)_radio)->setCodingRate(cr); }
35+
3436
float getLastRSSI() const override { return ((CustomLR1110 *)_radio)->getRSSI(); }
3537
float getLastSNR() const override { return ((CustomLR1110 *)_radio)->getSNR(); }
3638

0 commit comments

Comments
 (0)