ESPHome 2025.6.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 {
20namespace api {
21
22static const char *const TAG = "api";
23
24// APIServer
25APIServer *global_api_server = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
26
28 global_api_server = this;
29 // Pre-allocate shared write buffer
30 shared_write_buffer_.reserve(64);
31}
32
34 ESP_LOGCONFIG(TAG, "Running setup");
35 this->setup_controller();
36
37#ifdef USE_API_NOISE
38 uint32_t hash = 88491486UL;
39
41
42 SavedNoisePsk noise_pref_saved{};
43 if (this->noise_pref_.load(&noise_pref_saved)) {
44 ESP_LOGD(TAG, "Loaded saved Noise PSK");
45
46 this->set_noise_psk(noise_pref_saved.psk);
47 }
48#endif
49
50 this->socket_ = socket::socket_ip_loop_monitored(SOCK_STREAM, 0); // monitored for incoming connections
51 if (this->socket_ == nullptr) {
52 ESP_LOGW(TAG, "Could not create socket");
53 this->mark_failed();
54 return;
55 }
56 int enable = 1;
57 int err = this->socket_->setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int));
58 if (err != 0) {
59 ESP_LOGW(TAG, "Socket unable to set reuseaddr: errno %d", err);
60 // we can still continue
61 }
62 err = this->socket_->setblocking(false);
63 if (err != 0) {
64 ESP_LOGW(TAG, "Socket unable to set nonblocking mode: errno %d", err);
65 this->mark_failed();
66 return;
67 }
68
69 struct sockaddr_storage server;
70
71 socklen_t sl = socket::set_sockaddr_any((struct sockaddr *) &server, sizeof(server), this->port_);
72 if (sl == 0) {
73 ESP_LOGW(TAG, "Socket unable to set sockaddr: errno %d", errno);
74 this->mark_failed();
75 return;
76 }
77
78 err = this->socket_->bind((struct sockaddr *) &server, sl);
79 if (err != 0) {
80 ESP_LOGW(TAG, "Socket unable to bind: errno %d", errno);
81 this->mark_failed();
82 return;
83 }
84
85 err = this->socket_->listen(4);
86 if (err != 0) {
87 ESP_LOGW(TAG, "Socket unable to listen: errno %d", errno);
88 this->mark_failed();
89 return;
90 }
91
92#ifdef USE_LOGGER
93 if (logger::global_logger != nullptr) {
94 logger::global_logger->add_on_log_callback([this](int level, const char *tag, const char *message) {
95 if (this->shutting_down_) {
96 // Don't try to send logs during shutdown
97 // as it could result in a recursion and
98 // we would be filling a buffer we are trying to clear
99 return;
100 }
101 for (auto &c : this->clients_) {
102 if (!c->remove_)
103 c->try_send_log_message(level, tag, message);
104 }
105 });
106 }
107#endif
108
109 this->last_connected_ = millis();
110
111#ifdef USE_ESP32_CAMERA
114 [this](const std::shared_ptr<esp32_camera::CameraImage> &image) {
115 for (auto &c : this->clients_) {
116 if (!c->remove_)
117 c->set_camera_state(image);
118 }
119 });
120 }
121#endif
122}
123
125 // Accept new clients only if the socket exists and has incoming connections
126 if (this->socket_ && this->socket_->ready()) {
127 while (true) {
128 struct sockaddr_storage source_addr;
129 socklen_t addr_len = sizeof(source_addr);
130 auto sock = this->socket_->accept_loop_monitored((struct sockaddr *) &source_addr, &addr_len);
131 if (!sock)
132 break;
133 ESP_LOGD(TAG, "Accepted %s", sock->getpeername().c_str());
134
135 auto *conn = new APIConnection(std::move(sock), this);
136 this->clients_.emplace_back(conn);
137 conn->start();
138 }
139 }
140
141 // Process clients and remove disconnected ones in a single pass
142 if (!this->clients_.empty()) {
143 size_t client_index = 0;
144 while (client_index < this->clients_.size()) {
145 auto &client = this->clients_[client_index];
146
147 if (client->remove_) {
148 // Handle disconnection
149 this->client_disconnected_trigger_->trigger(client->client_info_, client->client_peername_);
150 ESP_LOGV(TAG, "Removing connection to %s", client->client_info_.c_str());
151
152 // Swap with the last element and pop (avoids expensive vector shifts)
153 if (client_index < this->clients_.size() - 1) {
154 std::swap(this->clients_[client_index], this->clients_.back());
155 }
156 this->clients_.pop_back();
157 // Don't increment client_index since we need to process the swapped element
158 } else {
159 // Process active client
160 client->loop();
161 client_index++; // Move to next client
162 }
163 }
164 }
165
166 if (this->reboot_timeout_ != 0) {
167 const uint32_t now = millis();
168 if (!this->is_connected()) {
169 if (now - this->last_connected_ > this->reboot_timeout_) {
170 ESP_LOGE(TAG, "No client connected; rebooting");
171 App.reboot();
172 }
173 this->status_set_warning();
174 } else {
175 this->last_connected_ = now;
176 this->status_clear_warning();
177 }
178 }
179}
180
182 ESP_LOGCONFIG(TAG,
183 "API Server:\n"
184 " Address: %s:%u",
185 network::get_use_address().c_str(), this->port_);
186#ifdef USE_API_NOISE
187 ESP_LOGCONFIG(TAG, " Using noise encryption: %s", YESNO(this->noise_ctx_->has_psk()));
188 if (!this->noise_ctx_->has_psk()) {
189 ESP_LOGCONFIG(TAG, " Supports noise encryption: YES");
190 }
191#else
192 ESP_LOGCONFIG(TAG, " Using noise encryption: NO");
193#endif
194}
195
196bool APIServer::uses_password() const { return !this->password_.empty(); }
197
198bool APIServer::check_password(const std::string &password) const {
199 // depend only on input password length
200 const char *a = this->password_.c_str();
201 uint32_t len_a = this->password_.length();
202 const char *b = password.c_str();
203 uint32_t len_b = password.length();
204
205 // disable optimization with volatile
206 volatile uint32_t length = len_b;
207 volatile const char *left = nullptr;
208 volatile const char *right = b;
209 uint8_t result = 0;
210
211 if (len_a == length) {
212 left = *((volatile const char **) &a);
213 result = 0;
214 }
215 if (len_a != length) {
216 left = b;
217 result = 1;
218 }
219
220 for (size_t i = 0; i < length; i++) {
221 result |= *left++ ^ *right++; // NOLINT
222 }
223
224 return result == 0;
225}
226
228
229#ifdef USE_BINARY_SENSOR
231 if (obj->is_internal())
232 return;
233 for (auto &c : this->clients_)
234 c->send_binary_sensor_state(obj);
235}
236#endif
237
238#ifdef USE_COVER
240 if (obj->is_internal())
241 return;
242 for (auto &c : this->clients_)
243 c->send_cover_state(obj);
244}
245#endif
246
247#ifdef USE_FAN
249 if (obj->is_internal())
250 return;
251 for (auto &c : this->clients_)
252 c->send_fan_state(obj);
253}
254#endif
255
256#ifdef USE_LIGHT
258 if (obj->is_internal())
259 return;
260 for (auto &c : this->clients_)
261 c->send_light_state(obj);
262}
263#endif
264
265#ifdef USE_SENSOR
267 if (obj->is_internal())
268 return;
269 for (auto &c : this->clients_)
270 c->send_sensor_state(obj);
271}
272#endif
273
274#ifdef USE_SWITCH
276 if (obj->is_internal())
277 return;
278 for (auto &c : this->clients_)
279 c->send_switch_state(obj);
280}
281#endif
282
283#ifdef USE_TEXT_SENSOR
285 if (obj->is_internal())
286 return;
287 for (auto &c : this->clients_)
288 c->send_text_sensor_state(obj);
289}
290#endif
291
292#ifdef USE_CLIMATE
294 if (obj->is_internal())
295 return;
296 for (auto &c : this->clients_)
297 c->send_climate_state(obj);
298}
299#endif
300
301#ifdef USE_NUMBER
303 if (obj->is_internal())
304 return;
305 for (auto &c : this->clients_)
306 c->send_number_state(obj);
307}
308#endif
309
310#ifdef USE_DATETIME_DATE
312 if (obj->is_internal())
313 return;
314 for (auto &c : this->clients_)
315 c->send_date_state(obj);
316}
317#endif
318
319#ifdef USE_DATETIME_TIME
321 if (obj->is_internal())
322 return;
323 for (auto &c : this->clients_)
324 c->send_time_state(obj);
325}
326#endif
327
328#ifdef USE_DATETIME_DATETIME
330 if (obj->is_internal())
331 return;
332 for (auto &c : this->clients_)
333 c->send_datetime_state(obj);
334}
335#endif
336
337#ifdef USE_TEXT
338void APIServer::on_text_update(text::Text *obj, const std::string &state) {
339 if (obj->is_internal())
340 return;
341 for (auto &c : this->clients_)
342 c->send_text_state(obj);
343}
344#endif
345
346#ifdef USE_SELECT
347void APIServer::on_select_update(select::Select *obj, const std::string &state, size_t index) {
348 if (obj->is_internal())
349 return;
350 for (auto &c : this->clients_)
351 c->send_select_state(obj);
352}
353#endif
354
355#ifdef USE_LOCK
357 if (obj->is_internal())
358 return;
359 for (auto &c : this->clients_)
360 c->send_lock_state(obj);
361}
362#endif
363
364#ifdef USE_VALVE
366 if (obj->is_internal())
367 return;
368 for (auto &c : this->clients_)
369 c->send_valve_state(obj);
370}
371#endif
372
373#ifdef USE_MEDIA_PLAYER
375 if (obj->is_internal())
376 return;
377 for (auto &c : this->clients_)
378 c->send_media_player_state(obj);
379}
380#endif
381
382#ifdef USE_EVENT
383void APIServer::on_event(event::Event *obj, const std::string &event_type) {
384 for (auto &c : this->clients_)
385 c->send_event(obj, event_type);
386}
387#endif
388
389#ifdef USE_UPDATE
391 for (auto &c : this->clients_)
392 c->send_update_state(obj);
393}
394#endif
395
396#ifdef USE_ALARM_CONTROL_PANEL
398 if (obj->is_internal())
399 return;
400 for (auto &c : this->clients_)
401 c->send_alarm_control_panel_state(obj);
402}
403#endif
404
406
407void APIServer::set_port(uint16_t port) { this->port_ = port; }
408
409void APIServer::set_password(const std::string &password) { this->password_ = password; }
410
411void APIServer::set_batch_delay(uint32_t batch_delay) { this->batch_delay_ = batch_delay; }
412
414 for (auto &client : this->clients_) {
415 client->send_homeassistant_service_call(call);
416 }
417}
418
420 std::function<void(std::string)> f) {
422 .entity_id = std::move(entity_id),
423 .attribute = std::move(attribute),
424 .callback = std::move(f),
425 .once = false,
426 });
427}
428
429void APIServer::get_home_assistant_state(std::string entity_id, optional<std::string> attribute,
430 std::function<void(std::string)> f) {
432 .entity_id = std::move(entity_id),
433 .attribute = std::move(attribute),
434 .callback = std::move(f),
435 .once = true,
436 });
437};
438
439const std::vector<APIServer::HomeAssistantStateSubscription> &APIServer::get_state_subs() const {
440 return this->state_subs_;
441}
442
443uint16_t APIServer::get_port() const { return this->port_; }
444
445void APIServer::set_reboot_timeout(uint32_t reboot_timeout) { this->reboot_timeout_ = reboot_timeout; }
446
447#ifdef USE_API_NOISE
448bool APIServer::save_noise_psk(psk_t psk, bool make_active) {
449 auto &old_psk = this->noise_ctx_->get_psk();
450 if (std::equal(old_psk.begin(), old_psk.end(), psk.begin())) {
451 ESP_LOGW(TAG, "New PSK matches old");
452 return true;
453 }
454
455 SavedNoisePsk new_saved_psk{psk};
456 if (!this->noise_pref_.save(&new_saved_psk)) {
457 ESP_LOGW(TAG, "Failed to save Noise PSK");
458 return false;
459 }
460 // ensure it's written immediately
461 if (!global_preferences->sync()) {
462 ESP_LOGW(TAG, "Failed to sync preferences");
463 return false;
464 }
465 ESP_LOGD(TAG, "Noise PSK saved");
466 if (make_active) {
467 this->set_timeout(100, [this, psk]() {
468 ESP_LOGW(TAG, "Disconnecting all clients to reset connections");
469 this->set_noise_psk(psk);
470 for (auto &c : this->clients_) {
471 c->send_message(DisconnectRequest());
472 }
473 });
474 }
475 return true;
476}
477#endif
478
479#ifdef USE_HOMEASSISTANT_TIME
481 for (auto &client : this->clients_) {
482 if (!client->remove_ && client->is_authenticated())
483 client->send_time_request();
484 }
485}
486#endif
487
488bool APIServer::is_connected() const { return !this->clients_.empty(); }
489
491 this->shutting_down_ = true;
492
493 // Close the listening socket to prevent new connections
494 if (this->socket_) {
495 this->socket_->close();
496 this->socket_ = nullptr;
497 }
498
499 // Change batch delay to 5ms for quick flushing during shutdown
500 this->batch_delay_ = 5;
501
502 // Send disconnect requests to all connected clients
503 for (auto &c : this->clients_) {
504 if (!c->send_message(DisconnectRequest())) {
505 // If we can't send the disconnect request directly (tx_buffer full),
506 // schedule it in the batch so it will be sent with the 5ms timer
508 }
509 }
510}
511
513 // If network is disconnected, no point trying to flush buffers
514 if (!network::is_connected()) {
515 return true;
516 }
517 this->loop();
518
519 // Return true only when all clients have been torn down
520 return this->clients_.empty();
521}
522
523} // namespace api
524} // namespace esphome
525#endif
virtual void mark_failed()
Mark this component as failed.
void status_set_warning(const char *message="unspecified")
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.
Definition component.cpp:75
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:33
void trigger(Ts... x)
Inform the parent automation that the event has triggered.
Definition automation.h:96
static uint16_t try_send_disconnect_request(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single)
void set_batch_delay(uint32_t batch_delay)
void on_valve_update(valve::Valve *obj) override
std::vector< std::unique_ptr< APIConnection > > clients_
Definition api_server.h:151
void on_select_update(select::Select *obj, const std::string &state, size_t index) override
void send_homeassistant_service_call(const HomeassistantServiceResponse &call)
void set_password(const std::string &password)
void on_time_update(datetime::TimeEntity *obj) override
void on_cover_update(cover::Cover *obj) override
void on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) override
void on_sensor_update(sensor::Sensor *obj, float state) override
void on_light_update(light::LightState *obj) override
void on_media_player_update(media_player::MediaPlayer *obj) override
void set_port(uint16_t port)
void dump_config() override
void handle_disconnect(APIConnection *conn)
void set_reboot_timeout(uint32_t reboot_timeout)
bool save_noise_psk(psk_t psk, bool make_active=true)
void on_lock_update(lock::Lock *obj) override
void setup() override
void on_date_update(datetime::DateEntity *obj) 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:160
void on_number_update(number::Number *obj, float state) override
void on_text_update(text::Text *obj, const std::string &state) override
Trigger< std::string, std::string > * client_disconnected_trigger_
Definition api_server.h:157
std::vector< uint8_t > shared_write_buffer_
Definition api_server.h:153
void subscribe_home_assistant_state(std::string entity_id, optional< std::string > attribute, std::function< void(std::string)> f)
void on_climate_update(climate::Climate *obj) override
ESPPreferenceObject noise_pref_
Definition api_server.h:161
void on_text_sensor_update(text_sensor::TextSensor *obj, const std::string &state) override
void on_fan_update(fan::Fan *obj) override
std::vector< HomeAssistantStateSubscription > state_subs_
Definition api_server.h:154
void on_switch_update(switch_::Switch *obj, bool state) override
uint16_t get_port() const
void set_noise_psk(psk_t psk)
Definition api_server.h:51
void on_datetime_update(datetime::DateTimeEntity *obj) override
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:146
void on_shutdown() override
void on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) override
static constexpr uint16_t MESSAGE_TYPE
Definition api_pb2.h:355
Base class for all binary_sensor-type classes.
ClimateDevice - This is the base class for all climate integrations.
Definition climate.h:168
Base class for all cover devices.
Definition cover.h:111
void add_image_callback(std::function< void(std::shared_ptr< CameraImage >)> &&callback)
This class represents the communication layer between the front-end MQTT layer and the hardware outpu...
Definition light_state.h:63
Base class for all locks.
Definition lock.h:103
void add_on_log_callback(std::function< void(int, const char *, const char *)> &&callback)
Register a callback that will be called for every log message sent.
Definition logger.cpp:204
Base-class for all numbers.
Definition number.h:39
Base-class for all selects.
Definition select.h:31
Base-class for all sensors.
Definition sensor.h:62
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
std::array< uint8_t, 32 > psk_t
ESP32Camera * global_esp32_camera
Logger * global_logger
Definition logger.cpp:242
std::string get_use_address()
Get the active network hostname.
Definition util.cpp:65
bool is_connected()
Return whether the node is connected to the network (through wifi, eth, ...)
Definition util.cpp:19
const float AFTER_WIFI
For components that should be initialized after WiFi is connected.
Definition component.cpp:27
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
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
ESPPreferences * global_preferences
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:28
Application App
Global storage of Application pointer - only one Application can exist.
uint16_t length
Definition tt21100.cpp:0