diff --git a/src/MessageStore.h b/src/MessageStore.h index 89c9f83bf..040806197 100644 --- a/src/MessageStore.h +++ b/src/MessageStore.h @@ -21,10 +21,12 @@ // How many messages are stored (RAM + flash). // Define -DMESSAGE_HISTORY_LIMIT=N in build_flags to control memory usage. #ifndef MESSAGE_HISTORY_LIMIT -#if defined(ARCH_ESP32) && \ - !(defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32S2)) -// Baseline ESP32 (non-PSRAM variants) has limited heap; reduce message history on resource-constrained builds. -// Override with -DMESSAGE_HISTORY_LIMIT=N if needed. +#if (defined(ARCH_ESP32) && \ + !(defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32S2))) || \ + defined(NRF52840_XXAA) +// Baseline ESP32 (non-PSRAM variants) and nRF52840 (~115 KB heap arena shared with SoftDevice + +// FreeRTOS stacks; 2.8.0 field reports hit 99% use) have limited heap; reduce message history on +// resource-constrained builds. Override with -DMESSAGE_HISTORY_LIMIT=N if needed. #define MESSAGE_HISTORY_LIMIT 10 #else #define MESSAGE_HISTORY_LIMIT 20 diff --git a/src/mesh/mesh-pb-constants.h b/src/mesh/mesh-pb-constants.h index eaaf5dadd..040bdf732 100644 --- a/src/mesh/mesh-pb-constants.h +++ b/src/mesh/mesh-pb-constants.h @@ -17,6 +17,12 @@ #ifndef MAX_RX_TOPHONE #if defined(ARCH_ESP32) && !(defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S3)) #define MAX_RX_TOPHONE 8 +#elif defined(NRF52840_XXAA) +// Each slot is a ~340 B MeshPacket in the static pool (Router.cpp MAX_PACKETS_STATIC), so 32 slots +// cost ~11 KB of .bss on the RAM-tightest platform (2.8.0 field reports: 99% heap). 16 still doubles +// 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 #define MAX_RX_TOPHONE 32 #endif @@ -126,7 +132,10 @@ static inline int get_max_num_nodes() // Keyed on the NRF52840_XXAA build flag, not ARCH_NRF52: the latter (from // 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 +// 100 (was 200): the RAM cache is 40 B/entry calloc'd from the ~115 KB heap +// 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) #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) @@ -136,10 +145,11 @@ static inline int get_max_num_nodes() #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 (200, raw-flash ring). Any other nRF52 (non-XXAA) and any -// future non-ESP32/non-RP LittleFS part fall through to this 320 default - flag for review if such a -// RAM-constrained nRF52 target is ever added. -#define WARM_NODE_COUNT 320 // other LittleFS-backed parts (e.g. non-nRF52840 nRF52) +// 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 @@ -177,11 +187,16 @@ static inline int get_max_num_nodes() #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. +#define TRAFFIC_MANAGEMENT_CACHE_SIZE 250 #else -// nRF52 (incl. nRF52840) and RP2040/RP2350 fall through here - there is no nRF/RP branch above, -// by design. These parts have no ESP32-style WiFi+BLE coexistence eating the heap, so the larger -// 1000-entry (~10 KB) cache fits: nRF52840 is BLE-only on 256 KB RAM; RP2040/RP2350 have 264/520 KB. -#define TRAFFIC_MANAGEMENT_CACHE_SIZE 1000 // nRF52 / RP2040 / RP2350 / other non-ESP32 +// 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. +#define TRAFFIC_MANAGEMENT_CACHE_SIZE 400 #endif #endif // TRAFFIC_MANAGEMENT_CACHE_SIZE diff --git a/test/test_warm_store/test_main.cpp b/test/test_warm_store/test_main.cpp index 43820b521..e26d9ee45 100644 --- a/test/test_warm_store/test_main.cpp +++ b/test/test_warm_store/test_main.cpp @@ -257,6 +257,50 @@ void test_ws_v1_migration_discardsLastHeard() b.saveIfDirty(); } +// Shrink safety: a warm.dat snapshot recording more entries than this build's +// WARM_NODE_COUNT (e.g. written before a per-platform tier reduction) must be +// rejected cleanly at the header check - load() starts empty instead of reading +// past entries[]. (The nRF52840 raw-flash ring backend has no such cliff: it +// replays records through place(), whose LRU admission keeps the newest.) +void test_ws_load_rejectsOversizedSnapshot() +{ + WarmNodeStore a; + a.absorb(0xA00, 111, NULL); + if (!a.saveIfDirty()) { + TEST_IGNORE_MESSAGE("Filesystem not available in this test environment"); + return; + } + + // Patch the header's count field (offset 8) to one past capacity. + std::vector buf; + { + auto f = FSCom.open("/prefs/warm.dat", FILE_O_READ); + if (!f) { + TEST_IGNORE_MESSAGE("warm.dat not readable in this environment"); + return; + } + buf.resize(f.size()); + f.read(buf.data(), buf.size()); + f.close(); + } + TEST_ASSERT_TRUE(buf.size() >= 16); + const uint16_t oversized = (uint16_t)(WARM_NODE_COUNT + 1); + memcpy(buf.data() + 8, &oversized, sizeof(oversized)); + { + auto f = FSCom.open("/prefs/warm.dat", FILE_O_WRITE); + TEST_ASSERT_TRUE((bool)f); + f.write(buf.data(), buf.size()); + f.close(); + } + + WarmNodeStore b; + b.load(); + TEST_ASSERT_EQUAL(0, b.count()); // rejected as invalid, started empty + + b.clear(); + b.saveIfDirty(); +} + WS_TEST_ENTRY void setup() { initializeTestEnvironment(); @@ -273,6 +317,7 @@ WS_TEST_ENTRY void setup() RUN_TEST(test_ws_remove_and_clear); RUN_TEST(test_ws_persistence_roundTrip); RUN_TEST(test_ws_v1_migration_discardsLastHeard); + RUN_TEST(test_ws_load_rejectsOversizedSnapshot); exit(UNITY_END()); } diff --git a/variants/nrf52840/heltec_mesh_node_t096/platformio.ini b/variants/nrf52840/heltec_mesh_node_t096/platformio.ini index e1bdd529d..03aa03310 100644 --- a/variants/nrf52840/heltec_mesh_node_t096/platformio.ini +++ b/variants/nrf52840/heltec_mesh_node_t096/platformio.ini @@ -15,6 +15,7 @@ board_level = pr debug_tool = jlink 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_t096 -D HAS_LORA_FEM=1 -D HELTEC_MESH_NODE_T096 diff --git a/variants/nrf52840/heltec_mesh_node_t1/platformio.ini b/variants/nrf52840/heltec_mesh_node_t1/platformio.ini index 664d1616e..7d822685a 100644 --- a/variants/nrf52840/heltec_mesh_node_t1/platformio.ini +++ b/variants/nrf52840/heltec_mesh_node_t1/platformio.ini @@ -15,6 +15,7 @@ board_level = pr debug_tool = jlink 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_t1 -DHELTEC_MESH_NODE_T1 -DUSE_TFTDISPLAY diff --git a/variants/nrf52840/heltec_mesh_node_t114/platformio.ini b/variants/nrf52840/heltec_mesh_node_t114/platformio.ini index 57fb39754..c9f998240 100644 --- a/variants/nrf52840/heltec_mesh_node_t114/platformio.ini +++ b/variants/nrf52840/heltec_mesh_node_t114/platformio.ini @@ -18,7 +18,6 @@ debug_tool = jlink build_flags = ${nrf52840_base.build_flags} -Ivariants/nrf52840/heltec_mesh_node_t114 -DHELTEC_T114 - -DOLEDDISPLAY_REDUCE_MEMORY build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/heltec_mesh_node_t114> lib_deps = diff --git a/variants/nrf52840/heltec_mesh_solar/platformio.ini b/variants/nrf52840/heltec_mesh_solar/platformio.ini index 3f2833740..e4d3c5058 100644 --- a/variants/nrf52840/heltec_mesh_solar/platformio.ini +++ b/variants/nrf52840/heltec_mesh_solar/platformio.ini @@ -107,7 +107,6 @@ extends = heltec_mesh_solar_base build_flags = ${heltec_mesh_solar_base.build_flags} -DHELTEC_MESH_SOLAR_TFT -DSPI_INTERFACES_COUNT=2 - -DOLEDDISPLAY_REDUCE_MEMORY -DHAS_SPI_TFT=1 -DUSE_ST7789 -DST7789_NSS=41 diff --git a/variants/nrf52840/nrf52.ini b/variants/nrf52840/nrf52.ini index f1502ecf3..6cf4c57cc 100644 --- a/variants/nrf52840/nrf52.ini +++ b/variants/nrf52840/nrf52.ini @@ -26,6 +26,8 @@ build_flags = -DLFS_NO_ASSERT ; Disable LFS assertions , see https://github.com/meshtastic/firmware/pull/3818 -DMESHTASTIC_EXCLUDE_AUDIO=1 -DMESHTASTIC_EXCLUDE_PAXCOUNTER=1 + -DMESHTASTIC_EXCLUDE_PKT_HISTORY_HASH=1 ; drop PacketHistory's 1 KB hash index; O(n) over ~240 records is negligible at LoRa packet rates + -DOLEDDISPLAY_REDUCE_MEMORY ; drop the ThingPulse OLED back buffer (~1 KB); TFT variants must -U this (TFTDisplay.cpp needs buffer_back) -Os -std=gnu++17 -flto ; whole-image LTO (~-60KB) on every nrf52840 target; nrf52_lto.py (pre: extra_script) keeps the interrupt handlers out of LTO so they survive diff --git a/variants/nrf52840/rak_wismeshtap/platformio.ini b/variants/nrf52840/rak_wismeshtap/platformio.ini index f058d9153..9ccc2b796 100644 --- a/variants/nrf52840/rak_wismeshtap/platformio.ini +++ b/variants/nrf52840/rak_wismeshtap/platformio.ini @@ -12,6 +12,7 @@ custom_meshtastic_tags = RAK extends = nrf52840_base board = wiscore_rak4631 build_flags = ${nrf52840_base.build_flags} + -UOLEDDISPLAY_REDUCE_MEMORY ; TFTDisplay.cpp needs the lib's buffer_back for dirty-window diffing -Ivariants/nrf52840/rak_wismeshtap -DWISMESH_TAP -DRAK_4631 diff --git a/variants/nrf52840/tracker-t1000-e/variant.h b/variants/nrf52840/tracker-t1000-e/variant.h index de6916ac7..b064dbc9f 100644 --- a/variants/nrf52840/tracker-t1000-e/variant.h +++ b/variants/nrf52840/tracker-t1000-e/variant.h @@ -154,15 +154,6 @@ 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