mirror of
https://github.com/esphome/esphome.git
synced 2026-09-20 11:38:48 +00:00
[esphome.ota] Reject multi-port esphome OTA, drop redundant wake
Address copilot review on #15636. 1. Enforce single ESPHome OTA instance (BREAKING CHANGE). The `ota_esphome_final_validate` hook has always merged multiple `ota: - platform: esphome` configs by port so a user config and a remote package that both define OTA would merge rather than break. That merge behavior is preserved. But if two configs survive on *different* ports they produce two independent listening sockets, which is not a sane deployment: it creates ambiguity for safe_mode coordination and for the socket wake hook added in this PR. Raise cv.Invalid when more than one port remains after merging. 2. Drop redundant main-loop wake in Application::wake_ota_component_any_context. Every caller (lwip fast-select callback already calls xTaskNotifyGive, raw-tcp accept callback already calls wake_loop_any_context(), host path is already running post-select in the main loop) has woken the main loop by the time we reach this hook. Calling enable_loop_soon_any_context() would re-wake it. Application is a friend of Component, so set pending_enable_loop_ and has_pending_enable_loop_requests_ directly instead.
This commit is contained in:
@@ -78,6 +78,20 @@ def ota_esphome_final_validate(config):
|
||||
else:
|
||||
new_ota_conf.append(ota_conf)
|
||||
|
||||
# BREAKING CHANGE: Only a single ESPHome OTA instance is supported. Historically
|
||||
# the config layer merged multiple configs by port to accommodate users who had a
|
||||
# local 'ota:' block and then imported a remote package that also declared one —
|
||||
# the merge kept their build working. That merge behavior is preserved. But two
|
||||
# ESPHome OTA instances on *different* ports is not a real-world use case and
|
||||
# creates ambiguity for listeners, socket wake hooks, and safe_mode coordination.
|
||||
if len(merged_ota_esphome_configs_by_port) > 1:
|
||||
raise cv.Invalid(
|
||||
f"Only a single '{CONF_OTA}' '{CONF_PLATFORM}: {CONF_ESPHOME}' instance is "
|
||||
f"supported, but multiple were configured on different ports "
|
||||
f"({sorted(merged_ota_esphome_configs_by_port.keys())}). Remove the extra "
|
||||
f"configurations or place them on the same port so they can be merged."
|
||||
)
|
||||
|
||||
new_ota_conf.extend(merged_ota_esphome_configs_by_port.values())
|
||||
|
||||
full_conf[CONF_OTA] = new_ota_conf
|
||||
|
||||
@@ -564,11 +564,16 @@ class Application {
|
||||
/// a new connection arrives on the listening socket. OTA disables its own
|
||||
/// loop while idle to avoid per-tick dispatch overhead.
|
||||
void set_ota_wake_component(Component *component) { this->ota_wake_component_ = component; }
|
||||
/// Wake the registered OTA component (if any) from any context. Safe to call
|
||||
/// from the LwIP TCP/IP task and other callback contexts.
|
||||
/// Mark the registered OTA component (if any) for loop re-enable from any
|
||||
/// context. Intentionally does NOT call wake_loop_any_context() — every
|
||||
/// caller (lwip fast-select callback, raw-tcp accept callback, host
|
||||
/// select() return path) has already woken the main loop, so a second wake
|
||||
/// here would be redundant. Application is a friend of Component, so we
|
||||
/// set the pending-enable flags directly.
|
||||
void IRAM_ATTR wake_ota_component_any_context() {
|
||||
if (this->ota_wake_component_ != nullptr) {
|
||||
this->ota_wake_component_->enable_loop_soon_any_context();
|
||||
this->ota_wake_component_->pending_enable_loop_ = true;
|
||||
this->has_pending_enable_loop_requests_ = true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user