Skip to content

Conversation

@h3lix1
Copy link
Contributor

@h3lix1 h3lix1 commented Jan 19, 2026

New Module: TrafficManagementModule

Architecture

classDiagram
    class MeshModule {
        +handleReceived(MeshPacket) ProcessMessage
        +alterReceived(MeshPacket)
        +wantPacket(MeshPacket) bool
        #isPromiscuous bool
        #encryptedOk bool
    }

    class OSThread {
        +runOnce() int32_t
        #setIntervalFromNow(ms)
    }

    class TrafficManagementModule {
        -cacheLock Lock
        -cache UnifiedCacheEntry*
        -stats TrafficManagementStats
        -cacheEpochMs uint32_t
        +getStats() TrafficManagementStats
        +resetStats()
        +recordRouterHopPreserved()
        -shouldDropPosition() bool
        -shouldDropUnknown() bool
        -isRateLimited() bool
        -shouldRespondToNodeInfo() bool
        -computePositionHash()$ uint8_t
    }

    MeshModule <|-- TrafficManagementModule
    OSThread <|-- TrafficManagementModule

    TrafficManagementModule --> Router : allocForSending
    TrafficManagementModule --> MeshService : sendToMesh
Loading
  • Dual inheritance: MeshModule (packet handling) + concurrency::OSThread (periodic maintenance)
  • Location: src/modules/TrafficManagementModule.cpp and src/modules/TrafficManagementModule.h
  • Thread model: Runs as a cooperative OSThread with 60-second maintenance intervals (kMaintenanceIntervalMs)
  • Promiscuous mode: isPromiscuous = true allows inspection of all packets, not just those addressed to this node
  • Encrypted packet support: encryptedOk = true permits handling undecryptable packets for unknown-packet tracking

Compile-Time Gating

Module instantiation in setupModules() requires all three conditions:

flowchart TD
    A[setupModules called] --> B{HAS_TRAFFIC_MANAGEMENT<br/>== 1?}
    B -->|No| Z[Module not compiled]
    B -->|Yes| C{MESHTASTIC_EXCLUDE_<br/>TRAFFIC_MANAGEMENT?}
    C -->|Yes| Z
    C -->|No| D{moduleConfig.<br/>traffic_management.<br/>enabled?}
    D -->|No| Y[Module compiled but<br/>not instantiated]
    D -->|Yes| E[new TrafficManagementModule]
    E --> F[trafficManagementModule = instance]
Loading
  1. HAS_TRAFFIC_MANAGEMENT defined as 1 (variant-level)
  2. !MESHTASTIC_EXCLUDE_TRAFFIC_MANAGEMENT (build exclusion flag)
  3. moduleConfig.traffic_management.enabled (runtime config)

Memory Management for Embedded Devices

Unified Cache with Cuckoo Hashing

The module uses a unified cache combining all per-node tracking into a single 10-byte structure with cuckoo hashing for O(1) lookups. This provides:

  • O(1) lookup via cuckoo hashing - critical at packet processing rates
  • 10 bytes per entry - unified across all platforms
  • Position fingerprinting - deterministic 8-bit fingerprint from truncated coordinates
  • Adaptive timestamp resolution - 8-bit timestamps with ~24 hour range
  • Saturating counters - uint8_t (max 255) for rate/unknown counts

UnifiedCacheEntry Structure (10 bytes) - All Platforms

A single compact structure used across ESP32, NRF52, and all other platforms:

struct __attribute__((packed)) UnifiedCacheEntry {
    NodeNum node;            // 4 bytes - Node identifier (0 = empty)
    uint8_t pos_fingerprint; // 1 byte  - Lower 4 bits of lat + 4 bits of lon
    uint8_t rate_count;      // 1 byte  - Packet count (saturates at 255)
    uint8_t unknown_count;   // 1 byte  - Unknown packet count
    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);

Position Fingerprinting

Instead of storing full coordinates (8 bytes) or using a computed hash, the module uses a deterministic 8-bit fingerprint derived from the truncated lat/lon:

fingerprint = (lat_low4 << 4) | lon_low4

Where lat_low4 and lon_low4 are the lower 4 significant bits of each truncated coordinate.

Benefits over hash-based approach:

  • No hash collisions for adjacent positions: Sequential grid cells have sequential fingerprints
  • Deterministic: Same input always produces same output
  • Collision only at distance: Two positions collide only if they differ by 16+ grid cells in BOTH dimensions

