From 616c7d7b0ec491ac75ba44b802611521968f4ccc Mon Sep 17 00:00:00 2001 From: GUVWAF Date: Wed, 20 Apr 2022 19:58:52 +0200 Subject: [PATCH 1/4] Expose front() function in MeshPacketQueue --- src/mesh/MeshPacketQueue.cpp | 10 ++++++++++ src/mesh/MeshPacketQueue.h | 2 ++ 2 files changed, 12 insertions(+) diff --git a/src/mesh/MeshPacketQueue.cpp b/src/mesh/MeshPacketQueue.cpp index 87347f309..51af89df8 100644 --- a/src/mesh/MeshPacketQueue.cpp +++ b/src/mesh/MeshPacketQueue.cpp @@ -72,6 +72,16 @@ MeshPacket *MeshPacketQueue::dequeue() return p; } +MeshPacket *MeshPacketQueue::getFront() +{ + if (empty()) { + return NULL; + } + + auto *p = queue.front(); + return p; +} + /** Attempt to find and remove a packet from this queue. Returns a pointer to the removed packet, or NULL if not found */ MeshPacket *MeshPacketQueue::remove(NodeNum from, PacketId id) { diff --git a/src/mesh/MeshPacketQueue.h b/src/mesh/MeshPacketQueue.h index c74addf4e..8c93b452e 100644 --- a/src/mesh/MeshPacketQueue.h +++ b/src/mesh/MeshPacketQueue.h @@ -28,6 +28,8 @@ class MeshPacketQueue MeshPacket *dequeue(); + MeshPacket *getFront(); + /** Attempt to find and remove a packet from this queue. Returns the packet which was removed from the queue */ MeshPacket *remove(NodeNum from, PacketId id); }; From 6d01f9aa891b0262fdf64547b3275ab5ce0f093a Mon Sep 17 00:00:00 2001 From: GUVWAF Date: Wed, 20 Apr 2022 20:04:44 +0200 Subject: [PATCH 2/4] Add isChannelActive() function to radio interface --- src/mesh/RF95Interface.cpp | 19 +++++++++++++++++++ src/mesh/RF95Interface.h | 3 +++ src/mesh/SX126xInterface.cpp | 17 +++++++++++++++++ src/mesh/SX126xInterface.h | 3 +++ 4 files changed, 42 insertions(+) diff --git a/src/mesh/RF95Interface.cpp b/src/mesh/RF95Interface.cpp index adaeead46..a96b95100 100644 --- a/src/mesh/RF95Interface.cpp +++ b/src/mesh/RF95Interface.cpp @@ -176,6 +176,25 @@ void RF95Interface::startReceive() enableInterrupt(isrRxLevel0); } +bool RF95Interface::isChannelActive() +{ + // check if we can detect a LoRa preamble on the current channel + int16_t result; + setTransmitEnable(false); + setStandby(); // needed for smooth transition + result = lora->scanChannel(); + + if (result == PREAMBLE_DETECTED) { + // DEBUG_MSG("Channel is busy!\n"); + return true; + } + + assert(result != ERR_WRONG_MODEM); + + // DEBUG_MSG("Channel is free!\n"); + return false; +} + /** Could we send right now (i.e. either not actively receving or transmitting)? */ bool RF95Interface::isActivelyReceiving() { diff --git a/src/mesh/RF95Interface.h b/src/mesh/RF95Interface.h index f62195a26..5e666ae8b 100644 --- a/src/mesh/RF95Interface.h +++ b/src/mesh/RF95Interface.h @@ -40,6 +40,9 @@ class RF95Interface : public RadioLibInterface */ virtual void enableInterrupt(void (*callback)()) { lora->setDio0Action(callback); } + /** can we detect a LoRa preamble on the current channel? */ + virtual bool isChannelActive() override; + /** are we actively receiving a packet (only called during receiving state) */ virtual bool isActivelyReceiving() override; diff --git a/src/mesh/SX126xInterface.cpp b/src/mesh/SX126xInterface.cpp index b0c48d55a..ac96aa70b 100644 --- a/src/mesh/SX126xInterface.cpp +++ b/src/mesh/SX126xInterface.cpp @@ -226,6 +226,23 @@ void SX126xInterface::startReceive() #endif } +/** Could we send right now (i.e. either not actively receving or transmitting)? */ +template +bool SX126xInterface::isChannelActive() +{ + // check if we can detect a LoRa preamble on the current channel + int16_t result; + + setStandby(); + result = lora.scanChannel(); + if (result == PREAMBLE_DETECTED) + return true; + + assert(result != ERR_WRONG_MODEM); + + return false; +} + /** Could we send right now (i.e. either not actively receving or transmitting)? */ template bool SX126xInterface::isActivelyReceiving() diff --git a/src/mesh/SX126xInterface.h b/src/mesh/SX126xInterface.h index 5168313e2..b0e5d9a32 100644 --- a/src/mesh/SX126xInterface.h +++ b/src/mesh/SX126xInterface.h @@ -46,6 +46,9 @@ class SX126xInterface : public RadioLibInterface */ virtual void enableInterrupt(void (*callback)()) { lora.setDio1Action(callback); } + /** can we detect a LoRa preamble on the current channel? */ + virtual bool isChannelActive() override; + /** are we actively receiving a packet (only called during receiving state) */ virtual bool isActivelyReceiving() override; From c60d4c1ecc8fedaec9bb6c23039eecdf3ba5f190 Mon Sep 17 00:00:00 2001 From: GUVWAF Date: Wed, 20 Apr 2022 20:09:12 +0200 Subject: [PATCH 3/4] Implement listen-before-talk mechanism - Function setRandomDelay() calls either startTransmitTimer() or startTransmitTimerSNR() - After coming back from Rx/Tx-ing, call setRandomDelay() - If channel is currently busy, call setRandomDelay() --- src/mesh/RadioLibInterface.cpp | 66 +++++++++++++++++++++------------- src/mesh/RadioLibInterface.h | 17 +++++---- 2 files changed, 49 insertions(+), 34 deletions(-) diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index 5f0e57210..cfadcf644 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -118,20 +118,10 @@ ErrorCode RadioLibInterface::send(MeshPacket *p) return res; } - // We want all sending/receiving to be done by our daemon thread, We use a delay here because this packet might have been sent - // in response to a packet we just received. So we want to make sure the other side has had a chance to reconfigure its radio - - /* We assume if rx_snr = 0 and rx_rssi = 0, the packet was not generated locally. - * This assumption is valid because of the offset generated by the radio to account for the noise - * floor. - */ - if (p->rx_snr == 0 && p->rx_rssi == 0) { - startTransmitTimer(true); - } else { - // If there is a SNR, start a timer scaled based on that SNR. - DEBUG_MSG("rx_snr found. hop_limit:%d rx_snr:%f\n", p->hop_limit, p->rx_snr); - startTransmitTimerSNR(p->rx_snr); - } + // set random transmit delay to let others reconfigure their radio, + // to avoid collisions and implement timing-based flooding + // DEBUG_MSG("Set random delay before transmitting.\n"); + setRandomDelay(); return res; #else @@ -164,8 +154,8 @@ bool RadioLibInterface::cancelSending(NodeNum from, PacketId id) /** radio helper thread callback. We never immediately transmit after any operation (either rx or tx). Instead we should start receiving and -wait a random delay of 50 to 200 ms to make sure we are not stomping on someone else. The 50ms delay at the beginning ensures all -possible listeners have had time to finish processing the previous packet and now have their radio in RX state. The up to 200ms +wait a random delay of 100ms to 100ms+shortPacketMsec to make sure we are not stomping on someone else. The 100ms delay at the beginning ensures all +possible listeners have had time to finish processing the previous packet and now have their radio in RX state. The up to 100ms+shortPacketMsec random delay gives a chance for all possible senders to have high odds of detecting that someone else started transmitting first and then they will wait until that packet finishes. @@ -192,20 +182,26 @@ void RadioLibInterface::onNotify(uint32_t notification) case TRANSMIT_DELAY_COMPLETED: // DEBUG_MSG("delay done\n"); - // If we are not currently in receive mode, then restart the timer and try again later (this can happen if the main thread + // If we are not currently in receive mode, then restart the random delay (this can happen if the main thread // has placed the unit into standby) FIXME, how will this work if the chipset is in sleep mode? if (!txQueue.empty()) { if (!canSendImmediately()) { - startTransmitTimer(); // try again in a little while + // DEBUG_MSG("Currently Rx/Tx-ing: set random delay\n"); + setRandomDelay(); // currently Rx/Tx-ing: reset random delay } else { - // Send any outgoing packets we have ready - MeshPacket *txp = txQueue.dequeue(); - assert(txp); - startSend(txp); + if (isChannelActive()) { // check if there is currently a LoRa packet on the channel + // DEBUG_MSG("Channel is active: set random delay\n"); + setRandomDelay(); // reset random delay + } else { + // Send any outgoing packets we have ready + MeshPacket *txp = txQueue.dequeue(); + assert(txp); + startSend(txp); - // Packet has been sent, count it toward our TX airtime utilization. - uint32_t xmitMsec = getPacketTime(txp); - airTime->logAirtime(TX_LOG, xmitMsec); + // Packet has been sent, count it toward our TX airtime utilization. + uint32_t xmitMsec = getPacketTime(txp); + airTime->logAirtime(TX_LOG, xmitMsec); + } } } else { // DEBUG_MSG("done with txqueue\n"); @@ -216,6 +212,26 @@ void RadioLibInterface::onNotify(uint32_t notification) } } +void RadioLibInterface::setRandomDelay() +{ + MeshPacket *p = txQueue.getFront(); + // We want all sending/receiving to be done by our daemon thread. + // We use a delay here because this packet might have been sent in response to a packet we just received. + // So we want to make sure the other side has had a chance to reconfigure its radio. + + /* We assume if rx_snr = 0 and rx_rssi = 0, the packet was generated locally. + * This assumption is valid because of the offset generated by the radio to account for the noise + * floor. + */ + if (p->rx_snr == 0 && p->rx_rssi == 0) { + startTransmitTimer(true); + } else { + // If there is a SNR, start a timer scaled based on that SNR. + DEBUG_MSG("rx_snr found. hop_limit:%d rx_snr:%f\n", p->hop_limit, p->rx_snr); + startTransmitTimerSNR(p->rx_snr); + } +} + void RadioLibInterface::startTransmitTimer(bool withDelay) { // If we have work to do and the timer wasn't already scheduled, schedule it now diff --git a/src/mesh/RadioLibInterface.h b/src/mesh/RadioLibInterface.h index 68dfe96d0..2b342a68c 100644 --- a/src/mesh/RadioLibInterface.h +++ b/src/mesh/RadioLibInterface.h @@ -132,6 +132,9 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified */ virtual void startReceive() = 0; + /** can we detect a LoRa preamble on the current channel? */ + virtual bool isChannelActive() = 0; + /** are we actively receiving a packet (only called during receiving state) * This method is only public to facilitate debugging. Do not call. */ @@ -142,17 +145,13 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified private: /** if we have something waiting to send, start a short random timer so we can come check for collision before actually doing - * the transmit - * - * If the timer was already running, we just wait for that one to occur. - * */ + * the transmit */ + void setRandomDelay(); + + /** random timer with certain min. and max. settings */ void startTransmitTimer(bool withDelay = true); - /** if we have something waiting to send, start a short scaled timer based on SNR so we can come check for collision before actually doing - * the transmit - * - * If the timer was already running, we just wait for that one to occur. - * */ + /** timer scaled to SNR of to be flooded packet */ void startTransmitTimerSNR(float snr); void handleTransmitInterrupt(); From a13157ebde2377b0c88300a11e9a3b6c2929f3ca Mon Sep 17 00:00:00 2001 From: GUVWAF Date: Sat, 23 Apr 2022 18:57:45 +0200 Subject: [PATCH 4/4] Rename setRandomDelay() function --- src/mesh/RadioLibInterface.cpp | 10 +++++----- src/mesh/RadioLibInterface.h | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index cfadcf644..a0058893a 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -118,10 +118,10 @@ ErrorCode RadioLibInterface::send(MeshPacket *p) return res; } - // set random transmit delay to let others reconfigure their radio, + // set (random) transmit delay to let others reconfigure their radio, // to avoid collisions and implement timing-based flooding // DEBUG_MSG("Set random delay before transmitting.\n"); - setRandomDelay(); + setTransmitDelay(); return res; #else @@ -187,11 +187,11 @@ void RadioLibInterface::onNotify(uint32_t notification) if (!txQueue.empty()) { if (!canSendImmediately()) { // DEBUG_MSG("Currently Rx/Tx-ing: set random delay\n"); - setRandomDelay(); // currently Rx/Tx-ing: reset random delay + setTransmitDelay(); // currently Rx/Tx-ing: reset random delay } else { if (isChannelActive()) { // check if there is currently a LoRa packet on the channel // DEBUG_MSG("Channel is active: set random delay\n"); - setRandomDelay(); // reset random delay + setTransmitDelay(); // reset random delay } else { // Send any outgoing packets we have ready MeshPacket *txp = txQueue.dequeue(); @@ -212,7 +212,7 @@ void RadioLibInterface::onNotify(uint32_t notification) } } -void RadioLibInterface::setRandomDelay() +void RadioLibInterface::setTransmitDelay() { MeshPacket *p = txQueue.getFront(); // We want all sending/receiving to be done by our daemon thread. diff --git a/src/mesh/RadioLibInterface.h b/src/mesh/RadioLibInterface.h index 2b342a68c..0f59c1fab 100644 --- a/src/mesh/RadioLibInterface.h +++ b/src/mesh/RadioLibInterface.h @@ -144,9 +144,9 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified virtual bool cancelSending(NodeNum from, PacketId id) override; private: - /** if we have something waiting to send, start a short random timer so we can come check for collision before actually doing + /** if we have something waiting to send, start a short (random) timer so we can come check for collision before actually doing * the transmit */ - void setRandomDelay(); + void setTransmitDelay(); /** random timer with certain min. and max. settings */ void startTransmitTimer(bool withDelay = true);