ESPHome 2025.12.0-dev
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"
9#include "esphome/core/hal.h"
10#include "esphome/core/log.h"
11#include "esphome/core/util.h"
13#ifdef USE_API_HOMEASSISTANT_SERVICES
15#endif
16
17#ifdef USE_LOGGER
19#endif
20
21#include <algorithm>
22#include <utility>
23
24namespace esphome::api {
25
26static const char *const TAG = "api";
27
28// APIServer
29APIServer *global_api_server = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
30
32 global_api_server = this;
33 // Pre-allocate shared write buffer
34 shared_write_buffer_.reserve(64);
35}
36
39
40#ifdef USE_API_NOISE
41 uint32_t hash = 88491486UL;
42
44
45#ifndef USE_API_NOISE_PSK_FROM_YAML
46 // Only load saved PSK if not set from YAML
47 SavedNoisePsk noise_pref_saved{};
48 if (this->noise_pref_.load(&noise_pref_saved)) {
49 ESP_LOGD(TAG, "Loaded saved Noise PSK");
50 this->set_noise_psk(noise_pref_saved.psk);
51 }
52#endif
53#endif
54
55 // Schedule reboot if no clients connect within timeout
56 if (this->reboot_timeout_ != 0) {
58 }
59
60 this->socket_ = socket::socket_ip_loop_monitored(SOCK_STREAM, 0); // monitored for incoming connections
61 if (this->socket_ == nullptr) {
62 ESP_LOGW(TAG, "Could not create socket");
63 this->mark_failed();
64 return;
65 }
66 int enable = 1;
67 int err = this->socket_->setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int));
68 if (err != 0) {
69 ESP_LOGW(TAG, "Socket unable to set reuseaddr: errno %d", err);
70 // we can still continue
71 }
72 err = this->socket_->setblocking(false);
73 if (err != 0) {
74 ESP_LOGW(TAG, "Socket unable to set nonblocking mode: errno %d", err);
75 this->mark_failed();
76 return;
77 }
78
79 struct sockaddr_storage server;
80
81 socklen_t sl = socket::set_sockaddr_any((struct sockaddr *) &server, sizeof(server), this->port_);
82 if (sl == 0) {
83 ESP_LOGW(TAG, "Socket unable to set sockaddr: errno %d", errno);
84 this->mark_failed();
85 return;
86 }
87
88 err = this->socket_->bind((struct sockaddr *) &server, sl);
89 if (err != 0) {
90 ESP_LOGW(TAG, "Socket unable to bind: errno %d", errno);
91 this->mark_failed();
92 return;
93 }
94
95 err = this->socket_->listen(this->listen_backlog_);
96 if (err != 0) {
97 ESP_LOGW(TAG, "Socket unable to listen: errno %d", errno);
98 this->mark_failed();
99 return;
100 }
101
102#ifdef USE_LOGGER
103 if (logger::global_logger != nullptr) {
105 [this](int level, const char *tag, const char *message, size_t message_len) {
106 if (this->shutting_down_) {
107 // Don't try to send logs during shutdown
108 // as it could result in a recursion and
109 // we would be filling a buffer we are trying to clear
110 return;
111 }
112 for (auto &c : this->clients_) {
113 if (!c->flags_.remove && c->get_log_subscription_level() >= level)
114 c->try_send_log_message(level, tag, message, message_len);
115 }
116 });
117 }
118#endif
119
120#ifdef USE_CAMERA
121 if (camera::Camera::instance() != nullptr && !camera::Camera::instance()->is_internal()) {
122 camera::Camera::instance()->add_image_callback([this](const std::shared_ptr<camera::CameraImage> &image) {
123 for (auto &c : this->clients_) {
124 if (!c->flags_.remove)
125 c->set_camera_state(image);
126 }
127 });
128 }
129#endif
130}
131
133 this->status_set_warning();
134 this->set_timeout("api_reboot", this->reboot_timeout_, []() {
135 if (!global_api_server->is_connected()) {
136 ESP_LOGE(TAG, "No clients; rebooting");
137 App.reboot();
138 }
139 });
140}
141
143 // Accept new clients only if the socket exists and has incoming connections
144 if (this->socket_ && this->socket_->ready()) {
145 while (true) {
146 struct sockaddr_storage source_addr;
147 socklen_t addr_len = sizeof(source_addr);
148
149 auto sock = this->socket_->accept_loop_monitored((struct sockaddr *) &source_addr, &addr_len);
150 if (!sock)
151 break;
152
153 // Check if we're at the connection limit
154 if (this->clients_.size() >= this->max_connections_) {
155 ESP_LOGW(TAG, "Max connections (%d), rejecting %s", this->max_connections_, sock->getpeername().c_str());
156 // Immediately close - socket destructor will handle cleanup
157 sock.reset();
158 continue;
159 }
160
161 ESP_LOGD(TAG, "Accept %s", sock->getpeername().c_str());
162
163 auto *conn = new APIConnection(std::move(sock), this);
164 this->clients_.emplace_back(conn);
165 conn->start();
166
167 // Clear warning status and cancel reboot when first client connects
168 if (this->clients_.size() == 1 && this->reboot_timeout_ != 0) {
169 this->status_clear_warning();
170 this->cancel_timeout("api_reboot");
171 }
172 }
173 }
174
175 if (this->clients_.empty()) {
176 return;
177 }
178
179 // Process clients and remove disconnected ones in a single pass
180 // Check network connectivity once for all clients
181 if (!network::is_connected()) {
182 // Network is down - disconnect all clients
183 for (auto &client : this->clients_) {
184 client->on_fatal_error();
185 ESP_LOGW(TAG, "%s (%s): Network down; disconnect", client->client_info_.name.c_str(),
186 client->client_info_.peername.c_str());
187 }
188 // Continue to process and clean up the clients below
189 }
190
191 size_t client_index = 0;
192 while (client_index < this->clients_.size()) {
193 auto &client = this->clients_[client_index];
194
195 if (!client->flags_.remove) {
196 // Common case: process active client
197 client->loop();
198 client_index++;
199 continue;
200 }
201
202 // Rare case: handle disconnection
203#ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER
204 this->client_disconnected_trigger_->trigger(client->client_info_.name, client->client_info_.peername);
205#endif
206 ESP_LOGV(TAG, "Remove connection %s", client->client_info_.name.c_str());
207
208 // Swap with the last element and pop (avoids expensive vector shifts)
209 if (client_index < this->clients_.size() - 1) {
210 std::swap(this->clients_[client_index], this->clients_.back());
211 }
212 this->clients_.pop_back();
213
214 // Schedule reboot when last client disconnects
215 if (this->clients_.empty() && this->reboot_timeout_ != 0) {
217 }
218 // Don't increment client_index since we need to process the swapped element
219 }
220}
221
223 ESP_LOGCONFIG(TAG,
224 "Server:\n"
225 " Address: %s:%u\n"
226 " Listen backlog: %u\n"
227 " Max connections: %u",
229#ifdef USE_API_NOISE
230 ESP_LOGCONFIG(TAG, " Noise encryption: %s", YESNO(this->noise_ctx_->has_psk()));
231 if (!this->noise_ctx_->has_psk()) {
232 ESP_LOGCONFIG(TAG, " Supports encryption: YES");
233 }
234#else
235 ESP_LOGCONFIG(TAG, " Noise encryption: NO");
236#endif
237}
238
239#ifdef USE_API_PASSWORD
240bool APIServer::check_password(const uint8_t *password_data, size_t password_len) const {
241 // depend only on input password length
242 const char *a = this->password_.c_str();
243 uint32_t len_a = this->password_.length();
244 const char *b = reinterpret_cast<const char *>(password_data);
245 uint32_t len_b = password_len;
246
247 // disable optimization with volatile
248 volatile uint32_t length = len_b;
249 volatile const char *left = nullptr;
250 volatile const char *right = b;
251 uint8_t result = 0;
252
253 if (len_a == length) {
254 left = *((volatile const char **) &a);
255 result = 0;
256 }
257 if (len_a != length) {
258 left = b;
259 result = 1;
260 }
261
262 for (size_t i = 0; i < length; i++) {
263 result |= *left++ ^ *right++; // NOLINT
264 }
265
266 return result == 0;
267}
268
269#endif
270
272
273// Macro for controller update dispatch
274#define API_DISPATCH_UPDATE(entity_type, entity_name) \
275 void APIServer::on_##entity_name##_update(entity_type *obj) { /* NOLINT(bugprone-macro-parentheses) */ \
276 if (obj->is_internal()) \
277 return; \
278 for (auto &c : this->clients_) \
279 c->send_##entity_name##_state(obj); \
280 }
281
282#ifdef USE_BINARY_SENSOR
284#endif
285
286#ifdef USE_COVER
288#endif
289
290#ifdef USE_FAN
292#endif
293
294#ifdef USE_LIGHT
296#endif
297
298#ifdef USE_SENSOR
300#endif
301
302#ifdef USE_SWITCH
304#endif
305
306#ifdef USE_TEXT_SENSOR
308#endif
309
310#ifdef USE_CLIMATE
312#endif
313
314#ifdef USE_NUMBER
316#endif
317
318#ifdef USE_DATETIME_DATE
320#endif
321
322#ifdef USE_DATETIME_TIME
324#endif
325
326#ifdef USE_DATETIME_DATETIME
328#endif
329
330#ifdef USE_TEXT
332#endif
333
334#ifdef USE_SELECT
336#endif
337
338#ifdef USE_LOCK
340#endif
341
342#ifdef USE_VALVE
344#endif
345
346#ifdef USE_MEDIA_PLAYER
348#endif
349
350#ifdef USE_EVENT
351// Event is a special case - unlike other entities with simple state fields,
352// events store their state in a member accessed via obj->get_last_event_type()
354 if (obj->is_internal())
355 return;
356 for (auto &c : this->clients_)
357 c->send_event(obj, obj->get_last_event_type());
358}
359#endif
360
361#ifdef USE_UPDATE
362// Update is a special case - the method is called on_update, not on_update_update
364 if (obj->is_internal())
365 return;
366 for (auto &c : this->clients_)
367 c->send_update_state(obj);
368}
369#endif
370
371#ifdef USE_ZWAVE_PROXY
373 // We could add code to manage a second subscription type, but, since this message type is
374 // very infrequent and small, we simply send it to all clients
375 for (auto &c : this->clients_)
376 c->send_message(msg, api::ZWaveProxyRequest::MESSAGE_TYPE);
377}
378#endif
379
380#ifdef USE_ALARM_CONTROL_PANEL
382#endif
383
385
386void APIServer::set_port(uint16_t port) { this->port_ = port; }
387
388#ifdef USE_API_PASSWORD
389void APIServer::set_password(const std::string &password) { this->password_ = password; }
390#endif
391
392void APIServer::set_batch_delay(uint16_t batch_delay) { this->batch_delay_ = batch_delay; }
393
394#ifdef USE_API_HOMEASSISTANT_SERVICES
396 for (auto &client : this->clients_) {
397 client->send_homeassistant_action(call);
398 }
399}
400#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
402 this->action_response_callbacks_.push_back({call_id, std::move(callback)});
403}
404
405void APIServer::handle_action_response(uint32_t call_id, bool success, const std::string &error_message) {
406 for (auto it = this->action_response_callbacks_.begin(); it != this->action_response_callbacks_.end(); ++it) {
407 if (it->call_id == call_id) {
408 auto callback = std::move(it->callback);
409 this->action_response_callbacks_.erase(it);
410 ActionResponse response(success, error_message);
411 callback(response);
412 return;
413 }
414 }
415}
416#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
417void APIServer::handle_action_response(uint32_t call_id, bool success, const std::string &error_message,
418 const uint8_t *response_data, size_t response_data_len) {
419 for (auto it = this->action_response_callbacks_.begin(); it != this->action_response_callbacks_.end(); ++it) {
420 if (it->call_id == call_id) {
421 auto callback = std::move(it->callback);
422 this->action_response_callbacks_.erase(it);
423 ActionResponse response(success, error_message, response_data, response_data_len);
424 callback(response);
425 return;
426 }
427 }
428}
429#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
430#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES
431#endif // USE_API_HOMEASSISTANT_SERVICES
432
433#ifdef USE_API_HOMEASSISTANT_STATES
435 std::function<void(std::string)> f) {
437 .entity_id = std::move(entity_id),
438 .attribute = std::move(attribute),
439 .callback = std::move(f),
440 .once = false,
441 });
442}
443
444void APIServer::get_home_assistant_state(std::string entity_id, optional<std::string> attribute,
445 std::function<void(std::string)> f) {
447 .entity_id = std::move(entity_id),
448 .attribute = std::move(attribute),
449 .callback = std::move(f),
450 .once = true,
451 });
452};
453
454const std::vector<APIServer::HomeAssistantStateSubscription> &APIServer::get_state_subs() const {
455 return this->state_subs_;
456}
457#endif
458
459uint16_t APIServer::get_port() const { return this->port_; }
460
461void APIServer::set_reboot_timeout(uint32_t reboot_timeout) { this->reboot_timeout_ = reboot_timeout; }
462
463#ifdef USE_API_NOISE
464bool APIServer::update_noise_psk_(const SavedNoisePsk &new_psk, const LogString *save_log_msg,
465 const LogString *fail_log_msg, const psk_t &active_psk, bool make_active) {
466 if (!this->noise_pref_.save(&new_psk)) {
467 ESP_LOGW(TAG, "%s", LOG_STR_ARG(fail_log_msg));
468 return false;
469 }
470 // ensure it's written immediately
471 if (!global_preferences->sync()) {
472 ESP_LOGW(TAG, "Failed to sync preferences");
473 return false;
474 }
475 ESP_LOGD(TAG, "%s", LOG_STR_ARG(save_log_msg));
476 if (make_active) {
477 this->set_timeout(100, [this, active_psk]() {
478 ESP_LOGW(TAG, "Disconnecting all clients to reset PSK");
479 this->set_noise_psk(active_psk);
480 for (auto &c : this->clients_) {
482 c->send_message(req, DisconnectRequest::MESSAGE_TYPE);
483 }
484 });
485 }
486 return true;
487}
488
489bool APIServer::save_noise_psk(psk_t psk, bool make_active) {
490#ifdef USE_API_NOISE_PSK_FROM_YAML
491 // When PSK is set from YAML, this function should never be called
492 // but if it is, reject the change
493 ESP_LOGW(TAG, "Key set in YAML");
494 return false;
495#else
496 auto &old_psk = this->noise_ctx_->get_psk();
497 if (std::equal(old_psk.begin(), old_psk.end(), psk.begin())) {
498 ESP_LOGW(TAG, "New PSK matches old");
499 return true;
500 }
501
502 SavedNoisePsk new_saved_psk{psk};
503 return this->update_noise_psk_(new_saved_psk, LOG_STR("Noise PSK saved"), LOG_STR("Failed to save Noise PSK"), psk,
504 make_active);
505#endif
506}
507bool APIServer::clear_noise_psk(bool make_active) {
508#ifdef USE_API_NOISE_PSK_FROM_YAML
509 // When PSK is set from YAML, this function should never be called
510 // but if it is, reject the change
511 ESP_LOGW(TAG, "Key set in YAML");
512 return false;
513#else
514 SavedNoisePsk empty_psk{};
515 psk_t empty{};
516 return this->update_noise_psk_(empty_psk, LOG_STR("Noise PSK cleared"), LOG_STR("Failed to clear Noise PSK"), empty,
517 make_active);
518#endif
519}
520#endif
521
522#ifdef USE_HOMEASSISTANT_TIME
524 for (auto &client : this->clients_) {
525 if (!client->flags_.remove && client->is_authenticated())
526 client->send_time_request();
527 }
528}
529#endif
530
531bool APIServer::is_connected() const { return !this->clients_.empty(); }
532
534 this->shutting_down_ = true;
535
536 // Close the listening socket to prevent new connections
537 if (this->socket_) {
538 this->socket_->close();
539 this->socket_ = nullptr;
540 }
541
542 // Change batch delay to 5ms for quick flushing during shutdown
543 this->batch_delay_ = 5;
544
545 // Send disconnect requests to all connected clients
546 for (auto &c : this->clients_) {
548 if (!c->send_message(req, DisconnectRequest::MESSAGE_TYPE)) {
549 // If we can't send the disconnect request directly (tx_buffer full),
550 // schedule it at the front of the batch so it will be sent with priority
553 }
554 }
555}
556
558 // If network is disconnected, no point trying to flush buffers
559 if (!network::is_connected()) {
560 return true;
561 }
562 this->loop();
563
564 // Return true only when all clients have been torn down
565 return this->clients_.empty();
566}
567
568} // namespace esphome::api
569#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.
static void register_controller(Controller *controller)
Register a controller to receive entity state updates.
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:48
void trigger(const Ts &...x)
Inform the parent automation that the event has triggered.
Definition automation.h:169
static uint16_t try_send_disconnect_request(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single)
void register_action_response_callback(uint32_t call_id, ActionResponseCallback callback)
std::vector< std::unique_ptr< APIConnection > > clients_
Definition api_server.h:201
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
void handle_action_response(uint32_t call_id, bool success, const std::string &error_message)
bool teardown() override
void send_homeassistant_action(const HomeassistantActionRequest &call)
void on_event(event::Event *obj) override
void on_update(update::UpdateEntity *obj) override
bool check_password(const uint8_t *password_data, size_t password_len) const
std::vector< PendingActionResponse > action_response_callbacks_
Definition api_server.h:217
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:231
Trigger< std::string, std::string > * client_disconnected_trigger_
Definition api_server.h:194
std::vector< uint8_t > shared_write_buffer_
Definition api_server.h:205
void subscribe_home_assistant_state(std::string entity_id, optional< std::string > attribute, std::function< void(std::string)> f)
std::function< void(const class ActionResponse &)> ActionResponseCallback
Definition api_server.h:118
bool update_noise_psk_(const SavedNoisePsk &new_psk, const LogString *save_log_msg, const LogString *fail_log_msg, const psk_t &active_psk, bool make_active)
ESPPreferenceObject noise_pref_
Definition api_server.h:232
std::vector< HomeAssistantStateSubscription > state_subs_
Definition api_server.h:207
void on_zwave_proxy_request(const esphome::api::ProtoMessage &msg)
bool clear_noise_psk(bool make_active=true)
uint16_t get_port() const
void set_noise_psk(psk_t psk)
Definition api_server.h:57
float get_setup_priority() const override
std::unique_ptr< socket::Socket > socket_
Definition api_server.h:189
void on_shutdown() override
static constexpr uint8_t MESSAGE_TYPE
Definition api_pb2.h:407
static constexpr uint8_t ESTIMATED_SIZE
Definition api_pb2.h:408
static constexpr uint8_t MESSAGE_TYPE
Definition api_pb2.h:3010
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:178
Base class for all cover devices.
Definition cover.h:112
const char * get_last_event_type() const
Return the last triggered event type (pointer to string in types_), or nullptr if no event triggered ...
Definition event.h:48
This class represents the communication layer between the front-end MQTT layer and the hardware outpu...
Definition light_state.h:69
Base class for all locks.
Definition lock.h:109
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:233
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
const char * message
Definition component.cpp:38
uint16_t addr_len
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:294
const char * 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:66
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