ESPHome 2026.8.0-dev
Loading...
Searching...
No Matches
openthread_esp.cpp
Go to the documentation of this file.
2#if defined(USE_OPENTHREAD) && defined(USE_ESP32)
3#include <openthread/logging.h>
4#include "openthread.h"
5
6#include "esp_log.h"
7#include "esp_openthread.h"
8#include "esp_openthread_lock.h"
9
10#include "esp_task_wdt.h"
11#include "esphome/core/hal.h"
13#include "esphome/core/log.h"
14
15#include "esp_err.h"
16#include "esp_event.h"
17#include "esp_netif.h"
18#include "esp_netif_types.h"
19#include "esp_openthread_cli.h"
20#include "esp_openthread_netif_glue.h"
21#include "esp_vfs_eventfd.h"
22#include "freertos/FreeRTOS.h"
23#include "freertos/task.h"
24#include "nvs_flash.h"
25
26static const char *const TAG = "openthread";
27
28namespace esphome::openthread {
29
31 // Used eventfds:
32 // * netif
33 // * ot task queue
34 // * radio driver
35 esp_vfs_eventfd_config_t eventfd_config = {
36 .max_fds = 3,
37 };
38 // Network interface setup handled by network component
39 ESP_ERROR_CHECK(nvs_flash_init());
40 ESP_ERROR_CHECK(esp_vfs_eventfd_register(&eventfd_config));
41
42 xTaskCreate(
43 [](void *arg) {
44 static_cast<OpenThreadComponent *>(arg)->ot_main();
45 vTaskDelete(nullptr);
46 },
47 "ot_main", 10240, this, 5, nullptr);
48}
49
50static esp_netif_t *init_openthread_netif(const esp_openthread_platform_config_t *config) {
51 esp_netif_config_t cfg = ESP_NETIF_DEFAULT_OPENTHREAD();
52 esp_netif_t *netif = esp_netif_new(&cfg);
53 assert(netif != nullptr);
54 ESP_ERROR_CHECK(esp_netif_attach(netif, esp_openthread_netif_glue_init(config)));
55
56 return netif;
57}
58
60 esp_openthread_platform_config_t config = {
61 .radio_config =
62 {
63 .radio_mode = RADIO_MODE_NATIVE,
64 .radio_uart_config = {},
65 },
66 .host_config =
67 {
68 // There is a conflict between esphome's logger which also
69 // claims the usb serial jtag device.
70 // .host_connection_mode = HOST_CONNECTION_MODE_CLI_USB,
71 // .host_usb_config = USB_SERIAL_JTAG_DRIVER_CONFIG_DEFAULT(),
72 },
73 .port_config =
74 {
75 .storage_partition_name = "nvs",
76 .netif_queue_size = 10,
77 .task_queue_size = 10,
78 },
79 };
80
81 // Initialize the OpenThread stack
82 // otLoggingSetLevel(OT_LOG_LEVEL_DEBG);
83 ESP_ERROR_CHECK(esp_openthread_init(&config));
84 // Mark lock as initialized so InstanceLock callers know it's safe to acquire.
85 // Must be set after esp_openthread_init() which creates the internal semaphore.
86 this->lock_initialized_ = true;
87 // Fetch OT instance once to avoid repeated call into OT stack
88 otInstance *instance = esp_openthread_get_instance();
89
90#if CONFIG_OPENTHREAD_STATE_INDICATOR_ENABLE
91 ESP_ERROR_CHECK(esp_openthread_state_indicator_init(instance));
92#endif
93
94#if CONFIG_OPENTHREAD_LOG_LEVEL_DYNAMIC
95 // The OpenThread log level directly matches ESP log level
96 (void) otLoggingSetLevel(CONFIG_LOG_DEFAULT_LEVEL);
97#endif
98 // Initialize the OpenThread cli
99#if CONFIG_OPENTHREAD_CLI
100 esp_openthread_cli_init();
101#endif
102
103 esp_netif_t *openthread_netif;
104 // Initialize the esp_netif bindings
105 openthread_netif = init_openthread_netif(&config);
106 esp_netif_set_default_netif(openthread_netif);
107
108#if CONFIG_OPENTHREAD_CLI_ESP_EXTENSION
109 esp_cli_custom_command_init();
110#endif // CONFIG_OPENTHREAD_CLI_ESP_EXTENSION
111
112 ESP_LOGD(TAG, "Thread Version: %" PRIu16, otThreadGetVersion());
113
114 this->apply_linkmode_(instance);
115
116 if (this->output_power_.has_value()) {
117 if (const auto err = otPlatRadioSetTransmitPower(instance, *this->output_power_); err != OT_ERROR_NONE) {
118 ESP_LOGE(TAG, "Failed to set power: %s", otThreadErrorToString(err));
119 }
120 }
121
122 // Run the main loop
123#if CONFIG_OPENTHREAD_CLI
124 esp_openthread_cli_create_task();
125#endif
126 ESP_LOGI(TAG, "Activating dataset...");
127 otOperationalDatasetTlvs dataset = {};
128
129#ifndef USE_OPENTHREAD_FORCE_DATASET
130 // Check if openthread has a valid dataset from a previous execution
131 otError error = otDatasetGetActiveTlvs(instance, &dataset);
132 if (error != OT_ERROR_NONE) {
133 // Make sure the length is 0 so we fallback to the configuration
134 dataset.mLength = 0;
135 } else {
136 ESP_LOGI(TAG, "Found existing dataset, ignoring config (force_dataset: true to override)");
137 }
138#endif
139
140#ifdef USE_OPENTHREAD_TLVS
141 if (dataset.mLength == 0) {
142 // If we didn't have an active dataset, and we have tlvs, parse it and pass it to esp_openthread_auto_start
143 size_t len = (sizeof(USE_OPENTHREAD_TLVS) - 1) / 2;
144 if (len > sizeof(dataset.mTlvs)) {
145 ESP_LOGW(TAG, "TLV buffer too small, truncating");
146 len = sizeof(dataset.mTlvs);
147 }
148 parse_hex(USE_OPENTHREAD_TLVS, sizeof(USE_OPENTHREAD_TLVS) - 1, dataset.mTlvs, len);
149 dataset.mLength = len;
150 }
151#endif
152
153 // Pass the existing dataset, or NULL which will use the preprocessor definitions
154 ESP_ERROR_CHECK(esp_openthread_auto_start(dataset.mLength > 0 ? &dataset : nullptr));
155
156 // Register state change callback to update connected_ reactively instead of polling
157 otError ot_err = otSetStateChangedCallback(instance, OpenThreadComponent::on_state_changed, this);
158 if (ot_err != OT_ERROR_NONE) {
159 ESP_LOGW(TAG, "Failed to register state change callback: %d", ot_err);
160 }
161
162 esp_openthread_launch_mainloop();
163
164 // Clean up - reset lock flag before deinit destroys the semaphore
165 this->lock_initialized_ = false;
166 esp_openthread_deinit();
167 esp_openthread_netif_glue_deinit();
168 esp_netif_destroy(openthread_netif);
169
170 esp_vfs_eventfd_unregister();
171 this->teardown_complete_ = true;
172 vTaskDelete(NULL);
173}
174
175int OpenThreadComponent::openthread_stop_() { return esp_openthread_mainloop_exit(); }
176
178 network::IPAddresses addresses;
179 struct esp_ip6_addr if_ip6s[CONFIG_LWIP_IPV6_NUM_ADDRESSES];
180 uint8_t count = 0;
181 esp_netif_t *netif = esp_netif_get_default_netif();
182 count = esp_netif_get_all_ip6(netif, if_ip6s);
183 assert(count <= CONFIG_LWIP_IPV6_NUM_ADDRESSES);
184 assert(count < addresses.size());
185 for (int i = 0; i < count; i++) {
186 addresses[i + 1] = network::IPAddress(&if_ip6s[i]);
187 }
188 return addresses;
189}
190
191// not thread safe, only use in read-only use cases
192otInstance *OpenThreadComponent::get_openthread_instance_() { return esp_openthread_get_instance(); }
193
196 return InstanceLock(false);
197 }
198 return InstanceLock(esp_openthread_lock_acquire(delay));
199}
200
202 // Wait for the lock to be created by ot_main() before attempting to acquire it.
203 // esp_openthread_lock_acquire() will assert-crash if called before esp_openthread_init().
204 constexpr uint32_t lock_init_timeout_ms = 10000;
205 uint32_t start = millis();
207 if (millis() - start > lock_init_timeout_ms) {
208 ESP_LOGE(TAG, "OpenThread lock not initialized after %" PRIu32 "ms, aborting", lock_init_timeout_ms);
209 abort();
210 }
211 delay(10);
212 esp_task_wdt_reset();
213 }
214 while (!esp_openthread_lock_acquire(100)) {
215 esp_task_wdt_reset();
216 }
217 return InstanceLock(true);
218}
219
220otInstance *InstanceLock::get_instance() { return esp_openthread_get_instance(); }
221
223 if (this->owns_) {
224 esp_openthread_lock_release();
225 }
226}
227
228} // namespace esphome::openthread
229#endif
static InstanceLock try_acquire(int delay)
InstanceLock(const InstanceLock &)=delete
void apply_linkmode_(otInstance *instance)
Apply Link Mode settings (incl poll period).
std::optional< int8_t > output_power_
Definition openthread.h:72
static void on_state_changed(otChangedFlags flags, void *context)
bool is_lock_initialized() const
Returns true once esp_openthread_init() has completed and the OT lock is usable.
Definition openthread.h:35
std::array< IPAddress, 5 > IPAddresses
Definition ip_address.h:299
OpenThreadComponent * global_openthread_component
const void size_t len
Definition hal.h:64
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.
Definition helpers.cpp:274
void HOT delay(uint32_t ms)
Definition hal.cpp:85
uint32_t IRAM_ATTR HOT millis()
Definition hal.cpp:28
static void uint32_t