ESPHome 2026.6.0-dev
Loading...
Searching...
No Matches
openthread.cpp
Go to the documentation of this file.
2#ifdef USE_OPENTHREAD
3#include "openthread.h"
4
5#include <openthread/cli.h>
6#include <openthread/instance.h>
7#include <openthread/logging.h>
8#include <openthread/netdata.h>
9#include <openthread/tasklet.h>
10
11#include <cstring>
12#include <utility>
13
16#include "esphome/core/log.h"
17
18static const char *const TAG = "openthread";
19
21
22OpenThreadComponent *global_openthread_component = // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
23 nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
24
26
28 ESP_LOGCONFIG(TAG, "Open Thread:");
29#if CONFIG_OPENTHREAD_FTD
30 ESP_LOGCONFIG(TAG, " Device Type: FTD");
31#elif CONFIG_OPENTHREAD_MTD
32 ESP_LOGCONFIG(TAG, " Device Type: MTD");
33 // TBD: Synchronized Sleepy End Device
34 if (this->poll_period_ > 0) {
35 ESP_LOGCONFIG(TAG, " Device is configured as Sleepy End Device (SED)");
36 uint32_t duration = this->poll_period_ / 1000;
37 ESP_LOGCONFIG(TAG, " Poll Period: %" PRIu32 "s", duration);
38 } else {
39 ESP_LOGCONFIG(TAG, " Device is configured as Minimal End Device (MED)");
40 }
41#endif
42 if (this->output_power_.has_value()) {
43 ESP_LOGCONFIG(TAG, " Output power: %" PRId8 "dBm", *this->output_power_);
44 }
45}
46
47void OpenThreadComponent::on_state_changed(otChangedFlags flags, void *context) {
48 if (flags & OT_CHANGED_THREAD_ROLE) {
49 auto *self = static_cast<OpenThreadComponent *>(context);
50 // This runs on the OpenThread task thread with the OT lock held,
51 // so we can safely call otThreadGetDeviceRole directly.
52 otInstance *instance = self->get_openthread_instance_();
53 otDeviceRole role = otThreadGetDeviceRole(instance);
54 self->connected_ = role >= OT_DEVICE_ROLE_CHILD;
55 }
56}
57
58// Gets the off-mesh routable address
59std::optional<otIp6Address> OpenThreadComponent::get_omr_address() {
61 return this->get_omr_address_(lock);
62}
63
64std::optional<otIp6Address> OpenThreadComponent::get_omr_address_(InstanceLock &lock) {
65 otNetworkDataIterator iterator = OT_NETWORK_DATA_ITERATOR_INIT;
66 otInstance *instance = nullptr;
67
68 instance = lock.get_instance();
69
70 otBorderRouterConfig config;
71 if (otNetDataGetNextOnMeshPrefix(instance, &iterator, &config) != OT_ERROR_NONE) {
72 return std::nullopt;
73 }
74
75 const otIp6Prefix *omr_prefix = &config.mPrefix;
76 const otNetifAddress *unicast_addresses = otIp6GetUnicastAddresses(instance);
77 for (const otNetifAddress *addr = unicast_addresses; addr; addr = addr->mNext) {
78 const otIp6Address *local_ip = &addr->mAddress;
79 if (otIp6PrefixMatch(&omr_prefix->mPrefix, local_ip)) {
80 return *local_ip;
81 }
82 }
83 return {};
84}
85
87 ESP_LOGD(TAG, "Defer factory_reset_external_callback_");
88 this->defer([this]() { this->factory_reset_external_callback_(); });
89}
90
91void OpenThreadSrpComponent::srp_callback(otError err, const otSrpClientHostInfo *host_info,
92 const otSrpClientService *services,
93 const otSrpClientService *removed_services, void *context) {
94 if (err != 0) {
95 ESP_LOGW(TAG, "SRP client reported an error: %s", otThreadErrorToString(err));
96 for (const otSrpClientHostInfo *host = host_info; host; host = nullptr) {
97 ESP_LOGW(TAG, " Host: %s", host->mName);
98 }
99 for (const otSrpClientService *service = services; service; service = service->mNext) {
100 ESP_LOGW(TAG, " Service: %s", service->mName);
101 }
102 }
103}
104
105void OpenThreadSrpComponent::srp_start_callback(const otSockAddr *server_socket_address, void *context) {
106 ESP_LOGI(TAG, "SRP client has started");
107}
108
109void OpenThreadSrpComponent::srp_factory_reset_callback(otError err, const otSrpClientHostInfo *host_info,
110 const otSrpClientService *services,
111 const otSrpClientService *removed_services, void *context) {
112 OpenThreadComponent *obj = (OpenThreadComponent *) context;
113 if (err == OT_ERROR_NONE && removed_services != NULL && host_info != NULL &&
114 host_info->mState == OT_SRP_CLIENT_ITEM_STATE_REMOVED) {
115 ESP_LOGD(TAG, "Successful Removal SRP Host and Services");
116 } else if (err != OT_ERROR_NONE) {
117 // Handle other SRP client events or errors
118 ESP_LOGW(TAG, "SRP client event/error: %s", otThreadErrorToString(err));
119 }
121}
122
124 otError error;
126 otInstance *instance = lock.get_instance();
127
128 otSrpClientSetCallback(instance, OpenThreadSrpComponent::srp_callback, nullptr);
129
130 // set the host name
131 uint16_t size;
132 char *existing_host_name = otSrpClientBuffersGetHostNameString(instance, &size);
133 const auto &host_name = App.get_name();
134 uint16_t host_name_len = host_name.size();
135 if (host_name_len > size) {
136 ESP_LOGW(TAG, "Hostname is too long, choose a shorter project name");
137 return;
138 }
139 memset(existing_host_name, 0, size);
140 memcpy(existing_host_name, host_name.c_str(), host_name_len);
141
142 error = otSrpClientSetHostName(instance, existing_host_name);
143 if (error != 0) {
144 ESP_LOGW(TAG, "Could not set host name");
145 return;
146 }
147
148 error = otSrpClientEnableAutoHostAddress(instance);
149 if (error != 0) {
150 ESP_LOGW(TAG, "Could not enable auto host address");
151 return;
152 }
153
154 // Get mdns services and copy their data (strings are copied with strdup below)
155 const auto &mdns_services = this->mdns_->get_services();
156 ESP_LOGD(TAG, "Setting up SRP services. count = %d\n", mdns_services.size());
157 for (const auto &service : mdns_services) {
158 otSrpClientBuffersServiceEntry *entry = otSrpClientBuffersAllocateService(instance);
159 if (!entry) {
160 ESP_LOGW(TAG, "Failed to allocate service entry");
161 continue;
162 }
163
164 // Set service name
165 char *string = otSrpClientBuffersGetServiceEntryServiceNameString(entry, &size);
166 std::string full_service = std::string(MDNS_STR_ARG(service.service_type)) + "." + MDNS_STR_ARG(service.proto);
167 if (full_service.size() > size) {
168 ESP_LOGW(TAG, "Service name too long: %s", full_service.c_str());
169 continue;
170 }
171 memcpy(string, full_service.c_str(), full_service.size() + 1);
172
173 // Set instance name (using host_name)
174 string = otSrpClientBuffersGetServiceEntryInstanceNameString(entry, &size);
175 if (host_name_len > size) {
176 ESP_LOGW(TAG, "Instance name too long: %s", host_name.c_str());
177 continue;
178 }
179 memset(string, 0, size);
180 memcpy(string, host_name.c_str(), host_name_len);
181
182 // Set port
183 entry->mService.mPort = service.port.value();
184
185 otDnsTxtEntry *txt_entries =
186 reinterpret_cast<otDnsTxtEntry *>(this->pool_alloc_(sizeof(otDnsTxtEntry) * service.txt_records.size()));
187 // Set TXT records
188 entry->mService.mNumTxtEntries = service.txt_records.size();
189 for (size_t i = 0; i < service.txt_records.size(); i++) {
190 const auto &txt = service.txt_records[i];
191 // Value is either a compile-time string literal in flash or a pointer to dynamic_txt_values_
192 // OpenThread SRP client expects the data to persist, so we strdup it
193 const char *value_str = MDNS_STR_ARG(txt.value);
194 txt_entries[i].mKey = MDNS_STR_ARG(txt.key);
195 txt_entries[i].mValue = reinterpret_cast<const uint8_t *>(strdup(value_str));
196 txt_entries[i].mValueLength = strlen(value_str);
197 }
198 entry->mService.mTxtEntries = txt_entries;
199 entry->mService.mNumTxtEntries = service.txt_records.size();
200
201 // Add service
202 error = otSrpClientAddService(instance, &entry->mService);
203 if (error != OT_ERROR_NONE) {
204 ESP_LOGW(TAG, "Failed to add service: %s", otThreadErrorToString(error));
205 }
206 ESP_LOGD(TAG, "Added service: %s", full_service.c_str());
207 }
208
209 otSrpClientEnableAutoStartMode(instance, OpenThreadSrpComponent::srp_start_callback, nullptr);
210 ESP_LOGD(TAG, "Finished SRP setup");
211}
212
214 uint8_t *ptr = new uint8_t[size];
215 this->memory_pool_.emplace_back(std::unique_ptr<uint8_t[]>(ptr));
216 return ptr;
217}
218
220
222 if (!this->teardown_started_) {
223 this->teardown_started_ = true;
224 ESP_LOGD(TAG, "Clear Srp");
226 if (!lock) {
227 ESP_LOGW(TAG, "Failed to acquire OpenThread lock during teardown, leaking memory");
228 return true;
229 }
230 otInstance *instance = lock->get_instance();
231 otSrpClientClearHostAndServices(instance);
232 otSrpClientBuffersFreeAllServices(instance);
234 ESP_LOGD(TAG, "Exit main loop ");
235 int error = this->openthread_stop_();
236 if (error != ESP_OK) {
237 ESP_LOGW(TAG, "Failed attempt to stop main loop %d", error);
238 this->teardown_complete_ = true;
239 }
240 }
241 return this->teardown_complete_;
242}
243
244void OpenThreadComponent::on_factory_reset(std::function<void()> callback) {
245 this->factory_reset_external_callback_ = std::move(callback);
246 ESP_LOGD(TAG, "Start Removal SRP Host and Services");
247 otError error;
249 otInstance *instance = lock.get_instance();
250 otSrpClientSetCallback(instance, OpenThreadSrpComponent::srp_factory_reset_callback, this);
251 error = otSrpClientRemoveHostAndServices(instance, true, true);
252 if (error != OT_ERROR_NONE) {
253 ESP_LOGW(TAG, "Failed to Remove SRP Host and Services");
254 return;
255 }
256 ESP_LOGD(TAG, "Waiting on Confirmation Removal SRP Host and Services");
257}
258
259} // namespace esphome::openthread
260#endif
const StringRef & get_name() const
Get the name of this Application set by pre_setup().
ESPDEPRECATED("Use const char* overload instead. Removed in 2026.7.0", "2026.1.0") void defer(const std voi defer)(const char *name, std::function< void()> &&f)
Defer a callback to the next loop() call.
Definition component.h:543
constexpr size_type size() const
Definition string_ref.h:74
const StaticVector< MDNSService, MDNS_SERVICE_COUNT > & get_services() const
static std::optional< InstanceLock > try_acquire(int delay)
std::optional< otIp6Address > get_omr_address_(InstanceLock &lock)
std::optional< int8_t > output_power_
Definition openthread.h:56
std::function< void()> factory_reset_external_callback_
Definition openthread.h:52
static void on_state_changed(otChangedFlags flags, void *context)
std::optional< otIp6Address > get_omr_address()
void on_factory_reset(std::function< void()> callback)
std::vector< std::unique_ptr< uint8_t[]> > memory_pool_
Definition openthread.h:85
static void srp_start_callback(const otSockAddr *server_socket_address, void *context)
void set_mdns(esphome::mdns::MDNSComponent *mdns)
static void srp_factory_reset_callback(otError err, const otSrpClientHostInfo *host_info, const otSrpClientService *services, const otSrpClientService *removed_services, void *context)
static void srp_callback(otError err, const otSrpClientHostInfo *host_info, const otSrpClientService *services, const otSrpClientService *removed_services, void *context)
esphome::mdns::MDNSComponent * mdns_
Definition openthread.h:84
uint16_t flags
uint8_t duration
Definition msa3xx.h:0
OpenThreadComponent * global_openthread_component
uint16_t size
Definition helpers.cpp:25
Application App
Global storage of Application pointer - only one Application can exist.
static void uint32_t
SemaphoreHandle_t lock