ESPHome 2025.8.0-dev
Loading...
Searching...
No Matches
api_connection.cpp
Go to the documentation of this file.
1#include "api_connection.h"
2#ifdef USE_API
3#ifdef USE_API_NOISE
5#endif
6#ifdef USE_API_PLAINTEXT
8#endif
9#include <cerrno>
10#include <cinttypes>
11#include <utility>
12#include <functional>
13#include <limits>
17#include "esphome/core/hal.h"
18#include "esphome/core/log.h"
20
21#ifdef USE_DEEP_SLEEP
23#endif
24#ifdef USE_HOMEASSISTANT_TIME
26#endif
27#ifdef USE_BLUETOOTH_PROXY
29#endif
30#ifdef USE_VOICE_ASSISTANT
32#endif
33
34namespace esphome::api {
35
36// Read a maximum of 5 messages per loop iteration to prevent starving other components.
37// This is a balance between API responsiveness and allowing other components to run.
38// Since each message could contain multiple protobuf messages when using packet batching,
39// this limits the number of messages processed, not the number of TCP packets.
40static constexpr uint8_t MAX_MESSAGES_PER_LOOP = 5;
41static constexpr uint8_t MAX_PING_RETRIES = 60;
42static constexpr uint16_t PING_RETRY_INTERVAL = 1000;
43static constexpr uint32_t KEEPALIVE_DISCONNECT_TIMEOUT = (KEEPALIVE_TIMEOUT_MS * 5) / 2;
44
45static const char *const TAG = "api.connection";
46#ifdef USE_CAMERA
47static const int CAMERA_STOP_STREAM = 5000;
48#endif
49
50#ifdef USE_DEVICES
51// Helper macro for entity command handlers - gets entity by key and device_id, returns if not found, and creates call
52// object
53#define ENTITY_COMMAND_MAKE_CALL(entity_type, entity_var, getter_name) \
54 entity_type *entity_var = App.get_##getter_name##_by_key(msg.key, msg.device_id); \
55 if ((entity_var) == nullptr) \
56 return; \
57 auto call = (entity_var)->make_call();
58
59// Helper macro for entity command handlers that don't use make_call() - gets entity by key and device_id and returns if
60// not found
61#define ENTITY_COMMAND_GET(entity_type, entity_var, getter_name) \
62 entity_type *entity_var = App.get_##getter_name##_by_key(msg.key, msg.device_id); \
63 if ((entity_var) == nullptr) \
64 return;
65#else // No device support, use simpler macros
66// Helper macro for entity command handlers - gets entity by key, returns if not found, and creates call
67// object
68#define ENTITY_COMMAND_MAKE_CALL(entity_type, entity_var, getter_name) \
69 entity_type *entity_var = App.get_##getter_name##_by_key(msg.key); \
70 if ((entity_var) == nullptr) \
71 return; \
72 auto call = (entity_var)->make_call();
73
74// Helper macro for entity command handlers that don't use make_call() - gets entity by key and returns if
75// not found
76#define ENTITY_COMMAND_GET(entity_type, entity_var, getter_name) \
77 entity_type *entity_var = App.get_##getter_name##_by_key(msg.key); \
78 if ((entity_var) == nullptr) \
79 return;
80#endif // USE_DEVICES
81
82APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *parent)
83 : parent_(parent), initial_state_iterator_(this), list_entities_iterator_(this) {
84#if defined(USE_API_PLAINTEXT) && defined(USE_API_NOISE)
85 auto noise_ctx = parent->get_noise_ctx();
86 if (noise_ctx->has_psk()) {
87 this->helper_ =
88 std::unique_ptr<APIFrameHelper>{new APINoiseFrameHelper(std::move(sock), noise_ctx, &this->client_info_)};
89 } else {
90 this->helper_ = std::unique_ptr<APIFrameHelper>{new APIPlaintextFrameHelper(std::move(sock), &this->client_info_)};
91 }
92#elif defined(USE_API_PLAINTEXT)
93 this->helper_ = std::unique_ptr<APIFrameHelper>{new APIPlaintextFrameHelper(std::move(sock), &this->client_info_)};
94#elif defined(USE_API_NOISE)
95 this->helper_ = std::unique_ptr<APIFrameHelper>{
96 new APINoiseFrameHelper(std::move(sock), parent->get_noise_ctx(), &this->client_info_)};
97#else
98#error "No frame helper defined"
99#endif
100#ifdef USE_CAMERA
101 if (camera::Camera::instance() != nullptr) {
102 this->image_reader_ = std::unique_ptr<camera::CameraImageReader>{camera::Camera::instance()->create_image_reader()};
103 }
104#endif
105}
106
107uint32_t APIConnection::get_batch_delay_ms_() const { return this->parent_->get_batch_delay(); }
108
109void APIConnection::start() {
110 this->last_traffic_ = App.get_loop_component_start_time();
111
112 APIError err = this->helper_->init();
113 if (err != APIError::OK) {
114 on_fatal_error();
115 this->log_warning_("Helper init failed", err);
116 return;
117 }
118 this->client_info_.peername = helper_->getpeername();
119 this->client_info_.name = this->client_info_.peername;
120}
121
122APIConnection::~APIConnection() {
123#ifdef USE_BLUETOOTH_PROXY
124 if (bluetooth_proxy::global_bluetooth_proxy->get_api_connection() == this) {
126 }
127#endif
128#ifdef USE_VOICE_ASSISTANT
129 if (voice_assistant::global_voice_assistant->get_api_connection() == this) {
131 }
132#endif
133}
134
135void APIConnection::loop() {
136 if (this->flags_.next_close) {
137 // requested a disconnect
138 this->helper_->close();
139 this->flags_.remove = true;
140 return;
141 }
142
143 APIError err = this->helper_->loop();
144 if (err != APIError::OK) {
145 on_fatal_error();
146 this->log_socket_operation_failed_(err);
147 return;
148 }
149
150 const uint32_t now = App.get_loop_component_start_time();
151 // Check if socket has data ready before attempting to read
152 if (this->helper_->is_socket_ready()) {
153 // Read up to MAX_MESSAGES_PER_LOOP messages per loop to improve throughput
154 for (uint8_t message_count = 0; message_count < MAX_MESSAGES_PER_LOOP; message_count++) {
155 ReadPacketBuffer buffer;
156 err = this->helper_->read_packet(&buffer);
157 if (err == APIError::WOULD_BLOCK) {
158 // No more data available
159 break;
160 } else if (err != APIError::OK) {
161 on_fatal_error();
162 this->log_warning_("Reading failed", err);
163 return;
164 } else {
165 this->last_traffic_ = now;
166 // read a packet
167 if (buffer.data_len > 0) {
168 this->read_message(buffer.data_len, buffer.type, &buffer.container[buffer.data_offset]);
169 } else {
170 this->read_message(0, buffer.type, nullptr);
171 }
172 if (this->flags_.remove)
173 return;
174 }
175 }
176 }
177
178 // Process deferred batch if scheduled and timer has expired
179 if (this->flags_.batch_scheduled && now - this->deferred_batch_.batch_start_time >= this->get_batch_delay_ms_()) {
180 this->process_batch_();
181 }
182
183 if (!this->list_entities_iterator_.completed()) {
184 this->process_iterator_batch_(this->list_entities_iterator_);
185 } else if (!this->initial_state_iterator_.completed()) {
186 this->process_iterator_batch_(this->initial_state_iterator_);
187
188 // If we've completed initial states, process any remaining and clear the flag
189 if (this->initial_state_iterator_.completed()) {
190 // Process any remaining batched messages immediately
191 if (!this->deferred_batch_.empty()) {
192 this->process_batch_();
193 }
194 // Now that everything is sent, enable immediate sending for future state changes
195 this->flags_.should_try_send_immediately = true;
196 }
197 }
198
199 if (this->flags_.sent_ping) {
200 // Disconnect if not responded within 2.5*keepalive
201 if (now - this->last_traffic_ > KEEPALIVE_DISCONNECT_TIMEOUT) {
202 on_fatal_error();
203 ESP_LOGW(TAG, "%s is unresponsive; disconnecting", this->get_client_combined_info().c_str());
204 }
205 } else if (now - this->last_traffic_ > KEEPALIVE_TIMEOUT_MS && !this->flags_.remove) {
206 // Only send ping if we're not disconnecting
207 ESP_LOGVV(TAG, "Sending keepalive PING");
208 PingRequest req;
209 this->flags_.sent_ping = this->send_message(req, PingRequest::MESSAGE_TYPE);
210 if (!this->flags_.sent_ping) {
211 // If we can't send the ping request directly (tx_buffer full),
212 // schedule it at the front of the batch so it will be sent with priority
213 ESP_LOGW(TAG, "Buffer full, ping queued");
214 this->schedule_message_front_(nullptr, &APIConnection::try_send_ping_request, PingRequest::MESSAGE_TYPE,
215 PingRequest::ESTIMATED_SIZE);
216 this->flags_.sent_ping = true; // Mark as sent to avoid scheduling multiple pings
217 }
218 }
219
220#ifdef USE_CAMERA
221 if (this->image_reader_ && this->image_reader_->available() && this->helper_->can_write_without_blocking()) {
222 uint32_t to_send = std::min((size_t) MAX_BATCH_PACKET_SIZE, this->image_reader_->available());
223 bool done = this->image_reader_->available() == to_send;
224
227 msg.set_data(this->image_reader_->peek_data_buffer(), to_send);
228 msg.done = done;
229#ifdef USE_DEVICES
231#endif
232
233 if (this->send_message_(msg, CameraImageResponse::MESSAGE_TYPE)) {
234 this->image_reader_->consume_data(to_send);
235 if (done) {
236 this->image_reader_->return_image();
237 }
238 }
239 }
240#endif
241
242#ifdef USE_API_HOMEASSISTANT_STATES
243 if (state_subs_at_ >= 0) {
244 this->process_state_subscriptions_();
245 }
246#endif
247}
248
249bool APIConnection::send_disconnect_response(const DisconnectRequest &msg) {
250 // remote initiated disconnect_client
251 // don't close yet, we still need to send the disconnect response
252 // close will happen on next loop
253 ESP_LOGD(TAG, "%s disconnected", this->get_client_combined_info().c_str());
254 this->flags_.next_close = true;
256 return this->send_message(resp, DisconnectResponse::MESSAGE_TYPE);
257}
258void APIConnection::on_disconnect_response(const DisconnectResponse &value) {
259 this->helper_->close();
260 this->flags_.remove = true;
261}
262
263// Encodes a message to the buffer and returns the total number of bytes used,
264// including header and footer overhead. Returns 0 if the message doesn't fit.
265uint16_t APIConnection::encode_message_to_buffer(ProtoMessage &msg, uint8_t message_type, APIConnection *conn,
266 uint32_t remaining_size, bool is_single) {
267#ifdef HAS_PROTO_MESSAGE_DUMP
268 // If in log-only mode, just log and return
269 if (conn->flags_.log_only_mode) {
270 conn->log_send_message_(msg.message_name(), msg.dump());
271 return 1; // Return non-zero to indicate "success" for logging
272 }
273#endif
274
275 // Calculate size
276 ProtoSize size_calc;
277 msg.calculate_size(size_calc);
278 uint32_t calculated_size = size_calc.get_size();
279
280 // Cache frame sizes to avoid repeated virtual calls
281 const uint8_t header_padding = conn->helper_->frame_header_padding();
282 const uint8_t footer_size = conn->helper_->frame_footer_size();
283
284 // Calculate total size with padding for buffer allocation
285 size_t total_calculated_size = calculated_size + header_padding + footer_size;
286
287 // Check if it fits
288 if (total_calculated_size > remaining_size) {
289 return 0; // Doesn't fit
290 }
291
292 // Allocate buffer space - pass payload size, allocation functions add header/footer space
293 ProtoWriteBuffer buffer = is_single ? conn->allocate_single_message_buffer(calculated_size)
294 : conn->allocate_batch_message_buffer(calculated_size);
295
296 // Get buffer size after allocation (which includes header padding)
297 std::vector<uint8_t> &shared_buf = conn->parent_->get_shared_buffer_ref();
298 size_t size_before_encode = shared_buf.size();
299
300 // Encode directly into buffer
301 msg.encode(buffer);
302
303 // Calculate actual encoded size (not including header that was already added)
304 size_t actual_payload_size = shared_buf.size() - size_before_encode;
305
306 // Return actual total size (header + actual payload + footer)
307 size_t actual_total_size = header_padding + actual_payload_size + footer_size;
308
309 // Verify that calculate_size() returned the correct value
310 assert(calculated_size == actual_payload_size);
311 return static_cast<uint16_t>(actual_total_size);
312}
313
314#ifdef USE_BINARY_SENSOR
315bool APIConnection::send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor) {
316 return this->send_message_smart_(binary_sensor, &APIConnection::try_send_binary_sensor_state,
317 BinarySensorStateResponse::MESSAGE_TYPE, BinarySensorStateResponse::ESTIMATED_SIZE);
318}
319
320uint16_t APIConnection::try_send_binary_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
321 bool is_single) {
322 auto *binary_sensor = static_cast<binary_sensor::BinarySensor *>(entity);
324 resp.state = binary_sensor->state;
325 resp.missing_state = !binary_sensor->has_state();
326 return fill_and_encode_entity_state(binary_sensor, resp, BinarySensorStateResponse::MESSAGE_TYPE, conn,
327 remaining_size, is_single);
328}
329
330uint16_t APIConnection::try_send_binary_sensor_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
331 bool is_single) {
332 auto *binary_sensor = static_cast<binary_sensor::BinarySensor *>(entity);
334 msg.set_device_class(binary_sensor->get_device_class_ref());
335 msg.is_status_binary_sensor = binary_sensor->is_status_binary_sensor();
336 return fill_and_encode_entity_info(binary_sensor, msg, ListEntitiesBinarySensorResponse::MESSAGE_TYPE, conn,
337 remaining_size, is_single);
338}
339#endif
340
341#ifdef USE_COVER
342bool APIConnection::send_cover_state(cover::Cover *cover) {
343 return this->send_message_smart_(cover, &APIConnection::try_send_cover_state, CoverStateResponse::MESSAGE_TYPE,
344 CoverStateResponse::ESTIMATED_SIZE);
345}
346uint16_t APIConnection::try_send_cover_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
347 bool is_single) {
348 auto *cover = static_cast<cover::Cover *>(entity);
350 auto traits = cover->get_traits();
351 msg.position = cover->position;
352 if (traits.get_supports_tilt())
353 msg.tilt = cover->tilt;
354 msg.current_operation = static_cast<enums::CoverOperation>(cover->current_operation);
355 return fill_and_encode_entity_state(cover, msg, CoverStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
356}
357uint16_t APIConnection::try_send_cover_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
358 bool is_single) {
359 auto *cover = static_cast<cover::Cover *>(entity);
361 auto traits = cover->get_traits();
362 msg.assumed_state = traits.get_is_assumed_state();
363 msg.supports_position = traits.get_supports_position();
364 msg.supports_tilt = traits.get_supports_tilt();
365 msg.supports_stop = traits.get_supports_stop();
366 msg.set_device_class(cover->get_device_class_ref());
367 return fill_and_encode_entity_info(cover, msg, ListEntitiesCoverResponse::MESSAGE_TYPE, conn, remaining_size,
368 is_single);
369}
370void APIConnection::cover_command(const CoverCommandRequest &msg) {
371 ENTITY_COMMAND_MAKE_CALL(cover::Cover, cover, cover)
372 if (msg.has_position)
373 call.set_position(msg.position);
374 if (msg.has_tilt)
375 call.set_tilt(msg.tilt);
376 if (msg.stop)
377 call.set_command_stop();
378 call.perform();
379}
380#endif
381
382#ifdef USE_FAN
383bool APIConnection::send_fan_state(fan::Fan *fan) {
384 return this->send_message_smart_(fan, &APIConnection::try_send_fan_state, FanStateResponse::MESSAGE_TYPE,
385 FanStateResponse::ESTIMATED_SIZE);
386}
387uint16_t APIConnection::try_send_fan_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
388 bool is_single) {
389 auto *fan = static_cast<fan::Fan *>(entity);
391 auto traits = fan->get_traits();
392 msg.state = fan->state;
393 if (traits.supports_oscillation())
394 msg.oscillating = fan->oscillating;
395 if (traits.supports_speed()) {
396 msg.speed_level = fan->speed;
397 }
398 if (traits.supports_direction())
399 msg.direction = static_cast<enums::FanDirection>(fan->direction);
400 if (traits.supports_preset_modes())
401 msg.set_preset_mode(StringRef(fan->preset_mode));
402 return fill_and_encode_entity_state(fan, msg, FanStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
403}
404uint16_t APIConnection::try_send_fan_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
405 bool is_single) {
406 auto *fan = static_cast<fan::Fan *>(entity);
408 auto traits = fan->get_traits();
409 msg.supports_oscillation = traits.supports_oscillation();
410 msg.supports_speed = traits.supports_speed();
411 msg.supports_direction = traits.supports_direction();
412 msg.supported_speed_count = traits.supported_speed_count();
413 msg.supported_preset_modes = &traits.supported_preset_modes_for_api_();
414 return fill_and_encode_entity_info(fan, msg, ListEntitiesFanResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
415}
416void APIConnection::fan_command(const FanCommandRequest &msg) {
417 ENTITY_COMMAND_MAKE_CALL(fan::Fan, fan, fan)
418 if (msg.has_state)
419 call.set_state(msg.state);
420 if (msg.has_oscillating)
421 call.set_oscillating(msg.oscillating);
422 if (msg.has_speed_level) {
423 // Prefer level
424 call.set_speed(msg.speed_level);
425 }
426 if (msg.has_direction)
427 call.set_direction(static_cast<fan::FanDirection>(msg.direction));
428 if (msg.has_preset_mode)
429 call.set_preset_mode(msg.preset_mode);
430 call.perform();
431}
432#endif
433
434#ifdef USE_LIGHT
435bool APIConnection::send_light_state(light::LightState *light) {
436 return this->send_message_smart_(light, &APIConnection::try_send_light_state, LightStateResponse::MESSAGE_TYPE,
437 LightStateResponse::ESTIMATED_SIZE);
438}
439uint16_t APIConnection::try_send_light_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
440 bool is_single) {
441 auto *light = static_cast<light::LightState *>(entity);
443 auto traits = light->get_traits();
444 auto values = light->remote_values;
445 auto color_mode = values.get_color_mode();
446 resp.state = values.is_on();
447 resp.color_mode = static_cast<enums::ColorMode>(color_mode);
448 resp.brightness = values.get_brightness();
449 resp.color_brightness = values.get_color_brightness();
450 resp.red = values.get_red();
451 resp.green = values.get_green();
452 resp.blue = values.get_blue();
453 resp.white = values.get_white();
454 resp.color_temperature = values.get_color_temperature();
455 resp.cold_white = values.get_cold_white();
456 resp.warm_white = values.get_warm_white();
457 if (light->supports_effects()) {
458 // get_effect_name() returns temporary std::string - must store it
459 std::string effect_name = light->get_effect_name();
460 resp.set_effect(StringRef(effect_name));
461 }
462 return fill_and_encode_entity_state(light, resp, LightStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
463}
464uint16_t APIConnection::try_send_light_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
465 bool is_single) {
466 auto *light = static_cast<light::LightState *>(entity);
468 auto traits = light->get_traits();
469 msg.supported_color_modes = &traits.get_supported_color_modes_for_api_();
470 if (traits.supports_color_capability(light::ColorCapability::COLOR_TEMPERATURE) ||
471 traits.supports_color_capability(light::ColorCapability::COLD_WARM_WHITE)) {
472 msg.min_mireds = traits.get_min_mireds();
473 msg.max_mireds = traits.get_max_mireds();
474 }
475 if (light->supports_effects()) {
476 msg.effects.emplace_back("None");
477 for (auto *effect : light->get_effects()) {
478 msg.effects.push_back(effect->get_name());
479 }
480 }
481 return fill_and_encode_entity_info(light, msg, ListEntitiesLightResponse::MESSAGE_TYPE, conn, remaining_size,
482 is_single);
483}
484void APIConnection::light_command(const LightCommandRequest &msg) {
485 ENTITY_COMMAND_MAKE_CALL(light::LightState, light, light)
486 if (msg.has_state)
487 call.set_state(msg.state);
488 if (msg.has_brightness)
489 call.set_brightness(msg.brightness);
490 if (msg.has_color_mode)
491 call.set_color_mode(static_cast<light::ColorMode>(msg.color_mode));
492 if (msg.has_color_brightness)
493 call.set_color_brightness(msg.color_brightness);
494 if (msg.has_rgb) {
495 call.set_red(msg.red);
496 call.set_green(msg.green);
497 call.set_blue(msg.blue);
498 }
499 if (msg.has_white)
500 call.set_white(msg.white);
501 if (msg.has_color_temperature)
502 call.set_color_temperature(msg.color_temperature);
503 if (msg.has_cold_white)
504 call.set_cold_white(msg.cold_white);
505 if (msg.has_warm_white)
506 call.set_warm_white(msg.warm_white);
507 if (msg.has_transition_length)
508 call.set_transition_length(msg.transition_length);
509 if (msg.has_flash_length)
510 call.set_flash_length(msg.flash_length);
511 if (msg.has_effect)
512 call.set_effect(msg.effect);
513 call.perform();
514}
515#endif
516
517#ifdef USE_SENSOR
518bool APIConnection::send_sensor_state(sensor::Sensor *sensor) {
519 return this->send_message_smart_(sensor, &APIConnection::try_send_sensor_state, SensorStateResponse::MESSAGE_TYPE,
520 SensorStateResponse::ESTIMATED_SIZE);
521}
522
523uint16_t APIConnection::try_send_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
524 bool is_single) {
525 auto *sensor = static_cast<sensor::Sensor *>(entity);
527 resp.state = sensor->state;
528 resp.missing_state = !sensor->has_state();
529 return fill_and_encode_entity_state(sensor, resp, SensorStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
530}
531
532uint16_t APIConnection::try_send_sensor_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
533 bool is_single) {
534 auto *sensor = static_cast<sensor::Sensor *>(entity);
536 msg.set_unit_of_measurement(sensor->get_unit_of_measurement_ref());
537 msg.accuracy_decimals = sensor->get_accuracy_decimals();
538 msg.force_update = sensor->get_force_update();
539 msg.set_device_class(sensor->get_device_class_ref());
540 msg.state_class = static_cast<enums::SensorStateClass>(sensor->get_state_class());
541 return fill_and_encode_entity_info(sensor, msg, ListEntitiesSensorResponse::MESSAGE_TYPE, conn, remaining_size,
542 is_single);
543}
544#endif
545
546#ifdef USE_SWITCH
547bool APIConnection::send_switch_state(switch_::Switch *a_switch) {
548 return this->send_message_smart_(a_switch, &APIConnection::try_send_switch_state, SwitchStateResponse::MESSAGE_TYPE,
549 SwitchStateResponse::ESTIMATED_SIZE);
550}
551
552uint16_t APIConnection::try_send_switch_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
553 bool is_single) {
554 auto *a_switch = static_cast<switch_::Switch *>(entity);
556 resp.state = a_switch->state;
557 return fill_and_encode_entity_state(a_switch, resp, SwitchStateResponse::MESSAGE_TYPE, conn, remaining_size,
558 is_single);
559}
560
561uint16_t APIConnection::try_send_switch_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
562 bool is_single) {
563 auto *a_switch = static_cast<switch_::Switch *>(entity);
565 msg.assumed_state = a_switch->assumed_state();
566 msg.set_device_class(a_switch->get_device_class_ref());
567 return fill_and_encode_entity_info(a_switch, msg, ListEntitiesSwitchResponse::MESSAGE_TYPE, conn, remaining_size,
568 is_single);
569}
570void APIConnection::switch_command(const SwitchCommandRequest &msg) {
571 ENTITY_COMMAND_GET(switch_::Switch, a_switch, switch)
572
573 if (msg.state) {
574 a_switch->turn_on();
575 } else {
576 a_switch->turn_off();
577 }
578}
579#endif
580
581#ifdef USE_TEXT_SENSOR
582bool APIConnection::send_text_sensor_state(text_sensor::TextSensor *text_sensor) {
583 return this->send_message_smart_(text_sensor, &APIConnection::try_send_text_sensor_state,
584 TextSensorStateResponse::MESSAGE_TYPE, TextSensorStateResponse::ESTIMATED_SIZE);
585}
586
587uint16_t APIConnection::try_send_text_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
588 bool is_single) {
589 auto *text_sensor = static_cast<text_sensor::TextSensor *>(entity);
591 resp.set_state(StringRef(text_sensor->state));
592 resp.missing_state = !text_sensor->has_state();
593 return fill_and_encode_entity_state(text_sensor, resp, TextSensorStateResponse::MESSAGE_TYPE, conn, remaining_size,
594 is_single);
595}
596uint16_t APIConnection::try_send_text_sensor_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
597 bool is_single) {
598 auto *text_sensor = static_cast<text_sensor::TextSensor *>(entity);
600 msg.set_device_class(text_sensor->get_device_class_ref());
601 return fill_and_encode_entity_info(text_sensor, msg, ListEntitiesTextSensorResponse::MESSAGE_TYPE, conn,
602 remaining_size, is_single);
603}
604#endif
605
606#ifdef USE_CLIMATE
607bool APIConnection::send_climate_state(climate::Climate *climate) {
608 return this->send_message_smart_(climate, &APIConnection::try_send_climate_state, ClimateStateResponse::MESSAGE_TYPE,
609 ClimateStateResponse::ESTIMATED_SIZE);
610}
611uint16_t APIConnection::try_send_climate_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
612 bool is_single) {
613 auto *climate = static_cast<climate::Climate *>(entity);
615 auto traits = climate->get_traits();
616 resp.mode = static_cast<enums::ClimateMode>(climate->mode);
617 resp.action = static_cast<enums::ClimateAction>(climate->action);
618 if (traits.get_supports_current_temperature())
619 resp.current_temperature = climate->current_temperature;
620 if (traits.get_supports_two_point_target_temperature()) {
621 resp.target_temperature_low = climate->target_temperature_low;
622 resp.target_temperature_high = climate->target_temperature_high;
623 } else {
624 resp.target_temperature = climate->target_temperature;
625 }
626 if (traits.get_supports_fan_modes() && climate->fan_mode.has_value())
627 resp.fan_mode = static_cast<enums::ClimateFanMode>(climate->fan_mode.value());
628 if (!traits.get_supported_custom_fan_modes().empty() && climate->custom_fan_mode.has_value()) {
629 resp.set_custom_fan_mode(StringRef(climate->custom_fan_mode.value()));
630 }
631 if (traits.get_supports_presets() && climate->preset.has_value()) {
632 resp.preset = static_cast<enums::ClimatePreset>(climate->preset.value());
633 }
634 if (!traits.get_supported_custom_presets().empty() && climate->custom_preset.has_value()) {
635 resp.set_custom_preset(StringRef(climate->custom_preset.value()));
636 }
637 if (traits.get_supports_swing_modes())
638 resp.swing_mode = static_cast<enums::ClimateSwingMode>(climate->swing_mode);
639 if (traits.get_supports_current_humidity())
640 resp.current_humidity = climate->current_humidity;
641 if (traits.get_supports_target_humidity())
642 resp.target_humidity = climate->target_humidity;
643 return fill_and_encode_entity_state(climate, resp, ClimateStateResponse::MESSAGE_TYPE, conn, remaining_size,
644 is_single);
645}
646uint16_t APIConnection::try_send_climate_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
647 bool is_single) {
648 auto *climate = static_cast<climate::Climate *>(entity);
650 auto traits = climate->get_traits();
651 msg.supports_current_temperature = traits.get_supports_current_temperature();
652 msg.supports_current_humidity = traits.get_supports_current_humidity();
653 msg.supports_two_point_target_temperature = traits.get_supports_two_point_target_temperature();
654 msg.supports_target_humidity = traits.get_supports_target_humidity();
655 msg.supported_modes = &traits.get_supported_modes_for_api_();
656 msg.visual_min_temperature = traits.get_visual_min_temperature();
657 msg.visual_max_temperature = traits.get_visual_max_temperature();
658 msg.visual_target_temperature_step = traits.get_visual_target_temperature_step();
659 msg.visual_current_temperature_step = traits.get_visual_current_temperature_step();
660 msg.visual_min_humidity = traits.get_visual_min_humidity();
661 msg.visual_max_humidity = traits.get_visual_max_humidity();
662 msg.supports_action = traits.get_supports_action();
663 msg.supported_fan_modes = &traits.get_supported_fan_modes_for_api_();
664 msg.supported_custom_fan_modes = &traits.get_supported_custom_fan_modes_for_api_();
665 msg.supported_presets = &traits.get_supported_presets_for_api_();
666 msg.supported_custom_presets = &traits.get_supported_custom_presets_for_api_();
667 msg.supported_swing_modes = &traits.get_supported_swing_modes_for_api_();
668 return fill_and_encode_entity_info(climate, msg, ListEntitiesClimateResponse::MESSAGE_TYPE, conn, remaining_size,
669 is_single);
670}
671void APIConnection::climate_command(const ClimateCommandRequest &msg) {
672 ENTITY_COMMAND_MAKE_CALL(climate::Climate, climate, climate)
673 if (msg.has_mode)
674 call.set_mode(static_cast<climate::ClimateMode>(msg.mode));
676 call.set_target_temperature(msg.target_temperature);
678 call.set_target_temperature_low(msg.target_temperature_low);
680 call.set_target_temperature_high(msg.target_temperature_high);
681 if (msg.has_target_humidity)
682 call.set_target_humidity(msg.target_humidity);
683 if (msg.has_fan_mode)
684 call.set_fan_mode(static_cast<climate::ClimateFanMode>(msg.fan_mode));
685 if (msg.has_custom_fan_mode)
686 call.set_fan_mode(msg.custom_fan_mode);
687 if (msg.has_preset)
688 call.set_preset(static_cast<climate::ClimatePreset>(msg.preset));
689 if (msg.has_custom_preset)
690 call.set_preset(msg.custom_preset);
691 if (msg.has_swing_mode)
692 call.set_swing_mode(static_cast<climate::ClimateSwingMode>(msg.swing_mode));
693 call.perform();
694}
695#endif
696
697#ifdef USE_NUMBER
698bool APIConnection::send_number_state(number::Number *number) {
699 return this->send_message_smart_(number, &APIConnection::try_send_number_state, NumberStateResponse::MESSAGE_TYPE,
700 NumberStateResponse::ESTIMATED_SIZE);
701}
702
703uint16_t APIConnection::try_send_number_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
704 bool is_single) {
705 auto *number = static_cast<number::Number *>(entity);
707 resp.state = number->state;
708 resp.missing_state = !number->has_state();
709 return fill_and_encode_entity_state(number, resp, NumberStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
710}
711
712uint16_t APIConnection::try_send_number_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
713 bool is_single) {
714 auto *number = static_cast<number::Number *>(entity);
716 msg.set_unit_of_measurement(number->traits.get_unit_of_measurement_ref());
717 msg.mode = static_cast<enums::NumberMode>(number->traits.get_mode());
718 msg.set_device_class(number->traits.get_device_class_ref());
719 msg.min_value = number->traits.get_min_value();
720 msg.max_value = number->traits.get_max_value();
721 msg.step = number->traits.get_step();
722 return fill_and_encode_entity_info(number, msg, ListEntitiesNumberResponse::MESSAGE_TYPE, conn, remaining_size,
723 is_single);
724}
725void APIConnection::number_command(const NumberCommandRequest &msg) {
726 ENTITY_COMMAND_MAKE_CALL(number::Number, number, number)
727 call.set_value(msg.state);
728 call.perform();
729}
730#endif
731
732#ifdef USE_DATETIME_DATE
733bool APIConnection::send_date_state(datetime::DateEntity *date) {
734 return this->send_message_smart_(date, &APIConnection::try_send_date_state, DateStateResponse::MESSAGE_TYPE,
735 DateStateResponse::ESTIMATED_SIZE);
736}
737uint16_t APIConnection::try_send_date_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
738 bool is_single) {
739 auto *date = static_cast<datetime::DateEntity *>(entity);
741 resp.missing_state = !date->has_state();
742 resp.year = date->year;
743 resp.month = date->month;
744 resp.day = date->day;
745 return fill_and_encode_entity_state(date, resp, DateStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
746}
747uint16_t APIConnection::try_send_date_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
748 bool is_single) {
749 auto *date = static_cast<datetime::DateEntity *>(entity);
751 return fill_and_encode_entity_info(date, msg, ListEntitiesDateResponse::MESSAGE_TYPE, conn, remaining_size,
752 is_single);
753}
754void APIConnection::date_command(const DateCommandRequest &msg) {
755 ENTITY_COMMAND_MAKE_CALL(datetime::DateEntity, date, date)
756 call.set_date(msg.year, msg.month, msg.day);
757 call.perform();
758}
759#endif
760
761#ifdef USE_DATETIME_TIME
762bool APIConnection::send_time_state(datetime::TimeEntity *time) {
763 return this->send_message_smart_(time, &APIConnection::try_send_time_state, TimeStateResponse::MESSAGE_TYPE,
764 TimeStateResponse::ESTIMATED_SIZE);
765}
766uint16_t APIConnection::try_send_time_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
767 bool is_single) {
768 auto *time = static_cast<datetime::TimeEntity *>(entity);
770 resp.missing_state = !time->has_state();
771 resp.hour = time->hour;
772 resp.minute = time->minute;
773 resp.second = time->second;
774 return fill_and_encode_entity_state(time, resp, TimeStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
775}
776uint16_t APIConnection::try_send_time_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
777 bool is_single) {
778 auto *time = static_cast<datetime::TimeEntity *>(entity);
780 return fill_and_encode_entity_info(time, msg, ListEntitiesTimeResponse::MESSAGE_TYPE, conn, remaining_size,
781 is_single);
782}
783void APIConnection::time_command(const TimeCommandRequest &msg) {
784 ENTITY_COMMAND_MAKE_CALL(datetime::TimeEntity, time, time)
785 call.set_time(msg.hour, msg.minute, msg.second);
786 call.perform();
787}
788#endif
789
790#ifdef USE_DATETIME_DATETIME
791bool APIConnection::send_datetime_state(datetime::DateTimeEntity *datetime) {
792 return this->send_message_smart_(datetime, &APIConnection::try_send_datetime_state,
793 DateTimeStateResponse::MESSAGE_TYPE, DateTimeStateResponse::ESTIMATED_SIZE);
794}
795uint16_t APIConnection::try_send_datetime_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
796 bool is_single) {
797 auto *datetime = static_cast<datetime::DateTimeEntity *>(entity);
799 resp.missing_state = !datetime->has_state();
800 if (datetime->has_state()) {
801 ESPTime state = datetime->state_as_esptime();
802 resp.epoch_seconds = state.timestamp;
803 }
804 return fill_and_encode_entity_state(datetime, resp, DateTimeStateResponse::MESSAGE_TYPE, conn, remaining_size,
805 is_single);
806}
807uint16_t APIConnection::try_send_datetime_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
808 bool is_single) {
809 auto *datetime = static_cast<datetime::DateTimeEntity *>(entity);
811 return fill_and_encode_entity_info(datetime, msg, ListEntitiesDateTimeResponse::MESSAGE_TYPE, conn, remaining_size,
812 is_single);
813}
814void APIConnection::datetime_command(const DateTimeCommandRequest &msg) {
815 ENTITY_COMMAND_MAKE_CALL(datetime::DateTimeEntity, datetime, datetime)
816 call.set_datetime(msg.epoch_seconds);
817 call.perform();
818}
819#endif
820
821#ifdef USE_TEXT
822bool APIConnection::send_text_state(text::Text *text) {
823 return this->send_message_smart_(text, &APIConnection::try_send_text_state, TextStateResponse::MESSAGE_TYPE,
824 TextStateResponse::ESTIMATED_SIZE);
825}
826
827uint16_t APIConnection::try_send_text_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
828 bool is_single) {
829 auto *text = static_cast<text::Text *>(entity);
831 resp.set_state(StringRef(text->state));
832 resp.missing_state = !text->has_state();
833 return fill_and_encode_entity_state(text, resp, TextStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
834}
835
836uint16_t APIConnection::try_send_text_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
837 bool is_single) {
838 auto *text = static_cast<text::Text *>(entity);
840 msg.mode = static_cast<enums::TextMode>(text->traits.get_mode());
841 msg.min_length = text->traits.get_min_length();
842 msg.max_length = text->traits.get_max_length();
843 msg.set_pattern(text->traits.get_pattern_ref());
844 return fill_and_encode_entity_info(text, msg, ListEntitiesTextResponse::MESSAGE_TYPE, conn, remaining_size,
845 is_single);
846}
847void APIConnection::text_command(const TextCommandRequest &msg) {
848 ENTITY_COMMAND_MAKE_CALL(text::Text, text, text)
849 call.set_value(msg.state);
850 call.perform();
851}
852#endif
853
854#ifdef USE_SELECT
855bool APIConnection::send_select_state(select::Select *select) {
856 return this->send_message_smart_(select, &APIConnection::try_send_select_state, SelectStateResponse::MESSAGE_TYPE,
857 SelectStateResponse::ESTIMATED_SIZE);
858}
859
860uint16_t APIConnection::try_send_select_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
861 bool is_single) {
862 auto *select = static_cast<select::Select *>(entity);
864 resp.set_state(StringRef(select->state));
865 resp.missing_state = !select->has_state();
866 return fill_and_encode_entity_state(select, resp, SelectStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
867}
868
869uint16_t APIConnection::try_send_select_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
870 bool is_single) {
871 auto *select = static_cast<select::Select *>(entity);
873 msg.options = &select->traits.get_options();
874 return fill_and_encode_entity_info(select, msg, ListEntitiesSelectResponse::MESSAGE_TYPE, conn, remaining_size,
875 is_single);
876}
877void APIConnection::select_command(const SelectCommandRequest &msg) {
878 ENTITY_COMMAND_MAKE_CALL(select::Select, select, select)
879 call.set_option(msg.state);
880 call.perform();
881}
882#endif
883
884#ifdef USE_BUTTON
885uint16_t APIConnection::try_send_button_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
886 bool is_single) {
887 auto *button = static_cast<button::Button *>(entity);
889 msg.set_device_class(button->get_device_class_ref());
890 return fill_and_encode_entity_info(button, msg, ListEntitiesButtonResponse::MESSAGE_TYPE, conn, remaining_size,
891 is_single);
892}
894 ENTITY_COMMAND_GET(button::Button, button, button)
895 button->press();
896}
897#endif
898
899#ifdef USE_LOCK
900bool APIConnection::send_lock_state(lock::Lock *a_lock) {
901 return this->send_message_smart_(a_lock, &APIConnection::try_send_lock_state, LockStateResponse::MESSAGE_TYPE,
902 LockStateResponse::ESTIMATED_SIZE);
903}
904
905uint16_t APIConnection::try_send_lock_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
906 bool is_single) {
907 auto *a_lock = static_cast<lock::Lock *>(entity);
909 resp.state = static_cast<enums::LockState>(a_lock->state);
910 return fill_and_encode_entity_state(a_lock, resp, LockStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
911}
912
913uint16_t APIConnection::try_send_lock_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
914 bool is_single) {
915 auto *a_lock = static_cast<lock::Lock *>(entity);
917 msg.assumed_state = a_lock->traits.get_assumed_state();
918 msg.supports_open = a_lock->traits.get_supports_open();
919 msg.requires_code = a_lock->traits.get_requires_code();
920 return fill_and_encode_entity_info(a_lock, msg, ListEntitiesLockResponse::MESSAGE_TYPE, conn, remaining_size,
921 is_single);
922}
923void APIConnection::lock_command(const LockCommandRequest &msg) {
924 ENTITY_COMMAND_GET(lock::Lock, a_lock, lock)
925
926 switch (msg.command) {
927 case enums::LOCK_UNLOCK:
928 a_lock->unlock();
929 break;
930 case enums::LOCK_LOCK:
931 a_lock->lock();
932 break;
933 case enums::LOCK_OPEN:
934 a_lock->open();
935 break;
936 }
937}
938#endif
939
940#ifdef USE_VALVE
941bool APIConnection::send_valve_state(valve::Valve *valve) {
942 return this->send_message_smart_(valve, &APIConnection::try_send_valve_state, ValveStateResponse::MESSAGE_TYPE,
943 ValveStateResponse::ESTIMATED_SIZE);
944}
945uint16_t APIConnection::try_send_valve_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
946 bool is_single) {
947 auto *valve = static_cast<valve::Valve *>(entity);
949 resp.position = valve->position;
950 resp.current_operation = static_cast<enums::ValveOperation>(valve->current_operation);
951 return fill_and_encode_entity_state(valve, resp, ValveStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
952}
953uint16_t APIConnection::try_send_valve_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
954 bool is_single) {
955 auto *valve = static_cast<valve::Valve *>(entity);
957 auto traits = valve->get_traits();
958 msg.set_device_class(valve->get_device_class_ref());
959 msg.assumed_state = traits.get_is_assumed_state();
960 msg.supports_position = traits.get_supports_position();
961 msg.supports_stop = traits.get_supports_stop();
962 return fill_and_encode_entity_info(valve, msg, ListEntitiesValveResponse::MESSAGE_TYPE, conn, remaining_size,
963 is_single);
964}
965void APIConnection::valve_command(const ValveCommandRequest &msg) {
966 ENTITY_COMMAND_MAKE_CALL(valve::Valve, valve, valve)
967 if (msg.has_position)
968 call.set_position(msg.position);
969 if (msg.stop)
970 call.set_command_stop();
971 call.perform();
972}
973#endif
974
975#ifdef USE_MEDIA_PLAYER
976bool APIConnection::send_media_player_state(media_player::MediaPlayer *media_player) {
977 return this->send_message_smart_(media_player, &APIConnection::try_send_media_player_state,
978 MediaPlayerStateResponse::MESSAGE_TYPE, MediaPlayerStateResponse::ESTIMATED_SIZE);
979}
980uint16_t APIConnection::try_send_media_player_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
981 bool is_single) {
982 auto *media_player = static_cast<media_player::MediaPlayer *>(entity);
986 : media_player->state;
987 resp.state = static_cast<enums::MediaPlayerState>(report_state);
988 resp.volume = media_player->volume;
989 resp.muted = media_player->is_muted();
990 return fill_and_encode_entity_state(media_player, resp, MediaPlayerStateResponse::MESSAGE_TYPE, conn, remaining_size,
991 is_single);
992}
993uint16_t APIConnection::try_send_media_player_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
994 bool is_single) {
995 auto *media_player = static_cast<media_player::MediaPlayer *>(entity);
997 auto traits = media_player->get_traits();
998 msg.supports_pause = traits.get_supports_pause();
999 msg.feature_flags = traits.get_feature_flags();
1000 for (auto &supported_format : traits.get_supported_formats()) {
1001 msg.supported_formats.emplace_back();
1002 auto &media_format = msg.supported_formats.back();
1003 media_format.set_format(StringRef(supported_format.format));
1004 media_format.sample_rate = supported_format.sample_rate;
1005 media_format.num_channels = supported_format.num_channels;
1006 media_format.purpose = static_cast<enums::MediaPlayerFormatPurpose>(supported_format.purpose);
1007 media_format.sample_bytes = supported_format.sample_bytes;
1008 }
1009 return fill_and_encode_entity_info(media_player, msg, ListEntitiesMediaPlayerResponse::MESSAGE_TYPE, conn,
1010 remaining_size, is_single);
1011}
1012void APIConnection::media_player_command(const MediaPlayerCommandRequest &msg) {
1013 ENTITY_COMMAND_MAKE_CALL(media_player::MediaPlayer, media_player, media_player)
1014 if (msg.has_command) {
1015 call.set_command(static_cast<media_player::MediaPlayerCommand>(msg.command));
1016 }
1017 if (msg.has_volume) {
1018 call.set_volume(msg.volume);
1019 }
1020 if (msg.has_media_url) {
1021 call.set_media_url(msg.media_url);
1022 }
1023 if (msg.has_announcement) {
1024 call.set_announcement(msg.announcement);
1025 }
1026 call.perform();
1027}
1028#endif
1029
1030#ifdef USE_CAMERA
1031void APIConnection::set_camera_state(std::shared_ptr<camera::CameraImage> image) {
1032 if (!this->flags_.state_subscription)
1033 return;
1034 if (!this->image_reader_)
1035 return;
1036 if (this->image_reader_->available())
1037 return;
1038 if (image->was_requested_by(esphome::camera::API_REQUESTER) || image->was_requested_by(esphome::camera::IDLE))
1039 this->image_reader_->set_image(std::move(image));
1040}
1041uint16_t APIConnection::try_send_camera_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
1042 bool is_single) {
1043 auto *camera = static_cast<camera::Camera *>(entity);
1045 return fill_and_encode_entity_info(camera, msg, ListEntitiesCameraResponse::MESSAGE_TYPE, conn, remaining_size,
1046 is_single);
1047}
1048void APIConnection::camera_image(const CameraImageRequest &msg) {
1049 if (camera::Camera::instance() == nullptr)
1050 return;
1051
1052 if (msg.single)
1054 if (msg.stream) {
1056
1057 App.scheduler.set_timeout(this->parent_, "api_camera_stop_stream", CAMERA_STOP_STREAM,
1059 }
1060}
1061#endif
1062
1063#ifdef USE_HOMEASSISTANT_TIME
1064void APIConnection::on_get_time_response(const GetTimeResponse &value) {
1067}
1068#endif
1069
1070bool APIConnection::send_get_time_response(const GetTimeRequest &msg) {
1071 GetTimeResponse resp;
1072 resp.epoch_seconds = ::time(nullptr);
1073 return this->send_message(resp, GetTimeResponse::MESSAGE_TYPE);
1074}
1075
1076#ifdef USE_BLUETOOTH_PROXY
1077void APIConnection::subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) {
1079}
1080void APIConnection::unsubscribe_bluetooth_le_advertisements(const UnsubscribeBluetoothLEAdvertisementsRequest &msg) {
1082}
1083void APIConnection::bluetooth_device_request(const BluetoothDeviceRequest &msg) {
1085}
1086void APIConnection::bluetooth_gatt_read(const BluetoothGATTReadRequest &msg) {
1088}
1089void APIConnection::bluetooth_gatt_write(const BluetoothGATTWriteRequest &msg) {
1091}
1092void APIConnection::bluetooth_gatt_read_descriptor(const BluetoothGATTReadDescriptorRequest &msg) {
1094}
1095void APIConnection::bluetooth_gatt_write_descriptor(const BluetoothGATTWriteDescriptorRequest &msg) {
1097}
1098void APIConnection::bluetooth_gatt_get_services(const BluetoothGATTGetServicesRequest &msg) {
1100}
1101
1102void APIConnection::bluetooth_gatt_notify(const BluetoothGATTNotifyRequest &msg) {
1104}
1105
1106bool APIConnection::send_subscribe_bluetooth_connections_free_response(
1109 return true;
1110}
1111
1112void APIConnection::bluetooth_scanner_set_mode(const BluetoothScannerSetModeRequest &msg) {
1114 msg.mode == enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_ACTIVE);
1115}
1116#endif
1117
1118#ifdef USE_VOICE_ASSISTANT
1119bool APIConnection::check_voice_assistant_api_connection_() const {
1120 return voice_assistant::global_voice_assistant != nullptr &&
1122}
1123
1124void APIConnection::subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) {
1127 }
1128}
1129void APIConnection::on_voice_assistant_response(const VoiceAssistantResponse &msg) {
1130 if (!this->check_voice_assistant_api_connection_()) {
1131 return;
1132 }
1133
1134 if (msg.error) {
1136 return;
1137 }
1138 if (msg.port == 0) {
1139 // Use API Audio
1141 } else {
1142 struct sockaddr_storage storage;
1143 socklen_t len = sizeof(storage);
1144 this->helper_->getpeername((struct sockaddr *) &storage, &len);
1146 }
1147};
1148void APIConnection::on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg) {
1149 if (this->check_voice_assistant_api_connection_()) {
1151 }
1152}
1153void APIConnection::on_voice_assistant_audio(const VoiceAssistantAudio &msg) {
1154 if (this->check_voice_assistant_api_connection_()) {
1156 }
1157};
1158void APIConnection::on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &msg) {
1159 if (this->check_voice_assistant_api_connection_()) {
1161 }
1162};
1163
1164void APIConnection::on_voice_assistant_announce_request(const VoiceAssistantAnnounceRequest &msg) {
1165 if (this->check_voice_assistant_api_connection_()) {
1167 }
1168}
1169
1170bool APIConnection::send_voice_assistant_get_configuration_response(const VoiceAssistantConfigurationRequest &msg) {
1172 if (!this->check_voice_assistant_api_connection_()) {
1173 return this->send_message(resp, VoiceAssistantConfigurationResponse::MESSAGE_TYPE);
1174 }
1175
1177 for (auto &wake_word : config.available_wake_words) {
1178 resp.available_wake_words.emplace_back();
1179 auto &resp_wake_word = resp.available_wake_words.back();
1180 resp_wake_word.set_id(StringRef(wake_word.id));
1181 resp_wake_word.set_wake_word(StringRef(wake_word.wake_word));
1182 for (const auto &lang : wake_word.trained_languages) {
1183 resp_wake_word.trained_languages.push_back(lang);
1184 }
1185 }
1186 resp.active_wake_words = &config.active_wake_words;
1187 resp.max_active_wake_words = config.max_active_wake_words;
1188 return this->send_message(resp, VoiceAssistantConfigurationResponse::MESSAGE_TYPE);
1189}
1190
1191void APIConnection::voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) {
1192 if (this->check_voice_assistant_api_connection_()) {
1194 }
1195}
1196
1197#endif
1198
1199#ifdef USE_ALARM_CONTROL_PANEL
1200bool APIConnection::send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) {
1201 return this->send_message_smart_(a_alarm_control_panel, &APIConnection::try_send_alarm_control_panel_state,
1202 AlarmControlPanelStateResponse::MESSAGE_TYPE,
1203 AlarmControlPanelStateResponse::ESTIMATED_SIZE);
1204}
1205uint16_t APIConnection::try_send_alarm_control_panel_state(EntityBase *entity, APIConnection *conn,
1206 uint32_t remaining_size, bool is_single) {
1207 auto *a_alarm_control_panel = static_cast<alarm_control_panel::AlarmControlPanel *>(entity);
1209 resp.state = static_cast<enums::AlarmControlPanelState>(a_alarm_control_panel->get_state());
1210 return fill_and_encode_entity_state(a_alarm_control_panel, resp, AlarmControlPanelStateResponse::MESSAGE_TYPE, conn,
1211 remaining_size, is_single);
1212}
1213uint16_t APIConnection::try_send_alarm_control_panel_info(EntityBase *entity, APIConnection *conn,
1214 uint32_t remaining_size, bool is_single) {
1215 auto *a_alarm_control_panel = static_cast<alarm_control_panel::AlarmControlPanel *>(entity);
1217 msg.supported_features = a_alarm_control_panel->get_supported_features();
1218 msg.requires_code = a_alarm_control_panel->get_requires_code();
1219 msg.requires_code_to_arm = a_alarm_control_panel->get_requires_code_to_arm();
1220 return fill_and_encode_entity_info(a_alarm_control_panel, msg, ListEntitiesAlarmControlPanelResponse::MESSAGE_TYPE,
1221 conn, remaining_size, is_single);
1222}
1223void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) {
1224 ENTITY_COMMAND_MAKE_CALL(alarm_control_panel::AlarmControlPanel, a_alarm_control_panel, alarm_control_panel)
1225 switch (msg.command) {
1226 case enums::ALARM_CONTROL_PANEL_DISARM:
1227 call.disarm();
1228 break;
1229 case enums::ALARM_CONTROL_PANEL_ARM_AWAY:
1230 call.arm_away();
1231 break;
1232 case enums::ALARM_CONTROL_PANEL_ARM_HOME:
1233 call.arm_home();
1234 break;
1235 case enums::ALARM_CONTROL_PANEL_ARM_NIGHT:
1236 call.arm_night();
1237 break;
1238 case enums::ALARM_CONTROL_PANEL_ARM_VACATION:
1239 call.arm_vacation();
1240 break;
1241 case enums::ALARM_CONTROL_PANEL_ARM_CUSTOM_BYPASS:
1242 call.arm_custom_bypass();
1243 break;
1244 case enums::ALARM_CONTROL_PANEL_TRIGGER:
1245 call.pending();
1246 break;
1247 }
1248 call.set_code(msg.code);
1249 call.perform();
1250}
1251#endif
1252
1253#ifdef USE_EVENT
1254void APIConnection::send_event(event::Event *event, const std::string &event_type) {
1255 this->schedule_message_(event, MessageCreator(event_type), EventResponse::MESSAGE_TYPE,
1256 EventResponse::ESTIMATED_SIZE);
1257}
1258uint16_t APIConnection::try_send_event_response(event::Event *event, const std::string &event_type, APIConnection *conn,
1259 uint32_t remaining_size, bool is_single) {
1260 EventResponse resp;
1261 resp.set_event_type(StringRef(event_type));
1262 return fill_and_encode_entity_state(event, resp, EventResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
1263}
1264
1265uint16_t APIConnection::try_send_event_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
1266 bool is_single) {
1267 auto *event = static_cast<event::Event *>(entity);
1269 msg.set_device_class(event->get_device_class_ref());
1270 for (const auto &event_type : event->get_event_types())
1271 msg.event_types.push_back(event_type);
1272 return fill_and_encode_entity_info(event, msg, ListEntitiesEventResponse::MESSAGE_TYPE, conn, remaining_size,
1273 is_single);
1274}
1275#endif
1276
1277#ifdef USE_UPDATE
1278bool APIConnection::send_update_state(update::UpdateEntity *update) {
1279 return this->send_message_smart_(update, &APIConnection::try_send_update_state, UpdateStateResponse::MESSAGE_TYPE,
1280 UpdateStateResponse::ESTIMATED_SIZE);
1281}
1282uint16_t APIConnection::try_send_update_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
1283 bool is_single) {
1284 auto *update = static_cast<update::UpdateEntity *>(entity);
1286 resp.missing_state = !update->has_state();
1287 if (update->has_state()) {
1289 if (update->update_info.has_progress) {
1290 resp.has_progress = true;
1291 resp.progress = update->update_info.progress;
1292 }
1293 resp.set_current_version(StringRef(update->update_info.current_version));
1294 resp.set_latest_version(StringRef(update->update_info.latest_version));
1295 resp.set_title(StringRef(update->update_info.title));
1296 resp.set_release_summary(StringRef(update->update_info.summary));
1297 resp.set_release_url(StringRef(update->update_info.release_url));
1298 }
1299 return fill_and_encode_entity_state(update, resp, UpdateStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
1300}
1301uint16_t APIConnection::try_send_update_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
1302 bool is_single) {
1303 auto *update = static_cast<update::UpdateEntity *>(entity);
1305 msg.set_device_class(update->get_device_class_ref());
1306 return fill_and_encode_entity_info(update, msg, ListEntitiesUpdateResponse::MESSAGE_TYPE, conn, remaining_size,
1307 is_single);
1308}
1309void APIConnection::update_command(const UpdateCommandRequest &msg) {
1310 ENTITY_COMMAND_GET(update::UpdateEntity, update, update)
1311
1312 switch (msg.command) {
1313 case enums::UPDATE_COMMAND_UPDATE:
1314 update->perform();
1315 break;
1316 case enums::UPDATE_COMMAND_CHECK:
1317 update->check();
1318 break;
1319 case enums::UPDATE_COMMAND_NONE:
1320 ESP_LOGE(TAG, "UPDATE_COMMAND_NONE not handled; confirm command is correct");
1321 break;
1322 default:
1323 ESP_LOGW(TAG, "Unknown update command: %" PRIu32, msg.command);
1324 break;
1325 }
1326}
1327#endif
1328
1329bool APIConnection::try_send_log_message(int level, const char *tag, const char *line, size_t message_len) {
1331 msg.level = static_cast<enums::LogLevel>(level);
1332 msg.set_message(reinterpret_cast<const uint8_t *>(line), message_len);
1333 return this->send_message_(msg, SubscribeLogsResponse::MESSAGE_TYPE);
1334}
1335
1336void APIConnection::complete_authentication_() {
1337 // Early return if already authenticated
1338 if (this->flags_.connection_state == static_cast<uint8_t>(ConnectionState::AUTHENTICATED)) {
1339 return;
1340 }
1341
1342 this->flags_.connection_state = static_cast<uint8_t>(ConnectionState::AUTHENTICATED);
1343 ESP_LOGD(TAG, "%s connected", this->get_client_combined_info().c_str());
1344#ifdef USE_API_CLIENT_CONNECTED_TRIGGER
1345 this->parent_->get_client_connected_trigger()->trigger(this->client_info_.name, this->client_info_.peername);
1346#endif
1347#ifdef USE_HOMEASSISTANT_TIME
1349 this->send_time_request();
1350 }
1351#endif
1352}
1353
1354bool APIConnection::send_hello_response(const HelloRequest &msg) {
1355 this->client_info_.name = msg.client_info;
1356 this->client_info_.peername = this->helper_->getpeername();
1357 this->client_api_version_major_ = msg.api_version_major;
1358 this->client_api_version_minor_ = msg.api_version_minor;
1359 ESP_LOGV(TAG, "Hello from client: '%s' | %s | API Version %" PRIu32 ".%" PRIu32, this->client_info_.name.c_str(),
1360 this->client_info_.peername.c_str(), this->client_api_version_major_, this->client_api_version_minor_);
1361
1362 HelloResponse resp;
1363 resp.api_version_major = 1;
1364 resp.api_version_minor = 12;
1365 // Temporary string for concatenation - will be valid during send_message call
1366 std::string server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")";
1367 resp.set_server_info(StringRef(server_info));
1368 resp.set_name(StringRef(App.get_name()));
1369
1370#ifdef USE_API_PASSWORD
1371 // Password required - wait for authentication
1372 this->flags_.connection_state = static_cast<uint8_t>(ConnectionState::CONNECTED);
1373#else
1374 // No password configured - auto-authenticate
1375 this->complete_authentication_();
1376#endif
1377
1378 return this->send_message(resp, HelloResponse::MESSAGE_TYPE);
1379}
1380bool APIConnection::send_connect_response(const ConnectRequest &msg) {
1381 bool correct = true;
1382#ifdef USE_API_PASSWORD
1383 correct = this->parent_->check_password(msg.password);
1384#endif
1385
1386 ConnectResponse resp;
1387 // bool invalid_password = 1;
1388 resp.invalid_password = !correct;
1389 if (correct) {
1390 this->complete_authentication_();
1391 }
1392 return this->send_message(resp, ConnectResponse::MESSAGE_TYPE);
1393}
1394
1395bool APIConnection::send_ping_response(const PingRequest &msg) {
1396 PingResponse resp;
1397 return this->send_message(resp, PingResponse::MESSAGE_TYPE);
1398}
1399
1400bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
1401 DeviceInfoResponse resp{};
1402#ifdef USE_API_PASSWORD
1403 resp.uses_password = true;
1404#endif
1405 resp.set_name(StringRef(App.get_name()));
1406 resp.set_friendly_name(StringRef(App.get_friendly_name()));
1407#ifdef USE_AREAS
1408 resp.set_suggested_area(StringRef(App.get_area()));
1409#endif
1410 // mac_address must store temporary string - will be valid during send_message call
1411 std::string mac_address = get_mac_address_pretty();
1412 resp.set_mac_address(StringRef(mac_address));
1413
1414 // Compile-time StringRef constants
1415 static constexpr auto ESPHOME_VERSION_REF = StringRef::from_lit(ESPHOME_VERSION);
1416 resp.set_esphome_version(ESPHOME_VERSION_REF);
1417
1418 // get_compilation_time() returns temporary std::string - must store it
1419 std::string compilation_time = App.get_compilation_time();
1420 resp.set_compilation_time(StringRef(compilation_time));
1421
1422 // Compile-time StringRef constants for manufacturers
1423#if defined(USE_ESP8266) || defined(USE_ESP32)
1424 static constexpr auto MANUFACTURER = StringRef::from_lit("Espressif");
1425#elif defined(USE_RP2040)
1426 static constexpr auto MANUFACTURER = StringRef::from_lit("Raspberry Pi");
1427#elif defined(USE_BK72XX)
1428 static constexpr auto MANUFACTURER = StringRef::from_lit("Beken");
1429#elif defined(USE_LN882X)
1430 static constexpr auto MANUFACTURER = StringRef::from_lit("Lightning");
1431#elif defined(USE_RTL87XX)
1432 static constexpr auto MANUFACTURER = StringRef::from_lit("Realtek");
1433#elif defined(USE_HOST)
1434 static constexpr auto MANUFACTURER = StringRef::from_lit("Host");
1435#endif
1436 resp.set_manufacturer(MANUFACTURER);
1437
1438 static constexpr auto MODEL = StringRef::from_lit(ESPHOME_BOARD);
1439 resp.set_model(MODEL);
1440#ifdef USE_DEEP_SLEEP
1441 resp.has_deep_sleep = deep_sleep::global_has_deep_sleep;
1442#endif
1443#ifdef ESPHOME_PROJECT_NAME
1444 static constexpr auto PROJECT_NAME = StringRef::from_lit(ESPHOME_PROJECT_NAME);
1445 static constexpr auto PROJECT_VERSION = StringRef::from_lit(ESPHOME_PROJECT_VERSION);
1446 resp.set_project_name(PROJECT_NAME);
1447 resp.set_project_version(PROJECT_VERSION);
1448#endif
1449#ifdef USE_WEBSERVER
1450 resp.webserver_port = USE_WEBSERVER_PORT;
1451#endif
1452#ifdef USE_BLUETOOTH_PROXY
1453 resp.bluetooth_proxy_feature_flags = bluetooth_proxy::global_bluetooth_proxy->get_feature_flags();
1454 // bt_mac must store temporary string - will be valid during send_message call
1456 resp.set_bluetooth_mac_address(StringRef(bluetooth_mac));
1457#endif
1458#ifdef USE_VOICE_ASSISTANT
1459 resp.voice_assistant_feature_flags = voice_assistant::global_voice_assistant->get_feature_flags();
1460#endif
1461#ifdef USE_API_NOISE
1462 resp.api_encryption_supported = true;
1463#endif
1464#ifdef USE_DEVICES
1465 size_t device_index = 0;
1466 for (auto const &device : App.get_devices()) {
1467 if (device_index >= ESPHOME_DEVICE_COUNT)
1468 break;
1469 auto &device_info = resp.devices[device_index++];
1470 device_info.device_id = device->get_device_id();
1471 device_info.set_name(StringRef(device->get_name()));
1472 device_info.area_id = device->get_area_id();
1473 }
1474#endif
1475#ifdef USE_AREAS
1476 size_t area_index = 0;
1477 for (auto const &area : App.get_areas()) {
1478 if (area_index >= ESPHOME_AREA_COUNT)
1479 break;
1480 auto &area_info = resp.areas[area_index++];
1481 area_info.area_id = area->get_area_id();
1482 area_info.set_name(StringRef(area->get_name()));
1483 }
1484#endif
1485
1486 return this->send_message(resp, DeviceInfoResponse::MESSAGE_TYPE);
1487}
1488
1489#ifdef USE_API_HOMEASSISTANT_STATES
1490void APIConnection::on_home_assistant_state_response(const HomeAssistantStateResponse &msg) {
1491 for (auto &it : this->parent_->get_state_subs()) {
1492 if (it.entity_id == msg.entity_id && it.attribute.value() == msg.attribute) {
1493 it.callback(msg.state);
1494 }
1495 }
1496}
1497#endif
1498#ifdef USE_API_SERVICES
1499void APIConnection::execute_service(const ExecuteServiceRequest &msg) {
1500 bool found = false;
1501 for (auto *service : this->parent_->get_user_services()) {
1502 if (service->execute_service(msg)) {
1503 found = true;
1504 }
1505 }
1506 if (!found) {
1507 ESP_LOGV(TAG, "Could not find service");
1508 }
1509}
1510#endif
1511#ifdef USE_API_NOISE
1512bool APIConnection::send_noise_encryption_set_key_response(const NoiseEncryptionSetKeyRequest &msg) {
1514 resp.success = false;
1515
1516 psk_t psk{};
1517 if (base64_decode(msg.key, psk.data(), msg.key.size()) != psk.size()) {
1518 ESP_LOGW(TAG, "Invalid encryption key length");
1519 } else if (!this->parent_->save_noise_psk(psk, true)) {
1520 ESP_LOGW(TAG, "Failed to save encryption key");
1521 } else {
1522 resp.success = true;
1523 }
1524
1525 return this->send_message(resp, NoiseEncryptionSetKeyResponse::MESSAGE_TYPE);
1526}
1527#endif
1528#ifdef USE_API_HOMEASSISTANT_STATES
1529void APIConnection::subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) {
1530 state_subs_at_ = 0;
1531}
1532#endif
1533bool APIConnection::try_to_clear_buffer(bool log_out_of_space) {
1534 if (this->flags_.remove)
1535 return false;
1536 if (this->helper_->can_write_without_blocking())
1537 return true;
1538 delay(0);
1539 APIError err = this->helper_->loop();
1540 if (err != APIError::OK) {
1541 on_fatal_error();
1542 this->log_socket_operation_failed_(err);
1543 return false;
1544 }
1545 if (this->helper_->can_write_without_blocking())
1546 return true;
1547 if (log_out_of_space) {
1548 ESP_LOGV(TAG, "Cannot send message because of TCP buffer space");
1549 }
1550 return false;
1551}
1552bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) {
1553 if (!this->try_to_clear_buffer(message_type != SubscribeLogsResponse::MESSAGE_TYPE)) { // SubscribeLogsResponse
1554 return false;
1555 }
1556
1557 APIError err = this->helper_->write_protobuf_packet(message_type, buffer);
1558 if (err == APIError::WOULD_BLOCK)
1559 return false;
1560 if (err != APIError::OK) {
1561 on_fatal_error();
1562 this->log_warning_("Packet write failed", err);
1563 return false;
1564 }
1565 // Do not set last_traffic_ on send
1566 return true;
1567}
1568#ifdef USE_API_PASSWORD
1569void APIConnection::on_unauthenticated_access() {
1570 this->on_fatal_error();
1571 ESP_LOGD(TAG, "%s access without authentication", this->get_client_combined_info().c_str());
1572}
1573#endif
1574void APIConnection::on_no_setup_connection() {
1575 this->on_fatal_error();
1576 ESP_LOGD(TAG, "%s access without full connection", this->get_client_combined_info().c_str());
1577}
1578void APIConnection::on_fatal_error() {
1579 this->helper_->close();
1580 this->flags_.remove = true;
1581}
1582
1583void APIConnection::DeferredBatch::add_item(EntityBase *entity, MessageCreator creator, uint8_t message_type,
1584 uint8_t estimated_size) {
1585 // Check if we already have a message of this type for this entity
1586 // This provides deduplication per entity/message_type combination
1587 // O(n) but optimized for RAM and not performance.
1588 for (auto &item : items) {
1589 if (item.entity == entity && item.message_type == message_type) {
1590 // Clean up old creator before replacing
1591 item.creator.cleanup(message_type);
1592 // Move assign the new creator
1593 item.creator = std::move(creator);
1594 return;
1595 }
1596 }
1597
1598 // No existing item found, add new one
1599 items.emplace_back(entity, std::move(creator), message_type, estimated_size);
1600}
1601
1602void APIConnection::DeferredBatch::add_item_front(EntityBase *entity, MessageCreator creator, uint8_t message_type,
1603 uint8_t estimated_size) {
1604 // Add high priority message and swap to front
1605 // This avoids expensive vector::insert which shifts all elements
1606 // Note: We only ever have one high-priority message at a time (ping OR disconnect)
1607 // If we're disconnecting, pings are blocked, so this simple swap is sufficient
1608 items.emplace_back(entity, std::move(creator), message_type, estimated_size);
1609 if (items.size() > 1) {
1610 // Swap the new high-priority item to the front
1611 std::swap(items.front(), items.back());
1612 }
1613}
1614
1615bool APIConnection::schedule_batch_() {
1616 if (!this->flags_.batch_scheduled) {
1617 this->flags_.batch_scheduled = true;
1618 this->deferred_batch_.batch_start_time = App.get_loop_component_start_time();
1619 }
1620 return true;
1621}
1622
1623ProtoWriteBuffer APIConnection::allocate_single_message_buffer(uint16_t size) { return this->create_buffer(size); }
1624
1625ProtoWriteBuffer APIConnection::allocate_batch_message_buffer(uint16_t size) {
1626 ProtoWriteBuffer result = this->prepare_message_buffer(size, this->flags_.batch_first_message);
1627 this->flags_.batch_first_message = false;
1628 return result;
1629}
1630
1631void APIConnection::process_batch_() {
1632 // Ensure PacketInfo remains trivially destructible for our placement new approach
1633 static_assert(std::is_trivially_destructible<PacketInfo>::value,
1634 "PacketInfo must remain trivially destructible with this placement-new approach");
1635
1636 if (this->deferred_batch_.empty()) {
1637 this->flags_.batch_scheduled = false;
1638 return;
1639 }
1640
1641 // Try to clear buffer first
1642 if (!this->try_to_clear_buffer(true)) {
1643 // Can't write now, we'll try again later
1644 return;
1645 }
1646
1647 // Get shared buffer reference once to avoid multiple calls
1648 auto &shared_buf = this->parent_->get_shared_buffer_ref();
1649 size_t num_items = this->deferred_batch_.size();
1650
1651 // Fast path for single message - allocate exact size needed
1652 if (num_items == 1) {
1653 const auto &item = this->deferred_batch_[0];
1654
1655 // Let the creator calculate size and encode if it fits
1656 uint16_t payload_size =
1657 item.creator(item.entity, this, std::numeric_limits<uint16_t>::max(), true, item.message_type);
1658
1659 if (payload_size > 0 && this->send_buffer(ProtoWriteBuffer{&shared_buf}, item.message_type)) {
1660#ifdef HAS_PROTO_MESSAGE_DUMP
1661 // Log messages after send attempt for VV debugging
1662 // It's safe to use the buffer for logging at this point regardless of send result
1663 this->log_batch_item_(item);
1664#endif
1665 this->clear_batch_();
1666 } else if (payload_size == 0) {
1667 // Message too large
1668 ESP_LOGW(TAG, "Message too large to send: type=%u", item.message_type);
1669 this->clear_batch_();
1670 }
1671 return;
1672 }
1673
1674 size_t packets_to_process = std::min(num_items, MAX_PACKETS_PER_BATCH);
1675
1676 // Stack-allocated array for packet info
1677 alignas(PacketInfo) char packet_info_storage[MAX_PACKETS_PER_BATCH * sizeof(PacketInfo)];
1678 PacketInfo *packet_info = reinterpret_cast<PacketInfo *>(packet_info_storage);
1679 size_t packet_count = 0;
1680
1681 // Cache these values to avoid repeated virtual calls
1682 const uint8_t header_padding = this->helper_->frame_header_padding();
1683 const uint8_t footer_size = this->helper_->frame_footer_size();
1684
1685 // Initialize buffer and tracking variables
1686 shared_buf.clear();
1687
1688 // Pre-calculate exact buffer size needed based on message types
1689 uint32_t total_estimated_size = num_items * (header_padding + footer_size);
1690 for (size_t i = 0; i < this->deferred_batch_.size(); i++) {
1691 const auto &item = this->deferred_batch_[i];
1692 total_estimated_size += item.estimated_size;
1693 }
1694
1695 // Calculate total overhead for all messages
1696 // Reserve based on estimated size (much more accurate than 24-byte worst-case)
1697 shared_buf.reserve(total_estimated_size);
1698 this->flags_.batch_first_message = true;
1699
1700 size_t items_processed = 0;
1701 uint16_t remaining_size = std::numeric_limits<uint16_t>::max();
1702
1703 // Track where each message's header padding begins in the buffer
1704 // For plaintext: this is where the 6-byte header padding starts
1705 // For noise: this is where the 7-byte header padding starts
1706 // The actual message data follows after the header padding
1707 uint32_t current_offset = 0;
1708
1709 // Process items and encode directly to buffer (up to our limit)
1710 for (size_t i = 0; i < packets_to_process; i++) {
1711 const auto &item = this->deferred_batch_[i];
1712 // Try to encode message
1713 // The creator will calculate overhead to determine if the message fits
1714 uint16_t payload_size = item.creator(item.entity, this, remaining_size, false, item.message_type);
1715
1716 if (payload_size == 0) {
1717 // Message won't fit, stop processing
1718 break;
1719 }
1720
1721 // Message was encoded successfully
1722 // payload_size is header_padding + actual payload size + footer_size
1723 uint16_t proto_payload_size = payload_size - header_padding - footer_size;
1724 // Use placement new to construct PacketInfo in pre-allocated stack array
1725 // This avoids default-constructing all MAX_PACKETS_PER_BATCH elements
1726 // Explicit destruction is not needed because PacketInfo is trivially destructible,
1727 // as ensured by the static_assert in its definition.
1728 new (&packet_info[packet_count++]) PacketInfo(item.message_type, current_offset, proto_payload_size);
1729
1730 // Update tracking variables
1731 items_processed++;
1732 // After first message, set remaining size to MAX_BATCH_PACKET_SIZE to avoid fragmentation
1733 if (items_processed == 1) {
1734 remaining_size = MAX_BATCH_PACKET_SIZE;
1735 }
1736 remaining_size -= payload_size;
1737 // Calculate where the next message's header padding will start
1738 // Current buffer size + footer space (that prepare_message_buffer will add for this message)
1739 current_offset = shared_buf.size() + footer_size;
1740 }
1741
1742 if (items_processed == 0) {
1743 this->deferred_batch_.clear();
1744 return;
1745 }
1746
1747 // Add footer space for the last message (for Noise protocol MAC)
1748 if (footer_size > 0) {
1749 shared_buf.resize(shared_buf.size() + footer_size);
1750 }
1751
1752 // Send all collected packets
1753 APIError err = this->helper_->write_protobuf_packets(ProtoWriteBuffer{&shared_buf},
1754 std::span<const PacketInfo>(packet_info, packet_count));
1755 if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
1756 on_fatal_error();
1757 this->log_warning_("Batch write failed", err);
1758 }
1759
1760#ifdef HAS_PROTO_MESSAGE_DUMP
1761 // Log messages after send attempt for VV debugging
1762 // It's safe to use the buffer for logging at this point regardless of send result
1763 for (size_t i = 0; i < items_processed; i++) {
1764 const auto &item = this->deferred_batch_[i];
1765 this->log_batch_item_(item);
1766 }
1767#endif
1768
1769 // Handle remaining items more efficiently
1770 if (items_processed < this->deferred_batch_.size()) {
1771 // Remove processed items from the beginning with proper cleanup
1772 this->deferred_batch_.remove_front(items_processed);
1773 // Reschedule for remaining items
1774 this->schedule_batch_();
1775 } else {
1776 // All items processed
1777 this->clear_batch_();
1778 }
1779}
1780
1781uint16_t APIConnection::MessageCreator::operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
1782 bool is_single, uint8_t message_type) const {
1783#ifdef USE_EVENT
1784 // Special case: EventResponse uses string pointer
1785 if (message_type == EventResponse::MESSAGE_TYPE) {
1786 auto *e = static_cast<event::Event *>(entity);
1787 return APIConnection::try_send_event_response(e, *data_.string_ptr, conn, remaining_size, is_single);
1788 }
1789#endif
1790
1791 // All other message types use function pointers
1792 return data_.function_ptr(entity, conn, remaining_size, is_single);
1793}
1794
1795uint16_t APIConnection::try_send_list_info_done(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
1796 bool is_single) {
1798 return encode_message_to_buffer(resp, ListEntitiesDoneResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
1799}
1800
1801uint16_t APIConnection::try_send_disconnect_request(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
1802 bool is_single) {
1804 return encode_message_to_buffer(req, DisconnectRequest::MESSAGE_TYPE, conn, remaining_size, is_single);
1805}
1806
1807uint16_t APIConnection::try_send_ping_request(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
1808 bool is_single) {
1809 PingRequest req;
1810 return encode_message_to_buffer(req, PingRequest::MESSAGE_TYPE, conn, remaining_size, is_single);
1811}
1812
1813#ifdef USE_API_HOMEASSISTANT_STATES
1814void APIConnection::process_state_subscriptions_() {
1815 const auto &subs = this->parent_->get_state_subs();
1816 if (this->state_subs_at_ >= static_cast<int>(subs.size())) {
1817 this->state_subs_at_ = -1;
1818 return;
1819 }
1820
1821 const auto &it = subs[this->state_subs_at_];
1823 resp.set_entity_id(StringRef(it.entity_id));
1824
1825 // Avoid string copy by directly using the optional's value if it exists
1826 resp.set_attribute(it.attribute.has_value() ? StringRef(it.attribute.value()) : StringRef(""));
1827
1828 resp.once = it.once;
1829 if (this->send_message(resp, SubscribeHomeAssistantStateResponse::MESSAGE_TYPE)) {
1830 this->state_subs_at_++;
1831 }
1832}
1833#endif // USE_API_HOMEASSISTANT_STATES
1834
1835void APIConnection::log_warning_(const char *message, APIError err) {
1836 ESP_LOGW(TAG, "%s: %s %s errno=%d", this->get_client_combined_info().c_str(), message, api_error_to_str(err), errno);
1837}
1838
1839void APIConnection::log_socket_operation_failed_(APIError err) { this->log_warning_("Socket operation failed", err); }
1840
1841} // namespace esphome::api
1842#endif
const std::string & get_friendly_name() const
Get the friendly name of this Application set by pre_setup().
const auto & get_areas()
std::string get_compilation_time() const
const char * get_area() const
Get the area of this Application set by pre_setup().
const auto & get_devices()
const std::string & get_name() const
Get the name of this Application set by pre_setup().
uint32_t IRAM_ATTR HOT get_loop_component_start_time() const
Get the cached time in milliseconds from when the current component started its loop execution.
uint32_t get_object_id_hash()
uint32_t get_device_id() const
Definition entity_base.h:68
StringRef is a reference to a string owned by something else.
Definition string_ref.h:22
static constexpr StringRef from_lit(const CharT(&s)[N])
Definition string_ref.h:46
struct esphome::api::APIConnection::APIFlags flags_
std::unique_ptr< APIFrameHelper > helper_
ProtoWriteBuffer allocate_batch_message_buffer(uint16_t size)
APIConnection(std::unique_ptr< socket::Socket > socket, APIServer *parent)
ProtoWriteBuffer allocate_single_message_buffer(uint16_t size)
void button_command(const ButtonCommandRequest &msg) override
void log_send_message_(const char *name, const std::string &dump)
std::shared_ptr< APINoiseContext > get_noise_ctx()
Definition api_server.h:54
std::vector< uint8_t > & get_shared_buffer_ref()
Definition api_server.h:49
enums::AlarmControlPanelStateCommand command
Definition api_pb2.h:2528
enums::AlarmControlPanelState state
Definition api_pb2.h:2512
enums::BluetoothScannerMode mode
Definition api_pb2.h:2238
void set_data(const uint8_t *data, size_t len)
Definition api_pb2.h:1287
enums::ClimateSwingMode swing_mode
Definition api_pb2.h:1398
enums::ClimateFanMode fan_mode
Definition api_pb2.h:1396
enums::ClimatePreset preset
Definition api_pb2.h:1402
enums::ClimateFanMode fan_mode
Definition api_pb2.h:1363
enums::ClimateSwingMode swing_mode
Definition api_pb2.h:1364
void set_custom_fan_mode(const StringRef &ref)
Definition api_pb2.h:1366
void set_custom_preset(const StringRef &ref)
Definition api_pb2.h:1369
enums::ClimateAction action
Definition api_pb2.h:1362
enums::ClimatePreset preset
Definition api_pb2.h:1367
enums::CoverOperation current_operation
Definition api_pb2.h:669
void set_event_type(const StringRef &ref)
Definition api_pb2.h:2732
enums::FanDirection direction
Definition api_pb2.h:753
enums::FanDirection direction
Definition api_pb2.h:729
void set_preset_mode(const StringRef &ref)
Definition api_pb2.h:732
void set_name(const StringRef &ref)
Definition api_pb2.h:354
void set_server_info(const StringRef &ref)
Definition api_pb2.h:352
void set_effect(const StringRef &ref)
Definition api_pb2.h:807
enums::ColorMode color_mode
Definition api_pb2.h:797
void set_device_class(const StringRef &ref)
Definition api_pb2.h:610
void set_device_class(const StringRef &ref)
Definition api_pb2.h:1654
const std::set< std::string > * supported_custom_presets
Definition api_pb2.h:1336
const std::set< climate::ClimateSwingMode > * supported_swing_modes
Definition api_pb2.h:1333
const std::set< std::string > * supported_custom_fan_modes
Definition api_pb2.h:1334
const std::set< climate::ClimateFanMode > * supported_fan_modes
Definition api_pb2.h:1332
const std::set< climate::ClimatePreset > * supported_presets
Definition api_pb2.h:1335
const std::set< climate::ClimateMode > * supported_modes
Definition api_pb2.h:1327
void set_device_class(const StringRef &ref)
Definition api_pb2.h:650
std::vector< std::string > event_types
Definition api_pb2.h:2715
void set_device_class(const StringRef &ref)
Definition api_pb2.h:2714
const std::set< std::string > * supported_preset_modes
Definition api_pb2.h:711
const std::set< light::ColorMode > * supported_color_modes
Definition api_pb2.h:776
std::vector< std::string > effects
Definition api_pb2.h:779
std::vector< MediaPlayerSupportedFormat > supported_formats
Definition api_pb2.h:1704
void set_unit_of_measurement(const StringRef &ref)
Definition api_pb2.h:1429
void set_device_class(const StringRef &ref)
Definition api_pb2.h:1432
const std::vector< std::string > * options
Definition api_pb2.h:1483
void set_unit_of_measurement(const StringRef &ref)
Definition api_pb2.h:868
void set_device_class(const StringRef &ref)
Definition api_pb2.h:872
enums::SensorStateClass state_class
Definition api_pb2.h:873
void set_device_class(const StringRef &ref)
Definition api_pb2.h:910
void set_pattern(const StringRef &ref)
Definition api_pb2.h:2551
void set_device_class(const StringRef &ref)
Definition api_pb2.h:961
void set_device_class(const StringRef &ref)
Definition api_pb2.h:2858
void set_device_class(const StringRef &ref)
Definition api_pb2.h:2751
enums::LockCommand command
Definition api_pb2.h:1632
enums::MediaPlayerCommand command
Definition api_pb2.h:1740
enums::MediaPlayerState state
Definition api_pb2.h:1721
virtual void encode(ProtoWriteBuffer buffer) const
Definition proto.h:340
virtual const char * message_name() const
Definition proto.h:346
std::string dump() const
Definition proto.cpp:84
virtual void calculate_size(ProtoSize &size) const
Definition proto.h:342
uint32_t get_size() const
Definition proto.h:387
void set_state(const StringRef &ref)
Definition api_pb2.h:1500
void set_message(const uint8_t *data, size_t len)
Definition api_pb2.h:1015
void set_state(const StringRef &ref)
Definition api_pb2.h:978
void set_state(const StringRef &ref)
Definition api_pb2.h:2569
enums::UpdateCommand command
Definition api_pb2.h:2903
void set_current_version(const StringRef &ref)
Definition api_pb2.h:2879
void set_latest_version(const StringRef &ref)
Definition api_pb2.h:2881
void set_release_summary(const StringRef &ref)
Definition api_pb2.h:2885
void set_title(const StringRef &ref)
Definition api_pb2.h:2883
void set_release_url(const StringRef &ref)
Definition api_pb2.h:2887
enums::ValveOperation current_operation
Definition api_pb2.h:2771
std::vector< VoiceAssistantWakeWord > available_wake_words
Definition api_pb2.h:2459
const std::vector< std::string > * active_wake_words
Definition api_pb2.h:2460
std::vector< std::string > active_wake_words
Definition api_pb2.h:2477
Base class for all binary_sensor-type classes.
void bluetooth_gatt_read(const api::BluetoothGATTReadRequest &msg)
void bluetooth_gatt_send_services(const api::BluetoothGATTGetServicesRequest &msg)
void bluetooth_device_request(const api::BluetoothDeviceRequest &msg)
void bluetooth_gatt_write_descriptor(const api::BluetoothGATTWriteDescriptorRequest &msg)
void subscribe_api_connection(api::APIConnection *api_connection, uint32_t flags)
void unsubscribe_api_connection(api::APIConnection *api_connection)
void bluetooth_gatt_read_descriptor(const api::BluetoothGATTReadDescriptorRequest &msg)
void bluetooth_gatt_write(const api::BluetoothGATTWriteRequest &msg)
void bluetooth_gatt_notify(const api::BluetoothGATTNotifyRequest &msg)
Base class for all buttons.
Definition button.h:29
Abstract camera base class.
Definition camera.h:57
virtual CameraImageReader * create_image_reader()=0
Returns a new camera image reader that keeps track of the JPEG data in the camera image.
virtual void start_stream(CameraRequester requester)=0
virtual void stop_stream(CameraRequester requester)=0
virtual void request_image(CameraRequester requester)=0
static Camera * instance()
The singleton instance of the camera implementation.
Definition camera.cpp:19
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:66
Base class for all locks.
Definition lock.h:103
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:59
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
void on_timer_event(const api::VoiceAssistantTimerEventResponse &msg)
void on_audio(const api::VoiceAssistantAudio &msg)
void client_subscription(api::APIConnection *client, bool subscribe)
void on_event(const api::VoiceAssistantEventResponse &msg)
void on_announce(const api::VoiceAssistantAnnounceRequest &msg)
api::APIConnection * get_api_connection() const
void on_set_configuration(const std::vector< std::string > &active_wake_words)
bool state
Definition fan.h:0
uint32_t socklen_t
Definition headers.h:97
std::array< uint8_t, 32 > psk_t
const char * api_error_to_str(APIError err)
BluetoothProxy * global_bluetooth_proxy
ClimatePreset
Enum for all preset modes.
ClimateSwingMode
Enum for all modes a climate swing can be in.
ClimateMode
Enum for all modes a climate device can be in.
FanDirection
Simple enum to represent the direction of a fan.
Definition fan.h:20
HomeassistantTime * global_homeassistant_time
ColorMode
Color modes are a combination of color capabilities that can be used at the same time.
Definition color_mode.h:49
@ COLOR_TEMPERATURE
Color temperature can be controlled.
@ COLD_WARM_WHITE
Brightness of cold and warm white output can be controlled.
VoiceAssistant * global_voice_assistant
std::string size_t len
Definition helpers.h:279
std::string get_mac_address_pretty()
Get the device MAC address as a string, in colon-separated uppercase hex notation.
Definition helpers.cpp:584
void IRAM_ATTR HOT delay(uint32_t ms)
Definition core.cpp:29
Application App
Global storage of Application pointer - only one Application can exist.
size_t base64_decode(const std::string &encoded_string, uint8_t *buf, size_t buf_len)
Definition helpers.cpp:424
A more user-friendly version of struct tm from time.h.
Definition time.h:15
std::vector< uint8_t > container
uint32_t payload_size()