Skip to content

Commit b280806

Browse files
authored
Add new ROUTER_LATE role (#5528)
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.
1 parent 2b33be2 commit b280806

7 files changed

+120
-30
lines changed

src/mesh/FloodingRouter.cpp

+5-1
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,15 @@ bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
2424
printPacket("Ignore dupe incoming msg", p);
2525
rxDupe++;
2626
if (config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER &&
27-
config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER) {
27+
config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER &&
28+
config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER_LATE) {
2829
// cancel rebroadcast of this message *if* there was already one, unless we're a router/repeater!
2930
if (Router::cancelSending(p->from, p->id))
3031
txRelayCanceled++;
3132
}
33+
if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_LATE && iface) {
34+
iface->clampToLateRebroadcastWindow(getFrom(p), p->id);
35+
}
3236

3337
/* If the original transmitter is doing retransmissions (hopStart equals hopLimit) for a reliable transmission, e.g., when
3438
the ACK got lost, we will handle the packet again to make sure it gets an ACK to its packet. */

src/mesh/MeshPacketQueue.cpp

+23-3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ inline uint32_t getPriority(const meshtastic_MeshPacket *p)
1616
bool CompareMeshPacketFunc(const meshtastic_MeshPacket *p1, const meshtastic_MeshPacket *p2)
1717
{
1818
assert(p1 && p2);
19+
20+
// If one packet is in the late transmit window, prefer the other one
21+
if ((bool)p1->tx_after != (bool)p2->tx_after) {
22+
return !p1->tx_after;
23+
}
24+
1925
auto p1p = getPriority(p1), p2p = getPriority(p2);
2026
// If priorities differ, use that
2127
// for equal priorities, prefer packets already on mesh.
@@ -94,11 +100,11 @@ meshtastic_MeshPacket *MeshPacketQueue::getFront()
94100
}
95101

96102
/** Attempt to find and remove a packet from this queue. Returns a pointer to the removed packet, or NULL if not found */
97-
meshtastic_MeshPacket *MeshPacketQueue::remove(NodeNum from, PacketId id)
103+
meshtastic_MeshPacket *MeshPacketQueue::remove(NodeNum from, PacketId id, bool tx_normal, bool tx_late)
98104
{
99105
for (auto it = queue.begin(); it != queue.end(); it++) {
100106
auto p = (*it);
101-
if (getFrom(p) == from && p->id == id) {
107+
if (getFrom(p) == from && p->id == id && ((tx_normal && !p->tx_after) || (tx_late && p->tx_after))) {
102108
queue.erase(it);
103109
return p;
104110
}
@@ -114,9 +120,10 @@ bool MeshPacketQueue::replaceLowerPriorityPacket(meshtastic_MeshPacket *p)
114120
if (queue.empty()) {
115121
return false; // No packets to replace
116122
}
123+
117124
// Check if the packet at the back has a lower priority than the new packet
118125
auto &backPacket = queue.back();
119-
if (backPacket->priority < p->priority) {
126+
if (!backPacket->tx_after && backPacket->priority < p->priority) {
120127
// Remove the back packet
121128
packetPool.release(backPacket);
122129
queue.pop_back();
@@ -125,6 +132,19 @@ bool MeshPacketQueue::replaceLowerPriorityPacket(meshtastic_MeshPacket *p)
125132
return true;
126133
}
127134

135+
if (backPacket->tx_after) {
136+
// Check if there's a non-late packet with lower priority
137+
auto it = queue.end();
138+
auto refPacket = *--it;
139+
for (; refPacket->tx_after && it != queue.begin(); refPacket = *--it)
140+
;
141+
if (!refPacket->tx_after && refPacket->priority < p->priority) {
142+
packetPool.release(refPacket);
143+
enqueue(refPacket);
144+
return true;
145+
}
146+
}
147+
128148
// If the back packet's priority is not lower, no replacement occurs
129149
return false;
130150
}

src/mesh/MeshPacketQueue.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,5 @@ class MeshPacketQueue
3636
meshtastic_MeshPacket *getFront();
3737

3838
/** Attempt to find and remove a packet from this queue. Returns the packet which was removed from the queue */
39-
meshtastic_MeshPacket *remove(NodeNum from, PacketId id);
40-
};
39+
meshtastic_MeshPacket *remove(NodeNum from, PacketId id, bool tx_normal = true, bool tx_late = true);
40+
};

src/mesh/RadioInterface.cpp

+17-3
Original file line numberDiff line numberDiff line change
@@ -254,19 +254,33 @@ uint32_t RadioInterface::getTxDelayMsec()
254254
return random(0, pow(2, CWsize)) * slotTimeMsec;
255255
}
256256

