From 5b4ae70c7c05584b250cdfcbdb2a74ff9bc3e357 Mon Sep 17 00:00:00 2001 From: Steve Gilberd Date: Sun, 8 Dec 2024 16:04:16 +1300 Subject: [PATCH] Add new ROUTER_INFILL role Will always rebroadcast packets, but will do so after all other modes. Intended for router nodes that are there to provide additional coverage in areas not already covered by other routers, or to bridge around problematic terrain, but should not be given priority over other routers in order to avoid unnecessaraily consuming hops. By default, this role will rebroadcast during the normal client window. However, if another node is overheard rebroadcasting the packet, then it will be moved to a second window *after* the normal client one, with the same timing behaviour. --- src/mesh/FloodingRouter.cpp | 6 +++- src/mesh/MeshPacketQueue.cpp | 13 +++++++ src/mesh/MeshPacketQueue.h | 3 ++ src/mesh/RadioInterface.cpp | 20 +++++++++-- src/mesh/RadioInterface.h | 9 +++++ src/mesh/RadioLibInterface.cpp | 62 +++++++++++++++++++++++++--------- src/mesh/RadioLibInterface.h | 19 ++++++++--- 7 files changed, 108 insertions(+), 24 deletions(-) diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp index e29c596df4..16e6287d32 100644 --- a/src/mesh/FloodingRouter.cpp +++ b/src/mesh/FloodingRouter.cpp @@ -24,11 +24,15 @@ bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p) printPacket("Ignore dupe incoming msg", p); rxDupe++; if (config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER && - config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER) { + config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER && + config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER_INFILL) { // cancel rebroadcast of this message *if* there was already one, unless we're a router/repeater! if (Router::cancelSending(p->from, p->id)) txRelayCanceled++; } + if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_INFILL && iface) { + iface->clampToLateRebroadcastWindow(getFrom(p), p->id); + } /* If the original transmitter is doing retransmissions (hopStart equals hopLimit) for a reliable transmission, e.g., when the ACK got lost, we will handle the packet again to make sure it gets an ACK to its packet. */ diff --git a/src/mesh/MeshPacketQueue.cpp b/src/mesh/MeshPacketQueue.cpp index 99ef41c1e4..ecbb3ed442 100644 --- a/src/mesh/MeshPacketQueue.cpp +++ b/src/mesh/MeshPacketQueue.cpp @@ -93,6 +93,19 @@ meshtastic_MeshPacket *MeshPacketQueue::getFront() return p; } +/** Attempt to find a packet in this queue. Returns a pointer to the found packet, or NULL if not found. */ +meshtastic_MeshPacket *MeshPacketQueue::find(NodeNum from, PacketId id) +{ + for (auto it = queue.begin(); it != queue.end(); it++) { + auto p = (*it); + if (getFrom(p) == from && p->id == id) { + return p; + } + } + + return NULL; +} + /** Attempt to find and remove a packet from this queue. Returns a pointer to the removed packet, or NULL if not found */ meshtastic_MeshPacket *MeshPacketQueue::remove(NodeNum from, PacketId id) { diff --git a/src/mesh/MeshPacketQueue.h b/src/mesh/MeshPacketQueue.h index 3c28fc5ce7..a26169bd77 100644 --- a/src/mesh/MeshPacketQueue.h +++ b/src/mesh/MeshPacketQueue.h @@ -35,6 +35,9 @@ class MeshPacketQueue meshtastic_MeshPacket *getFront(); + /** Attempt to find a packet in this queue. Returns a pointer to the found packet, or NULL if not found. */ + meshtastic_MeshPacket *find(NodeNum from, PacketId id); + /** Attempt to find and remove a packet from this queue. Returns the packet which was removed from the queue */ meshtastic_MeshPacket *remove(NodeNum from, PacketId id); }; diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index 5161ac41fc..cc40107f63 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -254,8 +254,8 @@ uint32_t RadioInterface::getTxDelayMsec() return random(0, pow(2, CWsize)) * slotTimeMsec; } -/** The delay to use when we want to flood a message */ -uint32_t RadioInterface::getTxDelayMsecWeighted(float snr) +/** The CW size to use when calculating SNR_based delays */ +uint8_t RadioInterface::getCWsize(float snr) { // The minimum value for a LoRa SNR const uint32_t SNR_MIN = -20; @@ -263,10 +263,24 @@ uint32_t RadioInterface::getTxDelayMsecWeighted(float snr) // The maximum value for a LoRa SNR const uint32_t SNR_MAX = 15; + return map(snr, SNR_MIN, SNR_MAX, CWmin, CWmax); +} + +/** The worst-case SNR_based packet delay */ +uint32_t RadioInterface::getTxDelayMsecWeightedWorst(float snr) +{ + uint8_t CWsize = getCWsize(snr); + // offset the maximum delay for routers: (2 * CWmax * slotTimeMsec) + return (2 * CWmax * slotTimeMsec) + pow(2, CWsize) * slotTimeMsec; +} + +/** The delay to use when we want to flood a message */ +uint32_t RadioInterface::getTxDelayMsecWeighted(float snr) +{ // high SNR = large CW size (Long Delay) // low SNR = small CW size (Short Delay) uint32_t delay = 0; - uint8_t CWsize = map(snr, SNR_MIN, SNR_MAX, CWmin, CWmax); + uint8_t CWsize = getCWsize(snr); // LOG_DEBUG("rx_snr of %f so setting CWsize to:%d", snr, CWsize); if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER || config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER) { diff --git a/src/mesh/RadioInterface.h b/src/mesh/RadioInterface.h index 89a4c70879..652b2269cd 100644 --- a/src/mesh/RadioInterface.h +++ b/src/mesh/RadioInterface.h @@ -173,9 +173,18 @@ class RadioInterface /** The delay to use when we want to send something */ uint32_t getTxDelayMsec(); + /** The CW to use when calculating SNR_based delays */ + uint8_t getCWsize(float snr); + + /** The worst-case SNR_based packet delay */ + uint32_t getTxDelayMsecWeightedWorst(float snr); + /** The delay to use when we want to flood a message. Use a weighted scale based on SNR */ uint32_t getTxDelayMsecWeighted(float snr); + /** If the packet is not already in the late rebroadcast window, move it there */ + virtual void clampToLateRebroadcastWindow(NodeNum from, PacketId id) { return; } + /** * Calculate airtime per * https://www.rs-online.com/designspark/rel-assets/ds-assets/uploads/knowledge-items/application-notes-for-the-internet-of-things/LoRa%20Design%20Guide.pdf diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index 5f82a41ced..3410795961 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -278,19 +278,28 @@ void RadioLibInterface::onNotify(uint32_t notification) startReceive(); // try receiving this packet, afterwards we'll be trying to transmit again setTransmitDelay(); } else { - // Send any outgoing packets we have ready as fast as possible to keep the time between channel scan and - // actual transmission as short as possible - meshtastic_MeshPacket *txp = txQueue.dequeue(); + // check for any remaining delay, and abort if we're still supposed to be waiting + meshtastic_MeshPacket *txp = txQueue.getFront(); assert(txp); - bool sent = startSend(txp); - if (sent) { - // Packet has been sent, count it toward our TX airtime utilization. - uint32_t xmitMsec = getPacketTime(txp); - airTime->logAirtime(TX_LOG, xmitMsec); + if (txp->tx_after > millis()) { + // There's still some delay pending on this packet, so resume waiting for it to elapse + notifyLater(txp->tx_after - millis(), TRANSMIT_DELAY_COMPLETED, false); + } else { + // Send any outgoing packets we have ready as fast as possible to keep the time between channel scan and + // actual transmission as short as possible + txp = txQueue.dequeue(); + assert(txp); + bool sent = startSend(txp); + if (sent) { + // Packet has been sent, count it toward our TX airtime utilization. + uint32_t xmitMsec = getPacketTime(txp); + airTime->logAirtime(TX_LOG, xmitMsec); + } } } } } else { + // Do nothing, because the queue is empty } break; default: @@ -305,34 +314,55 @@ void RadioLibInterface::setTransmitDelay() // 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); + /* 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. + */ + p->tx_after = millis() + startTransmitTimer(true); } else { // If there is a SNR, start a timer scaled based on that SNR. LOG_DEBUG("rx_snr found. hop_limit:%d rx_snr:%f", p->hop_limit, p->rx_snr); - startTransmitTimerSNR(p->rx_snr); + p->tx_after = millis() + startTransmitTimerSNR(p->rx_snr); } } -void RadioLibInterface::startTransmitTimer(bool withDelay) +uint32_t RadioLibInterface::startTransmitTimer(bool withDelay) { // If we have work to do and the timer wasn't already scheduled, schedule it now if (!txQueue.empty()) { uint32_t delay = !withDelay ? 1 : getTxDelayMsec(); notifyLater(delay, TRANSMIT_DELAY_COMPLETED, false); // This will implicitly enable + return delay; } + return 0; } -void RadioLibInterface::startTransmitTimerSNR(float snr) +uint32_t RadioLibInterface::startTransmitTimerSNR(float snr) { // If we have work to do and the timer wasn't already scheduled, schedule it now if (!txQueue.empty()) { uint32_t delay = getTxDelayMsecWeighted(snr); notifyLater(delay, TRANSMIT_DELAY_COMPLETED, false); // This will implicitly enable + return delay; + } + return 0; +} + +/** + * If the packet is not already in the late rebroadcast window, move it there + */ +void RadioLibInterface::clampToLateRebroadcastWindow(NodeNum from, PacketId id) +{ + meshtastic_MeshPacket *p = txQueue.find(from, id); + if (p && p->delayed != meshtastic_MeshPacket_Delayed_DELAYED_REBROADCAST) { + p->tx_after += getTxDelayMsecWeightedWorst(p->rx_snr); + p->delayed = meshtastic_MeshPacket_Delayed_DELAYED_REBROADCAST; + + // ensures that non-delayed packets are handled first + p->priority = meshtastic_MeshPacket_Priority_MIN; + + LOG_DEBUG("Moved packet to the late rebroadcast window"); } } diff --git a/src/mesh/RadioLibInterface.h b/src/mesh/RadioLibInterface.h index a5c2e30ddc..13ed50aac8 100644 --- a/src/mesh/RadioLibInterface.h +++ b/src/mesh/RadioLibInterface.h @@ -147,11 +147,17 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified * doing the transmit */ void setTransmitDelay(); - /** random timer with certain min. and max. settings */ - void startTransmitTimer(bool withDelay = true); + /** + * random timer with certain min. and max. settings + * @return Timestamp after which the packet may be sent + */ + uint32_t startTransmitTimer(bool withDelay = true); - /** timer scaled to SNR of to be flooded packet */ - void startTransmitTimerSNR(float snr); + /** + * timer scaled to SNR of to be flooded packet + * @return Timestamp after which the packet may be sent + */ + uint32_t startTransmitTimerSNR(float snr); void handleTransmitInterrupt(); void handleReceiveInterrupt(); @@ -200,4 +206,9 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified virtual void setStandby(); const char *radioLibErr = "RadioLib err="; + + /** + * If the packet is not already in the late rebroadcast window, move it there + */ + void clampToLateRebroadcastWindow(NodeNum from, PacketId id); }; \ No newline at end of file