Guard for low precision: If position_precision_bits < 4, uses min(precision, 4) bits from each coordinate.

Adaptive Timestamp Resolution

All timestamps use 8-bit values with adaptive resolution calculated from config at startup:

resolution = max(60, min(339, interval / 2))
  • 60 second minimum ensures reasonable precision for all intervals
  • 339 second maximum allows ~24 hour range (255 × 339 ≈ 86400 seconds)
  • interval/2 ensures at least 2 ticks per configured interval for accuracy

Since config changes require reboot, resolution is calculated once in the constructor.

Memory Efficiency vs NodeDB:

The traffic management cache is dramatically more memory-efficient than full NodeDB entries:

Structure Per-Entry Size 2048 Entries Contents
meshtastic_NodeInfoLite 196 bytes ~400 KB Full node: user info, position, device metrics, public key, etc.
UnifiedCacheEntry 10 bytes ~20 KB Traffic state only: fingerprint, counters, timestamps

The unified cache achieves ~20x memory reduction compared to duplicating NodeDB data because it stores only the minimal state needed for traffic decisions:

  • No encryption keys (32 bytes saved)
  • No user strings (long_name, short_name - ~50 bytes saved)
  • No device metrics (~24 bytes saved)
  • No full coordinates (8 bytes → 1 byte fingerprint)
  • Adaptive 8-bit timestamps with ~24 hour range
  • Saturating counters (8-bit vs 32-bit)

Cuckoo Hashing Algorithm

Cuckoo hashing provides O(1) worst-case lookup using two hash functions:

flowchart TD
    A[lookup node] --> B{cache h1 node == node?}
    B -->|Yes| C[Return entry at h1]
    B -->|No| D{cache h2 node == node?}
    D -->|Yes| E[Return entry at h2]
    D -->|No| F[Not found]

    G[insert node] --> H{slot h1 empty?}
    H -->|Yes| I[Insert at h1]
    H -->|No| J{slot h2 empty?}
    J -->|Yes| K[Insert at h2]
    J -->|No| L[Kick entry from h1]
    L --> M[Place kicked entry at its alternate]
    M --> N{Max kicks reached?}
    N -->|No| L
    N -->|Yes| O[Evict oldest entry]
Loading

Hash functions:

// h1: Simple mask for sequential NodeNums
uint16_t cuckooHash1(NodeNum n) { return n & cacheMask(); }

// h2: Golden ratio multiplicative hash (decorrelated from h1)
uint16_t cuckooHash2(NodeNum n) {
    return ((n * 2654435769u) >> (32 - bits)) & cacheMask();
}

Relative Timestamp Compression

Timestamps use relative offsets from a rolling epoch, with different granularity by platform:

Full mode (ESP32 + PSRAM):

  • Position timestamps: 16-bit seconds (~18 hour range)
  • Rate/unknown timestamps: 16-bit seconds
  • Epoch reset: ~17 hours

Compact mode (NRF52, non-PSRAM ESP32):

  • Position timestamps: 8-bit minutes (~4 hour range) - sufficient for position dedup
  • Rate/unknown timestamps: 16-bit seconds (unchanged)
  • Epoch reset: ~3.5 hours
// Full mode: 16-bit seconds
uint16_t toRelativeSecs(uint32_t nowMs) {
    return (nowMs - cacheEpochMs) / 1000;
}

// Compact mode: 8-bit minutes for position
uint8_t toRelativeMins(uint32_t nowMs) {
    return (nowMs - cacheEpochMs) / 60000;
}

The shorter epoch reset interval in compact mode (3.5h vs 17h) is acceptable since it only invalidates cached position data, not ongoing traffic management functionality.

Platform-Specific Allocation

flowchart TD
    subgraph ESP32_PSRAM["ESP32 + PSRAM"]
        A1[Constructor] --> A2{ps_calloc success?}
        A2 -->|Yes| A3[Full structure 20B<br/>in PSRAM]
        A2 -->|No| A4[Fallback to heap]
    end

    subgraph NATIVE["Native/Portduino"]
        D1[Constructor] --> D2[Full structure 20B<br/>in heap]
    end

    subgraph ESP32_NO_PSRAM["ESP32 without PSRAM"]
        B1[Constructor] --> B2[Compact structure 12B<br/>in heap]
    end

    subgraph NRF52["NRF52"]
        C1[Constructor] --> C2[Compact structure 12B<br/>in heap]
    end
