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