ESPHome 2025.12.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 ESP_LOGCONFIG(TAG, "Open Thread:");
34#if CONFIG_OPENTHREAD_FTD
35 ESP_LOGCONFIG(TAG, " Device Type: FTD");
36#elif CONFIG_OPENTHREAD_MTD
37 ESP_LOGCONFIG(TAG, " Device Type: MTD");
38 // TBD: Synchronized Sleepy End Device
39 if (this->poll_period > 0) {
40 ESP_LOGCONFIG(TAG, " Device is configured as Sleepy End Device (SED)");
41 uint32_t duration = this->poll_period / 1000;
42 ESP_LOGCONFIG(TAG, " Poll Period: %" PRIu32 "s", duration);
43 } else {
44 ESP_LOGCONFIG(TAG, " Device is configured as Minimal End Device (MED)");
45 }
46#endif
47}
48
50 auto lock = InstanceLock::try_acquire(100);
51 if (!lock) {
52 ESP_LOGW(TAG, "Failed to acquire OpenThread lock in is_connected");
53 return false;
54 }
55
56 otInstance *instance = lock->get_instance();
57 if (instance == nullptr) {
58 return false;
59 }
60
61 otDeviceRole role = otThreadGetDeviceRole(instance);
62
63 // TODO: If we're a leader, check that there is at least 1 known peer
64 return role >= OT_DEVICE_ROLE_CHILD;
65}
66
67// Gets the off-mesh routable address
68std::optional<otIp6Address> OpenThreadComponent::get_omr_address() {
70 return this->get_omr_address_(lock);
71}
72
73std::optional<otIp6Address> OpenThreadComponent::get_omr_address_(InstanceLock &lock) {
74 otNetworkDataIterator iterator = OT_NETWORK_DATA_ITERATOR_INIT;
75 otInstance *instance = nullptr;
76
77 instance = lock.get_instance();
78
79 otBorderRouterConfig config;
80 if (otNetDataGetNextOnMeshPrefix(instance, &iterator, &config) != OT_ERROR_NONE) {
81 return std::nullopt;
82 }
83
84 const otIp6Prefix *omr_prefix = &config.mPrefix;
85 const otNetifAddress *unicast_addresses = otIp6GetUnicastAddresses(instance);
86 for (const otNetifAddress *addr = unicast_addresses; addr; addr = addr->mNext) {
87 const otIp6Address *local_ip = &addr->mAddress;
88 if (otIp6PrefixMatch(&omr_prefix->mPrefix, local_ip)) {
89 return *local_ip;
90 }
91 }
92 return {};
93}
94
96 ESP_LOGD(TAG, "Defer factory_reset_external_callback_");
97 this->defer([this]() { this->factory_reset_external_callback_(); });
98}
99
100void OpenThreadSrpComponent::srp_callback(otError err, const otSrpClientHostInfo *host_info,
101 const otSrpClientService *services,
102 const otSrpClientService *removed_services, void *context) {
103 if (err != 0) {
104 ESP_LOGW(TAG, "SRP client reported an error: %s", otThreadErrorToString(err));
105 for (const otSrpClientHostInfo *host = host_info; host; host = nullptr) {
106 ESP_LOGW(TAG, " Host: %s", host->mName);
107 }
108 for (const otSrpClientService *service = services; service; service = service->mNext) {
109 ESP_LOGW(TAG, " Service: %s", service->mName);
110 }
111 }
112}
113
114void OpenThreadSrpComponent::srp_start_callback(const otSockAddr *server_socket_address, void *context) {
115 ESP_LOGI(TAG, "SRP client has started");
116}
117
118void OpenThreadSrpComponent::srp_factory_reset_callback(otError err, const otSrpClientHostInfo *host_info,
119 const otSrpClientService *services,
120 const otSrpClientService *removed_services, void *context) {
121 OpenThreadComponent *obj = (OpenThreadComponent *) context;
122 if (err == OT_ERROR_NONE && removed_services != NULL && host_info != NULL &&
123 host_info->mState == OT_SRP_CLIENT_ITEM_STATE_REMOVED) {
124 ESP_LOGD(TAG, "Successful Removal SRP Host and Services");
125 } else if (err != OT_ERROR_NONE) {
126 // Handle other SRP client events or errors
127 ESP_LOGW(TAG, "SRP client event/error: %s", otThreadErrorToString(err));
128 }
130}
131
133 otError error;
135 otInstance *instance = lock.get_instance();
136
137 otSrpClientSetCallback(instance, OpenThreadSrpComponent::srp_callback, nullptr);
138
139 // set the host name
140 uint16_t size;
141 char *existing_host_name = otSrpClientBuffersGetHostNameString(instance, &size);
142 const std::string &host_name = App.get_name();
143 uint16_t host_name_len = host_name.size();
144 if (host_name_len > size) {
145 ESP_LOGW(TAG, "Hostname is too long, choose a shorter project name");
146 return;
147 }
148 memset(existing_host_name, 0, size);
149 memcpy(existing_host_name, host_name.c_str(), host_name_len);
150
151 error = otSrpClientSetHostName(instance, existing_host_name);
152 if (error != 0) {
153 ESP_LOGW(TAG, "Could not set host name");
154 return;
155 }
156
157 error = otSrpClientEnableAutoHostAddress(instance);
158 if (error != 0) {
159 ESP_LOGW(TAG, "Could not enable auto host address");
160 return;
161 }
162
163 // Get mdns services and copy their data (strings are copied with strdup below)
164 const auto &mdns_services = this->mdns_->get_services();
165 ESP_LOGD(TAG, "Setting up SRP services. count = %d\n", mdns_services.size());
166 for (const auto &service : mdns_services) {
167 otSrpClientBuffersServiceEntry *entry = otSrpClientBuffersAllocateService(instance);
168 if (!entry) {
169 ESP_LOGW(TAG, "Failed to allocate service entry");
170 continue;
171 }
172
173 // Set service name
174 char *string = otSrpClientBuffersGetServiceEntryServiceNameString(entry, &size);
175 std::string full_service = std::string(MDNS_STR_ARG(service.service_type)) + "." + MDNS_STR_ARG(service.proto);
176 if (full_service.size() > size) {
177 ESP_LOGW(TAG, "Service name too long: %s", full_service.c_str());
178 continue;
179 }
180 memcpy(string, full_service.c_str(), full_service.size() + 1);
181
182 // Set instance name (using host_name)
183 string = otSrpClientBuffersGetServiceEntryInstanceNameString(entry, &size);
184 if (host_name_len > size) {
185 ESP_LOGW(TAG, "Instance name too long: %s", host_name.c_str());
186 continue;
187 }
188 memset(string, 0, size);
189 memcpy(string, host_name.c_str(), host_name_len);
190
191 // Set port
192 entry->mService.mPort = const_cast<TemplatableValue<uint16_t> &>(service.port).value();
193
194 otDnsTxtEntry *txt_entries =
195 reinterpret_cast<otDnsTxtEntry *>(this->pool_alloc_(sizeof(otDnsTxtEntry) * service.txt_records.size()));
196 // Set TXT records
197 entry->mService.mNumTxtEntries = service.txt_records.size();
198 for (size_t i = 0; i < service.txt_records.size(); i++) {
199 const auto &txt = service.txt_records[i];
200 // Value is either a compile-time string literal in flash or a pointer to dynamic_txt_values_
201 // OpenThread SRP client expects the data to persist, so we strdup it
202 const char *value_str = MDNS_STR_ARG(txt.value);
203 txt_entries[i].mKey = MDNS_STR_ARG(txt.key);
204 txt_entries[i].mValue = reinterpret_cast<const uint8_t *>(strdup(value_str));
205 txt_entries[i].mValueLength = strlen(value_str);
206 }
207 entry->mService.mTxtEntries = txt_entries;
208 entry->mService.mNumTxtEntries = service.txt_records.size();
209
210 // Add service
211 error = otSrpClientAddService(instance, &entry->mService);
212 if (error != OT_ERROR_NONE) {
213 ESP_LOGW(TAG, "Failed to add service: %s", otThreadErrorToString(error));
214 }
215 ESP_LOGD(TAG, "Added service: %s", full_service.c_str());
216 }
217
218 otSrpClientEnableAutoStartMode(instance, OpenThreadSrpComponent::srp_start_callback, nullptr);
219 ESP_LOGD(TAG, "Finished SRP setup");
220}
221
223 uint8_t *ptr = new uint8_t[size];
224 this->memory_pool_.emplace_back(std::unique_ptr<uint8_t[]>(ptr));
225 return ptr;
226}
227
229
231 if (!this->teardown_started_) {
232 this->teardown_started_ = true;
233 ESP_LOGD(TAG, "Clear Srp");
234 auto lock = InstanceLock::try_acquire(100);
235 if (!lock) {
236 ESP_LOGW(TAG, "Failed to acquire OpenThread lock during teardown, leaking memory");
237 return true;
238 }
239 otInstance *instance = lock->get_instance();
240 otSrpClientClearHostAndServices(instance);
241 otSrpClientBuffersFreeAllServices(instance);
243#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0)
244 ESP_LOGD(TAG, "Exit main loop ");
245 int error = esp_openthread_mainloop_exit();
246 if (error != ESP_OK) {
247 ESP_LOGW(TAG, "Failed attempt to stop main loop %d", error);
248 this->teardown_complete_ = true;
249 }
250#else
251 this->teardown_complete_ = true;
252#endif
253 }
254 return this->teardown_complete_;
255}
256
257void OpenThreadComponent::on_factory_reset(std::function<void()> callback) {
259 ESP_LOGD(TAG, "Start Removal SRP Host and Services");
260 otError error;
262 otInstance *instance = lock.get_instance();
263 otSrpClientSetCallback(instance, OpenThreadSrpComponent::srp_factory_reset_callback, this);
264 error = otSrpClientRemoveHostAndServices(instance, true, true);
265 if (error != OT_ERROR_NONE) {
266 ESP_LOGW(TAG, "Failed to Remove SRP Host and Services");
267 return;
268 }
269 ESP_LOGD(TAG, "Waiting on Confirmation Removal SRP Host and Services");
270}
271
272// set_use_address() is guaranteed to be called during component setup by Python code generation,
273// so use_address_ will always be valid when get_use_address() is called - no fallback needed.
274const char *OpenThreadComponent::get_use_address() const { return this->use_address_; }
275
276void OpenThreadComponent::set_use_address(const char *use_address) { this->use_address_ = use_address; }
277
278} // namespace openthread
279} // namespace esphome
280
281#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.
const StaticVector< MDNSService, MDNS_SERVICE_COUNT > & get_services() const
static std::optional< InstanceLock > try_acquire(int delay)
void set_use_address(const char *use_address)
std::optional< otIp6Address > get_omr_address_(InstanceLock &lock)
std::function< void()> factory_reset_external_callback_
Definition openthread.h:47
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:75
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:74
uint8_t duration
Definition msa3xx.h:0
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.