ESPHome 2025.9.3
Loading...
Searching...
No Matches
api_server.cpp
Go to the documentation of this file.
1#include "api_server.h"
2#ifdef USE_API
3#include <cerrno>
4#include "api_connection.h"
8#include "esphome/core/hal.h"
9#include "esphome/core/log.h"
10#include "esphome/core/util.h"
12
13#ifdef USE_LOGGER
15#endif
16
17#include <algorithm>
18
19namespace esphome::api {
20
21static const char *const TAG = "api";
22
23// APIServer
24APIServer *global_api_server = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
25
27 global_api_server = this;
28 // Pre-allocate shared write buffer
29 shared_write_buffer_.reserve(64);
30}
31
33 this->setup_controller();
34
35#ifdef USE_API_NOISE
36 uint32_t hash = 88491486UL;
37
39
40#ifndef USE_API_NOISE_PSK_FROM_YAML
41 // Only load saved PSK if not set from YAML
42 SavedNoisePsk noise_pref_saved{};
43 if (this->noise_pref_.load(&noise_pref_saved)) {
44 ESP_LOGD(TAG, "Loaded saved Noise PSK");
45 this->set_noise_psk(noise_pref_saved.psk);
46 }
47#endif
48#endif
49
50 // Schedule reboot if no clients connect within timeout
51 if (this->reboot_timeout_ != 0) {
53 }
54
55 this->socket_ = socket::socket_ip_loop_monitored(SOCK_STREAM, 0); // monitored for incoming connections
56 if (this->socket_ == nullptr) {
57 ESP_LOGW(TAG, "Could not create socket");
58 this->mark_failed();
59 return;
60 }
61 int enable = 1;
62 int err = this->socket_->setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int));
63 if (err != 0) {
64 ESP_LOGW(TAG, "Socket unable to set reuseaddr: errno %d", err);
65 // we can still continue
66 }
67 err = this->socket_->setblocking(false);
68 if (err != 0) {
69 ESP_LOGW(TAG, "Socket unable to set nonblocking mode: errno %d", err);
70 this->mark_failed();
71 return;
72 }
73
74 struct sockaddr_storage server;
75
76 socklen_t sl = socket::set_sockaddr_any((struct sockaddr *) &server, sizeof(server), this->port_);
77 if (sl == 0) {
78 ESP_LOGW(TAG, "Socket unable to set sockaddr: errno %d", errno);
79 this->mark_failed();
80 return;
81 }
82
83 err = this->socket_->bind((struct sockaddr *) &server, sl);
84 if (err != 0) {
85 ESP_LOGW(TAG, "Socket unable to bind: errno %d", errno);
86 this->mark_failed();
87 return;
88 }
89
90 err = this->socket_->listen(4);
91 if (err != 0) {
92 ESP_LOGW(TAG, "Socket unable to listen: errno %d", errno);
93 this->mark_failed();
94 return;
95 }
96
97#ifdef USE_LOGGER
98 if (logger::global_logger != nullptr) {
100 [this](int level, const char *tag, const char *message, size_t message_len) {
101 if (this->shutting_down_) {
102 // Don't try to send logs during shutdown
103 // as it could result in a recursion and
104 // we would be filling a buffer we are trying to clear
105 return;
106 }
107 for (auto &c : this->clients_) {
108 if (!c->flags_.remove && c->get_log_subscription_level() >= level)
109 c->try_send_log_message(level, tag, message, message_len);
110 }
111 });
112 }
113#endif
114
115#ifdef USE_CAMERA
116 if (camera::Camera::instance() != nullptr && !camera::Camera::instance()->is_internal()) {
117 camera::Camera::instance()->add_image_callback([this](const std::shared_ptr<camera::CameraImage> &image) {
118 for (auto &c : this->clients_) {
119 if (!c->flags_.remove)
120 c->set_camera_state(image);
121 }
122 });
123 }
124#endif
125}
126
128 this->status_set_warning();
129 this->set_timeout("api_reboot", this->reboot_timeout_, []() {
130 if (!global_api_server->is_connected()) {
131 ESP_LOGE(TAG, "No clients; rebooting");
132 App.reboot();
133 }
134 });
135}
136
138 // Accept new clients only if the socket exists and has incoming connections
139 if (this->socket_ && this->socket_->ready()) {
140 while (true) {
141 struct sockaddr_storage source_addr;
142 socklen_t addr_len = sizeof(source_addr);
143 auto sock = this->socket_->accept_loop_monitored((struct sockaddr *) &source_addr, &addr_len);
144 if (!sock)
145 break;
146 ESP_LOGD(TAG, "Accept %s", sock->getpeername().c_str());
147
148 auto *conn = new APIConnection(std::move(sock), this);
149 this->clients_.emplace_back(conn);
150 conn->start();
151
152 // Clear warning status and cancel reboot when first client connects
153 if (this->clients_.size() == 1 && this->reboot_timeout_ != 0) {
154 this->status_clear_warning();
155 this->cancel_timeout("api_reboot");
156 }
157 }
158 }
159
160 if (this->clients_.empty()) {
161 return;
162 }
163
164 // Process clients and remove disconnected ones in a single pass
165 // Check network connectivity once for all clients
166 if (!network::is_connected()) {
167 // Network is down - disconnect all clients
168 for (auto &client : this->clients_) {
169 client->on_fatal_error();
170 ESP_LOGW(TAG, "%s: Network down; disconnect", client->get_client_combined_info().c_str());
171 }
172 // Continue to process and clean up the clients below
173 }
174
175 size_t client_index = 0;
176 while (client_index < this->clients_.size()) {
177 auto &client = this->clients_[client_index];
178
179 if (!client->flags_.remove) {
180 // Common case: process active client
181 client->loop();
182 client_index++;
183 continue;
184 }
185
186 // Rare case: handle disconnection
187#ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER
188 this->client_disconnected_trigger_->trigger(client->client_info_.name, client->client_info_.peername);
189#endif
190 ESP_LOGV(TAG, "Remove connection %s", client->client_info_.name.c_str());
191
192 // Swap with the last element and pop (avoids expensive vector shifts)
193 if (client_index < this->clients_.size() - 1) {
194 std::swap(this->clients_[client_index], this->clients_.back());
195 }
196 this->clients_.pop_back();
197
198 // Schedule reboot when last client disconnects
199 if (this->clients_.empty() && this->reboot_timeout_ != 0) {
201 }
202 // Don't increment client_index since we need to process the swapped element
203 }
204}
205
207 ESP_LOGCONFIG(TAG,
208 "Server:\n"
209 " Address: %s:%u",
210 network::get_use_address().c_str(), this->port_);
211#ifdef USE_API_NOISE
212 ESP_LOGCONFIG(TAG, " Noise encryption: %s", YESNO(this->noise_ctx_->has_psk()));
213 if (!this->noise_ctx_->has_psk()) {
214 ESP_LOGCONFIG(TAG, " Supports encryption: YES");
215 }
216#else
217 ESP_LOGCONFIG(TAG, " Noise encryption: NO");
218#endif
219}
220
221#ifdef USE_API_PASSWORD
222bool APIServer::check_password(const std::string &password) const {
223 // depend only on input password length
224 const char *a = this->password_.c_str();
225 uint32_t len_a = this->password_.length();
226 const char *b = password.c_str();
227 uint32_t len_b = password.length();
228
229 // disable optimization with volatile
230 volatile uint32_t length = len_b;
231 volatile const char *left = nullptr;
232 volatile const char *right = b;
233 uint8_t result = 0;
234
235 if (len_a == length) {
236 left = *((volatile const char **) &a);
237 result = 0;
238 }
239 if (len_a != length) {
240 left = b;
241 result = 1;
242 }
243
244 for (size_t i = 0; i < length; i++) {
245 result |= *left++ ^ *right++; // NOLINT
246 }
247
248 return result == 0;
249}
250#endif
251
253
254// Macro for entities without extra parameters
255#define API_DISPATCH_UPDATE(entity_type, entity_name) \
256 void APIServer::on_##entity_name##_update(entity_type *obj) { /* NOLINT(bugprone-macro-parentheses) */ \
257 if (obj->is_internal()) \
258 return; \
259 for (auto &c : this->clients_) \
260 c->send_##entity_name##_state(obj); \
261 }
262
263// Macro for entities with extra parameters (but parameters not used in send)
264#define API_DISPATCH_UPDATE_IGNORE_PARAMS(entity_type, entity_name, ...) \
265 void APIServer::on_##entity_name##_update(entity_type *obj, __VA_ARGS__) { /* NOLINT(bugprone-macro-parentheses) */ \
266 if (obj->is_internal()) \
267 return; \
268 for (auto &c : this->clients_) \
269 c->send_##entity_name##_state(obj); \
270 }
271
272#ifdef USE_BINARY_SENSOR
274#endif
275
276#ifdef USE_COVER
278#endif
279
280#ifdef USE_FAN
282#endif
283
284#ifdef USE_LIGHT
286#endif
287
288#ifdef USE_SENSOR
289API_DISPATCH_UPDATE_IGNORE_PARAMS(sensor::Sensor, sensor, float state)
290#endif
291
292#ifdef USE_SWITCH
293API_DISPATCH_UPDATE_IGNORE_PARAMS(switch_::Switch, switch, bool state)
294#endif
295
296#ifdef USE_TEXT_SENSOR
297API_DISPATCH_UPDATE_IGNORE_PARAMS(text_sensor::TextSensor, text_sensor, const std::string &state)
298#endif
299
300#ifdef USE_CLIMATE
302#endif
303
304#ifdef USE_NUMBER
305API_DISPATCH_UPDATE_IGNORE_PARAMS(number::Number, number, float state)
306#endif
307
308#ifdef USE_DATETIME_DATE
310#endif
311
312#ifdef USE_DATETIME_TIME
314#endif
315
316#ifdef USE_DATETIME_DATETIME
318#endif
319
320#ifdef USE_TEXT
321API_DISPATCH_UPDATE_IGNORE_PARAMS(text::Text, text, const std::string &state)
322#endif
323
324#ifdef USE_SELECT
325API_DISPATCH_UPDATE_IGNORE_PARAMS(select::Select, select, const std::string &state, size_t index)
326#endif
327
328#ifdef USE_LOCK
330#endif
331
332#ifdef USE_VALVE
334#endif
335
336#ifdef USE_MEDIA_PLAYER
338#endif
339
340#ifdef USE_EVENT
341// Event is a special case - it's the only entity that passes extra parameters to the send method
342void APIServer::on_event(event::Event *obj, const std::string &event_type) {
343 if (obj->is_internal())
344 return;
345 for (auto &c : this->clients_)
346 c->send_event(obj, event_type);
347}
348#endif
349
350#ifdef USE_UPDATE
351// Update is a special case - the method is called on_update, not on_update_update
353 if (obj->is_internal())
354 return;
355 for (auto &c : this->clients_)
356 c->send_update_state(obj);
357}
358#endif
359
360#ifdef USE_ALARM_CONTROL_PANEL
362#endif
363
365
366void APIServer::set_port(uint16_t port) { this->port_ = port; }
367
368#ifdef USE_API_PASSWORD
369void APIServer::set_password(const std::string &password) { this->password_ = password; }
370#endif
371
372void APIServer::set_batch_delay(uint16_t batch_delay) { this->batch_delay_ = batch_delay; }
373
374#ifdef USE_API_HOMEASSISTANT_SERVICES
376 for (auto &client : this->clients_) {
377 client->send_homeassistant_service_call(call);
378 }
379}
380#endif
381
382#ifdef USE_API_HOMEASSISTANT_STATES
384 std::function<void(std::string)> f) {
386 .entity_id = std::move(entity_id),
387 .attribute = std::move(attribute),
388 .callback = std::move(f),
389 .once = false,
390 });
391}
392
393void APIServer::get_home_assistant_state(std::string entity_id, optional<std::string> attribute,
394 std::function<void(std::string)> f) {
396 .entity_id = std::move(entity_id),
397 .attribute = std::move(attribute),
398 .callback = std::move(f),
399 .once = true,
400 });
401};
402
403const std::vector<APIServer::HomeAssistantStateSubscription> &APIServer::get_state_subs() const {
404 return this->state_subs_;
405}
406#endif
407
408uint16_t APIServer::get_port() const { return this->port_; }
409
410void APIServer::set_reboot_timeout(uint32_t reboot_timeout) { this->reboot_timeout_ = reboot_timeout; }
411
412#ifdef USE_API_NOISE
413bool APIServer::save_noise_psk(psk_t psk, bool make_active) {
414#ifdef USE_API_NOISE_PSK_FROM_YAML
415 // When PSK is set from YAML, this function should never be called
416 // but if it is, reject the change
417 ESP_LOGW(TAG, "Key set in YAML");
418 return false;
419#else
420 auto &old_psk = this->noise_ctx_->get_psk();
421 if (std::equal(old_psk.begin(), old_psk.end(), psk.begin())) {
422 ESP_LOGW(TAG, "New PSK matches old");
423 return true;
424 }
425
426 SavedNoisePsk new_saved_psk{psk};
427 if (!this->noise_pref_.save(&new_saved_psk)) {
428 ESP_LOGW(TAG, "Failed to save Noise PSK");
429 return false;
430 }
431 // ensure it's written immediately
432 if (!global_preferences->sync()) {
433 ESP_LOGW(TAG, "Failed to sync preferences");
434 return false;
435 }
436 ESP_LOGD(TAG, "Noise PSK saved");
437 if (make_active) {
438 this->set_timeout(100, [this, psk]() {
439 ESP_LOGW(TAG, "Disconnecting all clients to reset PSK");
440 this->set_noise_psk(psk);
441 for (auto &c : this->clients_) {
443 c->send_message(req, DisconnectRequest::MESSAGE_TYPE);
444 }
445 });
446 }
447 return true;
448#endif
449}
450#endif
451
452#ifdef USE_HOMEASSISTANT_TIME
454 for (auto &client : this->clients_) {
455 if (!client->flags_.remove && client->is_authenticated())
456 client->send_time_request();
457 }
458}
459#endif
460
461bool APIServer::is_connected() const { return !this->clients_.empty(); }
462
464 this->shutting_down_ = true;
465
466 // Close the listening socket to prevent new connections
467 if (this->socket_) {
468 this->socket_->close();
469 this->socket_ = nullptr;
470 }
471
472 // Change batch delay to 5ms for quick flushing during shutdown
473 this->batch_delay_ = 5;
474
475 // Send disconnect requests to all connected clients
476 for (auto &c : this->clients_) {
478 if (!c->send_message(req, DisconnectRequest::MESSAGE_TYPE)) {
479 // If we can't send the disconnect request directly (tx_buffer full),
480 // schedule it at the front of the batch so it will be sent with priority
483 }
484 }
485}
486
488 // If network is disconnected, no point trying to flush buffers
489 if (!network::is_connected()) {
490 return true;
491 }
492 this->loop();
493
494 // Return true only when all clients have been torn down
495 return this->clients_.empty();
496}
497
498} // namespace esphome::api
499#endif
virtual void mark_failed()
Mark this component as failed.
void status_set_warning(const char *message=nullptr)
bool cancel_timeout(const std::string &name)
Cancel a timeout function.
void status_clear_warning()
void set_timeout(const std::string &name, uint32_t timeout, std::function< void()> &&f)
Set a timeout function with a unique name.
void setup_controller(bool include_internal=false)
Definition controller.cpp:7
bool save(const T *src)
Definition preferences.h:21
virtual bool sync()=0
Commit pending writes to flash.
virtual ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash)=0
bool is_internal() const
Definition entity_base.h:44
void trigger(Ts... x)
Inform the parent automation that the event has triggered.
Definition automation.h:145
static uint16_t try_send_disconnect_request(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single)
std::vector< std::unique_ptr< APIConnection > > clients_
Definition api_server.h:173
void send_homeassistant_service_call(const HomeassistantServiceResponse &call)
void set_password(const std::string &password)
void set_port(uint16_t port)
void dump_config() override
void handle_disconnect(APIConnection *conn)
void set_batch_delay(uint16_t batch_delay)
void set_reboot_timeout(uint32_t reboot_timeout)
bool save_noise_psk(psk_t psk, bool make_active=true)
void setup() override
bool teardown() override
void on_update(update::UpdateEntity *obj) override
bool check_password(const std::string &password) const
void get_home_assistant_state(std::string entity_id, optional< std::string > attribute, std::function< void(std::string)> f)
const std::vector< HomeAssistantStateSubscription > & get_state_subs() const
std::shared_ptr< APINoiseContext > noise_ctx_
Definition api_server.h:192
Trigger< std::string, std::string > * client_disconnected_trigger_
Definition api_server.h:166
std::vector< uint8_t > shared_write_buffer_
Definition api_server.h:177
void subscribe_home_assistant_state(std::string entity_id, optional< std::string > attribute, std::function< void(std::string)> f)
ESPPreferenceObject noise_pref_
Definition api_server.h:193
std::vector< HomeAssistantStateSubscription > state_subs_
Definition api_server.h:179
uint16_t get_port() const
void set_noise_psk(psk_t psk)
Definition api_server.h:53
void on_event(event::Event *obj, const std::string &event_type) override
float get_setup_priority() const override
std::unique_ptr< socket::Socket > socket_
Definition api_server.h:161
void on_shutdown() override
static constexpr uint8_t MESSAGE_TYPE
Definition api_pb2.h:396
static constexpr uint8_t ESTIMATED_SIZE
Definition api_pb2.h:397
Base class for all binary_sensor-type classes.
static Camera * instance()
The singleton instance of the camera implementation.
Definition camera.cpp:19
virtual void add_image_callback(std::function< void(std::shared_ptr< CameraImage >)> &&callback)=0
ClimateDevice - This is the base class for all climate integrations.
Definition climate.h:168
Base class for all cover devices.
Definition cover.h:111
This class represents the communication layer between the front-end MQTT layer and the hardware outpu...
Definition light_state.h:68
Base class for all locks.
Definition lock.h:103
void add_on_log_callback(std::function< void(uint8_t, const char *, const char *, size_t)> &&callback)
Register a callback that will be called for every log message sent.
Definition logger.cpp:245
Base-class for all numbers.
Definition number.h:30
Base-class for all selects.
Definition select.h:31
Base-class for all sensors.
Definition sensor.h:42
Base class for all switches.
Definition switch.h:39
Base-class for all text inputs.
Definition text.h:24
Base class for all valve devices.
Definition valve.h:105
bool state
Definition fan.h:0
uint32_t socklen_t
Definition headers.h:97
APIServer * global_api_server
API_DISPATCH_UPDATE(binary_sensor::BinarySensor, binary_sensor) API_DISPATCH_UPDATE(cover
std::array< uint8_t, 32 > psk_t
Logger * global_logger
Definition logger.cpp:304
std::string get_use_address()
Get the active network hostname.
Definition util.cpp:88
bool is_connected()
Return whether the node is connected to the network (through wifi, eth, ...)
Definition util.cpp:26
const float AFTER_WIFI
For components that should be initialized after WiFi is connected.
Definition component.cpp:56
std::unique_ptr< Socket > socket_ip_loop_monitored(int type, int protocol)
Definition socket.cpp:44
socklen_t set_sockaddr_any(struct sockaddr *addr, socklen_t addrlen, uint16_t port)
Set a sockaddr to the any address and specified port for the IP version used by socket_ip().
Definition socket.cpp:82
ESPPreferences * global_preferences
Application App
Global storage of Application pointer - only one Application can exist.
uint16_t length
Definition tt21100.cpp:0