diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index c471100b8c..4f4da3d9bc 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -1,5 +1,6 @@ #include "MyMesh.h" #include +#include /* ------------------------------ Config -------------------------------- */ @@ -87,6 +88,42 @@ void MyMesh::putNeighbour(const mesh::Identity &id, uint32_t timestamp, float sn #endif } +int8_t MyMesh::findNeighbourSNR(const uint8_t* hash, uint8_t hash_size) { +#if MAX_NEIGHBOURS + for (int i = 0; i < MAX_NEIGHBOURS; i++) { + if (neighbours[i].heard_timestamp != 0 && neighbours[i].id.isHashMatch(hash, hash_size)) { + return neighbours[i].snr; + } + } +#endif + return INT8_MAX; +} + +// Approximate SNR demod floor per SF (same as RadioLibWrappers.cpp) +static const float cr_snr_thresholds[] = { + -7.5f, // SF7 + -10.0f, // SF8 + -12.5f, // SF9 + -15.0f, // SF10 + -17.5f, // SF11 + -20.0f // SF12 +}; + +uint8_t MyMesh::selectCodingRateForPeer(const uint8_t* hash, uint8_t hash_size) { + int8_t snr4 = findNeighbourSNR(hash, hash_size); + if (snr4 == INT8_MAX) return 0; // unknown neighbor, use default + + float snr = snr4 / 4.0f; + float threshold = (_prefs.sf >= 7 && _prefs.sf <= 12) + ? cr_snr_thresholds[_prefs.sf - 7] : -15.0f; + float margin = snr - threshold; + + if (margin < 3.0f) return 8; + if (margin < 6.0f) return 7; + if (margin < 10.0f) return 6; + return 5; // good margin, use lightest CR +} + uint8_t MyMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data, bool is_flood) { ClientInfo* client = NULL; if (data[0] == 0) { // blank password, just check if sender is in ACL @@ -910,6 +947,7 @@ void MyMesh::begin(FILESYSTEM *fs) { radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr); radio_set_tx_power(_prefs.tx_power_dbm); + setDefaultCR(_prefs.cr); updateAdvertTimer(); updateFloodAdvertTimer(); diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 8857a1f71b..7b1d62702f 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -119,6 +119,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { #endif void putNeighbour(const mesh::Identity& id, uint32_t timestamp, float snr); + int8_t findNeighbourSNR(const uint8_t* hash, uint8_t hash_size); void sendNodeDiscoverReq(); uint8_t handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data, bool is_flood); uint8_t handleAnonRegionsReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data); @@ -156,6 +157,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { uint8_t getExtraAckTransmitCount() const override { return _prefs.multi_acks; } + uint8_t selectCodingRateForPeer(const uint8_t* hash, uint8_t hash_size) override; #if ENV_INCLUDE_GPS == 1 void applyGpsPrefs() { diff --git a/src/Dispatcher.cpp b/src/Dispatcher.cpp index 9d7a11131d..8ff881f477 100644 --- a/src/Dispatcher.cpp +++ b/src/Dispatcher.cpp @@ -105,6 +105,9 @@ void Dispatcher::loop() { } _radio->onSendFinished(); + if (outbound->_tx_cr != 0 && outbound->_tx_cr != _default_cr) { + _radio->setCodingRate(_default_cr); + } logTx(outbound, 2 + outbound->getPathByteLen() + outbound->payload_len); if (outbound->isRouteFlood()) { n_sent_flood++; @@ -117,6 +120,9 @@ void Dispatcher::loop() { MESH_DEBUG_PRINTLN("%s Dispatcher::loop(): WARNING: outbound packed send timed out!", getLogDateTime()); _radio->onSendFinished(); + if (outbound->_tx_cr != 0 && outbound->_tx_cr != _default_cr) { + _radio->setCodingRate(_default_cr); + } logTxFail(outbound, 2 + outbound->getPathByteLen() + outbound->payload_len); releasePacket(outbound); // return to pool @@ -323,14 +329,21 @@ void Dispatcher::checkSend() { } else { memcpy(&raw[len], outbound->payload, outbound->payload_len); len += outbound->payload_len; + if (outbound->_tx_cr != 0 && outbound->_tx_cr != _default_cr) { + _radio->setCodingRate(outbound->_tx_cr); + } + uint32_t max_airtime = _radio->getEstAirtimeFor(len)*3/2; outbound_start = _ms->getMillis(); bool success = _radio->startSendRaw(raw, len); if (!success) { MESH_DEBUG_PRINTLN("%s Dispatcher::loop(): ERROR: send start failed!", getLogDateTime()); + if (outbound->_tx_cr != 0 && outbound->_tx_cr != _default_cr) { + _radio->setCodingRate(_default_cr); + } logTxFail(outbound, outbound->getRawLength()); - + releasePacket(outbound); // return to pool outbound = NULL; return; @@ -359,6 +372,7 @@ Packet* Dispatcher::obtainNewPacket() { } else { pkt->payload_len = pkt->path_len = 0; pkt->_snr = 0; + pkt->_tx_cr = 0; } return pkt; } diff --git a/src/Dispatcher.h b/src/Dispatcher.h index 2a99d0682b..480651ac5e 100644 --- a/src/Dispatcher.h +++ b/src/Dispatcher.h @@ -74,6 +74,8 @@ class Radio { */ virtual bool isReceiving() { return false; } + virtual void setCodingRate(uint8_t cr) { } + virtual float getLastRSSI() const { return 0; } virtual float getLastSNR() const { return 0; } }; @@ -136,6 +138,8 @@ class Dispatcher { MillisecondClock* _ms; uint16_t _err_flags; + uint8_t _default_cr; + Dispatcher(Radio& radio, MillisecondClock& ms, PacketManager& mgr) : _radio(&radio), _ms(&ms), _mgr(&mgr) { @@ -150,6 +154,7 @@ class Dispatcher { tx_budget_ms = 0; last_budget_update = 0; duty_cycle_window_ms = 3600000; + _default_cr = 5; } virtual DispatcherAction onRecvPacket(Packet* pkt) = 0; @@ -184,6 +189,8 @@ class Dispatcher { uint32_t getNumSentDirect() const { return n_sent_direct; } uint32_t getNumRecvFlood() const { return n_recv_flood; } uint32_t getNumRecvDirect() const { return n_recv_direct; } + void setDefaultCR(uint8_t cr) { _default_cr = cr; } + void resetStats() { n_sent_flood = n_sent_direct = n_recv_flood = n_recv_direct = 0; _err_flags = 0; diff --git a/src/Mesh.cpp b/src/Mesh.cpp index 57fee14036..38dd3d8157 100644 --- a/src/Mesh.cpp +++ b/src/Mesh.cpp @@ -96,9 +96,10 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { if (!_tables->hasSeen(pkt)) { removeSelfFromPath(pkt); + pkt->_tx_cr = selectCodingRateForPeer(pkt->path, pkt->getPathHashSize()); uint32_t d = getDirectRetransmitDelay(pkt); - return ACTION_RETRANSMIT_DELAYED(0, d); // Routed traffic is HIGHEST priority + return ACTION_RETRANSMIT_DELAYED(0, d); // Routed traffic is HIGHEST priority } } return ACTION_RELEASE; // this node is NOT the next hop (OR this packet has already been forwarded), so discard. diff --git a/src/Mesh.h b/src/Mesh.h index f9f8786320..14c06bc02d 100644 --- a/src/Mesh.h +++ b/src/Mesh.h @@ -70,6 +70,14 @@ class Mesh : public Dispatcher { */ virtual uint8_t getExtraAckTransmitCount() const; + /** + * \brief Select a coding rate for the next-hop peer based on link quality. + * \param hash the hash of the next-hop peer (from path[0..hash_size-1]) + * \param hash_size bytes per hash entry + * \returns 0 = use node default CR, 5-8 = override CR for this packet + */ + virtual uint8_t selectCodingRateForPeer(const uint8_t* hash, uint8_t hash_size) { return 0; } + /** * \brief Perform search of local DB of peers/contacts. * \returns Number of peers with matching hash diff --git a/src/Packet.cpp b/src/Packet.cpp index aad3e2f48e..43f8c3a023 100644 --- a/src/Packet.cpp +++ b/src/Packet.cpp @@ -8,6 +8,8 @@ Packet::Packet() { header = 0; path_len = 0; payload_len = 0; + _snr = 0; + _tx_cr = 0; } bool Packet::isValidPathLen(uint8_t path_len) { diff --git a/src/Packet.h b/src/Packet.h index 7861954618..228d014502 100644 --- a/src/Packet.h +++ b/src/Packet.h @@ -49,6 +49,7 @@ class Packet { uint8_t path[MAX_PATH_SIZE]; uint8_t payload[MAX_PACKET_PAYLOAD]; int8_t _snr; + uint8_t _tx_cr; // 0 = use node default, 5-8 = override CR for this TX (not serialized to wire) /** * \brief calculate the hash of payload + type diff --git a/src/helpers/radiolib/CustomLLCC68Wrapper.h b/src/helpers/radiolib/CustomLLCC68Wrapper.h index 9e783a955b..6367b64740 100644 --- a/src/helpers/radiolib/CustomLLCC68Wrapper.h +++ b/src/helpers/radiolib/CustomLLCC68Wrapper.h @@ -16,6 +16,8 @@ class CustomLLCC68Wrapper : public RadioLibWrapper { float getLastRSSI() const override { return ((CustomLLCC68 *)_radio)->getRSSI(); } float getLastSNR() const override { return ((CustomLLCC68 *)_radio)->getSNR(); } + void setCodingRate(uint8_t cr) override { ((CustomLLCC68 *)_radio)->setCodingRate(cr); } + float packetScore(float snr, int packet_len) override { int sf = ((CustomLLCC68 *)_radio)->spreadingFactor; return packetScoreInt(snr, sf, packet_len); diff --git a/src/helpers/radiolib/CustomLR1110Wrapper.h b/src/helpers/radiolib/CustomLR1110Wrapper.h index a1e0a49370..51b9d99aad 100644 --- a/src/helpers/radiolib/CustomLR1110Wrapper.h +++ b/src/helpers/radiolib/CustomLR1110Wrapper.h @@ -22,6 +22,8 @@ class CustomLR1110Wrapper : public RadioLibWrapper { _radio->setPreambleLength(16); // overcomes weird issues with small and big pkts } + void setCodingRate(uint8_t cr) override { ((CustomLR1110 *)_radio)->setCodingRate(cr); } + float getLastRSSI() const override { return ((CustomLR1110 *)_radio)->getRSSI(); } float getLastSNR() const override { return ((CustomLR1110 *)_radio)->getSNR(); } int16_t setRxBoostedGainMode(bool en) { return ((CustomLR1110 *)_radio)->setRxBoostedGainMode(en); }; diff --git a/src/helpers/radiolib/CustomSTM32WLxWrapper.h b/src/helpers/radiolib/CustomSTM32WLxWrapper.h index e3e5202949..6cf84e3d02 100644 --- a/src/helpers/radiolib/CustomSTM32WLxWrapper.h +++ b/src/helpers/radiolib/CustomSTM32WLxWrapper.h @@ -17,6 +17,8 @@ class CustomSTM32WLxWrapper : public RadioLibWrapper { float getLastRSSI() const override { return ((CustomSTM32WLx *)_radio)->getRSSI(); } float getLastSNR() const override { return ((CustomSTM32WLx *)_radio)->getSNR(); } + void setCodingRate(uint8_t cr) override { ((CustomSTM32WLx *)_radio)->setCodingRate(cr); } + float packetScore(float snr, int packet_len) override { int sf = ((CustomSTM32WLx *)_radio)->spreadingFactor; return packetScoreInt(snr, sf, packet_len); diff --git a/src/helpers/radiolib/CustomSX1262Wrapper.h b/src/helpers/radiolib/CustomSX1262Wrapper.h index 5856720b9e..e3a798fa09 100644 --- a/src/helpers/radiolib/CustomSX1262Wrapper.h +++ b/src/helpers/radiolib/CustomSX1262Wrapper.h @@ -16,6 +16,8 @@ class CustomSX1262Wrapper : public RadioLibWrapper { float getLastRSSI() const override { return ((CustomSX1262 *)_radio)->getRSSI(); } float getLastSNR() const override { return ((CustomSX1262 *)_radio)->getSNR(); } + void setCodingRate(uint8_t cr) override { ((CustomSX1262 *)_radio)->setCodingRate(cr); } + float packetScore(float snr, int packet_len) override { int sf = ((CustomSX1262 *)_radio)->spreadingFactor; return packetScoreInt(snr, sf, packet_len); diff --git a/src/helpers/radiolib/CustomSX1268Wrapper.h b/src/helpers/radiolib/CustomSX1268Wrapper.h index 5149fc431f..a58b940ee1 100644 --- a/src/helpers/radiolib/CustomSX1268Wrapper.h +++ b/src/helpers/radiolib/CustomSX1268Wrapper.h @@ -16,6 +16,8 @@ class CustomSX1268Wrapper : public RadioLibWrapper { float getLastRSSI() const override { return ((CustomSX1268 *)_radio)->getRSSI(); } float getLastSNR() const override { return ((CustomSX1268 *)_radio)->getSNR(); } + void setCodingRate(uint8_t cr) override { ((CustomSX1268 *)_radio)->setCodingRate(cr); } + float packetScore(float snr, int packet_len) override { int sf = ((CustomSX1268 *)_radio)->spreadingFactor; return packetScoreInt(snr, sf, packet_len); diff --git a/src/helpers/radiolib/CustomSX1276Wrapper.h b/src/helpers/radiolib/CustomSX1276Wrapper.h index 282579906a..3d34d0d262 100644 --- a/src/helpers/radiolib/CustomSX1276Wrapper.h +++ b/src/helpers/radiolib/CustomSX1276Wrapper.h @@ -15,6 +15,8 @@ class CustomSX1276Wrapper : public RadioLibWrapper { float getLastRSSI() const override { return ((CustomSX1276 *)_radio)->getRSSI(); } float getLastSNR() const override { return ((CustomSX1276 *)_radio)->getSNR(); } + void setCodingRate(uint8_t cr) override { ((CustomSX1276 *)_radio)->setCodingRate(cr); } + float packetScore(float snr, int packet_len) override { int sf = ((CustomSX1276 *)_radio)->spreadingFactor; return packetScoreInt(snr, sf, packet_len);