Loading

Platform Detection: At compile time, #if (defined(ARCH_ESP32) && defined(BOARD_HAS_PSRAM)) || defined(ARCH_PORTDUINO) selects the full 20-byte structure with coordinate storage. Memory-constrained platforms (NRF52, non-PSRAM ESP32) use the compact 12-byte structure with 8-bit position hashing.

Cache Sizing

Cache size rounds up to power-of-2 for efficient cuckoo hash indexing:

// TRAFFIC_MANAGEMENT_CACHE_SIZE=1000 → cacheSize()=1024
constexpr uint16_t cacheSize() {
    uint16_t n = TRAFFIC_MANAGEMENT_CACHE_SIZE;
    n--; n |= n >> 1; n |= n >> 2; n |= n >> 4; n |= n >> 8;
    return n + 1;
}

Memory Budget Calculations

Cache memory at 2048 entries (unified 10-byte structure):

Platform Allocation Default Entries Total Memory
ESP32 + PSRAM PSRAM 2048 20 KB
ESP32 (no PSRAM) Heap 2048 20 KB
nRF52840 Heap 2048 20 KB
Native/Portduino Heap 2048 20 KB

Note: TRAFFIC_MANAGEMENT_CACHE_SIZE can be overridden per-variant. The value rounds up to the next power of 2 for efficient cuckoo hash indexing (e.g., 1000 → 1024, 2000 → 2048).

Typical available RAM by platform:

Platform Total RAM Free After Boot NodeDB Overhead
ESP32 + PSRAM 320KB DRAM + 4-8MB PSRAM 80-150KB DRAM ~16.6KB (100 nodes)
ESP32 no PSRAM ~320KB 80-150KB ~16.6KB (100 nodes)
nRF52840 256KB 50-80KB ~13.3KB (80 nodes)

Risk assessment with unified 10-byte structure (2048 entries = 20KB):

Platform Free Heap Cache Size % of Free RAM Risk Level
ESP32 + PSRAM PSRAM: 4-8MB 20KB → PSRAM <1% Safe
ESP32 no PSRAM 80-150KB 20KB → heap 13-25% Safe
nRF52840 ~160KB 20KB → heap ~12% Safe

Recommended Cache Sizes by Platform

Platform Recommended Size Memory Used Rationale
ESP32 + PSRAM 2048 20KB Abundant PSRAM available
ESP32 no PSRAM 2048 20KB ~13-25% of free heap
nRF52840 2048 20KB ~12% of free heap

Example variant enablement:

// In variant.h
#define HAS_TRAFFIC_MANAGEMENT 1
#define TRAFFIC_MANAGEMENT_CACHE_SIZE 2048  // ~20KB with 10-byte entries

Current Enablement Status

Traffic management is enabled on:

  • Heltec V4 (ESP32-S3 with PSRAM) - cache in PSRAM
  • Tracker T1000-E (nRF52840) - cache in heap

Both use TRAFFIC_MANAGEMENT_CACHE_SIZE=2048 (~20KB with unified 10-byte entries).

Other variants can enable via HAS_TRAFFIC_MANAGEMENT=1 in their variant.h.

Cuckoo Hash Lookup and Insertion

Cuckoo hashing provides O(1) lookup by checking exactly two locations:

flowchart TD
    subgraph Lookup["O(1) Lookup"]
        L1[findEntry node] --> L2{cache h1 == node?}
        L2 -->|Yes| L3[Return entry]
        L2 -->|No| L4{cache h2 == node?}
        L4 -->|Yes| L3
        L4 -->|No| L5[Return null]
    end

    subgraph Insert["O(1) Amortized Insert"]
        I1[findOrCreate node] --> I2[Check h1 and h2]
        I2 --> I3{Found existing?}
        I3 -->|Yes| I4[Return existing]
        I3 -->|No| I5{Empty slot?}
        I5 -->|Yes| I6[Insert and return]
        I5 -->|No| I7[Cuckoo displacement]
        I7 --> I8{Max kicks<br/>exceeded?}
        I8 -->|No| I9[Kick entry to<br/>alternate slot]
        I9 --> I7
        I8 -->|Yes| I10[Evict oldest<br/>globally]
        I10 --> I6
    end
Loading

Cuckoo displacement algorithm:

  1. Insert new entry at h1, displacing existing entry
  2. Move displaced entry to its alternate location (h2 if was at h1, or vice versa)
  3. If alternate location occupied, repeat displacement
  4. After 16 kicks (cycle detected), fall back to global oldest eviction

