diff --git a/src/DebugConfiguration.h b/src/DebugConfiguration.h index 98bbe0f72..173096b65 100644 --- a/src/DebugConfiguration.h +++ b/src/DebugConfiguration.h @@ -53,12 +53,42 @@ extern MemGet memGet; #define LOG_TRACE(...) SEGGER_RTT_printf(0, __VA_ARGS__) #else #if defined(DEBUG_PORT) && !defined(DEBUG_MUTE) -#define LOG_DEBUG(...) DEBUG_PORT.log(MESHTASTIC_LOG_LEVEL_DEBUG, __VA_ARGS__) -#define LOG_INFO(...) DEBUG_PORT.log(MESHTASTIC_LOG_LEVEL_INFO, __VA_ARGS__) -#define LOG_WARN(...) DEBUG_PORT.log(MESHTASTIC_LOG_LEVEL_WARN, __VA_ARGS__) -#define LOG_ERROR(...) DEBUG_PORT.log(MESHTASTIC_LOG_LEVEL_ERROR, __VA_ARGS__) -#define LOG_CRIT(...) DEBUG_PORT.log(MESHTASTIC_LOG_LEVEL_CRIT, __VA_ARGS__) -#define LOG_TRACE(...) DEBUG_PORT.log(MESHTASTIC_LOG_LEVEL_TRACE, __VA_ARGS__) +#define LOG_DEBUG(...) \ + do { \ + if (console) { \ + console->log(MESHTASTIC_LOG_LEVEL_DEBUG, __VA_ARGS__); \ + } \ + } while (0) +#define LOG_INFO(...) \ + do { \ + if (console) { \ + console->log(MESHTASTIC_LOG_LEVEL_INFO, __VA_ARGS__); \ + } \ + } while (0) +#define LOG_WARN(...) \ + do { \ + if (console) { \ + console->log(MESHTASTIC_LOG_LEVEL_WARN, __VA_ARGS__); \ + } \ + } while (0) +#define LOG_ERROR(...) \ + do { \ + if (console) { \ + console->log(MESHTASTIC_LOG_LEVEL_ERROR, __VA_ARGS__); \ + } \ + } while (0) +#define LOG_CRIT(...) \ + do { \ + if (console) { \ + console->log(MESHTASTIC_LOG_LEVEL_CRIT, __VA_ARGS__); \ + } \ + } while (0) +#define LOG_TRACE(...) \ + do { \ + if (console) { \ + console->log(MESHTASTIC_LOG_LEVEL_TRACE, __VA_ARGS__); \ + } \ + } while (0) #else #define LOG_DEBUG(...) #define LOG_INFO(...) @@ -70,7 +100,12 @@ extern MemGet memGet; #endif #if defined(DEBUG_HEAP) -#define LOG_HEAP(...) DEBUG_PORT.log(MESHTASTIC_LOG_LEVEL_HEAP, __VA_ARGS__) +#define LOG_HEAP(...) \ + do { \ + if (console) { \ + console->log(MESHTASTIC_LOG_LEVEL_HEAP, __VA_ARGS__); \ + } \ + } while (0) // Macro-based heap debugging #define DEBUG_HEAP_BEFORE auto heapBefore = memGet.getFreeHeap(); @@ -195,4 +230,4 @@ class Syslog bool vlogf(uint16_t pri, const char *appName, const char *fmt, va_list args) __attribute__((format(printf, 3, 0))); }; -#endif // HAS_NETWORKING \ No newline at end of file +#endif // HAS_NETWORKING diff --git a/src/main.cpp b/src/main.cpp index 38c8e8ca5..77076dd59 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -294,7 +294,7 @@ void printInfo() { LOG_INFO("S:B:%d,%s,%s,%s", HW_VENDOR, optstr(APP_VERSION), optstr(APP_ENV), optstr(APP_REPO)); } -#ifndef PIO_UNIT_TESTING +#if !defined(PIO_UNIT_TESTING) || !(PIO_UNIT_TESTING) void setup() { @@ -1580,7 +1580,7 @@ void scannerToSensorsMap(const std::unique_ptr &i2cScanner, Scan } #endif -#ifndef PIO_UNIT_TESTING +#if !defined(PIO_UNIT_TESTING) || !(PIO_UNIT_TESTING) void loop() { runASAP = false; diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index d6a6acba2..d768a8c35 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -38,6 +38,12 @@ #include #include #include +#if __has_include() +#include +#define NODEDB_HAS_ESP_PTR 1 +#else +#define NODEDB_HAS_ESP_PTR 0 +#endif #include #include #include @@ -68,19 +74,60 @@ meshtastic_DeviceUIConfig uiconfig{.screen_brightness = 153, .screen_timeout = 3 meshtastic_LocalModuleConfig moduleConfig; meshtastic_ChannelFile channelFile; -#if defined(CONFIG_IDF_TARGET_ESP32S3) +//------------------------------------------------------------------------------ +// Runtime instrumentation helpers +//------------------------------------------------------------------------------ -// Hot/cold storage helpers ------------------------------------------------- -// ESP32-S3 targets (Station G2 and similar) keep the heavy NodeInfoLite payloads -// in PSRAM while mirroring routing/UI critical fields in a compact DRAM cache. -// The helpers below keep both views in sync. +namespace +{ + +// Log the pool headroom every 100 inserts (and when we hit MAX) so field logs +// capture how close we are to exhausting heap/PSRAM on real hardware. + +void logNodeInsertStats(size_t count, const char *poolLabel) +{ + if (count == 0) + return; + if ((count % 100) != 0 && count != MAX_NUM_NODES) + return; + + LOG_INFO("NodeDB %s pool usage %u/%u nodes, heap free %u, psram free %u", poolLabel, static_cast(count), + static_cast(MAX_NUM_NODES), memGet.getFreeHeap(), memGet.getFreePsram()); +} + +#if defined(CONFIG_IDF_TARGET_ESP32S3) +bool logPsramAllocationOnce(void *ptr, size_t capacity) +{ + static bool logged = false; + if (logged || !ptr) + return logged; + +#if NODEDB_HAS_ESP_PTR + bool inPsram = esp_ptr_external_ram(ptr); +#else + bool inPsram = false; +#endif + LOG_INFO("NodeDB PSRAM backing at %p (%s) capacity %u entries (~%u bytes)", ptr, inPsram ? "PSRAM" : "DRAM", + static_cast(capacity), static_cast(capacity * sizeof(meshtastic_NodeInfoLite))); + logged = true; + return logged; +} +#endif + +} // namespace + +#if defined(CONFIG_IDF_TARGET_ESP32S3) void NodeDB::initHotCache() { + // Pre-reserve the full cold store in PSRAM during boot so the high watermark + // shows up immediately in PSRAM usage logs and we avoid fragmented + // allocations later in the mission. psramMeshNodes.resize(MAX_NUM_NODES); hotNodes.resize(MAX_NUM_NODES); hotDirty.assign(MAX_NUM_NODES, true); meshNodes = &psramMeshNodes; + logPsramAllocationOnce(psramMeshNodes.data(), psramMeshNodes.capacity()); } void NodeDB::refreshHotCache() @@ -2263,6 +2310,7 @@ meshtastic_NodeInfoLite *NodeDB::getOrCreateMeshNode(NodeNum n) syncHotFromCold(index); lite = &psramMeshNodes[index]; LOG_INFO("Adding node to database with %i nodes and %u bytes free!", numMeshNodes, memGet.getFreeHeap()); + logNodeInsertStats(numMeshNodes, "PSRAM"); } markHotDirty(lite); @@ -2314,6 +2362,7 @@ meshtastic_NodeInfoLite *NodeDB::getOrCreateMeshNode(NodeNum n) memset(lite, 0, sizeof(*lite)); lite->num = n; LOG_INFO("Adding node to database with %i nodes and %u bytes free!", numMeshNodes, memGet.getFreeHeap()); + logNodeInsertStats(numMeshNodes, "Heap"); } return lite; diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 0d3a96b58..8917636c1 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -43,7 +43,7 @@ Allocator &packetPool = dynamicPool; (MAX_RX_TOPHONE + MAX_RX_FROMRADIO + 2 * MAX_TX_QUEUE + \ 2) // max number of packets which can be in flight (either queued from reception or queued for sending) -#if defined(CONFIG_IDF_TARGET_ESP32S3) +#if defined(CONFIG_IDF_TARGET_ESP32S3) && defined(BOARD_HAS_PSRAM) // Try to put the heavy MeshPacket pool into PSRAM. If that fails we fall back to // heap allocation so the radio stays functional (at the cost of fewer packets). static PsramMemoryPool psramPool; @@ -51,6 +51,9 @@ static MemoryDynamic fallbackPool; Allocator &packetPool = psramPool.isValid() ? static_cast &>(psramPool) : static_cast &>(fallbackPool); +#elif defined(CONFIG_IDF_TARGET_ESP32S3) +static MemoryPool staticPool; +Allocator &packetPool = staticPool; #else static MemoryPool staticPool; Allocator &packetPool = staticPool; @@ -66,14 +69,6 @@ static uint8_t bytes[MAX_LORA_PAYLOAD_LEN + 1] __attribute__((__aligned__)); */ Router::Router() : concurrency::OSThread("Router"), fromRadioQueue(MAX_RX_FROMRADIO) { -#if defined(CONFIG_IDF_TARGET_ESP32S3) - if (!psramPool.isValid()) { - LOG_WARN("PSRAM packet pool unavailable, falling back to heap allocations"); - } - if (!has_psram() && MAX_RX_TOPHONE > get_rx_tophone_limit()) { - LOG_WARN("Detected <2MB PSRAM, limiting phone queue to %d packets", get_rx_tophone_limit()); - } -#endif // This is called pre main(), don't touch anything here, the following code is not safe /* LOG_DEBUG("Size of NodeInfo %d", sizeof(NodeInfo)); diff --git a/src/mesh/mesh-pb-constants.h b/src/mesh/mesh-pb-constants.h index 1ea0b95dc..176fe237a 100644 --- a/src/mesh/mesh-pb-constants.h +++ b/src/mesh/mesh-pb-constants.h @@ -32,7 +32,13 @@ inline bool has_psram(size_t minimumBytes = PSRAM_LARGE_THRESHOLD_BYTES) inline int get_rx_tophone_limit() { #if defined(CONFIG_IDF_TARGET_ESP32S3) - return has_psram() ? 400 : 32; +#if defined(BOARD_MAX_RX_TOPHONE) + return BOARD_MAX_RX_TOPHONE; +#elif defined(BOARD_HAS_PSRAM) + return 800; +#else + return 32; +#endif #elif defined(ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3) return 8; #else @@ -45,8 +51,14 @@ inline int get_rx_tophone_limit() // RAM #define MAX_RX_TOPHONE (member_size(DeviceState, receive_queue) / member_size(DeviceState, receive_queue[0])) #ifndef MAX_RX_TOPHONE #if defined(CONFIG_IDF_TARGET_ESP32S3) +#if defined(BOARD_MAX_RX_TOPHONE) +#define MAX_RX_TOPHONE BOARD_MAX_RX_TOPHONE +#elif defined(BOARD_HAS_PSRAM) static constexpr int MAX_RX_TOPHONE_WITH_PSRAM = 800; #define MAX_RX_TOPHONE MAX_RX_TOPHONE_WITH_PSRAM +#else +#define MAX_RX_TOPHONE 32 +#endif #elif defined(ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3) #define MAX_RX_TOPHONE 8 #else @@ -80,13 +92,14 @@ static_assert(sizeof(meshtastic_NodeInfoLite) <= 200, "NodeInfoLite size increas #elif defined(ARCH_NRF52) #define MAX_NUM_NODES 80 #elif defined(CONFIG_IDF_TARGET_ESP32S3) +#if defined(BOARD_MAX_NUM_NODES) +#define MAX_NUM_NODES BOARD_MAX_NUM_NODES +#elif defined(BOARD_HAS_PSRAM) +#define MAX_NUM_NODES 3000 +#else #include "Esp.h" static inline int get_max_num_nodes() { - if (has_psram()) { - return 5000; - } - uint32_t flash_size = ESP.getFlashChipSize() / (1024 * 1024); // Fallback based on flash size if (flash_size >= 15) { return 250; @@ -96,6 +109,7 @@ static inline int get_max_num_nodes() return 100; } #define MAX_NUM_NODES get_max_num_nodes() +#endif #else #define MAX_NUM_NODES 100 #endif