diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index f8925ec7..d50c10ca 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -314,6 +314,10 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch } else { strcpy(reply, "ERR: clock cannot go backwards"); } + } else if (memcmp(command, "memory", 6) == 0) { + sprintf(reply, "Free: %d, Min: %d, Max: %d, Queue: %d", + ESP.getFreeHeap(), ESP.getMinFreeHeap(), ESP.getMaxAllocHeap(), + _callbacks->getQueueSize()); } else if (memcmp(command, "start ota", 9) == 0) { if (!_board->startOTAUpdate(_prefs->node_name, reply)) { strcpy(reply, "Error"); diff --git a/src/helpers/bridges/MQTTBridge.cpp b/src/helpers/bridges/MQTTBridge.cpp index 7bf7d84c..df0f63e6 100644 --- a/src/helpers/bridges/MQTTBridge.cpp +++ b/src/helpers/bridges/MQTTBridge.cpp @@ -315,6 +315,39 @@ void MQTTBridge::loop() { publishStatus(); _last_status_publish = millis(); } + + // Memory monitoring (every 5 minutes) + static unsigned long last_memory_log = 0; + if (millis() - last_memory_log > 300000) { // 5 minutes + logMemoryStatus(); + last_memory_log = millis(); + } + + // Critical memory check (every 15 minutes) + static unsigned long last_critical_check = 0; + if (millis() - last_critical_check > 900000) { // 15 minutes + if (ESP.getMaxAllocHeap() < 60000) { // Less than 60KB max alloc + MQTT_DEBUG_PRINTLN("WARNING: Max alloc heap below 60KB - potential memory leak detected!"); + MQTT_DEBUG_PRINTLN("Free: %d, Min: %d, Max: %d", ESP.getFreeHeap(), ESP.getMinFreeHeap(), ESP.getMaxAllocHeap()); + + // Attempt memory defragmentation by forcing garbage collection + MQTT_DEBUG_PRINTLN("Attempting memory defragmentation..."); + // Force a small allocation and immediate free to trigger defrag + void* temp = malloc(1024); + if (temp) { + free(temp); + MQTT_DEBUG_PRINTLN("Defragmentation complete. New Max Alloc: %d", ESP.getMaxAllocHeap()); + } + } + + // Critical threshold check + if (ESP.getMaxAllocHeap() < 40000) { // Less than 40KB max alloc + MQTT_DEBUG_PRINTLN("CRITICAL: Max alloc heap below 40KB - severe memory leak!"); + MQTT_DEBUG_PRINTLN("Free: %d, Min: %d, Max: %d", ESP.getFreeHeap(), ESP.getMinFreeHeap(), ESP.getMaxAllocHeap()); + } + + last_critical_check = millis(); + } } void MQTTBridge::onPacketReceived(mesh::Packet *packet) { @@ -459,6 +492,12 @@ void MQTTBridge::processPacketQueue() { publishRaw(queued.packet); } + // Free packet memory before removing from queue + if (queued.packet) { + _mgr->free(queued.packet); + queued.packet = nullptr; + } + // Remove from queue dequeuePacket(); processed++; @@ -533,7 +572,7 @@ void MQTTBridge::publishStatus() { void MQTTBridge::publishPacket(mesh::Packet* packet, bool is_tx) { if (!packet) return; - char json_buffer[1024]; + char json_buffer[512]; char origin_id[65]; // Use actual device ID @@ -1057,26 +1096,41 @@ void MQTTBridge::syncTimeWithNTP() { MQTT_DEBUG_PRINTLN("Time synced: %lu", epochTime); - // Set timezone from string (with DST support) - MQTT_DEBUG_PRINTLN("Setting timezone: %s", _prefs->timezone_string); - - // Clean up old timezone object to prevent memory leak - if (_timezone) { - delete _timezone; - _timezone = nullptr; - } - - // Create timezone object based on timezone string - Timezone* tz = createTimezoneFromString(_prefs->timezone_string); - if (tz) { - MQTT_DEBUG_PRINTLN("Timezone created successfully"); - // Store timezone for later use in message building - _timezone = tz; - } else { - MQTT_DEBUG_PRINTLN("Failed to create timezone, using UTC"); - // Create UTC timezone as fallback - TimeChangeRule utc = {"UTC", Last, Sun, Mar, 0, 0}; - _timezone = new Timezone(utc, utc); + // Set timezone from string (with DST support) - only if changed + static char last_timezone[64] = ""; + if (strcmp(_prefs->timezone_string, last_timezone) != 0) { + MQTT_DEBUG_PRINTLN("Setting timezone: %s", _prefs->timezone_string); + + // Clean up old timezone object to prevent memory leak + if (_timezone) { + delete _timezone; + _timezone = nullptr; + } + + // Create timezone object based on timezone string + Timezone* tz = createTimezoneFromString(_prefs->timezone_string); + if (tz) { + MQTT_DEBUG_PRINTLN("Timezone created successfully"); + // Store timezone for later use in message building + _timezone = tz; + } else { + MQTT_DEBUG_PRINTLN("Failed to create timezone, using UTC"); + // Create UTC timezone as fallback + TimeChangeRule utc = {"UTC", Last, Sun, Mar, 0, 0}; + _timezone = new Timezone(utc, utc); + } + + // Remember this timezone string + strncpy(last_timezone, _prefs->timezone_string, sizeof(last_timezone) - 1); + last_timezone[sizeof(last_timezone) - 1] = '\0'; + + // Force memory defragmentation after timezone recreation + MQTT_DEBUG_PRINTLN("Forcing memory defragmentation after timezone change"); + void* temp = malloc(1024); + if (temp) { + free(temp); + MQTT_DEBUG_PRINTLN("Defragmentation complete. Max Alloc: %d", ESP.getMaxAllocHeap()); + } } // Show current time in both UTC and local @@ -1227,4 +1281,15 @@ Timezone* MQTTBridge::createTimezoneFromString(const char* tz_string) { } } +void MQTTBridge::logMemoryStatus() { + MQTT_DEBUG_PRINTLN("=== Memory Status ==="); + MQTT_DEBUG_PRINTLN("Free heap: %d bytes", ESP.getFreeHeap()); + MQTT_DEBUG_PRINTLN("Min free heap: %d bytes", ESP.getMinFreeHeap()); + MQTT_DEBUG_PRINTLN("Max alloc heap: %d bytes", ESP.getMaxAllocHeap()); + MQTT_DEBUG_PRINTLN("Heap size: %d bytes", ESP.getHeapSize()); + MQTT_DEBUG_PRINTLN("Free PSRAM: %d bytes", ESP.getFreePsram()); + MQTT_DEBUG_PRINTLN("Queue size: %d/%d packets", _queue_count, MAX_QUEUE_SIZE); + MQTT_DEBUG_PRINTLN("==================="); +} + #endif diff --git a/src/helpers/bridges/MQTTBridge.h b/src/helpers/bridges/MQTTBridge.h index 4f6c03c4..e95ba23d 100644 --- a/src/helpers/bridges/MQTTBridge.h +++ b/src/helpers/bridges/MQTTBridge.h @@ -86,7 +86,7 @@ private: bool is_tx; }; - static const int MAX_QUEUE_SIZE = 50; + static const int MAX_QUEUE_SIZE = 10; QueuedPacket _packet_queue[MAX_QUEUE_SIZE]; int _queue_head; int _queue_tail; @@ -111,8 +111,8 @@ private: // Let's Mesh Analyzer support bool _analyzer_us_enabled; bool _analyzer_eu_enabled; - char _auth_token_us[1024]; // JWT token for US server authentication - char _auth_token_eu[1024]; // JWT token for EU server authentication + char _auth_token_us[512]; // JWT token for US server authentication + char _auth_token_eu[512]; // JWT token for EU server authentication char _analyzer_username[70]; // Username in format v1_{UPPERCASE_PUBLIC_KEY} // Device identity for JWT token creation @@ -312,6 +312,12 @@ public: * @return Number of queued packets */ int getQueueSize() const; + +private: + /** + * Log memory status for debugging + */ + void logMemoryStatus(); }; #endif diff --git a/variants/heltec_v3/platformio.ini b/variants/heltec_v3/platformio.ini index 4118ec68..2bb63aa9 100644 --- a/variants/heltec_v3/platformio.ini +++ b/variants/heltec_v3/platformio.ini @@ -51,6 +51,9 @@ build_flags = build_src_filter = ${Heltec_lora32_v3.build_src_filter} + +<../examples/simple_repeater> + - + - + - lib_deps = ${Heltec_lora32_v3.lib_deps} ${esp32_ota.lib_deps} @@ -275,6 +278,9 @@ build_flags = ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_lora32_v3.build_src_filter} +<../examples/simple_repeater> + - + - + - lib_deps = ${Heltec_lora32_v3.lib_deps} ${esp32_ota.lib_deps} diff --git a/variants/station_g2/platformio.ini b/variants/station_g2/platformio.ini index 9fb8e6d4..20a60248 100644 --- a/variants/station_g2/platformio.ini +++ b/variants/station_g2/platformio.ini @@ -54,6 +54,9 @@ build_flags = ; -D MESH_DEBUG=1 build_src_filter = ${Station_G2.build_src_filter} +<../examples/simple_repeater> + - + - + - lib_deps = ${Station_G2.lib_deps} ${esp32_ota.lib_deps} @@ -115,6 +118,9 @@ build_flags = ; -D MESH_DEBUG=1 build_src_filter = ${Station_G2.build_src_filter} +<../examples/simple_repeater> + - + - + - lib_deps = ${Station_G2.lib_deps} ${esp32_ota.lib_deps}