ESPHome 2026.8.0-dev
Loading...
Searching...
No Matches
openthread_zephyr.cpp
Go to the documentation of this file.
2#if defined(USE_OPENTHREAD) && defined(USE_NRF52)
3#include <openthread/dataset.h>
4#include <openthread/thread.h>
5#include <openthread/logging.h>
6#include "openthread.h"
8#include <zephyr/net/openthread.h>
9
10static const char *const TAG = "openthread";
11
12namespace esphome::openthread {
13
14static void on_thread_state_changed(otChangedFlags flags, struct openthread_context *ot_context, void *user_data) {
15 // Delegate connection status tracking to common callback
16 if (global_openthread_component != nullptr) {
18 }
19 if (flags & OT_CHANGED_THREAD_ROLE) {
20 otDeviceRole role = otThreadGetDeviceRole(ot_context->instance);
21 ESP_LOGI(TAG, "Thread role changed to %s", otThreadDeviceRoleToString(role));
22 }
23 if (flags & OT_CHANGED_THREAD_NETDATA) {
24 ESP_LOGI(TAG, "Thread network data updated");
25 }
26 if (flags & (OT_CHANGED_THREAD_ROLE | OT_CHANGED_THREAD_NETDATA)) {
27 char buf[NET_IPV6_ADDR_LEN];
28 for (const otNetifAddress *addr = otIp6GetUnicastAddresses(ot_context->instance); addr != nullptr;
29 addr = addr->mNext) {
30 ESP_LOGI(TAG, " Address: %s", net_addr_ntop(AF_INET6, &addr->mAddress, buf, sizeof(buf)));
31 }
32 }
33}
34
35static struct openthread_state_changed_cb ot_state_changed_cb = {.state_changed_cb = on_thread_state_changed};
36
38 struct openthread_context *context = openthread_get_default_context();
39 this->lock_initialized_ = true;
40 otOperationalDatasetTlvs dataset = {};
41
42#ifndef USE_OPENTHREAD_FORCE_DATASET
43 otError error = otDatasetGetActiveTlvs(context->instance, &dataset);
44 if (error != OT_ERROR_NONE) {
45 dataset.mLength = 0;
46 } else {
47 ESP_LOGI(TAG, "Found existing dataset, ignoring config (force_dataset: true to override)");
48 }
49#endif
50
51#ifdef USE_OPENTHREAD_TLVS
52 if (dataset.mLength == 0) {
53 const size_t tlv_chars = sizeof(USE_OPENTHREAD_TLVS) - 1;
54 if ((tlv_chars % 2) != 0) {
55 ESP_LOGE(TAG, "Invalid OpenThread TLV hex string length (must be even, got %zu)", tlv_chars);
56 this->mark_failed();
57 return;
58 }
59
60 size_t len = tlv_chars / 2;
61 if (len > sizeof(dataset.mTlvs)) {
62 ESP_LOGE(TAG, "OpenThread TLV too long (max %zu bytes, got %zu bytes)", sizeof(dataset.mTlvs), len);
63 this->mark_failed();
64 return;
65 }
66
67 size_t parsed = parse_hex(USE_OPENTHREAD_TLVS, tlv_chars, dataset.mTlvs, len);
68 if (parsed != tlv_chars) {
69 ESP_LOGE(TAG, "Invalid OpenThread TLV hex string (expected %zu hex chars, got %zu)", tlv_chars, parsed);
70 this->mark_failed();
71 return;
72 }
73 dataset.mLength = len;
74 }
75#endif
76 if (dataset.mLength > 0) {
77 otError error = otDatasetSetActiveTlvs(context->instance, &dataset);
78 if (error != OT_ERROR_NONE) {
79 ESP_LOGE(TAG, "Failed to set active dataset: %s", otThreadErrorToString(error));
80 this->mark_failed();
81 return;
82 }
83 }
84 openthread_state_changed_cb_register(context, &ot_state_changed_cb);
85 openthread_start(context);
86}
87
89
90otInstance *OpenThreadComponent::get_openthread_instance_() { return openthread_get_default_instance(); }
91
93 // OT stack is intentionally left running — no Zephyr stop API. The state callback stays
94 // registered but is safe (null-checks global_openthread_component). nRF52840 never
95 // re-enters setup() after teardown so this is functionally correct.
96 this->teardown_complete_ = true;
97 return 0;
98}
99
101 network::IPAddresses addresses;
103 size_t addr_count = 0;
104 for (const otNetifAddress *addr = otIp6GetUnicastAddresses(openthread_get_default_instance());
105 addr != nullptr && addr_count + 1 < addresses.size(); addr = addr->mNext) {
106 struct in6_addr ip6;
107 memcpy(&ip6, addr->mAddress.mFields.m8, sizeof(ip6));
108 addresses[addr_count + 1] = network::IPAddress(&ip6);
109 addr_count++;
110 }
111 return addresses;
112}
113
114InstanceLock InstanceLock::try_acquire(int delay) {
116 return InstanceLock(false);
117 }
118 struct openthread_context *ot_context = openthread_get_default_context();
119 if (k_mutex_lock(&ot_context->api_lock, K_MSEC(delay)) == 0) {
120 return InstanceLock(true);
121 }
122 return InstanceLock(false);
123}
124
125InstanceLock InstanceLock::acquire() {
126 struct openthread_context *ot_context = openthread_get_default_context();
127 k_mutex_lock(&ot_context->api_lock, K_FOREVER);
128 return InstanceLock(true);
129}
130
131otInstance *InstanceLock::get_instance() { return openthread_get_default_instance(); }
132
134 if (this->owns_) {
135 struct openthread_context *ot_context = openthread_get_default_context();
136 k_mutex_unlock(&ot_context->api_lock);
137 }
138}
139
140} // namespace esphome::openthread
141#endif
void mark_failed()
Mark this component as failed.
static InstanceLock try_acquire(int delay)
InstanceLock(const InstanceLock &)=delete
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
uint16_t flags
std::array< IPAddress, 5 > IPAddresses
Definition ip_address.h:196
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
SemaphoreHandle_t lock