This O(1) lookup is critical for packet processing performance:

  • Predictable latency: Every lookup checks exactly 2 slots (vs O(n) linear scan)
  • Scales with cache size: 1000 entries has same lookup cost as 100 entries
  • No dynamic allocation: All operations work on pre-allocated array
  • Cache-friendly: Sequential memory access for displacement chain

Performance comparison:

Operation Old (Linear Scan) New (Cuckoo Hash)
Lookup O(n) = ~1000 ops O(1) = 2 ops
Insert O(n) = ~1000 ops O(1) amortized
Eviction O(n) scan O(n) fallback only

Thread Safety

Lock Architecture

The module uses a single mutable concurrency::Lock cacheLock member to protect all cache and statistics operations.

Codebase Lock Pattern Comparison:

Pattern Declaration Usage Used By
Global pointer extern Lock *spiLock; LockGuard g(spiLock); SPI bus, crypto
Member by value Lock mutex; LockGuard g(&mutex); PhoneAPI, Screen
Mutable member mutable Lock cacheLock; LockGuard g(&cacheLock); TrafficManagementModule

Why mutable? The getStats() method is const but must acquire a lock. Declaring the lock as mutable allows locking in const methods without requiring a dangerous const_cast. This is semantically correct: locking doesn't affect the logical const-ness of the object.

Compare to ScanI2CTwoWire which uses a non-mutable lock and requires casting in const methods:

// ScanI2CTwoWire (older pattern - requires cast)
concurrency::LockGuard guard((concurrency::Lock *)&lock);

// TrafficManagementModule (better pattern - no cast needed)
concurrency::LockGuard guard(&cacheLock);

FreeRTOS Implementation Details

The underlying concurrency::Lock uses FreeRTOS binary semaphores:

Lock::Lock() : handle(xSemaphoreCreateBinary())
{
    assert(handle);
    xSemaphoreGive(handle);  // Initialize as available
}

void Lock::lock() {
    xSemaphoreTake(handle, portMAX_DELAY);  // Block indefinitely
}

void Lock::unlock() {
    xSemaphoreGive(handle);
}

On non-FreeRTOS platforms, locking is a no-op (single-threaded execution).

Critical Sections Analysis

Function Lock Scope Duration Notes
getStats() Entire function ~32 byte copy Returns copy to prevent torn reads
resetStats() Entire function Single memset Fast zero-initialization
incrementStat() Entire function Single increment Called frequently
runOnce() Full cache sweep O(CACHE_SIZE) Runs every 60s on OSThread
shouldDropPosition() Cache lookup + update O(CACHE_SIZE) scan Per-packet cost
isRateLimited() Cache lookup + update O(CACHE_SIZE) scan Per-packet cost
shouldDropUnknown() Cache lookup + update O(CACHE_SIZE) scan Per-packet cost

Variable Naming Convention

Codebase uses both g and guard for lock guard variables:

  • g - SafeFile, FSCommon, Router, ContentHandler, TFTDisplay
  • guard - PhoneAPI, ScanI2CTwoWire, FlashData, MessageStore

TrafficManagementModule uses guard, consistent with PhoneAPI and modern modules.

Statistics Counters

  • Protected increment via incrementStat() with lock guard
  • Statistics struct (meshtastic_TrafficManagementStats) is 32 bytes (8 x uint32_t)
  • getStats() returns copy under lock to prevent torn reads across multiple counters

Packet Handling Flow

handleReceived() Processing Order

