ESPHome 2026.2.2
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#ifdef USE_API_USER_DEFINED_ACTIONS
10#include "user_services.h"
11#endif
12#include <cerrno>
13#include <cinttypes>
14#include <functional>
15#include <limits>
16#include <new>
17#include <utility>
18#ifdef USE_ESP8266
19#include <pgmspace.h>
20#endif
24#include "esphome/core/hal.h"
25#include "esphome/core/log.h"
27
28#ifdef USE_DEEP_SLEEP
30#endif
31#ifdef USE_HOMEASSISTANT_TIME
33#endif
34#ifdef USE_BLUETOOTH_PROXY
36#endif
37#ifdef USE_CLIMATE
39#endif
40#ifdef USE_VOICE_ASSISTANT
42#endif
43#ifdef USE_ZWAVE_PROXY
45#endif
46#ifdef USE_WATER_HEATER
48#endif
49#ifdef USE_INFRARED
51#endif
52
53namespace esphome::api {
54
55// Read a maximum of 5 messages per loop iteration to prevent starving other components.
56// This is a balance between API responsiveness and allowing other components to run.
57// Since each message could contain multiple protobuf messages when using packet batching,
58// this limits the number of messages processed, not the number of TCP packets.
59static constexpr uint8_t MAX_MESSAGES_PER_LOOP = 5;
60static constexpr uint8_t MAX_PING_RETRIES = 60;
61static constexpr uint16_t PING_RETRY_INTERVAL = 1000;
62static constexpr uint32_t KEEPALIVE_DISCONNECT_TIMEOUT = (KEEPALIVE_TIMEOUT_MS * 5) / 2;
63
64static constexpr auto ESPHOME_VERSION_REF = StringRef::from_lit(ESPHOME_VERSION);
65
66static const char *const TAG = "api.connection";
67#ifdef USE_CAMERA
68static const int CAMERA_STOP_STREAM = 5000;
69#endif
70
71#ifdef USE_DEVICES
72// Helper macro for entity command handlers - gets entity by key and device_id, returns if not found, and creates call
73// object
74#define ENTITY_COMMAND_MAKE_CALL(entity_type, entity_var, getter_name) \
75 entity_type *entity_var = App.get_##getter_name##_by_key(msg.key, msg.device_id); \
76 if ((entity_var) == nullptr) \
77 return; \
78 auto call = (entity_var)->make_call();
79
80// Helper macro for entity command handlers that don't use make_call() - gets entity by key and device_id and returns if
81// not found
82#define ENTITY_COMMAND_GET(entity_type, entity_var, getter_name) \
83 entity_type *entity_var = App.get_##getter_name##_by_key(msg.key, msg.device_id); \
84 if ((entity_var) == nullptr) \
85 return;
86#else // No device support, use simpler macros
87// Helper macro for entity command handlers - gets entity by key, returns if not found, and creates call
88// object
89#define ENTITY_COMMAND_MAKE_CALL(entity_type, entity_var, getter_name) \
90 entity_type *entity_var = App.get_##getter_name##_by_key(msg.key); \
91 if ((entity_var) == nullptr) \
92 return; \
93 auto call = (entity_var)->make_call();
94
95// Helper macro for entity command handlers that don't use make_call() - gets entity by key and returns if
96// not found
97#define ENTITY_COMMAND_GET(entity_type, entity_var, getter_name) \
98 entity_type *entity_var = App.get_##getter_name##_by_key(msg.key); \
99 if ((entity_var) == nullptr) \
100 return;
101#endif // USE_DEVICES
102
103APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *parent) : parent_(parent) {
104#if defined(USE_API_PLAINTEXT) && defined(USE_API_NOISE)
105 auto &noise_ctx = parent->get_noise_ctx();
106 if (noise_ctx.has_psk()) {
107 this->helper_ = std::unique_ptr<APIFrameHelper>{new APINoiseFrameHelper(std::move(sock), noise_ctx)};
108 } else {
109 this->helper_ = std::unique_ptr<APIFrameHelper>{new APIPlaintextFrameHelper(std::move(sock))};
110 }
111#elif defined(USE_API_PLAINTEXT)
112 this->helper_ = std::unique_ptr<APIFrameHelper>{new APIPlaintextFrameHelper(std::move(sock))};
113#elif defined(USE_API_NOISE)
114 this->helper_ = std::unique_ptr<APIFrameHelper>{new APINoiseFrameHelper(std::move(sock), parent->get_noise_ctx())};
115#else
116#error "No frame helper defined"
117#endif
118#ifdef USE_CAMERA
119 if (camera::Camera::instance() != nullptr) {
120 this->image_reader_ = std::unique_ptr<camera::CameraImageReader>{camera::Camera::instance()->create_image_reader()};
121 }
122#endif
123}
124
125uint32_t APIConnection::get_batch_delay_ms_() const { return this->parent_->get_batch_delay(); }
126
127void APIConnection::start() {
128 this->last_traffic_ = App.get_loop_component_start_time();
129
130 APIError err = this->helper_->init();
131 if (err != APIError::OK) {
132 this->fatal_error_with_log_(LOG_STR("Helper init failed"), err);
133 return;
134 }
135 // Initialize client name with peername (IP address) until Hello message provides actual name
136 char peername[socket::SOCKADDR_STR_LEN];
137 this->helper_->set_client_name(this->helper_->get_peername_to(peername), strlen(peername));
138}
139
140APIConnection::~APIConnection() {
141 this->destroy_active_iterator_();
142#ifdef USE_BLUETOOTH_PROXY
143 if (bluetooth_proxy::global_bluetooth_proxy->get_api_connection() == this) {
145 }
146#endif
147#ifdef USE_VOICE_ASSISTANT
148 if (voice_assistant::global_voice_assistant->get_api_connection() == this) {
150 }
151#endif
152}
153
154void APIConnection::destroy_active_iterator_() {
155 switch (this->active_iterator_) {
156 case ActiveIterator::LIST_ENTITIES:
157 this->iterator_storage_.list_entities.~ListEntitiesIterator();
158 break;
159 case ActiveIterator::INITIAL_STATE:
160 this->iterator_storage_.initial_state.~InitialStateIterator();
161 break;
162 case ActiveIterator::NONE:
163 break;
164 }
165 this->active_iterator_ = ActiveIterator::NONE;
166}
167
168void APIConnection::begin_iterator_(ActiveIterator type) {
169 this->destroy_active_iterator_();
170 this->active_iterator_ = type;
171 if (type == ActiveIterator::LIST_ENTITIES) {
172 new (&this->iterator_storage_.list_entities) ListEntitiesIterator(this);
173 this->iterator_storage_.list_entities.begin();
174 } else {
175 new (&this->iterator_storage_.initial_state) InitialStateIterator(this);
176 this->iterator_storage_.initial_state.begin();
177 }
178}
179
180void APIConnection::loop() {
181 if (this->flags_.next_close) {
182 // requested a disconnect - don't close socket here, let APIServer::loop() do it
183 // so getpeername() still works for the disconnect trigger
184 this->flags_.remove = true;
185 return;
186 }
187
188 APIError err = this->helper_->loop();
189 if (err != APIError::OK) {
190 this->fatal_error_with_log_(LOG_STR("Socket operation failed"), err);
191 return;
192 }
193
194 const uint32_t now = App.get_loop_component_start_time();
195 // Check if socket has data ready before attempting to read
196 if (this->helper_->is_socket_ready()) {
197 // Read up to MAX_MESSAGES_PER_LOOP messages per loop to improve throughput
198 for (uint8_t message_count = 0; message_count < MAX_MESSAGES_PER_LOOP; message_count++) {
199 ReadPacketBuffer buffer;
200 err = this->helper_->read_packet(&buffer);
201 if (err == APIError::WOULD_BLOCK) {
202 // No more data available
203 break;
204 } else if (err != APIError::OK) {
205 this->fatal_error_with_log_(LOG_STR("Reading failed"), err);
206 return;
207 } else {
208 this->last_traffic_ = now;
209 // read a packet
210 this->read_message(buffer.data_len, buffer.type, buffer.data);
211 if (this->flags_.remove)
212 return;
213 }
214 }
215 }
216
217 // Process deferred batch if scheduled and timer has expired
218 if (this->flags_.batch_scheduled && now - this->deferred_batch_.batch_start_time >= this->get_batch_delay_ms_()) {
219 this->process_batch_();
220 }
221
222 if (this->active_iterator_ != ActiveIterator::NONE) {
223 this->process_active_iterator_();
224 }
225
226 if (this->flags_.sent_ping) {
227 // Disconnect if not responded within 2.5*keepalive
228 if (now - this->last_traffic_ > KEEPALIVE_DISCONNECT_TIMEOUT) {
229 on_fatal_error();
230 this->log_client_(ESPHOME_LOG_LEVEL_WARN, LOG_STR("is unresponsive; disconnecting"));
231 }
232 } else if (now - this->last_traffic_ > KEEPALIVE_TIMEOUT_MS && !this->flags_.remove) {
233 // Only send ping if we're not disconnecting
234 ESP_LOGVV(TAG, "Sending keepalive PING");
235 PingRequest req;
236 this->flags_.sent_ping = this->send_message(req, PingRequest::MESSAGE_TYPE);
237 if (!this->flags_.sent_ping) {
238 // If we can't send the ping request directly (tx_buffer full),
239 // schedule it at the front of the batch so it will be sent with priority
240 ESP_LOGW(TAG, "Buffer full, ping queued");
241 this->schedule_message_front_(nullptr, PingRequest::MESSAGE_TYPE, PingRequest::ESTIMATED_SIZE);
242 this->flags_.sent_ping = true; // Mark as sent to avoid scheduling multiple pings
243 }
244 }
245
246#ifdef USE_API_HOMEASSISTANT_STATES
247 if (state_subs_at_ >= 0) {
248 this->process_state_subscriptions_();
249 }
250#endif
251
252#ifdef USE_CAMERA
253 // Process camera last - state updates are higher priority
254 // (missing a frame is fine, missing a state update is not)
255 this->try_send_camera_image_();
256#endif
257}
258
259void APIConnection::process_active_iterator_() {
260 // Caller ensures active_iterator_ != NONE
261 if (this->active_iterator_ == ActiveIterator::LIST_ENTITIES) {
262 if (this->iterator_storage_.list_entities.completed()) {
263 this->destroy_active_iterator_();
264 if (this->flags_.state_subscription) {
265 this->begin_iterator_(ActiveIterator::INITIAL_STATE);
266 }
267 } else {
268 this->process_iterator_batch_(this->iterator_storage_.list_entities);
269 }
270 } else { // INITIAL_STATE
271 if (this->iterator_storage_.initial_state.completed()) {
272 this->destroy_active_iterator_();
273 // Process any remaining batched messages immediately
274 if (!this->deferred_batch_.empty()) {
275 this->process_batch_();
276 }
277 // Now that everything is sent, enable immediate sending for future state changes
278 this->flags_.should_try_send_immediately = true;
279 // Release excess memory from buffers that grew during initial sync
280 this->deferred_batch_.release_buffer();
281 this->helper_->release_buffers();
282 } else {
283 this->process_iterator_batch_(this->iterator_storage_.initial_state);
284 }
285 }
286}
287
288void APIConnection::process_iterator_batch_(ComponentIterator &iterator) {
289 size_t initial_size = this->deferred_batch_.size();
290 size_t max_batch = this->get_max_batch_size_();
291 while (!iterator.completed() && (this->deferred_batch_.size() - initial_size) < max_batch) {
292 iterator.advance();
293 }
294
295 // If the batch is full, process it immediately
296 // Note: iterator.advance() already calls schedule_batch_() via schedule_message_()
297 if (this->deferred_batch_.size() >= max_batch) {
298 this->process_batch_();
299 }
300}
301
302bool APIConnection::send_disconnect_response_() {
303 // remote initiated disconnect_client
304 // don't close yet, we still need to send the disconnect response
305 // close will happen on next loop
306 this->log_client_(ESPHOME_LOG_LEVEL_DEBUG, LOG_STR("disconnected"));
307 this->flags_.next_close = true;
309 return this->send_message(resp, DisconnectResponse::MESSAGE_TYPE);
310}
311void APIConnection::on_disconnect_response() {
312 // Don't close socket here, let APIServer::loop() do it
313 // so getpeername() still works for the disconnect trigger
314 this->flags_.remove = true;
315}
316
317// Encodes a message to the buffer and returns the total number of bytes used,
318// including header and footer overhead. Returns 0 if the message doesn't fit.
319uint16_t APIConnection::encode_message_to_buffer(ProtoMessage &msg, uint8_t message_type, APIConnection *conn,
320 uint32_t remaining_size) {
321#ifdef HAS_PROTO_MESSAGE_DUMP
322 // If in log-only mode, just log and return
323 if (conn->flags_.log_only_mode) {
324 DumpBuffer dump_buf;
325 conn->log_send_message_(msg.message_name(), msg.dump_to(dump_buf));
326 return 1; // Return non-zero to indicate "success" for logging
327 }
328#endif
329
330 // Calculate size
331 ProtoSize size_calc;
332 msg.calculate_size(size_calc);
333 uint32_t calculated_size = size_calc.get_size();
334
335 // Cache frame sizes to avoid repeated virtual calls
336 const uint8_t header_padding = conn->helper_->frame_header_padding();
337 const uint8_t footer_size = conn->helper_->frame_footer_size();
338
339 // Calculate total size with padding for buffer allocation
340 size_t total_calculated_size = calculated_size + header_padding + footer_size;
341
342 // Check if it fits
343 if (total_calculated_size > remaining_size) {
344 return 0; // Doesn't fit
345 }
346
347 // Get buffer size after allocation (which includes header padding)
348 std::vector<uint8_t> &shared_buf = conn->parent_->get_shared_buffer_ref();
349
350 if (conn->flags_.batch_first_message) {
351 // First message - buffer already prepared by caller, just clear flag
352 conn->flags_.batch_first_message = false;
353 } else {
354 // Batch message second or later
355 // Add padding for previous message footer + this message header
356 size_t current_size = shared_buf.size();
357 shared_buf.reserve(current_size + total_calculated_size);
358 shared_buf.resize(current_size + footer_size + header_padding);
359 }
360
361 // Encode directly into buffer
362 size_t size_before_encode = shared_buf.size();
363 msg.encode({&shared_buf});
364
365 // Calculate actual encoded size (not including header that was already added)
366 size_t actual_payload_size = shared_buf.size() - size_before_encode;
367
368 // Return actual total size (header + actual payload + footer)
369 size_t actual_total_size = header_padding + actual_payload_size + footer_size;
370
371 // Verify that calculate_size() returned the correct value
372 assert(calculated_size == actual_payload_size);
373 return static_cast<uint16_t>(actual_total_size);
374}
375
376#ifdef USE_BINARY_SENSOR
377bool APIConnection::send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor) {
378 return this->send_message_smart_(binary_sensor, BinarySensorStateResponse::MESSAGE_TYPE,
379 BinarySensorStateResponse::ESTIMATED_SIZE);
380}
381
382uint16_t APIConnection::try_send_binary_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
383 auto *binary_sensor = static_cast<binary_sensor::BinarySensor *>(entity);
385 resp.state = binary_sensor->state;
386 resp.missing_state = !binary_sensor->has_state();
387 return fill_and_encode_entity_state(binary_sensor, resp, BinarySensorStateResponse::MESSAGE_TYPE, conn,
388 remaining_size);
389}
390
391uint16_t APIConnection::try_send_binary_sensor_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
392 auto *binary_sensor = static_cast<binary_sensor::BinarySensor *>(entity);
394 msg.device_class = binary_sensor->get_device_class_ref();
395 msg.is_status_binary_sensor = binary_sensor->is_status_binary_sensor();
396 return fill_and_encode_entity_info(binary_sensor, msg, ListEntitiesBinarySensorResponse::MESSAGE_TYPE, conn,
397 remaining_size);
398}
399#endif
400
401#ifdef USE_COVER
402bool APIConnection::send_cover_state(cover::Cover *cover) {
403 return this->send_message_smart_(cover, CoverStateResponse::MESSAGE_TYPE, CoverStateResponse::ESTIMATED_SIZE);
404}
405uint16_t APIConnection::try_send_cover_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
406 auto *cover = static_cast<cover::Cover *>(entity);
408 auto traits = cover->get_traits();
409 msg.position = cover->position;
410 if (traits.get_supports_tilt())
411 msg.tilt = cover->tilt;
412 msg.current_operation = static_cast<enums::CoverOperation>(cover->current_operation);
413 return fill_and_encode_entity_state(cover, msg, CoverStateResponse::MESSAGE_TYPE, conn, remaining_size);
414}
415uint16_t APIConnection::try_send_cover_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
416 auto *cover = static_cast<cover::Cover *>(entity);
418 auto traits = cover->get_traits();
419 msg.assumed_state = traits.get_is_assumed_state();
420 msg.supports_position = traits.get_supports_position();
421 msg.supports_tilt = traits.get_supports_tilt();
422 msg.supports_stop = traits.get_supports_stop();
423 msg.device_class = cover->get_device_class_ref();
424 return fill_and_encode_entity_info(cover, msg, ListEntitiesCoverResponse::MESSAGE_TYPE, conn, remaining_size);
425}
426void APIConnection::on_cover_command_request(const CoverCommandRequest &msg) {
427 ENTITY_COMMAND_MAKE_CALL(cover::Cover, cover, cover)
428 if (msg.has_position)
429 call.set_position(msg.position);
430 if (msg.has_tilt)
431 call.set_tilt(msg.tilt);
432 if (msg.stop)
433 call.set_command_stop();
434 call.perform();
435}
436#endif
437
438#ifdef USE_FAN
439bool APIConnection::send_fan_state(fan::Fan *fan) {
440 return this->send_message_smart_(fan, FanStateResponse::MESSAGE_TYPE, FanStateResponse::ESTIMATED_SIZE);
441}
442uint16_t APIConnection::try_send_fan_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
443 auto *fan = static_cast<fan::Fan *>(entity);
445 auto traits = fan->get_traits();
446 msg.state = fan->state;
447 if (traits.supports_oscillation())
448 msg.oscillating = fan->oscillating;
449 if (traits.supports_speed()) {
450 msg.speed_level = fan->speed;
451 }
452 if (traits.supports_direction())
453 msg.direction = static_cast<enums::FanDirection>(fan->direction);
454 if (traits.supports_preset_modes() && fan->has_preset_mode())
455 msg.preset_mode = fan->get_preset_mode();
456 return fill_and_encode_entity_state(fan, msg, FanStateResponse::MESSAGE_TYPE, conn, remaining_size);
457}
458uint16_t APIConnection::try_send_fan_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
459 auto *fan = static_cast<fan::Fan *>(entity);
461 auto traits = fan->get_traits();
462 msg.supports_oscillation = traits.supports_oscillation();
463 msg.supports_speed = traits.supports_speed();
464 msg.supports_direction = traits.supports_direction();
465 msg.supported_speed_count = traits.supported_speed_count();
466 msg.supported_preset_modes = &traits.supported_preset_modes();
467 return fill_and_encode_entity_info(fan, msg, ListEntitiesFanResponse::MESSAGE_TYPE, conn, remaining_size);
468}
469void APIConnection::on_fan_command_request(const FanCommandRequest &msg) {
470 ENTITY_COMMAND_MAKE_CALL(fan::Fan, fan, fan)
471 if (msg.has_state)
472 call.set_state(msg.state);
473 if (msg.has_oscillating)
474 call.set_oscillating(msg.oscillating);
475 if (msg.has_speed_level) {
476 // Prefer level
477 call.set_speed(msg.speed_level);
478 }
479 if (msg.has_direction)
480 call.set_direction(static_cast<fan::FanDirection>(msg.direction));
481 if (msg.has_preset_mode)
482 call.set_preset_mode(msg.preset_mode.c_str(), msg.preset_mode.size());
483 call.perform();
484}
485#endif
486
487#ifdef USE_LIGHT
488bool APIConnection::send_light_state(light::LightState *light) {
489 return this->send_message_smart_(light, LightStateResponse::MESSAGE_TYPE, LightStateResponse::ESTIMATED_SIZE);
490}
491uint16_t APIConnection::try_send_light_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
492 auto *light = static_cast<light::LightState *>(entity);
494 auto values = light->remote_values;
495 auto color_mode = values.get_color_mode();
496 resp.state = values.is_on();
497 resp.color_mode = static_cast<enums::ColorMode>(color_mode);
498 resp.brightness = values.get_brightness();
499 resp.color_brightness = values.get_color_brightness();
500 resp.red = values.get_red();
501 resp.green = values.get_green();
502 resp.blue = values.get_blue();
503 resp.white = values.get_white();
504 resp.color_temperature = values.get_color_temperature();
505 resp.cold_white = values.get_cold_white();
506 resp.warm_white = values.get_warm_white();
507 if (light->supports_effects()) {
508 resp.effect = light->get_effect_name();
509 }
510 return fill_and_encode_entity_state(light, resp, LightStateResponse::MESSAGE_TYPE, conn, remaining_size);
511}
512uint16_t APIConnection::try_send_light_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
513 auto *light = static_cast<light::LightState *>(entity);
515 auto traits = light->get_traits();
516 auto supported_modes = traits.get_supported_color_modes();
517 // Pass pointer to ColorModeMask so the iterator can encode actual ColorMode enum values
518 msg.supported_color_modes = &supported_modes;
519 if (traits.supports_color_capability(light::ColorCapability::COLOR_TEMPERATURE) ||
520 traits.supports_color_capability(light::ColorCapability::COLD_WARM_WHITE)) {
521 msg.min_mireds = traits.get_min_mireds();
522 msg.max_mireds = traits.get_max_mireds();
523 }
524 FixedVector<const char *> effects_list;
525 if (light->supports_effects()) {
526 auto &light_effects = light->get_effects();
527 effects_list.init(light_effects.size() + 1);
528 effects_list.push_back("None");
529 for (auto *effect : light_effects) {
530 // c_str() is safe as effect names are null-terminated strings from codegen
531 effects_list.push_back(effect->get_name().c_str());
532 }
533 }
534 msg.effects = &effects_list;
535 return fill_and_encode_entity_info(light, msg, ListEntitiesLightResponse::MESSAGE_TYPE, conn, remaining_size);
536}
537void APIConnection::on_light_command_request(const LightCommandRequest &msg) {
538 ENTITY_COMMAND_MAKE_CALL(light::LightState, light, light)
539 if (msg.has_state)
540 call.set_state(msg.state);
541 if (msg.has_brightness)
542 call.set_brightness(msg.brightness);
543 if (msg.has_color_mode)
544 call.set_color_mode(static_cast<light::ColorMode>(msg.color_mode));
545 if (msg.has_color_brightness)
546 call.set_color_brightness(msg.color_brightness);
547 if (msg.has_rgb) {
548 call.set_red(msg.red);
549 call.set_green(msg.green);
550 call.set_blue(msg.blue);
551 }
552 if (msg.has_white)
553 call.set_white(msg.white);
554 if (msg.has_color_temperature)
555 call.set_color_temperature(msg.color_temperature);
556 if (msg.has_cold_white)
557 call.set_cold_white(msg.cold_white);
558 if (msg.has_warm_white)
559 call.set_warm_white(msg.warm_white);
560 if (msg.has_transition_length)
561 call.set_transition_length(msg.transition_length);
562 if (msg.has_flash_length)
563 call.set_flash_length(msg.flash_length);
564 if (msg.has_effect)
565 call.set_effect(msg.effect.c_str(), msg.effect.size());
566 call.perform();
567}
568#endif
569
570#ifdef USE_SENSOR
571bool APIConnection::send_sensor_state(sensor::Sensor *sensor) {
572 return this->send_message_smart_(sensor, SensorStateResponse::MESSAGE_TYPE, SensorStateResponse::ESTIMATED_SIZE);
573}
574
575uint16_t APIConnection::try_send_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
576 auto *sensor = static_cast<sensor::Sensor *>(entity);
578 resp.state = sensor->state;
579 resp.missing_state = !sensor->has_state();
580 return fill_and_encode_entity_state(sensor, resp, SensorStateResponse::MESSAGE_TYPE, conn, remaining_size);
581}
582
583uint16_t APIConnection::try_send_sensor_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
584 auto *sensor = static_cast<sensor::Sensor *>(entity);
586 msg.unit_of_measurement = sensor->get_unit_of_measurement_ref();
587 msg.accuracy_decimals = sensor->get_accuracy_decimals();
588 msg.force_update = sensor->get_force_update();
589 msg.device_class = sensor->get_device_class_ref();
590 msg.state_class = static_cast<enums::SensorStateClass>(sensor->get_state_class());
591 return fill_and_encode_entity_info(sensor, msg, ListEntitiesSensorResponse::MESSAGE_TYPE, conn, remaining_size);
592}
593#endif
594
595#ifdef USE_SWITCH
596bool APIConnection::send_switch_state(switch_::Switch *a_switch) {
597 return this->send_message_smart_(a_switch, SwitchStateResponse::MESSAGE_TYPE, SwitchStateResponse::ESTIMATED_SIZE);
598}
599
600uint16_t APIConnection::try_send_switch_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
601 auto *a_switch = static_cast<switch_::Switch *>(entity);
603 resp.state = a_switch->state;
604 return fill_and_encode_entity_state(a_switch, resp, SwitchStateResponse::MESSAGE_TYPE, conn, remaining_size);
605}
606
607uint16_t APIConnection::try_send_switch_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
608 auto *a_switch = static_cast<switch_::Switch *>(entity);
610 msg.assumed_state = a_switch->assumed_state();
611 msg.device_class = a_switch->get_device_class_ref();
612 return fill_and_encode_entity_info(a_switch, msg, ListEntitiesSwitchResponse::MESSAGE_TYPE, conn, remaining_size);
613}
614void APIConnection::on_switch_command_request(const SwitchCommandRequest &msg) {
615 ENTITY_COMMAND_GET(switch_::Switch, a_switch, switch)
616
617 if (msg.state) {
618 a_switch->turn_on();
619 } else {
620 a_switch->turn_off();
621 }
622}
623#endif
624
625#ifdef USE_TEXT_SENSOR
626bool APIConnection::send_text_sensor_state(text_sensor::TextSensor *text_sensor) {
627 return this->send_message_smart_(text_sensor, TextSensorStateResponse::MESSAGE_TYPE,
628 TextSensorStateResponse::ESTIMATED_SIZE);
629}
630
631uint16_t APIConnection::try_send_text_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
632 auto *text_sensor = static_cast<text_sensor::TextSensor *>(entity);
634 resp.state = StringRef(text_sensor->state);
635 resp.missing_state = !text_sensor->has_state();
636 return fill_and_encode_entity_state(text_sensor, resp, TextSensorStateResponse::MESSAGE_TYPE, conn, remaining_size);
637}
638uint16_t APIConnection::try_send_text_sensor_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
639 auto *text_sensor = static_cast<text_sensor::TextSensor *>(entity);
641 msg.device_class = text_sensor->get_device_class_ref();
642 return fill_and_encode_entity_info(text_sensor, msg, ListEntitiesTextSensorResponse::MESSAGE_TYPE, conn,
643 remaining_size);
644}
645#endif
646
647#ifdef USE_CLIMATE
648bool APIConnection::send_climate_state(climate::Climate *climate) {
649 return this->send_message_smart_(climate, ClimateStateResponse::MESSAGE_TYPE, ClimateStateResponse::ESTIMATED_SIZE);
650}
651uint16_t APIConnection::try_send_climate_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
652 auto *climate = static_cast<climate::Climate *>(entity);
654 auto traits = climate->get_traits();
655 resp.mode = static_cast<enums::ClimateMode>(climate->mode);
656 resp.action = static_cast<enums::ClimateAction>(climate->action);
657 if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_TEMPERATURE))
658 resp.current_temperature = climate->current_temperature;
659 if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE |
661 resp.target_temperature_low = climate->target_temperature_low;
662 resp.target_temperature_high = climate->target_temperature_high;
663 } else {
664 resp.target_temperature = climate->target_temperature;
665 }
666 if (traits.get_supports_fan_modes() && climate->fan_mode.has_value())
667 resp.fan_mode = static_cast<enums::ClimateFanMode>(climate->fan_mode.value());
668 if (!traits.get_supported_custom_fan_modes().empty() && climate->has_custom_fan_mode()) {
669 resp.custom_fan_mode = climate->get_custom_fan_mode();
670 }
671 if (traits.get_supports_presets() && climate->preset.has_value()) {
672 resp.preset = static_cast<enums::ClimatePreset>(climate->preset.value());
673 }
674 if (!traits.get_supported_custom_presets().empty() && climate->has_custom_preset()) {
675 resp.custom_preset = climate->get_custom_preset();
676 }
677 if (traits.get_supports_swing_modes())
678 resp.swing_mode = static_cast<enums::ClimateSwingMode>(climate->swing_mode);
679 if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_HUMIDITY))
680 resp.current_humidity = climate->current_humidity;
681 if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_TARGET_HUMIDITY))
682 resp.target_humidity = climate->target_humidity;
683 return fill_and_encode_entity_state(climate, resp, ClimateStateResponse::MESSAGE_TYPE, conn, remaining_size);
684}
685uint16_t APIConnection::try_send_climate_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
686 auto *climate = static_cast<climate::Climate *>(entity);
688 auto traits = climate->get_traits();
689 // Flags set for backward compatibility, deprecated in 2025.11.0
692 msg.supports_two_point_target_temperature = traits.has_feature_flags(
695 msg.supports_action = traits.has_feature_flags(climate::CLIMATE_SUPPORTS_ACTION);
696 // Current feature flags and other supported parameters
697 msg.feature_flags = traits.get_feature_flags();
698 msg.supported_modes = &traits.get_supported_modes();
699 msg.visual_min_temperature = traits.get_visual_min_temperature();
700 msg.visual_max_temperature = traits.get_visual_max_temperature();
701 msg.visual_target_temperature_step = traits.get_visual_target_temperature_step();
702 msg.visual_current_temperature_step = traits.get_visual_current_temperature_step();
703 msg.visual_min_humidity = traits.get_visual_min_humidity();
704 msg.visual_max_humidity = traits.get_visual_max_humidity();
705 msg.supported_fan_modes = &traits.get_supported_fan_modes();
706 msg.supported_custom_fan_modes = &traits.get_supported_custom_fan_modes();
707 msg.supported_presets = &traits.get_supported_presets();
708 msg.supported_custom_presets = &traits.get_supported_custom_presets();
709 msg.supported_swing_modes = &traits.get_supported_swing_modes();
710 return fill_and_encode_entity_info(climate, msg, ListEntitiesClimateResponse::MESSAGE_TYPE, conn, remaining_size);
711}
712void APIConnection::on_climate_command_request(const ClimateCommandRequest &msg) {
713 ENTITY_COMMAND_MAKE_CALL(climate::Climate, climate, climate)
714 if (msg.has_mode)
715 call.set_mode(static_cast<climate::ClimateMode>(msg.mode));
717 call.set_target_temperature(msg.target_temperature);
719 call.set_target_temperature_low(msg.target_temperature_low);
721 call.set_target_temperature_high(msg.target_temperature_high);
722 if (msg.has_target_humidity)
723 call.set_target_humidity(msg.target_humidity);
724 if (msg.has_fan_mode)
725 call.set_fan_mode(static_cast<climate::ClimateFanMode>(msg.fan_mode));
726 if (msg.has_custom_fan_mode)
727 call.set_fan_mode(msg.custom_fan_mode.c_str(), msg.custom_fan_mode.size());
728 if (msg.has_preset)
729 call.set_preset(static_cast<climate::ClimatePreset>(msg.preset));
730 if (msg.has_custom_preset)
731 call.set_preset(msg.custom_preset.c_str(), msg.custom_preset.size());
732 if (msg.has_swing_mode)
733 call.set_swing_mode(static_cast<climate::ClimateSwingMode>(msg.swing_mode));
734 call.perform();
735}
736#endif
737
738#ifdef USE_NUMBER
739bool APIConnection::send_number_state(number::Number *number) {
740 return this->send_message_smart_(number, NumberStateResponse::MESSAGE_TYPE, NumberStateResponse::ESTIMATED_SIZE);
741}
742
743uint16_t APIConnection::try_send_number_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
744 auto *number = static_cast<number::Number *>(entity);
746 resp.state = number->state;
747 resp.missing_state = !number->has_state();
748 return fill_and_encode_entity_state(number, resp, NumberStateResponse::MESSAGE_TYPE, conn, remaining_size);
749}
750
751uint16_t APIConnection::try_send_number_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
752 auto *number = static_cast<number::Number *>(entity);
754 msg.unit_of_measurement = number->traits.get_unit_of_measurement_ref();
755 msg.mode = static_cast<enums::NumberMode>(number->traits.get_mode());
756 msg.device_class = number->traits.get_device_class_ref();
757 msg.min_value = number->traits.get_min_value();
758 msg.max_value = number->traits.get_max_value();
759 msg.step = number->traits.get_step();
760 return fill_and_encode_entity_info(number, msg, ListEntitiesNumberResponse::MESSAGE_TYPE, conn, remaining_size);
761}
762void APIConnection::on_number_command_request(const NumberCommandRequest &msg) {
763 ENTITY_COMMAND_MAKE_CALL(number::Number, number, number)
764 call.set_value(msg.state);
765 call.perform();
766}
767#endif
768
769#ifdef USE_DATETIME_DATE
770bool APIConnection::send_date_state(datetime::DateEntity *date) {
771 return this->send_message_smart_(date, DateStateResponse::MESSAGE_TYPE, DateStateResponse::ESTIMATED_SIZE);
772}
773uint16_t APIConnection::try_send_date_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
774 auto *date = static_cast<datetime::DateEntity *>(entity);
776 resp.missing_state = !date->has_state();
777 resp.year = date->year;
778 resp.month = date->month;
779 resp.day = date->day;
780 return fill_and_encode_entity_state(date, resp, DateStateResponse::MESSAGE_TYPE, conn, remaining_size);
781}
782uint16_t APIConnection::try_send_date_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
783 auto *date = static_cast<datetime::DateEntity *>(entity);
785 return fill_and_encode_entity_info(date, msg, ListEntitiesDateResponse::MESSAGE_TYPE, conn, remaining_size);
786}
787void APIConnection::on_date_command_request(const DateCommandRequest &msg) {
788 ENTITY_COMMAND_MAKE_CALL(datetime::DateEntity, date, date)
789 call.set_date(msg.year, msg.month, msg.day);
790 call.perform();
791}
792#endif
793
794#ifdef USE_DATETIME_TIME
795bool APIConnection::send_time_state(datetime::TimeEntity *time) {
796 return this->send_message_smart_(time, TimeStateResponse::MESSAGE_TYPE, TimeStateResponse::ESTIMATED_SIZE);
797}
798uint16_t APIConnection::try_send_time_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
799 auto *time = static_cast<datetime::TimeEntity *>(entity);
801 resp.missing_state = !time->has_state();
802 resp.hour = time->hour;
803 resp.minute = time->minute;
804 resp.second = time->second;
805 return fill_and_encode_entity_state(time, resp, TimeStateResponse::MESSAGE_TYPE, conn, remaining_size);
806}
807uint16_t APIConnection::try_send_time_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
808 auto *time = static_cast<datetime::TimeEntity *>(entity);
810 return fill_and_encode_entity_info(time, msg, ListEntitiesTimeResponse::MESSAGE_TYPE, conn, remaining_size);
811}
812void APIConnection::on_time_command_request(const TimeCommandRequest &msg) {
813 ENTITY_COMMAND_MAKE_CALL(datetime::TimeEntity, time, time)
814 call.set_time(msg.hour, msg.minute, msg.second);
815 call.perform();
816}
817#endif
818
819#ifdef USE_DATETIME_DATETIME
820bool APIConnection::send_datetime_state(datetime::DateTimeEntity *datetime) {
821 return this->send_message_smart_(datetime, DateTimeStateResponse::MESSAGE_TYPE,
822 DateTimeStateResponse::ESTIMATED_SIZE);
823}
824uint16_t APIConnection::try_send_datetime_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
825 auto *datetime = static_cast<datetime::DateTimeEntity *>(entity);
827 resp.missing_state = !datetime->has_state();
828 if (datetime->has_state()) {
829 ESPTime state = datetime->state_as_esptime();
830 resp.epoch_seconds = state.timestamp;
831 }
832 return fill_and_encode_entity_state(datetime, resp, DateTimeStateResponse::MESSAGE_TYPE, conn, remaining_size);
833}
834uint16_t APIConnection::try_send_datetime_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
835 auto *datetime = static_cast<datetime::DateTimeEntity *>(entity);
837 return fill_and_encode_entity_info(datetime, msg, ListEntitiesDateTimeResponse::MESSAGE_TYPE, conn, remaining_size);
838}
839void APIConnection::on_date_time_command_request(const DateTimeCommandRequest &msg) {
840 ENTITY_COMMAND_MAKE_CALL(datetime::DateTimeEntity, datetime, datetime)
841 call.set_datetime(msg.epoch_seconds);
842 call.perform();
843}
844#endif
845
846#ifdef USE_TEXT
847bool APIConnection::send_text_state(text::Text *text) {
848 return this->send_message_smart_(text, TextStateResponse::MESSAGE_TYPE, TextStateResponse::ESTIMATED_SIZE);
849}
850
851uint16_t APIConnection::try_send_text_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
852 auto *text = static_cast<text::Text *>(entity);
854 resp.state = StringRef(text->state);
855 resp.missing_state = !text->has_state();
856 return fill_and_encode_entity_state(text, resp, TextStateResponse::MESSAGE_TYPE, conn, remaining_size);
857}
858
859uint16_t APIConnection::try_send_text_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
860 auto *text = static_cast<text::Text *>(entity);
862 msg.mode = static_cast<enums::TextMode>(text->traits.get_mode());
863 msg.min_length = text->traits.get_min_length();
864 msg.max_length = text->traits.get_max_length();
865 msg.pattern = text->traits.get_pattern_ref();
866 return fill_and_encode_entity_info(text, msg, ListEntitiesTextResponse::MESSAGE_TYPE, conn, remaining_size);
867}
868void APIConnection::on_text_command_request(const TextCommandRequest &msg) {
869 ENTITY_COMMAND_MAKE_CALL(text::Text, text, text)
870 call.set_value(msg.state);
871 call.perform();
872}
873#endif
874
875#ifdef USE_SELECT
876bool APIConnection::send_select_state(select::Select *select) {
877 return this->send_message_smart_(select, SelectStateResponse::MESSAGE_TYPE, SelectStateResponse::ESTIMATED_SIZE);
878}
879
880uint16_t APIConnection::try_send_select_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
881 auto *select = static_cast<select::Select *>(entity);
883 resp.state = select->current_option();
884 resp.missing_state = !select->has_state();
885 return fill_and_encode_entity_state(select, resp, SelectStateResponse::MESSAGE_TYPE, conn, remaining_size);
886}
887
888uint16_t APIConnection::try_send_select_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
889 auto *select = static_cast<select::Select *>(entity);
891 msg.options = &select->traits.get_options();
892 return fill_and_encode_entity_info(select, msg, ListEntitiesSelectResponse::MESSAGE_TYPE, conn, remaining_size);
893}
894void APIConnection::on_select_command_request(const SelectCommandRequest &msg) {
895 ENTITY_COMMAND_MAKE_CALL(select::Select, select, select)
896 call.set_option(msg.state.c_str(), msg.state.size());
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 auto *button = static_cast<button::Button *>(entity);
905 msg.device_class = button->get_device_class_ref();
906 return fill_and_encode_entity_info(button, msg, ListEntitiesButtonResponse::MESSAGE_TYPE, conn, remaining_size);
907}
909 ENTITY_COMMAND_GET(button::Button, button, button)
910 button->press();
911}
912#endif
913
914#ifdef USE_LOCK
915bool APIConnection::send_lock_state(lock::Lock *a_lock) {
916 return this->send_message_smart_(a_lock, LockStateResponse::MESSAGE_TYPE, LockStateResponse::ESTIMATED_SIZE);
917}
918
919uint16_t APIConnection::try_send_lock_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
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);
924}
925
926uint16_t APIConnection::try_send_lock_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
927 auto *a_lock = static_cast<lock::Lock *>(entity);
929 msg.assumed_state = a_lock->traits.get_assumed_state();
930 msg.supports_open = a_lock->traits.get_supports_open();
931 msg.requires_code = a_lock->traits.get_requires_code();
932 return fill_and_encode_entity_info(a_lock, msg, ListEntitiesLockResponse::MESSAGE_TYPE, conn, remaining_size);
933}
934void APIConnection::on_lock_command_request(const LockCommandRequest &msg) {
935 ENTITY_COMMAND_GET(lock::Lock, a_lock, lock)
936
937 switch (msg.command) {
938 case enums::LOCK_UNLOCK:
939 a_lock->unlock();
940 break;
941 case enums::LOCK_LOCK:
942 a_lock->lock();
943 break;
944 case enums::LOCK_OPEN:
945 a_lock->open();
946 break;
947 }
948}
949#endif
950
951#ifdef USE_VALVE
952bool APIConnection::send_valve_state(valve::Valve *valve) {
953 return this->send_message_smart_(valve, ValveStateResponse::MESSAGE_TYPE, ValveStateResponse::ESTIMATED_SIZE);
954}
955uint16_t APIConnection::try_send_valve_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
956 auto *valve = static_cast<valve::Valve *>(entity);
958 resp.position = valve->position;
959 resp.current_operation = static_cast<enums::ValveOperation>(valve->current_operation);
960 return fill_and_encode_entity_state(valve, resp, ValveStateResponse::MESSAGE_TYPE, conn, remaining_size);
961}
962uint16_t APIConnection::try_send_valve_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
963 auto *valve = static_cast<valve::Valve *>(entity);
965 auto traits = valve->get_traits();
966 msg.device_class = valve->get_device_class_ref();
967 msg.assumed_state = traits.get_is_assumed_state();
968 msg.supports_position = traits.get_supports_position();
969 msg.supports_stop = traits.get_supports_stop();
970 return fill_and_encode_entity_info(valve, msg, ListEntitiesValveResponse::MESSAGE_TYPE, conn, remaining_size);
971}
972void APIConnection::on_valve_command_request(const ValveCommandRequest &msg) {
973 ENTITY_COMMAND_MAKE_CALL(valve::Valve, valve, valve)
974 if (msg.has_position)
975 call.set_position(msg.position);
976 if (msg.stop)
977 call.set_command_stop();
978 call.perform();
979}
980#endif
981
982#ifdef USE_MEDIA_PLAYER
983bool APIConnection::send_media_player_state(media_player::MediaPlayer *media_player) {
984 return this->send_message_smart_(media_player, MediaPlayerStateResponse::MESSAGE_TYPE,
985 MediaPlayerStateResponse::ESTIMATED_SIZE);
986}
987uint16_t APIConnection::try_send_media_player_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
988 auto *media_player = static_cast<media_player::MediaPlayer *>(entity);
992 : media_player->state;
993 resp.state = static_cast<enums::MediaPlayerState>(report_state);
994 resp.volume = media_player->volume;
995 resp.muted = media_player->is_muted();
996 return fill_and_encode_entity_state(media_player, resp, MediaPlayerStateResponse::MESSAGE_TYPE, conn, remaining_size);
997}
998uint16_t APIConnection::try_send_media_player_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
999 auto *media_player = static_cast<media_player::MediaPlayer *>(entity);
1001 auto traits = media_player->get_traits();
1002 msg.supports_pause = traits.get_supports_pause();
1003 msg.feature_flags = traits.get_feature_flags();
1004 for (auto &supported_format : traits.get_supported_formats()) {
1005 msg.supported_formats.emplace_back();
1006 auto &media_format = msg.supported_formats.back();
1007 media_format.format = StringRef(supported_format.format);
1008 media_format.sample_rate = supported_format.sample_rate;
1009 media_format.num_channels = supported_format.num_channels;
1010 media_format.purpose = static_cast<enums::MediaPlayerFormatPurpose>(supported_format.purpose);
1011 media_format.sample_bytes = supported_format.sample_bytes;
1012 }
1013 return fill_and_encode_entity_info(media_player, msg, ListEntitiesMediaPlayerResponse::MESSAGE_TYPE, conn,
1014 remaining_size);
1015}
1016void APIConnection::on_media_player_command_request(const MediaPlayerCommandRequest &msg) {
1017 ENTITY_COMMAND_MAKE_CALL(media_player::MediaPlayer, media_player, media_player)
1018 if (msg.has_command) {
1019 call.set_command(static_cast<media_player::MediaPlayerCommand>(msg.command));
1020 }
1021 if (msg.has_volume) {
1022 call.set_volume(msg.volume);
1023 }
1024 if (msg.has_media_url) {
1025 call.set_media_url(msg.media_url);
1026 }
1027 if (msg.has_announcement) {
1028 call.set_announcement(msg.announcement);
1029 }
1030 call.perform();
1031}
1032#endif
1033
1034#ifdef USE_CAMERA
1035void APIConnection::try_send_camera_image_() {
1036 if (!this->image_reader_)
1037 return;
1038
1039 // Send as many chunks as possible without blocking
1040 while (this->image_reader_->available()) {
1041 if (!this->helper_->can_write_without_blocking())
1042 return;
1043
1044 uint32_t to_send = std::min((size_t) MAX_BATCH_PACKET_SIZE, this->image_reader_->available());
1045 bool done = this->image_reader_->available() == to_send;
1046
1049 msg.set_data(this->image_reader_->peek_data_buffer(), to_send);
1050 msg.done = done;
1051#ifdef USE_DEVICES
1053#endif
1054
1055 if (!this->send_message_impl(msg, CameraImageResponse::MESSAGE_TYPE)) {
1056 return; // Send failed, try again later
1057 }
1058 this->image_reader_->consume_data(to_send);
1059 if (done) {
1060 this->image_reader_->return_image();
1061 return;
1062 }
1063 }
1064}
1065void APIConnection::set_camera_state(std::shared_ptr<camera::CameraImage> image) {
1066 if (!this->flags_.state_subscription)
1067 return;
1068 if (!this->image_reader_)
1069 return;
1070 if (this->image_reader_->available())
1071 return;
1072 if (image->was_requested_by(esphome::camera::API_REQUESTER) || image->was_requested_by(esphome::camera::IDLE)) {
1073 this->image_reader_->set_image(std::move(image));
1074 // Try to send immediately to reduce latency
1075 this->try_send_camera_image_();
1076 }
1077}
1078uint16_t APIConnection::try_send_camera_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
1079 auto *camera = static_cast<camera::Camera *>(entity);
1081 return fill_and_encode_entity_info(camera, msg, ListEntitiesCameraResponse::MESSAGE_TYPE, conn, remaining_size);
1082}
1083void APIConnection::on_camera_image_request(const CameraImageRequest &msg) {
1084 if (camera::Camera::instance() == nullptr)
1085 return;
1086
1087 if (msg.single)
1089 if (msg.stream) {
1091
1092 App.scheduler.set_timeout(this->parent_, "api_camera_stop_stream", CAMERA_STOP_STREAM,
1094 }
1095}
1096#endif
1097
1098#ifdef USE_HOMEASSISTANT_TIME
1099void APIConnection::on_get_time_response(const GetTimeResponse &value) {
1102#ifdef USE_TIME_TIMEZONE
1103 if (!value.timezone.empty()) {
1105 }
1106#endif
1107 }
1108}
1109#endif
1110
1111#ifdef USE_BLUETOOTH_PROXY
1112void APIConnection::on_subscribe_bluetooth_le_advertisements_request(
1115}
1116void APIConnection::on_unsubscribe_bluetooth_le_advertisements_request() {
1118}
1119void APIConnection::on_bluetooth_device_request(const BluetoothDeviceRequest &msg) {
1121}
1122void APIConnection::on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &msg) {
1124}
1125void APIConnection::on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &msg) {
1127}
1128void APIConnection::on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &msg) {
1130}
1131void APIConnection::on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &msg) {
1133}
1134void APIConnection::on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &msg) {
1136}
1137
1138void APIConnection::on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &msg) {
1140}
1141
1142bool APIConnection::send_subscribe_bluetooth_connections_free_response_() {
1144 return true;
1145}
1146void APIConnection::on_subscribe_bluetooth_connections_free_request() {
1147 if (!this->send_subscribe_bluetooth_connections_free_response_()) {
1148 this->on_fatal_error();
1149 }
1150}
1151
1152void APIConnection::on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &msg) {
1154 msg.mode == enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_ACTIVE);
1155}
1156#endif
1157
1158#ifdef USE_VOICE_ASSISTANT
1159bool APIConnection::check_voice_assistant_api_connection_() const {
1160 return voice_assistant::global_voice_assistant != nullptr &&
1162}
1163
1164void APIConnection::on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) {
1167 }
1168}
1169void APIConnection::on_voice_assistant_response(const VoiceAssistantResponse &msg) {
1170 if (!this->check_voice_assistant_api_connection_()) {
1171 return;
1172 }
1173
1174 if (msg.error) {
1176 return;
1177 }
1178 if (msg.port == 0) {
1179 // Use API Audio
1181 } else {
1182 struct sockaddr_storage storage;
1183 socklen_t len = sizeof(storage);
1184 this->helper_->getpeername((struct sockaddr *) &storage, &len);
1186 }
1187};
1188void APIConnection::on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg) {
1189 if (this->check_voice_assistant_api_connection_()) {
1191 }
1192}
1193void APIConnection::on_voice_assistant_audio(const VoiceAssistantAudio &msg) {
1194 if (this->check_voice_assistant_api_connection_()) {
1196 }
1197};
1198void APIConnection::on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &msg) {
1199 if (this->check_voice_assistant_api_connection_()) {
1201 }
1202};
1203
1204void APIConnection::on_voice_assistant_announce_request(const VoiceAssistantAnnounceRequest &msg) {
1205 if (this->check_voice_assistant_api_connection_()) {
1207 }
1208}
1209
1210bool APIConnection::send_voice_assistant_get_configuration_response_(const VoiceAssistantConfigurationRequest &msg) {
1212 if (!this->check_voice_assistant_api_connection_()) {
1213 return this->send_message(resp, VoiceAssistantConfigurationResponse::MESSAGE_TYPE);
1214 }
1215
1217 for (auto &wake_word : config.available_wake_words) {
1218 resp.available_wake_words.emplace_back();
1219 auto &resp_wake_word = resp.available_wake_words.back();
1220 resp_wake_word.id = StringRef(wake_word.id);
1221 resp_wake_word.wake_word = StringRef(wake_word.wake_word);
1222 for (const auto &lang : wake_word.trained_languages) {
1223 resp_wake_word.trained_languages.push_back(lang);
1224 }
1225 }
1226
1227 // Filter external wake words
1228 for (auto &wake_word : msg.external_wake_words) {
1229 if (wake_word.model_type != "micro") {
1230 // microWakeWord only
1231 continue;
1232 }
1233
1234 resp.available_wake_words.emplace_back();
1235 auto &resp_wake_word = resp.available_wake_words.back();
1236 resp_wake_word.id = StringRef(wake_word.id);
1237 resp_wake_word.wake_word = StringRef(wake_word.wake_word);
1238 for (const auto &lang : wake_word.trained_languages) {
1239 resp_wake_word.trained_languages.push_back(lang);
1240 }
1241 }
1242
1243 resp.active_wake_words = &config.active_wake_words;
1244 resp.max_active_wake_words = config.max_active_wake_words;
1245 return this->send_message(resp, VoiceAssistantConfigurationResponse::MESSAGE_TYPE);
1246}
1247void APIConnection::on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &msg) {
1248 if (!this->send_voice_assistant_get_configuration_response_(msg)) {
1249 this->on_fatal_error();
1250 }
1251}
1252
1253void APIConnection::on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) {
1254 if (this->check_voice_assistant_api_connection_()) {
1256 }
1257}
1258#endif
1259
1260#ifdef USE_ZWAVE_PROXY
1261void APIConnection::on_z_wave_proxy_frame(const ZWaveProxyFrame &msg) {
1263}
1264
1265void APIConnection::on_z_wave_proxy_request(const ZWaveProxyRequest &msg) {
1267}
1268#endif
1269
1270#ifdef USE_ALARM_CONTROL_PANEL
1271bool APIConnection::send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) {
1272 return this->send_message_smart_(a_alarm_control_panel, AlarmControlPanelStateResponse::MESSAGE_TYPE,
1273 AlarmControlPanelStateResponse::ESTIMATED_SIZE);
1274}
1275uint16_t APIConnection::try_send_alarm_control_panel_state(EntityBase *entity, APIConnection *conn,
1276 uint32_t remaining_size) {
1277 auto *a_alarm_control_panel = static_cast<alarm_control_panel::AlarmControlPanel *>(entity);
1279 resp.state = static_cast<enums::AlarmControlPanelState>(a_alarm_control_panel->get_state());
1280 return fill_and_encode_entity_state(a_alarm_control_panel, resp, AlarmControlPanelStateResponse::MESSAGE_TYPE, conn,
1281 remaining_size);
1282}
1283uint16_t APIConnection::try_send_alarm_control_panel_info(EntityBase *entity, APIConnection *conn,
1284 uint32_t remaining_size) {
1285 auto *a_alarm_control_panel = static_cast<alarm_control_panel::AlarmControlPanel *>(entity);
1287 msg.supported_features = a_alarm_control_panel->get_supported_features();
1288 msg.requires_code = a_alarm_control_panel->get_requires_code();
1289 msg.requires_code_to_arm = a_alarm_control_panel->get_requires_code_to_arm();
1290 return fill_and_encode_entity_info(a_alarm_control_panel, msg, ListEntitiesAlarmControlPanelResponse::MESSAGE_TYPE,
1291 conn, remaining_size);
1292}
1293void APIConnection::on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) {
1294 ENTITY_COMMAND_MAKE_CALL(alarm_control_panel::AlarmControlPanel, a_alarm_control_panel, alarm_control_panel)
1295 switch (msg.command) {
1296 case enums::ALARM_CONTROL_PANEL_DISARM:
1297 call.disarm();
1298 break;
1299 case enums::ALARM_CONTROL_PANEL_ARM_AWAY:
1300 call.arm_away();
1301 break;
1302 case enums::ALARM_CONTROL_PANEL_ARM_HOME:
1303 call.arm_home();
1304 break;
1305 case enums::ALARM_CONTROL_PANEL_ARM_NIGHT:
1306 call.arm_night();
1307 break;
1308 case enums::ALARM_CONTROL_PANEL_ARM_VACATION:
1309 call.arm_vacation();
1310 break;
1311 case enums::ALARM_CONTROL_PANEL_ARM_CUSTOM_BYPASS:
1312 call.arm_custom_bypass();
1313 break;
1314 case enums::ALARM_CONTROL_PANEL_TRIGGER:
1315 call.pending();
1316 break;
1317 }
1318 call.set_code(msg.code);
1319 call.perform();
1320}
1321#endif
1322
1323#ifdef USE_WATER_HEATER
1324bool APIConnection::send_water_heater_state(water_heater::WaterHeater *water_heater) {
1325 return this->send_message_smart_(water_heater, WaterHeaterStateResponse::MESSAGE_TYPE,
1326 WaterHeaterStateResponse::ESTIMATED_SIZE);
1327}
1328uint16_t APIConnection::try_send_water_heater_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
1329 auto *wh = static_cast<water_heater::WaterHeater *>(entity);
1331 resp.mode = static_cast<enums::WaterHeaterMode>(wh->get_mode());
1332 resp.current_temperature = wh->get_current_temperature();
1333 resp.target_temperature = wh->get_target_temperature();
1334 resp.target_temperature_low = wh->get_target_temperature_low();
1335 resp.target_temperature_high = wh->get_target_temperature_high();
1336 resp.state = wh->get_state();
1337
1338 return fill_and_encode_entity_state(wh, resp, WaterHeaterStateResponse::MESSAGE_TYPE, conn, remaining_size);
1339}
1340uint16_t APIConnection::try_send_water_heater_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
1341 auto *wh = static_cast<water_heater::WaterHeater *>(entity);
1343 auto traits = wh->get_traits();
1344 msg.min_temperature = traits.get_min_temperature();
1345 msg.max_temperature = traits.get_max_temperature();
1346 msg.target_temperature_step = traits.get_target_temperature_step();
1347 msg.supported_modes = &traits.get_supported_modes();
1348 msg.supported_features = traits.get_feature_flags();
1349 return fill_and_encode_entity_info(wh, msg, ListEntitiesWaterHeaterResponse::MESSAGE_TYPE, conn, remaining_size);
1350}
1351
1352void APIConnection::on_water_heater_command_request(const WaterHeaterCommandRequest &msg) {
1353 ENTITY_COMMAND_MAKE_CALL(water_heater::WaterHeater, water_heater, water_heater)
1354 if (msg.has_fields & enums::WATER_HEATER_COMMAND_HAS_MODE)
1355 call.set_mode(static_cast<water_heater::WaterHeaterMode>(msg.mode));
1356 if (msg.has_fields & enums::WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE)
1357 call.set_target_temperature(msg.target_temperature);
1358 if (msg.has_fields & enums::WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_LOW)
1359 call.set_target_temperature_low(msg.target_temperature_low);
1360 if (msg.has_fields & enums::WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_HIGH)
1361 call.set_target_temperature_high(msg.target_temperature_high);
1362 if ((msg.has_fields & enums::WATER_HEATER_COMMAND_HAS_AWAY_STATE) ||
1363 (msg.has_fields & enums::WATER_HEATER_COMMAND_HAS_STATE)) {
1364 call.set_away((msg.state & water_heater::WATER_HEATER_STATE_AWAY) != 0);
1365 }
1366 if ((msg.has_fields & enums::WATER_HEATER_COMMAND_HAS_ON_STATE) ||
1367 (msg.has_fields & enums::WATER_HEATER_COMMAND_HAS_STATE)) {
1368 call.set_on((msg.state & water_heater::WATER_HEATER_STATE_ON) != 0);
1369 }
1370 call.perform();
1371}
1372#endif
1373
1374#ifdef USE_EVENT
1375// Event is a special case - unlike other entities with simple state fields,
1376// events store their state in a member accessed via obj->get_last_event_type()
1377void APIConnection::send_event(event::Event *event) {
1378 this->send_message_smart_(event, EventResponse::MESSAGE_TYPE, EventResponse::ESTIMATED_SIZE,
1379 event->get_last_event_type_index());
1380}
1381uint16_t APIConnection::try_send_event_response(event::Event *event, StringRef event_type, APIConnection *conn,
1382 uint32_t remaining_size) {
1383 EventResponse resp;
1384 resp.event_type = event_type;
1385 return fill_and_encode_entity_state(event, resp, EventResponse::MESSAGE_TYPE, conn, remaining_size);
1386}
1387
1388uint16_t APIConnection::try_send_event_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
1389 auto *event = static_cast<event::Event *>(entity);
1391 msg.device_class = event->get_device_class_ref();
1392 msg.event_types = &event->get_event_types();
1393 return fill_and_encode_entity_info(event, msg, ListEntitiesEventResponse::MESSAGE_TYPE, conn, remaining_size);
1394}
1395#endif
1396
1397#ifdef USE_IR_RF
1398void APIConnection::on_infrared_rf_transmit_raw_timings_request(const InfraredRFTransmitRawTimingsRequest &msg) {
1399 // TODO: When RF is implemented, add a field to the message to distinguish IR vs RF
1400 // and dispatch to the appropriate entity type based on that field.
1401#ifdef USE_INFRARED
1402 ENTITY_COMMAND_MAKE_CALL(infrared::Infrared, infrared, infrared)
1403 call.set_carrier_frequency(msg.carrier_frequency);
1404 call.set_raw_timings_packed(msg.timings_data_, msg.timings_length_, msg.timings_count_);
1405 call.set_repeat_count(msg.repeat_count);
1406 call.perform();
1407#endif
1408}
1409
1410void APIConnection::send_infrared_rf_receive_event(const InfraredRFReceiveEvent &msg) {
1411 this->send_message(msg, InfraredRFReceiveEvent::MESSAGE_TYPE);
1412}
1413#endif
1414
1415#ifdef USE_INFRARED
1416uint16_t APIConnection::try_send_infrared_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
1417 auto *infrared = static_cast<infrared::Infrared *>(entity);
1419 msg.capabilities = infrared->get_capability_flags();
1420 return fill_and_encode_entity_info(infrared, msg, ListEntitiesInfraredResponse::MESSAGE_TYPE, conn, remaining_size);
1421}
1422#endif
1423
1424#ifdef USE_UPDATE
1425bool APIConnection::send_update_state(update::UpdateEntity *update) {
1426 return this->send_message_smart_(update, UpdateStateResponse::MESSAGE_TYPE, UpdateStateResponse::ESTIMATED_SIZE);
1427}
1428uint16_t APIConnection::try_send_update_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
1429 auto *update = static_cast<update::UpdateEntity *>(entity);
1431 resp.missing_state = !update->has_state();
1432 if (update->has_state()) {
1434 if (update->update_info.has_progress) {
1435 resp.has_progress = true;
1436 resp.progress = update->update_info.progress;
1437 }
1438 resp.current_version = StringRef(update->update_info.current_version);
1439 resp.latest_version = StringRef(update->update_info.latest_version);
1440 resp.title = StringRef(update->update_info.title);
1441 resp.release_summary = StringRef(update->update_info.summary);
1442 resp.release_url = StringRef(update->update_info.release_url);
1443 }
1444 return fill_and_encode_entity_state(update, resp, UpdateStateResponse::MESSAGE_TYPE, conn, remaining_size);
1445}
1446uint16_t APIConnection::try_send_update_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
1447 auto *update = static_cast<update::UpdateEntity *>(entity);
1449 msg.device_class = update->get_device_class_ref();
1450 return fill_and_encode_entity_info(update, msg, ListEntitiesUpdateResponse::MESSAGE_TYPE, conn, remaining_size);
1451}
1452void APIConnection::on_update_command_request(const UpdateCommandRequest &msg) {
1453 ENTITY_COMMAND_GET(update::UpdateEntity, update, update)
1454
1455 switch (msg.command) {
1456 case enums::UPDATE_COMMAND_UPDATE:
1457 update->perform();
1458 break;
1459 case enums::UPDATE_COMMAND_CHECK:
1460 update->check();
1461 break;
1462 case enums::UPDATE_COMMAND_NONE:
1463 ESP_LOGE(TAG, "UPDATE_COMMAND_NONE not handled; confirm command is correct");
1464 break;
1465 default:
1466 ESP_LOGW(TAG, "Unknown update command: %" PRIu32, msg.command);
1467 break;
1468 }
1469}
1470#endif
1471
1472bool APIConnection::try_send_log_message(int level, const char *tag, const char *line, size_t message_len) {
1474 msg.level = static_cast<enums::LogLevel>(level);
1475 msg.set_message(reinterpret_cast<const uint8_t *>(line), message_len);
1476 return this->send_message_impl(msg, SubscribeLogsResponse::MESSAGE_TYPE);
1477}
1478
1479void APIConnection::complete_authentication_() {
1480 // Early return if already authenticated
1481 if (this->flags_.connection_state == static_cast<uint8_t>(ConnectionState::AUTHENTICATED)) {
1482 return;
1483 }
1484
1485 this->flags_.connection_state = static_cast<uint8_t>(ConnectionState::AUTHENTICATED);
1486 this->log_client_(ESPHOME_LOG_LEVEL_DEBUG, LOG_STR("connected"));
1487#ifdef USE_API_CLIENT_CONNECTED_TRIGGER
1488 {
1489 char peername[socket::SOCKADDR_STR_LEN];
1490 this->parent_->get_client_connected_trigger()->trigger(std::string(this->helper_->get_client_name()),
1491 std::string(this->helper_->get_peername_to(peername)));
1492 }
1493#endif
1494#ifdef USE_HOMEASSISTANT_TIME
1496 this->send_time_request();
1497 }
1498#endif
1499#ifdef USE_ZWAVE_PROXY
1500 if (zwave_proxy::global_zwave_proxy != nullptr) {
1502 }
1503#endif
1504}
1505
1506bool APIConnection::send_hello_response_(const HelloRequest &msg) {
1507 // Copy client name with truncation if needed (set_client_name handles truncation)
1508 this->helper_->set_client_name(msg.client_info.c_str(), msg.client_info.size());
1509 this->client_api_version_major_ = msg.api_version_major;
1510 this->client_api_version_minor_ = msg.api_version_minor;
1511 char peername[socket::SOCKADDR_STR_LEN];
1512 ESP_LOGV(TAG, "Hello from client: '%s' | %s | API Version %" PRIu16 ".%" PRIu16, this->helper_->get_client_name(),
1513 this->helper_->get_peername_to(peername), this->client_api_version_major_, this->client_api_version_minor_);
1514
1515 HelloResponse resp;
1516 resp.api_version_major = 1;
1517 resp.api_version_minor = 14;
1518 // Send only the version string - the client only logs this for debugging and doesn't use it otherwise
1519 resp.server_info = ESPHOME_VERSION_REF;
1520 resp.name = StringRef(App.get_name());
1521
1522 // Auto-authenticate - password auth was removed in ESPHome 2026.1.0
1523 this->complete_authentication_();
1524
1525 return this->send_message(resp, HelloResponse::MESSAGE_TYPE);
1526}
1527
1528bool APIConnection::send_ping_response_() {
1529 PingResponse resp;
1530 return this->send_message(resp, PingResponse::MESSAGE_TYPE);
1531}
1532
1533bool APIConnection::send_device_info_response_() {
1534 DeviceInfoResponse resp{};
1535 resp.name = StringRef(App.get_name());
1536 resp.friendly_name = StringRef(App.get_friendly_name());
1537#ifdef USE_AREAS
1538 resp.suggested_area = StringRef(App.get_area());
1539#endif
1540 // Stack buffer for MAC address (XX:XX:XX:XX:XX:XX\0 = 18 bytes)
1541 char mac_address[18];
1542 uint8_t mac[6];
1544 format_mac_addr_upper(mac, mac_address);
1545 resp.mac_address = StringRef(mac_address);
1546
1547 resp.esphome_version = ESPHOME_VERSION_REF;
1548
1549 // Stack buffer for build time string
1550 char build_time_str[Application::BUILD_TIME_STR_SIZE];
1551 App.get_build_time_string(build_time_str);
1552 resp.compilation_time = StringRef(build_time_str);
1553
1554 // Manufacturer string - define once, handle ESP8266 PROGMEM separately
1555#if defined(USE_ESP8266) || defined(USE_ESP32)
1556#define ESPHOME_MANUFACTURER "Espressif"
1557#elif defined(USE_RP2040)
1558#define ESPHOME_MANUFACTURER "Raspberry Pi"
1559#elif defined(USE_BK72XX)
1560#define ESPHOME_MANUFACTURER "Beken"
1561#elif defined(USE_LN882X)
1562#define ESPHOME_MANUFACTURER "Lightning"
1563#elif defined(USE_NRF52)
1564#define ESPHOME_MANUFACTURER "Nordic Semiconductor"
1565#elif defined(USE_RTL87XX)
1566#define ESPHOME_MANUFACTURER "Realtek"
1567#elif defined(USE_HOST)
1568#define ESPHOME_MANUFACTURER "Host"
1569#endif
1570
1571#ifdef USE_ESP8266
1572 // ESP8266 requires PROGMEM for flash storage, copy to stack for memcpy compatibility
1573 static const char MANUFACTURER_PROGMEM[] PROGMEM = ESPHOME_MANUFACTURER;
1574 char manufacturer_buf[sizeof(MANUFACTURER_PROGMEM)];
1575 memcpy_P(manufacturer_buf, MANUFACTURER_PROGMEM, sizeof(MANUFACTURER_PROGMEM));
1576 resp.manufacturer = StringRef(manufacturer_buf, sizeof(MANUFACTURER_PROGMEM) - 1);
1577#else
1578 static constexpr auto MANUFACTURER = StringRef::from_lit(ESPHOME_MANUFACTURER);
1579 resp.manufacturer = MANUFACTURER;
1580#endif
1581#undef ESPHOME_MANUFACTURER
1582
1583#ifdef USE_ESP8266
1584 static const char MODEL_PROGMEM[] PROGMEM = ESPHOME_BOARD;
1585 char model_buf[sizeof(MODEL_PROGMEM)];
1586 memcpy_P(model_buf, MODEL_PROGMEM, sizeof(MODEL_PROGMEM));
1587 resp.model = StringRef(model_buf, sizeof(MODEL_PROGMEM) - 1);
1588#else
1589 static constexpr auto MODEL = StringRef::from_lit(ESPHOME_BOARD);
1590 resp.model = MODEL;
1591#endif
1592#ifdef USE_DEEP_SLEEP
1593 resp.has_deep_sleep = deep_sleep::global_has_deep_sleep;
1594#endif
1595#ifdef ESPHOME_PROJECT_NAME
1596#ifdef USE_ESP8266
1597 static const char PROJECT_NAME_PROGMEM[] PROGMEM = ESPHOME_PROJECT_NAME;
1598 static const char PROJECT_VERSION_PROGMEM[] PROGMEM = ESPHOME_PROJECT_VERSION;
1599 char project_name_buf[sizeof(PROJECT_NAME_PROGMEM)];
1600 char project_version_buf[sizeof(PROJECT_VERSION_PROGMEM)];
1601 memcpy_P(project_name_buf, PROJECT_NAME_PROGMEM, sizeof(PROJECT_NAME_PROGMEM));
1602 memcpy_P(project_version_buf, PROJECT_VERSION_PROGMEM, sizeof(PROJECT_VERSION_PROGMEM));
1603 resp.project_name = StringRef(project_name_buf, sizeof(PROJECT_NAME_PROGMEM) - 1);
1604 resp.project_version = StringRef(project_version_buf, sizeof(PROJECT_VERSION_PROGMEM) - 1);
1605#else
1606 static constexpr auto PROJECT_NAME = StringRef::from_lit(ESPHOME_PROJECT_NAME);
1607 static constexpr auto PROJECT_VERSION = StringRef::from_lit(ESPHOME_PROJECT_VERSION);
1608 resp.project_name = PROJECT_NAME;
1609 resp.project_version = PROJECT_VERSION;
1610#endif
1611#endif
1612#ifdef USE_WEBSERVER
1613 resp.webserver_port = USE_WEBSERVER_PORT;
1614#endif
1615#ifdef USE_BLUETOOTH_PROXY
1616 resp.bluetooth_proxy_feature_flags = bluetooth_proxy::global_bluetooth_proxy->get_feature_flags();
1617 // Stack buffer for Bluetooth MAC address (XX:XX:XX:XX:XX:XX\0 = 18 bytes)
1618 char bluetooth_mac[18];
1620 resp.bluetooth_mac_address = StringRef(bluetooth_mac);
1621#endif
1622#ifdef USE_VOICE_ASSISTANT
1623 resp.voice_assistant_feature_flags = voice_assistant::global_voice_assistant->get_feature_flags();
1624#endif
1625#ifdef USE_ZWAVE_PROXY
1626 resp.zwave_proxy_feature_flags = zwave_proxy::global_zwave_proxy->get_feature_flags();
1627 resp.zwave_home_id = zwave_proxy::global_zwave_proxy->get_home_id();
1628#endif
1629#ifdef USE_API_NOISE
1630 resp.api_encryption_supported = true;
1631#endif
1632#ifdef USE_DEVICES
1633 size_t device_index = 0;
1634 for (auto const &device : App.get_devices()) {
1635 if (device_index >= ESPHOME_DEVICE_COUNT)
1636 break;
1637 auto &device_info = resp.devices[device_index++];
1638 device_info.device_id = device->get_device_id();
1639 device_info.name = StringRef(device->get_name());
1640 device_info.area_id = device->get_area_id();
1641 }
1642#endif
1643#ifdef USE_AREAS
1644 size_t area_index = 0;
1645 for (auto const &area : App.get_areas()) {
1646 if (area_index >= ESPHOME_AREA_COUNT)
1647 break;
1648 auto &area_info = resp.areas[area_index++];
1649 area_info.area_id = area->get_area_id();
1650 area_info.name = StringRef(area->get_name());
1651 }
1652#endif
1653
1654 return this->send_message(resp, DeviceInfoResponse::MESSAGE_TYPE);
1655}
1656void APIConnection::on_hello_request(const HelloRequest &msg) {
1657 if (!this->send_hello_response_(msg)) {
1658 this->on_fatal_error();
1659 }
1660}
1661void APIConnection::on_disconnect_request() {
1662 if (!this->send_disconnect_response_()) {
1663 this->on_fatal_error();
1664 }
1665}
1666void APIConnection::on_ping_request() {
1667 if (!this->send_ping_response_()) {
1668 this->on_fatal_error();
1669 }
1670}
1671void APIConnection::on_device_info_request() {
1672 if (!this->send_device_info_response_()) {
1673 this->on_fatal_error();
1674 }
1675}
1676
1677#ifdef USE_API_HOMEASSISTANT_STATES
1678void APIConnection::on_home_assistant_state_response(const HomeAssistantStateResponse &msg) {
1679 // Skip if entity_id is empty (invalid message)
1680 if (msg.entity_id.empty()) {
1681 return;
1682 }
1683
1684 for (auto &it : this->parent_->get_state_subs()) {
1685 // Compare entity_id: check length matches and content matches
1686 size_t entity_id_len = strlen(it.entity_id);
1687 if (entity_id_len != msg.entity_id.size() ||
1688 memcmp(it.entity_id, msg.entity_id.c_str(), msg.entity_id.size()) != 0) {
1689 continue;
1690 }
1691
1692 // Compare attribute: either both have matching attribute, or both have none
1693 size_t sub_attr_len = it.attribute != nullptr ? strlen(it.attribute) : 0;
1694 if (sub_attr_len != msg.attribute.size() ||
1695 (sub_attr_len > 0 && memcmp(it.attribute, msg.attribute.c_str(), sub_attr_len) != 0)) {
1696 continue;
1697 }
1698
1699 // Create null-terminated state for callback (parse_number needs null-termination)
1700 // HA state max length is 255 characters, but attributes can be much longer
1701 // Use stack buffer for common case (states), heap fallback for large attributes
1702 size_t state_len = msg.state.size();
1703 SmallBufferWithHeapFallback<MAX_STATE_LEN + 1> state_buf_alloc(state_len + 1);
1704 char *state_buf = reinterpret_cast<char *>(state_buf_alloc.get());
1705 if (state_len > 0) {
1706 memcpy(state_buf, msg.state.c_str(), state_len);
1707 }
1708 state_buf[state_len] = '\0';
1709 it.callback(StringRef(state_buf, state_len));
1710 }
1711}
1712#endif
1713#ifdef USE_API_USER_DEFINED_ACTIONS
1714void APIConnection::on_execute_service_request(const ExecuteServiceRequest &msg) {
1715 bool found = false;
1716#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES
1717 // Register the call and get a unique server-generated action_call_id
1718 // This avoids collisions when multiple clients use the same call_id
1719 uint32_t action_call_id = 0;
1720 if (msg.call_id != 0) {
1721 action_call_id = this->parent_->register_active_action_call(msg.call_id, this);
1722 }
1723 // Use the overload that passes action_call_id separately (avoids copying msg)
1724 for (auto *service : this->parent_->get_user_services()) {
1725 if (service->execute_service(msg, action_call_id)) {
1726 found = true;
1727 }
1728 }
1729#else
1730 for (auto *service : this->parent_->get_user_services()) {
1731 if (service->execute_service(msg)) {
1732 found = true;
1733 }
1734 }
1735#endif
1736 if (!found) {
1737 ESP_LOGV(TAG, "Could not find service");
1738 }
1739 // Note: For services with supports_response != none, the call is unregistered
1740 // by an automatically appended APIUnregisterServiceCallAction at the end of
1741 // the action list. This ensures async actions (delays, waits) complete first.
1742}
1743#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES
1744void APIConnection::send_execute_service_response(uint32_t call_id, bool success, StringRef error_message) {
1746 resp.call_id = call_id;
1747 resp.success = success;
1748 resp.error_message = error_message;
1749 this->send_message(resp, ExecuteServiceResponse::MESSAGE_TYPE);
1750}
1751#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
1752void APIConnection::send_execute_service_response(uint32_t call_id, bool success, StringRef error_message,
1753 const uint8_t *response_data, size_t response_data_len) {
1755 resp.call_id = call_id;
1756 resp.success = success;
1757 resp.error_message = error_message;
1758 resp.response_data = response_data;
1759 resp.response_data_len = response_data_len;
1760 this->send_message(resp, ExecuteServiceResponse::MESSAGE_TYPE);
1761}
1762#endif // USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
1763#endif // USE_API_USER_DEFINED_ACTION_RESPONSES
1764#endif
1765
1766#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
1767void APIConnection::on_homeassistant_action_response(const HomeassistantActionResponse &msg) {
1768#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
1769 if (msg.response_data_len > 0) {
1770 this->parent_->handle_action_response(msg.call_id, msg.success, msg.error_message, msg.response_data,
1771 msg.response_data_len);
1772 } else
1773#endif
1774 {
1775 this->parent_->handle_action_response(msg.call_id, msg.success, msg.error_message);
1776 }
1777};
1778#endif
1779#ifdef USE_API_NOISE
1780bool APIConnection::send_noise_encryption_set_key_response_(const NoiseEncryptionSetKeyRequest &msg) {
1782 resp.success = false;
1783
1784 psk_t psk{};
1785 if (msg.key_len == 0) {
1786 if (this->parent_->clear_noise_psk(true)) {
1787 resp.success = true;
1788 } else {
1789 ESP_LOGW(TAG, "Failed to clear encryption key");
1790 }
1791 } else if (base64_decode(msg.key, msg.key_len, psk.data(), psk.size()) != psk.size()) {
1792 ESP_LOGW(TAG, "Invalid encryption key length");
1793 } else if (!this->parent_->save_noise_psk(psk, true)) {
1794 ESP_LOGW(TAG, "Failed to save encryption key");
1795 } else {
1796 resp.success = true;
1797 }
1798
1799 return this->send_message(resp, NoiseEncryptionSetKeyResponse::MESSAGE_TYPE);
1800}
1801void APIConnection::on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) {
1802 if (!this->send_noise_encryption_set_key_response_(msg)) {
1803 this->on_fatal_error();
1804 }
1805}
1806#endif
1807#ifdef USE_API_HOMEASSISTANT_STATES
1808void APIConnection::on_subscribe_home_assistant_states_request() { state_subs_at_ = 0; }
1809#endif
1810bool APIConnection::try_to_clear_buffer(bool log_out_of_space) {
1811 if (this->flags_.remove)
1812 return false;
1813 if (this->helper_->can_write_without_blocking())
1814 return true;
1815 delay(0);
1816 APIError err = this->helper_->loop();
1817 if (err != APIError::OK) {
1818 this->fatal_error_with_log_(LOG_STR("Socket operation failed"), err);
1819 return false;
1820 }
1821 if (this->helper_->can_write_without_blocking())
1822 return true;
1823 if (log_out_of_space) {
1824 ESP_LOGV(TAG, "Cannot send message because of TCP buffer space");
1825 }
1826 return false;
1827}
1828bool APIConnection::send_message_impl(const ProtoMessage &msg, uint8_t message_type) {
1830 msg.calculate_size(size);
1831 std::vector<uint8_t> &shared_buf = this->parent_->get_shared_buffer_ref();
1832 this->prepare_first_message_buffer(shared_buf, size.get_size());
1833 msg.encode({&shared_buf});
1834 return this->send_buffer({&shared_buf}, message_type);
1835}
1836bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) {
1837 const bool is_log_message = (message_type == SubscribeLogsResponse::MESSAGE_TYPE);
1838
1839 if (!this->try_to_clear_buffer(!is_log_message)) {
1840 return false;
1841 }
1842
1843 // Set TCP_NODELAY based on message type - see set_nodelay_for_message() for details
1844 this->helper_->set_nodelay_for_message(is_log_message);
1845
1846 APIError err = this->helper_->write_protobuf_packet(message_type, buffer);
1847 if (err == APIError::WOULD_BLOCK)
1848 return false;
1849 if (err != APIError::OK) {
1850 this->fatal_error_with_log_(LOG_STR("Packet write failed"), err);
1851 return false;
1852 }
1853 // Do not set last_traffic_ on send
1854 return true;
1855}
1856void APIConnection::on_no_setup_connection() {
1857 this->on_fatal_error();
1858 this->log_client_(ESPHOME_LOG_LEVEL_DEBUG, LOG_STR("no connection setup"));
1859}
1860void APIConnection::on_fatal_error() {
1861 // Don't close socket here - keep it open so getpeername() works for logging
1862 // Socket will be closed when client is removed from the list in APIServer::loop()
1863 this->flags_.remove = true;
1864}
1865
1866void APIConnection::DeferredBatch::add_item(EntityBase *entity, uint8_t message_type, uint8_t estimated_size,
1867 uint8_t aux_data_index) {
1868 // Check if we already have a message of this type for this entity
1869 // This provides deduplication per entity/message_type combination
1870 // O(n) but optimized for RAM and not performance.
1871 // Skip deduplication for events - they are edge-triggered, every occurrence matters
1872#ifdef USE_EVENT
1873 if (message_type != EventResponse::MESSAGE_TYPE)
1874#endif
1875 {
1876 for (const auto &item : items) {
1877 if (item.entity == entity && item.message_type == message_type)
1878 return; // Already queued
1879 }
1880 }
1881 // No existing item found (or event), add new one
1882 items.push_back({entity, message_type, estimated_size, aux_data_index});
1883}
1884
1885void APIConnection::DeferredBatch::add_item_front(EntityBase *entity, uint8_t message_type, uint8_t estimated_size) {
1886 // Add high priority message and swap to front
1887 // This avoids expensive vector::insert which shifts all elements
1888 // Note: We only ever have one high-priority message at a time (ping OR disconnect)
1889 // If we're disconnecting, pings are blocked, so this simple swap is sufficient
1890 items.push_back({entity, message_type, estimated_size, AUX_DATA_UNUSED});
1891 if (items.size() > 1) {
1892 // Swap the new high-priority item to the front
1893 std::swap(items.front(), items.back());
1894 }
1895}
1896
1897bool APIConnection::send_message_smart_(EntityBase *entity, uint8_t message_type, uint8_t estimated_size,
1898 uint8_t aux_data_index) {
1899 if (this->should_send_immediately_(message_type) && this->helper_->can_write_without_blocking()) {
1900 auto &shared_buf = this->parent_->get_shared_buffer_ref();
1901 this->prepare_first_message_buffer(shared_buf, estimated_size);
1902 DeferredBatch::BatchItem item{entity, message_type, estimated_size, aux_data_index};
1903 if (this->dispatch_message_(item, MAX_BATCH_PACKET_SIZE, true) &&
1904 this->send_buffer(ProtoWriteBuffer{&shared_buf}, message_type)) {
1905#ifdef HAS_PROTO_MESSAGE_DUMP
1906 this->log_batch_item_(item);
1907#endif
1908 return true;
1909 }
1910 }
1911 return this->schedule_message_(entity, message_type, estimated_size, aux_data_index);
1912}
1913
1914bool APIConnection::schedule_batch_() {
1915 if (!this->flags_.batch_scheduled) {
1916 this->flags_.batch_scheduled = true;
1917 this->deferred_batch_.batch_start_time = App.get_loop_component_start_time();
1918 }
1919 return true;
1920}
1921
1922void APIConnection::process_batch_() {
1923 if (this->deferred_batch_.empty()) {
1924 this->flags_.batch_scheduled = false;
1925 return;
1926 }
1927
1928 // Try to clear buffer first
1929 if (!this->try_to_clear_buffer(true)) {
1930 // Can't write now, we'll try again later
1931 return;
1932 }
1933
1934 // Get shared buffer reference once to avoid multiple calls
1935 auto &shared_buf = this->parent_->get_shared_buffer_ref();
1936 size_t num_items = this->deferred_batch_.size();
1937
1938 // Cache these values to avoid repeated virtual calls
1939 const uint8_t header_padding = this->helper_->frame_header_padding();
1940 const uint8_t footer_size = this->helper_->frame_footer_size();
1941
1942 // Pre-calculate exact buffer size needed based on message types
1943 uint32_t total_estimated_size = num_items * (header_padding + footer_size);
1944 for (size_t i = 0; i < num_items; i++) {
1945 total_estimated_size += this->deferred_batch_[i].estimated_size;
1946 }
1947 // Clamp to MAX_BATCH_PACKET_SIZE — we won't send more than that per batch
1948 if (total_estimated_size > MAX_BATCH_PACKET_SIZE) {
1949 total_estimated_size = MAX_BATCH_PACKET_SIZE;
1950 }
1951
1952 this->prepare_first_message_buffer(shared_buf, header_padding, total_estimated_size);
1953
1954 // Fast path for single message - buffer already allocated above
1955 if (num_items == 1) {
1956 const auto &item = this->deferred_batch_[0];
1957 // Let dispatch_message_ calculate size and encode if it fits
1958 uint16_t payload_size = this->dispatch_message_(item, std::numeric_limits<uint16_t>::max(), true);
1959
1960 if (payload_size > 0 && this->send_buffer(ProtoWriteBuffer{&shared_buf}, item.message_type)) {
1961#ifdef HAS_PROTO_MESSAGE_DUMP
1962 // Log message after send attempt for VV debugging
1963 this->log_batch_item_(item);
1964#endif
1965 this->clear_batch_();
1966 } else if (payload_size == 0) {
1967 // Message too large to fit in available space
1968 ESP_LOGW(TAG, "Message too large to send: type=%u", item.message_type);
1969 this->clear_batch_();
1970 }
1971 return;
1972 }
1973
1974 // Multi-message path — heavy stack frame isolated in separate noinline function
1975 this->process_batch_multi_(shared_buf, num_items, header_padding, footer_size);
1976}
1977
1978// Separated from process_batch_() so the single-message fast path gets a minimal
1979// stack frame without the MAX_MESSAGES_PER_BATCH * sizeof(MessageInfo) array.
1980void APIConnection::process_batch_multi_(std::vector<uint8_t> &shared_buf, size_t num_items, uint8_t header_padding,
1981 uint8_t footer_size) {
1982 // Ensure MessageInfo remains trivially destructible for our placement new approach
1983 static_assert(std::is_trivially_destructible<MessageInfo>::value,
1984 "MessageInfo must remain trivially destructible with this placement-new approach");
1985
1986 const size_t messages_to_process = std::min(num_items, MAX_MESSAGES_PER_BATCH);
1987 const uint8_t frame_overhead = header_padding + footer_size;
1988
1989 // Stack-allocated array for message info
1990 alignas(MessageInfo) char message_info_storage[MAX_MESSAGES_PER_BATCH * sizeof(MessageInfo)];
1991 MessageInfo *message_info = reinterpret_cast<MessageInfo *>(message_info_storage);
1992 size_t items_processed = 0;
1993 uint16_t remaining_size = std::numeric_limits<uint16_t>::max();
1994 // Track where each message's header padding begins in the buffer
1995 // For plaintext: this is where the 6-byte header padding starts
1996 // For noise: this is where the 7-byte header padding starts
1997 // The actual message data follows after the header padding
1998 uint32_t current_offset = 0;
1999
2000 // Process items and encode directly to buffer (up to our limit)
2001 for (size_t i = 0; i < messages_to_process; i++) {
2002 const auto &item = this->deferred_batch_[i];
2003 // Try to encode message via dispatch
2004 // The dispatch function calculates overhead to determine if the message fits
2005 uint16_t payload_size = this->dispatch_message_(item, remaining_size, i == 0);
2006
2007 if (payload_size == 0) {
2008 // Message won't fit, stop processing
2009 break;
2010 }
2011
2012 // Message was encoded successfully
2013 // payload_size is header_padding + actual payload size + footer_size
2014 uint16_t proto_payload_size = payload_size - frame_overhead;
2015 // Use placement new to construct MessageInfo in pre-allocated stack array
2016 // This avoids default-constructing all MAX_MESSAGES_PER_BATCH elements
2017 // Explicit destruction is not needed because MessageInfo is trivially destructible,
2018 // as ensured by the static_assert in its definition.
2019 new (&message_info[items_processed++]) MessageInfo(item.message_type, current_offset, proto_payload_size);
2020 // After first message, set remaining size to MAX_BATCH_PACKET_SIZE to avoid fragmentation
2021 if (items_processed == 1) {
2022 remaining_size = MAX_BATCH_PACKET_SIZE;
2023 }
2024 remaining_size -= payload_size;
2025 // Calculate where the next message's header padding will start
2026 // Current buffer size + footer space for this message
2027 current_offset = shared_buf.size() + footer_size;
2028 }
2029
2030 if (items_processed > 0) {
2031 // Add footer space for the last message (for Noise protocol MAC)
2032 if (footer_size > 0) {
2033 shared_buf.resize(shared_buf.size() + footer_size);
2034 }
2035
2036 // Send all collected messages
2037 APIError err = this->helper_->write_protobuf_messages(ProtoWriteBuffer{&shared_buf},
2038 std::span<const MessageInfo>(message_info, items_processed));
2039 if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
2040 this->fatal_error_with_log_(LOG_STR("Batch write failed"), err);
2041 }
2042
2043#ifdef HAS_PROTO_MESSAGE_DUMP
2044 // Log messages after send attempt for VV debugging
2045 // It's safe to use the buffer for logging at this point regardless of send result
2046 for (size_t i = 0; i < items_processed; i++) {
2047 const auto &item = this->deferred_batch_[i];
2048 this->log_batch_item_(item);
2049 }
2050#endif
2051
2052 // Partial batch — remove processed items and reschedule
2053 if (items_processed < this->deferred_batch_.size()) {
2054 this->deferred_batch_.remove_front(items_processed);
2055 this->schedule_batch_();
2056 return;
2057 }
2058 }
2059
2060 // All items processed (or none could be processed)
2061 this->clear_batch_();
2062}
2063
2064// Dispatch message encoding based on message_type
2065// Switch assigns function pointer, single call site for smaller code size
2066uint16_t APIConnection::dispatch_message_(const DeferredBatch::BatchItem &item, uint32_t remaining_size,
2067 bool batch_first) {
2068 this->flags_.batch_first_message = batch_first;
2069#ifdef USE_EVENT
2070 // Events need aux_data_index to look up event type from entity
2071 if (item.message_type == EventResponse::MESSAGE_TYPE) {
2072 // Skip if aux_data_index is invalid (should never happen in normal operation)
2073 if (item.aux_data_index == DeferredBatch::AUX_DATA_UNUSED)
2074 return 0;
2075 auto *event = static_cast<event::Event *>(item.entity);
2076 return try_send_event_response(event, StringRef::from_maybe_nullptr(event->get_event_type(item.aux_data_index)),
2077 this, remaining_size);
2078 }
2079#endif
2080
2081 // All other message types use function pointer lookup via switch
2082 MessageCreatorPtr func = nullptr;
2083
2084// Macros to reduce repetitive switch cases
2085#define CASE_STATE_INFO(entity_name, StateResp, InfoResp) \
2086 case StateResp::MESSAGE_TYPE: \
2087 func = &try_send_##entity_name##_state; \
2088 break; \
2089 case InfoResp::MESSAGE_TYPE: \
2090 func = &try_send_##entity_name##_info; \
2091 break;
2092#define CASE_INFO_ONLY(entity_name, InfoResp) \
2093 case InfoResp::MESSAGE_TYPE: \
2094 func = &try_send_##entity_name##_info; \
2095 break;
2096
2097 switch (item.message_type) {
2098#ifdef USE_BINARY_SENSOR
2099 CASE_STATE_INFO(binary_sensor, BinarySensorStateResponse, ListEntitiesBinarySensorResponse)
2100#endif
2101#ifdef USE_COVER
2102 CASE_STATE_INFO(cover, CoverStateResponse, ListEntitiesCoverResponse)
2103#endif
2104#ifdef USE_FAN
2105 CASE_STATE_INFO(fan, FanStateResponse, ListEntitiesFanResponse)
2106#endif
2107#ifdef USE_LIGHT
2108 CASE_STATE_INFO(light, LightStateResponse, ListEntitiesLightResponse)
2109#endif
2110#ifdef USE_SENSOR
2111 CASE_STATE_INFO(sensor, SensorStateResponse, ListEntitiesSensorResponse)
2112#endif
2113#ifdef USE_SWITCH
2114 CASE_STATE_INFO(switch, SwitchStateResponse, ListEntitiesSwitchResponse)
2115#endif
2116#ifdef USE_BUTTON
2117 CASE_INFO_ONLY(button, ListEntitiesButtonResponse)
2118#endif
2119#ifdef USE_TEXT_SENSOR
2120 CASE_STATE_INFO(text_sensor, TextSensorStateResponse, ListEntitiesTextSensorResponse)
2121#endif
2122#ifdef USE_CLIMATE
2123 CASE_STATE_INFO(climate, ClimateStateResponse, ListEntitiesClimateResponse)
2124#endif
2125#ifdef USE_NUMBER
2126 CASE_STATE_INFO(number, NumberStateResponse, ListEntitiesNumberResponse)
2127#endif
2128#ifdef USE_DATETIME_DATE
2129 CASE_STATE_INFO(date, DateStateResponse, ListEntitiesDateResponse)
2130#endif
2131#ifdef USE_DATETIME_TIME
2132 CASE_STATE_INFO(time, TimeStateResponse, ListEntitiesTimeResponse)
2133#endif
2134#ifdef USE_DATETIME_DATETIME
2135 CASE_STATE_INFO(datetime, DateTimeStateResponse, ListEntitiesDateTimeResponse)
2136#endif
2137#ifdef USE_TEXT
2138 CASE_STATE_INFO(text, TextStateResponse, ListEntitiesTextResponse)
2139#endif
2140#ifdef USE_SELECT
2141 CASE_STATE_INFO(select, SelectStateResponse, ListEntitiesSelectResponse)
2142#endif
2143#ifdef USE_LOCK
2144 CASE_STATE_INFO(lock, LockStateResponse, ListEntitiesLockResponse)
2145#endif
2146#ifdef USE_VALVE
2147 CASE_STATE_INFO(valve, ValveStateResponse, ListEntitiesValveResponse)
2148#endif
2149#ifdef USE_MEDIA_PLAYER
2150 CASE_STATE_INFO(media_player, MediaPlayerStateResponse, ListEntitiesMediaPlayerResponse)
2151#endif
2152#ifdef USE_ALARM_CONTROL_PANEL
2153 CASE_STATE_INFO(alarm_control_panel, AlarmControlPanelStateResponse, ListEntitiesAlarmControlPanelResponse)
2154#endif
2155#ifdef USE_WATER_HEATER
2156 CASE_STATE_INFO(water_heater, WaterHeaterStateResponse, ListEntitiesWaterHeaterResponse)
2157#endif
2158#ifdef USE_CAMERA
2159 CASE_INFO_ONLY(camera, ListEntitiesCameraResponse)
2160#endif
2161#ifdef USE_INFRARED
2162 CASE_INFO_ONLY(infrared, ListEntitiesInfraredResponse)
2163#endif
2164#ifdef USE_EVENT
2165 CASE_INFO_ONLY(event, ListEntitiesEventResponse)
2166#endif
2167#ifdef USE_UPDATE
2168 CASE_STATE_INFO(update, UpdateStateResponse, ListEntitiesUpdateResponse)
2169#endif
2170 // Special messages (not entity state/info)
2171 case ListEntitiesDoneResponse::MESSAGE_TYPE:
2172 func = &try_send_list_info_done;
2173 break;
2174 case DisconnectRequest::MESSAGE_TYPE:
2175 func = &try_send_disconnect_request;
2176 break;
2177 case PingRequest::MESSAGE_TYPE:
2178 func = &try_send_ping_request;
2179 break;
2180 default:
2181 return 0;
2182 }
2183
2184#undef CASE_STATE_INFO
2185#undef CASE_INFO_ONLY
2186
2187 return func(item.entity, this, remaining_size);
2188}
2189
2190uint16_t APIConnection::try_send_list_info_done(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
2192 return encode_message_to_buffer(resp, ListEntitiesDoneResponse::MESSAGE_TYPE, conn, remaining_size);
2193}
2194
2195uint16_t APIConnection::try_send_disconnect_request(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
2197 return encode_message_to_buffer(req, DisconnectRequest::MESSAGE_TYPE, conn, remaining_size);
2198}
2199
2200uint16_t APIConnection::try_send_ping_request(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
2201 PingRequest req;
2202 return encode_message_to_buffer(req, PingRequest::MESSAGE_TYPE, conn, remaining_size);
2203}
2204
2205#ifdef USE_API_HOMEASSISTANT_STATES
2206void APIConnection::process_state_subscriptions_() {
2207 const auto &subs = this->parent_->get_state_subs();
2208 if (this->state_subs_at_ >= static_cast<int>(subs.size())) {
2209 this->state_subs_at_ = -1;
2210 return;
2211 }
2212
2213 const auto &it = subs[this->state_subs_at_];
2215 resp.entity_id = StringRef(it.entity_id);
2216
2217 // Avoid string copy by using the const char* pointer if it exists
2218 resp.attribute = it.attribute != nullptr ? StringRef(it.attribute) : StringRef("");
2219
2220 resp.once = it.once;
2221 if (this->send_message(resp, SubscribeHomeAssistantStateResponse::MESSAGE_TYPE)) {
2222 this->state_subs_at_++;
2223 }
2224}
2225#endif // USE_API_HOMEASSISTANT_STATES
2226
2227void APIConnection::log_client_(int level, const LogString *message) {
2228 char peername[socket::SOCKADDR_STR_LEN];
2229 esp_log_printf_(level, TAG, __LINE__, ESPHOME_LOG_FORMAT("%s (%s): %s"), this->helper_->get_client_name(),
2230 this->helper_->get_peername_to(peername), LOG_STR_ARG(message));
2231}
2232
2233void APIConnection::log_warning_(const LogString *message, APIError err) {
2234 char peername[socket::SOCKADDR_STR_LEN];
2235 ESP_LOGW(TAG, "%s (%s): %s %s errno=%d", this->helper_->get_client_name(), this->helper_->get_peername_to(peername),
2236 LOG_STR_ARG(message), LOG_STR_ARG(api_error_to_logstr(err)), errno);
2237}
2238
2239} // namespace esphome::api
2240#endif
const std::string & get_friendly_name() const
Get the friendly name of this Application set by pre_setup().
const auto & get_areas()
static constexpr size_t BUILD_TIME_STR_SIZE
Size of buffer required for build time string (including null terminator)
void get_build_time_string(std::span< char, BUILD_TIME_STR_SIZE > buffer)
Copy the build time string into the provided buffer Buffer must be BUILD_TIME_STR_SIZE bytes (compile...
const char * get_area() const
Get the area of this Application set by pre_setup().
const auto & get_devices()
const std::string & get_name() const
Get the name of this Application set by pre_setup().
uint32_t IRAM_ATTR HOT get_loop_component_start_time() const
Get the cached time in milliseconds from when the current component started its loop execution.
void begin(bool include_internal=false)
ESPDEPRECATED("object_id mangles names and all object_id methods are planned for removal " "(see https://github.com/esphome/backlog/issues/76). " "Now is the time to stop using object_id. If still needed, use get_object_id_to() " "which will remain available longer. get_object_id() will be removed in 2026.7.0", "2025.12.0") std uint32_t get_object_id_hash()
uint32_t get_device_id() const
Fixed-capacity vector - allocates once at runtime, never reallocates This avoids std::vector template...
Definition helpers.h:227
void push_back(const T &value)
Add element without bounds checking Caller must ensure sufficient capacity was allocated via init() S...
Definition helpers.h:344
void init(size_t n)
Definition helpers.h:317
Helper class for efficient buffer allocation - uses stack for small sizes, heap for large This is use...
Definition helpers.h:411
StringRef is a reference to a string owned by something else.
Definition string_ref.h:26
constexpr const char * c_str() const
Definition string_ref.h:73
constexpr bool empty() const
Definition string_ref.h:76
constexpr size_type size() const
Definition string_ref.h:74
static constexpr StringRef from_lit(const CharT(&s)[N])
Definition string_ref.h:50
static StringRef from_maybe_nullptr(const char *s)
Definition string_ref.h:53
struct esphome::api::APIConnection::APIFlags flags_
std::unique_ptr< APIFrameHelper > helper_
APIConnection(std::unique_ptr< socket::Socket > socket, APIServer *parent)
uint16_t(*)(EntityBase *, APIConnection *, uint32_t remaining_size) MessageCreatorPtr
void on_button_command_request(const ButtonCommandRequest &msg) override
void log_send_message_(const char *name, const char *dump)
APINoiseContext & get_noise_ctx()
Definition api_server.h:78
std::vector< uint8_t > & get_shared_buffer_ref()
Definition api_server.h:72
enums::AlarmControlPanelStateCommand command
Definition api_pb2.h:2549
enums::AlarmControlPanelState state
Definition api_pb2.h:2533
enums::BluetoothScannerMode mode
Definition api_pb2.h:2249
void set_data(const uint8_t *data, size_t len)
Definition api_pb2.h:1264
enums::ClimateSwingMode swing_mode
Definition api_pb2.h:1374
enums::ClimateFanMode fan_mode
Definition api_pb2.h:1372
enums::ClimatePreset preset
Definition api_pb2.h:1378
enums::ClimateFanMode fan_mode
Definition api_pb2.h:1341
enums::ClimateSwingMode swing_mode
Definition api_pb2.h:1342
enums::ClimateAction action
Definition api_pb2.h:1340
enums::ClimatePreset preset
Definition api_pb2.h:1344
enums::CoverOperation current_operation
Definition api_pb2.h:617
Fixed-size buffer for message dumps - avoids heap allocation.
Definition proto.h:350
enums::FanDirection direction
Definition api_pb2.h:700
enums::FanDirection direction
Definition api_pb2.h:677
enums::ColorMode color_mode
Definition api_pb2.h:744
const std::vector< const char * > * supported_custom_presets
Definition api_pb2.h:1313
const climate::ClimateSwingModeMask * supported_swing_modes
Definition api_pb2.h:1310
const std::vector< const char * > * supported_custom_fan_modes
Definition api_pb2.h:1311
const climate::ClimatePresetMask * supported_presets
Definition api_pb2.h:1312
const climate::ClimateFanModeMask * supported_fan_modes
Definition api_pb2.h:1309
const climate::ClimateModeMask * supported_modes
Definition api_pb2.h:1304
const FixedVector< const char * > * event_types
Definition api_pb2.h:2733
const std::vector< const char * > * supported_preset_modes
Definition api_pb2.h:659
const FixedVector< const char * > * effects
Definition api_pb2.h:726
const light::ColorModeMask * supported_color_modes
Definition api_pb2.h:723
std::vector< MediaPlayerSupportedFormat > supported_formats
Definition api_pb2.h:1738
const FixedVector< const char * > * options
Definition api_pb2.h:1521
enums::SensorStateClass state_class
Definition api_pb2.h:817
const water_heater::WaterHeaterModeMask * supported_modes
Definition api_pb2.h:1404
enums::LockCommand command
Definition api_pb2.h:1668
enums::MediaPlayerCommand command
Definition api_pb2.h:1774
enums::MediaPlayerState state
Definition api_pb2.h:1755
virtual void encode(ProtoWriteBuffer buffer) const
Definition proto.h:419
virtual const char * message_name() const
Definition proto.h:424
virtual void calculate_size(ProtoSize &size) const
Definition proto.h:421
virtual const char * dump_to(DumpBuffer &out) const =0
uint32_t get_size() const
Definition proto.h:476
void set_message(const uint8_t *data, size_t len)
Definition api_pb2.h:956
enums::UpdateCommand command
Definition api_pb2.h:2913
enums::ValveOperation current_operation
Definition api_pb2.h:2787
std::vector< VoiceAssistantExternalWakeWord > external_wake_words
Definition api_pb2.h:2465
std::vector< VoiceAssistantWakeWord > available_wake_words
Definition api_pb2.h:2480
const std::vector< std::string > * active_wake_words
Definition api_pb2.h:2481
std::vector< std::string > active_wake_words
Definition api_pb2.h:2498
enums::ZWaveProxyRequestType type
Definition api_pb2.h:2949
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 get_bluetooth_mac_address_pretty(std::span< char, 18 > output)
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:25
Abstract camera base class.
Definition camera.h:115
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:182
Base class for all cover devices.
Definition cover.h:110
uint8_t get_last_event_type_index() const
Return index of last triggered event type, or max uint8_t if no event triggered yet.
Definition event.h:54
Infrared - Base class for infrared remote control implementations.
Definition infrared.h:110
This class represents the communication layer between the front-end MQTT layer and the hardware outpu...
Definition light_state.h:91
Base class for all locks.
Definition lock.h:110
Base-class for all numbers.
Definition number.h:29
Base-class for all selects.
Definition select.h:29
Base-class for all sensors.
Definition sensor.h:43
Base class for all switches.
Definition switch.h:39
Base-class for all text inputs.
Definition text.h:22
void set_timezone(const std::string &tz)
Set the time zone.
Base class for all valve devices.
Definition valve.h:104
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:59
void api_connection_authenticated(api::APIConnection *conn)
const char * message
Definition component.cpp:38
uint16_t type
bool state
Definition fan.h:2
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:21
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
@ WATER_HEATER_STATE_ON
Water heater is on (not in standby)
@ WATER_HEATER_STATE_AWAY
Away/vacation mode is currently active.
ZWaveProxy * global_zwave_proxy
void HOT esp_log_printf_(int level, const char *tag, int line, const char *format,...)
Definition log.cpp:11
std::string size_t len
Definition helpers.h:692
size_t size
Definition helpers.h:729
void get_mac_address_raw(uint8_t *mac)
Get the device MAC address as raw bytes, written into the provided byte array (6 bytes).
Definition helpers.cpp:73
void IRAM_ATTR HOT delay(uint32_t ms)
Definition core.cpp:26
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:588
char * format_mac_addr_upper(const uint8_t *mac, char *output)
Format MAC address as XX:XX:XX:XX:XX:XX (uppercase, colon separators)
Definition helpers.h:1045
A more user-friendly version of struct tm from time.h.
Definition time.h:17
uint32_t payload_size()
const uint8_t ESPHOME_WEBSERVER_INDEX_HTML[] PROGMEM
Definition web_server.h:27