mirror of
https://github.com/meshtastic/firmware.git
synced 2025-09-03 10:13:44 +00:00
Compare commits
5 Commits
b8a114ebbf
...
e91651b7ff
Author | SHA1 | Date | |
---|---|---|---|
![]() |
e91651b7ff | ||
![]() |
0383c29383 | ||
![]() |
0589b8384f | ||
![]() |
947b1c9e46 | ||
![]() |
6f2a678a2d |
270
src/mesh/CountingCoverageFilter.cpp
Normal file
270
src/mesh/CountingCoverageFilter.cpp
Normal file
@ -0,0 +1,270 @@
|
||||
#pragma once
|
||||
|
||||
#include "CountingCoverageFilter.h"
|
||||
|
||||
CountingCoverageFilter::CountingCoverageFilter()
|
||||
{
|
||||
clear();
|
||||
instantiationTime_ = getTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an item (node) to this counting bloom filter.
|
||||
* Increments the counters for each hash position (up to the max for BITS_PER_COUNTER).
|
||||
*/
|
||||
void CountingCoverageFilter::add(NodeNum item)
|
||||
{
|
||||
// We'll do BLOOM_HASH_FUNCTIONS hash functions. Typically BLOOM_HASH_FUNCTIONS=2 for simplicity.
|
||||
size_t indices[BLOOM_HASH_FUNCTIONS];
|
||||
computeHashIndices(item, indices);
|
||||
|
||||
for (size_t i = 0; i < BLOOM_HASH_FUNCTIONS; i++) {
|
||||
incrementCounter(indices[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an item (node), decrementing counters at each hash position (if >0).
|
||||
*/
|
||||
void CountingCoverageFilter::remove(NodeNum item)
|
||||
{
|
||||
size_t indices[BLOOM_HASH_FUNCTIONS];
|
||||
computeHashIndices(item, indices);
|
||||
|
||||
for (size_t i = 0; i < BLOOM_HASH_FUNCTIONS; i++) {
|
||||
decrementCounter(indices[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an item "might" be in the set:
|
||||
* - If ALL counters at those BLOOM_HASH_FUNCTIONS positions are > 0,
|
||||
* item is "possible" (false positive possible).
|
||||
* - If ANY position is zero, item is definitely not in the set.
|
||||
*/
|
||||
bool CountingCoverageFilter::check(NodeNum item) const
|
||||
{
|
||||
size_t indices[BLOOM_HASH_FUNCTIONS];
|
||||
computeHashIndices(item, indices);
|
||||
|
||||
for (size_t i = 0; i < BLOOM_HASH_FUNCTIONS; i++) {
|
||||
if (getCounterValue(indices[i]) == 0) {
|
||||
return false; // definitely not in
|
||||
}
|
||||
}
|
||||
return true; // might be in
|
||||
}
|
||||
|
||||
/**
|
||||
* Approximate count of how many items are in the filter.
|
||||
* The naive approach is sum(counters)/BLOOM_HASH_FUNCTIONS. Collisions can inflate this, though.
|
||||
*/
|
||||
float CountingCoverageFilter::approximateCount() const
|
||||
{
|
||||
uint64_t sum = 0;
|
||||
for (size_t i = 0; i < NUM_UNKNOWN_NODE_COUNTERS; i++) {
|
||||
sum += getCounterValue(i);
|
||||
}
|
||||
// We do K increments per item, so a naive estimate is sum/BLOOM_HASH_FUNCTIONS
|
||||
return static_cast<float>(sum) / static_cast<float>(BLOOM_HASH_FUNCTIONS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge (union) this filter with another filter of the same params.
|
||||
* We'll take the max of each counter.
|
||||
* (Alternatively you could add, but max is safer for a union.)
|
||||
*/
|
||||
void CountingCoverageFilter::merge(const CountingCoverageFilter &other)
|
||||
{
|
||||
for (size_t i = 0; i < NUM_UNKNOWN_NODE_COUNTERS; i++) {
|
||||
uint8_t mine = getCounterValue(i);
|
||||
uint8_t theirs = other.getCounterValue(i);
|
||||
uint8_t mergedVal = (mine > theirs) ? mine : theirs;
|
||||
setCounterValue(i, mergedVal);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear out all counters to zero.
|
||||
*/
|
||||
void CountingCoverageFilter::clear()
|
||||
{
|
||||
storage_.fill(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare a standard Bloom (bit-based, e.g., 16 bytes => 128 bits) to see how many bits
|
||||
* are newly set that we do not have a nonzero counter for.
|
||||
* This is purely an approximate approach for "new coverage" bits.
|
||||
*/
|
||||
int CountingCoverageFilter::approximateNewCoverageCount(const CoverageFilter &incoming) const
|
||||
{
|
||||
if (isStale())
|
||||
return 0.0f;
|
||||
|
||||
// 1) Retrieve the bits from the incoming coverage filter
|
||||
const auto &bits = incoming.getBits(); // this is a std::array<uint8_t, BLOOM_FILTER_SIZE_BYTES>
|
||||
size_t coverageByteCount = bits.size(); // typically 16 bytes => 128 bits
|
||||
|
||||
size_t maxBitsToCheck = coverageByteCount * 8;
|
||||
if (maxBitsToCheck > NUM_UNKNOWN_NODE_COUNTERS) {
|
||||
maxBitsToCheck = NUM_UNKNOWN_NODE_COUNTERS;
|
||||
}
|
||||
|
||||
int newCoverageBits = 0;
|
||||
for (size_t bitIndex = 0; bitIndex < maxBitsToCheck; bitIndex++) {
|
||||
size_t byteIndex = bitIndex / 8;
|
||||
uint8_t bitMask = 1 << (bitIndex % 8);
|
||||
|
||||
// Was this bit set in the incoming coverage filter?
|
||||
bool coverageBitSet = (bits[byteIndex] & bitMask) != 0;
|
||||
if (!coverageBitSet) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If our local counter at bitIndex is 0 => "new coverage" bit
|
||||
if (getCounterValue(bitIndex) == 0) {
|
||||
newCoverageBits++;
|
||||
}
|
||||
}
|
||||
return newCoverageBits;
|
||||
}
|
||||
|
||||
float CountingCoverageFilter::approximateCoverageRatio(const CoverageFilter &incoming) const
|
||||
{
|
||||
if (isStale())
|
||||
return 0.0f;
|
||||
|
||||
// 1) How many "new coverage" bits do we see?
|
||||
int newBits = approximateNewCoverageCount(incoming);
|
||||
|
||||
// 2) How many items do we hold, approx?
|
||||
float myApproxCount = approximateCount();
|
||||
if (myApproxCount < 0.00001f) {
|
||||
// Avoid division by zero; or you can return 0 or 1 as suits your logic.
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
// newBits is a bit count, approximateCount() is an item count.
|
||||
// This is a rough ratio. Decide if you want them in the same domain.
|
||||
// We'll treat "newBits" ~ "new items," so ratio = newBits / myApproxCount
|
||||
return static_cast<float>(newBits) / myApproxCount;
|
||||
}
|
||||
|
||||
uint8_t CountingCoverageFilter::getCounterValue(size_t idx) const
|
||||
{
|
||||
assert(idx < NUM_UNKNOWN_NODE_COUNTERS);
|
||||
if (BITS_PER_UNKNOWN_NODE_COUNTER == 8) {
|
||||
// Easiest case: 1 byte per counter
|
||||
return storage_[idx];
|
||||
} else if (BITS_PER_UNKNOWN_NODE_COUNTER == 4) {
|
||||
// 2 counters per byte
|
||||
size_t byteIndex = idx / 2; // each byte holds 2 counters
|
||||
bool second = (idx % 2) == 1; // 0 => lower nibble, 1 => upper nibble
|
||||
uint8_t rawByte = storage_[byteIndex];
|
||||
if (!second) {
|
||||
// lower 4 bits
|
||||
return (rawByte & 0x0F);
|
||||
} else {
|
||||
// upper 4 bits
|
||||
return (rawByte >> 4) & 0x0F;
|
||||
}
|
||||
} else {
|
||||
// If you want to handle other bit widths (2, 3, 16, etc.), you'd do more logic here.
|
||||
static_assert(BITS_PER_UNKNOWN_NODE_COUNTER == 4 || BITS_PER_UNKNOWN_NODE_COUNTER == 8,
|
||||
"Only 4-bit or 8-bit counters allowed.");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the counter at position idx to val (clamped to max representable).
|
||||
*/
|
||||
void CountingCoverageFilter::setCounterValue(size_t idx, uint8_t val)
|
||||
{
|
||||
assert(idx < NUM_UNKNOWN_NODE_COUNTERS);
|
||||
// clamp val
|
||||
uint8_t maxVal = (1 << BITS_PER_UNKNOWN_NODE_COUNTER) - 1; // e.g. 15 for 4 bits, 255 for 8 bits
|
||||
if (val > maxVal)
|
||||
val = maxVal;
|
||||
|
||||
if (BITS_PER_UNKNOWN_NODE_COUNTER == 8) {
|
||||
storage_[idx] = val;
|
||||
} else if (BITS_PER_UNKNOWN_NODE_COUNTER == 4) {
|
||||
size_t byteIndex = idx / 2;
|
||||
bool second = (idx % 2) == 1;
|
||||
uint8_t rawByte = storage_[byteIndex];
|
||||
|
||||
if (!second) {
|
||||
// Lower nibble
|
||||
// clear lower nibble, then set
|
||||
rawByte = (rawByte & 0xF0) | (val & 0x0F);
|
||||
} else {
|
||||
// Upper nibble
|
||||
// clear upper nibble, then set
|
||||
rawByte = (rawByte & 0x0F) | ((val & 0x0F) << 4);
|
||||
}
|
||||
storage_[byteIndex] = rawByte;
|
||||
}
|
||||
}
|
||||
|
||||
bool CountingCoverageFilter::isStale() const
|
||||
{
|
||||
// How long has it been since this filter was created?
|
||||
uint32_t now = getTime();
|
||||
uint32_t age = now - instantiationTime_;
|
||||
return age > STALE_COVERAGE_SECONDS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment the counter at idx by 1 (clamped to max).
|
||||
*/
|
||||
void CountingCoverageFilter::incrementCounter(size_t idx)
|
||||
{
|
||||
// read current
|
||||
uint8_t currVal = getCounterValue(idx);
|
||||
// increment
|
||||
uint8_t nextVal = currVal + 1; // might overflow if at max
|
||||
setCounterValue(idx, nextVal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrement the counter at idx by 1 (if >0).
|
||||
*/
|
||||
void CountingCoverageFilter::decrementCounter(size_t idx)
|
||||
{
|
||||
// read current
|
||||
uint8_t currVal = getCounterValue(idx);
|
||||
if (currVal > 0) {
|
||||
setCounterValue(idx, currVal - 1);
|
||||
}
|
||||
// else do nothing (can't go negative)
|
||||
}
|
||||
|
||||
void CountingCoverageFilter::computeHashIndices(NodeNum value, size_t outIndices[BLOOM_HASH_FUNCTIONS]) const
|
||||
{
|
||||
// We can use two or more seeds for separate hashes. Here we do two seeds as an example.
|
||||
// If BLOOM_HASH_FUNCTIONS > 2, you'd do more seeds or vary the combined approach.
|
||||
static const uint64_t seed1 = 0xDEADBEEF;
|
||||
static const uint64_t seed2 = 0xBADC0FFE;
|
||||
|
||||
outIndices[0] = hashGeneric(value, seed1);
|
||||
if (BLOOM_HASH_FUNCTIONS >= 2) {
|
||||
outIndices[1] = hashGeneric(value, seed2);
|
||||
}
|
||||
// If BLOOM_HASH_FUNCTIONS were greater than 2, we'd have to update similarly for outIndices[2],
|
||||
// outIndices[3], etc. with new seeds
|
||||
}
|
||||
|
||||
size_t CountingCoverageFilter::hashGeneric(NodeNum value, uint64_t seed) const
|
||||
{
|
||||
// Just a simplistic combine of "value" and "seed" then do std::hash<uint64_t>.
|
||||
uint64_t combined = value ^ (seed + (value << 6) + (value >> 2));
|
||||
|
||||
std::hash<uint64_t> hasher;
|
||||
uint64_t hashOut = hasher(combined);
|
||||
|
||||
// Then map to [0..(NUM_UNKNOWN_NODE_COUNTERS-1)]
|
||||
// because each "slot" is an index from 0..NUM_UNKNOWN_NODE_COUNTERS-1
|
||||
return static_cast<size_t>(hashOut % NUM_UNKNOWN_NODE_COUNTERS);
|
||||
}
|
123
src/mesh/CountingCoverageFilter.h
Normal file
123
src/mesh/CountingCoverageFilter.h
Normal file
@ -0,0 +1,123 @@
|
||||
#include "CoverageFilter.h"
|
||||
#include "MeshTypes.h"
|
||||
|
||||
#include <RTC.h>
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* A generic Counting Coverage (bloom) filter, which can be parameterized by:
|
||||
* - NUM_UNKNOWN_NODE_COUNTERS (how many counter "slots")
|
||||
* - BITS_PER_UNKNOWN_NODE_COUNTER (4 bits, 8 bits, etc.)
|
||||
* - BLOOM_HASH_FUNCTIONS (number of hash functions, typically 2 or more)
|
||||
*
|
||||
*/
|
||||
|
||||
// We have NUM_UNKNOWN_NODE_COUNTERS total "slots," and each slot is BITS_PER_UNKNOWN_NODE_COUNTER wide.
|
||||
// For BITS_PER_UNKNOWN_NODE_COUNTER=4, each slot can hold 0..15.
|
||||
// We store these slots in a byte array sized for the total number of bits.
|
||||
// 1) Calculate how many total bits we need:
|
||||
#define STORAGE_BITS NUM_UNKNOWN_NODE_COUNTERS *BITS_PER_UNKNOWN_NODE_COUNTER
|
||||
|
||||
// 2) Convert that to bytes (rounding up)
|
||||
#define STORAGE_BYTES ((STORAGE_BITS + 7) / 8) // integer ceiling division
|
||||
|
||||
class CountingCoverageFilter
|
||||
{
|
||||
public:
|
||||
CountingCoverageFilter();
|
||||
|
||||
/**
|
||||
* Add an item (node) to this counting bloom filter.
|
||||
* Increments the counters for each hash position (up to the max for BITS_PER_COUNTER).
|
||||
*/
|
||||
void add(NodeNum item);
|
||||
|
||||
/**
|
||||
* Remove an item (node), decrementing counters at each hash position (if >0).
|
||||
*/
|
||||
void remove(NodeNum item);
|
||||
|
||||
/**
|
||||
* Check if an item "might" be in the set:
|
||||
* - If ALL counters at those BLOOM_HASH_FUNCTIONS positions are > 0,
|
||||
* item is "possible" (false positive possible).
|
||||
* - If ANY position is zero, item is definitely not in the set.
|
||||
*/
|
||||
bool check(NodeNum item) const;
|
||||
|
||||
/**
|
||||
* Approximate count of how many items are in the filter.
|
||||
* The naive approach is sum(counters)/BLOOM_HASH_FUNCTIONS. Collisions can inflate this, though.
|
||||
*/
|
||||
float approximateCount() const;
|
||||
|
||||
/**
|
||||
* Merge (union) this filter with another filter of the same params.
|
||||
* We'll take the max of each counter.
|
||||
* (Alternatively you could add, but max is safer for a union.)
|
||||
*/
|
||||
void merge(const CountingCoverageFilter &other);
|
||||
|
||||
/**
|
||||
* Clear out all counters to zero.
|
||||
*/
|
||||
void clear();
|
||||
|
||||
/**
|
||||
* Compare a standard Bloom (bit-based, e.g., 16 bytes => 128 bits) to see how many bits
|
||||
* are newly set that we do not have a nonzero counter for.
|
||||
* This is purely an approximate approach for "new coverage" bits.
|
||||
*/
|
||||
int approximateNewCoverageCount(const CoverageFilter &incoming) const;
|
||||
|
||||
/**
|
||||
* Compare a standard Bloom (bit-based, e.g., 16 bytes => 128 bits) to see how many bits
|
||||
* are newly set that we do not have a nonzero counter for, vs. total approx. input
|
||||
* This is purely an approximate approach for "new coverage" ratio.
|
||||
*/
|
||||
float approximateCoverageRatio(const CoverageFilter &incoming) const;
|
||||
|
||||
private:
|
||||
uint32_t instantiationTime_;
|
||||
|
||||
/**
|
||||
* The storage array, sized for all counters combined.
|
||||
* e.g. for NUM_UNKNOWN_NODE_COUNTERS=64, BITS_PER_UNKNOWN_NODE_COUNTER=4 => 64*4=256 bits => 256/8=32 bytes.
|
||||
*/
|
||||
std::array<uint8_t, STORAGE_BYTES> storage_;
|
||||
|
||||
/**
|
||||
* Retrieve the integer value of the counter at position idx
|
||||
* (0 <= idx < NUM_UNKNOWN_NODE_COUNTERS).
|
||||
*/
|
||||
uint8_t getCounterValue(size_t idx) const;
|
||||
|
||||
/**
|
||||
* Set the counter at position idx to val (clamped to max representable).
|
||||
*/
|
||||
void setCounterValue(size_t idx, uint8_t val);
|
||||
|
||||
/**
|
||||
* Returns true if this instance is stale (based on instantiation time).
|
||||
*/
|
||||
bool isStale() const;
|
||||
|
||||
/**
|
||||
* Increment the counter at idx by 1 (clamped to max).
|
||||
*/
|
||||
void incrementCounter(size_t idx);
|
||||
|
||||
/**
|
||||
* Decrement the counter at idx by 1 (if >0).
|
||||
*/
|
||||
void decrementCounter(size_t idx);
|
||||
|
||||
void computeHashIndices(NodeNum value, size_t outIndices[BLOOM_HASH_FUNCTIONS]) const;
|
||||
|
||||
size_t hashGeneric(NodeNum value, uint64_t seed) const;
|
||||
};
|
@ -1,3 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "MeshTypes.h"
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
|
@ -76,7 +76,10 @@ bool FloodingRouter::perhapsRebroadcast(const meshtastic_MeshPacket *p)
|
||||
CoverageFilter incomingCoverage;
|
||||
loadCoverageFilterFromPacket(p, incomingCoverage);
|
||||
|
||||
float forwardProb = calculateForwardProbability(incomingCoverage, p->from);
|
||||
CoverageFilter updatedCoverage = incomingCoverage;
|
||||
mergeMyCoverage(updatedCoverage);
|
||||
|
||||
float forwardProb = calculateForwardProbability(incomingCoverage, updatedCoverage, p->from);
|
||||
|
||||
float rnd = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);
|
||||
if (rnd <= forwardProb) {
|
||||
@ -92,8 +95,6 @@ bool FloodingRouter::perhapsRebroadcast(const meshtastic_MeshPacket *p)
|
||||
}
|
||||
#endif
|
||||
|
||||
CoverageFilter updatedCoverage = incomingCoverage;
|
||||
mergeMyCoverage(updatedCoverage);
|
||||
storeCoverageFilterInPacket(updatedCoverage, tosend);
|
||||
|
||||
LOG_INFO("Rebroadcasting packet ID=0x%x with ForwardProb=%.2f", p->id, forwardProb);
|
||||
@ -155,9 +156,12 @@ void FloodingRouter::mergeMyCoverage(CoverageFilter &coverage)
|
||||
for (auto &nodeId : recentNeighbors) {
|
||||
coverage.add(nodeId);
|
||||
}
|
||||
|
||||
// Always add ourselves to prevent a rebroadcast for a packet we've already seen
|
||||
coverage.add(nodeDB->getNodeNum());
|
||||
}
|
||||
|
||||
float FloodingRouter::calculateForwardProbability(const CoverageFilter &incoming, NodeNum from)
|
||||
float FloodingRouter::calculateForwardProbability(const CoverageFilter &incoming, const CoverageFilter &updated, NodeNum from)
|
||||
{
|
||||
#ifdef USERPREFS_USE_COVERAGE_FILTER
|
||||
bool useCoverageFilter = USERPREFS_USE_COVERAGE_FILTER;
|
||||
@ -165,8 +169,10 @@ float FloodingRouter::calculateForwardProbability(const CoverageFilter &incoming
|
||||
return 1.0f;
|
||||
#endif
|
||||
// If we are a router or repeater, always forward because it's assumed these are in the most advantageous locations
|
||||
// or if we haven't heard from any other nodes within the stale coverage time, fall back to always forward
|
||||
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 ||
|
||||
nodeDB->secondsSinceLastNodeHeard() >= STALE_COVERAGE_SECONDS) {
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
@ -200,31 +206,21 @@ float FloodingRouter::calculateForwardProbability(const CoverageFilter &incoming
|
||||
if (neighbors > 0) {
|
||||
coverageRatio = static_cast<float>(uncovered) / static_cast<float>(neighbors);
|
||||
}
|
||||
float forwardProb = BASE_FORWARD_PROB;
|
||||
|
||||
/* BEGIN OPTION 1: forward probability is based on coverage ratio and scales up or down
|
||||
* depending on the extent to which this node provides new coverage
|
||||
*/
|
||||
/* END OPTION 1 */
|
||||
forwardProb = BASE_FORWARD_PROB + (coverageRatio * COVERAGE_SCALE_FACTOR);
|
||||
// Compare our unknown node coverage filter to our updated coverage filter
|
||||
// We use the updated coverage filter because we don't want to double count nodes
|
||||
// that have already made it into the main in memory nodedb storage mechanism
|
||||
float unknownNodeCoverageRatio = nodeDB->getUnknownCoverage().approximateCoverageRatio(updated);
|
||||
|
||||
/* BEGIN OPTION 2: forward probability is piecewise logic:
|
||||
* - Reduce to BASE_FORWARD_PROB if no new coverage is likely
|
||||
* (remember false positive rate of bloom filter means a node that think its neighbor is covered when it isnt)
|
||||
* - If 1 uncovered neighbor, ramp probability up significantly to 0.8
|
||||
* - If more than 1 uncovered neighbor, ramp probability up to 1.0
|
||||
* if (uncovered == 1) {
|
||||
* forwardProb = 0.8f;
|
||||
* } else if (uncovered > 1) {
|
||||
* forwardProb = 1.0f;
|
||||
* }
|
||||
*/
|
||||
/* END OPTION 2 */
|
||||
// unknownNodeCoverageRatio is inherently iffy so don't scale up its contribution to the probability of rebroadcast
|
||||
// This essentially makes the forward probability non-zero for nodes that have a set of "unknown" neighbors
|
||||
float forwardProb = BASE_FORWARD_PROB + (coverageRatio * COVERAGE_SCALE_FACTOR) + unknownNodeCoverageRatio;
|
||||
|
||||
// Clamp probability between 0 and 1
|
||||
forwardProb = std::min(std::max(forwardProb, 0.0f), 1.0f);
|
||||
|
||||
LOG_DEBUG("CoverageRatio=%.2f, ForwardProb=%.2f (Uncovered=%d, Total=%zu)", coverageRatio, forwardProb, uncovered, neighbors);
|
||||
LOG_DEBUG("CoverageRatio=%.2f, UnknownNodeCoverageRatio=%.2f, ForwardProb=%.2f (Uncovered=%d, Total=%zu)", coverageRatio,
|
||||
unknownNodeCoverageRatio, forwardProb, uncovered, neighbors);
|
||||
|
||||
return forwardProb;
|
||||
}
|
@ -42,7 +42,7 @@ class FloodingRouter : public Router, protected PacketHistory
|
||||
|
||||
void mergeMyCoverage(CoverageFilter &coverage);
|
||||
|
||||
float calculateForwardProbability(const CoverageFilter &incoming, NodeNum from);
|
||||
float calculateForwardProbability(const CoverageFilter &incoming, const CoverageFilter &updated, NodeNum from);
|
||||
|
||||
public:
|
||||
/**
|
||||
|
@ -43,15 +43,15 @@ enum RxSource {
|
||||
// Maximum number of neighbors a node adds to the Bloom filter per hop
|
||||
#define MAX_NEIGHBORS_PER_HOP 20
|
||||
|
||||
// Number of seconds after which coverage is considered stale
|
||||
#define STALE_COVERAGE_SECONDS 60 * 60 * 0.5 // 1 hour
|
||||
|
||||
// Size of the Bloom filter in bytes (128 bits)
|
||||
#define BLOOM_FILTER_SIZE_BYTES 16
|
||||
|
||||
// Size of the Bloom filter in bits (128 bits)
|
||||
#define BLOOM_FILTER_SIZE_BITS (BLOOM_FILTER_SIZE_BYTES * 8)
|
||||
|
||||
// Number of hash functions to use in the Bloom filter
|
||||
#define NUM_HASH_FUNCTIONS 2
|
||||
|
||||
// Base forwarding probability - never drop below this value
|
||||
// 0.2 could be suitable because the worst case False Positive Rate of the
|
||||
// coverage filter is 37%. That's if its saturated with 60 unique nodes.
|
||||
@ -64,6 +64,12 @@ enum RxSource {
|
||||
// Currently set to 1 hour because that is the minimum interval for nodeinfo broadcasts
|
||||
#define RECENCY_THRESHOLD_MINUTES 60
|
||||
|
||||
#define NUM_UNKNOWN_NODE_COUNTERS 64
|
||||
|
||||
#define BITS_PER_UNKNOWN_NODE_COUNTER 4
|
||||
|
||||
#define BLOOM_HASH_FUNCTIONS 2
|
||||
|
||||
typedef int ErrorCode;
|
||||
|
||||
/// Alloc and free packets to our global, ISR safe pool
|
||||
|
@ -895,11 +895,30 @@ std::vector<NodeNum> NodeDB::getDistinctRecentDirectNeighborIds(uint32_t timeWin
|
||||
return recentNeighbors;
|
||||
}
|
||||
|
||||
uint32_t NodeDB::secondsSinceLastNodeHeard()
|
||||
{
|
||||
// If maxLastHeard_ == 0, we have not heard from any remote node
|
||||
if (maxLastHeard_ == 0) {
|
||||
return UINT32_MAX;
|
||||
}
|
||||
|
||||
uint32_t now = getTime();
|
||||
// If the clock isn’t set or has jumped backwards, clamp to 0
|
||||
if (now < maxLastHeard_) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (now - maxLastHeard_);
|
||||
}
|
||||
|
||||
void NodeDB::cleanupMeshDB()
|
||||
{
|
||||
int newPos = 0, removed = 0;
|
||||
for (int i = 0; i < numMeshNodes; i++) {
|
||||
if (meshNodes->at(i).has_user) {
|
||||
if (meshNodes->at(i).last_heard > maxLastHeard_) {
|
||||
maxLastHeard_ = meshNodes->at(i).last_heard;
|
||||
}
|
||||
if (meshNodes->at(i).user.public_key.size > 0) {
|
||||
if (memfll(meshNodes->at(i).user.public_key.bytes, 0, meshNodes->at(i).user.public_key.size)) {
|
||||
meshNodes->at(i).user.public_key.size = 0;
|
||||
@ -907,6 +926,14 @@ void NodeDB::cleanupMeshDB()
|
||||
}
|
||||
meshNodes->at(newPos++) = meshNodes->at(i);
|
||||
} else {
|
||||
// Check if this unknown node is a direct neighbor (hops_away == 0)
|
||||
if (meshNodes->at(i).has_hops_away && meshNodes->at(i).hops_away == 0) {
|
||||
// ADD for unknown coverage:
|
||||
// If this node doesn't have user data, we consider it "unknown"
|
||||
// and add it to the unknownCoverage_ filter:
|
||||
unknownCoverage_.add(meshNodes->at(i).num);
|
||||
}
|
||||
|
||||
removed++;
|
||||
}
|
||||
}
|
||||
@ -1483,8 +1510,12 @@ void NodeDB::updateFrom(const meshtastic_MeshPacket &mp)
|
||||
return;
|
||||
}
|
||||
|
||||
if (mp.rx_time) // if the packet has a valid timestamp use it to update our last_heard
|
||||
if (mp.rx_time) { // if the packet has a valid timestamp use it to update our last_heard
|
||||
info->last_heard = mp.rx_time;
|
||||
if (info->last_heard > maxLastHeard_) {
|
||||
maxLastHeard_ = info->last_heard;
|
||||
}
|
||||
}
|
||||
|
||||
if (mp.rx_snr)
|
||||
info->snr = mp.rx_snr; // keep the most recent SNR we received for this node.
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include <assert.h>
|
||||
#include <vector>
|
||||
|
||||
#include "CountingCoverageFilter.h"
|
||||
#include "MeshTypes.h"
|
||||
#include "NodeStatus.h"
|
||||
#include "configuration.h"
|
||||
@ -176,8 +177,15 @@ class NodeDB
|
||||
*/
|
||||
std::vector<NodeNum> getDistinctRecentDirectNeighborIds(uint32_t timeWindowSecs);
|
||||
|
||||
uint32_t secondsSinceLastNodeHeard();
|
||||
|
||||
const CountingCoverageFilter &getUnknownCoverage() const { return unknownCoverage_; }
|
||||
|
||||
private:
|
||||
uint32_t lastNodeDbSave = 0; // when we last saved our db to flash
|
||||
uint32_t maxLastHeard_ = 0; // the most recent last_heard value we've seen
|
||||
CountingCoverageFilter unknownCoverage_;
|
||||
|
||||
/// Find a node in our DB, create an empty NodeInfoLite if missing
|
||||
meshtastic_NodeInfoLite *getOrCreateMeshNode(NodeNum n);
|
||||
|
||||
|
@ -45,7 +45,7 @@ typedef struct {
|
||||
uint8_t relay_node;
|
||||
|
||||
// A 16-byte Bloom filter that tracks coverage of the current node.
|
||||
uint8_t coverage_filter[BLOOM_FILTER_SIZE_BYTES] __attribute__((__aligned));
|
||||
uint8_t coverage_filter[BLOOM_FILTER_SIZE_BYTES] __attribute__((__aligned__));
|
||||
} PacketHeader;
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user