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); }; 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/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index 5f0e57210..a0058893a 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"); + setTransmitDelay(); 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"); + setTransmitDelay(); // 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"); + setTransmitDelay(); // 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::setTransmitDelay() +{ + 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..0f59c1fab 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. */ @@ -141,18 +144,14 @@ 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 - * the transmit - * - * If the timer was already running, we just wait for that one to occur. - * */ + /** 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 setTransmitDelay(); + + /** 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(); 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;