diff --git a/esphome/components/openthread/openthread.cpp b/esphome/components/openthread/openthread.cpp index 8bfc16b2e0..b98f109172 100644 --- a/esphome/components/openthread/openthread.cpp +++ b/esphome/components/openthread/openthread.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -229,26 +230,43 @@ void *OpenThreadSrpComponent::pool_alloc_(size_t size) { void OpenThreadSrpComponent::set_mdns(esphome::mdns::MDNSComponent *mdns) { this->mdns_ = mdns; } bool OpenThreadComponent::teardown() { - if (!this->teardown_started_) { - this->teardown_started_ = true; - ESP_LOGD(TAG, "Clear Srp"); - auto lock = InstanceLock::try_acquire(100); - if (!lock) { - ESP_LOGW(TAG, "Failed to acquire OpenThread lock during teardown, leaking memory"); - return true; - } - otInstance *instance = lock.get_instance(); - otSrpClientClearHostAndServices(instance); - otSrpClientBuffersFreeAllServices(instance); - global_openthread_component = nullptr; - ESP_LOGD(TAG, "Exit main loop "); - int error = this->openthread_stop_(); - if (error != 0) { - ESP_LOGW(TAG, "Failed attempt to stop main loop %d", error); - this->teardown_complete_ = true; - } + switch (this->teardown_stage_) { + case TeardownStage::TEARDOWN_STAGE_NOT_STARTED: { + auto lock = InstanceLock::try_acquire(100); + if (!lock) { + // Try again on next teardown loop + ESP_LOGV(TAG, "Failed to acquire OpenThread lock during teardown"); + return false; + } + // Start tearing down + this->teardown_stage_ = TeardownStage::TEARDOWN_STAGE_STOP_IN_PROCESS; + ESP_LOGV(TAG, "Clear SRP"); + otInstance *instance = lock.get_instance(); + otSrpClientClearHostAndServices(instance); + otSrpClientBuffersFreeAllServices(instance); + if (otThreadSetEnabled(instance, false) != OT_ERROR_NONE) { + ESP_LOGW(TAG, "Failed to disable Thread during teardown"); + } + if (otIp6SetEnabled(instance, false) != OT_ERROR_NONE) { + ESP_LOGW(TAG, "Failed to disable IPv6 during teardown"); + } + // Stop OpenThread + global_openthread_component = nullptr; + ESP_LOGV(TAG, "Stop OpenThread"); + int error = this->openthread_stop_(); + if (error != 0) { + ESP_LOGW(TAG, "Failed attempt to stop OpenThread %d", error); + this->teardown_stage_ = TeardownStage::TEARDOWN_STAGE_COMPLETED; + } + } break; + case TeardownStage::TEARDOWN_STAGE_STOP_IN_PROCESS: + // Waiting on OpenThread stop + break; + case TeardownStage::TEARDOWN_STAGE_COMPLETED: + ESP_LOGV(TAG, "OpenThreadComponent Teardown Complete"); + break; } - return this->teardown_complete_; + return this->teardown_stage_ == TeardownStage::TEARDOWN_STAGE_COMPLETED; } void OpenThreadComponent::on_factory_reset(std::function callback) { diff --git a/esphome/components/openthread/openthread.h b/esphome/components/openthread/openthread.h index b4654af21f..f4c6d0962a 100644 --- a/esphome/components/openthread/openthread.h +++ b/esphome/components/openthread/openthread.h @@ -19,6 +19,12 @@ namespace esphome::openthread { class InstanceLock; +enum class TeardownStage : uint8_t { + TEARDOWN_STAGE_NOT_STARTED = 0, + TEARDOWN_STAGE_STOP_IN_PROCESS, + TEARDOWN_STAGE_COMPLETED, +}; + template class OpenThreadComponentPollPeriodAction; class OpenThreadComponent final : public Component { @@ -71,9 +77,8 @@ class OpenThreadComponent final : public Component { #endif std::optional output_power_{}; std::atomic lock_initialized_{false}; - bool teardown_started_{false}; - bool teardown_complete_{false}; - bool connected_{false}; + std::atomic teardown_stage_{TeardownStage::TEARDOWN_STAGE_NOT_STARTED}; + std::atomic connected_{false}; private: // Stores a pointer to a string literal (static storage duration). diff --git a/esphome/components/openthread/openthread_esp.cpp b/esphome/components/openthread/openthread_esp.cpp index 4f6e618f49..881bbea3c9 100644 --- a/esphome/components/openthread/openthread_esp.cpp +++ b/esphome/components/openthread/openthread_esp.cpp @@ -168,7 +168,7 @@ void OpenThreadComponent::ot_main() { esp_netif_destroy(openthread_netif); esp_vfs_eventfd_unregister(); - this->teardown_complete_ = true; + this->teardown_stage_ = TeardownStage::TEARDOWN_STAGE_COMPLETED; vTaskDelete(NULL); } diff --git a/esphome/components/openthread/openthread_zephyr.cpp b/esphome/components/openthread/openthread_zephyr.cpp index 7b9f14ab8c..cacb4c0122 100644 --- a/esphome/components/openthread/openthread_zephyr.cpp +++ b/esphome/components/openthread/openthread_zephyr.cpp @@ -90,10 +90,8 @@ void OpenThreadComponent::ot_main() {} otInstance *OpenThreadComponent::get_openthread_instance_() { return openthread_get_default_instance(); } int OpenThreadComponent::openthread_stop_() { - // OT stack is intentionally left running — no Zephyr stop API. The state callback stays - // registered but is safe (null-checks global_openthread_component). nRF52840 never - // re-enters setup() after teardown so this is functionally correct. - this->teardown_complete_ = true; + // Zephyr has no stack-stop API, so stop is synchronous here; mark complete immediately. + this->teardown_stage_ = TeardownStage::TEARDOWN_STAGE_COMPLETED; return 0; }