Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Probabilistic Forwarding - Using Heuristic Analysis of Network Conditions to Reduce Load #5629

Closed
wants to merge 15 commits into from
Closed
9 changes: 9 additions & 0 deletions src/mesh/Channels.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,15 @@ void Channels::setChannel(const meshtastic_Channel &c)
old = c; // slam in the new settings/role
}

int8_t Channels::getIndexByHash(ChannelHash channelHash)
{
for (int i = 0; i < getNumChannels(); i++)
if (getHash(i) == channelHash)
return i;

return -1;
}

bool Channels::anyMqttEnabled()
{
#if USERPREFS_EVENT_MODE
Expand Down
6 changes: 3 additions & 3 deletions src/mesh/Channels.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,16 +92,16 @@ class Channels
// Returns true if any of our channels have enabled MQTT uplink or downlink
bool anyMqttEnabled();

/** Return the channel index for the specified channel hash, or -1 for not found */
int8_t getIndexByHash(ChannelHash channelHash);

private:
/** Given a channel index, change to use the crypto key specified by that index
*
* @eturn the (0 to 255) hash for that channel - if no suitable channel could be found, return -1
*/
int16_t setCrypto(ChannelIndex chIndex);

/** Return the channel index for the specified channel hash, or -1 for not found */
int8_t getIndexByHash(ChannelHash channelHash);

/** Given a channel number, return the (0 to 255) hash for that channel
* If no suitable channel could be found, return -1
*
Expand Down
67 changes: 55 additions & 12 deletions src/mesh/FloodingRouter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,23 +58,29 @@ bool FloodingRouter::perhapsRebroadcast(const meshtastic_MeshPacket *p)
if (!isToUs(p) && (p->hop_limit > 0) && !isFromUs(p)) {
if (p->id != 0) {
if (isRebroadcaster()) {
meshtastic_MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it
float forwardProb = calculateForwardProbability(p);
float rnd = (float)rand() / (float)RAND_MAX;
if (rnd <= forwardProb) {
meshtastic_MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it

tosend->hop_limit--; // bump down the hop count
tosend->hop_limit--; // bump down the hop count
#if USERPREFS_EVENT_MODE
if (tosend->hop_limit > 2) {
// if we are "correcting" the hop_limit, "correct" the hop_start by the same amount to preserve hops away.
tosend->hop_start -= (tosend->hop_limit - 2);
tosend->hop_limit = 2;
}
if (tosend->hop_limit > 2) {
// if we are "correcting" the hop_limit, "correct" the hop_start by the same amount to preserve hops away.
tosend->hop_start -= (tosend->hop_limit - 2);
tosend->hop_limit = 2;
}
#endif

LOG_INFO("Rebroadcast received floodmsg");
// Note: we are careful to resend using the original senders node id
// We are careful not to call our hooked version of send() - because we don't want to check this again
Router::send(tosend);
LOG_INFO("Rebroadcast received floodmsg");
// Note: we are careful to resend using the original senders node id
// We are careful not to call our hooked version of send() - because we don't want to check this again
Router::send(tosend);

return true;
return true;
} else {
LOG_DEBUG("No rebroadcast: Random number %f > Forward Probability %f", rnd, forwardProb);
}
} else {
LOG_DEBUG("No rebroadcast: Role = CLIENT_MUTE or Rebroadcast Mode = NONE");
}
Expand All @@ -99,4 +105,41 @@ void FloodingRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtas

// handle the packet as normal
Router::sniffReceived(p, c);
}

float FloodingRouter::calculateForwardProbability(const meshtastic_MeshPacket *p)
{
// Routers and repeaters always forward, so skip expensive calcs and return the highest value
if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ||
config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER) {
return 1.0f;
}

uint8_t RECENCY_THRESHOLD_MINUTES = 5;
size_t neighborCount = nodeDB->getDistinctRecentDirectNeighborCount((RECENCY_THRESHOLD_MINUTES * 60));

float NEIGHBOR_INFLUENCE_FACTOR = 0.1f;
float neighborFactor = 1.0f / (1.0f + neighborCount * NEIGHBOR_INFLUENCE_FACTOR);

float REDUNDANCY_INFLUENCE_FACTOR = 0.05f;
int distinctSources = getDistinctSourcesCount(p->id);
float redundancyFactor = 1.0f / (1.0f + distinctSources * REDUNDANCY_INFLUENCE_FACTOR);

float LOAD_INFLUENCE_FACTOR = 0.01f;
float LOAD_THRESHOLD_MINUTES = 2;
float recentPacketRate = getRecentUniquePacketRate(LOAD_THRESHOLD_MINUTES * 60 * 1000);
float loadFactor = 1.0f / (1.0f + recentPacketRate * LOAD_INFLUENCE_FACTOR);

// Start from a base probability
float BASELINE_FORWARDING_PROBABILITY = 1.0f;
float prob = BASELINE_FORWARDING_PROBABILITY;

// Adjust with observed factors
prob *= neighborFactor;
prob *= redundancyFactor;
prob *= loadFactor;

// Clamp probability
prob = min(max(prob, 0.0f), 1.0f);
return prob;
}
2 changes: 2 additions & 0 deletions src/mesh/FloodingRouter.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ class FloodingRouter : public Router, protected PacketHistory
* @return true if rebroadcasted */
bool perhapsRebroadcast(const meshtastic_MeshPacket *p);

float calculateForwardProbability(const meshtastic_MeshPacket *p);

public:
/**
* Constructor
Expand Down
34 changes: 34 additions & 0 deletions src/mesh/NodeDB.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -807,6 +807,40 @@ void NodeDB::clearLocalPosition()
setLocalPosition(meshtastic_Position_init_default);
}

size_t NodeDB::getDistinctRecentDirectNeighborCount(uint32_t timeWindowSecs)
{
uint32_t now = getTime();
size_t count = 0;
for (int i = 0; i < numMeshNodes; i++) {
const meshtastic_NodeInfoLite &node = meshNodes->at(i);

// Skip our own node entry
if (node.num == getNodeNum()) {
continue;
}

// Skip ignored nodes
if (node.is_ignored) {
continue;
}

// Check if this node is a direct neighbor (hops_away == 0)
if (!node.has_hops_away || node.hops_away != 0) {
continue;
}

if (node.via_mqtt) {
continue;
}

// Check if the node was heard recently
if (node.last_heard > 0 && (now - node.last_heard <= timeWindowSecs)) {
count++;
}
}
return count;
}

void NodeDB::cleanupMeshDB()
{
int newPos = 0, removed = 0;
Expand Down
3 changes: 3 additions & 0 deletions src/mesh/NodeDB.h
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,9 @@ class NodeDB

bool hasValidPosition(const meshtastic_NodeInfoLite *n);

/// get the number of recent nodes we've directly heard from
size_t getDistinctRecentDirectNeighborCount(uint32_t timeWindowSecs);

private:
uint32_t lastNodeDbSave = 0; // when we last saved our db to flash
/// Find a node in our DB, create an empty NodeInfoLite if missing
Expand Down
28 changes: 28 additions & 0 deletions src/mesh/PacketHistory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,34 @@ PacketHistory::PacketHistory()
// setup our periodic task
}

// Determines the number of distinct sources that have sent a packet with the given ID
size_t PacketHistory::getDistinctSourcesCount(PacketId targetId)
{
std::unordered_set<NodeNum> distinctSenders;
for (auto &record : recentPackets) {
if (record.id == targetId) {
distinctSenders.insert(record.sender);
}
}
return distinctSenders.size();
}

// Returns the rate of unique packets received in the last windowMs milliseconds
float PacketHistory::getRecentUniquePacketRate(uint32_t windowMs)
{
uint32_t now = millis();
std::unordered_set<PacketId> uniqueIds;

for (auto &record : recentPackets) {
if (now - record.rxTimeMsec <= windowMs) {
uniqueIds.insert(record.id);
}
}

float windowSec = (float)windowMs / 1000.0f;
return (windowSec > 0) ? (uniqueIds.size() / windowSec) : 0.0f;
}

/**
* Update recentBroadcasts and return true if we have already seen this packet
*/
Expand Down
6 changes: 5 additions & 1 deletion src/mesh/PacketHistory.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,14 @@ class PacketHistory
public:
PacketHistory();

size_t getDistinctSourcesCount(PacketId targetId);

float getRecentUniquePacketRate(uint32_t windowMs);

/**
* Update recentBroadcasts and return true if we have already seen this packet
*
* @param withUpdate if true and not found we add an entry to recentPackets
*/
bool wasSeenRecently(const meshtastic_MeshPacket *p, bool withUpdate = true);
};
};
9 changes: 9 additions & 0 deletions src/modules/NodeInfoModule.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "NodeInfoModule.h"
#include "Default.h"
#include "MeshService.h"
#include "Channels.h"
#include "NodeDB.h"
#include "RTC.h"
#include "Router.h"
Expand Down Expand Up @@ -53,6 +54,14 @@ void NodeInfoModule::sendOurNodeInfo(NodeNum dest, bool wantReplies, uint8_t cha
LOG_DEBUG("Send ourNodeInfo to channel %d", channel);
p->channel = channel;
}
#ifdef USERPREFS_NODEINFO_BROADCAST_CHANNEL_HASH
// If this is a broadcast over the default channel, we can safely change this to the discovery channel if defined
if (dest == NODENUM_BROADCAST && channel == 0) {
int8_t discoveryChannelIdx = channels.getIndexByHash((ChannelHash)USERPREFS_NODEINFO_BROADCAST_CHANNEL_HASH);
// Fallback to primary channel if discovery channel is not found by its hash
p->channel = discoveryChannelIdx > 0 ? discoveryChannelIdx : channel;
}
#endif

prevPacketId = p->id;

Expand Down
1 change: 1 addition & 0 deletions userPrefs.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,5 @@
// "USERPREFS_USE_ADMIN_KEY_0": "{ 0xcd, 0xc0, 0xb4, 0x3c, 0x53, 0x24, 0xdf, 0x13, 0xca, 0x5a, 0xa6, 0x0c, 0x0d, 0xec, 0x85, 0x5a, 0x4c, 0xf6, 0x1a, 0x96, 0x04, 0x1a, 0x3e, 0xfc, 0xbb, 0x8e, 0x33, 0x71, 0xe5, 0xfc, 0xff, 0x3c }",
// "USERPREFS_USE_ADMIN_KEY_1": "{}",
// "USERPREFS_USE_ADMIN_KEY_2": "{}"
// "USERPREFS_NODEINFO_BROADCAST_CHANNEL_HASH": "8"
}
Loading