Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions src/memory/MemClass.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#pragma once

// Central memory-class ladder: RAM-sized caches key their per-platform tier off
// MESHTASTIC_MEM_CLASS. Classes rank *usable app heap after platform overheads*
// (SoftDevice, WiFi+BLE stacks) - not raw RAM - and an unclassified chip lands in
// SMALL on purpose: small caches are a recoverable default, an exhausted heap is not.
//
// MEM_CLASS_TINY <32 KB free heap STM32WL
// MEM_CLASS_SMALL ~100-250 KB nRF52840, classic ESP32/S2/C3, RP2040/RP2350
// MEM_CLASS_MEDIUM ~250-500 KB, no PSRAM ESP32-S3/C6/P4 without PSRAM
// MEM_CLASS_LARGE PSRAM or host ESP32-S3+PSRAM, portduino
//
// Compare ordinally (>=). RP2350 rides with RP2040 so this header stays a behavioral
// no-op (MEDIUM candidate later). Variants may predefine MESHTASTIC_MEM_CLASS or any
// cache constant - consumer ladders stay #ifndef-guarded. MAX_NUM_NODES is deliberately
// unclassed (flash-shaped: nodes.proto vs filesystem). Included before configuration.h,
// so only toolchain/board -D macros and the ARCH_* macros the ladders already use are
// safe here.

#define MEM_CLASS_TINY 1
#define MEM_CLASS_SMALL 2
#define MEM_CLASS_MEDIUM 3
#define MEM_CLASS_LARGE 4

#ifndef MESHTASTIC_MEM_CLASS
#if defined(ARCH_STM32WL)
#define MESHTASTIC_MEM_CLASS MEM_CLASS_TINY
#elif (defined(CONFIG_IDF_TARGET_ESP32S3) && defined(BOARD_HAS_PSRAM)) || defined(ARCH_PORTDUINO)
#define MESHTASTIC_MEM_CLASS MEM_CLASS_LARGE
#elif defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C6) || defined(CONFIG_IDF_TARGET_ESP32P4)
#define MESHTASTIC_MEM_CLASS MEM_CLASS_MEDIUM
#else
// Classic ESP32 / S2 / C3, all nRF52, RP2040/RP2350, and - by design - any chip
// nobody has classified yet: small caches are a recoverable default, an
// exhausted heap is not. New RAM-rich targets opt up by adding a branch above.
#define MESHTASTIC_MEM_CLASS MEM_CLASS_SMALL
#endif
#endif // MESHTASTIC_MEM_CLASS

// Ceiling for the boot-allocated mesh caches (TMM + warm store + packet history);
// mesh-pb-constants.h static_asserts their sum, so an oversized cache fails the build.
// Raising a budget is allowed - as a visible, reviewable one-line diff.
#ifndef MESHTASTIC_BOOT_CACHE_BUDGET
#if MESHTASTIC_MEM_CLASS <= MEM_CLASS_TINY
#define MESHTASTIC_BOOT_CACHE_BUDGET (8 * 1024)
#elif MESHTASTIC_MEM_CLASS == MEM_CLASS_SMALL
#define MESHTASTIC_BOOT_CACHE_BUDGET (16 * 1024)
#elif MESHTASTIC_MEM_CLASS == MEM_CLASS_MEDIUM
#define MESHTASTIC_BOOT_CACHE_BUDGET (32 * 1024)
#else
#define MESHTASTIC_BOOT_CACHE_BUDGET (256 * 1024)
#endif
#endif // MESHTASTIC_BOOT_CACHE_BUDGET
4 changes: 0 additions & 4 deletions src/mesh/PacketHistory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,6 @@
#endif
#include "Throttle.h"

#define PACKETHISTORY_MAX \
max((uint32_t)(MAX_NUM_NODES * 2.0), \
(uint32_t)100) // x2..3 Should suffice. Empirical setup. 16B per record malloc'ed, but no less than 100

#define RECENT_WARN_AGE (10 * 60 * 1000L) // Warn if the packet that gets removed was more recent than 10 min

