diff --git a/esphome/components/api/__init__.py b/esphome/components/api/__init__.py index 4b63c76fba..ae621d4802 100644 --- a/esphome/components/api/__init__.py +++ b/esphome/components/api/__init__.py @@ -1,8 +1,10 @@ import base64 +import logging from esphome import automation from esphome.automation import Condition import esphome.codegen as cg +from esphome.components.esp32 import add_idf_sdkconfig_option import esphome.config_validation as cv from esphome.const import ( CONF_ACTION, @@ -23,12 +25,14 @@ from esphome.const import ( CONF_TRIGGER_ID, CONF_VARIABLES, ) -from esphome.core import coroutine_with_priority +from esphome.core import CORE, coroutine_with_priority DEPENDENCIES = ["network"] AUTO_LOAD = ["socket"] CODEOWNERS = ["@OttoWinter"] +_LOGGER = logging.getLogger(__name__) + api_ns = cg.esphome_ns.namespace("api") APIServer = api_ns.class_("APIServer", cg.Component, cg.Controller) HomeAssistantServiceCallAction = api_ns.class_( @@ -49,6 +53,9 @@ SERVICE_ARG_NATIVE_TYPES = { "string[]": cg.std_vector.template(cg.std_string), } CONF_ENCRYPTION = "encryption" +CONF_HEAP_TRACING = "heap_tracing" +CONF_HEAP_TRACING_STANDALONE = "standalone" # vs SYSTEM +CONF_HEAP_TRACING_RECORDS = "num_records" def validate_encryption_key(value): @@ -95,6 +102,20 @@ def _encryption_schema(config): return ENCRYPTION_SCHEMA(config) +HEAP_TRACING_SCHEMA = cv.Schema( + { + cv.Optional(CONF_HEAP_TRACING_STANDALONE, default=True): cv.boolean, + cv.Optional(CONF_HEAP_TRACING_RECORDS, default=100): cv.positive_int, + } +) + + +def _heap_tracing_schema(config): + if config is None: + config = {} + return HEAP_TRACING_SCHEMA(config) + + CONFIG_SCHEMA = cv.All( cv.Schema( { @@ -109,6 +130,7 @@ CONFIG_SCHEMA = cv.All( ): ACTIONS_SCHEMA, cv.Exclusive(CONF_ACTIONS, group_of_exclusion=CONF_ACTIONS): ACTIONS_SCHEMA, cv.Optional(CONF_ENCRYPTION): _encryption_schema, + cv.Optional(CONF_HEAP_TRACING): _heap_tracing_schema, cv.Optional(CONF_ON_CLIENT_CONNECTED): automation.validate_automation( single=True ), @@ -176,6 +198,59 @@ async def to_code(config): else: cg.add_define("USE_API_PLAINTEXT") + # Handle heap tracing configuration if ESP32 platform and using ESP-IDF + if (heap_tracing_config := config.get(CONF_HEAP_TRACING, None)) is not None: + if CORE.using_esp_idf: + # Enable heap tracing in sdkconfig + add_idf_sdkconfig_option("CONFIG_HEAP_TRACING", True) + + # Set tracing mode (standalone or system) + if heap_tracing_config[CONF_HEAP_TRACING_STANDALONE]: + add_idf_sdkconfig_option("CONFIG_HEAP_TRACING_STANDALONE", True) + else: + add_idf_sdkconfig_option("CONFIG_HEAP_TRACING_SYSTEM", True) + + # Generate code to implement heap tracing + cg.add_global(cg.RawStatement('#include "esp_heap_trace.h"')) + + # Define the trace record buffer + num_records = heap_tracing_config[CONF_HEAP_TRACING_RECORDS] + cg.add_global( + cg.RawStatement( + f"static heap_trace_record_t trace_record[{num_records}];" + ) + ) + + # Add helper functions for heap tracing + cg.add_global( + cg.RawStatement( + """ +void start_heap_trace() { + heap_trace_init_standalone(trace_record, """ + + str(num_records) + + """); + heap_trace_start(HEAP_TRACE_LEAKS); +} + +void stop_and_dump_heap_trace() { + heap_trace_stop(); + heap_trace_dump(); +} +""" + ) + ) + + # Add periodic heap trace dumping to the api_server.cpp file + # This will be added in C++ code + cg.add_define("USE_API_HEAP_TRACE") + + else: + # Not using ESP-IDF, so we can't use heap tracing + _LOGGER.warning( + "Heap tracing is only available when using ESP-IDF. " + "Disabling heap tracing configuration." + ) + cg.add_define("USE_API") cg.add_global(api_ns.using) diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index 9a594c9223..830b0acce1 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -30,6 +30,11 @@ void APIServer::setup() { ESP_LOGCONFIG(TAG, "Setting up Home Assistant API server..."); this->setup_controller(); +#ifdef USE_API_HEAP_TRACE + ESP_LOGI(TAG, "Initializing heap tracing"); + start_heap_trace(); +#endif + #ifdef USE_API_NOISE uint32_t hash = 88491486UL; @@ -154,6 +159,21 @@ void APIServer::loop() { this->status_clear_warning(); } } + +#ifdef USE_API_HEAP_TRACE + // Periodically dump heap trace information (every 30 seconds) + static uint32_t last_heap_trace_dump = 0; + const uint32_t now = millis(); + if (now - last_heap_trace_dump > 30000) { // 30 seconds + ESP_LOGI(TAG, "Dumping heap trace information"); + stop_and_dump_heap_trace(); + + // Start a new trace for the next period + start_heap_trace(); + + last_heap_trace_dump = now; + } +#endif } void APIServer::dump_config() { @@ -466,6 +486,12 @@ void APIServer::on_shutdown() { c->send_disconnect_request(DisconnectRequest()); } delay(10); + +#ifdef USE_API_HEAP_TRACE + // Make sure to stop tracing on shutdown to get final results + ESP_LOGI(TAG, "Final heap trace dump on shutdown"); + stop_and_dump_heap_trace(); +#endif } } // namespace api diff --git a/heap_trace_example.yaml b/heap_trace_example.yaml new file mode 100644 index 0000000000..376a67c552 --- /dev/null +++ b/heap_trace_example.yaml @@ -0,0 +1,33 @@ +esphome: + name: esp32-heap-trace + platform: ESP32 + board: esp32dev + # Use ESP-IDF framework which is required for heap tracing + framework: + type: esp-idf + version: recommended + +# Enable logging +logger: + level: INFO + +# Enable Home Assistant API +api: + # Enable heap tracing with the following configuration + heap_tracing: + # Use standalone tracing (vs system tracing) + standalone: true + # Number of trace records to keep (more records = more memory usage) + num_records: 100 + +# Enable OTA updates +ota: + +wifi: + ssid: !secret wifi_ssid + password: !secret wifi_password + + # Enable fallback hotspot in case wifi connection fails + ap: + ssid: "Esp32-Heap-Trace" + password: "12345678"