2#if defined(USE_OPENTHREAD) && defined(USE_ESP32)
3#include <openthread/dataset.h>
4#include <openthread/ip6.h>
5#include <openthread/tasklet.h>
6#ifdef USE_OPENTHREAD_BORDER_ROUTER
7#include <openthread/platform/infra_if.h>
11#ifdef USE_OPENTHREAD_BORDER_ROUTER
12#include "esp_openthread_border_router.h"
14#ifndef USE_OPENTHREAD_RCP_UART
15#include "esp_coexist.h"
18#ifdef USE_OPENTHREAD_RCP_UART
19#include "esp_openthread_spinel.h"
22#include "esp_openthread.h"
23#include "esp_openthread_lock.h"
24#include "esp_openthread_types.h"
25#include "esp_task_wdt.h"
34#include "esp_openthread_netif_glue.h"
35#include "esp_vfs_eventfd.h"
36#include "freertos/FreeRTOS.h"
37#include "freertos/task.h"
43void OpenThreadComponent::setup() {
44#ifdef USE_OPENTHREAD_BORDER_ROUTER
46 if (backbone_netif ==
nullptr) {
47 ESP_LOGE(TAG,
"Wi-Fi STA backbone netif is not initialized");
48 this->teardown_stage_ = TeardownStage::TEARDOWN_STAGE_COMPLETED;
52 esp_openthread_set_backbone_netif(backbone_netif);
58#if defined(USE_OPENTHREAD_BORDER_ROUTER) && !defined(USE_OPENTHREAD_RCP_UART)
59 constexpr size_t max_eventfds = 4;
61 constexpr size_t max_eventfds = 3;
63 esp_vfs_eventfd_config_t eventfd_config = {
64 .max_fds = max_eventfds,
70 ESP_LOGE(TAG,
"NVS unavailable");
71 this->teardown_stage_ = TeardownStage::TEARDOWN_STAGE_COMPLETED;
75 ESP_ERROR_CHECK(esp_vfs_eventfd_register(&eventfd_config));
82 static_cast<OpenThreadComponent *
>(arg)->ot_main_();
85 CONFIG_OPENTHREAD_TASK_NAME, CONFIG_OPENTHREAD_TASK_SIZE,
this, CONFIG_OPENTHREAD_TASK_PRIORITY,
87 ESP_LOGE(TAG,
"Failed to create OpenThread task");
88 esp_vfs_eventfd_unregister();
89 this->teardown_stage_ = TeardownStage::TEARDOWN_STAGE_COMPLETED;
94void OpenThreadComponent::ot_main_() {
95 esp_openthread_platform_config_t config = {
98#ifdef USE_OPENTHREAD_RCP_UART
99 .radio_mode = RADIO_MODE_UART_RCP,
105 .baud_rate =
static_cast<int>(this->rcp_baud_rate_),
106 .data_bits = UART_DATA_8_BITS,
107 .parity = UART_PARITY_DISABLE,
108 .stop_bits = UART_STOP_BITS_1,
109 .flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
110 .rx_flow_ctrl_thresh = 0,
111 .source_clk = UART_SCLK_DEFAULT,
113 .rx_pin =
static_cast<gpio_num_t
>(this->rcp_rx_pin_),
114 .tx_pin =
static_cast<gpio_num_t
>(this->rcp_tx_pin_),
117 .radio_mode = RADIO_MODE_NATIVE,
118 .radio_uart_config = {},
130 .storage_partition_name =
"nvs",
131 .netif_queue_size = 10,
132 .task_queue_size = 10,
136#ifdef USE_OPENTHREAD_RCP_UART
138 if (this->rcp_reset_pin_ !=
nullptr) {
139 this->rcp_reset_pin_->setup();
140 esp_openthread_register_rcp_failure_handler(OpenThreadComponent::rcp_failure_handler);
141 esp_openthread_set_coprocessor_reset_failure_callback(OpenThreadComponent::rcp_failure_handler);
142 this->reset_rcp_(
false);
146 ESP_ERROR_CHECK(esp_openthread_init(&config));
149 esp_netif_config_t netif_config = ESP_NETIF_DEFAULT_OPENTHREAD();
151 assert(netif !=
nullptr);
152 ESP_ERROR_CHECK(esp_netif_attach(netif, esp_openthread_netif_glue_init(&config)));
153#ifndef USE_OPENTHREAD_BORDER_ROUTER
154 esp_netif_set_default_netif(netif);
157 ESP_LOGD(TAG,
"Thread Version: %" PRIu16, otThreadGetVersion());
159 otInstance *instance = esp_openthread_get_instance();
160 bool launch_mainloop =
true;
161#if defined(USE_OPENTHREAD_BORDER_ROUTER) && !defined(USE_OPENTHREAD_RCP_UART)
164 if (
const esp_err_t err = esp_coex_wifi_i154_enable(); err != ESP_OK) {
165 ESP_LOGE(TAG,
"Failed to enable Wi-Fi/802.15.4 coexistence: %s",
esp_err_to_name(err));
166 this->mark_task_failed_(LOG_STR(
"Coexistence could not be enabled"));
167 launch_mainloop =
false;
175 esp_openthread_lock_acquire(portMAX_DELAY);
176 this->lock_initialized_ =
true;
177 InstanceLock
lock = InstanceLock::acquire();
178 esp_openthread_lock_release();
180#ifndef USE_OPENTHREAD_BORDER_ROUTER
181 this->apply_linkmode_(instance);
184 if (this->output_power_.has_value()) {
185 if (
const auto err = otPlatRadioSetTransmitPower(instance, *this->output_power_); err != OT_ERROR_NONE) {
186 ESP_LOGE(TAG,
"Failed to set power: %s", otThreadErrorToString(err));
190 ESP_LOGI(TAG,
"Activating dataset...");
191 otOperationalDatasetTlvs dataset = {};
192 bool stored_dataset =
false;
193#ifndef USE_OPENTHREAD_FORCE_DATASET
195 if (otDatasetGetActiveTlvs(instance, &dataset) != OT_ERROR_NONE) {
199 stored_dataset =
true;
200 ESP_LOGI(TAG,
"Found existing dataset, ignoring config (force_dataset: true to override)");
204#ifdef USE_OPENTHREAD_TLVS
205 if (dataset.mLength == 0) {
207 constexpr size_t len = (
sizeof(USE_OPENTHREAD_TLVS) - 1) / 2;
208 static_assert(
len <=
sizeof(dataset.mTlvs),
"OpenThread dataset TLV is too long");
209 parse_hex(USE_OPENTHREAD_TLVS,
sizeof(USE_OPENTHREAD_TLVS) - 1, dataset.mTlvs,
len);
210 dataset.mLength =
len;
214 if (dataset.mLength > 0 && !otDatasetIsValid(&dataset,
true)) {
215 if (stored_dataset) {
216 ESP_LOGE(TAG,
"Stored OpenThread Active Operational Dataset is invalid; set force_dataset: true to replace it");
218 ESP_LOGE(TAG,
"Configured OpenThread Active Operational Dataset TLV is invalid");
220 this->mark_task_failed_(LOG_STR(
"Invalid dataset"));
221 launch_mainloop =
false;
222 }
else if (launch_mainloop) {
226 if (
const esp_err_t err = esp_openthread_auto_start(dataset.mLength > 0 ? &dataset :
nullptr); err != ESP_OK) {
228 this->mark_task_failed_(LOG_STR(
"Start failed"));
229 launch_mainloop =
false;
233 if (launch_mainloop) {
235 otError ot_err = otSetStateChangedCallback(instance, OpenThreadComponent::on_state_changed,
this);
236 if (ot_err != OT_ERROR_NONE) {
237 ESP_LOGE(TAG,
"Failed to register state change callback: %d", ot_err);
240 this->mark_task_failed_(LOG_STR(
"State callback could not be registered"));
241 launch_mainloop =
false;
246 if (launch_mainloop) {
247#ifdef USE_OPENTHREAD_BORDER_ROUTER
248 this->mainloop_running_ =
true;
250 const esp_err_t err = esp_openthread_launch_mainloop();
251#ifdef USE_OPENTHREAD_BORDER_ROUTER
252 this->mainloop_running_ =
false;
255 ESP_LOGE(TAG,
"OpenThread main loop failed: %s",
esp_err_to_name(err));
256 this->mark_task_failed_(LOG_STR(
"Main loop failed"));
262 esp_openthread_lock_acquire(portMAX_DELAY);
264 otIp6SetAddressCallback(instance,
nullptr,
nullptr);
265#ifdef USE_OPENTHREAD_BORDER_ROUTER
266 if (this->border_router_started_) {
267 (void) esp_openthread_border_router_deinit();
268 this->border_router_started_ =
false;
271 (void) otThreadSetEnabled(instance,
false);
272 (void) otIp6SetEnabled(instance,
false);
275 for (uint8_t i = 0; i < 16 && otTaskletsArePending(instance); i++) {
276 otTaskletsProcess(instance);
278 this->lock_initialized_ =
false;
279 this->connected_ =
false;
280 this->task_exited_ =
true;
281 esp_openthread_lock_release();
282 esp_openthread_netif_glue_deinit();
283 esp_netif_destroy(netif);
286 esp_openthread_lock_acquire(portMAX_DELAY);
287 esp_openthread_deinit();
288 esp_vfs_eventfd_unregister();
289 this->teardown_stage_ = TeardownStage::TEARDOWN_STAGE_COMPLETED;
294void OpenThreadComponent::mark_task_failed_(
const LogString *reason) {
295 this->defer([
this, reason]() {
296 ESP_LOGE(TAG,
"OpenThread task failed: %s", LOG_STR_ARG(reason));
301#ifdef USE_OPENTHREAD_RCP_UART
302void OpenThreadComponent::rcp_failure_handler() {
304 ESP_LOGE(TAG,
"RCP failure reported after OpenThread component was torn down");
310void OpenThreadComponent::reset_rcp_(
bool recovery_attempt) {
311 if (recovery_attempt) {
312 static constexpr uint8_t MAX_RESET_ATTEMPTS = 5;
313 uint8_t attempt = this->rcp_reset_attempts_;
314 if (attempt > MAX_RESET_ATTEMPTS) {
319 if (attempt != 0 && now - this->last_rcp_reset_ms_ > RCP_RESET_FORGET_MS) {
322 this->rcp_reset_attempts_ = ++attempt;
323 if (attempt > MAX_RESET_ATTEMPTS) {
324 ESP_LOGE(TAG,
"RCP failed to recover after %u reset attempts", MAX_RESET_ATTEMPTS);
325 this->mark_task_failed_(LOG_STR(
"RCP does not recover"));
328 ESP_LOGW(TAG,
"RCP failure detected, reset attempt %u of %u", attempt, MAX_RESET_ATTEMPTS);
329 this->last_rcp_reset_ms_ = now;
331 this->rcp_reset_pin_->digital_write(
true);
332 vTaskDelay(pdMS_TO_TICKS(10));
333 this->rcp_reset_pin_->digital_write(
false);
334 vTaskDelay(pdMS_TO_TICKS(30));
338int OpenThreadComponent::openthread_stop_() {
return esp_openthread_mainloop_exit(); }
345 auto lock = InstanceLock::try_acquire(10);
347 ESP_LOGV(TAG,
"Instance lock busy, no addresses this time");
350 if (
esp_netif_t *netif = esp_openthread_get_netif(); netif !=
nullptr) {
357#ifdef USE_OPENTHREAD_BORDER_ROUTER
358static constexpr uint32_t BORDER_ROUTER_RETRY_MS = 10000;
360void OpenThreadComponent::loop() {
365 if (!this->mainloop_running_) {
369 if (backbone_up == this->backbone_up_) {
370 this->disable_loop();
374 auto lock = InstanceLock::try_acquire(0);
378 auto retry_later = [
this](
const LogString *reason) {
379 this->status_set_error(reason);
380 this->disable_loop();
381 this->set_timeout(BORDER_ROUTER_RETRY_MS, [
this]() { this->enable_loop(); });
384 if (this->border_router_started_) {
385 ESP_LOGI(TAG,
"Wi-Fi backbone lost, border routing pauses");
386 esp_netif_t *backbone = esp_openthread_get_backbone_netif();
388 if (otPlatInfraIfStateChanged(
lock.get_instance(), esp_netif_get_netif_impl_index(backbone),
false) !=
390 ESP_LOGW(TAG,
"Stack did not take the backbone loss, routes stay until Wi-Fi returns");
393 this->backbone_up_ =
false;
394 this->disable_loop();
397 if (this->border_router_started_) {
398 ESP_LOGI(TAG,
"Wi-Fi backbone back, border routing restarts");
400 if (
const esp_err_t err = esp_openthread_border_router_deinit(); err != ESP_OK) {
401 ESP_LOGE(TAG,
"Failed to deinitialize OpenThread Border Router: %s",
esp_err_to_name(err));
402 retry_later(LOG_STR(
"Border router deinit failed"));
405 this->border_router_started_ =
false;
407 if (
const esp_err_t err = esp_openthread_border_router_init(); err != ESP_OK) {
408 ESP_LOGE(TAG,
"Failed to initialize OpenThread Border Router: %s",
esp_err_to_name(err));
409 retry_later(LOG_STR(
"Border router init failed"));
412 this->status_clear_error();
413 this->border_router_started_ =
true;
414 this->backbone_up_ =
true;
415 ESP_LOGI(TAG,
"Border routing started");
416 this->disable_loop();
421otInstance *OpenThreadComponent::get_openthread_instance_() {
return esp_openthread_get_instance(); }
423InstanceLock InstanceLock::try_acquire(
int delay) {
425 return InstanceLock(
false);
427 if (!esp_openthread_lock_acquire(
delay)) {
428 return InstanceLock(
false);
432 esp_openthread_lock_release();
433 return InstanceLock(
false);
435 return InstanceLock(
true);
439static void abort_if_task_exited() {
441 ESP_LOGE(TAG,
"OpenThread task has exited, cannot acquire instance lock");
446InstanceLock InstanceLock::acquire() {
450 ESP_LOGE(TAG,
"OpenThread component torn down, cannot acquire instance lock");
455 ESP_LOGE(TAG,
"Setup failed, cannot acquire instance lock");
460 constexpr uint32_t lock_init_timeout_ms = 10000;
463 abort_if_task_exited();
464 if (
millis() - start > lock_init_timeout_ms) {
465 ESP_LOGE(TAG,
"OpenThread lock not initialized after %" PRIu32
"ms, aborting", lock_init_timeout_ms);
469 esp_task_wdt_reset();
471 while (!esp_openthread_lock_acquire(100)) {
472 abort_if_task_exited();
473 esp_task_wdt_reset();
477 esp_openthread_lock_release();
478 abort_if_task_exited();
480 return InstanceLock(
true);
483otInstance *InstanceLock::get_instance() {
return esp_openthread_get_instance(); }
485InstanceLock::~InstanceLock() {
487 esp_openthread_lock_release();
void add_connect_state_listener(WiFiConnectStateListener *listener)
Add a listener for WiFi connection state changes.
esp_netif_t * get_esp_netif_sta()
esp_netif handle of the station interface, used by network for default-route arbitration.
bool is_connected() const
const char * esp_err_to_name(esp_err_t err)
std::array< IPAddress, 5 > IPAddresses
void fill_ip6_addresses(esp_netif_t *netif, IPAddresses &addresses)
Copies the IPv6 addresses of an esp_netif into addresses[1..], warning once when some do not fit.
OpenThreadComponent * global_openthread_component
WiFiComponent * global_wifi_component
size_t parse_hex(const char *str, size_t length, uint8_t *data, size_t count)
Parse bytes from a hex-encoded string into a byte array.
ESPPreferences * global_preferences
void HOT delay(uint32_t ms)
uint32_t IRAM_ATTR HOT millis()
struct esp_netif_obj esp_netif_t
ESPHOME_LOG_TAG(TAG, "openthread")