diff --git a/src/DisplayFormatters.cpp b/src/DisplayFormatters.cpp
index b2749806c..5193e1cb4 100644
--- a/src/DisplayFormatters.cpp
+++ b/src/DisplayFormatters.cpp
@@ -52,6 +52,9 @@ const char *DisplayFormatters::getDeviceRole(meshtastic_Config_DeviceConfig_Role
case meshtastic_Config_DeviceConfig_Role_CLIENT_HIDDEN:
return "Client Hidden";
break;
+ case meshtastic_Config_DeviceConfig_Role_CLIENT_BASE:
+ return "Client Base";
+ break;
case meshtastic_Config_DeviceConfig_Role_LOST_AND_FOUND:
return "Lost and Found";
break;
diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp
index eb8093947..14ed91a1e 100644
--- a/src/graphics/Screen.cpp
+++ b/src/graphics/Screen.cpp
@@ -25,6 +25,7 @@ along with this program. If not, see .
#include "PowerMon.h"
#include "Throttle.h"
#include "configuration.h"
+#include "meshUtils.h"
#if HAS_SCREEN
#include
@@ -58,7 +59,6 @@ along with this program. If not, see .
#include "mesh-pb-constants.h"
#include "mesh/Channels.h"
#include "mesh/generated/meshtastic/deviceonly.pb.h"
-#include "meshUtils.h"
#include "modules/ExternalNotificationModule.h"
#include "modules/TextMessageModule.h"
#include "modules/WaypointModule.h"
@@ -1605,13 +1605,15 @@ bool shouldWakeOnReceivedMessage()
/*
The goal here is to determine when we do NOT wake up the screen on message received:
- Any ext. notifications are turned on
- - If role is not client / client_mute
+ - If role is not CLIENT / CLIENT_MUTE / CLIENT_HIDDEN / CLIENT_BASE
- If the battery level is very low
*/
if (moduleConfig.external_notification.enabled) {
return false;
}
- if (!meshtastic_Config_DeviceConfig_Role_CLIENT && !meshtastic_Config_DeviceConfig_Role_CLIENT_MUTE) {
+ if (!IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_CLIENT,
+ meshtastic_Config_DeviceConfig_Role_CLIENT_MUTE, meshtastic_Config_DeviceConfig_Role_CLIENT_HIDDEN,
+ meshtastic_Config_DeviceConfig_Role_CLIENT_BASE)) {
return false;
}
if (powerStatus && powerStatus->getBatteryChargePercent() < 10) {
diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp
index dbd458b61..f805055c8 100644
--- a/src/mesh/FloodingRouter.cpp
+++ b/src/mesh/FloodingRouter.cpp
@@ -43,12 +43,30 @@ bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
return Router::shouldFilterReceived(p);
}
+bool FloodingRouter::roleAllowsCancelingDupe(const meshtastic_MeshPacket *p)
+{
+ if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ||
+ config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER ||
+ config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_LATE) {
+ // ROUTER, REPEATER, ROUTER_LATE should never cancel relaying a packet (i.e. we should always rebroadcast),
+ // even if we've heard another station rebroadcast it already.
+ return false;
+ }
+
+ if (config.device.role == meshtastic_Config_DeviceConfig_Role_CLIENT_BASE) {
+ // CLIENT_BASE: if the packet is from or to a favorited node,
+ // we should act like a ROUTER and should never cancel a rebroadcast (i.e. we should always rebroadcast),
+ // even if we've heard another station rebroadcast it already.
+ return !nodeDB->isFromOrToFavoritedNode(*p);
+ }
+
+ // All other roles (such as CLIENT) should cancel a rebroadcast if they hear another station's rebroadcast.
+ return true;
+}
+
void FloodingRouter::perhapsCancelDupe(const meshtastic_MeshPacket *p)
{
- if (config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER &&
- config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER &&
- config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER_LATE &&
- p->transport_mechanism == meshtastic_MeshPacket_TransportMechanism_TRANSPORT_LORA) {
+ if (p->transport_mechanism == meshtastic_MeshPacket_TransportMechanism_TRANSPORT_LORA && roleAllowsCancelingDupe(p)) {
// cancel rebroadcast of this message *if* there was already one, unless we're a router/repeater!
// But only LoRa packets should be able to trigger this.
if (Router::cancelSending(p->from, p->id))
diff --git a/src/mesh/FloodingRouter.h b/src/mesh/FloodingRouter.h
index 36c6ad8aa..30ad5945b 100644
--- a/src/mesh/FloodingRouter.h
+++ b/src/mesh/FloodingRouter.h
@@ -59,6 +59,10 @@ class FloodingRouter : public Router
*/
virtual void sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c) override;
+ // Return false for roles like ROUTER or REPEATER which should always rebroadcast even when we've heard another rebroadcast of
+ // the same packet
+ bool roleAllowsCancelingDupe(const meshtastic_MeshPacket *p);
+
/* Call when receiving a duplicate packet to check whether we should cancel a packet in the Tx queue */
void perhapsCancelDupe(const meshtastic_MeshPacket *p);
diff --git a/src/mesh/NextHopRouter.cpp b/src/mesh/NextHopRouter.cpp
index 7ceca2195..9bb8b240c 100644
--- a/src/mesh/NextHopRouter.cpp
+++ b/src/mesh/NextHopRouter.cpp
@@ -161,6 +161,15 @@ bool NextHopRouter::stopRetransmission(NodeNum from, PacketId id)
return stopRetransmission(key);
}
+bool NextHopRouter::roleAllowsCancelingFromTxQueue(const meshtastic_MeshPacket *p)
+{
+ // Return true if we're allowed to cancel a packet in the txQueue (so we may never transmit it even once)
+
+ // Return false for roles like ROUTER, REPEATER, ROUTER_LATE which should always transmit the packet at least once.
+
+ return roleAllowsCancelingDupe(p); // same logic as FloodingRouter::roleAllowsCancelingDupe
+}
+
bool NextHopRouter::stopRetransmission(GlobalPacketId key)
{
auto old = findPendingPacket(key);
@@ -170,17 +179,21 @@ bool NextHopRouter::stopRetransmission(GlobalPacketId key)
to avoid canceling a transmission if it was ACKed super fast via MQTT */
if (old->numRetransmissions < NUM_RELIABLE_RETX - 1) {
// We only cancel it if we are the original sender or if we're not a router(_late)/repeater
- if (isFromUs(p) || (config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER &&
- config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER &&
- config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER_LATE)) {
+ if (isFromUs(p) || roleAllowsCancelingFromTxQueue(p)) {
// remove the 'original' (identified by originator and packet->id) from the txqueue and free it
cancelSending(getFrom(p), p->id);
- // now free the pooled copy for retransmission too
- packetPool.release(p);
}
}
+
+ // Regardless of whether or not we canceled this packet from the txQueue, remove it from our pending list so it doesn't
+ // get scheduled again. (This is the core of stopRetransmission.)
auto numErased = pending.erase(key);
assert(numErased == 1);
+
+ // When we remove an entry from pending, always be sure to release the copy of the packet that was allocated in the call
+ // to startRetransmission.
+ packetPool.release(p);
+
return true;
} else
return false;
diff --git a/src/mesh/NextHopRouter.h b/src/mesh/NextHopRouter.h
index 6c2764aff..0022644e9 100644
--- a/src/mesh/NextHopRouter.h
+++ b/src/mesh/NextHopRouter.h
@@ -121,6 +121,9 @@ class NextHopRouter : public FloodingRouter
*/
PendingPacket *startRetransmission(meshtastic_MeshPacket *p, uint8_t numReTx = NUM_INTERMEDIATE_RETX);
+ // Return true if we're allowed to cancel a packet in the txQueue (so we may never transmit it even once)
+ bool roleAllowsCancelingFromTxQueue(const meshtastic_MeshPacket *p);
+
/**
* Stop any retransmissions we are doing of the specified node/packet ID pair
*
diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp
index 52a18a53f..1859ca27b 100644
--- a/src/mesh/NodeDB.cpp
+++ b/src/mesh/NodeDB.cpp
@@ -1770,6 +1770,65 @@ void NodeDB::set_favorite(bool is_favorite, uint32_t nodeId)
}
}
+bool NodeDB::isFavorite(uint32_t nodeId)
+{
+ // returns true if nodeId is_favorite; false if not or not found
+
+ // NODENUM_BROADCAST will never be in the DB
+ if (nodeId == NODENUM_BROADCAST)
+ return false;
+
+ meshtastic_NodeInfoLite *lite = getMeshNode(nodeId);
+
+ if (lite) {
+ return lite->is_favorite;
+ }
+ return false;
+}
+
+bool NodeDB::isFromOrToFavoritedNode(const meshtastic_MeshPacket &p)
+{
+ // This method is logically equivalent to:
+ // return isFavorite(p.from) || isFavorite(p.to);
+ // but is more efficient by:
+ // 1. doing only one pass through the database, instead of two
+ // 2. exiting early when a favorite is found, or if both from and to have been seen
+
+ if (p.to == NODENUM_BROADCAST)
+ return isFavorite(p.from); // we never store NODENUM_BROADCAST in the DB, so we only need to check p.from
+
+ meshtastic_NodeInfoLite *lite = NULL;
+
+ bool seenFrom = false;
+ bool seenTo = false;
+
+ for (int i = 0; i < numMeshNodes; i++) {
+ lite = &meshNodes->at(i);
+
+ if (lite->num == p.from) {
+ if (lite->is_favorite)
+ return true;
+
+ seenFrom = true;
+ }
+
+ if (lite->num == p.to) {
+ if (lite->is_favorite)
+ return true;
+
+ seenTo = true;
+ }
+
+ if (seenFrom && seenTo)
+ return false; // we've seen both, and neither is a favorite, so we can stop searching early
+
+ // Note: if we knew that sortMeshDB was always called after any change to is_favorite, we could exit early after searching
+ // all favorited nodes first.
+ }
+
+ return false;
+}
+
void NodeDB::pause_sort(bool paused)
{
sortingIsPaused = paused;
diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h
index 167dc1337..f73f64f92 100644
--- a/src/mesh/NodeDB.h
+++ b/src/mesh/NodeDB.h
@@ -185,6 +185,16 @@ class NodeDB
*/
void set_favorite(bool is_favorite, uint32_t nodeId);
+ /*
+ * Returns true if the node is in the NodeDB and marked as favorite
+ */
+ bool isFavorite(uint32_t nodeId);
+
+ /*
+ * Returns true if p->from or p->to is a favorited node
+ */
+ bool isFromOrToFavoritedNode(const meshtastic_MeshPacket &p);
+
/**
* Other functions like the node picker can request a pause in the node sorting
*/
diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp
index 20a0bdbd1..31c68c302 100644
--- a/src/mesh/RadioInterface.cpp
+++ b/src/mesh/RadioInterface.cpp
@@ -314,16 +314,33 @@ uint32_t RadioInterface::getTxDelayMsecWeightedWorst(float snr)
return (2 * CWmax * slotTimeMsec) + pow_of_2(CWsize) * slotTimeMsec;
}
+/** Returns true if we should rebroadcast early like a ROUTER */
+bool RadioInterface::shouldRebroadcastEarlyLikeRouter(meshtastic_MeshPacket *p)
+{
+ // If we are a ROUTER or REPEATER, we always rebroadcast early
+ if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ||
+ config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER) {
+ return true;
+ }
+
+ // If we are a CLIENT_BASE and the packet is from or to a favorited node, we should rebroadcast early
+ if (config.device.role == meshtastic_Config_DeviceConfig_Role_CLIENT_BASE) {
+ return nodeDB->isFromOrToFavoritedNode(*p);
+ }
+
+ return false;
+}
+
/** The delay to use when we want to flood a message */
-uint32_t RadioInterface::getTxDelayMsecWeighted(float snr)
+uint32_t RadioInterface::getTxDelayMsecWeighted(meshtastic_MeshPacket *p)
{
// high SNR = large CW size (Long Delay)
// low SNR = small CW size (Short Delay)
+ float snr = p->rx_snr;
uint32_t delay = 0;
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) {
+ if (shouldRebroadcastEarlyLikeRouter(p)) {
delay = random(0, 2 * CWsize) * slotTimeMsec;
LOG_DEBUG("rx_snr found in packet. Router: setting tx delay:%d", delay);
} else {
diff --git a/src/mesh/RadioInterface.h b/src/mesh/RadioInterface.h
index c9e71cfa8..eff284747 100644
--- a/src/mesh/RadioInterface.h
+++ b/src/mesh/RadioInterface.h
@@ -180,8 +180,11 @@ class RadioInterface
/** The worst-case SNR_based packet delay */
uint32_t getTxDelayMsecWeightedWorst(float snr);
+ /** Returns true if we should rebroadcast early like a ROUTER */
+ bool shouldRebroadcastEarlyLikeRouter(meshtastic_MeshPacket *p);
+
/** The delay to use when we want to flood a message. Use a weighted scale based on SNR */
- uint32_t getTxDelayMsecWeighted(float snr);
+ uint32_t getTxDelayMsecWeighted(meshtastic_MeshPacket *p);
/** If the packet is not already in the late rebroadcast window, move it there */
virtual void clampToLateRebroadcastWindow(NodeNum from, PacketId id) { return; }
diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp
index c18612101..19d0f794a 100644
--- a/src/mesh/RadioLibInterface.cpp
+++ b/src/mesh/RadioLibInterface.cpp
@@ -310,7 +310,7 @@ void RadioLibInterface::setTransmitDelay()
// So we want to make sure the other side has had a chance to reconfigure its radio.
if (p->tx_after) {
- unsigned long add_delay = p->rx_rssi ? getTxDelayMsecWeighted(p->rx_snr) : getTxDelayMsec();
+ unsigned long add_delay = p->rx_rssi ? getTxDelayMsecWeighted(p) : getTxDelayMsec();
unsigned long now = millis();
p->tx_after = min(max(p->tx_after + add_delay, now + add_delay), now + 2 * getTxDelayMsecWeightedWorst(p->rx_snr));
notifyLater(p->tx_after - now, TRANSMIT_DELAY_COMPLETED, false);
@@ -323,7 +323,7 @@ void RadioLibInterface::setTransmitDelay()
} 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);
+ startTransmitTimerRebroadcast(p);
}
}
@@ -336,11 +336,11 @@ void RadioLibInterface::startTransmitTimer(bool withDelay)
}
}
-void RadioLibInterface::startTransmitTimerSNR(float snr)
+void RadioLibInterface::startTransmitTimerRebroadcast(meshtastic_MeshPacket *p)
{
// If we have work to do and the timer wasn't already scheduled, schedule it now
if (!txQueue.empty()) {
- uint32_t delay = getTxDelayMsecWeighted(snr);
+ uint32_t delay = getTxDelayMsecWeighted(p);
notifyLater(delay, TRANSMIT_DELAY_COMPLETED, false); // This will implicitly enable
}
}
diff --git a/src/mesh/RadioLibInterface.h b/src/mesh/RadioLibInterface.h
index 2ab2679c0..9f497812f 100644
--- a/src/mesh/RadioLibInterface.h
+++ b/src/mesh/RadioLibInterface.h
@@ -161,7 +161,7 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
* timer scaled to SNR of to be flooded packet
* @return Timestamp after which the packet may be sent
*/
- void startTransmitTimerSNR(float snr);
+ void startTransmitTimerRebroadcast(meshtastic_MeshPacket *p);
void handleTransmitInterrupt();
void handleReceiveInterrupt();
diff --git a/src/platform/portduino/SimRadio.cpp b/src/platform/portduino/SimRadio.cpp
index 4e748c5f9..cea1eab3a 100644
--- a/src/platform/portduino/SimRadio.cpp
+++ b/src/platform/portduino/SimRadio.cpp
@@ -43,7 +43,7 @@ void SimRadio::setTransmitDelay()
} 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);
+ startTransmitTimerRebroadcast(p);
}
}
@@ -57,11 +57,11 @@ void SimRadio::startTransmitTimer(bool withDelay)
}
}
-void SimRadio::startTransmitTimerSNR(float snr)
+void SimRadio::startTransmitTimerRebroadcast(meshtastic_MeshPacket *p)
{
// If we have work to do and the timer wasn't already scheduled, schedule it now
if (!txQueue.empty()) {
- uint32_t delayMsec = getTxDelayMsecWeighted(snr);
+ uint32_t delayMsec = getTxDelayMsecWeighted(p);
// LOG_DEBUG("xmit timer %d", delay);
notifyLater(delayMsec, TRANSMIT_DELAY_COMPLETED, false);
}
diff --git a/src/platform/portduino/SimRadio.h b/src/platform/portduino/SimRadio.h
index ea534bd65..d8b53739f 100644
--- a/src/platform/portduino/SimRadio.h
+++ b/src/platform/portduino/SimRadio.h
@@ -64,7 +64,7 @@ class SimRadio : public RadioInterface, protected concurrency::NotifiedWorkerThr
void startTransmitTimer(bool withDelay = true);
/** timer scaled to SNR of to be flooded packet */
- void startTransmitTimerSNR(float snr);
+ void startTransmitTimerRebroadcast(meshtastic_MeshPacket *p);
void handleTransmitInterrupt();
void handleReceiveInterrupt();