PlatformIO's idedata may list flash images that do not exist on disk
(e.g. a tasmota tinyuf2.bin referenced by the adafruit_qtpy_esp32s3_n4r2
board). Previously the CLI passed every entry straight to esptool, which
aborted the entire flash with "No such file or directory". The dashboard
path is unaffected because it flashes the pre-merged firmware.factory.bin
produced by the post-build step, which already tolerates missing inputs.
Filter non-existent extra_flash_images with a warning so a stale or
incorrect platform-declared image no longer breaks esphome run.
Fixes https://github.com/esphome/esphome/issues/15634
Addresses Copilot review suggestion on #15720: when the handler aborts a
previously interrupted OTA session because a new upload arrived, also fire
the OTA_ABORT state notification so user-facing on_abort: automations and
other OTA state listeners observe the teardown. Without this, the abort
would be silent to listeners (distinct from OTA_ERROR, which signals a
failed session rather than a superseded one).
The previous fix tracked the AsyncWebServerRequest pointer to distinguish
web_server_idf's double index==0 callbacks (Start marker with data==nullptr,
then the first real data chunk) from a retry after an interrupted upload.
That is unreliable: AsyncWebServerRequest is a stack-allocated object in the
httpd task, so a new request that happens to reuse the same stack address
as an interrupted one compares equal and silently skips the abort. Closing
a browser tab mid-upload and starting a fresh upload from another window
could then concatenate partial data from the first upload with the new
image.
Gate the init block on 'index == 0 && len > 0' instead. This uniquely
identifies the first real byte of an upload on both IDF (start-marker has
len==0) and Arduino, no identity tracking needed.
web_server_idf invokes handleUpload twice with index==0 at the start of every
upload (once as a 'Start' signal with nullptr data, then with the first data
chunk). The previous fix treated the second call as a stale-session retry,
wastefully aborting and re-initializing the backend on every single upload.
Store the AsyncWebServerRequest pointer that owns the current session and only
tear down the backend when a different request arrives at index==0. Clear the
tracked pointer on every backend reset path so a stack-reused request pointer
from a later upload cannot collide.
If a captive_portal or web_server OTA upload is interrupted mid-stream (e.g. TCP
connection reset), the shared OTARequestHandler's ota_backend_ is left open. When
the browser resends the multipart POST, the guard 'index == 0 && !ota_backend_'
skipped re-initialization, so new bytes were written at the previous session's
offset. The Updater's end() then reports success with a concatenated image in
flash, bricking the device on reboot.
Abort and reset any in-progress backend whenever a new multipart upload starts
(index == 0) so the fresh upload begins a clean OTA session.
The esp32 to_code runs at PLATFORM priority (1000) before component
to_code at COMPONENT priority (0), so the flag was set too late.
Move to config validation to ensure it is set before esp32 processes
sdkconfig options.
When flash cache is disabled during background flash operations (NVS
writes by WiFi, BLE, Zigbee, Thread, power management, etc.), the ADC
oneshot read function will crash if it is in flash. This places the
ADC oneshot control functions in IRAM by setting
CONFIG_ADC_ONESHOT_CTRL_FUNC_IN_IRAM when the ADC component is used.
Adds require_adc_oneshot_iram() helper and adc_oneshot_in_iram advanced
config option.
Mark SubscribeLogsResponse with (speed_optimized) = true so its
encode() and calculate_size() are compiled with -O2. Log messages
are among the most frequent messages sent over the API, especially
during debug sessions.
Add __attribute__((optimize("O2"))) to the main loop functions
(loop_task on ESP32, codegen loop() on other platforms) so GCC
inlines scheduler helpers and loop bookkeeping more aggressively.
Under -Os, GCC outlines small functions (Scheduler::call helpers,
millis conversions, etc.) that are called every loop iteration.
With -O2, these get inlined into the loop body, reducing call
overhead on the hottest code path in the firmware.
ESP32: loop_task grows from 303 to 416 bytes (+113 bytes).
ESP8266: no change (already fully inlined via ESPHOME_ALWAYS_INLINE).
Add __attribute__((optimize("O2"))) to the main loop functions
(loop_task on ESP32, codegen loop() on other platforms) so GCC
inlines scheduler helpers and loop bookkeeping more aggressively.
Under -Os, GCC outlines small functions (Scheduler::call helpers,
millis conversions, etc.) that are called every loop iteration.
With -O2, these get inlined into the loop body, reducing call
overhead on the hottest code path in the firmware.
ESP32: loop_task grows from 303 to 416 bytes (+113 bytes).
ESP8266: no change (already fully inlined via ESPHOME_ALWAYS_INLINE).