flowchart TD
    A[Packet Received] --> B{Module Enabled?}
    B -->|No| Z[Return CONTINUE]
    B -->|Yes| C[Increment packets_inspected]

    C --> D{Undecoded Packet?}
    D -->|Yes| E{drop_unknown enabled?}
    E -->|Yes| F{Threshold exceeded?}
    F -->|Yes| G[/"Drop: unknown_packet_drops++<br/>Return STOP"/]
    F -->|No| H[Track in UnknownTracker]
    E -->|No| H
    H --> Z
    D -->|No| I{NodeInfo Request?}

    I -->|Yes| J{nodeinfo_direct_response<br/>enabled?}
    J -->|Yes| K{want_response &&<br/>!broadcast && !toUs?}
    K -->|Yes| L{hopsAway <= maxHops?<br/>Router: 3, Client: 0}
    L -->|Yes| M[/"Send cached User<br/>hop_limit=0<br/>nodeinfo_cache_hits++<br/>Return STOP"/]
    L -->|No - too far| N[Continue to next check]
    K -->|No| N
    J -->|No| N
    I -->|No| N

    N --> O{Position Packet?<br/>Not from us?}
    O -->|Yes| P{position_dedup_enabled?}
    P -->|Yes| Q[Decode & truncate lat/lon]
    Q --> R{Duplicate within<br/>min_interval?}
    R -->|Yes| S[/"Drop: position_dedup_drops++<br/>Return STOP"/]
    R -->|No| T[Update cache]
    T --> U
    P -->|No| U
    O -->|No| U

    U{Rate limit check?<br/>Not from us?<br/>Not ROUTING/ADMIN?}
    U -->|Yes| V{rate_limit_enabled?}
    V -->|Yes| W{Limit exceeded?}
    W -->|Yes| X[/"Drop: rate_limit_drops++<br/>Return STOP"/]
    W -->|No| Y[Increment counter]
    Y --> Z
    V -->|No| Z
    U -->|No| Z

    Z[Return CONTINUE]
Loading

NodeInfo Direct Response - Role-Based Limits

Both routers and clients use maxHops logic: respond when hopsAway <= threshold.
Role determines the enforced maximum (not just a default).

Device Role Max Limit Responds to
ROUTER, ROUTER_LATE, CLIENT_BASE 3 Nodes within 0-3 hops
All other roles (clients) 0 Direct neighbors only (0 hops)

Examples:

  • Router with config=0: uses limit (3) → responds to 0, 1, 2, 3 hops
  • Router with config=2: uses 2 → responds to 0, 1, 2 hops
  • Router with config=5: clamped to 3 → responds to 0, 1, 2, 3 hops
  • Client with config=0: uses limit (0) → responds to 0 hops only
  • Client with config=2: clamped to 0 → responds to 0 hops only

Note: nodeinfo_direct_response must be enabled separately for this feature to work.

Adjustable constants in TrafficManagementModule.cpp:

  • kRouterDefaultMaxHops = 3 - Router limit: max 3 hops
  • kClientDefaultMaxHops = 0 - Client limit: direct only (cannot increase)

alterReceived() - Local Broadcast Hop Exhaustion

For locally-originated broadcasts (isFromUs(&mp) && isBroadcast(mp.to)):

  • If exhaust_hop_telemetry and port is TELEMETRY_APP: set hop_limit = 0
  • If exhaust_hop_position and port is POSITION_APP: set hop_limit = 0
  • Prevents local sensor data from propagating beyond direct neighbors

Dry Run Mode

When cfg.dry_run == true:

  • All detection logic executes normally
  • dry_run_would_drop counter incremented instead of action counter
  • No packets dropped, modified, or responded to
  • Full logging with [TM] prefix for analysis

Position Truncation Algorithm

Coordinates are truncated to configurable precision for deduplication:

int32_t truncateLatLon(int32_t value, uint8_t precision)
{
    if (precision == 0 || precision >= 32)
        return value;

    uint32_t mask = UINT32_MAX << (32 - precision);
    uint32_t truncated = static_cast<uint32_t>(value) & mask;
    truncated += (1u << (31 - precision));  // Round to center of cell
    return static_cast<int32_t>(truncated);
}

Precision examples (approximate):

Bits Grid Size Use Case
32 ~1cm No truncation
24 ~10m Default - pedestrian/general
20 ~40m Vehicle
16 ~600m City block
12 ~10km Regional

Default Values

Defaults are defined in src/mesh/Default.h and applied at runtime via Default::getConfiguredOrDefault():

Setting Default Meaning
position_precision_bits 24 ~10m grid cells
position_min_interval_secs 86400 (ONE_DAY) 1 day between identical positions

These defaults provide aggressive deduplication suitable for reducing network traffic while still allowing meaningful position updates. Users can adjust via app settings.


Router Hop Preservation

Router.cpp Changes

When traffic_management.router_preserve_hops is enabled and the local device is a router role (ROUTER, ROUTER_LATE, or CLIENT_BASE), hops are unconditionally preserved. This is a "best effort" approach that does not attempt to identify whether the previous relay was also a router, since the relay_node field only contains the last byte of the node ID (8 bits), making reliable identification impossible due to collision risk.

