ESPHome 2026.2.1
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"
12#include "esphome/core/log.h"
13
14#include "esp_err.h"
15#include "esp_event.h"
16#include "esp_netif.h"
17#include "esp_netif_types.h"
18#include "esp_openthread_cli.h"
19#include "esp_openthread_netif_glue.h"
20#include "esp_vfs_eventfd.h"
21#include "freertos/FreeRTOS.h"
22#include "freertos/task.h"
23#include "nvs_flash.h"
24
25static const char *const TAG = "openthread";
26
27namespace esphome::openthread {
28
30 // Used eventfds:
31 // * netif
32 // * ot task queue
33 // * radio driver
34 esp_vfs_eventfd_config_t eventfd_config = {
35 .max_fds = 3,
36 };
37 ESP_ERROR_CHECK(nvs_flash_init());
38 ESP_ERROR_CHECK(esp_event_loop_create_default());
39 ESP_ERROR_CHECK(esp_netif_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
85#if CONFIG_OPENTHREAD_STATE_INDICATOR_ENABLE
86 ESP_ERROR_CHECK(esp_openthread_state_indicator_init(esp_openthread_get_instance()));
87#endif
88
89#if CONFIG_OPENTHREAD_LOG_LEVEL_DYNAMIC
90 // The OpenThread log level directly matches ESP log level
91 (void) otLoggingSetLevel(CONFIG_LOG_DEFAULT_LEVEL);
92#endif
93 // Initialize the OpenThread cli
94#if CONFIG_OPENTHREAD_CLI
95 esp_openthread_cli_init();
96#endif
97
98 esp_netif_t *openthread_netif;
99 // Initialize the esp_netif bindings
100 openthread_netif = init_openthread_netif(&config);
101 esp_netif_set_default_netif(openthread_netif);
102
103#if CONFIG_OPENTHREAD_CLI_ESP_EXTENSION
104 esp_cli_custom_command_init();
105#endif // CONFIG_OPENTHREAD_CLI_ESP_EXTENSION
106
107 otLinkModeConfig link_mode_config{};
108#if CONFIG_OPENTHREAD_FTD
109 link_mode_config.mRxOnWhenIdle = true;
110 link_mode_config.mDeviceType = true;
111 link_mode_config.mNetworkData = true;
112#elif CONFIG_OPENTHREAD_MTD
113 if (this->poll_period > 0) {
114 if (otLinkSetPollPeriod(esp_openthread_get_instance(), this->poll_period) != OT_ERROR_NONE) {
115 ESP_LOGE(TAG, "Failed to set OpenThread pollperiod.");
116 }
117 uint32_t link_polling_period = otLinkGetPollPeriod(esp_openthread_get_instance());
118 ESP_LOGD(TAG, "Link Polling Period: %" PRIu32, link_polling_period);
119 }
120 link_mode_config.mRxOnWhenIdle = this->poll_period == 0;
121 link_mode_config.mDeviceType = false;
122 link_mode_config.mNetworkData = false;
123#endif
124
125 if (otThreadSetLinkMode(esp_openthread_get_instance(), link_mode_config) != OT_ERROR_NONE) {
126 ESP_LOGE(TAG, "Failed to set OpenThread linkmode.");
127 }
128 link_mode_config = otThreadGetLinkMode(esp_openthread_get_instance());
129 ESP_LOGD(TAG,
130 "Link Mode Device Type: %s\n"
131 "Link Mode Network Data: %s\n"
132 "Link Mode RX On When Idle: %s",
133 link_mode_config.mDeviceType ? "true" : "false", link_mode_config.mNetworkData ? "true" : "false",
134 link_mode_config.mRxOnWhenIdle ? "true" : "false");
135
136 // Run the main loop
137#if CONFIG_OPENTHREAD_CLI
138 esp_openthread_cli_create_task();
139#endif
140 ESP_LOGI(TAG, "Activating dataset...");
141 otOperationalDatasetTlvs dataset = {};
142
143#ifndef USE_OPENTHREAD_FORCE_DATASET
144 // Check if openthread has a valid dataset from a previous execution
145 otError error = otDatasetGetActiveTlvs(esp_openthread_get_instance(), &dataset);
146 if (error != OT_ERROR_NONE) {
147 // Make sure the length is 0 so we fallback to the configuration
148 dataset.mLength = 0;
149 } else {
150 ESP_LOGI(TAG, "Found OpenThread-managed dataset, ignoring esphome configuration\n"
151 "(set force_dataset: true to override)");
152 }
153#endif
154
155#ifdef USE_OPENTHREAD_TLVS
156 if (dataset.mLength == 0) {
157 // If we didn't have an active dataset, and we have tlvs, parse it and pass it to esp_openthread_auto_start
158 size_t len = (sizeof(USE_OPENTHREAD_TLVS) - 1) / 2;
159 if (len > sizeof(dataset.mTlvs)) {
160 ESP_LOGW(TAG, "TLV buffer too small, truncating");
161 len = sizeof(dataset.mTlvs);
162 }
163 parse_hex(USE_OPENTHREAD_TLVS, sizeof(USE_OPENTHREAD_TLVS) - 1, dataset.mTlvs, len);
164 dataset.mLength = len;
165 }
166#endif
167
168 // Pass the existing dataset, or NULL which will use the preprocessor definitions
169 ESP_ERROR_CHECK(esp_openthread_auto_start(dataset.mLength > 0 ? &dataset : nullptr));
170
171 esp_openthread_launch_mainloop();
172
173 // Clean up
174 esp_openthread_deinit();
175 esp_openthread_netif_glue_deinit();
176 esp_netif_destroy(openthread_netif);
177
178 esp_vfs_eventfd_unregister();
179 this->teardown_complete_ = true;
180 vTaskDelete(NULL);
181}
182
184 network::IPAddresses addresses;
185 struct esp_ip6_addr if_ip6s[CONFIG_LWIP_IPV6_NUM_ADDRESSES];
186 uint8_t count = 0;
187 esp_netif_t *netif = esp_netif_get_default_netif();
188 count = esp_netif_get_all_ip6(netif, if_ip6s);
189 assert(count <= CONFIG_LWIP_IPV6_NUM_ADDRESSES);
190 for (int i = 0; i < count; i++) {
191 addresses[i + 1] = network::IPAddress(&if_ip6s[i]);
192 }
193 return addresses;
194}
195
196std::optional<InstanceLock> InstanceLock::try_acquire(int delay) {
197 if (esp_openthread_lock_acquire(delay)) {
198 return InstanceLock();
199 }
200 return {};
201}
202
204 while (!esp_openthread_lock_acquire(100)) {
205 esp_task_wdt_reset();
206 }
207 return InstanceLock();
208}
209
210otInstance *InstanceLock::get_instance() { return esp_openthread_get_instance(); }
211
212InstanceLock::~InstanceLock() { esp_openthread_lock_release(); }
213
214} // namespace esphome::openthread
215#endif
static std::optional< InstanceLock > try_acquire(int delay)
std::array< IPAddress, 5 > IPAddresses
Definition ip_address.h:184
std::string size_t len
Definition helpers.h:692
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:294
void IRAM_ATTR HOT delay(uint32_t ms)
Definition core.cpp:26