diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index aed4ecad90..afe9251efe 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -1287,6 +1287,18 @@ async def to_code(config): # Increase freertos tick speed from 100Hz to 1kHz so that delay() resolution is 1ms add_idf_sdkconfig_option("CONFIG_FREERTOS_HZ", 1000) + # Reduce FreeRTOS max priorities from 25 to 16 to save RAM + # pxReadyTasksLists uses 20 bytes per priority level, so this saves 180 bytes + # All ESPHome tasks use relative priorities (configMAX_PRIORITIES - X) to scale automatically + # See https://github.com/espressif/esp-idf/issues/13041 for context + add_idf_sdkconfig_option("CONFIG_FREERTOS_MAX_PRIORITIES", 16) + + # Set LWIP TCP/IP task priority to fit within reduced priority range (0-15) + # Default is 18, which would be invalid with MAX_PRIORITIES=16 + # Priority 8 maintains the original hierarchy: I2S speaker (10) > LWIP (8) > mixer (6) + # This ensures audio I/O tasks aren't blocked by network, while network isn't starved by mixing + add_idf_sdkconfig_option("CONFIG_LWIP_TCPIP_TASK_PRIO", 8) + # Place non-ISR FreeRTOS functions into flash instead of IRAM # This saves up to 8KB of IRAM. ISR-safe functions (FromISR variants) stay in IRAM. # In ESP-IDF 6.0 this becomes the default and CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH diff --git a/esphome/components/esp32/core.cpp b/esphome/components/esp32/core.cpp index 09a45c14a6..4c5f7e3334 100644 --- a/esphome/components/esp32/core.cpp +++ b/esphome/components/esp32/core.cpp @@ -3,6 +3,7 @@ #include "esphome/core/defines.h" #include "esphome/core/hal.h" #include "esphome/core/helpers.h" +#include "esphome/core/task_priorities.h" #include "preferences.h" #include #include @@ -66,10 +67,14 @@ void loop_task(void *pv_params) { extern "C" void app_main() { initArduino(); esp32::setup_preferences(); + // TASK_PRIORITY_APPLICATION: baseline priority for main loop - all component loops + // run here. Higher priority tasks (audio, network) preempt this when needed. #if CONFIG_FREERTOS_UNICORE - xTaskCreate(loop_task, "loopTask", ESPHOME_LOOP_TASK_STACK_SIZE, nullptr, 1, &loop_task_handle); + xTaskCreate(loop_task, "loopTask", ESPHOME_LOOP_TASK_STACK_SIZE, nullptr, TASK_PRIORITY_APPLICATION, + &loop_task_handle); #else - xTaskCreatePinnedToCore(loop_task, "loopTask", ESPHOME_LOOP_TASK_STACK_SIZE, nullptr, 1, &loop_task_handle, 1); + xTaskCreatePinnedToCore(loop_task, "loopTask", ESPHOME_LOOP_TASK_STACK_SIZE, nullptr, TASK_PRIORITY_APPLICATION, + &loop_task_handle, 1); #endif } diff --git a/esphome/components/esp32_camera/esp32_camera.cpp b/esphome/components/esp32_camera/esp32_camera.cpp index 5466d2e7ef..7221055743 100644 --- a/esphome/components/esp32_camera/esp32_camera.cpp +++ b/esphome/components/esp32_camera/esp32_camera.cpp @@ -4,6 +4,7 @@ #include "esphome/core/application.h" #include "esphome/core/hal.h" #include "esphome/core/log.h" +#include "esphome/core/task_priorities.h" #include @@ -42,11 +43,13 @@ void ESP32Camera::setup() { /* initialize RTOS */ this->framebuffer_get_queue_ = xQueueCreate(1, sizeof(camera_fb_t *)); this->framebuffer_return_queue_ = xQueueCreate(1, sizeof(camera_fb_t *)); + // TASK_PRIORITY_APPLICATION: same as main loop - camera capture is buffered, + // not real-time critical like audio xTaskCreatePinnedToCore(&ESP32Camera::framebuffer_task, "framebuffer_task", // name FRAMEBUFFER_TASK_STACK_SIZE, // stack size this, // task pv params - 1, // priority + TASK_PRIORITY_APPLICATION, // priority nullptr, // handle 1 // core ); diff --git a/esphome/components/http_request/update/http_request_update.cpp b/esphome/components/http_request/update/http_request_update.cpp index c63e55d159..1efecccf38 100644 --- a/esphome/components/http_request/update/http_request_update.cpp +++ b/esphome/components/http_request/update/http_request_update.cpp @@ -2,6 +2,9 @@ #include "esphome/core/application.h" #include "esphome/core/version.h" +#ifdef USE_ESP32 +#include "esphome/core/task_priorities.h" +#endif #include "esphome/components/json/json_util.h" #include "esphome/components/network/util.h" @@ -46,7 +49,9 @@ void HttpRequestUpdate::update() { return; } #ifdef USE_ESP32 - xTaskCreate(HttpRequestUpdate::update_task, "update_task", 8192, (void *) this, 1, &this->update_task_handle_); + // TASK_PRIORITY_APPLICATION: same as main loop - update check is background work + xTaskCreate(HttpRequestUpdate::update_task, "update_task", 8192, (void *) this, TASK_PRIORITY_APPLICATION, + &this->update_task_handle_); #else this->update_task(this); #endif diff --git a/esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp b/esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp index cdebc214e2..b0b9caba32 100644 --- a/esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp +++ b/esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp @@ -11,6 +11,7 @@ #include "esphome/core/hal.h" #include "esphome/core/log.h" +#include "esphome/core/task_priorities.h" #include "esphome/components/audio/audio.h" @@ -22,7 +23,6 @@ static const UBaseType_t MAX_LISTENERS = 16; static const uint32_t READ_DURATION_MS = 16; static const size_t TASK_STACK_SIZE = 4096; -static const ssize_t TASK_PRIORITY = 23; static const char *const TAG = "i2s_audio.microphone"; @@ -520,8 +520,10 @@ void I2SAudioMicrophone::loop() { } if (this->task_handle_ == nullptr) { - xTaskCreate(I2SAudioMicrophone::mic_task, "mic_task", TASK_STACK_SIZE, (void *) this, TASK_PRIORITY, - &this->task_handle_); + // TASK_PRIORITY_AUDIO_CAPTURE: highest application priority - real-time audio + // input cannot tolerate delays without dropping samples + xTaskCreate(I2SAudioMicrophone::mic_task, "mic_task", TASK_STACK_SIZE, (void *) this, + TASK_PRIORITY_AUDIO_CAPTURE, &this->task_handle_); if (this->task_handle_ == nullptr) { ESP_LOGE(TAG, "Task failed to start, retrying in 1 second"); diff --git a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp index c934d12d65..9328cef700 100644 --- a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp +++ b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp @@ -14,6 +14,7 @@ #include "esphome/core/application.h" #include "esphome/core/hal.h" #include "esphome/core/log.h" +#include "esphome/core/task_priorities.h" #include "esp_timer.h" @@ -24,7 +25,6 @@ static const uint32_t DMA_BUFFER_DURATION_MS = 15; static const size_t DMA_BUFFERS_COUNT = 4; static const size_t TASK_STACK_SIZE = 4096; -static const ssize_t TASK_PRIORITY = 19; static const size_t I2S_EVENT_QUEUE_COUNT = DMA_BUFFERS_COUNT + 1; @@ -151,8 +151,10 @@ void I2SAudioSpeaker::loop() { } if (this->speaker_task_handle_ == nullptr) { - xTaskCreate(I2SAudioSpeaker::speaker_task, "speaker_task", TASK_STACK_SIZE, (void *) this, TASK_PRIORITY, - &this->speaker_task_handle_); + // TASK_PRIORITY_AUDIO_OUTPUT: high priority for real-time audio output, + // below capture (TASK_PRIORITY_AUDIO_CAPTURE) but above network tasks + xTaskCreate(I2SAudioSpeaker::speaker_task, "speaker_task", TASK_STACK_SIZE, (void *) this, + TASK_PRIORITY_AUDIO_OUTPUT, &this->speaker_task_handle_); if (this->speaker_task_handle_ == nullptr) { ESP_LOGE(TAG, "Task failed to start, retrying in 1 second"); diff --git a/esphome/components/micro_wake_word/micro_wake_word.cpp b/esphome/components/micro_wake_word/micro_wake_word.cpp index d7e80efc84..8ccc49a1ec 100644 --- a/esphome/components/micro_wake_word/micro_wake_word.cpp +++ b/esphome/components/micro_wake_word/micro_wake_word.cpp @@ -6,6 +6,7 @@ #include "esphome/core/hal.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" +#include "esphome/core/task_priorities.h" #include "esphome/components/audio/audio_transfer_buffer.h" @@ -25,7 +26,6 @@ static const size_t DATA_TIMEOUT_MS = 50; static const uint32_t RING_BUFFER_DURATION_MS = 120; static const uint32_t INFERENCE_TASK_STACK_SIZE = 3072; -static const UBaseType_t INFERENCE_TASK_PRIORITY = 3; enum EventGroupBits : uint32_t { COMMAND_STOP = (1 << 0), // Signals the inference task should stop @@ -305,8 +305,10 @@ void MicroWakeWord::loop() { return; } + // TASK_PRIORITY_INFERENCE: above main loop (TASK_PRIORITY_APPLICATION) but below + // protocol tasks (TASK_PRIORITY_PROTOCOL) - ML inference is background work xTaskCreate(MicroWakeWord::inference_task, "mww", INFERENCE_TASK_STACK_SIZE, (void *) this, - INFERENCE_TASK_PRIORITY, &this->inference_task_handle_); + TASK_PRIORITY_INFERENCE, &this->inference_task_handle_); if (this->inference_task_handle_ == nullptr) { FrontendFreeStateContents(&this->frontend_state_); // Deallocate frontend state diff --git a/esphome/components/mixer/speaker/mixer_speaker.cpp b/esphome/components/mixer/speaker/mixer_speaker.cpp index 043b629cf1..a325e26930 100644 --- a/esphome/components/mixer/speaker/mixer_speaker.cpp +++ b/esphome/components/mixer/speaker/mixer_speaker.cpp @@ -5,6 +5,7 @@ #include "esphome/core/hal.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" +#include "esphome/core/task_priorities.h" #include #include @@ -12,8 +13,6 @@ namespace esphome { namespace mixer_speaker { -static const UBaseType_t MIXER_TASK_PRIORITY = 10; - static const uint32_t TRANSFER_BUFFER_DURATION_MS = 50; static const uint32_t TASK_DELAY_MS = 25; @@ -385,8 +384,10 @@ esp_err_t MixerSpeaker::start_task_() { } if (this->task_handle_ == nullptr) { + // TASK_PRIORITY_AUDIO_MIXER: below I2S tasks (TASK_PRIORITY_AUDIO_OUTPUT) but + // above protocol tasks - mixing is buffered but feeds real-time output this->task_handle_ = xTaskCreateStatic(audio_mixer_task, "mixer", TASK_STACK_SIZE, (void *) this, - MIXER_TASK_PRIORITY, this->task_stack_buffer_, &this->task_stack_); + TASK_PRIORITY_AUDIO_MIXER, this->task_stack_buffer_, &this->task_stack_); } if (this->task_handle_ == nullptr) { diff --git a/esphome/components/mqtt/mqtt_backend_esp32.cpp b/esphome/components/mqtt/mqtt_backend_esp32.cpp index c12c79499f..f127d69332 100644 --- a/esphome/components/mqtt/mqtt_backend_esp32.cpp +++ b/esphome/components/mqtt/mqtt_backend_esp32.cpp @@ -61,7 +61,10 @@ bool MQTTBackendESP32::initialize_() { // Create the task only after MQTT client is initialized successfully // Use larger stack size when TLS is enabled size_t stack_size = this->ca_certificate_.has_value() ? TASK_STACK_SIZE_TLS : TASK_STACK_SIZE; - xTaskCreate(esphome_mqtt_task, "esphome_mqtt", stack_size, (void *) this, TASK_PRIORITY, &this->task_handle_); + // TASK_PRIORITY_PROTOCOL: above main loop (TASK_PRIORITY_APPLICATION) but below + // audio tasks - MQTT needs responsive scheduling for message handling + xTaskCreate(esphome_mqtt_task, "esphome_mqtt", stack_size, (void *) this, TASK_PRIORITY_PROTOCOL, + &this->task_handle_); if (this->task_handle_ == nullptr) { ESP_LOGE(TAG, "Failed to create MQTT task"); // Clean up MQTT client since we can't start the async task diff --git a/esphome/components/mqtt/mqtt_backend_esp32.h b/esphome/components/mqtt/mqtt_backend_esp32.h index bd2d2a67b2..18761d2196 100644 --- a/esphome/components/mqtt/mqtt_backend_esp32.h +++ b/esphome/components/mqtt/mqtt_backend_esp32.h @@ -14,6 +14,7 @@ #include "esphome/core/helpers.h" #include "esphome/core/lock_free_queue.h" #include "esphome/core/event_pool.h" +#include "esphome/core/task_priorities.h" namespace esphome::mqtt { @@ -117,8 +118,7 @@ class MQTTBackendESP32 final : public MQTTBackend { static const size_t MQTT_BUFFER_SIZE = 4096; static const size_t TASK_STACK_SIZE = 3072; static const size_t TASK_STACK_SIZE_TLS = 4096; // Larger stack for TLS operations - static const ssize_t TASK_PRIORITY = 5; - static const uint8_t MQTT_QUEUE_LENGTH = 30; // 30*12 bytes = 360 + static const uint8_t MQTT_QUEUE_LENGTH = 30; // 30*12 bytes = 360 void set_keep_alive(uint16_t keep_alive) final { this->keep_alive_ = keep_alive; } void set_client_id(const char *client_id) final { this->client_id_ = client_id; } diff --git a/esphome/components/openthread/openthread_esp.cpp b/esphome/components/openthread/openthread_esp.cpp index a9aff3cce4..9b2873fb9c 100644 --- a/esphome/components/openthread/openthread_esp.cpp +++ b/esphome/components/openthread/openthread_esp.cpp @@ -10,6 +10,7 @@ #include "esp_task_wdt.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" +#include "esphome/core/task_priorities.h" #include "esp_err.h" #include "esp_event.h" @@ -39,12 +40,14 @@ void OpenThreadComponent::setup() { ESP_ERROR_CHECK(esp_netif_init()); ESP_ERROR_CHECK(esp_vfs_eventfd_register(&eventfd_config)); + // TASK_PRIORITY_PROTOCOL: same as USB host/MQTT - network protocol tasks need + // responsive scheduling but below audio tasks xTaskCreate( [](void *arg) { static_cast(arg)->ot_main(); vTaskDelete(nullptr); }, - "ot_main", 10240, this, 5, nullptr); + "ot_main", 10240, this, TASK_PRIORITY_PROTOCOL, nullptr); } static esp_netif_t *init_openthread_netif(const esp_openthread_platform_config_t *config) { diff --git a/esphome/components/resampler/speaker/resampler_speaker.cpp b/esphome/components/resampler/speaker/resampler_speaker.cpp index ad61aca084..7ed709ee43 100644 --- a/esphome/components/resampler/speaker/resampler_speaker.cpp +++ b/esphome/components/resampler/speaker/resampler_speaker.cpp @@ -6,6 +6,7 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" +#include "esphome/core/task_priorities.h" #include #include @@ -13,8 +14,6 @@ namespace esphome { namespace resampler { -static const UBaseType_t RESAMPLER_TASK_PRIORITY = 1; - static const uint32_t TRANSFER_BUFFER_DURATION_MS = 50; static const uint32_t TASK_DELAY_MS = 20; @@ -185,8 +184,10 @@ esp_err_t ResamplerSpeaker::start_task_() { } if (this->task_handle_ == nullptr) { + // TASK_PRIORITY_APPLICATION: same as main loop - resampling is buffered audio + // processing, not real-time I/O this->task_handle_ = xTaskCreateStatic(resample_task, "sample", TASK_STACK_SIZE, (void *) this, - RESAMPLER_TASK_PRIORITY, this->task_stack_buffer_, &this->task_stack_); + TASK_PRIORITY_APPLICATION, this->task_stack_buffer_, &this->task_stack_); } if (this->task_handle_ == nullptr) { diff --git a/esphome/components/speaker/media_player/speaker_media_player.cpp b/esphome/components/speaker/media_player/speaker_media_player.cpp index 172bc980a8..e7ef677ed2 100644 --- a/esphome/components/speaker/media_player/speaker_media_player.cpp +++ b/esphome/components/speaker/media_player/speaker_media_player.cpp @@ -3,6 +3,7 @@ #ifdef USE_ESP32 #include "esphome/core/log.h" +#include "esphome/core/task_priorities.h" #include "esphome/components/audio/audio.h" #ifdef USE_OTA @@ -45,9 +46,6 @@ namespace speaker { static const uint32_t MEDIA_CONTROLS_QUEUE_LENGTH = 20; -static const UBaseType_t MEDIA_PIPELINE_TASK_PRIORITY = 1; -static const UBaseType_t ANNOUNCEMENT_PIPELINE_TASK_PRIORITY = 1; - static const char *const TAG = "speaker_media_player"; void SpeakerMediaPlayer::setup() { @@ -70,9 +68,10 @@ void SpeakerMediaPlayer::setup() { ota::get_global_ota_callback()->add_global_state_listener(this); #endif - this->announcement_pipeline_ = - make_unique(this->announcement_speaker_, this->buffer_size_, this->task_stack_in_psram_, "ann", - ANNOUNCEMENT_PIPELINE_TASK_PRIORITY); + // TASK_PRIORITY_APPLICATION: same as main loop - media pipelines handle buffered + // audio streaming, not real-time I/O, so they don't need elevated priority + this->announcement_pipeline_ = make_unique( + this->announcement_speaker_, this->buffer_size_, this->task_stack_in_psram_, "ann", TASK_PRIORITY_APPLICATION); if (this->announcement_pipeline_ == nullptr) { ESP_LOGE(TAG, "Failed to create announcement pipeline"); @@ -81,7 +80,7 @@ void SpeakerMediaPlayer::setup() { if (!this->single_pipeline_()) { this->media_pipeline_ = make_unique(this->media_speaker_, this->buffer_size_, - this->task_stack_in_psram_, "med", MEDIA_PIPELINE_TASK_PRIORITY); + this->task_stack_in_psram_, "med", TASK_PRIORITY_APPLICATION); if (this->media_pipeline_ == nullptr) { ESP_LOGE(TAG, "Failed to create media pipeline"); diff --git a/esphome/components/uart/uart_component_esp_idf.cpp b/esphome/components/uart/uart_component_esp_idf.cpp index 90997787aa..a973e8cb2b 100644 --- a/esphome/components/uart/uart_component_esp_idf.cpp +++ b/esphome/components/uart/uart_component_esp_idf.cpp @@ -7,6 +7,7 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" #include "esphome/core/gpio.h" +#include "esphome/core/task_priorities.h" #include "driver/gpio.h" #include "soc/gpio_num.h" #include "soc/uart_pins.h" @@ -367,12 +368,13 @@ void IDFUARTComponent::check_logger_conflict() {} #ifdef USE_UART_WAKE_LOOP_ON_RX void IDFUARTComponent::start_rx_event_task_() { - // Create FreeRTOS task to monitor UART events - BaseType_t result = xTaskCreate(rx_event_task_func, // Task function - "uart_rx_evt", // Task name (max 16 chars) - 2240, // Stack size in bytes (~2.2KB); increase if needed for logging - this, // Task parameter (this pointer) - tskIDLE_PRIORITY + 1, // Priority (low, just above idle) + // TASK_PRIORITY_APPLICATION: same as main loop - UART RX monitoring is lightweight, + // just wakes main loop when data arrives + BaseType_t result = xTaskCreate(rx_event_task_func, // Task function + "uart_rx_evt", // Task name (max 16 chars) + 2240, // Stack size in bytes (~2.2KB) + this, // Task parameter (this pointer) + TASK_PRIORITY_APPLICATION, &this->rx_event_task_handle_ // Task handle ); diff --git a/esphome/components/usb_cdc_acm/usb_cdc_acm_esp32.cpp b/esphome/components/usb_cdc_acm/usb_cdc_acm_esp32.cpp index 224d6e3ab1..c20c19c93a 100644 --- a/esphome/components/usb_cdc_acm/usb_cdc_acm_esp32.cpp +++ b/esphome/components/usb_cdc_acm/usb_cdc_acm_esp32.cpp @@ -2,6 +2,7 @@ #include "usb_cdc_acm.h" #include "esphome/core/application.h" #include "esphome/core/log.h" +#include "esphome/core/task_priorities.h" #include #include @@ -162,7 +163,9 @@ void USBCDCACMInstance::setup() { // Create a simple, unique task name per interface char task_name[] = "usb_tx_0"; task_name[sizeof(task_name) - 1] = format_hex_char(static_cast(this->itf_)); - xTaskCreate(usb_tx_task_fn, task_name, stack_size, this, 4, &this->usb_tx_task_handle_); + // TASK_PRIORITY_USB_SERIAL: above main loop (TASK_PRIORITY_APPLICATION) and + // wake word (TASK_PRIORITY_INFERENCE), below protocol tasks (TASK_PRIORITY_PROTOCOL) + xTaskCreate(usb_tx_task_fn, task_name, stack_size, this, TASK_PRIORITY_USB_SERIAL, &this->usb_tx_task_handle_); if (this->usb_tx_task_handle_ == nullptr) { ESP_LOGE(TAG, "Failed to create USB TX task for itf %d", this->itf_); diff --git a/esphome/components/usb_host/usb_host.h b/esphome/components/usb_host/usb_host.h index d11a148a0f..0ea1bac925 100644 --- a/esphome/components/usb_host/usb_host.h +++ b/esphome/components/usb_host/usb_host.h @@ -4,6 +4,7 @@ #if defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) #include "esphome/core/defines.h" #include "esphome/core/component.h" +#include "esphome/core/task_priorities.h" #include #include "usb/usb_host.h" #include @@ -69,7 +70,6 @@ static constexpr trq_bitmask_t ALL_REQUESTS_IN_USE = MAX_REQUESTS == 32 ? ~0 : ( static constexpr size_t USB_EVENT_QUEUE_SIZE = 32; // Size of event queue between USB task and main loop static constexpr size_t USB_TASK_STACK_SIZE = 4096; // Stack size for USB task (same as ESP-IDF USB examples) -static constexpr UBaseType_t USB_TASK_PRIORITY = 5; // Higher priority than main loop (tskIDLE_PRIORITY + 5) // used to report a transfer status struct TransferStatus { diff --git a/esphome/components/usb_host/usb_host_client.cpp b/esphome/components/usb_host/usb_host_client.cpp index 09da6e3b73..1a4bc17c98 100644 --- a/esphome/components/usb_host/usb_host_client.cpp +++ b/esphome/components/usb_host/usb_host_client.cpp @@ -215,11 +215,12 @@ void USBClient::setup() { } // Create and start USB task + // TASK_PRIORITY_PROTOCOL: above main loop (TASK_PRIORITY_APPLICATION) but below + // audio tasks - USB host needs responsive scheduling for device communication xTaskCreate(usb_task_fn, "usb_task", USB_TASK_STACK_SIZE, // Stack size this, // Task parameter - USB_TASK_PRIORITY, // Priority (higher than main loop) - &this->usb_task_handle_); + TASK_PRIORITY_PROTOCOL, &this->usb_task_handle_); if (this->usb_task_handle_ == nullptr) { ESP_LOGE(TAG, "Failed to create USB task");