The feature simply preserves hop budget when router-role devices relay packets, extending effective mesh range through infrastructure routers.


Protocol Buffer Definitions

TrafficManagementConfig (module_config.proto)

15 configuration fields using nanopb STATIC encoding:

message TrafficManagementConfig {
    bool enabled = 1;
    bool dry_run = 2;
    bool position_dedup_enabled = 3;
    uint32 position_precision_bits = 4;
    uint32 position_min_interval_secs = 5;
    bool nodeinfo_direct_response = 6;
    uint32 nodeinfo_direct_response_max_hops = 7;
    bool rate_limit_enabled = 8;
    uint32 rate_limit_window_secs = 9;
    uint32 rate_limit_max_packets = 10;
    bool drop_unknown_enabled = 11;
    uint32 unknown_packet_threshold = 12;
    bool exhaust_hop_telemetry = 13;
    bool exhaust_hop_position = 14;
    bool router_preserve_hops = 15;
}

Generated C struct: meshtastic_ModuleConfig_TrafficManagementConfig (~36 bytes)

TrafficManagementStats (telemetry.proto)

8 counter fields for telemetry reporting:

message TrafficManagementStats {
    uint32 packets_inspected = 1;
    uint32 position_dedup_drops = 2;
    uint32 nodeinfo_cache_hits = 3;
    uint32 rate_limit_drops = 4;
    uint32 unknown_packet_drops = 5;
    uint32 hop_exhausted_packets = 6;
    uint32 dry_run_would_drop = 7;
    uint32 router_hops_preserved = 8;
}

Generated C struct: meshtastic_TrafficManagementStats (32 bytes)


Admin/Phone API Integration

AdminModule.cpp Changes

Get handler (handleGetModuleConfig):

case meshtastic_AdminMessage_ModuleConfigType_TRAFFICMANAGEMENT_CONFIG:
    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;

Set handler (handleSetModuleConfig):

case meshtastic_ModuleConfig_traffic_management_tag:
    moduleConfig.has_traffic_management = true;
    moduleConfig.traffic_management = c.payload_variant.traffic_management;
    break;

PhoneAPI.cpp Changes

Traffic management config included in module config enumeration sent to connected clients.


Periodic Maintenance Thread

The runOnce() method executes every 60 seconds to garbage-collect stale cache entries:

int32_t TrafficManagementModule::runOnce()
{
    // TTL calculations
    const uint32_t positionTtlMs = positionIntervalMs * 4;  // 4x interval
    const uint32_t rateTtlMs = rateIntervalMs * 2;          // 2x window
    const uint32_t unknownTtlMs = kUnknownResetMs * 5;      // 5 minutes

    concurrency::LockGuard guard(&cacheLock);
    for (size_t i = 0; i < TRAFFIC_MANAGEMENT_CACHE_SIZE; ++i) {
        // Clear entries older than TTL
        if (positionCache[i].node != 0 &&
            !isWithinWindow(nowMs, positionCache[i].last_seen_ms, positionTtlMs)) {
            memset(&positionCache[i], 0, sizeof(PositionCacheEntry));
        }
        // ... similar for rateLimitCache, unknownCache, hashCache
    }
    return kMaintenanceIntervalMs;
}

This prevents cache pollution from inactive nodes and ensures memory is reclaimed.


Build/Variant Enablement

mesh-pb-constants.h Additions

// Default disabled; variants opt-in
#ifndef HAS_TRAFFIC_MANAGEMENT
#define HAS_TRAFFIC_MANAGEMENT 0
#endif

// Default cache size when enabled
#ifndef TRAFFIC_MANAGEMENT_CACHE_SIZE
#if HAS_TRAFFIC_MANAGEMENT
#define TRAFFIC_MANAGEMENT_CACHE_SIZE 1000
#else
#define TRAFFIC_MANAGEMENT_CACHE_SIZE 0
#endif
#endif

Variant Example: Heltec V4

variants/esp32s3/heltec_v4/variant.h:

#ifdef HAS_TRAFFIC_MANAGEMENT
#undef HAS_TRAFFIC_MANAGEMENT
#endif
#define HAS_TRAFFIC_MANAGEMENT 1

#ifdef TRAFFIC_MANAGEMENT_CACHE_SIZE
#undef TRAFFIC_MANAGEMENT_CACHE_SIZE
#endif
#define TRAFFIC_MANAGEMENT_CACHE_SIZE 2048

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant