ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
openthread.h
Go to the documentation of this file.
1#pragma once
3#ifdef USE_OPENTHREAD
4
7#ifdef USE_OPENTHREAD_BORDER_ROUTER
9#endif
11#include "esphome/core/gpio.h"
12
13#include <openthread/srp_client.h>
14#include <openthread/srp_client_buffers.h>
15#include <openthread/instance.h>
16#include <openthread/thread.h>
17
18#include <atomic>
19#include <optional>
20#include <vector>
21
22namespace esphome::openthread {
23
24class InstanceLock;
25
26enum class TeardownStage : uint8_t {
27 TEARDOWN_STAGE_NOT_STARTED = 0,
28 TEARDOWN_STAGE_STOP_IN_PROCESS,
29 TEARDOWN_STAGE_COMPLETED,
30};
31
32#ifdef USE_OPENTHREAD_BORDER_ROUTER
33class OpenThreadComponent final : public Component, public wifi::WiFiConnectStateListener {
34#else
35class OpenThreadComponent final : public Component {
36#endif
37 public:
38 OpenThreadComponent();
39#ifdef USE_OPENTHREAD_RCP_UART
40 OpenThreadComponent(uint32_t rcp_baud_rate, uint8_t rcp_rx_pin, uint8_t rcp_tx_pin, GPIOPin *rcp_reset_pin);
41#endif
42 ~OpenThreadComponent();
43 void dump_config() override;
44 void setup() override;
45#ifdef USE_OPENTHREAD_BORDER_ROUTER
46 void loop() override;
48 void on_wifi_connect_state(StringRef ssid, std::span<const uint8_t, 6> bssid) override { this->enable_loop(); }
49#endif
50 bool teardown() override;
51 float get_setup_priority() const override {
52#ifdef USE_OPENTHREAD_BORDER_ROUTER
53 // After every network interface, whichever order network.priority gives them: the backbone netif has to exist
54 return setup_priority::BEFORE_CONNECTION;
55#else
56 return setup_priority::WIFI;
57#endif
58 }
59
60 bool is_connected() const { return this->connected_; }
62 bool is_lock_initialized() const { return this->lock_initialized_; }
63#ifdef USE_ESP32
65 bool is_task_exited() const { return this->task_exited_; }
66#endif
67 network::IPAddresses get_ip_addresses();
68 std::optional<otIp6Address> get_omr_address();
69 void on_factory_reset(const std::function<void()> &callback);
70#ifdef USE_OPENTHREAD_SRP
71 void defer_factory_reset_external_callback();
72#endif
73
76 const char *get_use_address() const { return this->use_address_; }
77 void set_use_address(const char *use_address) { this->use_address_ = use_address; }
78#if CONFIG_OPENTHREAD_MTD
79 void set_poll_period(uint32_t poll_period) { this->poll_period_ = poll_period; }
80 uint32_t get_poll_period() const { return this->poll_period_; }
81#endif
83 void apply_poll_period(uint32_t poll_period);
84 void set_output_power(int8_t output_power) { this->output_power_ = output_power; }
85 void set_connected(bool connected) { this->connected_ = connected; }
86 static void on_state_changed(otChangedFlags flags, void *context);
87#ifdef USE_OPENTHREAD_RCP_UART
88 static void rcp_failure_handler();
89#endif
90
91 protected:
95 void apply_linkmode_(otInstance *instance);
96#ifdef USE_ESP32
97 void ot_main_();
98 void mark_task_failed_(const LogString *reason);
99#endif
100#ifdef USE_OPENTHREAD_RCP_UART
101 void reset_rcp_(bool recovery_attempt);
102#endif
103
104 std::optional<otIp6Address> get_omr_address_(InstanceLock &lock);
105 otInstance *get_openthread_instance_();
106 int openthread_stop_();
107#ifdef USE_OPENTHREAD_SRP
108 std::function<void()> factory_reset_external_callback_;
109#endif
110#if CONFIG_OPENTHREAD_MTD
111 uint32_t poll_period_{0};
112#endif
113 std::optional<int8_t> output_power_{};
114 std::atomic<bool> lock_initialized_{false};
115 std::atomic<TeardownStage> teardown_stage_{TeardownStage::TEARDOWN_STAGE_NOT_STARTED};
116 std::atomic<bool> connected_{false};
117#ifdef USE_ESP32
118 std::atomic<bool> task_exited_{false};
119#endif
120#ifdef USE_OPENTHREAD_RCP_UART
121 uint32_t rcp_baud_rate_{0};
122 uint8_t rcp_rx_pin_{0};
123 uint8_t rcp_tx_pin_{0};
124 GPIOPin *rcp_reset_pin_{nullptr};
125 // Both only touched on the OpenThread task, by the failure handler
126 uint8_t rcp_reset_attempts_{0};
127 uint32_t last_rcp_reset_ms_{0};
128 static constexpr uint32_t RCP_RESET_FORGET_MS = 10 * 60 * 1000;
129#endif
130#ifdef USE_OPENTHREAD_BORDER_ROUTER
131 std::atomic<bool> mainloop_running_{false};
132 bool border_router_started_{false};
133 bool backbone_up_{false}; // what the stack was told about the infra interface last
134#endif
135
136 private:
137 // Stores a pointer to a string literal (static storage duration).
138 // ONLY set from Python-generated code with string literals - never dynamic strings.
139 const char *use_address_{nullptr};
140};
141
142extern OpenThreadComponent *global_openthread_component; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
143
144#ifdef USE_OPENTHREAD_SRP
145class OpenThreadSrpComponent final : public Component {
146 public:
147 void set_mdns(esphome::mdns::MDNSComponent *mdns) { this->mdns_ = mdns; }
148 // This has to run after the mdns component or else no services are available to advertise
149 float get_setup_priority() const override { return this->mdns_->get_setup_priority() - 1.0f; }
150 void setup() override;
151 static void srp_callback(otError err, const otSrpClientHostInfo *host_info, const otSrpClientService *services,
152 const otSrpClientService *removed_services, void *context);
153 static void srp_start_callback(const otSockAddr *server_socket_address, void *context);
154 static void srp_factory_reset_callback(otError err, const otSrpClientHostInfo *host_info,
155 const otSrpClientService *services, const otSrpClientService *removed_services,
156 void *context);
157
158 protected:
159 esphome::mdns::MDNSComponent *mdns_{nullptr};
160 std::vector<std::unique_ptr<uint8_t[]>> memory_pool_;
161 void *pool_alloc_(size_t size);
162};
163#endif
164
165// RAII guard for the OpenThread API lock. Modeled on std::unique_lock: the
166// guard may or may not own the lock (try_acquire can fail), so check it with
167// operator bool before use. Non-copyable and non-movable: the factories return
168// by value via guaranteed copy elision, so a guard is never duplicated and the
169// lock is released exactly once, when the owning guard goes out of scope.
170class InstanceLock {
171 public:
172 // May fail to acquire within delay ms; check the returned guard with operator bool.
173 static InstanceLock try_acquire(int delay);
174 // Blocks until the lock is held.
175 static InstanceLock acquire();
176 InstanceLock(const InstanceLock &) = delete;
177 InstanceLock(InstanceLock &&) = delete;
178 InstanceLock &operator=(const InstanceLock &) = delete;
179 InstanceLock &operator=(InstanceLock &&) = delete;
180 ~InstanceLock();
181
182 explicit operator bool() const { return this->owns_; }
183
184 // Returns the global openthread instance. Only valid on an owning guard
185 // (operator bool is true); the instance must not be used without the lock held.
186 otInstance *get_instance();
187
188 private:
189 explicit InstanceLock(bool owns) : owns_(owns) {}
190 bool owns_;
191};
192
193} // namespace esphome::openthread
194#endif
float get_setup_priority() const override
void delay(unsigned long ms)
uint16_t flags
void setup()
void loop()
ESPHOME_ALWAYS_INLINE bool is_connected()
Return whether the node is connected to the network (through wifi, eth, ...)
Definition util.h:36
network::IPAddresses get_ip_addresses()
Definition util.cpp:98
OpenThreadComponent * global_openthread_component
static void uint32_t
SemaphoreHandle_t lock