From a881121110111ba829ab830b338dfb7675b9a979 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Wed, 22 Apr 2026 23:06:31 -0400 Subject: [PATCH] [ota] Make set_auth_password() lambda-callable via empty-password opt-in (#15928) --- esphome/components/esphome/ota/__init__.py | 10 +++++++--- esphome/components/esphome/ota/ota_esphome.h | 8 ++++++++ .../ota/test-empty_password.esp8266-ard.yaml | 14 ++++++++++++++ 3 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 tests/components/ota/test-empty_password.esp8266-ard.yaml diff --git a/esphome/components/esphome/ota/__init__.py b/esphome/components/esphome/ota/__init__.py index 5d35910fbd..bfa5ffb55c 100644 --- a/esphome/components/esphome/ota/__init__.py +++ b/esphome/components/esphome/ota/__init__.py @@ -150,10 +150,14 @@ async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) cg.add(var.set_port(config[CONF_PORT])) - # Password could be set to an empty string and we can assume that means no password - if config.get(CONF_PASSWORD): - cg.add(var.set_auth_password(config[CONF_PASSWORD])) + # Compile the auth path whenever `password:` is present in YAML, even if empty. + # An empty password opts in to the auth code path so set_auth_password() can be + # called at runtime (e.g. to rotate the password from a lambda). When `password:` + # is omitted entirely, the auth path is excluded to save flash on small devices. + if CONF_PASSWORD in config: cg.add_define("USE_OTA_PASSWORD") + if config[CONF_PASSWORD]: + cg.add(var.set_auth_password(config[CONF_PASSWORD])) cg.add_define("USE_OTA_VERSION", config[CONF_VERSION]) # Build flag so lwip_fast_select.c (a .c file that can't include defines.h) sees it. cg.add_build_flag("-DUSE_OTA_PLATFORM_ESPHOME") diff --git a/esphome/components/esphome/ota/ota_esphome.h b/esphome/components/esphome/ota/ota_esphome.h index f3a5952398..53288fc000 100644 --- a/esphome/components/esphome/ota/ota_esphome.h +++ b/esphome/components/esphome/ota/ota_esphome.h @@ -28,6 +28,14 @@ class ESPHomeOTAComponent final : public ota::OTAComponent { }; #ifdef USE_OTA_PASSWORD void set_auth_password(const std::string &password) { password_ = password; } +#else + // Stub so lambdas referencing set_auth_password() produce a clear error instead of + // a cryptic "no member" diagnostic. Only fires if the stub is actually instantiated. + template void set_auth_password(const std::string &) { + static_assert(B, "set_auth_password() requires the OTA auth path to be compiled. " + "Add 'password: \"\"' (empty string) to your 'ota: - platform: esphome' " + "config to enable runtime password rotation."); + } #endif // USE_OTA_PASSWORD /// Manually set the port OTA should listen on diff --git a/tests/components/ota/test-empty_password.esp8266-ard.yaml b/tests/components/ota/test-empty_password.esp8266-ard.yaml new file mode 100644 index 0000000000..e48f67e47e --- /dev/null +++ b/tests/components/ota/test-empty_password.esp8266-ard.yaml @@ -0,0 +1,14 @@ +wifi: + ssid: MySSID + password: password1 + +ota: + - platform: esphome + id: my_ota + port: 3287 + password: "" + +esphome: + on_boot: + then: + - lambda: id(my_ota).set_auth_password("runtime_password");