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