#define VERBOSE_PACKET_HISTORY 0 // Set to 1 for verbose logging, 2 for heavy debugging
Expand Down
2 changes: 2 additions & 0 deletions src/mesh/PacketHistory.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ class PacketHistory
// bit 3-5: our hop limit when we first transmitted it
uint8_t relayed_by[NUM_RELAYERS]; // Array of nodes that relayed this packet
}; // 4B + 4B + 4B + 1B + 1B + 6B = 20B
static_assert(sizeof(PacketRecord) == 20,
"PacketRecord size feeds the boot-cache budget math in mesh-pb-constants.h - update both together");

uint32_t recentPacketsCapacity =
0; // Can be set in constructor, no need to recompile. Used to allocate memory for mx_recentPackets.
Expand Down
88 changes: 58 additions & 30 deletions src/mesh/mesh-pb-constants.h
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
#pragma once
#include <vector>

#include "memory/MemClass.h"
#include "mesh/generated/meshtastic/admin.pb.h"
#include "mesh/generated/meshtastic/deviceonly.pb.h"
#include "mesh/generated/meshtastic/localonly.pb.h"
#include "mesh/generated/meshtastic/mesh.pb.h"

// this file defines constants which come from mesh.options
//
// RAM-shaped cache tiers key off MESHTASTIC_MEM_CLASS (memory/MemClass.h) so
// unclassified chips fail safe-small; class-deviant branches say why inline.

// Tricky macro to let you find the sizeof a type member
#define member_size(type, member) sizeof(((type *)0)->member)
Expand All @@ -23,8 +27,13 @@
// the 8 classic ESP32 has shipped with for years; drops start when a stalled phone/serial client has
// 16 packets queued.
#define MAX_RX_TOPHONE 16
#else
#elif MESHTASTIC_MEM_CLASS >= MEM_CLASS_MEDIUM || defined(ARCH_RP2040) || defined(CONFIG_IDF_TARGET_ESP32C3) || \
defined(ARCH_STM32WL)
// RP2040/RP2350, ESP32-C3 and STM32WL keep their historical 32 (no field pressure to cut them;
// STM32WL's pool is dynamic, so the constant only bounds in-flight packets there).
#define MAX_RX_TOPHONE 32
#else
#define MAX_RX_TOPHONE 16 // unclassified small parts: fail safe-small
#endif
#endif

Expand Down Expand Up @@ -109,13 +118,20 @@ static inline int get_max_num_nodes()
#endif // platform
#endif // MAX_NUM_NODES

/// Packet-history capacity: 2x the hot store so dedup/relayer state survives a
/// full mesh, floored at 100. Shared by PacketHistory's constructor clamp and
/// the boot-cache budget assert below so the two cannot drift.
#ifndef PACKETHISTORY_MAX
#define PACKETHISTORY_MAX (MAX_NUM_NODES * 2 > 100 ? (uint32_t)(MAX_NUM_NODES * 2) : (uint32_t)100)
#endif

