Merge branch 'develop' into fix/hang-after-app-config-reboot

This commit is contained in:
Ethac.chen
2026-03-14 17:00:17 +08:00
committed by GitHub
20 changed files with 3137 additions and 10 deletions
@@ -87,6 +87,9 @@
</screenshots>
<releases>
<release version="2.7.21" date="2026-03-11">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.21</url>
</release>
<release version="2.7.20" date="2026-02-11">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.20</url>
</release>
+6
View File
@@ -1,3 +1,9 @@
meshtasticd (2.7.21.0) unstable; urgency=medium
* Version 2.7.21
-- GitHub Actions <github-actions[bot]@users.noreply.github.com> Wed, 11 Mar 2026 11:45:36 +0000
meshtasticd (2.7.20.0) unstable; urgency=medium
* Version 2.7.20
+1
View File
@@ -499,6 +499,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define MESHTASTIC_EXCLUDE_REMOTEHARDWARE 1
#define MESHTASTIC_EXCLUDE_STOREFORWARD 1
#define MESHTASTIC_EXCLUDE_TEXTMESSAGE 1
#define MESHTASTIC_EXCLUDE_TRAFFIC_MANAGEMENT 1
#define MESHTASTIC_EXCLUDE_ATAK 1
#define MESHTASTIC_EXCLUDE_CANNEDMESSAGES 1
#define MESHTASTIC_EXCLUDE_NEIGHBORINFO 1
+5
View File
@@ -30,6 +30,11 @@
#define min_node_info_broadcast_secs 60 * 60 // No regular broadcasts of more than once an hour
#define min_neighbor_info_broadcast_secs 4 * 60 * 60
#define default_map_publish_interval_secs 60 * 60
// Traffic management defaults
#define default_traffic_mgmt_position_precision_bits 24 // ~10m grid cells
#define default_traffic_mgmt_position_min_interval_secs ONE_DAY // 1 day between identical positions
#ifdef USERPREFS_RINGTONE_NAG_SECS
#define default_ringtone_nag_secs USERPREFS_RINGTONE_NAG_SECS
#else
+1 -1
View File
@@ -8,7 +8,7 @@ typedef enum { GC1109_PA, KCT8103L_PA, OTHER_FEM_TYPES } LoRaFEMType;
class LoRaFEMInterface
{
public:
LoRaFEMInterface() {}
LoRaFEMInterface() : fem_type(OTHER_FEM_TYPES) {}
virtual ~LoRaFEMInterface() {}
void init(void);
void setSleepModeEnable(void);
+19 -3
View File
@@ -4,6 +4,9 @@
#if !MESHTASTIC_EXCLUDE_TRACEROUTE
#include "modules/TraceRouteModule.h"
#endif
#if HAS_TRAFFIC_MANAGEMENT
#include "modules/TrafficManagementModule.h"
#endif
#include "NodeDB.h"
NextHopRouter::NextHopRouter() {}
@@ -126,15 +129,28 @@ void NextHopRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtast
/* Check if we should be rebroadcasting this packet if so, do so. */
bool NextHopRouter::perhapsRebroadcast(const meshtastic_MeshPacket *p)
{
if (!isToUs(p) && !isFromUs(p) && p->hop_limit > 0) {
// Check if traffic management wants to exhaust this packet's hops
bool exhaustHops = false;
#if HAS_TRAFFIC_MANAGEMENT
if (trafficManagementModule && trafficManagementModule->shouldExhaustHops(*p)) {
exhaustHops = true;
}
#endif
// Allow rebroadcast if hop_limit > 0 OR if we're exhausting hops (which sets hop_limit = 0 but still needs one relay)
if (!isToUs(p) && !isFromUs(p) && (p->hop_limit > 0 || exhaustHops)) {
if (p->id != 0) {
if (isRebroadcaster()) {
if (p->next_hop == NO_NEXT_HOP_PREFERENCE || p->next_hop == nodeDB->getLastByteOfNodeNum(getNodeNum())) {
meshtastic_MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it
LOG_INFO("Rebroadcast received message coming from %x", p->relay_node);
// Use shared logic to determine if hop_limit should be decremented
if (shouldDecrementHopLimit(p)) {
// If exhausting hops, force hop_limit = 0 regardless of other logic
if (exhaustHops) {
tosend->hop_limit = 0;
LOG_INFO("Traffic management: exhausting hops for 0x%08x, setting hop_limit=0", getFrom(p));
} else if (shouldDecrementHopLimit(p)) {
// Use shared logic to determine if hop_limit should be decremented
tosend->hop_limit--; // bump down the hop count
} else {
LOG_INFO("favorite-ROUTER/CLIENT_BASE-to-ROUTER/CLIENT_BASE rebroadcast: preserving hop_limit");
+5
View File
@@ -449,6 +449,11 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
fromRadioScratch.moduleConfig.which_payload_variant = meshtastic_ModuleConfig_paxcounter_tag;
fromRadioScratch.moduleConfig.payload_variant.paxcounter = moduleConfig.paxcounter;
break;
case meshtastic_ModuleConfig_traffic_management_tag:
LOG_DEBUG("Send module config: traffic management");
fromRadioScratch.moduleConfig.which_payload_variant = meshtastic_ModuleConfig_traffic_management_tag;
fromRadioScratch.moduleConfig.payload_variant.traffic_management = moduleConfig.traffic_management;
break;
default:
LOG_ERROR("Unknown module config type %d", config_state);
}
+17
View File
@@ -11,6 +11,9 @@
#include "mesh-pb-constants.h"
#include "meshUtils.h"
#include "modules/RoutingModule.h"
#if HAS_TRAFFIC_MANAGEMENT
#include "modules/TrafficManagementModule.h"
#endif
#if !MESHTASTIC_EXCLUDE_MQTT
#include "mqtt/MQTT.h"
#endif
@@ -95,6 +98,20 @@ bool Router::shouldDecrementHopLimit(const meshtastic_MeshPacket *p)
return true;
}
#if HAS_TRAFFIC_MANAGEMENT
// When router_preserve_hops is enabled, preserve hops for decoded packets that are not
// position or telemetry (those have their own exhaust_hop controls).
if (moduleConfig.has_traffic_management && moduleConfig.traffic_management.enabled &&
moduleConfig.traffic_management.router_preserve_hops && p->which_payload_variant == meshtastic_MeshPacket_decoded_tag &&
p->decoded.portnum != meshtastic_PortNum_POSITION_APP && p->decoded.portnum != meshtastic_PortNum_TELEMETRY_APP) {
LOG_DEBUG("Router hop preserved: port=%d from=0x%08x (traffic_management)", p->decoded.portnum, getFrom(p));
if (trafficManagementModule) {
trafficManagementModule->recordRouterHopPreserved();
}
return false;
}
#endif
// For subsequent hops, check if previous relay is a favorite router
// Optimized search for favorite routers with matching last byte
// Check ordering optimized for IoT devices (cheapest checks first)
+17 -1
View File
@@ -69,6 +69,22 @@ static inline int get_max_num_nodes()
/// Max number of channels allowed
#define MAX_NUM_CHANNELS (member_size(meshtastic_ChannelFile, channels) / member_size(meshtastic_ChannelFile, channels[0]))
// Traffic Management module configuration
// Enable per-variant by defining HAS_TRAFFIC_MANAGEMENT=1 in variant.h
#ifndef HAS_TRAFFIC_MANAGEMENT
#define HAS_TRAFFIC_MANAGEMENT 0
#endif
// Cache size for traffic management (number of nodes to track)
// Can be overridden per-variant based on available memory
#ifndef TRAFFIC_MANAGEMENT_CACHE_SIZE
#if HAS_TRAFFIC_MANAGEMENT
#define TRAFFIC_MANAGEMENT_CACHE_SIZE 1000
#else
#define TRAFFIC_MANAGEMENT_CACHE_SIZE 0
#endif
#endif
/// helper function for encoding a record as a protobuf, any failures to encode are fatal and we will panic
/// returns the encoded packet size
size_t pb_encode_to_bytes(uint8_t *destbuf, size_t destbufsize, const pb_msgdesc_t *fields, const void *src_struct);
@@ -90,4 +106,4 @@ bool writecb(pb_ostream_t *stream, const uint8_t *buf, size_t count);
*/
bool is_in_helper(uint32_t n, const uint32_t *array, pb_size_t count);
#define is_in_repeated(name, n) is_in_helper(n, name, name##_count)
#define is_in_repeated(name, n) is_in_helper(n, name, name##_count)
+10
View File
@@ -1008,6 +1008,11 @@ bool AdminModule::handleSetModuleConfig(const meshtastic_ModuleConfig &c)
moduleConfig.statusmessage = c.payload_variant.statusmessage;
shouldReboot = false;
break;
case meshtastic_ModuleConfig_traffic_management_tag:
LOG_INFO("Set module config: Traffic Management");
moduleConfig.has_traffic_management = true;
moduleConfig.traffic_management = c.payload_variant.traffic_management;
break;
}
saveChanges(SEGMENT_MODULECONFIG, shouldReboot);
return true;
@@ -1193,6 +1198,11 @@ void AdminModule::handleGetModuleConfig(const meshtastic_MeshPacket &req, const
res.get_module_config_response.which_payload_variant = meshtastic_ModuleConfig_statusmessage_tag;
res.get_module_config_response.payload_variant.statusmessage = moduleConfig.statusmessage;
break;
case meshtastic_AdminMessage_ModuleConfigType_TRAFFICMANAGEMENT_CONFIG:
LOG_INFO("Get module config: Traffic Management");
res.get_module_config_response.which_payload_variant = meshtastic_ModuleConfig_traffic_management_tag;
res.get_module_config_response.payload_variant.traffic_management = moduleConfig.traffic_management;
break;
}
// NOTE: The phone app needs to know the ls_secsvalue so it can properly expect sleep behavior.
+11
View File
@@ -38,6 +38,9 @@
#include "modules/PowerStressModule.h"
#endif
#include "modules/RoutingModule.h"
#if HAS_TRAFFIC_MANAGEMENT && !MESHTASTIC_EXCLUDE_TRAFFIC_MANAGEMENT
#include "modules/TrafficManagementModule.h"
#endif
#include "modules/TextMessageModule.h"
#if !MESHTASTIC_EXCLUDE_TRACEROUTE
#include "modules/TraceRouteModule.h"
@@ -120,6 +123,14 @@ void setupModules()
#if !MESHTASTIC_EXCLUDE_REPLYBOT
new ReplyBotModule();
#endif
#if HAS_TRAFFIC_MANAGEMENT && !MESHTASTIC_EXCLUDE_TRAFFIC_MANAGEMENT
// Instantiate only when enabled to avoid extra memory use and background work.
if (moduleConfig.has_traffic_management && moduleConfig.traffic_management.enabled) {
trafficManagementModule = new TrafficManagementModule();
}
#endif
#if !MESHTASTIC_EXCLUDE_ADMIN
adminModule = new AdminModule();
#endif
+3 -3
View File
@@ -23,7 +23,7 @@ bool SEN5XSensor::getVersion()
}
delay(20); // From Sensirion Datasheet
uint8_t versionBuffer[12];
uint8_t versionBuffer[12]{};
size_t charNumber = readBuffer(&versionBuffer[0], 3);
if (charNumber == 0) {
LOG_ERROR("SEN5X: Error getting data ready flag value");
@@ -638,7 +638,7 @@ bool SEN5XSensor::readValues()
LOG_DEBUG("SEN5X: Reading PM Values");
delay(20); // From Sensirion Datasheet
uint8_t dataBuffer[16];
uint8_t dataBuffer[16]{};
size_t receivedNumber = readBuffer(&dataBuffer[0], 24);
if (receivedNumber == 0) {
LOG_ERROR("SEN5X: Error getting values");
@@ -691,7 +691,7 @@ bool SEN5XSensor::readPNValues(bool cumulative)
LOG_DEBUG("SEN5X: Reading PN Values");
delay(20); // From Sensirion Datasheet
uint8_t dataBuffer[20];
uint8_t dataBuffer[20]{};
size_t receivedNumber = readBuffer(&dataBuffer[0], 30);
if (receivedNumber == 0) {
LOG_ERROR("SEN5X: Error getting PN values");
File diff suppressed because it is too large Load Diff
+434
View File
@@ -0,0 +1,434 @@
#pragma once
#include "MeshModule.h"
#include "concurrency/Lock.h"
#include "concurrency/OSThread.h"
#include "mesh/generated/meshtastic/mesh.pb.h"
#include "mesh/generated/meshtastic/telemetry.pb.h"
#if HAS_TRAFFIC_MANAGEMENT
/**
* TrafficManagementModule - Packet inspection and traffic shaping for mesh networks.
*
* This module provides:
* - Position deduplication (drop redundant position broadcasts)
* - Per-node rate limiting (throttle chatty nodes)
* - Unknown packet filtering (drop undecoded packets from repeat offenders)
* - NodeInfo direct response (answer queries from cache to reduce mesh chatter)
* - Local-only telemetry/position (exhaust hop_limit for local broadcasts)
* - Router hop preservation (maintain hop_limit for router-to-router traffic)
*
* Memory Optimization:
* Uses a unified cache with cuckoo hashing for O(1) lookups and 56% memory reduction
* compared to separate per-feature caches. Timestamps are stored as 8-bit relative
* offsets from a rolling epoch to further reduce memory footprint.
*/
class TrafficManagementModule : public MeshModule, private concurrency::OSThread
{
public:
TrafficManagementModule();
~TrafficManagementModule();
// Singleton — no copying or moving
TrafficManagementModule(const TrafficManagementModule &) = delete;
TrafficManagementModule &operator=(const TrafficManagementModule &) = delete;
meshtastic_TrafficManagementStats getStats() const;
void resetStats();
void recordRouterHopPreserved();
/**
* Check if this packet should have its hops exhausted.
* Called from perhapsRebroadcast() to force hop_limit = 0 regardless of
* router_preserve_hops or favorite node logic.
*/
bool shouldExhaustHops(const meshtastic_MeshPacket &mp) const
{
return exhaustRequested && exhaustRequestedFrom == getFrom(&mp) && exhaustRequestedId == mp.id;
}
protected:
ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override;
bool wantPacket(const meshtastic_MeshPacket *p) override { return true; }
void alterReceived(meshtastic_MeshPacket &mp) override;
int32_t runOnce() override;
// Protected so test shims can force epoch rollover behavior.
void resetEpoch(uint32_t nowMs);
private:
// =========================================================================
// Unified Cache Entry (10 bytes) - Same for ALL platforms
// =========================================================================
//
// A single compact structure used across ESP32, NRF52, and all other platforms.
// Memory: 10 bytes × 2048 entries = 20KB
//
// Position Fingerprinting:
// Instead of storing full coordinates (8 bytes) or a computed hash,
// we store an 8-bit fingerprint derived deterministically from the
// truncated lat/lon. This extracts the lower 4 significant bits from
// each coordinate: fingerprint = (lat_low4 << 4) | lon_low4
//
// Benefits over hash:
// - Adjacent grid cells have sequential fingerprints (no collision)
// - Two positions only collide if 16+ grid cells apart in BOTH dimensions
// - Deterministic: same input always produces same output
//
// Adaptive Timestamp Resolution:
// All timestamps use 8-bit values with adaptive resolution calculated
// from config at startup. Resolution = max(60, min(339, interval/2)).
// - Min 60 seconds ensures reasonable precision
// - Max 339 seconds allows ~24 hour range (255 * 339 = 86445 sec)
// - interval/2 ensures at least 2 ticks per configured interval
//
// Layout:
// [0-3] node - NodeNum (4 bytes)
// [4] pos_fingerprint - 4 bits lat + 4 bits lon (1 byte)
// [5] rate_count - Packets in current window (1 byte)
// [6] unknown_count - Unknown packets count (1 byte)
// [7] pos_time - Position timestamp (1 byte, adaptive resolution)
// [8] rate_time - Rate window start (1 byte, adaptive resolution)
// [9] unknown_time - Unknown tracking start (1 byte, adaptive resolution)
//
struct __attribute__((packed)) UnifiedCacheEntry {
NodeNum node; // 4 bytes - Node identifier (0 = empty slot)
uint8_t pos_fingerprint; // 1 byte - Lower 4 bits of lat + lon
uint8_t rate_count; // 1 byte - Packet count (saturates at 255)
uint8_t unknown_count; // 1 byte - Unknown packet count (saturates at 255)
uint8_t pos_time; // 1 byte - Position timestamp (adaptive resolution)
uint8_t rate_time; // 1 byte - Rate window start (adaptive resolution)
uint8_t unknown_time; // 1 byte - Unknown tracking start (adaptive resolution)
};
static_assert(sizeof(UnifiedCacheEntry) == 10, "UnifiedCacheEntry should be 10 bytes");
// =========================================================================
// Cuckoo Hash Table Implementation
// =========================================================================
//
// Cuckoo hashing provides O(1) worst-case lookup time using two hash functions.
// Each key can be in one of two possible locations (h1 or h2). On collision,
// the existing entry is "kicked" to its alternate location.
//
// Benefits over linear scan:
// - O(1) lookup vs O(n) - critical at packet processing rates
// - O(1) insertion (amortized) with simple eviction on cycles
// - ~95% load factor achievable
//
// Cache size rounds to power-of-2 for fast modulo via bitmask.
// TRAFFIC_MANAGEMENT_CACHE_SIZE=2000 → cacheSize()=2048
//
static constexpr uint16_t cacheSize();
static constexpr uint16_t cacheMask();
// Hash functions for cuckoo hashing
inline uint16_t cuckooHash1(NodeNum node) const { return node & cacheMask(); }
inline uint16_t cuckooHash2(NodeNum node) const { return ((node * 2654435769u) >> (32 - cuckooHashBits())) & cacheMask(); }
static constexpr uint8_t cuckooHashBits();
// NodeInfo cache configuration (PSRAM path):
// - Payload lives in PSRAM
// - DRAM keeps packed 12-bit tags with 4-way bucketed cuckoo hashing
// (Fan et al., CoNEXT 2014). Tag value 0 is reserved as "empty".
static constexpr uint16_t kNodeInfoIndexMetadataBudgetBytes = 3072; // 3KB DRAM tag store
static constexpr uint8_t kNodeInfoTargetOccupancyPercent = 95;
static constexpr uint8_t kNodeInfoBucketSize = 4;
static constexpr uint8_t kNodeInfoTagBits = 12;
static constexpr uint16_t kNodeInfoTagMask = static_cast<uint16_t>((1u << kNodeInfoTagBits) - 1u);
static constexpr uint16_t kNodeInfoIndexSlotsRaw =
static_cast<uint16_t>((kNodeInfoIndexMetadataBudgetBytes * 8u) / kNodeInfoTagBits);
static constexpr uint16_t kNodeInfoIndexSlots =
static_cast<uint16_t>(kNodeInfoIndexSlotsRaw - (kNodeInfoIndexSlotsRaw % kNodeInfoBucketSize));
static constexpr uint16_t kNodeInfoTargetEntries =
static_cast<uint16_t>((kNodeInfoIndexSlots * kNodeInfoTargetOccupancyPercent) / 100u);
static_assert((kNodeInfoIndexSlots % kNodeInfoBucketSize) == 0, "NodeInfo slot count must align to bucket size");
static_assert(kNodeInfoTargetEntries < (1u << kNodeInfoTagBits), "NodeInfo tag bits must encode payload index");
static constexpr uint16_t nodeInfoTargetEntries();
static constexpr uint16_t nodeInfoIndexMetadataBudgetBytes();
static constexpr uint8_t nodeInfoTargetOccupancyPercent();
static constexpr uint8_t nodeInfoBucketSize();
static constexpr uint8_t nodeInfoTagBits();
static constexpr uint16_t nodeInfoTagMask();
static constexpr uint16_t nodeInfoIndexSlots();
static constexpr uint16_t nodeInfoBucketCount();
static constexpr uint16_t nodeInfoBucketMask();
static constexpr uint8_t nodeInfoBucketHashBits();
inline uint16_t nodeInfoHash1(NodeNum node) const { return node & nodeInfoBucketMask(); }
inline uint16_t nodeInfoHash2(NodeNum node) const
{
return ((node * 2246822519u) >> (32 - nodeInfoBucketHashBits())) & nodeInfoBucketMask();
}
// =========================================================================
// Adaptive Timestamp Resolution
// =========================================================================
//
// All timestamps use 8-bit values with adaptive resolution calculated from
// config at startup. This allows ~24 hour range while maintaining precision.
//
// Resolution formula: max(60, min(339, interval/2))
// - 60 sec minimum ensures reasonable precision
// - 339 sec maximum allows 24 hour range (255 * 339 ≈ 86400 sec)
// - interval/2 ensures at least 2 ticks per configured interval
//
// Since config changes require reboot, resolution is calculated once.
//
uint32_t cacheEpochMs = 0;
uint16_t posTimeResolution = 60; // Seconds per tick for position
uint16_t rateTimeResolution = 60; // Seconds per tick for rate limiting
uint16_t unknownTimeResolution = 60; // Seconds per tick for unknown tracking
// Calculate resolution from configured interval (called once at startup)
static uint16_t calcTimeResolution(uint32_t intervalSecs)
{
// Resolution = interval/2 to ensure at least 2 ticks per interval
// Clamped to [60, 339] for min precision and max 24h range
uint32_t res = (intervalSecs > 0) ? (intervalSecs / 2) : 60;
if (res < 60)
res = 60;
if (res > 339)
res = 339;
return static_cast<uint16_t>(res);
}
// Convert to/from 8-bit relative timestamps with given resolution
uint8_t toRelativeTime(uint32_t nowMs, uint16_t resolutionSecs) const
{
uint32_t ticks = (nowMs - cacheEpochMs) / (resolutionSecs * 1000UL);
return (ticks > UINT8_MAX) ? UINT8_MAX : static_cast<uint8_t>(ticks);
}
uint32_t fromRelativeTime(uint8_t ticks, uint16_t resolutionSecs) const
{
return cacheEpochMs + (static_cast<uint32_t>(ticks) * resolutionSecs * 1000UL);
}
// Convenience wrappers for each timestamp type
uint8_t toRelativePosTime(uint32_t nowMs) const { return toRelativeTime(nowMs, posTimeResolution); }
uint32_t fromRelativePosTime(uint8_t t) const { return fromRelativeTime(t, posTimeResolution); }
uint8_t toRelativeRateTime(uint32_t nowMs) const { return toRelativeTime(nowMs, rateTimeResolution); }
uint32_t fromRelativeRateTime(uint8_t t) const { return fromRelativeTime(t, rateTimeResolution); }
uint8_t toRelativeUnknownTime(uint32_t nowMs) const { return toRelativeTime(nowMs, unknownTimeResolution); }
uint32_t fromRelativeUnknownTime(uint8_t t) const { return fromRelativeTime(t, unknownTimeResolution); }
// Epoch reset when any timestamp approaches overflow
// With max resolution of 339 sec, 200 ticks = ~19 hours (safe margin for 24h max)
bool needsEpochReset(uint32_t nowMs) const
{
uint16_t maxRes = posTimeResolution;
if (rateTimeResolution > maxRes)
maxRes = rateTimeResolution;
if (unknownTimeResolution > maxRes)
maxRes = unknownTimeResolution;
return (nowMs - cacheEpochMs) > (200UL * maxRes * 1000UL);
}
// =========================================================================
// Position Fingerprint
// =========================================================================
//
// Computes 8-bit fingerprint from truncated lat/lon coordinates.
// Extracts lower 4 significant bits from each coordinate.
//
// fingerprint = (lat_low4 << 4) | lon_low4
//
// Unlike a hash, adjacent grid cells have sequential fingerprints,
// so nearby positions never collide. Collisions only occur for
// positions 16+ grid cells apart in both dimensions.
//
// Guards: If precision < 4 bits, uses min(precision, 4) bits.
//
static uint8_t computePositionFingerprint(int32_t lat_truncated, int32_t lon_truncated, uint8_t precision);
// =========================================================================
// Cache Storage
// =========================================================================
mutable concurrency::Lock cacheLock; // Protects all cache access
UnifiedCacheEntry *cache = nullptr; // Cuckoo hash table (unified for all platforms)
bool cacheFromPsram = false; // Tracks allocator for correct deallocation
struct NodeInfoPayloadEntry {
// Node identifier associated with this payload slot.
// 0 means the slot is currently unused.
NodeNum node;
// Cached NODEINFO_APP payload body. This is separate from NodeDB and is only
// used by the PSRAM-backed direct-response path in this module.
meshtastic_User user;
// Extra response metadata captured from the latest observed NODEINFO_APP
// packet for this node. shouldRespondToNodeInfo() uses this metadata when
// building spoofed replies for requesting clients.
// Last local uptime tick (millis) when this entry was refreshed.
uint32_t lastObservedMs;
// Last RTC/packet timestamp (seconds) observed for this NodeInfo frame.
// If unavailable in packet, remains 0.
uint32_t lastObservedRxTime;
// Channel where we most recently heard this node's NodeInfo.
uint8_t sourceChannel;
// Cached decoded bitfield metadata from the source packet.
// We preserve non-OK_TO_MQTT bits in direct replies when available.
bool hasDecodedBitfield;
uint8_t decodedBitfield;
};
NodeInfoPayloadEntry *nodeInfoPayload = nullptr; // NodeInfo payloads in PSRAM
bool nodeInfoPayloadFromPsram = false; // Tracks allocator for correct deallocation
uint8_t *nodeInfoIndex = nullptr; // Packed 12-bit NodeInfo tags in DRAM
uint16_t nodeInfoAllocHint = 0;
uint16_t nodeInfoEvictCursor = 0;
meshtastic_TrafficManagementStats stats;
// Flag set during alterReceived() when packet should be exhausted.
// Checked by perhapsRebroadcast() to force hop_limit = 0 only for the
// matching packet key (from + id). Reset at start of handleReceived().
bool exhaustRequested = false;
NodeNum exhaustRequestedFrom = 0;
PacketId exhaustRequestedId = 0;
// =========================================================================
// Cache Operations
// =========================================================================
// Find or create entry for node using cuckoo hashing
// Returns nullptr if cache is full and eviction fails
UnifiedCacheEntry *findOrCreateEntry(NodeNum node, bool *isNew);
// Find existing entry (no creation)
UnifiedCacheEntry *findEntry(NodeNum node);
// NodeInfo cache operations (bucketed cuckoo index + PSRAM payloads)
const NodeInfoPayloadEntry *findNodeInfoEntry(NodeNum node) const;
NodeInfoPayloadEntry *findOrCreateNodeInfoEntry(NodeNum node, bool *usedEmptySlot);
uint16_t findNodeInfoPayloadIndex(NodeNum node) const;
bool removeNodeInfoIndexEntry(NodeNum node, uint16_t payloadIndex);
uint16_t allocateNodeInfoPayloadSlot();
uint16_t evictNodeInfoPayloadSlot();
bool tryInsertNodeInfoEntryInBucket(uint16_t bucket, uint16_t tag);
uint16_t encodeNodeInfoTag(uint16_t payloadIndex) const;
uint16_t decodeNodeInfoPayloadIndex(uint16_t tag) const;
uint16_t getNodeInfoTag(uint16_t slot) const;
void setNodeInfoTag(uint16_t slot, uint16_t tag);
uint16_t countNodeInfoEntriesLocked() const;
void cacheNodeInfoPacket(const meshtastic_MeshPacket &mp);
// =========================================================================
// Traffic Management Logic
// =========================================================================
bool shouldDropPosition(const meshtastic_MeshPacket *p, const meshtastic_Position *pos, uint32_t nowMs);
bool shouldRespondToNodeInfo(const meshtastic_MeshPacket *p, bool sendResponse);
bool isMinHopsFromRequestor(const meshtastic_MeshPacket *p) const;
bool isRateLimited(NodeNum from, uint32_t nowMs);
bool shouldDropUnknown(const meshtastic_MeshPacket *p, uint32_t nowMs);
void logAction(const char *action, const meshtastic_MeshPacket *p, const char *reason) const;
void incrementStat(uint32_t *field);
};
// =========================================================================
// Compile-time Cache Size Calculations
// =========================================================================
//
// Round TRAFFIC_MANAGEMENT_CACHE_SIZE up to next power of 2 for efficient
// cuckoo hash indexing (allows bitmask instead of modulo).
//
// These use C++11-compatible constexpr (single return statement).
//
namespace detail
{
// Helper: round up to next power of 2 using bit manipulation
constexpr uint16_t nextPow2(uint16_t n)
{
return n == 0 ? 0 : (((n - 1) | ((n - 1) >> 1) | ((n - 1) >> 2) | ((n - 1) >> 4) | ((n - 1) >> 8)) + 1);
}
// Helper: floor(log2(n)) for n >= 0, C++11-compatible constexpr.
constexpr uint8_t log2Floor(uint16_t n)
{
return n <= 1 ? 0 : static_cast<uint8_t>(1 + log2Floor(static_cast<uint16_t>(n >> 1)));
}
// Helper: ceil(log2(n)) for n >= 1, C++11-compatible constexpr.
constexpr uint8_t log2Ceil(uint16_t n)
{
return n <= 1 ? 0 : static_cast<uint8_t>(1 + log2Floor(static_cast<uint16_t>(n - 1)));
}
} // namespace detail
constexpr uint16_t TrafficManagementModule::cacheSize()
{
return detail::nextPow2(TRAFFIC_MANAGEMENT_CACHE_SIZE);
}
constexpr uint16_t TrafficManagementModule::cacheMask()
{
return cacheSize() > 0 ? cacheSize() - 1 : 0;
}
constexpr uint8_t TrafficManagementModule::cuckooHashBits()
{
return detail::log2Floor(cacheSize());
}
constexpr uint16_t TrafficManagementModule::nodeInfoTargetEntries()
{
return kNodeInfoTargetEntries;
}
constexpr uint16_t TrafficManagementModule::nodeInfoIndexMetadataBudgetBytes()
{
return kNodeInfoIndexMetadataBudgetBytes;
}
constexpr uint8_t TrafficManagementModule::nodeInfoTargetOccupancyPercent()
{
return kNodeInfoTargetOccupancyPercent;
}
constexpr uint8_t TrafficManagementModule::nodeInfoBucketSize()
{
return kNodeInfoBucketSize;
}
constexpr uint8_t TrafficManagementModule::nodeInfoTagBits()
{
return kNodeInfoTagBits;
}
constexpr uint16_t TrafficManagementModule::nodeInfoTagMask()
{
return kNodeInfoTagMask;
}
constexpr uint16_t TrafficManagementModule::nodeInfoIndexSlots()
{
return kNodeInfoIndexSlots;
}
constexpr uint16_t TrafficManagementModule::nodeInfoBucketCount()
{
return static_cast<uint16_t>(nodeInfoIndexSlots() / nodeInfoBucketSize());
}
constexpr uint16_t TrafficManagementModule::nodeInfoBucketMask()
{
return nodeInfoBucketCount() > 0 ? nodeInfoBucketCount() - 1 : 0;
}
constexpr uint8_t TrafficManagementModule::nodeInfoBucketHashBits()
{
return detail::log2Floor(nodeInfoBucketCount());
}
extern TrafficManagementModule *trafficManagementModule;
#endif
File diff suppressed because it is too large Load Diff
+9 -1
View File
@@ -29,6 +29,14 @@
#define SX126X_DIO2_AS_RF_SWITCH
#define SX126X_DIO3_TCXO_VOLTAGE 1.8
// Enable Traffic Management Module for Heltec V4
#ifndef HAS_TRAFFIC_MANAGEMENT
#define HAS_TRAFFIC_MANAGEMENT 1
#endif
#ifndef TRAFFIC_MANAGEMENT_CACHE_SIZE
#define TRAFFIC_MANAGEMENT_CACHE_SIZE 2048
#endif
// ---- GC1109 RF FRONT END CONFIGURATION ----
// The Heltec V4.2 uses a GC1109 FEM chip with integrated PA and LNA
// RF path: SX1262 -> Pi attenuator -> GC1109 PA -> Antenna
@@ -89,4 +97,4 @@
// Seems to be missing on this new board
#define GPS_TX_PIN (38) // This is for bits going TOWARDS the CPU
#define GPS_RX_PIN (39) // This is for bits going TOWARDS the GPS
#define GPS_THREAD_INTERVAL 50
#define GPS_THREAD_INTERVAL 50
+8
View File
@@ -40,6 +40,14 @@ Board Information: https://wiki.uniteng.com/en/meshtastic/station-g2
#define SX126X_MAX_POWER 19
#endif
// Enable Traffic Management Module for Station G2
#ifndef HAS_TRAFFIC_MANAGEMENT
#define HAS_TRAFFIC_MANAGEMENT 1
#endif
#ifndef TRAFFIC_MANAGEMENT_CACHE_SIZE
#define TRAFFIC_MANAGEMENT_CACHE_SIZE 2048
#endif
/*
#define BATTERY_PIN 4 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage
#define ADC_CHANNEL ADC1_GPIO4_CHANNEL
+8
View File
@@ -8,3 +8,11 @@
// RAK12002 RTC Module
#define RV3028_RTC (uint8_t)0b1010010
// Enable Traffic Management Module for native/portduino
#ifndef HAS_TRAFFIC_MANAGEMENT
#define HAS_TRAFFIC_MANAGEMENT 1
#endif
#ifndef TRAFFIC_MANAGEMENT_CACHE_SIZE
#define TRAFFIC_MANAGEMENT_CACHE_SIZE 2048
#endif
@@ -154,6 +154,15 @@ extern "C" {
#define HAS_SCREEN 0
// Enable Traffic Management Module for testing on T1000-E
// NRF52840 has 256KB RAM - 1024 entries uses ~10KB
#ifndef HAS_TRAFFIC_MANAGEMENT
#define HAS_TRAFFIC_MANAGEMENT 1
#endif
#ifndef TRAFFIC_MANAGEMENT_CACHE_SIZE
#define TRAFFIC_MANAGEMENT_CACHE_SIZE 1024
#endif
#ifdef __cplusplus
}
#endif
+1 -1
View File
@@ -1,4 +1,4 @@
[VERSION]
major = 2
minor = 7
build = 20
build = 21