Traffic Management module: dedup, rate limiting, role-aware policing (#10706)

Adds the Traffic Management module (TMM) plus the NodeDB/warm-store and
next-hop foundations it builds on:

- Unified per-node cache (flat array, 8-bit relative ticks) shared by all
  features; role-aware throttles for tracker / lost-and-found.
- Position deduplication: drop unchanged position rebroadcasts within a
  configurable interval; precision driven off the channel ceiling (clamped to
  the public-key max on well-known channels). Enabled by default at 11h.
- Per-node rate limiting and unknown-packet filtering (config-driven; a
  non-zero companion field enables each feature -- no bool toggles).
- NodeInfo direct response from cache with role-based hop clamps.
- Persistent next-hop overflow store: confirmed hops have no TTL, are seeded
  from NodeInfoLite at boot, and survive hot-store eviction.
- Three-tier sender-role resolution (hot NodeInfoLite -> warm store -> TMM
  cache). Role is cached write-time (seeded on first track, refreshed from
  NodeInfo), pins its cache entry like a next-hop hint, and is evicted last.
- Warm store caches device role + protected category across reboot/eviction.
- PositionModule stationary floor for tracker / lost-and-found.
- PSRAM gating for warm/satellite/TMM cache sizes; STM32WL excluded.

Protobufs: TrafficManagementConfig trimmed to the five uint32 fields actually
used; submodule repointed to protobufs develop.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Tom
2026-06-20 20:35:14 -05:00
committed by GitHub
co-authored by GitHub Claude Sonnet 4.6
parent d8d92b7b71
commit c51c01607d
26 changed files with 1186 additions and 610 deletions
+18
View File
@@ -440,6 +440,24 @@ bool Channels::usesPublicKey(ChannelIndex chIndex)
return (psk.size == sizeof(defaultpsk) && memcmp(psk.bytes, defaultpsk, sizeof(defaultpsk) - 1) == 0);
}
bool Channels::isWellKnownChannel(ChannelIndex chIndex)
{
const auto &ch = getByIndex(chIndex);
// Absent (unencrypted) or single-byte PSK — all the well-known key indexes
if (ch.settings.psk.size > 1)
return false;
const char *name = getName(chIndex);
for (int p = _meshtastic_Config_LoRaConfig_ModemPreset_MIN; p <= _meshtastic_Config_LoRaConfig_ModemPreset_MAX; p++) {
const char *presetName =
DisplayFormatters::getModemPresetDisplayName(static_cast<meshtastic_Config_LoRaConfig_ModemPreset>(p), false, true);
// Presets without a display name fall through to "Invalid" — never a match
if (strcmp(presetName, "Invalid") != 0 && strcmp(name, presetName) == 0)
return true;
}
return false;
}
bool Channels::hasDefaultChannel()
{
// If we don't use a preset or the default frequency slot, or we override the frequency, we don't have a default channel
+6
View File
@@ -88,6 +88,12 @@ class Channels
// Returns true if this channel's effective key is publicly decryptable (open or well-known/default PSK).
bool usesPublicKey(ChannelIndex chIndex);
// Returns true if the channel is "well known": its PSK is absent or a
// single-byte well-known key index, AND its name is any modem-preset
// display name (e.g. a channel named "LongFast" counts even while the
// radio runs MediumFast). Broader than isDefaultChannel, which only
// matches the current preset's name and PSK byte 1.
bool isWellKnownChannel(ChannelIndex chIndex);
// Returns true if we can be reached via a channel with the default settings given a region and modem preset
bool hasDefaultChannel();
+11 -2
View File
@@ -18,6 +18,9 @@
#define default_telemetry_broadcast_interval_secs IF_ROUTER(ONE_DAY / 2, 60 * 60)
#define default_broadcast_interval_secs IF_ROUTER(ONE_DAY / 2, 60 * 60)
#define default_broadcast_smart_minimum_interval_secs 5 * 60
// Floor for our own position broadcasts when stationary (unchanged beyond the broadcast
// precision) or fixed_position: identical positions get deduped by traffic management anyway.
#define default_position_stationary_broadcast_secs (12 * 60 * 60)
#define min_default_broadcast_interval_secs IF_ROUTER(ONE_DAY / 2, 60 * 60)
#define min_default_broadcast_smart_minimum_interval_secs 5 * 60
#define default_wait_bluetooth_secs IF_ROUTER(1, 60)
@@ -34,8 +37,14 @@
enum class TrafficType { POSITION, TELEMETRY };
// Traffic management defaults
#define default_traffic_mgmt_position_precision_bits 19 // ~90m grid cells (±45m)
#define default_traffic_mgmt_position_min_interval_secs (ONE_DAY / 2) // 12 hours between identical positions
#define default_traffic_mgmt_position_precision_bits 19 // ~90m grid cells (±45m)
#define default_traffic_mgmt_position_min_interval_secs (11 * 60 * 60) // 11 hours between identical positions
// Role cap: tracker-role origins may refresh a duplicate position this often (vs the 11h default).
#define default_traffic_mgmt_tracker_position_min_interval_secs (60 * 60) // 1 hour
// Role cap: lost-and-found origins may refresh a duplicate position this often, so a lost
// device updates frequently without flooding. (Quantised to the dedup tick: ~2 ticks.)
// Unlike before, lost-and-found is NOT exempt from the relayed precision clamp.
#define default_traffic_mgmt_lost_and_found_position_min_interval_secs (15 * 60) // 15 minutes
// Hop scaling defaults
#define default_hop_scaling_min_target_nodes 40 // walk threshold: first hop reaching this cumulative count
+22 -2
View File
@@ -1155,8 +1155,12 @@ static void installTrafficManagementDefaults(meshtastic_LocalModuleConfig &mc)
{
mc.has_traffic_management = true;
mc.traffic_management = meshtastic_ModuleConfig_TrafficManagementConfig_init_zero;
mc.traffic_management.enabled = true;
mc.traffic_management.position_dedup_enabled = true;
#if HAS_TRAFFIC_MANAGEMENT
// Position dedup ships enabled at the 11-hour default window on all supported targets.
// STM32WL is excluded at compile time (HAS_TRAFFIC_MANAGEMENT=0 in mesh-pb-constants.h).
// Set position_min_interval_secs=0 at runtime to disable dedup.
mc.traffic_management.position_min_interval_secs = default_traffic_mgmt_position_min_interval_secs;
#endif
}
void NodeDB::installDefaultModuleConfig()
@@ -3450,6 +3454,20 @@ bool NodeDB::copyPublicKey(NodeNum n, meshtastic_NodeInfoLite_public_key_t &out)
return false;
}
meshtastic_Config_DeviceConfig_Role NodeDB::getNodeRole(NodeNum n)
{
const meshtastic_NodeInfoLite *info = getMeshNode(n);
if (nodeInfoLiteHasUser(info))
return info->role;
#if WARM_NODE_COUNT > 0
// Hot-store miss: fall back to the role the warm tier cached at eviction.
uint8_t role = 0, prot = 0;
if (warmStore.lookupMeta(n, role, prot))
return static_cast<meshtastic_Config_DeviceConfig_Role>(role);
#endif
return meshtastic_Config_DeviceConfig_Role_CLIENT;
}
/// Find a node in our DB, create an empty NodeInfo if missing
meshtastic_NodeInfoLite *NodeDB::getOrCreateMeshNode(NodeNum n)
{
@@ -3672,6 +3690,8 @@ bool NodeDB::createNewIdentity()
myNodeInfo.my_node_num = newNodeNum;
meshtastic_NodeInfoLite *info = getOrCreateMeshNode(getNodeNum());
if (!info)
return false;
TypeConversions::CopyUserToNodeInfoLite(info, owner);
return true;
+5
View File
@@ -341,6 +341,11 @@ class NodeDB
/// tier. Returns false if we don't know a key for n.
bool copyPublicKey(NodeNum n, meshtastic_NodeInfoLite_public_key_t &out);
/// Resolve a node's device role — hot store (with user) first, then the role
/// cached in the warm tier, else CLIENT. Lets role-aware policy keep firing for
/// nodes that have aged out of the hot store.
meshtastic_Config_DeviceConfig_Role getNodeRole(NodeNum n);
/// last_heard of a hot-store node, or 0 if absent. Plain scan of meshNodes
/// with no allocation side effects (unlike getOrCreateMeshNode).
uint32_t hotNodeLastHeard(NodeNum n) const;
+9 -1
View File
@@ -28,8 +28,11 @@ uint32_t getPositionPrecisionForChannel(uint8_t channelIndex)
return precision;
}
static int32_t truncateCoordinate(int32_t coordinate, uint32_t precision)
int32_t truncateCoordinate(int32_t coordinate, uint32_t precision)
{
if (precision == 0 || precision >= 32)
return coordinate;
uint32_t coordinateBits = static_cast<uint32_t>(coordinate);
uint32_t truncated = coordinateBits & (UINT32_MAX << (32 - precision));
@@ -39,6 +42,11 @@ static int32_t truncateCoordinate(int32_t coordinate, uint32_t precision)
return static_cast<int32_t>(truncated);
}
int32_t truncateCoordinate(int32_t coordinate, uint8_t precision)
{
return truncateCoordinate(coordinate, static_cast<uint32_t>(precision));
}
void applyPositionPrecision(meshtastic_Position &position, uint32_t precision)
{
if (precision == 0) {
+6
View File
@@ -15,6 +15,12 @@ uint32_t getPositionPrecisionForChannel(const meshtastic_Channel &channel);
// Configured precision, clamped to MAX_POSITION_PRECISION_PUBLIC_KEY when the channel's effective key is publicly decryptable.
uint32_t getPositionPrecisionForChannel(uint8_t channelIndex);
// Truncate a single latitude_i/longitude_i to `precision` significant bits, centered in the
// resulting grid cell (stable under GPS jitter). precision 0 or >=32 returns the value unchanged.
// The return is the coordinate (int32_t); the uint8_t overload only narrows the precision arg.
int32_t truncateCoordinate(int32_t coordinate, uint32_t precision);
int32_t truncateCoordinate(int32_t coordinate, uint8_t precision);
void applyPositionPrecision(meshtastic_Position &position, uint32_t precision);
bool applyPositionPrecision(meshtastic_MeshPacket &packet, uint32_t precision);
bool applyPositionPrecisionForChannel(meshtastic_MeshPacket &packet, uint8_t channelIndex);
+6 -13
View File
@@ -100,19 +100,12 @@ 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
// router_preserve_hops: not suitable right now — removed from config until
// the right heuristics for when to preserve vs. exhaust hops are established.
// #if HAS_TRAFFIC_MANAGEMENT
// if (moduleConfig.has_traffic_management &&
// moduleConfig.traffic_management.router_preserve_hops && ...) { ... }
// #endif
// For subsequent hops, preserve hop_limit only when the previous relay is UNAMBIGUOUSLY a favorite
// router. The relay_node byte is just the last byte of a 32-bit node number, so on a dense mesh it
@@ -452,7 +452,7 @@ extern const pb_msgdesc_t meshtastic_BackupPreferences_msg;
/* Maximum encoded size of messages (where known) */
/* meshtastic_NodeDatabase_size depends on runtime parameters */
#define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_BackupPreferences_size
#define meshtastic_BackupPreferences_size 2432
#define meshtastic_BackupPreferences_size 2410
#define meshtastic_ChannelFile_size 718
#define meshtastic_DeviceState_size 1944
#define meshtastic_NodeEnvironmentEntry_size 170
+1 -1
View File
@@ -206,7 +206,7 @@ extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg;
/* Maximum encoded size of messages (where known) */
#define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalModuleConfig_size
#define meshtastic_LocalConfig_size 757
#define meshtastic_LocalModuleConfig_size 820
#define meshtastic_LocalModuleConfig_size 798
#ifdef __cplusplus
} /* extern "C" */
@@ -232,34 +232,23 @@ typedef struct _meshtastic_ModuleConfig_PaxcounterConfig {
/* Config for the Traffic Management module.
Provides packet inspection and traffic shaping to help reduce channel utilization */
typedef struct _meshtastic_ModuleConfig_TrafficManagementConfig {
/* Master enable for traffic management module */
bool enabled;
/* Enable position deduplication to drop redundant position broadcasts */
bool position_dedup_enabled;
/* Number of bits of precision for position deduplication (0-32) */
uint32_t position_precision_bits;
/* Minimum interval in seconds between position updates from the same node */
/* Minimum interval in seconds between position updates from the same node.
A non-zero value implicitly enables the suppression window; 0 disables it. */
uint32_t position_min_interval_secs;
/* Enable direct response to NodeInfo requests from local cache */
bool nodeinfo_direct_response;
/* Minimum hop distance from requestor before responding to NodeInfo requests */
/* Maximum hop distance from the requestor at which direct NodeInfo responses
are served from the local cache. A non-zero value implicitly enables direct
response; 0 disables it. */
uint32_t nodeinfo_direct_response_max_hops;
/* Enable per-node rate limiting to throttle chatty nodes */
bool rate_limit_enabled;
/* Time window in seconds for rate limiting calculations */
/* Time window in seconds for per-node rate limiting.
A non-zero value implicitly enables rate limiting; 0 disables it. */
uint32_t rate_limit_window_secs;
/* Maximum packets allowed per node within the rate limit window */
/* Maximum packets allowed per node within the rate limit window.
A non-zero value implicitly enables rate limiting; 0 disables it. */
uint32_t rate_limit_max_packets;
/* Enable dropping of unknown/undecryptable packets per rate_limit_window_secs */
bool drop_unknown_enabled;
/* Number of unknown packets before dropping from a node */
/* Maximum unknown/undecryptable packets per rate window before the source
is dropped. A non-zero value implicitly enables unknown-packet filtering;
0 disables it. */
uint32_t unknown_packet_threshold;
/* Set hop_limit to 0 for relayed telemetry broadcasts (own packets unaffected) */
bool exhaust_hop_telemetry;
/* Set hop_limit to 0 for relayed position broadcasts (own packets unaffected) */
bool exhaust_hop_position;
/* Preserve hop_limit for router-to-router traffic */
bool router_preserve_hops;
} meshtastic_ModuleConfig_TrafficManagementConfig;
/* Serial Config */
@@ -588,7 +577,7 @@ extern "C" {
#define meshtastic_ModuleConfig_DetectionSensorConfig_init_default {0, 0, 0, 0, "", 0, _meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_MIN, 0}
#define meshtastic_ModuleConfig_AudioConfig_init_default {0, 0, _meshtastic_ModuleConfig_AudioConfig_Audio_Baud_MIN, 0, 0, 0, 0}
#define meshtastic_ModuleConfig_PaxcounterConfig_init_default {0, 0, 0, 0}
#define meshtastic_ModuleConfig_TrafficManagementConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_ModuleConfig_TrafficManagementConfig_init_default {0, 0, 0, 0, 0}
#define meshtastic_ModuleConfig_SerialConfig_init_default {0, 0, 0, 0, _meshtastic_ModuleConfig_SerialConfig_Serial_Baud_MIN, 0, _meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MIN, 0}
#define meshtastic_ModuleConfig_ExternalNotificationConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_ModuleConfig_StoreForwardConfig_init_default {0, 0, 0, 0, 0, 0}
@@ -607,7 +596,7 @@ extern "C" {
#define meshtastic_ModuleConfig_DetectionSensorConfig_init_zero {0, 0, 0, 0, "", 0, _meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_MIN, 0}
#define meshtastic_ModuleConfig_AudioConfig_init_zero {0, 0, _meshtastic_ModuleConfig_AudioConfig_Audio_Baud_MIN, 0, 0, 0, 0}
#define meshtastic_ModuleConfig_PaxcounterConfig_init_zero {0, 0, 0, 0}
#define meshtastic_ModuleConfig_TrafficManagementConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_ModuleConfig_TrafficManagementConfig_init_zero {0, 0, 0, 0, 0}
#define meshtastic_ModuleConfig_SerialConfig_init_zero {0, 0, 0, 0, _meshtastic_ModuleConfig_SerialConfig_Serial_Baud_MIN, 0, _meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MIN, 0}
#define meshtastic_ModuleConfig_ExternalNotificationConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_ModuleConfig_StoreForwardConfig_init_zero {0, 0, 0, 0, 0, 0}
@@ -656,20 +645,11 @@ extern "C" {
#define meshtastic_ModuleConfig_PaxcounterConfig_paxcounter_update_interval_tag 2
#define meshtastic_ModuleConfig_PaxcounterConfig_wifi_threshold_tag 3
#define meshtastic_ModuleConfig_PaxcounterConfig_ble_threshold_tag 4
#define meshtastic_ModuleConfig_TrafficManagementConfig_enabled_tag 1
#define meshtastic_ModuleConfig_TrafficManagementConfig_position_dedup_enabled_tag 2
#define meshtastic_ModuleConfig_TrafficManagementConfig_position_precision_bits_tag 3
#define meshtastic_ModuleConfig_TrafficManagementConfig_position_min_interval_secs_tag 4
#define meshtastic_ModuleConfig_TrafficManagementConfig_nodeinfo_direct_response_tag 5
#define meshtastic_ModuleConfig_TrafficManagementConfig_nodeinfo_direct_response_max_hops_tag 6
#define meshtastic_ModuleConfig_TrafficManagementConfig_rate_limit_enabled_tag 7
#define meshtastic_ModuleConfig_TrafficManagementConfig_rate_limit_window_secs_tag 8
#define meshtastic_ModuleConfig_TrafficManagementConfig_rate_limit_max_packets_tag 9
#define meshtastic_ModuleConfig_TrafficManagementConfig_drop_unknown_enabled_tag 10
#define meshtastic_ModuleConfig_TrafficManagementConfig_unknown_packet_threshold_tag 11
#define meshtastic_ModuleConfig_TrafficManagementConfig_exhaust_hop_telemetry_tag 12
#define meshtastic_ModuleConfig_TrafficManagementConfig_exhaust_hop_position_tag 13
#define meshtastic_ModuleConfig_TrafficManagementConfig_router_preserve_hops_tag 14
#define meshtastic_ModuleConfig_SerialConfig_enabled_tag 1
#define meshtastic_ModuleConfig_SerialConfig_echo_tag 2
#define meshtastic_ModuleConfig_SerialConfig_rxd_tag 3
@@ -867,20 +847,11 @@ X(a, STATIC, SINGULAR, INT32, ble_threshold, 4)
#define meshtastic_ModuleConfig_PaxcounterConfig_DEFAULT NULL
#define meshtastic_ModuleConfig_TrafficManagementConfig_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, BOOL, enabled, 1) \
X(a, STATIC, SINGULAR, BOOL, position_dedup_enabled, 2) \
X(a, STATIC, SINGULAR, UINT32, position_precision_bits, 3) \
X(a, STATIC, SINGULAR, UINT32, position_min_interval_secs, 4) \
X(a, STATIC, SINGULAR, BOOL, nodeinfo_direct_response, 5) \
X(a, STATIC, SINGULAR, UINT32, nodeinfo_direct_response_max_hops, 6) \
X(a, STATIC, SINGULAR, BOOL, rate_limit_enabled, 7) \
X(a, STATIC, SINGULAR, UINT32, rate_limit_window_secs, 8) \
X(a, STATIC, SINGULAR, UINT32, rate_limit_max_packets, 9) \
X(a, STATIC, SINGULAR, BOOL, drop_unknown_enabled, 10) \
X(a, STATIC, SINGULAR, UINT32, unknown_packet_threshold, 11) \
X(a, STATIC, SINGULAR, BOOL, exhaust_hop_telemetry, 12) \
X(a, STATIC, SINGULAR, BOOL, exhaust_hop_position, 13) \
X(a, STATIC, SINGULAR, BOOL, router_preserve_hops, 14)
X(a, STATIC, SINGULAR, UINT32, unknown_packet_threshold, 11)
#define meshtastic_ModuleConfig_TrafficManagementConfig_CALLBACK NULL
#define meshtastic_ModuleConfig_TrafficManagementConfig_DEFAULT NULL
@@ -1053,7 +1024,7 @@ extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg;
#define meshtastic_ModuleConfig_StoreForwardConfig_size 24
#define meshtastic_ModuleConfig_TAKConfig_size 4
#define meshtastic_ModuleConfig_TelemetryConfig_size 50
#define meshtastic_ModuleConfig_TrafficManagementConfig_size 52
#define meshtastic_ModuleConfig_TrafficManagementConfig_size 30
#define meshtastic_ModuleConfig_size 227
#define meshtastic_RemoteHardwarePin_size 21
+20 -22
View File
@@ -99,23 +99,20 @@ static inline int get_max_num_nodes()
#elif defined(ARCH_PORTDUINO)
#define MAX_NUM_NODES 250 // native host: no flash/RAM constraint; match the ESP32-S3 top tier
#else
#define MAX_NUM_NODES 120 // nRF52840 (28 KB LittleFS) and generic ESP32
#define MAX_NUM_NODES 120 // nRF52840 and generic ESP32 (inc. ESP32C3 etc.)
#endif // platform
#endif // MAX_NUM_NODES
/// Per-map cap (position/telemetry/environment/status): only the freshest
/// MAX_SATELLITE_NODES nodes keep satellite payloads, the rest just the
/// NodeInfoLite header. RAM-bound: the four maps live in internal SRAM (not
/// PSRAM). PSRAM-equipped ESP32-S3 (and native) keep the full 250; other ESP32
/// (no-PSRAM, incl. S3) get 80 -- ~32 KB worst case, affordable now the warm
/// tier is trimmed; nRF52840 and other tight parts stay at 40.
/// NodeInfoLite header. RAM-bound (the maps are internal-SRAM, not PSRAM), so
/// flash-rich hosts get a cap >= their hot store (satellites for every node, as
/// before the cap existed) while constrained parts stay at 40.
#ifndef MAX_SATELLITE_NODES
#if defined(ARCH_PORTDUINO) || (defined(CONFIG_IDF_TARGET_ESP32S3) && defined(BOARD_HAS_PSRAM))
#define MAX_SATELLITE_NODES 250 // native / PSRAM-equipped ESP32-S3
#elif defined(ARCH_ESP32)
#define MAX_SATELLITE_NODES 80 // no-PSRAM ESP32 (incl. ESP32-S3)
#if (defined(CONFIG_IDF_TARGET_ESP32S3) && defined(BOARD_HAS_PSRAM)) || defined(ARCH_PORTDUINO)
#define MAX_SATELLITE_NODES 250
#else
#define MAX_SATELLITE_NODES 40 // nRF52840 (28 KB LittleFS) and other constrained parts
#define MAX_SATELLITE_NODES 40 // nRF52840, generic ESP32, and ESP32-S3 without PSRAM
#endif // platform
#endif // MAX_SATELLITE_NODES
@@ -130,14 +127,12 @@ static inline int get_max_num_nodes()
// architecture.h via configuration.h) isn't defined this early in every include
// chain. Backed by the raw-flash ring below LittleFS — see WarmNodeStore.h.
#define WARM_NODE_COUNT 200
#elif defined(CONFIG_IDF_TARGET_ESP32S3) && defined(BOARD_HAS_PSRAM)
#define WARM_NODE_COUNT 2000 // ESP32-S3 with PSRAM (external); warm.dat ~80 KB
#elif (defined(CONFIG_IDF_TARGET_ESP32S3) && defined(BOARD_HAS_PSRAM)) || defined(ARCH_PORTDUINO)
#define WARM_NODE_COUNT 2000 // PSRAM-equipped ESP32-S3 / native host; warm.dat ~80 KB
#else
// generic ESP32 and no-PSRAM ESP32-S3: ~12.5 KB in internal heap (calloc fallback in
// WarmNodeStore), leaving room for the BLE controller. PSRAM-equipped S3 takes the 2000 case above.
#define WARM_NODE_COUNT 320
#endif // platform
#endif // WARM_NODE_COUNT
#define WARM_NODE_COUNT 320 // Generic ESP32, ESP32-S3 without PSRAM, ESP32C3 etc.
#endif // platform
#endif // WARM_NODE_COUNT
/// Max number of channels allowed
#define MAX_NUM_CHANNELS (member_size(meshtastic_ChannelFile, channels) / member_size(meshtastic_ChannelFile, channels[0]))
@@ -157,18 +152,21 @@ static inline int get_max_num_nodes()
#ifdef ARCH_STM32WL
#define HAS_VARIABLE_HOPS 0
#endif
#ifndef HAS_VARIABLE_HOPS
#define HAS_VARIABLE_HOPS 1
#endif
// Cache size for traffic management (number of nodes to track)
// Can be overridden per-variant based on available memory
// Can be overridden per-variant by defining before this header is included.
#ifndef TRAFFIC_MANAGEMENT_CACHE_SIZE
#if HAS_TRAFFIC_MANAGEMENT
#define TRAFFIC_MANAGEMENT_CACHE_SIZE 1000
#else
#if !HAS_TRAFFIC_MANAGEMENT
#define TRAFFIC_MANAGEMENT_CACHE_SIZE 0
#endif // HAS_TRAFFIC_MANAGEMENT
#elif (defined(CONFIG_IDF_TARGET_ESP32S3) && defined(BOARD_HAS_PSRAM)) || defined(ARCH_PORTDUINO)
#define TRAFFIC_MANAGEMENT_CACHE_SIZE 2048 // PSRAM-equipped ESP32-S3 / native host
#else
#define TRAFFIC_MANAGEMENT_CACHE_SIZE 1000 // Generic ESP32, ESP32-S3 without PSRAM
#endif
#endif // TRAFFIC_MANAGEMENT_CACHE_SIZE
/// helper function for encoding a record as a protobuf, any failures to encode are fatal and we will panic