/// 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 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(CONFIG_IDF_TARGET_ESP32S3) && defined(BOARD_HAS_PSRAM)) || defined(ARCH_PORTDUINO)
#if MESHTASTIC_MEM_CLASS >= MEM_CLASS_LARGE
#define MAX_SATELLITE_NODES 250
#else
#define MAX_SATELLITE_NODES 40 // nRF52840, generic ESP32, and ESP32-S3 without PSRAM
Expand All @@ -126,7 +142,7 @@ static inline int get_max_num_nodes()
/// so DMs to/from them keep decrypting. 0 disables it; size is per-platform
/// below, persisted to /prefs/warm.dat (or the nRF52840 raw-flash ring).
#ifndef WARM_NODE_COUNT
#if defined(ARCH_STM32WL)
#if MESHTASTIC_MEM_CLASS <= MEM_CLASS_TINY
#define WARM_NODE_COUNT 0
#elif defined(NRF52840_XXAA)
// Keyed on the NRF52840_XXAA build flag, not ARCH_NRF52: the latter (from
Expand All @@ -136,30 +152,28 @@ static inline int get_max_num_nodes()
// arena, which 2.8.0 field reports showed at 99% use; 120 hot + 100 warm
// identities still covers meshes well past the hot cap.
#define WARM_NODE_COUNT 100
#elif (defined(CONFIG_IDF_TARGET_ESP32S3) && defined(BOARD_HAS_PSRAM)) || defined(ARCH_PORTDUINO)
#elif defined(ARCH_RP2040)
// Class-deviant on purpose: bounded so the warm.dat write fits the 8s watchdog (#10746),
// not by RAM (RP2040 264 KB / RP2350 520 KB could hold more).
#define WARM_NODE_COUNT 150
#elif MESHTASTIC_MEM_CLASS >= MEM_CLASS_LARGE
#define WARM_NODE_COUNT 2000 // PSRAM-equipped ESP32-S3 / native host; warm cache in PSRAM (~80 KB)
#elif defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C6) || defined(CONFIG_IDF_TARGET_ESP32P4)
#elif MESHTASTIC_MEM_CLASS == MEM_CLASS_MEDIUM
#define WARM_NODE_COUNT 150 // 512 KB+ SRAM, no PSRAM (S3/C6/P4): ~6 KB heap (#10705)
#elif defined(ARCH_ESP32)
#define WARM_NODE_COUNT 100 // classic ESP32 (520 KB) / S2 (320 KB) / C3 (400 KB): tightest free heap w/ BLE+WiFi, ~4 KB (#10705)
#elif defined(ARCH_RP2040)
#define WARM_NODE_COUNT 150 // RP2040 (264 KB) / RP2350 (520 KB): bounded so warm.dat write fits the 8s watchdog (#10746)
#else
// nRF52840 is handled explicitly above (raw-flash ring). Any other nRF52 (non-XXAA) and any future
// non-ESP32/non-RP LittleFS part lands here. Reviewed after the 2.8.0 nRF52840 heap-exhaustion
// reports: fail small (match nRF52840) so an unclassified RAM-constrained part can't boot-allocate
// 12.8 KB; RAM-rich targets should opt up explicitly in their variant.
#define WARM_NODE_COUNT 100 // other LittleFS-backed parts (e.g. non-nRF52840 nRF52)
#endif // platform
#endif // WARM_NODE_COUNT
// MEM_CLASS_SMALL: classic ESP32/S2/C3 (~4 KB heap, #10705) and anything unclassified -
// fail small so a new RAM-constrained part can't boot-allocate 12.8 KB (2.8.0 lesson).
#define WARM_NODE_COUNT 100
#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]))

// Traffic Management module configuration
// Enabled by default; STM32WL is excluded due to RAM constraints (MAX_NUM_NODES=10).
// Enabled by default; TINY parts (STM32WL) are excluded due to RAM constraints (MAX_NUM_NODES=10).
// Disable per-variant by defining HAS_TRAFFIC_MANAGEMENT=0 in variant.h
#ifdef ARCH_STM32WL
#if MESHTASTIC_MEM_CLASS <= MEM_CLASS_TINY
#define HAS_TRAFFIC_MANAGEMENT 0
#endif
#ifndef HAS_TRAFFIC_MANAGEMENT
Expand All @@ -181,25 +195,39 @@ static inline int get_max_num_nodes()
#ifndef TRAFFIC_MANAGEMENT_CACHE_SIZE
#if !HAS_TRAFFIC_MANAGEMENT
#define TRAFFIC_MANAGEMENT_CACHE_SIZE 0
#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
#elif defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C6) || defined(CONFIG_IDF_TARGET_ESP32P4)
#define TRAFFIC_MANAGEMENT_CACHE_SIZE 500 // 512 KB+ SRAM, no PSRAM (S3/C6/P4): ~5 KB heap (#10705)
#elif defined(ARCH_ESP32)
#define TRAFFIC_MANAGEMENT_CACHE_SIZE 400 // classic ESP32 / S2 / C3: tightest free heap, ~4 KB (#10705)
#elif defined(NRF52840_XXAA)
// Keyed on NRF52840_XXAA (not ARCH_NRF52) for the same include-order reason as WARM_NODE_COUNT above.
// The 256 KB part shares its ~115 KB heap arena with SoftDevice and the FreeRTOS task stacks; 2.8.0
// field reports hit 99% heap use with the old 1000-entry (~10 KB) cache. 250 entries (2.5 KB) still
// tracks >2x the 120-node hot store; LRU victim recycling absorbs busier meshes.
// Class-deviant on purpose (SMALL would be 400): the 256 KB part shares its ~115 KB heap arena with
// SoftDevice and the FreeRTOS task stacks; 2.8.0 field reports hit 99% heap use with the old
// 1000-entry (~10 KB) cache. 250 entries (2.5 KB) still tracks >2x the 120-node hot store; LRU
// victim recycling absorbs busier meshes.
#define TRAFFIC_MANAGEMENT_CACHE_SIZE 250
#elif MESHTASTIC_MEM_CLASS >= MEM_CLASS_LARGE
#define TRAFFIC_MANAGEMENT_CACHE_SIZE 2048 // PSRAM-equipped ESP32-S3 / native host
#elif MESHTASTIC_MEM_CLASS == MEM_CLASS_MEDIUM
#define TRAFFIC_MANAGEMENT_CACHE_SIZE 500 // 512 KB+ SRAM, no PSRAM (S3/C6/P4): ~5 KB heap (#10705)
#else
// RP2040/RP2350 and anything unclassified land here: match the classic-ESP32 tier rather than
// defaulting large. RAM-rich targets should opt up explicitly in their variant.
// MEM_CLASS_SMALL: classic ESP32/S2/C3 (~4 KB heap, #10705), RP2040/RP2350, and anything
// unclassified - fail small rather than defaulting large (2.8.0 lesson).
#define TRAFFIC_MANAGEMENT_CACHE_SIZE 400
#endif
#endif // TRAFFIC_MANAGEMENT_CACHE_SIZE

