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