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