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