ESPHome 2025.10.0-dev
Loading...
Searching...
No Matches
openthread.cpp
Go to the documentation of this file.
2#ifdef USE_OPENTHREAD
3#include "openthread.h"
4#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0)
5#include "esp_openthread.h"
6#endif
7
8#include <freertos/portmacro.h>
9
10#include <openthread/cli.h>
11#include <openthread/instance.h>
12#include <openthread/logging.h>
13#include <openthread/netdata.h>
14#include <openthread/tasklet.h>
15
16#include <cstring>
17
20#include "esphome/core/log.h"
21
22static const char *const TAG = "openthread";
23
24namespace esphome {
25namespace openthread {
26
27OpenThreadComponent *global_openthread_component = // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
28 nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
29
31
33 auto lock = InstanceLock::try_acquire(100);
34 if (!lock) {
35 ESP_LOGW(TAG, "Failed to acquire OpenThread lock in is_connected");
36 return false;
37 }
38
39 otInstance *instance = lock->get_instance();
40 if (instance == nullptr) {
41 return false;
42 }
43
44 otDeviceRole role = otThreadGetDeviceRole(instance);
45
46 // TODO: If we're a leader, check that there is at least 1 known peer
47 return role >= OT_DEVICE_ROLE_CHILD;
48}
49
50// Gets the off-mesh routable address
51std::optional<otIp6Address> OpenThreadComponent::get_omr_address() {
53 return this->get_omr_address_(lock);
54}
55
56std::optional<otIp6Address> OpenThreadComponent::get_omr_address_(InstanceLock &lock) {
57 otNetworkDataIterator iterator = OT_NETWORK_DATA_ITERATOR_INIT;
58 otInstance *instance = nullptr;
59
60 instance = lock.get_instance();
61
62 otBorderRouterConfig config;
63 if (otNetDataGetNextOnMeshPrefix(instance, &iterator, &config) != OT_ERROR_NONE) {
64 return std::nullopt;
65 }
66
67 const otIp6Prefix *omr_prefix = &config.mPrefix;
68 const otNetifAddress *unicast_addresses = otIp6GetUnicastAddresses(instance);
69 for (const otNetifAddress *addr = unicast_addresses; addr; addr = addr->mNext) {
70 const otIp6Address *local_ip = &addr->mAddress;
71 if (otIp6PrefixMatch(&omr_prefix->mPrefix, local_ip)) {
72 return *local_ip;
73 }
74 }
75 return {};
76}
77
79 ESP_LOGD(TAG, "Defer factory_reset_external_callback_");
80 this->defer([this]() { this->factory_reset_external_callback_(); });
81}
82
83void OpenThreadSrpComponent::srp_callback(otError err, const otSrpClientHostInfo *host_info,
84 const otSrpClientService *services,
85 const otSrpClientService *removed_services, void *context) {
86 if (err != 0) {
87 ESP_LOGW(TAG, "SRP client reported an error: %s", otThreadErrorToString(err));
88 for (const otSrpClientHostInfo *host = host_info; host; host = nullptr) {
89 ESP_LOGW(TAG, " Host: %s", host->mName);
90 }
91 for (const otSrpClientService *service = services; service; service = service->mNext) {
92 ESP_LOGW(TAG, " Service: %s", service->mName);
93 }
94 }
95}
96
97void OpenThreadSrpComponent::srp_start_callback(const otSockAddr *server_socket_address, void *context) {
98 ESP_LOGI(TAG, "SRP client has started");
99}
100
101void OpenThreadSrpComponent::srp_factory_reset_callback(otError err, const otSrpClientHostInfo *host_info,
102 const otSrpClientService *services,
103 const otSrpClientService *removed_services, void *context) {
104 OpenThreadComponent *obj = (OpenThreadComponent *) context;
105 if (err == OT_ERROR_NONE && removed_services != NULL && host_info != NULL &&
106 host_info->mState == OT_SRP_CLIENT_ITEM_STATE_REMOVED) {
107 ESP_LOGD(TAG, "Successful Removal SRP Host and Services");
108 } else if (err != OT_ERROR_NONE) {
109 // Handle other SRP client events or errors
110 ESP_LOGW(TAG, "SRP client event/error: %s", otThreadErrorToString(err));
111 }
113}
114
116 otError error;
118 otInstance *instance = lock.get_instance();
119
120 otSrpClientSetCallback(instance, OpenThreadSrpComponent::srp_callback, nullptr);
121
122 // set the host name
123 uint16_t size;
124 char *existing_host_name = otSrpClientBuffersGetHostNameString(instance, &size);
125 const std::string &host_name = App.get_name();
126 uint16_t host_name_len = host_name.size();
127 if (host_name_len > size) {
128 ESP_LOGW(TAG, "Hostname is too long, choose a shorter project name");
129 return;
130 }
131 memset(existing_host_name, 0, size);
132 memcpy(existing_host_name, host_name.c_str(), host_name_len);
133
134 error = otSrpClientSetHostName(instance, existing_host_name);
135 if (error != 0) {
136 ESP_LOGW(TAG, "Could not set host name");
137 return;
138 }
139
140 error = otSrpClientEnableAutoHostAddress(instance);
141 if (error != 0) {
142 ESP_LOGW(TAG, "Could not enable auto host address");
143 return;
144 }
145
146 // Copy the mdns services to our local instance so that the c_str pointers remain valid for the lifetime of this
147 // component
148 this->mdns_services_ = this->mdns_->get_services();
149 ESP_LOGD(TAG, "Setting up SRP services. count = %d\n", this->mdns_services_.size());
150 for (const auto &service : this->mdns_services_) {
151 otSrpClientBuffersServiceEntry *entry = otSrpClientBuffersAllocateService(instance);
152 if (!entry) {
153 ESP_LOGW(TAG, "Failed to allocate service entry");
154 continue;
155 }
156
157 // Set service name
158 char *string = otSrpClientBuffersGetServiceEntryServiceNameString(entry, &size);
159 std::string full_service = service.service_type + "." + service.proto;
160 if (full_service.size() > size) {
161 ESP_LOGW(TAG, "Service name too long: %s", full_service.c_str());
162 continue;
163 }
164 memcpy(string, full_service.c_str(), full_service.size() + 1);
165
166 // Set instance name (using host_name)
167 string = otSrpClientBuffersGetServiceEntryInstanceNameString(entry, &size);
168 if (host_name_len > size) {
169 ESP_LOGW(TAG, "Instance name too long: %s", host_name.c_str());
170 continue;
171 }
172 memset(string, 0, size);
173 memcpy(string, host_name.c_str(), host_name_len);
174
175 // Set port
176 entry->mService.mPort = const_cast<TemplatableValue<uint16_t> &>(service.port).value();
177
178 otDnsTxtEntry *txt_entries =
179 reinterpret_cast<otDnsTxtEntry *>(this->pool_alloc_(sizeof(otDnsTxtEntry) * service.txt_records.size()));
180 // Set TXT records
181 entry->mService.mNumTxtEntries = service.txt_records.size();
182 for (size_t i = 0; i < service.txt_records.size(); i++) {
183 const auto &txt = service.txt_records[i];
184 auto value = const_cast<TemplatableValue<std::string> &>(txt.value).value();
185 txt_entries[i].mKey = strdup(txt.key.c_str());
186 txt_entries[i].mValue = reinterpret_cast<const uint8_t *>(strdup(value.c_str()));
187 txt_entries[i].mValueLength = value.size();
188 }
189 entry->mService.mTxtEntries = txt_entries;
190 entry->mService.mNumTxtEntries = service.txt_records.size();
191
192 // Add service
193 error = otSrpClientAddService(instance, &entry->mService);
194 if (error != OT_ERROR_NONE) {
195 ESP_LOGW(TAG, "Failed to add service: %s", otThreadErrorToString(error));
196 }
197 ESP_LOGD(TAG, "Added service: %s", full_service.c_str());
198 }
199
200 otSrpClientEnableAutoStartMode(instance, OpenThreadSrpComponent::srp_start_callback, nullptr);
201 ESP_LOGD(TAG, "Finished SRP setup");
202}
203
205 uint8_t *ptr = new uint8_t[size];
206 this->memory_pool_.emplace_back(std::unique_ptr<uint8_t[]>(ptr));
207 return ptr;
208}
209
211
213 if (!this->teardown_started_) {
214 this->teardown_started_ = true;
215 ESP_LOGD(TAG, "Clear Srp");
216 auto lock = InstanceLock::try_acquire(100);
217 if (!lock) {
218 ESP_LOGW(TAG, "Failed to acquire OpenThread lock during teardown, leaking memory");
219 return true;
220 }
221 otInstance *instance = lock->get_instance();
222 otSrpClientClearHostAndServices(instance);
223 otSrpClientBuffersFreeAllServices(instance);
225#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0)
226 ESP_LOGD(TAG, "Exit main loop ");
227 int error = esp_openthread_mainloop_exit();
228 if (error != ESP_OK) {
229 ESP_LOGW(TAG, "Failed attempt to stop main loop %d", error);
230 this->teardown_complete_ = true;
231 }
232#else
233 this->teardown_complete_ = true;
234#endif
235 }
236 return this->teardown_complete_;
237}
238
239void OpenThreadComponent::on_factory_reset(std::function<void()> callback) {
241 ESP_LOGD(TAG, "Start Removal SRP Host and Services");
242 otError error;
244 otInstance *instance = lock.get_instance();
245 otSrpClientSetCallback(instance, OpenThreadSrpComponent::srp_factory_reset_callback, this);
246 error = otSrpClientRemoveHostAndServices(instance, true, true);
247 if (error != OT_ERROR_NONE) {
248 ESP_LOGW(TAG, "Failed to Remove SRP Host and Services");
249 return;
250 }
251 ESP_LOGD(TAG, "Waiting on Confirmation Removal SRP Host and Services");
252}
253
254} // namespace openthread
255} // namespace esphome
256
257#endif
const std::string & get_name() const
Get the name of this Application set by pre_setup().
void defer(const std::string &name, std::function< void()> &&f)
Defer a callback to the next loop() call.
std::vector< MDNSService > get_services()
static std::optional< InstanceLock > try_acquire(int delay)
std::optional< otIp6Address > get_omr_address_(InstanceLock &lock)
std::function< void()> factory_reset_external_callback_
Definition openthread.h:40
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:61
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)
std::vector< esphome::mdns::MDNSService > mdns_services_
Definition openthread.h:60
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:59
OpenThreadComponent * global_openthread_component
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
Application App
Global storage of Application pointer - only one Application can exist.