257-
/** The delay to use when we want to flood a message */
258-
uint32_t RadioInterface::getTxDelayMsecWeighted(float snr)
257+
/** The CW size to use when calculating SNR_based delays */
258+
uint8_t RadioInterface::getCWsize(float snr)
259259
{
260260
// The minimum value for a LoRa SNR
261261
const uint32_t SNR_MIN = -20;
262262

263263
// The maximum value for a LoRa SNR
264264
const uint32_t SNR_MAX = 15;
265265

266+
return map(snr, SNR_MIN, SNR_MAX, CWmin, CWmax);
267+
}
268+
269+
/** The worst-case SNR_based packet delay */
270+
uint32_t RadioInterface::getTxDelayMsecWeightedWorst(float snr)
271+
{
272+
uint8_t CWsize = getCWsize(snr);
273+
// offset the maximum delay for routers: (2 * CWmax * slotTimeMsec)
274+
return (2 * CWmax * slotTimeMsec) + pow(2, CWsize) * slotTimeMsec;
275+
}
276+
277+
/** The delay to use when we want to flood a message */
278+
uint32_t RadioInterface::getTxDelayMsecWeighted(float snr)
279+
{
266280
// high SNR = large CW size (Long Delay)
267281
// low SNR = small CW size (Short Delay)
268282
uint32_t delay = 0;
269-
uint8_t CWsize = map(snr, SNR_MIN, SNR_MAX, CWmin, CWmax);
283+
uint8_t CWsize = getCWsize(snr);
270284
// LOG_DEBUG("rx_snr of %f so setting CWsize to:%d", snr, CWsize);
271285
if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ||
272286
config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER) {

src/mesh/RadioInterface.h

+9
Original file line numberDiff line numberDiff line change
@@ -173,9 +173,18 @@ class RadioInterface
173173
/** The delay to use when we want to send something */
174174
uint32_t getTxDelayMsec();
175175

176+
/** The CW to use when calculating SNR_based delays */
177+
uint8_t getCWsize(float snr);
178+
179+
/** The worst-case SNR_based packet delay */
180+
uint32_t getTxDelayMsecWeightedWorst(float snr);
181+
176182
/** The delay to use when we want to flood a message. Use a weighted scale based on SNR */
177183
uint32_t getTxDelayMsecWeighted(float snr);
178184

185+
/** If the packet is not already in the late rebroadcast window, move it there */
186+
virtual void clampToLateRebroadcastWindow(NodeNum from, PacketId id) { return; }
187+
179188
/**
180189
* Calculate airtime per
181190
* https://www.rs-online.com/designspark/rel-assets/ds-assets/uploads/knowledge-items/application-notes-for-the-internet-of-things/LoRa%20Design%20Guide.pdf

src/mesh/RadioLibInterface.cpp

+51-19
Original file line numberDiff line numberDiff line change
@@ -235,12 +235,12 @@ void RadioLibInterface::onNotify(uint32_t notification)
235235
case ISR_TX:
236236
handleTransmitInterrupt();
237237
startReceive();
238-
startTransmitTimer();
238+
setTransmitDelay();
239239
break;
240240
case ISR_RX:
241241
handleReceiveInterrupt();
242242
startReceive();
243-
startTransmitTimer();
243+
setTransmitDelay();
244244
break;
245245
case TRANSMIT_DELAY_COMPLETED:
246246

@@ -250,23 +250,32 @@ void RadioLibInterface::onNotify(uint32_t notification)
250250
if (!canSendImmediately()) {
251251
setTransmitDelay(); // currently Rx/Tx-ing: reset random delay
252252
} else {
253-
if (isChannelActive()) { // check if there is currently a LoRa packet on the channel
254-
startReceive(); // try receiving this packet, afterwards we'll be trying to transmit again
255-
setTransmitDelay();
253+
meshtastic_MeshPacket *txp = txQueue.getFront();
254+
assert(txp);
255+
long delay_remaining = txp->tx_after ? txp->tx_after - millis() : 0;
256+
if (delay_remaining > 0) {
257+
// There's still some delay pending on this packet, so resume waiting for it to elapse
258+
notifyLater(delay_remaining, TRANSMIT_DELAY_COMPLETED, false);
256259
} else {
257-
// Send any outgoing packets we have ready as fast as possible to keep the time between channel scan and
258-
// actual transmission as short as possible
259-
meshtastic_MeshPacket *txp = txQueue.dequeue();
260-
assert(txp);
261-
bool sent = startSend(txp);
262-
if (sent) {
263-
// Packet has been sent, count it toward our TX airtime utilization.
264-
uint32_t xmitMsec = getPacketTime(txp);
265-
airTime->logAirtime(TX_LOG, xmitMsec);
260+
if (isChannelActive()) { // check if there is currently a LoRa packet on the channel
261+
startReceive(); // try receiving this packet, afterwards we'll be trying to transmit again
262+
setTransmitDelay();
263+
} else {
264+
// Send any outgoing packets we have ready as fast as possible to keep the time between channel scan and
265+
// actual transmission as short as possible
266+
txp = txQueue.dequeue();
267+
assert(txp);
268+
bool sent = startSend(txp);
269+
if (sent) {
270+
// Packet has been sent, count it toward our TX airtime utilization.
271+
uint32_t xmitMsec = getPacketTime(txp);
272+
airTime->logAirtime(TX_LOG, xmitMsec);
273+
}
266274
}
267275
}
268276
}
269277
} else {
278+
// Do nothing, because the queue is empty
270279
}
271280
break;
272281
default:
@@ -277,15 +286,24 @@ void RadioLibInterface::onNotify(uint32_t notification)
277286
void RadioLibInterface::setTransmitDelay()
278287
{
279288
meshtastic_MeshPacket *p = txQueue.getFront();
289+
if (!p) {
290+
return; // noop if there's nothing in the queue
291+
}
292+
280293
// We want all sending/receiving to be done by our daemon thread.
281294
// We use a delay here because this packet might have been sent in response to a packet we just received.
282295
// So we want to make sure the other side has had a chance to reconfigure its radio.
283296

284-
/* We assume if rx_snr = 0 and rx_rssi = 0, the packet was generated locally.
285-
* This assumption is valid because of the offset generated by the radio to account for the noise
286-
* floor.
287-
*/
288-
if (p->rx_snr == 0 && p->rx_rssi == 0) {
297+
if (p->tx_after) {
298+
unsigned long add_delay = p->rx_rssi ? getTxDelayMsecWeighted(p->rx_snr) : getTxDelayMsec();
299+
unsigned long now = millis();
300+
p->tx_after = max(p->tx_after + add_delay, now + add_delay);
301+
notifyLater(now - p->tx_after, TRANSMIT_DELAY_COMPLETED, false);
302+
} else if (p->rx_snr == 0 && p->rx_rssi == 0) {
303+
/* We assume if rx_snr = 0 and rx_rssi = 0, the packet was generated locally.
304+
* This assumption is valid because of the offset generated by the radio to account for the noise
305+
* floor.
306+
*/
289307
startTransmitTimer(true);
290308
} else {
291309
// If there is a SNR, start a timer scaled based on that SNR.
@@ -312,6 +330,20 @@ void RadioLibInterface::startTransmitTimerSNR(float snr)
312330
}
313331
}
314332

333+
/**
334+
* If the packet is not already in the late rebroadcast window, move it there
335+
*/
336+
void RadioLibInterface::clampToLateRebroadcastWindow(NodeNum from, PacketId id)
337+
{
338+
// Look for non-late packets only, so we don't do this twice!
339+
meshtastic_MeshPacket *p = txQueue.remove(from, id, true, false);
340+
if (p) {
341+
p->tx_after = millis() + getTxDelayMsecWeightedWorst(p->rx_snr);
342+
txQueue.enqueue(p);
343+
LOG_DEBUG("Move existing queued packet to the late rebroadcast window %dms from now", p->tx_after - millis());
344+
}
345+
}
346+
315347
void RadioLibInterface::handleTransmitInterrupt()
316348
{
317349
// This can be null if we forced the device to enter standby mode. In that case

src/mesh/RadioLibInterface.h

+13-2
Original file line numberDiff line numberDiff line change
@@ -140,10 +140,16 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
140140
* doing the transmit */
141141
void setTransmitDelay();
142142

143-
/** random timer with certain min. and max. settings */
143+
/**
144+
* random timer with certain min. and max. settings
145+
* @return Timestamp after which the packet may be sent
146+
*/
144147
void startTransmitTimer(bool withDelay = true);
145148

146-
/** timer scaled to SNR of to be flooded packet */
149+
/**
150+
* timer scaled to SNR of to be flooded packet
151+
* @return Timestamp after which the packet may be sent
152+
*/
147153
void startTransmitTimerSNR(float snr);
148154

149155
void handleTransmitInterrupt();
@@ -193,4 +199,9 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
193199
virtual void setStandby();
194200

195201
const char *radioLibErr = "RadioLib err=";
202+
203+
/**
204+
* If the packet is not already in the late rebroadcast window, move it there
205+
*/
206+
void clampToLateRebroadcastWindow(NodeNum from, PacketId id);
196207
};

0 commit comments

Comments
 (0)