ESPHome 2026.7.4
Loading...
Searching...
No Matches
api_connection.cpp
Go to the documentation of this file.
1#include "api_connection.h"
2#ifdef USE_API
3#include "api_connection_buffer.h" // for encode_to_buffer / get_batch_delay_ms_ inlines
4#ifdef USE_API_NOISE
6#endif
7#ifdef USE_API_PLAINTEXT
9#endif
10#ifdef USE_API_USER_DEFINED_ACTIONS
11#include "user_services.h"
12#endif
13#include <cerrno>
14#include <cinttypes>
15#include <functional>
16#include <limits>
17#include <new>
18#include <utility>
19#ifdef USE_ESP8266
20#include <pgmspace.h>
21#endif
25#include "esphome/core/hal.h"
26#include "esphome/core/log.h"
28#ifdef USE_PROVISIONING
30#endif
31
32#ifdef USE_DEEP_SLEEP
34#endif
35#ifdef USE_HOMEASSISTANT_TIME
37#endif
38#ifdef USE_BLUETOOTH_PROXY
40#endif
41#ifdef USE_CLIMATE
43#endif
44#ifdef USE_VOICE_ASSISTANT
46#endif
47#ifdef USE_ZWAVE_PROXY
49#endif
50#ifdef USE_WATER_HEATER
52#endif
53#ifdef USE_INFRARED
55#endif
56#ifdef USE_RADIO_FREQUENCY
58#endif
59
60namespace esphome::api {
61
62// Maximum messages to read per loop iteration to prevent starving other components.
63// This is a balance between API responsiveness and allowing other components to run.
64// Since each message could contain multiple protobuf messages when using packet batching,
65// this limits the number of messages processed, not the number of TCP packets.
66static constexpr uint8_t MAX_MESSAGES_PER_LOOP = 10;
67static constexpr uint8_t MAX_PING_RETRIES = 60;
68static constexpr uint16_t PING_RETRY_INTERVAL = 1000;
69static constexpr uint32_t KEEPALIVE_DISCONNECT_TIMEOUT = (KEEPALIVE_TIMEOUT_MS * 5) / 2;
70// Timeout for completing the handshake (Noise transport + HelloRequest).
71// A stalled handshake from a buggy client or network glitch holds a connection
72// slot, which can prevent legitimate clients from reconnecting. Also hardens
73// against the less likely case of intentional connection slot exhaustion.
74//
75// 60s is intentionally high: on ESP8266 with power_save_mode: LIGHT and weak
76// WiFi (-70 dBm+), TCP retransmissions push real-world handshake times to
77// 28-30s. See https://github.com/esphome/esphome/issues/14999
78static constexpr uint32_t HANDSHAKE_TIMEOUT_MS = 60000;
79
80static constexpr auto ESPHOME_VERSION_REF = StringRef::from_lit(ESPHOME_VERSION);
81
82// Cross-validate C++ constants against proto max_data_length annotations in api.proto
83static_assert(MAC_ADDRESS_PRETTY_BUFFER_SIZE - 1 == 17,
84 "Update max_data_length for mac_address/bluetooth_mac_address in api.proto");
85static_assert(Application::BUILD_TIME_STR_SIZE - 1 == 25, "Update max_data_length for compilation_time in api.proto");
86static_assert(sizeof(ESPHOME_VERSION) - 1 <= 32, "Update max_data_length for esphome_version in api.proto");
87static_assert(ESPHOME_DEVICE_NAME_MAX_LEN <= 31, "Update max_data_length for name in api.proto");
88static_assert(ESPHOME_FRIENDLY_NAME_MAX_LEN <= 120, "Update max_data_length for friendly_name in api.proto");
89
90static const char *const TAG = "api.connection";
91#ifdef USE_CAMERA
92static const int CAMERA_STOP_STREAM = 5000;
93#endif
94
95#ifdef USE_DEVICES
96// Helper macro for entity command handlers - gets entity by key and device_id, returns if not found, and creates call
97// object
98#define ENTITY_COMMAND_MAKE_CALL(entity_type, entity_var, getter_name) \
99 entity_type *entity_var = App.get_##getter_name##_by_key(msg.key, msg.device_id); \
100 if ((entity_var) == nullptr) \
101 return; \
102 auto call = (entity_var)->make_call();
103
104// Helper macro for entity command handlers that don't use make_call() - gets entity by key and device_id and returns if
105// not found
106#define ENTITY_COMMAND_GET(entity_type, entity_var, getter_name) \
107 entity_type *entity_var = App.get_##getter_name##_by_key(msg.key, msg.device_id); \
108 if ((entity_var) == nullptr) \
109 return;
110
111// Helper macro for multi-entity dispatch: looks up an entity by key and device_id without early return or make_call().
112// Use when multiple entity types must be checked in sequence (at most one will match).
113#define ENTITY_COMMAND_LOOKUP(entity_type, entity_var, getter_name) \
114 entity_type *entity_var = App.get_##getter_name##_by_key(msg.key, msg.device_id)
115
116#else // No device support, use simpler macros
117// Helper macro for entity command handlers - gets entity by key, returns if not found, and creates call
118// object
119#define ENTITY_COMMAND_MAKE_CALL(entity_type, entity_var, getter_name) \
120 entity_type *entity_var = App.get_##getter_name##_by_key(msg.key); \
121 if ((entity_var) == nullptr) \
122 return; \
123 auto call = (entity_var)->make_call();
124
125// Helper macro for entity command handlers that don't use make_call() - gets entity by key and returns if
126// not found
127#define ENTITY_COMMAND_GET(entity_type, entity_var, getter_name) \
128 entity_type *entity_var = App.get_##getter_name##_by_key(msg.key); \
129 if ((entity_var) == nullptr) \
130 return;
131
132// Helper macro for multi-entity dispatch: looks up an entity by key without early return or make_call().
133// Use when multiple entity types must be checked in sequence (at most one will match).
134#define ENTITY_COMMAND_LOOKUP(entity_type, entity_var, getter_name) \
135 entity_type *entity_var = App.get_##getter_name##_by_key(msg.key)
136
137#endif // USE_DEVICES
138
139APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *parent) : parent_(parent) {
140#if defined(USE_API_PLAINTEXT) && defined(USE_API_NOISE)
141 auto &noise_ctx = parent->get_noise_ctx();
142 if (noise_ctx.has_psk()) {
143 this->helper_ = std::unique_ptr<APIFrameHelper>{new APINoiseFrameHelper(std::move(sock), noise_ctx)};
144 } else {
145 this->helper_ = std::unique_ptr<APIFrameHelper>{new APIPlaintextFrameHelper(std::move(sock))};
146 }
147#elif defined(USE_API_PLAINTEXT)
148 this->helper_ = std::unique_ptr<APIPlaintextFrameHelper>{new APIPlaintextFrameHelper(std::move(sock))};
149#elif defined(USE_API_NOISE)
150 this->helper_ =
151 std::unique_ptr<APINoiseFrameHelper>{new APINoiseFrameHelper(std::move(sock), parent->get_noise_ctx())};
152#else
153#error "No frame helper defined"
154#endif
155#ifdef USE_CAMERA
156 if (camera::Camera::instance() != nullptr) {
157 this->image_reader_ = std::unique_ptr<camera::CameraImageReader>{camera::Camera::instance()->create_image_reader()};
158 }
159#endif
160}
161
162void APIConnection::start() {
163 this->last_traffic_ = App.get_loop_component_start_time();
164
165 APIError err = this->helper_->init();
166 if (err != APIError::OK) {
167 this->fatal_error_with_log_(LOG_STR("Helper init failed"), err);
168 return;
169 }
170 // Initialize client name with peername (IP address) until Hello message provides actual name
171 char peername[socket::SOCKADDR_STR_LEN];
172 this->helper_->set_client_name(this->helper_->get_peername_to(peername), strlen(peername));
173}
174
175APIConnection::~APIConnection() {
176 this->destroy_active_iterator_();
177#ifdef USE_BLUETOOTH_PROXY
178 if (bluetooth_proxy::global_bluetooth_proxy->get_api_connection() == this) {
180 }
181#endif
182#ifdef USE_VOICE_ASSISTANT
183 if (voice_assistant::global_voice_assistant->get_api_connection() == this) {
185 }
186#endif
187#ifdef USE_ZWAVE_PROXY
188 if (zwave_proxy::global_zwave_proxy != nullptr && zwave_proxy::global_zwave_proxy->get_api_connection() == this) {
189 zwave_proxy::global_zwave_proxy->zwave_proxy_request(this, enums::ZWAVE_PROXY_REQUEST_TYPE_UNSUBSCRIBE);
190 }
191#endif
192#ifdef USE_SERIAL_PROXY
193 for (auto *proxy : App.get_serial_proxies()) {
194 if (proxy->get_api_connection() == this) {
195 proxy->serial_proxy_request(this, enums::SERIAL_PROXY_REQUEST_TYPE_UNSUBSCRIBE);
196 }
197 }
198#endif
199}
200
201#if defined(USE_API_NOISE) && defined(USE_API_PLAINTEXT)
202void APIConnection::upgrade_helper_to_noise_() {
203 // The client opened with a Noise hello while this device has no encryption
204 // key set. Replace the plaintext helper with a Noise helper so the key can
205 // be provisioned over an encrypted channel: the noise context PSK is all
206 // zeros when unprovisioned, and NNpsk0 still runs a fresh ephemeral X25519
207 // exchange, so a passive listener cannot read the session. A publicly known
208 // PSK authenticates nobody; this protects against sniffing only.
209 auto *plaintext = static_cast<APIPlaintextFrameHelper *>(this->helper_.get());
210 uint8_t header[3];
211 uint8_t header_len = plaintext->get_consumed_header(header);
212 auto *noise = new APINoiseFrameHelper(plaintext->release_socket_for_switch(), this->parent_->get_noise_ctx());
213 // Carry over the peername-based client name (Hello has not arrived yet)
214 const char *name = plaintext->get_client_name();
215 noise->set_client_name(name, strlen(name));
216 this->helper_.reset(noise); // destroys the plaintext helper
217 APIError err = noise->init_from_handoff(header, header_len);
218 if (err != APIError::OK) {
219 this->fatal_error_with_log_(LOG_STR("Noise handoff failed"), err);
220 }
221}
222#endif // USE_API_NOISE && USE_API_PLAINTEXT
223
224void APIConnection::destroy_active_iterator_() {
225 switch (this->active_iterator_) {
226 case ActiveIterator::LIST_ENTITIES:
227 this->iterator_storage_.list_entities.~ListEntitiesIterator();
228 break;
229 case ActiveIterator::INITIAL_STATE:
230 this->iterator_storage_.initial_state.~InitialStateIterator();
231 break;
232 case ActiveIterator::NONE:
233 break;
234 }
235 this->active_iterator_ = ActiveIterator::NONE;
236}
237
238void APIConnection::begin_iterator_(ActiveIterator type) {
239 this->destroy_active_iterator_();
240 this->active_iterator_ = type;
241 if (type == ActiveIterator::LIST_ENTITIES) {
242 new (&this->iterator_storage_.list_entities) ListEntitiesIterator(this);
243 this->iterator_storage_.list_entities.begin();
244 } else {
245 new (&this->iterator_storage_.initial_state) InitialStateIterator(this);
246 this->iterator_storage_.initial_state.begin();
247 }
248}
249
250void APIConnection::loop() {
251 if (this->flags_.next_close) {
252 // requested a disconnect - don't close socket here, let APIServer::loop() do it
253 // so getpeername() still works for the disconnect trigger
254 this->flags_.remove = true;
255 return;
256 }
257
258 APIError err = this->helper_->loop();
259 if (err != APIError::OK) {
260 this->fatal_error_with_log_(LOG_STR("Socket operation failed"), err);
261 return;
262 }
263
265 // Check if socket has data ready before attempting to read.
266 // Also try reading if we hit the message limit last time — LWIP's rcvevent
267 // (used by is_socket_ready) tracks pbuf dequeues, not bytes. When multiple
268 // messages share a TCP segment, the last message's data stays in LWIP's
269 // lastdata cache after rcvevent hits 0, making is_socket_ready() return false
270 // even though data remains.
271 if (this->helper_->is_socket_ready() || this->flags_.may_have_remaining_data) {
272 this->flags_.may_have_remaining_data = false;
273 // Read up to MAX_MESSAGES_PER_LOOP messages per loop to improve throughput
274 uint8_t message_count = 0;
275 for (; message_count < MAX_MESSAGES_PER_LOOP; message_count++) {
276 ReadPacketBuffer buffer;
277 err = this->helper_->read_packet(&buffer);
278 if (err == APIError::WOULD_BLOCK) {
279 // No more data available
280 break;
281 } else if (err != APIError::OK) {
282#if defined(USE_API_NOISE) && defined(USE_API_PLAINTEXT)
283 // Checked inside the error branch to keep the hot err == OK path
284 // free of it; this can only fire on the first bytes of a plaintext
285 // helper on an unprovisioned device
286 if (err == APIError::PROTOCOL_SWITCH_TO_NOISE) {
287 this->upgrade_helper_to_noise_();
288 return;
289 }
290#endif
291 this->fatal_error_with_log_(LOG_STR("Reading failed"), err);
292 return;
293 } else {
294 // Only update last_traffic_ after authentication to ensure the
295 // handshake timeout is an absolute deadline from connection start.
296 // Pre-auth messages (e.g. PingRequest) must not reset the timer.
297 if (this->is_authenticated()) {
298 this->last_traffic_ = now;
299 }
300 // read a packet
301 this->read_message_(buffer.data_len, buffer.type, buffer.data);
302 if (this->flags_.remove)
303 return;
304 }
305 }
306 // If we hit the limit, there may be more data remaining in LWIP's
307 // lastdata cache that rcvevent doesn't account for.
308 if (message_count == MAX_MESSAGES_PER_LOOP) {
309 this->flags_.may_have_remaining_data = true;
310 }
311 }
312
313 // Process deferred batch if scheduled and timer has expired
314 if (this->flags_.batch_scheduled && now - this->deferred_batch_.batch_start_time >= this->get_batch_delay_ms_()) {
315 this->process_batch_();
316 }
317
318 if (this->active_iterator_ != ActiveIterator::NONE) {
319 this->process_active_iterator_();
320 }
321
322 // Disconnect clients that haven't completed the handshake in time.
323 // Stale half-open connections from buggy clients or network issues can
324 // accumulate and block legitimate clients from reconnecting.
325 if (!this->is_authenticated() && now - this->last_traffic_ > HANDSHAKE_TIMEOUT_MS) {
326 this->on_fatal_error();
327 this->log_client_(ESPHOME_LOG_LEVEL_WARN, LOG_STR("handshake timeout; disconnecting"));
328 return;
329 }
330
331 // Keepalive: only call into the cold path when enough time has elapsed.
332 // When sent_ping is true, last_traffic_ hasn't been updated so this
333 // condition is already satisfied — covers both send-ping and disconnect cases.
334 if (now - this->last_traffic_ > KEEPALIVE_TIMEOUT_MS) {
335 this->check_keepalive_(now);
336 }
337
338#ifdef USE_API_HOMEASSISTANT_STATES
339 if (state_subs_at_ >= 0) {
340 this->process_state_subscriptions_();
341 }
342#endif
343
344#ifdef USE_CAMERA
345 // Process camera last - state updates are higher priority
346 // (missing a frame is fine, missing a state update is not)
347 this->try_send_camera_image_();
348#endif
349}
350
351void APIConnection::check_keepalive_(uint32_t now) {
352 // Caller guarantees: now - last_traffic_ > KEEPALIVE_TIMEOUT_MS
353 if (this->flags_.sent_ping) {
354 // Disconnect if not responded within 2.5*keepalive
355 if (now - this->last_traffic_ > KEEPALIVE_DISCONNECT_TIMEOUT) {
356 on_fatal_error();
357 this->log_client_(ESPHOME_LOG_LEVEL_WARN, LOG_STR("is unresponsive; disconnecting"));
358 }
359 } else if (!this->flags_.remove) {
360 // Only send ping if we're not disconnecting
361 ESP_LOGVV(TAG, "Sending keepalive PING");
362 PingRequest req;
363 this->flags_.sent_ping = this->send_message(req);
364 if (!this->flags_.sent_ping) {
365 // If we can't send the ping request directly (tx_buffer full),
366 // schedule it at the front of the batch so it will be sent with priority
367 ESP_LOGW(TAG, "Buffer full, ping queued");
368 this->schedule_message_front_(nullptr, PingRequest::MESSAGE_TYPE, PingRequest::ESTIMATED_SIZE);
369 this->flags_.sent_ping = true; // Mark as sent to avoid scheduling multiple pings
370 }
371 }
372}
373
374void APIConnection::process_active_iterator_() {
375 // Caller ensures active_iterator_ != NONE
376 if (this->active_iterator_ == ActiveIterator::LIST_ENTITIES) {
377 if (this->iterator_storage_.list_entities.completed()) {
378 this->destroy_active_iterator_();
379 if (this->flags_.state_subscription) {
380 this->begin_iterator_(ActiveIterator::INITIAL_STATE);
381 } else {
382 this->finalize_iterator_sync_();
383 }
384 } else {
385 this->process_iterator_batch_(this->iterator_storage_.list_entities);
386 }
387 } else { // INITIAL_STATE
388 if (this->iterator_storage_.initial_state.completed()) {
389 this->destroy_active_iterator_();
390 this->finalize_iterator_sync_();
391 } else {
392 this->process_iterator_batch_(this->iterator_storage_.initial_state);
393 }
394 }
395}
396
397void APIConnection::finalize_iterator_sync_() {
398 // Flush any remaining batched messages immediately so clients
399 // receive completion responses (e.g. ListEntitiesDoneResponse)
400 // without waiting for the batch timer.
401 if (!this->deferred_batch_.empty()) {
402 this->process_batch_();
403 }
404 // Enable immediate sending for future state changes
405 this->flags_.should_try_send_immediately = true;
406 // Release excess memory from buffers that grew during initial sync
407 this->deferred_batch_.release_buffer();
408 this->helper_->release_buffers();
409}
410
411void APIConnection::process_iterator_batch_(ComponentIterator &iterator) {
412 size_t initial_size = this->deferred_batch_.size();
413 size_t max_batch = MAX_INITIAL_PER_BATCH;
414 while (!iterator.completed() && (this->deferred_batch_.size() - initial_size) < max_batch) {
415 iterator.advance();
416 }
417
418 // If the batch is full, process it immediately
419 // Note: iterator.advance() already calls schedule_batch_() via schedule_message_()
420 if (this->deferred_batch_.size() >= max_batch) {
421 this->process_batch_();
422 }
423}
424
425bool APIConnection::send_disconnect_response_() {
426 // remote initiated disconnect_client
427 // don't close yet, we still need to send the disconnect response
428 // close will happen on next loop
429 this->log_client_(ESPHOME_LOG_LEVEL_DEBUG, LOG_STR("disconnected"));
430 this->flags_.next_close = true;
432 return this->send_message(resp);
433}
434void APIConnection::on_disconnect_response() {
435 // Don't close socket here, let APIServer::loop() do it
436 // so getpeername() still works for the disconnect trigger
437 this->flags_.remove = true;
438}
439
440uint16_t APIConnection::fill_and_encode_entity_state(EntityBase *entity, StateResponseProtoMessage &msg,
441 CalculateSizeFn size_fn, MessageEncodeFn encode_fn,
442 APIConnection *conn, uint32_t remaining_size) {
443 msg.key = entity->get_object_id_hash();
444#ifdef USE_DEVICES
445 msg.device_id = entity->get_device_id();
446#endif
447 return encode_to_buffer(size_fn(&msg), encode_fn, &msg, conn, remaining_size);
448}
449
450uint16_t APIConnection::fill_and_encode_entity_info(EntityBase *entity, InfoResponseProtoMessage &msg,
451 CalculateSizeFn size_fn, MessageEncodeFn encode_fn,
452 APIConnection *conn, uint32_t remaining_size) {
453 // Set common fields that are shared by all entity types
454 msg.key = entity->get_object_id_hash();
455
456 if (entity->has_own_name()) {
457 msg.name = entity->get_name();
458 }
459
460 // Set common EntityBase properties
461#ifdef USE_ENTITY_ICON
462 char icon_buf[MAX_ICON_LENGTH];
463 msg.icon = StringRef(entity->get_icon_to(icon_buf));
464#endif
466 msg.entity_category = static_cast<enums::EntityCategory>(entity->get_entity_category());
467#ifdef USE_DEVICES
468 msg.device_id = entity->get_device_id();
469#endif
470 return encode_to_buffer_slow(size_fn(&msg), encode_fn, &msg, conn, remaining_size);
471}
472
473uint16_t APIConnection::fill_and_encode_entity_info_with_device_class(EntityBase *entity, InfoResponseProtoMessage &msg,
474 StringRef &device_class_field,
475 CalculateSizeFn size_fn,
476 MessageEncodeFn encode_fn, APIConnection *conn,
477 uint32_t remaining_size) {
478 char dc_buf[MAX_DEVICE_CLASS_LENGTH];
479 device_class_field = StringRef(entity->get_device_class_to(dc_buf));
480 return fill_and_encode_entity_info(entity, msg, size_fn, encode_fn, conn, remaining_size);
481}
482
483#ifdef USE_BINARY_SENSOR
484bool APIConnection::send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor) {
485 return this->send_message_smart_(binary_sensor, BinarySensorStateResponse::MESSAGE_TYPE,
486 BinarySensorStateResponse::ESTIMATED_SIZE);
487}
488
489uint16_t APIConnection::try_send_binary_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
490 auto *binary_sensor = static_cast<binary_sensor::BinarySensor *>(entity);
492 resp.state = binary_sensor->state;
493 resp.missing_state = !binary_sensor->has_state();
494 return fill_and_encode_entity_state(binary_sensor, resp, conn, remaining_size);
495}
496
497uint16_t APIConnection::try_send_binary_sensor_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
498 auto *binary_sensor = static_cast<binary_sensor::BinarySensor *>(entity);
500 msg.is_status_binary_sensor = binary_sensor->is_status_binary_sensor();
501 return fill_and_encode_entity_info_with_device_class(binary_sensor, msg, msg.device_class, conn, remaining_size);
502}
503#endif
504
505#ifdef USE_COVER
506bool APIConnection::send_cover_state(cover::Cover *cover) {
507 return this->send_message_smart_(cover, CoverStateResponse::MESSAGE_TYPE, CoverStateResponse::ESTIMATED_SIZE);
508}
509uint16_t APIConnection::try_send_cover_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
510 auto *cover = static_cast<cover::Cover *>(entity);
512 auto traits = cover->get_traits();
513 msg.position = cover->position;
514 if (traits.get_supports_tilt())
515 msg.tilt = cover->tilt;
516 msg.current_operation = static_cast<enums::CoverOperation>(cover->current_operation);
517 return fill_and_encode_entity_state(cover, msg, conn, remaining_size);
518}
519uint16_t APIConnection::try_send_cover_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
520 auto *cover = static_cast<cover::Cover *>(entity);
522 auto traits = cover->get_traits();
523 msg.assumed_state = traits.get_is_assumed_state();
524 msg.supports_position = traits.get_supports_position();
525 msg.supports_tilt = traits.get_supports_tilt();
526 msg.supports_stop = traits.get_supports_stop();
527 return fill_and_encode_entity_info_with_device_class(cover, msg, msg.device_class, conn, remaining_size);
528}
529void APIConnection::on_cover_command_request(const CoverCommandRequest &msg) {
530 ENTITY_COMMAND_MAKE_CALL(cover::Cover, cover, cover)
531 if (msg.has_position)
532 call.set_position(msg.position);
533 if (msg.has_tilt)
534 call.set_tilt(msg.tilt);
535 if (msg.stop)
536 call.set_command_stop();
537 call.perform();
538}
539#endif
540
541#ifdef USE_FAN
542bool APIConnection::send_fan_state(fan::Fan *fan) {
543 return this->send_message_smart_(fan, FanStateResponse::MESSAGE_TYPE, FanStateResponse::ESTIMATED_SIZE);
544}
545uint16_t APIConnection::try_send_fan_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
546 auto *fan = static_cast<fan::Fan *>(entity);
548 auto traits = fan->get_traits();
549 msg.state = fan->state;
550 if (traits.supports_oscillation())
551 msg.oscillating = fan->oscillating;
552 if (traits.supports_speed()) {
553 msg.speed_level = fan->speed;
554 }
555 if (traits.supports_direction())
556 msg.direction = static_cast<enums::FanDirection>(fan->direction);
557 if (traits.supports_preset_modes() && fan->has_preset_mode())
558 msg.preset_mode = fan->get_preset_mode();
559 return fill_and_encode_entity_state(fan, msg, conn, remaining_size);
560}
561uint16_t APIConnection::try_send_fan_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
562 auto *fan = static_cast<fan::Fan *>(entity);
564 auto traits = fan->get_traits();
565 msg.supports_oscillation = traits.supports_oscillation();
566 msg.supports_speed = traits.supports_speed();
567 msg.supports_direction = traits.supports_direction();
568 msg.supported_speed_count = traits.supported_speed_count();
569 msg.supported_preset_modes = &traits.supported_preset_modes();
570 return fill_and_encode_entity_info(fan, msg, conn, remaining_size);
571}
572void APIConnection::on_fan_command_request(const FanCommandRequest &msg) {
573 ENTITY_COMMAND_MAKE_CALL(fan::Fan, fan, fan)
574 if (msg.has_state)
575 call.set_state(msg.state);
576 if (msg.has_oscillating)
577 call.set_oscillating(msg.oscillating);
578 if (msg.has_speed_level) {
579 // Prefer level
580 call.set_speed(msg.speed_level);
581 }
582 if (msg.has_direction)
583 call.set_direction(static_cast<fan::FanDirection>(msg.direction));
584 if (msg.has_preset_mode)
585 call.set_preset_mode(msg.preset_mode.c_str(), msg.preset_mode.size());
586 call.perform();
587}
588#endif
589
590#ifdef USE_LIGHT
591bool APIConnection::send_light_state(light::LightState *light) {
592 return this->send_message_smart_(light, LightStateResponse::MESSAGE_TYPE, LightStateResponse::ESTIMATED_SIZE);
593}
594uint16_t APIConnection::try_send_light_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
595 auto *light = static_cast<light::LightState *>(entity);
597 auto values = light->remote_values;
598 auto color_mode = values.get_color_mode();
599 resp.state = values.is_on();
600 resp.color_mode = static_cast<enums::ColorMode>(color_mode);
601 resp.brightness = values.get_brightness();
602 resp.color_brightness = values.get_color_brightness();
603 resp.red = values.get_red();
604 resp.green = values.get_green();
605 resp.blue = values.get_blue();
606 resp.white = values.get_white();
607 resp.color_temperature = values.get_color_temperature();
608 resp.cold_white = values.get_cold_white();
609 resp.warm_white = values.get_warm_white();
610 if (light->supports_effects()) {
611 resp.effect = light->get_effect_name();
612 }
613 return fill_and_encode_entity_state(light, resp, conn, remaining_size);
614}
615uint16_t APIConnection::try_send_light_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
616 auto *light = static_cast<light::LightState *>(entity);
618 auto traits = light->get_traits();
619 auto supported_modes = traits.get_supported_color_modes();
620 // Pass pointer to ColorModeMask so the iterator can encode actual ColorMode enum values
621 msg.supported_color_modes = &supported_modes;
622 if (traits.supports_color_capability(light::ColorCapability::COLOR_TEMPERATURE) ||
623 traits.supports_color_capability(light::ColorCapability::COLD_WARM_WHITE)) {
624 msg.min_mireds = traits.get_min_mireds();
625 msg.max_mireds = traits.get_max_mireds();
626 }
627 FixedVector<const char *> effects_list;
628 if (light->supports_effects()) {
629 auto &light_effects = light->get_effects();
630 effects_list.init(light_effects.size() + 1);
631 effects_list.push_back("None");
632 for (auto *effect : light_effects) {
633 // c_str() is safe as effect names are null-terminated strings from codegen
634 effects_list.push_back(effect->get_name().c_str());
635 }
636 }
637 msg.effects = &effects_list;
638 return fill_and_encode_entity_info(light, msg, conn, remaining_size);
639}
640void APIConnection::on_light_command_request(const LightCommandRequest &msg) {
641 ENTITY_COMMAND_MAKE_CALL(light::LightState, light, light)
642 if (msg.has_state)
643 call.set_state(msg.state);
644 if (msg.has_brightness)
645 call.set_brightness(msg.brightness);
646 if (msg.has_color_mode)
647 call.set_color_mode(static_cast<light::ColorMode>(msg.color_mode));
648 if (msg.has_color_brightness)
649 call.set_color_brightness(msg.color_brightness);
650 if (msg.has_rgb) {
651 call.set_red(msg.red);
652 call.set_green(msg.green);
653 call.set_blue(msg.blue);
654 }
655 if (msg.has_white)
656 call.set_white(msg.white);
657 if (msg.has_color_temperature)
658 call.set_color_temperature(msg.color_temperature);
659 if (msg.has_cold_white)
660 call.set_cold_white(msg.cold_white);
661 if (msg.has_warm_white)
662 call.set_warm_white(msg.warm_white);
663 if (msg.has_transition_length)
664 call.set_transition_length(msg.transition_length);
665 if (msg.has_flash_length)
666 call.set_flash_length(msg.flash_length);
667 if (msg.has_effect)
668 call.set_effect(msg.effect.c_str(), msg.effect.size());
669 call.perform();
670}
671#endif
672
673#ifdef USE_SENSOR
674bool APIConnection::send_sensor_state(sensor::Sensor *sensor) {
675 return this->send_message_smart_(sensor, SensorStateResponse::MESSAGE_TYPE, SensorStateResponse::ESTIMATED_SIZE);
676}
677
678uint16_t APIConnection::try_send_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
679 auto *sensor = static_cast<sensor::Sensor *>(entity);
681 resp.state = sensor->state;
682 resp.missing_state = !sensor->has_state();
683 return fill_and_encode_entity_state(sensor, resp, conn, remaining_size);
684}
685
686uint16_t APIConnection::try_send_sensor_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
687 auto *sensor = static_cast<sensor::Sensor *>(entity);
689 msg.unit_of_measurement = sensor->get_unit_of_measurement_ref();
690 msg.accuracy_decimals = sensor->get_accuracy_decimals();
691 msg.force_update = sensor->get_force_update();
692 msg.state_class = static_cast<enums::SensorStateClass>(sensor->get_state_class());
693 return fill_and_encode_entity_info_with_device_class(sensor, msg, msg.device_class, conn, remaining_size);
694}
695#endif
696
697#ifdef USE_SWITCH
698bool APIConnection::send_switch_state(switch_::Switch *a_switch) {
699 return this->send_message_smart_(a_switch, SwitchStateResponse::MESSAGE_TYPE, SwitchStateResponse::ESTIMATED_SIZE);
700}
701
702uint16_t APIConnection::try_send_switch_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
703 auto *a_switch = static_cast<switch_::Switch *>(entity);
705 resp.state = a_switch->state;
706 return fill_and_encode_entity_state(a_switch, resp, conn, remaining_size);
707}
708
709uint16_t APIConnection::try_send_switch_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
710 auto *a_switch = static_cast<switch_::Switch *>(entity);
712 msg.assumed_state = a_switch->assumed_state();
713 return fill_and_encode_entity_info_with_device_class(a_switch, msg, msg.device_class, conn, remaining_size);
714}
715void APIConnection::on_switch_command_request(const SwitchCommandRequest &msg) {
716 ENTITY_COMMAND_GET(switch_::Switch, a_switch, switch)
717
718 if (msg.state) {
719 a_switch->turn_on();
720 } else {
721 a_switch->turn_off();
722 }
723}
724#endif
725
726#ifdef USE_TEXT_SENSOR
727bool APIConnection::send_text_sensor_state(text_sensor::TextSensor *text_sensor) {
728 return this->send_message_smart_(text_sensor, TextSensorStateResponse::MESSAGE_TYPE,
729 TextSensorStateResponse::ESTIMATED_SIZE);
730}
731
732uint16_t APIConnection::try_send_text_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
733 auto *text_sensor = static_cast<text_sensor::TextSensor *>(entity);
735 resp.state = StringRef(text_sensor->state);
736 resp.missing_state = !text_sensor->has_state();
737 return fill_and_encode_entity_state(text_sensor, resp, conn, remaining_size);
738}
739uint16_t APIConnection::try_send_text_sensor_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
740 auto *text_sensor = static_cast<text_sensor::TextSensor *>(entity);
742 return fill_and_encode_entity_info_with_device_class(text_sensor, msg, msg.device_class, conn, remaining_size);
743}
744#endif
745
746#ifdef USE_CLIMATE
747bool APIConnection::send_climate_state(climate::Climate *climate) {
748 return this->send_message_smart_(climate, ClimateStateResponse::MESSAGE_TYPE, ClimateStateResponse::ESTIMATED_SIZE);
749}
750uint16_t APIConnection::try_send_climate_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
751 auto *climate = static_cast<climate::Climate *>(entity);
753 auto traits = climate->get_traits();
754 resp.mode = static_cast<enums::ClimateMode>(climate->mode);
755 resp.action = static_cast<enums::ClimateAction>(climate->action);
756 if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_TEMPERATURE))
757 resp.current_temperature = climate->current_temperature;
758 if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE |
760 resp.target_temperature_low = climate->target_temperature_low;
761 resp.target_temperature_high = climate->target_temperature_high;
762 } else {
763 resp.target_temperature = climate->target_temperature;
764 }
765 if (traits.get_supports_fan_modes() && climate->fan_mode.has_value())
766 resp.fan_mode = static_cast<enums::ClimateFanMode>(climate->fan_mode.value());
767 if (!traits.get_supported_custom_fan_modes().empty() && climate->has_custom_fan_mode()) {
768 resp.custom_fan_mode = climate->get_custom_fan_mode();
769 }
770 if (traits.get_supports_presets() && climate->preset.has_value()) {
771 resp.preset = static_cast<enums::ClimatePreset>(climate->preset.value());
772 }
773 if (!traits.get_supported_custom_presets().empty() && climate->has_custom_preset()) {
774 resp.custom_preset = climate->get_custom_preset();
775 }
776 if (traits.get_supports_swing_modes())
777 resp.swing_mode = static_cast<enums::ClimateSwingMode>(climate->swing_mode);
778 if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_HUMIDITY))
779 resp.current_humidity = climate->current_humidity;
780 if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_TARGET_HUMIDITY))
781 resp.target_humidity = climate->target_humidity;
782 return fill_and_encode_entity_state(climate, resp, conn, remaining_size);
783}
784uint16_t APIConnection::try_send_climate_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
785 auto *climate = static_cast<climate::Climate *>(entity);
787 auto traits = climate->get_traits();
788 // Flags set for backward compatibility, deprecated in 2025.11.0
791 msg.supports_two_point_target_temperature = traits.has_feature_flags(
794 msg.supports_action = traits.has_feature_flags(climate::CLIMATE_SUPPORTS_ACTION);
795 // Current feature flags and other supported parameters
796 msg.feature_flags = traits.get_feature_flags();
797 msg.supported_modes = &traits.get_supported_modes();
798 msg.visual_min_temperature = traits.get_visual_min_temperature();
799 msg.visual_max_temperature = traits.get_visual_max_temperature();
800 msg.visual_target_temperature_step = traits.get_visual_target_temperature_step();
801 msg.visual_current_temperature_step = traits.get_visual_current_temperature_step();
802 msg.visual_min_humidity = traits.get_visual_min_humidity();
803 msg.visual_max_humidity = traits.get_visual_max_humidity();
804 msg.supported_fan_modes = &traits.get_supported_fan_modes();
805 msg.supported_custom_fan_modes = &traits.get_supported_custom_fan_modes();
806 msg.supported_presets = &traits.get_supported_presets();
807 msg.supported_custom_presets = &traits.get_supported_custom_presets();
808 msg.supported_swing_modes = &traits.get_supported_swing_modes();
809 return fill_and_encode_entity_info(climate, msg, conn, remaining_size);
810}
811void APIConnection::on_climate_command_request(const ClimateCommandRequest &msg) {
812 ENTITY_COMMAND_MAKE_CALL(climate::Climate, climate, climate)
813 if (msg.has_mode)
814 call.set_mode(static_cast<climate::ClimateMode>(msg.mode));
816 call.set_target_temperature(msg.target_temperature);
818 call.set_target_temperature_low(msg.target_temperature_low);
820 call.set_target_temperature_high(msg.target_temperature_high);
821 if (msg.has_target_humidity)
822 call.set_target_humidity(msg.target_humidity);
823 if (msg.has_fan_mode)
824 call.set_fan_mode(static_cast<climate::ClimateFanMode>(msg.fan_mode));
825 if (msg.has_custom_fan_mode)
826 call.set_fan_mode(msg.custom_fan_mode.c_str(), msg.custom_fan_mode.size());
827 if (msg.has_preset)
828 call.set_preset(static_cast<climate::ClimatePreset>(msg.preset));
829 if (msg.has_custom_preset)
830 call.set_preset(msg.custom_preset.c_str(), msg.custom_preset.size());
831 if (msg.has_swing_mode)
832 call.set_swing_mode(static_cast<climate::ClimateSwingMode>(msg.swing_mode));
833 call.perform();
834}
835#endif
836
837#ifdef USE_NUMBER
838bool APIConnection::send_number_state(number::Number *number) {
839 return this->send_message_smart_(number, NumberStateResponse::MESSAGE_TYPE, NumberStateResponse::ESTIMATED_SIZE);
840}
841
842uint16_t APIConnection::try_send_number_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
843 auto *number = static_cast<number::Number *>(entity);
845 resp.state = number->state;
846 resp.missing_state = !number->has_state();
847 return fill_and_encode_entity_state(number, resp, conn, remaining_size);
848}
849
850uint16_t APIConnection::try_send_number_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
851 auto *number = static_cast<number::Number *>(entity);
853 msg.unit_of_measurement = number->get_unit_of_measurement_ref();
854 msg.mode = static_cast<enums::NumberMode>(number->traits.get_mode());
855 msg.min_value = number->traits.get_min_value();
856 msg.max_value = number->traits.get_max_value();
857 msg.step = number->traits.get_step();
858 return fill_and_encode_entity_info_with_device_class(number, msg, msg.device_class, conn, remaining_size);
859}
860void APIConnection::on_number_command_request(const NumberCommandRequest &msg) {
861 ENTITY_COMMAND_MAKE_CALL(number::Number, number, number)
862 call.set_value(msg.state);
863 call.perform();
864}
865#endif
866
867#ifdef USE_DATETIME_DATE
868bool APIConnection::send_date_state(datetime::DateEntity *date) {
869 return this->send_message_smart_(date, DateStateResponse::MESSAGE_TYPE, DateStateResponse::ESTIMATED_SIZE);
870}
871uint16_t APIConnection::try_send_date_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
872 auto *date = static_cast<datetime::DateEntity *>(entity);
874 resp.missing_state = !date->has_state();
875 resp.year = date->year;
876 resp.month = date->month;
877 resp.day = date->day;
878 return fill_and_encode_entity_state(date, resp, conn, remaining_size);
879}
880uint16_t APIConnection::try_send_date_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
881 auto *date = static_cast<datetime::DateEntity *>(entity);
883 return fill_and_encode_entity_info(date, msg, conn, remaining_size);
884}
885void APIConnection::on_date_command_request(const DateCommandRequest &msg) {
886 ENTITY_COMMAND_MAKE_CALL(datetime::DateEntity, date, date)
887 call.set_date(msg.year, msg.month, msg.day);
888 call.perform();
889}
890#endif
891
892#ifdef USE_DATETIME_TIME
893bool APIConnection::send_time_state(datetime::TimeEntity *time) {
894 return this->send_message_smart_(time, TimeStateResponse::MESSAGE_TYPE, TimeStateResponse::ESTIMATED_SIZE);
895}
896uint16_t APIConnection::try_send_time_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
897 auto *time = static_cast<datetime::TimeEntity *>(entity);
899 resp.missing_state = !time->has_state();
900 resp.hour = time->hour;
901 resp.minute = time->minute;
902 resp.second = time->second;
903 return fill_and_encode_entity_state(time, resp, conn, remaining_size);
904}
905uint16_t APIConnection::try_send_time_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
906 auto *time = static_cast<datetime::TimeEntity *>(entity);
908 return fill_and_encode_entity_info(time, msg, conn, remaining_size);
909}
910void APIConnection::on_time_command_request(const TimeCommandRequest &msg) {
911 ENTITY_COMMAND_MAKE_CALL(datetime::TimeEntity, time, time)
912 call.set_time(msg.hour, msg.minute, msg.second);
913 call.perform();
914}
915#endif
916
917#ifdef USE_DATETIME_DATETIME
918bool APIConnection::send_datetime_state(datetime::DateTimeEntity *datetime) {
919 return this->send_message_smart_(datetime, DateTimeStateResponse::MESSAGE_TYPE,
920 DateTimeStateResponse::ESTIMATED_SIZE);
921}
922uint16_t APIConnection::try_send_datetime_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
923 auto *datetime = static_cast<datetime::DateTimeEntity *>(entity);
925 resp.missing_state = !datetime->has_state();
926 if (datetime->has_state()) {
927 ESPTime state = datetime->state_as_esptime();
928 resp.epoch_seconds = state.timestamp;
929 }
930 return fill_and_encode_entity_state(datetime, resp, conn, remaining_size);
931}
932uint16_t APIConnection::try_send_datetime_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
933 auto *datetime = static_cast<datetime::DateTimeEntity *>(entity);
935 return fill_and_encode_entity_info(datetime, msg, conn, remaining_size);
936}
937void APIConnection::on_date_time_command_request(const DateTimeCommandRequest &msg) {
938 ENTITY_COMMAND_MAKE_CALL(datetime::DateTimeEntity, datetime, datetime)
939 call.set_datetime(msg.epoch_seconds);
940 call.perform();
941}
942#endif
943
944#ifdef USE_TEXT
945bool APIConnection::send_text_state(text::Text *text) {
946 return this->send_message_smart_(text, TextStateResponse::MESSAGE_TYPE, TextStateResponse::ESTIMATED_SIZE);
947}
948
949uint16_t APIConnection::try_send_text_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
950 auto *text = static_cast<text::Text *>(entity);
952 resp.state = StringRef(text->state);
953 resp.missing_state = !text->has_state();
954 return fill_and_encode_entity_state(text, resp, conn, remaining_size);
955}
956
957uint16_t APIConnection::try_send_text_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
958 auto *text = static_cast<text::Text *>(entity);
960 msg.mode = static_cast<enums::TextMode>(text->traits.get_mode());
961 msg.min_length = text->traits.get_min_length();
962 msg.max_length = text->traits.get_max_length();
963 msg.pattern = text->traits.get_pattern_ref();
964 return fill_and_encode_entity_info(text, msg, conn, remaining_size);
965}
966void APIConnection::on_text_command_request(const TextCommandRequest &msg) {
967 ENTITY_COMMAND_MAKE_CALL(text::Text, text, text)
968 call.set_value(msg.state.c_str(), msg.state.size());
969 call.perform();
970}
971#endif
972
973#ifdef USE_SELECT
974bool APIConnection::send_select_state(select::Select *select) {
975 return this->send_message_smart_(select, SelectStateResponse::MESSAGE_TYPE, SelectStateResponse::ESTIMATED_SIZE);
976}
977
978uint16_t APIConnection::try_send_select_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
979 auto *select = static_cast<select::Select *>(entity);
981 resp.state = select->current_option();
982 resp.missing_state = !select->has_state();
983 return fill_and_encode_entity_state(select, resp, conn, remaining_size);
984}
985
986uint16_t APIConnection::try_send_select_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
987 auto *select = static_cast<select::Select *>(entity);
989 msg.options = &select->traits.get_options();
990 return fill_and_encode_entity_info(select, msg, conn, remaining_size);
991}
992void APIConnection::on_select_command_request(const SelectCommandRequest &msg) {
993 ENTITY_COMMAND_MAKE_CALL(select::Select, select, select)
994 call.set_option(msg.state.c_str(), msg.state.size());
995 call.perform();
996}
997#endif
998
999#ifdef USE_BUTTON
1000uint16_t APIConnection::try_send_button_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
1001 auto *button = static_cast<button::Button *>(entity);
1003 return fill_and_encode_entity_info_with_device_class(button, msg, msg.device_class, conn, remaining_size);
1004}
1006 ENTITY_COMMAND_GET(button::Button, button, button)
1007 button->press();
1008}
1009#endif
1010
1011#ifdef USE_LOCK
1012bool APIConnection::send_lock_state(lock::Lock *a_lock) {
1013 return this->send_message_smart_(a_lock, LockStateResponse::MESSAGE_TYPE, LockStateResponse::ESTIMATED_SIZE);
1014}
1015
1016uint16_t APIConnection::try_send_lock_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
1017 auto *a_lock = static_cast<lock::Lock *>(entity);
1018 LockStateResponse resp;
1019 resp.state = static_cast<enums::LockState>(a_lock->state);
1020 return fill_and_encode_entity_state(a_lock, resp, conn, remaining_size);
1021}
1022
1023uint16_t APIConnection::try_send_lock_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
1024 auto *a_lock = static_cast<lock::Lock *>(entity);
1026 msg.assumed_state = a_lock->traits.get_assumed_state();
1027 msg.supports_open = a_lock->traits.get_supports_open();
1028 msg.requires_code = a_lock->traits.get_requires_code();
1029 return fill_and_encode_entity_info(a_lock, msg, conn, remaining_size);
1030}
1031void APIConnection::on_lock_command_request(const LockCommandRequest &msg) {
1032 ENTITY_COMMAND_GET(lock::Lock, a_lock, lock)
1033
1034 switch (msg.command) {
1035 case enums::LOCK_UNLOCK:
1036 a_lock->unlock();
1037 break;
1038 case enums::LOCK_LOCK:
1039 a_lock->lock();
1040 break;
1041 case enums::LOCK_OPEN:
1042 a_lock->open();
1043 break;
1044 }
1045}
1046#endif
1047
1048#ifdef USE_VALVE
1049bool APIConnection::send_valve_state(valve::Valve *valve) {
1050 return this->send_message_smart_(valve, ValveStateResponse::MESSAGE_TYPE, ValveStateResponse::ESTIMATED_SIZE);
1051}
1052uint16_t APIConnection::try_send_valve_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
1053 auto *valve = static_cast<valve::Valve *>(entity);
1054 ValveStateResponse resp;
1055 resp.position = valve->position;
1056 resp.current_operation = static_cast<enums::ValveOperation>(valve->current_operation);
1057 return fill_and_encode_entity_state(valve, resp, conn, remaining_size);
1058}
1059uint16_t APIConnection::try_send_valve_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
1060 auto *valve = static_cast<valve::Valve *>(entity);
1062 auto traits = valve->get_traits();
1063 msg.assumed_state = traits.get_is_assumed_state();
1064 msg.supports_position = traits.get_supports_position();
1065 msg.supports_stop = traits.get_supports_stop();
1066 return fill_and_encode_entity_info_with_device_class(valve, msg, msg.device_class, conn, remaining_size);
1067}
1068void APIConnection::on_valve_command_request(const ValveCommandRequest &msg) {
1069 ENTITY_COMMAND_MAKE_CALL(valve::Valve, valve, valve)
1070 if (msg.has_position)
1071 call.set_position(msg.position);
1072 if (msg.stop)
1073 call.set_command_stop();
1074 call.perform();
1075}
1076#endif
1077
1078#ifdef USE_MEDIA_PLAYER
1079bool APIConnection::send_media_player_state(media_player::MediaPlayer *media_player) {
1080 return this->send_message_smart_(media_player, MediaPlayerStateResponse::MESSAGE_TYPE,
1081 MediaPlayerStateResponse::ESTIMATED_SIZE);
1082}
1083uint16_t APIConnection::try_send_media_player_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
1084 auto *media_player = static_cast<media_player::MediaPlayer *>(entity);
1088 : media_player->state;
1089 resp.state = static_cast<enums::MediaPlayerState>(report_state);
1090 resp.volume = media_player->volume;
1091 resp.muted = media_player->is_muted();
1092 return fill_and_encode_entity_state(media_player, resp, conn, remaining_size);
1093}
1094uint16_t APIConnection::try_send_media_player_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
1095 auto *media_player = static_cast<media_player::MediaPlayer *>(entity);
1097 auto traits = media_player->get_traits();
1098 msg.supports_pause = traits.get_supports_pause();
1099 msg.feature_flags = traits.get_feature_flags();
1100 for (auto &supported_format : traits.get_supported_formats()) {
1101 msg.supported_formats.emplace_back();
1102 auto &media_format = msg.supported_formats.back();
1103 media_format.format = StringRef(supported_format.format);
1104 media_format.sample_rate = supported_format.sample_rate;
1105 media_format.num_channels = supported_format.num_channels;
1106 media_format.purpose = static_cast<enums::MediaPlayerFormatPurpose>(supported_format.purpose);
1107 media_format.sample_bytes = supported_format.sample_bytes;
1108 }
1109 return fill_and_encode_entity_info(media_player, msg, conn, remaining_size);
1110}
1111void APIConnection::on_media_player_command_request(const MediaPlayerCommandRequest &msg) {
1112 ENTITY_COMMAND_MAKE_CALL(media_player::MediaPlayer, media_player, media_player)
1113 if (msg.has_command) {
1114 call.set_command(static_cast<media_player::MediaPlayerCommand>(msg.command));
1115 }
1116 if (msg.has_volume) {
1117 call.set_volume(msg.volume);
1118 }
1119 if (msg.has_media_url) {
1120 call.set_media_url(msg.media_url);
1121 }
1122 if (msg.has_announcement) {
1123 call.set_announcement(msg.announcement);
1124 }
1125 call.perform();
1126}
1127#endif
1128
1129#ifdef USE_CAMERA
1130void APIConnection::try_send_camera_image_() {
1131 if (!this->image_reader_)
1132 return;
1133
1134 // Send as many chunks as possible without blocking
1135 while (this->image_reader_->available()) {
1136 if (!this->helper_->can_write_without_blocking())
1137 return;
1138
1139 uint32_t to_send = std::min((size_t) MAX_BATCH_PACKET_SIZE, this->image_reader_->available());
1140 bool done = this->image_reader_->available() == to_send;
1141
1144 msg.set_data(this->image_reader_->peek_data_buffer(), to_send);
1145 msg.done = done;
1146#ifdef USE_DEVICES
1148#endif
1149
1150 if (!this->send_message(msg)) {
1151 return; // Send failed, try again later
1152 }
1153 this->image_reader_->consume_data(to_send);
1154 if (done) {
1155 this->image_reader_->return_image();
1156 return;
1157 }
1158 }
1159}
1160void APIConnection::set_camera_state(std::shared_ptr<camera::CameraImage> image) {
1161 if (!this->flags_.state_subscription)
1162 return;
1163 if (!this->image_reader_)
1164 return;
1165 if (this->image_reader_->available())
1166 return;
1167 if (image->was_requested_by(esphome::camera::API_REQUESTER) || image->was_requested_by(esphome::camera::IDLE)) {
1168 this->image_reader_->set_image(std::move(image));
1169 // Try to send immediately to reduce latency
1170 this->try_send_camera_image_();
1171 }
1172}
1173uint16_t APIConnection::try_send_camera_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
1174 auto *camera = static_cast<camera::Camera *>(entity);
1176 return fill_and_encode_entity_info(camera, msg, conn, remaining_size);
1177}
1178void APIConnection::on_camera_image_request(const CameraImageRequest &msg) {
1179 if (camera::Camera::instance() == nullptr)
1180 return;
1181
1182 if (msg.single)
1184 if (msg.stream) {
1186
1187 App.scheduler.set_timeout(this->parent_, "api_camera_stop_stream", CAMERA_STOP_STREAM,
1189 }
1190}
1191#endif
1192
1193#ifdef USE_HOMEASSISTANT_TIME
1194void APIConnection::on_get_time_response(const GetTimeResponse &value) {
1197#if defined(USE_HOMEASSISTANT_TIMEZONE) && defined(USE_TIME_TIMEZONE)
1198 if (!value.timezone.empty()) {
1199 // Check if the sender provided pre-parsed timezone data.
1200 // If std_offset is non-zero or DST rules are present, the parsed data was populated.
1201 // For UTC (all zeros), string parsing produces the same result, so the fallback is equivalent.
1202 const auto &pt = value.parsed_timezone;
1203 if (pt.std_offset_seconds != 0 || pt.dst_start.type != enums::DST_RULE_TYPE_NONE) {
1205 tz.std_offset_seconds = pt.std_offset_seconds;
1206 tz.dst_offset_seconds = pt.dst_offset_seconds;
1207 tz.dst_start.time_seconds = pt.dst_start.time_seconds;
1208 tz.dst_start.day = static_cast<uint16_t>(pt.dst_start.day);
1209 tz.dst_start.type = static_cast<time::DSTRuleType>(pt.dst_start.type);
1210 tz.dst_start.month = static_cast<uint8_t>(pt.dst_start.month);
1211 tz.dst_start.week = static_cast<uint8_t>(pt.dst_start.week);
1212 tz.dst_start.day_of_week = static_cast<uint8_t>(pt.dst_start.day_of_week);
1213 tz.dst_end.time_seconds = pt.dst_end.time_seconds;
1214 tz.dst_end.day = static_cast<uint16_t>(pt.dst_end.day);
1215 tz.dst_end.type = static_cast<time::DSTRuleType>(pt.dst_end.type);
1216 tz.dst_end.month = static_cast<uint8_t>(pt.dst_end.month);
1217 tz.dst_end.week = static_cast<uint8_t>(pt.dst_end.week);
1218 tz.dst_end.day_of_week = static_cast<uint8_t>(pt.dst_end.day_of_week);
1220 } else {
1222 }
1223 }
1224#endif
1225 }
1226}
1227#endif
1228
1229#ifdef USE_BLUETOOTH_PROXY
1230void APIConnection::on_subscribe_bluetooth_le_advertisements_request(
1233}
1234void APIConnection::on_unsubscribe_bluetooth_le_advertisements_request() {
1236}
1237void APIConnection::on_bluetooth_device_request(const BluetoothDeviceRequest &msg) {
1239}
1240void APIConnection::on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &msg) {
1242}
1243void APIConnection::on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &msg) {
1245}
1246void APIConnection::on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &msg) {
1248}
1249void APIConnection::on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &msg) {
1251}
1252void APIConnection::on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &msg) {
1254}
1255
1256void APIConnection::on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &msg) {
1258}
1259
1260bool APIConnection::send_subscribe_bluetooth_connections_free_response_() {
1262 return true;
1263}
1264void APIConnection::on_subscribe_bluetooth_connections_free_request() {
1265 if (!this->send_subscribe_bluetooth_connections_free_response_()) {
1266 this->on_fatal_error();
1267 }
1268}
1269
1270void APIConnection::on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &msg) {
1272 msg.mode == enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_ACTIVE);
1273}
1274void APIConnection::on_bluetooth_set_connection_params_request(const BluetoothSetConnectionParamsRequest &msg) {
1276}
1277#endif
1278
1279#ifdef USE_VOICE_ASSISTANT
1280bool APIConnection::check_voice_assistant_api_connection_() const {
1281 return voice_assistant::global_voice_assistant != nullptr &&
1283}
1284
1285void APIConnection::on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) {
1288 }
1289}
1290void APIConnection::on_voice_assistant_response(const VoiceAssistantResponse &msg) {
1291 if (!this->check_voice_assistant_api_connection_()) {
1292 return;
1293 }
1294
1295 if (msg.error) {
1297 return;
1298 }
1299 if (msg.port == 0) {
1300 // Use API Audio
1302 } else {
1303 struct sockaddr_storage storage;
1304 socklen_t len = sizeof(storage);
1305 this->helper_->getpeername((struct sockaddr *) &storage, &len);
1307 }
1308};
1309void APIConnection::on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg) {
1310 if (this->check_voice_assistant_api_connection_()) {
1312 }
1313}
1314void APIConnection::on_voice_assistant_audio(const VoiceAssistantAudio &msg) {
1315 if (this->check_voice_assistant_api_connection_()) {
1317 }
1318};
1319void APIConnection::on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &msg) {
1320 if (this->check_voice_assistant_api_connection_()) {
1322 }
1323};
1324
1325void APIConnection::on_voice_assistant_announce_request(const VoiceAssistantAnnounceRequest &msg) {
1326 if (this->check_voice_assistant_api_connection_()) {
1328 }
1329}
1330
1331bool APIConnection::send_voice_assistant_get_configuration_response_(const VoiceAssistantConfigurationRequest &msg) {
1333 if (!this->check_voice_assistant_api_connection_()) {
1334 // send_message encodes synchronously, so this stack local outlives the encode
1335 const std::vector<std::string> empty_wake_words;
1336 resp.active_wake_words = &empty_wake_words;
1337 return this->send_message(resp);
1338 }
1339
1341 for (auto &wake_word : config.available_wake_words) {
1342 resp.available_wake_words.emplace_back();
1343 auto &resp_wake_word = resp.available_wake_words.back();
1344 resp_wake_word.id = StringRef(wake_word.id);
1345 resp_wake_word.wake_word = StringRef(wake_word.wake_word);
1346 for (const auto &lang : wake_word.trained_languages) {
1347 resp_wake_word.trained_languages.push_back(lang);
1348 }
1349 }
1350
1351 // Filter external wake words
1352 for (auto &wake_word : msg.external_wake_words) {
1353 if (wake_word.model_type != "micro") {
1354 // microWakeWord only
1355 continue;
1356 }
1357
1358 resp.available_wake_words.emplace_back();
1359 auto &resp_wake_word = resp.available_wake_words.back();
1360 resp_wake_word.id = StringRef(wake_word.id);
1361 resp_wake_word.wake_word = StringRef(wake_word.wake_word);
1362 for (const auto &lang : wake_word.trained_languages) {
1363 resp_wake_word.trained_languages.push_back(lang);
1364 }
1365 }
1366
1367 resp.active_wake_words = &config.active_wake_words;
1368 resp.max_active_wake_words = config.max_active_wake_words;
1369 return this->send_message(resp);
1370}
1371void APIConnection::on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &msg) {
1372 if (!this->send_voice_assistant_get_configuration_response_(msg)) {
1373 this->on_fatal_error();
1374 }
1375}
1376
1377void APIConnection::on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) {
1378 if (this->check_voice_assistant_api_connection_()) {
1380 }
1381}
1382#endif
1383
1384#ifdef USE_ZWAVE_PROXY
1385void APIConnection::on_z_wave_proxy_frame(const ZWaveProxyFrame &msg) {
1387}
1388
1389void APIConnection::on_z_wave_proxy_request(const ZWaveProxyRequest &msg) {
1391}
1392#endif
1393
1394#ifdef USE_ALARM_CONTROL_PANEL
1395bool APIConnection::send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) {
1396 return this->send_message_smart_(a_alarm_control_panel, AlarmControlPanelStateResponse::MESSAGE_TYPE,
1397 AlarmControlPanelStateResponse::ESTIMATED_SIZE);
1398}
1399uint16_t APIConnection::try_send_alarm_control_panel_state(EntityBase *entity, APIConnection *conn,
1400 uint32_t remaining_size) {
1401 auto *a_alarm_control_panel = static_cast<alarm_control_panel::AlarmControlPanel *>(entity);
1403 resp.state = static_cast<enums::AlarmControlPanelState>(a_alarm_control_panel->get_state());
1404 return fill_and_encode_entity_state(a_alarm_control_panel, resp, conn, remaining_size);
1405}
1406uint16_t APIConnection::try_send_alarm_control_panel_info(EntityBase *entity, APIConnection *conn,
1407 uint32_t remaining_size) {
1408 auto *a_alarm_control_panel = static_cast<alarm_control_panel::AlarmControlPanel *>(entity);
1410 msg.supported_features = a_alarm_control_panel->get_supported_features();
1411 msg.requires_code = a_alarm_control_panel->get_requires_code();
1412 msg.requires_code_to_arm = a_alarm_control_panel->get_requires_code_to_arm();
1413 return fill_and_encode_entity_info(a_alarm_control_panel, msg, conn, remaining_size);
1414}
1415void APIConnection::on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) {
1416 ENTITY_COMMAND_MAKE_CALL(alarm_control_panel::AlarmControlPanel, a_alarm_control_panel, alarm_control_panel)
1417 switch (msg.command) {
1418 case enums::ALARM_CONTROL_PANEL_DISARM:
1419 call.disarm();
1420 break;
1421 case enums::ALARM_CONTROL_PANEL_ARM_AWAY:
1422 call.arm_away();
1423 break;
1424 case enums::ALARM_CONTROL_PANEL_ARM_HOME:
1425 call.arm_home();
1426 break;
1427 case enums::ALARM_CONTROL_PANEL_ARM_NIGHT:
1428 call.arm_night();
1429 break;
1430 case enums::ALARM_CONTROL_PANEL_ARM_VACATION:
1431 call.arm_vacation();
1432 break;
1433 case enums::ALARM_CONTROL_PANEL_ARM_CUSTOM_BYPASS:
1434 call.arm_custom_bypass();
1435 break;
1436 case enums::ALARM_CONTROL_PANEL_TRIGGER:
1437 call.pending();
1438 break;
1439 }
1440 call.set_code(msg.code.c_str(), msg.code.size());
1441 call.perform();
1442}
1443#endif
1444
1445#ifdef USE_WATER_HEATER
1446bool APIConnection::send_water_heater_state(water_heater::WaterHeater *water_heater) {
1447 return this->send_message_smart_(water_heater, WaterHeaterStateResponse::MESSAGE_TYPE,
1448 WaterHeaterStateResponse::ESTIMATED_SIZE);
1449}
1450uint16_t APIConnection::try_send_water_heater_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
1451 auto *wh = static_cast<water_heater::WaterHeater *>(entity);
1453 resp.mode = static_cast<enums::WaterHeaterMode>(wh->get_mode());
1454 resp.current_temperature = wh->get_current_temperature();
1455 resp.target_temperature = wh->get_target_temperature();
1456 resp.target_temperature_low = wh->get_target_temperature_low();
1457 resp.target_temperature_high = wh->get_target_temperature_high();
1458 resp.state = wh->get_state();
1459
1460 return fill_and_encode_entity_state(wh, resp, conn, remaining_size);
1461}
1462uint16_t APIConnection::try_send_water_heater_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
1463 auto *wh = static_cast<water_heater::WaterHeater *>(entity);
1465 auto traits = wh->get_traits();
1466 msg.min_temperature = traits.get_min_temperature();
1467 msg.max_temperature = traits.get_max_temperature();
1468 msg.target_temperature_step = traits.get_target_temperature_step();
1469 msg.supported_modes = &traits.get_supported_modes();
1470 msg.supported_features = traits.get_feature_flags();
1471 return fill_and_encode_entity_info(wh, msg, conn, remaining_size);
1472}
1473
1474void APIConnection::on_water_heater_command_request(const WaterHeaterCommandRequest &msg) {
1475 ENTITY_COMMAND_MAKE_CALL(water_heater::WaterHeater, water_heater, water_heater)
1476 if (msg.has_fields & enums::WATER_HEATER_COMMAND_HAS_MODE)
1477 call.set_mode(static_cast<water_heater::WaterHeaterMode>(msg.mode));
1478 if (msg.has_fields & enums::WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE)
1479 call.set_target_temperature(msg.target_temperature);
1480 if (msg.has_fields & enums::WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_LOW)
1481 call.set_target_temperature_low(msg.target_temperature_low);
1482 if (msg.has_fields & enums::WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_HIGH)
1483 call.set_target_temperature_high(msg.target_temperature_high);
1484 if ((msg.has_fields & enums::WATER_HEATER_COMMAND_HAS_AWAY_STATE) ||
1485 (msg.has_fields & enums::WATER_HEATER_COMMAND_HAS_STATE)) {
1486 call.set_away((msg.state & water_heater::WATER_HEATER_STATE_AWAY) != 0);
1487 }
1488 if ((msg.has_fields & enums::WATER_HEATER_COMMAND_HAS_ON_STATE) ||
1489 (msg.has_fields & enums::WATER_HEATER_COMMAND_HAS_STATE)) {
1490 call.set_on((msg.state & water_heater::WATER_HEATER_STATE_ON) != 0);
1491 }
1492 call.perform();
1493}
1494#endif
1495
1496#ifdef USE_EVENT
1497// Event is a special case - unlike other entities with simple state fields,
1498// events store their state in a member accessed via obj->get_last_event_type()
1499void APIConnection::send_event(event::Event *event) {
1500 this->send_message_smart_(event, EventResponse::MESSAGE_TYPE, EventResponse::ESTIMATED_SIZE,
1501 event->get_last_event_type_index());
1502}
1503uint16_t APIConnection::try_send_event_response(event::Event *event, StringRef event_type, APIConnection *conn,
1504 uint32_t remaining_size) {
1505 EventResponse resp;
1506 resp.event_type = event_type;
1507 return fill_and_encode_entity_state(event, resp, conn, remaining_size);
1508}
1509
1510uint16_t APIConnection::try_send_event_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
1511 auto *event = static_cast<event::Event *>(entity);
1513 msg.event_types = &event->get_event_types();
1514 return fill_and_encode_entity_info_with_device_class(event, msg, msg.device_class, conn, remaining_size);
1515}
1516#endif
1517
1518#if defined(USE_IR_RF) || defined(USE_RADIO_FREQUENCY)
1519void APIConnection::on_infrared_rf_transmit_raw_timings_request(const InfraredRFTransmitRawTimingsRequest &msg) {
1520 // Dispatch by key: infrared entities are checked first, then radio frequency entities.
1521 // The key is unique across all entity instances on a device, so at most one lookup will succeed.
1522#ifdef USE_INFRARED
1523 ENTITY_COMMAND_LOOKUP(infrared::Infrared, infrared, infrared);
1524 if (infrared != nullptr) {
1525 auto call = infrared->make_call();
1526 call.set_carrier_frequency(msg.carrier_frequency);
1527 call.set_raw_timings_packed(msg.timings_data_, msg.timings_length_, msg.timings_count_);
1528 call.set_repeat_count(msg.repeat_count);
1529 call.perform();
1530 return;
1531 }
1532#endif
1533#ifdef USE_RADIO_FREQUENCY
1534 ENTITY_COMMAND_LOOKUP(radio_frequency::RadioFrequency, radio_frequency, radio_frequency);
1535 if (radio_frequency != nullptr) {
1536 auto call = radio_frequency->make_call();
1537 call.set_frequency(msg.carrier_frequency);
1538 call.set_modulation(static_cast<radio_frequency::RadioFrequencyModulation>(msg.modulation));
1539 call.set_repeat_count(msg.repeat_count);
1540 call.set_raw_timings_packed(msg.timings_data_, msg.timings_length_, msg.timings_count_);
1541 call.perform();
1542 }
1543#endif
1544}
1545#endif
1546
1547#if defined(USE_IR_RF) || defined(USE_RADIO_FREQUENCY)
1548void APIConnection::send_infrared_rf_receive_event(const InfraredRFReceiveEvent &msg) { this->send_message(msg); }
1549#endif
1550
1551#ifdef USE_SERIAL_PROXY
1552void APIConnection::on_serial_proxy_configure_request(const SerialProxyConfigureRequest &msg) {
1553 auto &proxies = App.get_serial_proxies();
1554 if (msg.instance >= proxies.size()) {
1555 ESP_LOGW(TAG, "Serial proxy instance %" PRIu32 " out of range (max %" PRIu32 ")", msg.instance,
1556 static_cast<uint32_t>(proxies.size()));
1557 return;
1558 }
1559 proxies[msg.instance]->configure(msg.baudrate, msg.flow_control, static_cast<uint8_t>(msg.parity), msg.stop_bits,
1560 msg.data_size);
1561}
1562
1563void APIConnection::on_serial_proxy_write_request(const SerialProxyWriteRequest &msg) {
1564 auto &proxies = App.get_serial_proxies();
1565 if (msg.instance >= proxies.size()) {
1566 ESP_LOGW(TAG, "Serial proxy instance %" PRIu32 " out of range", msg.instance);
1567 return;
1568 }
1569 proxies[msg.instance]->write_from_client(msg.data, msg.data_len);
1570}
1571
1572void APIConnection::on_serial_proxy_set_modem_pins_request(const SerialProxySetModemPinsRequest &msg) {
1573 auto &proxies = App.get_serial_proxies();
1574 if (msg.instance >= proxies.size()) {
1575 ESP_LOGW(TAG, "Serial proxy instance %" PRIu32 " out of range", msg.instance);
1576 return;
1577 }
1578 proxies[msg.instance]->set_modem_pins(msg.line_states);
1579}
1580
1581void APIConnection::on_serial_proxy_get_modem_pins_request(const SerialProxyGetModemPinsRequest &msg) {
1582 auto &proxies = App.get_serial_proxies();
1583 if (msg.instance >= proxies.size()) {
1584 ESP_LOGW(TAG, "Serial proxy instance %" PRIu32 " out of range", msg.instance);
1585 return;
1586 }
1588 resp.instance = msg.instance;
1589 resp.line_states = proxies[msg.instance]->get_modem_pins();
1590 this->send_message(resp);
1591}
1592
1593void APIConnection::on_serial_proxy_request(const SerialProxyRequest &msg) {
1594 auto &proxies = App.get_serial_proxies();
1595 if (msg.instance >= proxies.size()) {
1596 ESP_LOGW(TAG, "Serial proxy instance %" PRIu32 " out of range", msg.instance);
1597 return;
1598 }
1599 switch (msg.type) {
1600 case enums::SERIAL_PROXY_REQUEST_TYPE_SUBSCRIBE:
1601 case enums::SERIAL_PROXY_REQUEST_TYPE_UNSUBSCRIBE:
1602 proxies[msg.instance]->serial_proxy_request(this, msg.type);
1603 break;
1604 case enums::SERIAL_PROXY_REQUEST_TYPE_FLUSH: {
1606 resp.instance = msg.instance;
1607 resp.type = enums::SERIAL_PROXY_REQUEST_TYPE_FLUSH;
1608 switch (proxies[msg.instance]->flush_port()) {
1610 resp.status = enums::SERIAL_PROXY_STATUS_OK;
1611 break;
1613 resp.status = enums::SERIAL_PROXY_STATUS_ASSUMED_SUCCESS;
1614 break;
1616 resp.status = enums::SERIAL_PROXY_STATUS_TIMEOUT;
1617 break;
1619 resp.status = enums::SERIAL_PROXY_STATUS_ERROR;
1620 break;
1621 }
1622 this->send_message(resp);
1623 break;
1624 }
1625 default:
1626 ESP_LOGW(TAG, "Unknown serial proxy request type: %" PRIu32, static_cast<uint32_t>(msg.type));
1627 break;
1628 }
1629}
1630
1631void APIConnection::send_serial_proxy_data(const SerialProxyDataReceived &msg) { this->send_message(msg); }
1632#endif
1633
1634#ifdef USE_INFRARED
1635uint16_t APIConnection::try_send_infrared_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
1636 auto *infrared = static_cast<infrared::Infrared *>(entity);
1638 msg.capabilities = infrared->get_capability_flags();
1639 msg.receiver_frequency = infrared->get_traits().get_receiver_frequency_hz();
1640 return fill_and_encode_entity_info(infrared, msg, conn, remaining_size);
1641}
1642#endif
1643
1644#ifdef USE_RADIO_FREQUENCY
1645uint16_t APIConnection::try_send_radio_frequency_info(EntityBase *entity, APIConnection *conn,
1646 uint32_t remaining_size) {
1647 auto *rf = static_cast<radio_frequency::RadioFrequency *>(entity);
1649 msg.capabilities = rf->get_capability_flags();
1650 msg.frequency_min = rf->get_traits().get_frequency_min_hz();
1651 msg.frequency_max = rf->get_traits().get_frequency_max_hz();
1652 msg.supported_modulations = rf->get_traits().get_supported_modulations();
1653 return fill_and_encode_entity_info(rf, msg, conn, remaining_size);
1654}
1655#endif
1656
1657#ifdef USE_UPDATE
1658bool APIConnection::send_update_state(update::UpdateEntity *update) {
1659 return this->send_message_smart_(update, UpdateStateResponse::MESSAGE_TYPE, UpdateStateResponse::ESTIMATED_SIZE);
1660}
1661uint16_t APIConnection::try_send_update_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
1662 auto *update = static_cast<update::UpdateEntity *>(entity);
1664 resp.missing_state = !update->has_state();
1665 if (update->has_state()) {
1667 if (update->update_info.has_progress) {
1668 resp.has_progress = true;
1669 resp.progress = update->update_info.progress;
1670 }
1671 resp.current_version = StringRef(update->update_info.current_version);
1672 resp.latest_version = StringRef(update->update_info.latest_version);
1673 resp.title = StringRef(update->update_info.title);
1674 resp.release_summary = StringRef(update->update_info.summary);
1675 resp.release_url = StringRef(update->update_info.release_url);
1676 }
1677 return fill_and_encode_entity_state(update, resp, conn, remaining_size);
1678}
1679uint16_t APIConnection::try_send_update_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
1680 auto *update = static_cast<update::UpdateEntity *>(entity);
1682 return fill_and_encode_entity_info_with_device_class(update, msg, msg.device_class, conn, remaining_size);
1683}
1684void APIConnection::on_update_command_request(const UpdateCommandRequest &msg) {
1685 ENTITY_COMMAND_GET(update::UpdateEntity, update, update)
1686
1687 switch (msg.command) {
1688 case enums::UPDATE_COMMAND_UPDATE:
1689 update->perform();
1690 break;
1691 case enums::UPDATE_COMMAND_CHECK:
1692 update->check();
1693 break;
1694 case enums::UPDATE_COMMAND_NONE:
1695 ESP_LOGE(TAG, "UPDATE_COMMAND_NONE not handled; confirm command is correct");
1696 break;
1697 default:
1698 ESP_LOGW(TAG, "Unknown update command: %" PRIu32, msg.command);
1699 break;
1700 }
1701}
1702#endif
1703
1704bool APIConnection::try_send_log_message(int level, const char *tag, const char *line, size_t message_len) {
1706 msg.level = static_cast<enums::LogLevel>(level);
1707 msg.set_message(reinterpret_cast<const uint8_t *>(line), message_len);
1708 return this->send_message(msg);
1709}
1710
1711void APIConnection::complete_authentication_() {
1712 // Early return if already authenticated
1713 if (this->flags_.connection_state == static_cast<uint8_t>(ConnectionState::AUTHENTICATED)) {
1714 return;
1715 }
1716
1717 this->flags_.connection_state = static_cast<uint8_t>(ConnectionState::AUTHENTICATED);
1718 // Reset traffic timer so keepalive starts from authentication, not connection start
1719 this->last_traffic_ = App.get_loop_component_start_time();
1720 this->log_client_(ESPHOME_LOG_LEVEL_DEBUG, LOG_STR("connected"));
1721#ifdef USE_API_CLIENT_CONNECTED_TRIGGER
1722 {
1723 char peername[socket::SOCKADDR_STR_LEN];
1724 this->parent_->get_client_connected_trigger()->trigger(std::string(this->helper_->get_client_name()),
1725 std::string(this->helper_->get_peername_to(peername)));
1726 }
1727#endif
1728#ifdef USE_HOMEASSISTANT_TIME
1730 this->send_time_request();
1731 }
1732#endif
1733#ifdef USE_ZWAVE_PROXY
1734 if (zwave_proxy::global_zwave_proxy != nullptr) {
1736 }
1737#endif
1738}
1739
1740bool APIConnection::send_hello_response_(const HelloRequest &msg) {
1741 // Copy client name with truncation if needed (set_client_name handles truncation)
1742 this->helper_->set_client_name(msg.client_info.c_str(), msg.client_info.size());
1743 this->client_api_version_major_ = msg.api_version_major;
1744 this->client_api_version_minor_ = msg.api_version_minor;
1745 char peername[socket::SOCKADDR_STR_LEN];
1746 ESP_LOGV(TAG, "Hello from client: '%s' | %s | API Version %" PRIu16 ".%" PRIu16, this->helper_->get_client_name(),
1747 this->helper_->get_peername_to(peername), this->client_api_version_major_, this->client_api_version_minor_);
1748
1749 // TODO: Remove before 2026.8.0 (one version after get_object_id backward compat removal)
1750 if (!this->client_supports_api_version(1, 14)) {
1751 ESP_LOGW(TAG, "'%s' using outdated API %" PRIu16 ".%" PRIu16 ", update to 1.14+", this->helper_->get_client_name(),
1752 this->client_api_version_major_, this->client_api_version_minor_);
1753 }
1754
1755 HelloResponse resp;
1756 resp.api_version_major = 1;
1757 resp.api_version_minor = 14;
1758 // Send only the version string - the client only logs this for debugging and doesn't use it otherwise
1759 resp.server_info = ESPHOME_VERSION_REF;
1760 resp.name = StringRef(App.get_name());
1761
1762#ifdef USE_PROVISIONING
1764 // The provisioning window has closed without the device being provisioned.
1765 // Acknowledge the hello so the client can read the server name, then request
1766 // disconnect with the reason. Authentication is intentionally not completed.
1767 this->log_client_(ESPHOME_LOG_LEVEL_WARN, LOG_STR("Provisioning closed; rejecting connection"));
1768 this->send_message(resp);
1770 req.reason = enums::DISCONNECT_REASON_PROVISIONING_CLOSED;
1771 return this->send_message(req);
1772 }
1773#endif
1774
1775 // Auto-authenticate - password auth was removed in ESPHome 2026.1.0
1776 this->complete_authentication_();
1777
1778 return this->send_message(resp);
1779}
1780
1781bool APIConnection::send_ping_response_() {
1782 PingResponse resp;
1783 return this->send_message(resp);
1784}
1785
1786bool APIConnection::send_device_info_response_() {
1787 DeviceInfoResponse resp;
1788 resp.name = StringRef(App.get_name());
1790#ifdef USE_AREAS
1792#endif
1793 // Stack buffer for MAC address (XX:XX:XX:XX:XX:XX\0 = 18 bytes)
1794 char mac_address[18];
1795 uint8_t mac[6];
1797 format_mac_addr_upper(mac, mac_address);
1798 resp.mac_address = StringRef(mac_address);
1799
1800 resp.esphome_version = ESPHOME_VERSION_REF;
1801
1802 // Stack buffer for build time string
1803 char build_time_str[Application::BUILD_TIME_STR_SIZE];
1804 App.get_build_time_string(build_time_str);
1805 resp.compilation_time = StringRef(build_time_str);
1806
1807 // Manufacturer string - define once, handle ESP8266 PROGMEM separately
1808#if defined(USE_ESP8266) || defined(USE_ESP32)
1809#define ESPHOME_MANUFACTURER "Espressif"
1810#elif defined(USE_RP2)
1811#define ESPHOME_MANUFACTURER "Raspberry Pi"
1812#elif defined(USE_BK72XX)
1813#define ESPHOME_MANUFACTURER "Beken"
1814#elif defined(USE_LN882X)
1815#define ESPHOME_MANUFACTURER "Lightning"
1816#elif defined(USE_NRF52)
1817#define ESPHOME_MANUFACTURER "Nordic Semiconductor"
1818#elif defined(USE_RTL87XX)
1819#define ESPHOME_MANUFACTURER "Realtek"
1820#elif defined(USE_HOST)
1821#define ESPHOME_MANUFACTURER "Host"
1822#endif
1823
1824#ifdef USE_ESP8266
1825 // ESP8266 requires PROGMEM for flash storage, copy to stack for memcpy compatibility
1826 static const char MANUFACTURER_PROGMEM[] PROGMEM = ESPHOME_MANUFACTURER;
1827 char manufacturer_buf[sizeof(MANUFACTURER_PROGMEM)];
1828 memcpy_P(manufacturer_buf, MANUFACTURER_PROGMEM, sizeof(MANUFACTURER_PROGMEM));
1829 resp.manufacturer = StringRef(manufacturer_buf, sizeof(MANUFACTURER_PROGMEM) - 1);
1830#else
1831 static constexpr auto MANUFACTURER = StringRef::from_lit(ESPHOME_MANUFACTURER);
1832 resp.manufacturer = MANUFACTURER;
1833#endif
1834 static_assert(sizeof(ESPHOME_MANUFACTURER) - 1 <= 20, "Update max_data_length for manufacturer in api.proto");
1835#undef ESPHOME_MANUFACTURER
1836
1837#ifdef USE_ESP8266
1838 static const char MODEL_PROGMEM[] PROGMEM = ESPHOME_BOARD;
1839 char model_buf[sizeof(MODEL_PROGMEM)];
1840 memcpy_P(model_buf, MODEL_PROGMEM, sizeof(MODEL_PROGMEM));
1841 resp.model = StringRef(model_buf, sizeof(MODEL_PROGMEM) - 1);
1842#else
1843 static constexpr auto MODEL = StringRef::from_lit(ESPHOME_BOARD);
1844 resp.model = MODEL;
1845#endif
1846#ifdef USE_DEEP_SLEEP
1848#endif
1849#ifdef ESPHOME_PROJECT_NAME
1850#ifdef USE_ESP8266
1851 static const char PROJECT_NAME_PROGMEM[] PROGMEM = ESPHOME_PROJECT_NAME;
1852 static const char PROJECT_VERSION_PROGMEM[] PROGMEM = ESPHOME_PROJECT_VERSION;
1853 char project_name_buf[sizeof(PROJECT_NAME_PROGMEM)];
1854 char project_version_buf[sizeof(PROJECT_VERSION_PROGMEM)];
1855 memcpy_P(project_name_buf, PROJECT_NAME_PROGMEM, sizeof(PROJECT_NAME_PROGMEM));
1856 memcpy_P(project_version_buf, PROJECT_VERSION_PROGMEM, sizeof(PROJECT_VERSION_PROGMEM));
1857 resp.project_name = StringRef(project_name_buf, sizeof(PROJECT_NAME_PROGMEM) - 1);
1858 resp.project_version = StringRef(project_version_buf, sizeof(PROJECT_VERSION_PROGMEM) - 1);
1859#else
1860 static constexpr auto PROJECT_NAME = StringRef::from_lit(ESPHOME_PROJECT_NAME);
1861 static constexpr auto PROJECT_VERSION = StringRef::from_lit(ESPHOME_PROJECT_VERSION);
1862 resp.project_name = PROJECT_NAME;
1863 resp.project_version = PROJECT_VERSION;
1864#endif
1865#endif
1866#ifdef USE_WEBSERVER
1867 resp.webserver_port = USE_WEBSERVER_PORT;
1868#endif
1869#ifdef USE_BLUETOOTH_PROXY
1871 // Stack buffer for Bluetooth MAC address (XX:XX:XX:XX:XX:XX\0 = 18 bytes)
1872 char bluetooth_mac[18];
1874 resp.bluetooth_mac_address = StringRef(bluetooth_mac);
1875#endif
1876#ifdef USE_VOICE_ASSISTANT
1878#endif
1879#ifdef USE_ZWAVE_PROXY
1882#endif
1883#ifdef USE_SERIAL_PROXY
1884 size_t serial_proxy_index = 0;
1885 for (auto const &proxy : App.get_serial_proxies()) {
1886 if (serial_proxy_index >= SERIAL_PROXY_COUNT)
1887 break;
1888 auto &info = resp.serial_proxies[serial_proxy_index++];
1889 info.name = StringRef(proxy->get_name());
1890 info.port_type = proxy->get_port_type();
1891 }
1892#endif
1893#ifdef USE_API_NOISE
1894 resp.api_encryption_supported = true;
1895#ifndef USE_API_NOISE_PSK_FROM_YAML
1896 // No key from YAML: while no key is set, the key can be provisioned over a
1897 // zero-PSK Noise connection. Gated on the YAML define (not the plaintext
1898 // one) so this advertisement survives the plaintext removal in 2027.2.0.
1899 resp.api_encryption_provisionable = !this->parent_->get_noise_ctx().has_psk();
1900#endif
1901#endif
1902#ifdef USE_DEVICES
1903 size_t device_index = 0;
1904 for (auto const &device : App.get_devices()) {
1905 if (device_index >= ESPHOME_DEVICE_COUNT)
1906 break;
1907 auto &device_info = resp.devices[device_index++];
1908 device_info.device_id = device->get_device_id();
1909 device_info.name = StringRef(device->get_name());
1910 device_info.area_id = device->get_area_id();
1911 }
1912#endif
1913#ifdef USE_AREAS
1914 size_t area_index = 0;
1915 for (auto const &area : App.get_areas()) {
1916 if (area_index >= ESPHOME_AREA_COUNT)
1917 break;
1918 auto &area_info = resp.areas[area_index++];
1919 area_info.area_id = area->get_area_id();
1920 area_info.name = StringRef(area->get_name());
1921 }
1922#endif
1923
1924 return this->send_message(resp);
1925}
1926void APIConnection::on_hello_request(const HelloRequest &msg) {
1927 if (!this->send_hello_response_(msg)) {
1928 this->on_fatal_error();
1929 }
1930}
1931void APIConnection::on_disconnect_request(const DisconnectRequest & /*msg*/) {
1932 // The reason is informational when a client disconnects us; we always ack and close.
1933 if (!this->send_disconnect_response_()) {
1934 this->on_fatal_error();
1935 }
1936}
1937void APIConnection::on_ping_request() {
1938 if (!this->send_ping_response_()) {
1939 this->on_fatal_error();
1940 }
1941}
1942void APIConnection::on_device_info_request() {
1943 if (!this->send_device_info_response_()) {
1944 this->on_fatal_error();
1945 }
1946}
1947
1948#ifdef USE_API_HOMEASSISTANT_STATES
1949void APIConnection::on_home_assistant_state_response(const HomeAssistantStateResponse &msg) {
1950 // Skip if entity_id is empty (invalid message)
1951 if (msg.entity_id.empty()) {
1952 return;
1953 }
1954
1955 // Null-terminate state in-place for safe c_str() usage (e.g., parse_number in callbacks).
1956 // Safe: decode is complete, byte after string data was already consumed during parse,
1957 // and frame helpers reserve RX_BUF_NULL_TERMINATOR extra byte in rx_buf_.
1958 // const_cast is safe: msg references mutable rx_buf_ data; the const& handler
1959 // signature is a generated protobuf pattern, not a true immutability contract.
1960 if (!msg.state.empty()) {
1961 const_cast<char *>(msg.state.c_str())[msg.state.size()] = '\0';
1962 }
1963
1964 for (auto &it : this->parent_->get_state_subs()) {
1965 if (msg.entity_id != it.entity_id) {
1966 continue;
1967 }
1968
1969 // If subscriber has attribute filter (non-null), message attribute must match it;
1970 // if subscriber has no filter (nullptr), message must have no attribute.
1971 if (it.attribute != nullptr ? msg.attribute != it.attribute : !msg.attribute.empty()) {
1972 continue;
1973 }
1974
1975 it.callback(msg.state);
1976 }
1977}
1978#endif
1979#ifdef USE_API_USER_DEFINED_ACTIONS
1980void APIConnection::on_execute_service_request(const ExecuteServiceRequest &msg) {
1981 // Null-terminate string args in-place for safe c_str() usage in YAML service triggers.
1982 // Safe: full ExecuteServiceRequest decode is complete, all bytes in rx_buf_ consumed,
1983 // and frame helpers reserve RX_BUF_NULL_TERMINATOR extra byte for the last field.
1984 // const_cast is safe: msg references mutable rx_buf_ data; the const& handler
1985 // signature is a generated protobuf pattern, not a true immutability contract.
1986 for (auto &arg : const_cast<ExecuteServiceRequest &>(msg).args) {
1987 if (!arg.string_.empty()) {
1988 const_cast<char *>(arg.string_.c_str())[arg.string_.size()] = '\0';
1989 }
1990 }
1991 bool found = false;
1992#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES
1993 // Register the call and get a unique server-generated action_call_id
1994 // This avoids collisions when multiple clients use the same call_id
1995 uint32_t action_call_id = 0;
1996 if (msg.call_id != 0) {
1997 action_call_id = this->parent_->register_active_action_call(msg.call_id, this);
1998 }
1999 // Use the overload that passes action_call_id separately (avoids copying msg)
2000 for (auto *service : this->parent_->get_user_services()) {
2001 if (service->execute_service(msg, action_call_id)) {
2002 found = true;
2003 }
2004 }
2005#else
2006 for (auto *service : this->parent_->get_user_services()) {
2007 if (service->execute_service(msg)) {
2008 found = true;
2009 }
2010 }
2011#endif
2012 if (!found) {
2013 ESP_LOGV(TAG, "Could not find service");
2014 }
2015 // Note: For services with supports_response != none, the call is unregistered
2016 // by an automatically appended APIUnregisterServiceCallAction at the end of
2017 // the action list. This ensures async actions (delays, waits) complete first.
2018}
2019#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES
2020void APIConnection::send_execute_service_response(uint32_t call_id, bool success, StringRef error_message) {
2022 resp.call_id = call_id;
2023 resp.success = success;
2024 resp.error_message = error_message;
2025 this->send_message(resp);
2026}
2027#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
2028void APIConnection::send_execute_service_response(uint32_t call_id, bool success, StringRef error_message,
2029 const uint8_t *response_data, size_t response_data_len) {
2031 resp.call_id = call_id;
2032 resp.success = success;
2033 resp.error_message = error_message;
2034 resp.response_data = response_data;
2035 resp.response_data_len = response_data_len;
2036 this->send_message(resp);
2037}
2038#endif // USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
2039#endif // USE_API_USER_DEFINED_ACTION_RESPONSES
2040#endif
2041
2042#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
2043void APIConnection::on_homeassistant_action_response(const HomeassistantActionResponse &msg) {
2044#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
2045 if (msg.response_data_len > 0) {
2046 this->parent_->handle_action_response(msg.call_id, msg.success, msg.error_message, msg.response_data,
2047 msg.response_data_len);
2048 } else
2049#endif
2050 {
2051 this->parent_->handle_action_response(msg.call_id, msg.success, msg.error_message);
2052 }
2053};
2054#endif
2055#ifdef USE_API_NOISE
2056bool APIConnection::send_noise_encryption_set_key_response_(const NoiseEncryptionSetKeyRequest &msg) {
2058 resp.success = false;
2059
2060#ifdef USE_PROVISIONING
2061 // Refuse to set a key once the provisioning window has closed (defense in depth;
2062 // such connections are already rejected at hello).
2064 ESP_LOGW(TAG, "Provisioning closed; rejecting key set");
2065 return this->send_message(resp);
2066 }
2067#endif
2068
2069 psk_t psk{};
2070 if (msg.key_len == 0) {
2071 if (this->parent_->clear_noise_psk(true)) {
2072 resp.success = true;
2073 } else {
2074 ESP_LOGW(TAG, "Failed to clear encryption key");
2075 }
2076 } else if (base64_decode(msg.key, msg.key_len, psk.data(), psk.size()) != psk.size()) {
2077 ESP_LOGW(TAG, "Invalid encryption key length");
2078 } else if (APINoiseContext::is_all_zeros(psk)) {
2079 // Accepting the reserved provisioning PSK would report success without
2080 // enabling encryption (or silently clear an existing key)
2081 ESP_LOGW(TAG, "Rejecting all-zero encryption key");
2082 } else if (!this->parent_->save_noise_psk(psk, true)) {
2083 ESP_LOGW(TAG, "Failed to save encryption key");
2084 } else {
2085 resp.success = true;
2086#ifdef USE_API_PLAINTEXT
2087 if (this->helper_->frame_footer_size() == 0) {
2088 // Plaintext transport has no frame footer; Noise always has the MAC footer.
2089 // Remove after 2027.2.0 together with plaintext support on keyless devices.
2090 ESP_LOGW(TAG, "Key received over plaintext; deprecated, will be removed in 2027.2.0");
2091 }
2092#endif
2093 }
2094
2095 return this->send_message(resp);
2096}
2097void APIConnection::on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) {
2098 if (!this->send_noise_encryption_set_key_response_(msg)) {
2099 this->on_fatal_error();
2100 }
2101}
2102#endif
2103#ifdef USE_API_HOMEASSISTANT_STATES
2104void APIConnection::on_subscribe_home_assistant_states_request() { state_subs_at_ = 0; }
2105#endif
2106bool APIConnection::try_to_clear_buffer_slow_(bool log_out_of_space) {
2107 delay(0);
2108 APIError err = this->helper_->loop();
2109 if (err != APIError::OK) {
2110 this->fatal_error_with_log_(LOG_STR("Socket operation failed"), err);
2111 return false;
2112 }
2113 if (this->helper_->can_write_without_blocking())
2114 return true;
2115 if (log_out_of_space) {
2116 ESP_LOGV(TAG, "Cannot send message because of TCP buffer space");
2117 }
2118 return false;
2119}
2120bool APIConnection::send_message_(uint32_t payload_size, uint8_t message_type, MessageEncodeFn encode_fn,
2121 const void *msg) {
2122#ifdef HAS_PROTO_MESSAGE_DUMP
2123 // Skip dump for log messages (recursive logging risk) and camera frames (high-frequency noise)
2124 if (message_type != SubscribeLogsResponse::MESSAGE_TYPE
2125#ifdef USE_CAMERA
2126 && message_type != CameraImageResponse::MESSAGE_TYPE
2127#endif
2128 ) {
2129 auto *proto_msg = static_cast<const ProtoMessage *>(msg);
2130 DumpBuffer dump_buf;
2131 this->log_send_message_(proto_msg->message_name(), proto_msg->dump_to(dump_buf));
2132 }
2133#endif
2134 auto &shared_buf = this->parent_->get_shared_buffer_ref();
2135 this->prepare_first_message_buffer(shared_buf, payload_size);
2136 size_t write_start = shared_buf.size();
2137 shared_buf.resize(write_start + payload_size);
2138 ProtoWriteBuffer buffer{&shared_buf, write_start};
2139 encode_fn(msg, buffer PROTO_ENCODE_DEBUG_INIT(&shared_buf));
2140 return this->send_buffer(ProtoWriteBuffer{&shared_buf}, message_type);
2141}
2142// encode_to_buffer is defined inline in api_connection.h (ESPHOME_ALWAYS_INLINE)
2143
2144// Noinline version for cold paths — single shared copy
2145uint16_t APIConnection::encode_to_buffer_slow(uint32_t calculated_size, MessageEncodeFn encode_fn, const void *msg,
2146 APIConnection *conn, uint32_t remaining_size) {
2147 return encode_to_buffer(calculated_size, encode_fn, msg, conn, remaining_size);
2148}
2149bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) {
2150 const bool is_log_message = (message_type == SubscribeLogsResponse::MESSAGE_TYPE);
2151
2152 if (!this->try_to_clear_buffer(!is_log_message)) {
2153 return false;
2154 }
2155
2156 // Set TCP_NODELAY based on message type - see set_nodelay_for_message() for details
2157 this->helper_->set_nodelay_for_message(is_log_message);
2158
2159 APIError err = this->helper_->write_protobuf_packet(message_type, buffer);
2160 if (err == APIError::WOULD_BLOCK)
2161 return false;
2162 if (err != APIError::OK) {
2163 this->fatal_error_with_log_(LOG_STR("Packet write failed"), err);
2164 return false;
2165 }
2166 // Do not set last_traffic_ on send
2167 return true;
2168}
2169void APIConnection::on_no_setup_connection() {
2170 this->on_fatal_error();
2171 this->log_client_(ESPHOME_LOG_LEVEL_DEBUG, LOG_STR("no connection setup"));
2172}
2173void APIConnection::on_fatal_error() {
2174 // Don't close socket here - keep it open so getpeername() works for logging
2175 // Socket will be closed when client is removed from the list in APIServer::loop()
2176 this->flags_.remove = true;
2177}
2178
2179bool APIConnection::schedule_message_front_(EntityBase *entity, uint8_t message_type, uint8_t estimated_size) {
2180 this->deferred_batch_.add_item_front(entity, message_type, estimated_size);
2181 return this->schedule_batch_();
2182}
2183
2184bool APIConnection::send_message_smart_(EntityBase *entity, uint8_t message_type, uint8_t estimated_size,
2185 uint8_t aux_data_index) {
2186 if (this->should_send_immediately_(message_type) && this->helper_->can_write_without_blocking()) {
2187 auto &shared_buf = this->parent_->get_shared_buffer_ref();
2188 this->prepare_first_message_buffer(shared_buf, estimated_size);
2189 DeferredBatch::BatchItem item{entity, message_type, estimated_size, aux_data_index};
2190 if (this->dispatch_message_(item, MAX_BATCH_PACKET_SIZE, true) &&
2191 this->send_buffer(ProtoWriteBuffer{&shared_buf}, message_type)) {
2192#ifdef HAS_PROTO_MESSAGE_DUMP
2193 this->log_batch_item_(item);
2194#endif
2195 return true;
2196 }
2197 }
2198 return this->schedule_message_(entity, message_type, estimated_size, aux_data_index);
2199}
2200
2201bool APIConnection::schedule_batch_() {
2202 if (!this->flags_.batch_scheduled) {
2203 this->flags_.batch_scheduled = true;
2204 this->deferred_batch_.batch_start_time = App.get_loop_component_start_time();
2205 }
2206 return true;
2207}
2208
2209void APIConnection::process_batch_() {
2210 if (this->deferred_batch_.empty()) {
2211 this->flags_.batch_scheduled = false;
2212 return;
2213 }
2214
2215 // Ensure TCP_NODELAY is on before draining overflow and writing batch data.
2216 // Log messages enable Nagle (NODELAY off) to coalesce small packets.
2217 // If Nagle is still on when we try to drain, LWIP holds data in the
2218 // Nagle buffer, the TCP send buffer stays full, and the overflow
2219 // buffer can never drain — blocking the batch write indefinitely.
2220 this->helper_->set_nodelay_for_message(false);
2221
2222 // Try to clear buffer first
2223 if (!this->try_to_clear_buffer(true)) {
2224 // Can't write now, we'll try again later
2225 return;
2226 }
2227
2228 // Get shared buffer reference once to avoid multiple calls
2229 auto &shared_buf = this->parent_->get_shared_buffer_ref();
2230 size_t num_items = this->deferred_batch_.size();
2231
2232 // Cache these values to avoid repeated virtual calls
2233 const uint8_t header_padding = this->helper_->frame_header_padding();
2234 const uint8_t footer_size = this->helper_->frame_footer_size();
2235
2236 // Pre-calculate exact buffer size needed based on message types
2237 uint32_t total_estimated_size = num_items * (header_padding + footer_size);
2238 for (size_t i = 0; i < num_items; i++) {
2239 total_estimated_size += this->deferred_batch_[i].estimated_size;
2240 }
2241 // Clamp to MAX_BATCH_PACKET_SIZE — we won't send more than that per batch
2242 if (total_estimated_size > MAX_BATCH_PACKET_SIZE) {
2243 total_estimated_size = MAX_BATCH_PACKET_SIZE;
2244 }
2245
2246 this->prepare_first_message_buffer(shared_buf, header_padding, total_estimated_size);
2247
2248 // Fast path for single message - buffer already allocated above
2249 if (num_items == 1) {
2250 const auto &item = this->deferred_batch_[0];
2251 // Let dispatch_message_ calculate size and encode if it fits
2252 uint16_t payload_size = this->dispatch_message_(item, std::numeric_limits<uint16_t>::max(), true);
2253
2254 if (payload_size > 0 && this->send_buffer(ProtoWriteBuffer{&shared_buf}, item.message_type)) {
2255#ifdef HAS_PROTO_MESSAGE_DUMP
2256 // Log message after send attempt for VV debugging
2257 this->log_batch_item_(item);
2258#endif
2259 this->clear_batch_();
2260 } else if (payload_size == 0) {
2261 // Message too large to fit in available space
2262 ESP_LOGW(TAG, "Message too large to send: type=%u", item.message_type);
2263 this->clear_batch_();
2264 }
2265 return;
2266 }
2267
2268 // Multi-message path — heavy stack frame isolated in separate noinline function
2269 this->process_batch_multi_(shared_buf, num_items, header_padding, footer_size);
2270}
2271
2272// Separated from process_batch_() so the single-message fast path gets a minimal
2273// stack frame without the MAX_MESSAGES_PER_BATCH * sizeof(MessageInfo) array.
2274void APIConnection::process_batch_multi_(APIBuffer &shared_buf, size_t num_items, uint8_t header_padding,
2275 uint8_t footer_size) {
2276 // Ensure MessageInfo remains trivially destructible for our placement new approach
2277 static_assert(std::is_trivially_destructible<MessageInfo>::value,
2278 "MessageInfo must remain trivially destructible with this placement-new approach");
2279
2280 const size_t messages_to_process = std::min(num_items, MAX_MESSAGES_PER_BATCH);
2281
2282 // Stack-allocated array for message info
2283 alignas(MessageInfo) char message_info_storage[MAX_MESSAGES_PER_BATCH * sizeof(MessageInfo)];
2284 MessageInfo *message_info = reinterpret_cast<MessageInfo *>(message_info_storage);
2285 size_t items_processed = 0;
2286 uint16_t remaining_size = std::numeric_limits<uint16_t>::max();
2287 // Track where each message's header begins in the buffer
2288 // First message: offset 0 (max padding, may have unused leading bytes)
2289 // Subsequent messages: offset points to exact header start (no gaps)
2290 uint32_t current_offset = 0;
2291
2292 // Process items and encode directly to buffer (up to our limit)
2293 for (size_t i = 0; i < messages_to_process; i++) {
2294 const auto &item = this->deferred_batch_[i];
2295 // Try to encode message via dispatch
2296 // The dispatch function calculates overhead to determine if the message fits
2297 uint16_t payload_size = this->dispatch_message_(item, remaining_size, i == 0);
2298
2299 if (payload_size == 0) {
2300 // Message won't fit, stop processing
2301 break;
2302 }
2303
2304 // Message was encoded successfully
2305 // payload_size = header_size + proto_payload_size + footer_size
2306 uint16_t proto_payload_size = payload_size - this->batch_header_size_ - footer_size;
2307 // Use placement new to construct MessageInfo in pre-allocated stack array
2308 // This avoids default-constructing all MAX_MESSAGES_PER_BATCH elements
2309 // Explicit destruction is not needed because MessageInfo is trivially destructible,
2310 // as ensured by the static_assert in its definition.
2311 new (&message_info[items_processed++])
2312 MessageInfo(item.message_type, current_offset, proto_payload_size, this->batch_header_size_);
2313 // After first message, set remaining size to MAX_BATCH_PACKET_SIZE to avoid fragmentation
2314 if (items_processed == 1) {
2315 remaining_size = MAX_BATCH_PACKET_SIZE;
2316 }
2317 remaining_size -= payload_size;
2318 // Calculate where the next message's header padding will start
2319 // Current buffer size + footer space for this message
2320 current_offset = shared_buf.size() + footer_size;
2321 }
2322
2323 if (items_processed > 0) {
2324 // Add footer space for the last message (for Noise protocol MAC)
2325 if (footer_size > 0) {
2326 shared_buf.resize(shared_buf.size() + footer_size);
2327 }
2328
2329 // Send all collected messages
2330 APIError err = this->helper_->write_protobuf_messages(ProtoWriteBuffer{&shared_buf},
2331 std::span<const MessageInfo>(message_info, items_processed));
2332 if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
2333 this->fatal_error_with_log_(LOG_STR("Batch write failed"), err);
2334 }
2335
2336#ifdef HAS_PROTO_MESSAGE_DUMP
2337 // Log messages after send attempt for VV debugging
2338 // It's safe to use the buffer for logging at this point regardless of send result
2339 for (size_t i = 0; i < items_processed; i++) {
2340 const auto &item = this->deferred_batch_[i];
2341 this->log_batch_item_(item);
2342 }
2343#endif
2344
2345 // Partial batch — remove processed items and reschedule
2346 if (items_processed < this->deferred_batch_.size()) {
2347 this->deferred_batch_.remove_front(items_processed);
2348 this->schedule_batch_();
2349 return;
2350 }
2351 }
2352
2353 // All items processed (or none could be processed)
2354 this->clear_batch_();
2355}
2356
2357// Dispatch message encoding based on message_type
2358// Switch assigns function pointer, single call site for smaller code size
2359uint16_t APIConnection::dispatch_message_(const DeferredBatch::BatchItem &item, uint32_t remaining_size,
2360 bool batch_first) {
2361 this->flags_.batch_first_message = batch_first;
2362 this->batch_message_type_ = item.message_type;
2363#ifdef USE_EVENT
2364 // Events need aux_data_index to look up event type from entity
2365 if (item.message_type == EventResponse::MESSAGE_TYPE) {
2366 // Skip if aux_data_index is invalid (should never happen in normal operation)
2367 if (item.aux_data_index == DeferredBatch::AUX_DATA_UNUSED)
2368 return 0;
2369 auto *event = static_cast<event::Event *>(item.entity);
2370 return try_send_event_response(event, StringRef::from_maybe_nullptr(event->get_event_type(item.aux_data_index)),
2371 this, remaining_size);
2372 }
2373#endif
2374
2375 // All other message types use function pointer lookup via switch
2376 MessageCreatorPtr func = nullptr;
2377
2378// Macros to reduce repetitive switch cases
2379#define CASE_STATE_INFO(entity_name, StateResp, InfoResp) \
2380 case StateResp::MESSAGE_TYPE: \
2381 func = &try_send_##entity_name##_state; \
2382 break; \
2383 case InfoResp::MESSAGE_TYPE: \
2384 func = &try_send_##entity_name##_info; \
2385 break;
2386#define CASE_INFO_ONLY(entity_name, InfoResp) \
2387 case InfoResp::MESSAGE_TYPE: \
2388 func = &try_send_##entity_name##_info; \
2389 break;
2390
2391 switch (item.message_type) {
2392#ifdef USE_BINARY_SENSOR
2393 CASE_STATE_INFO(binary_sensor, BinarySensorStateResponse, ListEntitiesBinarySensorResponse)
2394#endif
2395#ifdef USE_COVER
2396 CASE_STATE_INFO(cover, CoverStateResponse, ListEntitiesCoverResponse)
2397#endif
2398#ifdef USE_FAN
2399 CASE_STATE_INFO(fan, FanStateResponse, ListEntitiesFanResponse)
2400#endif
2401#ifdef USE_LIGHT
2402 CASE_STATE_INFO(light, LightStateResponse, ListEntitiesLightResponse)
2403#endif
2404#ifdef USE_SENSOR
2405 CASE_STATE_INFO(sensor, SensorStateResponse, ListEntitiesSensorResponse)
2406#endif
2407#ifdef USE_SWITCH
2408 CASE_STATE_INFO(switch, SwitchStateResponse, ListEntitiesSwitchResponse)
2409#endif
2410#ifdef USE_BUTTON
2411 CASE_INFO_ONLY(button, ListEntitiesButtonResponse)
2412#endif
2413#ifdef USE_TEXT_SENSOR
2414 CASE_STATE_INFO(text_sensor, TextSensorStateResponse, ListEntitiesTextSensorResponse)
2415#endif
2416#ifdef USE_CLIMATE
2417 CASE_STATE_INFO(climate, ClimateStateResponse, ListEntitiesClimateResponse)
2418#endif
2419#ifdef USE_NUMBER
2420 CASE_STATE_INFO(number, NumberStateResponse, ListEntitiesNumberResponse)
2421#endif
2422#ifdef USE_DATETIME_DATE
2423 CASE_STATE_INFO(date, DateStateResponse, ListEntitiesDateResponse)
2424#endif
2425#ifdef USE_DATETIME_TIME
2426 CASE_STATE_INFO(time, TimeStateResponse, ListEntitiesTimeResponse)
2427#endif
2428#ifdef USE_DATETIME_DATETIME
2429 CASE_STATE_INFO(datetime, DateTimeStateResponse, ListEntitiesDateTimeResponse)
2430#endif
2431#ifdef USE_TEXT
2432 CASE_STATE_INFO(text, TextStateResponse, ListEntitiesTextResponse)
2433#endif
2434#ifdef USE_SELECT
2435 CASE_STATE_INFO(select, SelectStateResponse, ListEntitiesSelectResponse)
2436#endif
2437#ifdef USE_LOCK
2439#endif
2440#ifdef USE_VALVE
2441 CASE_STATE_INFO(valve, ValveStateResponse, ListEntitiesValveResponse)
2442#endif
2443#ifdef USE_MEDIA_PLAYER
2444 CASE_STATE_INFO(media_player, MediaPlayerStateResponse, ListEntitiesMediaPlayerResponse)
2445#endif
2446#ifdef USE_ALARM_CONTROL_PANEL
2447 CASE_STATE_INFO(alarm_control_panel, AlarmControlPanelStateResponse, ListEntitiesAlarmControlPanelResponse)
2448#endif
2449#ifdef USE_WATER_HEATER
2450 CASE_STATE_INFO(water_heater, WaterHeaterStateResponse, ListEntitiesWaterHeaterResponse)
2451#endif
2452#ifdef USE_CAMERA
2453 CASE_INFO_ONLY(camera, ListEntitiesCameraResponse)
2454#endif
2455#ifdef USE_INFRARED
2456 CASE_INFO_ONLY(infrared, ListEntitiesInfraredResponse)
2457#endif
2458#ifdef USE_RADIO_FREQUENCY
2459 CASE_INFO_ONLY(radio_frequency, ListEntitiesRadioFrequencyResponse)
2460#endif
2461#ifdef USE_EVENT
2462 CASE_INFO_ONLY(event, ListEntitiesEventResponse)
2463#endif
2464#ifdef USE_UPDATE
2465 CASE_STATE_INFO(update, UpdateStateResponse, ListEntitiesUpdateResponse)
2466#endif
2467 // Special messages (not entity state/info)
2468 case ListEntitiesDoneResponse::MESSAGE_TYPE:
2469 func = &try_send_list_info_done;
2470 break;
2471 case DisconnectRequest::MESSAGE_TYPE:
2472 func = &try_send_disconnect_request;
2473 break;
2474 case PingRequest::MESSAGE_TYPE:
2475 func = &try_send_ping_request;
2476 break;
2477 default:
2478 return 0;
2479 }
2480
2481#undef CASE_STATE_INFO
2482#undef CASE_INFO_ONLY
2483
2484 return func(item.entity, this, remaining_size);
2485}
2486
2487uint16_t APIConnection::try_send_list_info_done(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
2489 return encode_message_to_buffer(resp, conn, remaining_size);
2490}
2491
2492uint16_t APIConnection::try_send_disconnect_request(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
2494 return encode_message_to_buffer(req, conn, remaining_size);
2495}
2496
2497uint16_t APIConnection::try_send_ping_request(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
2498 PingRequest req;
2499 return encode_message_to_buffer(req, conn, remaining_size);
2500}
2501
2502#ifdef USE_API_HOMEASSISTANT_STATES
2503void APIConnection::process_state_subscriptions_() {
2504 const auto &subs = this->parent_->get_state_subs();
2505 if (this->state_subs_at_ >= static_cast<int>(subs.size())) {
2506 this->state_subs_at_ = -1;
2507 return;
2508 }
2509
2510 const auto &it = subs[this->state_subs_at_];
2512 resp.entity_id = StringRef(it.entity_id);
2513
2514 // Avoid string copy by using the const char* pointer if it exists
2515 resp.attribute = it.attribute != nullptr ? StringRef(it.attribute) : StringRef("");
2516
2517 resp.once = it.once;
2518 if (this->send_message(resp)) {
2519 this->state_subs_at_++;
2520 }
2521}
2522#endif // USE_API_HOMEASSISTANT_STATES
2523
2524void APIConnection::log_client_(int level, const LogString *message) {
2525 char peername[socket::SOCKADDR_STR_LEN];
2526 esp_log_printf_(level, TAG, __LINE__, ESPHOME_LOG_FORMAT("%s (%s): %s"), this->helper_->get_client_name(),
2527 this->helper_->get_peername_to(peername), LOG_STR_ARG(message));
2528}
2529
2530void APIConnection::log_warning_(const LogString *message, APIError err) {
2531 char peername[socket::SOCKADDR_STR_LEN];
2532 ESP_LOGW(TAG, "%s (%s): %s %s errno=%d", this->helper_->get_client_name(), this->helper_->get_peername_to(peername),
2533 LOG_STR_ARG(message), LOG_STR_ARG(api_error_to_logstr(err)), errno);
2534}
2535
2536} // namespace esphome::api
2537#endif
const StringRef & get_name() const
Get the 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)
const StringRef & get_friendly_name() const
Get the friendly name of this Application set by pre_setup().
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()
auto & get_serial_proxies() const
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)
const char * get_device_class_to(std::span< char, MAX_DEVICE_CLASS_LENGTH > buffer) const
bool has_own_name() const
Definition entity_base.h:74
const StringRef & get_name() const
Definition entity_base.h:71
ESPDEPRECATED("Use get_unit_of_measurement_ref() instead for better performance (avoids string copy). Will be " "removed in ESPHome 2026.9.0", "2026.3.0") std const char * get_icon_to(std::span< char, MAX_ICON_LENGTH > buffer) const
Get the unit of measurement as std::string (deprecated, prefer get_unit_of_measurement_ref())
uint32_t get_object_id_hash() const
Definition entity_base.h:77
ESPDEPRECATED("Use get_icon_to() instead. Will be removed in ESPHome 2026.9.0", "2026.3.0") std uint32_t get_device_id() const
bool is_disabled_by_default() const
EntityCategory get_entity_category() const
Fixed-capacity vector - allocates once at runtime, never reallocates This avoids std::vector template...
Definition helpers.h:534
void push_back(const T &value)
Add element without bounds checking Caller must ensure sufficient capacity was allocated via init() S...
Definition helpers.h:651
void init(size_t n)
Definition helpers.h:624
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
Byte buffer that skips zero-initialization on resize().
Definition api_buffer.h:36
size_t size() const
Definition api_buffer.h:55
void resize(size_t n) ESPHOME_ALWAYS_INLINE
Definition api_buffer.h:43
void on_button_command_request(const ButtonCommandRequest &msg)
uint8_t *(*)(const void *, ProtoWriteBuffer &PROTO_ENCODE_DEBUG_PARAM) MessageEncodeFn
APIConnection(std::unique_ptr< socket::Socket > socket, APIServer *parent)
uint16_t(*)(EntityBase *, APIConnection *, uint32_t remaining_size) MessageCreatorPtr
uint32_t(*)(const void *) CalculateSizeFn
uint8_t get_consumed_header(uint8_t out[3]) const
APINoiseContext & get_noise_ctx()
Definition api_server.h:79
enums::AlarmControlPanelStateCommand command
Definition api_pb2.h:2646
enums::AlarmControlPanelState state
Definition api_pb2.h:2630
enums::BluetoothScannerMode mode
Definition api_pb2.h:2344
void set_data(const uint8_t *data, size_t len)
Definition api_pb2.h:1359
enums::ClimateSwingMode swing_mode
Definition api_pb2.h:1470
enums::ClimateFanMode fan_mode
Definition api_pb2.h:1468
enums::ClimatePreset preset
Definition api_pb2.h:1474
enums::ClimateFanMode fan_mode
Definition api_pb2.h:1437
enums::ClimateSwingMode swing_mode
Definition api_pb2.h:1438
enums::ClimateAction action
Definition api_pb2.h:1436
enums::ClimatePreset preset
Definition api_pb2.h:1440
enums::CoverOperation current_operation
Definition api_pb2.h:682
std::array< AreaInfo, ESPHOME_AREA_COUNT > areas
Definition api_pb2.h:578
std::array< SerialProxyInfo, SERIAL_PROXY_COUNT > serial_proxies
Definition api_pb2.h:590
std::array< DeviceInfo, ESPHOME_DEVICE_COUNT > devices
Definition api_pb2.h:575
enums::DisconnectReason reason
Definition api_pb2.h:441
Fixed-size buffer for message dumps - avoids heap allocation.
Definition proto.h:543
enums::FanDirection direction
Definition api_pb2.h:765
enums::FanDirection direction
Definition api_pb2.h:742
ParsedTimezone parsed_timezone
Definition api_pb2.h:1222
enums::EntityCategory entity_category
Definition api_pb2.h:367
enums::ColorMode color_mode
Definition api_pb2.h:809
const std::vector< const char * > * supported_custom_presets
Definition api_pb2.h:1408
const climate::ClimateSwingModeMask * supported_swing_modes
Definition api_pb2.h:1405
const std::vector< const char * > * supported_custom_fan_modes
Definition api_pb2.h:1406
const climate::ClimatePresetMask * supported_presets
Definition api_pb2.h:1407
const climate::ClimateFanModeMask * supported_fan_modes
Definition api_pb2.h:1404
const climate::ClimateModeMask * supported_modes
Definition api_pb2.h:1399
const FixedVector< const char * > * event_types
Definition api_pb2.h:2830
const std::vector< const char * > * supported_preset_modes
Definition api_pb2.h:724
const FixedVector< const char * > * effects
Definition api_pb2.h:791
const light::ColorModeMask * supported_color_modes
Definition api_pb2.h:788
std::vector< MediaPlayerSupportedFormat > supported_formats
Definition api_pb2.h:1835
const FixedVector< const char * > * options
Definition api_pb2.h:1618
enums::SensorStateClass state_class
Definition api_pb2.h:882
const water_heater::WaterHeaterModeMask * supported_modes
Definition api_pb2.h:1500
enums::LockCommand command
Definition api_pb2.h:1765
enums::MediaPlayerCommand command
Definition api_pb2.h:1871
enums::MediaPlayerState state
Definition api_pb2.h:1852
enums::SerialProxyParity parity
Definition api_pb2.h:3159
enums::SerialProxyRequestType type
Definition api_pb2.h:3265
void set_message(const uint8_t *data, size_t len)
Definition api_pb2.h:1021
enums::UpdateCommand command
Definition api_pb2.h:3010
enums::ValveOperation current_operation
Definition api_pb2.h:2884
std::vector< VoiceAssistantExternalWakeWord > external_wake_words
Definition api_pb2.h:2562
std::vector< VoiceAssistantWakeWord > available_wake_words
Definition api_pb2.h:2577
const std::vector< std::string > * active_wake_words
Definition api_pb2.h:2578
std::vector< std::string > active_wake_words
Definition api_pb2.h:2595
enums::ZWaveProxyRequestType type
Definition api_pb2.h:3046
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_set_connection_params(const api::BluetoothSetConnectionParamsRequest &msg)
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:114
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:18
ClimateDevice - This is the base class for all climate integrations.
Definition climate.h:187
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:53
Infrared - Base class for infrared remote control implementations.
Definition infrared.h:114
This class represents the communication layer between the front-end MQTT layer and the hardware outpu...
Definition light_state.h:93
Base class for all locks.
Definition lock.h:112
Base-class for all numbers.
Definition number.h:29
RadioFrequency - Base class for radio frequency implementations.
Base-class for all selects.
Definition select.h:29
Base-class for all sensors.
Definition sensor.h:47
Base class for all switches.
Definition switch.h:38
Base-class for all text inputs.
Definition text.h:21
void set_timezone(const char *tz)
Set the time zone from a POSIX TZ string.
Base class for all valve devices.
Definition valve.h:103
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)
uint32_t get_feature_flags() const
Definition zwave_proxy.h:66
void send_frame(api::APIConnection *api_connection, const uint8_t *data, size_t length)
void api_connection_authenticated(api::APIConnection *conn)
const LogString * message
Definition component.cpp:35
uint16_t type
bool state
Definition fan.h:2
uint32_t socklen_t
Definition headers.h:99
const LogString * api_error_to_logstr(APIError err)
std::array< uint8_t, 32 > psk_t
BluetoothProxy * global_bluetooth_proxy
@ CLIMATE_SUPPORTS_CURRENT_HUMIDITY
@ CLIMATE_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE
@ CLIMATE_SUPPORTS_CURRENT_TEMPERATURE
@ CLIMATE_REQUIRES_TWO_POINT_TARGET_TEMPERATURE
ClimatePreset
Enum for all preset modes NOTE: If adding values, update ClimatePresetMask in climate_traits....
ClimateSwingMode
Enum for all modes a climate swing can be in NOTE: If adding values, update ClimateSwingModeMask in c...
ClimateMode
Enum for all modes a climate device can be in.
ClimateFanMode
NOTE: If adding values, update ClimateFanModeMask in climate_traits.h to use the new last value.
FanDirection
Simple enum to represent the direction of a fan.
Definition fan.h:20
HomeassistantTime * global_homeassistant_time
ColorMode
Color modes are a combination of color capabilities that can be used at the same time.
Definition color_mode.h:49
@ COLOR_TEMPERATURE
Color temperature can be controlled.
@ COLD_WARM_WHITE
Brightness of cold and warm white output can be controlled.
ProvisioningManager * global_provisioning_manager
RadioFrequencyModulation
Modulation types supported by radio frequency implementations.
void set_global_tz(const ParsedTimezone &tz)
Set the global timezone used by epoch_to_local_tm() when called without a timezone.
Definition posix_tz.cpp:15
DSTRuleType
Type of DST transition rule.
Definition posix_tz.h:11
@ UART_FLUSH_RESULT_ASSUMED_SUCCESS
Platform cannot report result; success is assumed.
@ UART_FLUSH_RESULT_SUCCESS
Confirmed: all bytes left the TX FIFO.
@ UART_FLUSH_RESULT_FAILED
Confirmed: driver or hardware error.
@ UART_FLUSH_RESULT_TIMEOUT
Confirmed: timed out before TX completed.
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
const char int line
Definition log.h:74
const char * tag
Definition log.h:74
const char int const __FlashStringHelper va_list args
Definition log.h:74
void HOT esp_log_printf_(int level, const char *tag, int line, const char *format,...)
Definition log.cpp:21
const void size_t len
Definition hal.h:64
std::vector< uint8_t > base64_decode(const std::string &encoded_string)
Decode a base64 string to a byte vector.
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:74
void HOT delay(uint32_t ms)
Definition hal.cpp:85
Application App
Global storage of Application pointer - only one Application can exist.
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:1467
static void uint32_t
A more user-friendly version of struct tm from time.h.
Definition time.h:23
uint16_t day
Day of year (for JULIAN_NO_LEAP and DAY_OF_YEAR)
Definition posix_tz.h:21
DSTRuleType type
Type of rule.
Definition posix_tz.h:22
uint8_t week
Week 1-5, 5 = last (for MONTH_WEEK_DAY)
Definition posix_tz.h:24
int32_t time_seconds
Seconds after midnight (default 7200 = 2:00 AM)
Definition posix_tz.h:20
uint8_t day_of_week
Day 0-6, 0 = Sunday (for MONTH_WEEK_DAY)
Definition posix_tz.h:25
uint8_t month
Month 1-12 (for MONTH_WEEK_DAY)
Definition posix_tz.h:23
Parsed POSIX timezone information (packed for 32-bit: 32 bytes)
Definition posix_tz.h:29
DSTRule dst_end
When DST ends.
Definition posix_tz.h:33
DSTRule dst_start
When DST starts.
Definition posix_tz.h:32
int32_t dst_offset_seconds
DST offset from UTC in seconds.
Definition posix_tz.h:31
int32_t std_offset_seconds
Standard time offset from UTC in seconds (positive = west)
Definition posix_tz.h:30
uint32_t payload_size()
SemaphoreHandle_t lock
const uint8_t ESPHOME_WEBSERVER_INDEX_HTML[] PROGMEM
Definition web_server.h:28