// Enforce the per-class boot-cache budget (memory/MemClass.h) over the three big
// boot-allocated mesh caches. Entry sizes are pinned by static_asserts at their
// definitions: TMM UnifiedCacheEntry = 10 B (TrafficManagementModule.h),
// WarmNodeEntry = 40 B (WarmNodeStore.h), PacketHistory::PacketRecord = 20 B
// (PacketHistory.h; its optional hash index is excluded here). Skipped where
Comment thread
thebentern marked this conversation as resolved.
// MAX_NUM_NODES is not a compile-time constant: ESP32-S3 (runtime, from flash
// size) and portduino (runtime, from portduino_config.MaxNodes via variant.h).
// If this fires on a new feature: shrink the cache for this class, or raise the
// class budget in MemClass.h as a visible, reviewable decision.
#if !defined(CONFIG_IDF_TARGET_ESP32S3) && !defined(ARCH_PORTDUINO)
static_assert((uint32_t)TRAFFIC_MANAGEMENT_CACHE_SIZE * 10u + (uint32_t)WARM_NODE_COUNT * 40u + 20u * PACKETHISTORY_MAX <=
MESHTASTIC_BOOT_CACHE_BUDGET,
"Boot-allocated mesh caches exceed this memory class's budget - shrink a tier or consciously raise "
"MESHTASTIC_BOOT_CACHE_BUDGET in memory/MemClass.h");
#endif

Comment thread
coderabbitai[bot] marked this conversation as resolved.
/// 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);
Expand Down
1 change: 1 addition & 0 deletions variants/nrf52840/heltec_mesh_node_t114/platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ debug_tool = jlink

# add -DCFG_SYSVIEW if you want to use the Segger systemview tool for OS profiling.
build_flags = ${nrf52840_base.build_flags}
-UOLEDDISPLAY_REDUCE_MEMORY ; TFTDisplay.cpp needs the lib's buffer_back for dirty-window diffing
-Ivariants/nrf52840/heltec_mesh_node_t114
-DHELTEC_T114

Expand Down
1 change: 1 addition & 0 deletions variants/nrf52840/heltec_mesh_solar/platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ build_flags = ${heltec_mesh_solar_base.build_flags}
[env:heltec-mesh-solar-tft]
extends = heltec_mesh_solar_base
build_flags = ${heltec_mesh_solar_base.build_flags}
-UOLEDDISPLAY_REDUCE_MEMORY ; TFTDisplay.cpp needs the lib's buffer_back for dirty-window diffing
-DHELTEC_MESH_SOLAR_TFT
-DSPI_INTERFACES_COUNT=2
-DHAS_SPI_TFT=1
Expand Down
Loading