The PartitionTablePlan field that drives the optional app copy stores
the partition that will receive the running app (i.e. the destination
in the current table at the new slot's flash offset). The old name
``copy_source_part`` described the value backwards and risked future
callers swapping the esp_partition_copy() arguments; renamed to
``copy_dest_part`` along with the matching local
``app_copy_dest_part`` and the comments around them.
Also replace esp_ota_get_running_partition() in the copy path with
find_app_partition_at(running_app_offset, running_app_size). The IDF
call can return nullptr after a prior aborted partition-table OTA in
the same boot called esp_partition_unload_all() (the same condition
the cache in get_running_app_position() exists for); the previous code
would have dereferenced nullptr on the retry that the client error
message explicitly suggests.
No functional change on the success path; nullptr deref on the failed-
retry path is now reported as OTA_RESPONSE_ERROR_PARTITION_TABLE_UPDATE.
Every OTA backend's begin() now takes ``(size_t image_size, OTAType
ota_type = OTA_TYPE_UPDATE_APP)``. ESPHomeOTAComponent::handle_data_()
calls ``backend_->begin(ota_size, ota_type)`` unconditionally; the
USE_OTA_PARTITIONS ifdef around the call disappears. The four non-ESP32
backends (esp8266, rp2040, libretiny, host) accept the new parameter
and reject anything other than OTA_TYPE_UPDATE_APP up front, so the
client-visible behaviour is unchanged.
Same commit also trims the verbose block comments in the partition-
table TU and the IDF backend header, keeping the genuinely non-obvious
WHYs (slot-selection policy, NvsReinitGuard semantics, unload-then-null
ordering, cache-init flag, dump_config nullptr fallback) and dropping
the procedural narration.
No functional change.
Splits the partition-table OTA implementation out of the shared
ota_backend_esp_idf.cpp into a new translation unit gated by
USE_OTA_PARTITIONS. Builds without allow_partition_access compile
strictly less code and don't link esp_image_format / nvs_flash; the
common app-OTA backend is also easier to read without ~340 lines of
unrelated partition handling interleaved.
What moves: validate_new_partition_table_, update_partition_table,
get_running_app_position, the file-static running-app cache, the
NvsReinitGuard RAII helper, and the find_app_partition_at /
check_overlap helpers. What stays: begin/write/end/abort and the
factory.
Behaviour-preserving refactor.
Replace the four explicit nvs_flash_init() calls with a small RAII
guard (NvsReinitGuard) declared right after nvs_flash_deinit(). Each
failure path now just returns; the guard reinits NVS in its destructor.
The success path disarms it before the trailing return because the
device reboots immediately afterwards and reinit would only churn the
partition cache.
No behaviour change.
- upload_program: allow MQTT/MQTTIP devices for --partition-table.
MQTTIP gets resolved to a real IP by _resolve_network_devices(), so
rejecting any non-NETWORK port_type was incorrect; only SERIAL and
BOOTSEL are non-OTA upload paths.
- update_partition_table: re-initialize NVS on every failure path past
nvs_flash_deinit() so components that survive a failed OTA aren't
left with broken NVS handles. Success path stays as-is because the
device reboots immediately afterwards.
Adds an MQTTIP upload test and refreshes the gate's comment.
The running-app position cache lived as three function-local statics
inside get_running_app_position(). They cannot be IDFOTABackend members
(the backend is per-connection, the cache must outlive a backend that
called esp_partition_unload_all() in a prior aborted partition-table
OTA), but burying them inside the function made the lifetime and
shared-across-connections semantics implicit. Move them to file scope
with s_running_app_ prefix so the process-scoped lifetime is visible
at first read, and tighten the surrounding comments.
No behaviour change.
Splits the non-destructive validation phase out of update_partition_table()
into a dedicated method. update_partition_table() now reads top-down as
"check buffer state, find running app, validate the new table + plan the
target slot, then commit", with the destructive write isolated to the
final block.
The chosen slot and optional copy-source partition are returned via a
small PartitionTablePlan struct so the caller no longer juggles the two
candidate-index variables. Refactor only; behaviour and error semantics
are unchanged.
Split the combined "missing app, otadata, or nvs" verify failure into
three separate ESP_LOGE messages so users can see which check failed,
trim trailing spaces from the (err=0x%X) log strings, and document why
the partition-table espota2 test mocks SERVER_FEATURE_SUPPORTS_COMPRESSION
(intentional protocol-path coverage; the real IDFOTABackend never sets it).
Read the resolved partition-table file in upload_program before opening
a network connection. Reject anything that isn't 0xC00 bytes, doesn't
start with ESP_PARTITION_MAGIC, or is missing the MD5 checksum entry, so
mistakes (wrong file, swapped --file path) surface as a local error
instead of a post-handshake OTA_RESPONSE_ERROR_PARTITION_TABLE_VERIFY.
Includes unit tests covering size, magic, md5-presence, missing-file,
and end-to-end upload_program rejection, plus three real partition
tables checked in as fixtures (ESPHome build, ESP-IDF Hello-world,
esphome_dashboard prebuilt).