ESPHome 2025.7.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#include <cerrno>
4#include <cinttypes>
5#include <utility>
6#include <functional>
7#include <limits>
11#include "esphome/core/hal.h"
12#include "esphome/core/log.h"
14
15#ifdef USE_DEEP_SLEEP
17#endif
18#ifdef USE_HOMEASSISTANT_TIME
20#endif
21#ifdef USE_BLUETOOTH_PROXY
23#endif
24#ifdef USE_VOICE_ASSISTANT
26#endif
27
28namespace esphome {
29namespace api {
30
31// Read a maximum of 5 messages per loop iteration to prevent starving other components.
32// This is a balance between API responsiveness and allowing other components to run.
33// Since each message could contain multiple protobuf messages when using packet batching,
34// this limits the number of messages processed, not the number of TCP packets.
35static constexpr uint8_t MAX_MESSAGES_PER_LOOP = 5;
36static constexpr uint8_t MAX_PING_RETRIES = 60;
37static constexpr uint16_t PING_RETRY_INTERVAL = 1000;
38static constexpr uint32_t KEEPALIVE_DISCONNECT_TIMEOUT = (KEEPALIVE_TIMEOUT_MS * 5) / 2;
39
40static const char *const TAG = "api.connection";
41#ifdef USE_CAMERA
42static const int CAMERA_STOP_STREAM = 5000;
43#endif
44
45// Helper macro for entity command handlers - gets entity by key, returns if not found, and creates call object
46#define ENTITY_COMMAND_MAKE_CALL(entity_type, entity_var, getter_name) \
47 entity_type *entity_var = App.get_##getter_name##_by_key(msg.key); \
48 if ((entity_var) == nullptr) \
49 return; \
50 auto call = (entity_var)->make_call();
51
52// Helper macro for entity command handlers that don't use make_call() - gets entity by key and returns if not found
53#define ENTITY_COMMAND_GET(entity_type, entity_var, getter_name) \
54 entity_type *entity_var = App.get_##getter_name##_by_key(msg.key); \
55 if ((entity_var) == nullptr) \
56 return;
57
58APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *parent)
59 : parent_(parent), initial_state_iterator_(this), list_entities_iterator_(this) {
60#if defined(USE_API_PLAINTEXT) && defined(USE_API_NOISE)
61 auto noise_ctx = parent->get_noise_ctx();
62 if (noise_ctx->has_psk()) {
63 this->helper_ = std::unique_ptr<APIFrameHelper>{new APINoiseFrameHelper(std::move(sock), noise_ctx)};
64 } else {
65 this->helper_ = std::unique_ptr<APIFrameHelper>{new APIPlaintextFrameHelper(std::move(sock))};
66 }
67#elif defined(USE_API_PLAINTEXT)
68 this->helper_ = std::unique_ptr<APIFrameHelper>{new APIPlaintextFrameHelper(std::move(sock))};
69#elif defined(USE_API_NOISE)
70 this->helper_ = std::unique_ptr<APIFrameHelper>{new APINoiseFrameHelper(std::move(sock), parent->get_noise_ctx())};
71#else
72#error "No frame helper defined"
73#endif
74#ifdef USE_CAMERA
75 if (camera::Camera::instance() != nullptr) {
76 this->image_reader_ = std::unique_ptr<camera::CameraImageReader>{camera::Camera::instance()->create_image_reader()};
77 }
78#endif
79}
80
81uint32_t APIConnection::get_batch_delay_ms_() const { return this->parent_->get_batch_delay(); }
82
83void APIConnection::start() {
84 this->last_traffic_ = App.get_loop_component_start_time();
85
86 APIError err = this->helper_->init();
87 if (err != APIError::OK) {
88 on_fatal_error();
89 ESP_LOGW(TAG, "%s: Helper init failed: %s errno=%d", this->get_client_combined_info().c_str(),
90 api_error_to_str(err), errno);
91 return;
92 }
93 this->client_info_ = helper_->getpeername();
94 this->client_peername_ = this->client_info_;
95 this->helper_->set_log_info(this->client_info_);
96}
97
98APIConnection::~APIConnection() {
99#ifdef USE_BLUETOOTH_PROXY
100 if (bluetooth_proxy::global_bluetooth_proxy->get_api_connection() == this) {
102 }
103#endif
104#ifdef USE_VOICE_ASSISTANT
105 if (voice_assistant::global_voice_assistant->get_api_connection() == this) {
107 }
108#endif
109}
110
111void APIConnection::loop() {
112 if (this->flags_.next_close) {
113 // requested a disconnect
114 this->helper_->close();
115 this->flags_.remove = true;
116 return;
117 }
118
119 APIError err = this->helper_->loop();
120 if (err != APIError::OK) {
121 on_fatal_error();
122 ESP_LOGW(TAG, "%s: Socket operation failed: %s errno=%d", this->get_client_combined_info().c_str(),
123 api_error_to_str(err), errno);
124 return;
125 }
126
127 const uint32_t now = App.get_loop_component_start_time();
128 // Check if socket has data ready before attempting to read
129 if (this->helper_->is_socket_ready()) {
130 // Read up to MAX_MESSAGES_PER_LOOP messages per loop to improve throughput
131 for (uint8_t message_count = 0; message_count < MAX_MESSAGES_PER_LOOP; message_count++) {
132 ReadPacketBuffer buffer;
133 err = this->helper_->read_packet(&buffer);
134 if (err == APIError::WOULD_BLOCK) {
135 // No more data available
136 break;
137 } else if (err != APIError::OK) {
138 on_fatal_error();
139 if (err == APIError::SOCKET_READ_FAILED && errno == ECONNRESET) {
140 ESP_LOGW(TAG, "%s: Connection reset", this->get_client_combined_info().c_str());
141 } else if (err == APIError::CONNECTION_CLOSED) {
142 ESP_LOGW(TAG, "%s: Connection closed", this->get_client_combined_info().c_str());
143 } else {
144 ESP_LOGW(TAG, "%s: Reading failed: %s errno=%d", this->get_client_combined_info().c_str(),
145 api_error_to_str(err), errno);
146 }
147 return;
148 } else {
149 this->last_traffic_ = now;
150 // read a packet
151 if (buffer.data_len > 0) {
152 this->read_message(buffer.data_len, buffer.type, &buffer.container[buffer.data_offset]);
153 } else {
154 this->read_message(0, buffer.type, nullptr);
155 }
156 if (this->flags_.remove)
157 return;
158 }
159 }
160 }
161
162 // Process deferred batch if scheduled and timer has expired
163 if (this->flags_.batch_scheduled && now - this->deferred_batch_.batch_start_time >= this->get_batch_delay_ms_()) {
164 this->process_batch_();
165 }
166
167 if (!this->list_entities_iterator_.completed()) {
168 this->process_iterator_batch_(this->list_entities_iterator_);
169 } else if (!this->initial_state_iterator_.completed()) {
170 this->process_iterator_batch_(this->initial_state_iterator_);
171
172 // If we've completed initial states, process any remaining and clear the flag
173 if (this->initial_state_iterator_.completed()) {
174 // Process any remaining batched messages immediately
175 if (!this->deferred_batch_.empty()) {
176 this->process_batch_();
177 }
178 // Now that everything is sent, enable immediate sending for future state changes
179 this->flags_.should_try_send_immediately = true;
180 }
181 }
182
183 if (this->flags_.sent_ping) {
184 // Disconnect if not responded within 2.5*keepalive
185 if (now - this->last_traffic_ > KEEPALIVE_DISCONNECT_TIMEOUT) {
186 on_fatal_error();
187 ESP_LOGW(TAG, "%s is unresponsive; disconnecting", this->get_client_combined_info().c_str());
188 }
189 } else if (now - this->last_traffic_ > KEEPALIVE_TIMEOUT_MS) {
190 ESP_LOGVV(TAG, "Sending keepalive PING");
191 this->flags_.sent_ping = this->send_message(PingRequest());
192 if (!this->flags_.sent_ping) {
193 // If we can't send the ping request directly (tx_buffer full),
194 // schedule it at the front of the batch so it will be sent with priority
195 ESP_LOGW(TAG, "Buffer full, ping queued");
196 this->schedule_message_front_(nullptr, &APIConnection::try_send_ping_request, PingRequest::MESSAGE_TYPE,
197 PingRequest::ESTIMATED_SIZE);
198 this->flags_.sent_ping = true; // Mark as sent to avoid scheduling multiple pings
199 }
200 }
201
202#ifdef USE_CAMERA
203 if (this->image_reader_ && this->image_reader_->available() && this->helper_->can_write_without_blocking()) {
204 uint32_t to_send = std::min((size_t) MAX_BATCH_PACKET_SIZE, this->image_reader_->available());
205 bool done = this->image_reader_->available() == to_send;
206 uint32_t msg_size = 0;
207 ProtoSize::add_fixed_field<4>(msg_size, 1, true);
208 // partial message size calculated manually since its a special case
209 // 1 for the data field, varint for the data size, and the data itself
210 msg_size += 1 + ProtoSize::varint(to_send) + to_send;
211 ProtoSize::add_bool_field(msg_size, 1, done);
212
213 auto buffer = this->create_buffer(msg_size);
214 // fixed32 key = 1;
215 buffer.encode_fixed32(1, camera::Camera::instance()->get_object_id_hash());
216 // bytes data = 2;
217 buffer.encode_bytes(2, this->image_reader_->peek_data_buffer(), to_send);
218 // bool done = 3;
219 buffer.encode_bool(3, done);
220
221 bool success = this->send_buffer(buffer, CameraImageResponse::MESSAGE_TYPE);
222
223 if (success) {
224 this->image_reader_->consume_data(to_send);
225 if (done) {
226 this->image_reader_->return_image();
227 }
228 }
229 }
230#endif
231
232 if (state_subs_at_ >= 0) {
233 const auto &subs = this->parent_->get_state_subs();
234 if (state_subs_at_ < static_cast<int>(subs.size())) {
235 auto &it = subs[state_subs_at_];
237 resp.entity_id = it.entity_id;
238 resp.attribute = it.attribute.value();
239 resp.once = it.once;
240 if (this->send_message(resp)) {
241 state_subs_at_++;
242 }
243 } else {
244 state_subs_at_ = -1;
245 }
246 }
247}
248
249std::string get_default_unique_id(const std::string &component_type, EntityBase *entity) {
250 return App.get_name() + component_type + entity->get_object_id();
251}
252
253DisconnectResponse APIConnection::disconnect(const DisconnectRequest &msg) {
254 // remote initiated disconnect_client
255 // don't close yet, we still need to send the disconnect response
256 // close will happen on next loop
257 ESP_LOGD(TAG, "%s disconnected", this->get_client_combined_info().c_str());
258 this->flags_.next_close = true;
260 return resp;
261}
262void APIConnection::on_disconnect_response(const DisconnectResponse &value) {
263 this->helper_->close();
264 this->flags_.remove = true;
265}
266
267// Encodes a message to the buffer and returns the total number of bytes used,
268// including header and footer overhead. Returns 0 if the message doesn't fit.
269uint16_t APIConnection::encode_message_to_buffer(ProtoMessage &msg, uint8_t message_type, APIConnection *conn,
270 uint32_t remaining_size, bool is_single) {
271#ifdef HAS_PROTO_MESSAGE_DUMP
272 // If in log-only mode, just log and return
273 if (conn->flags_.log_only_mode) {
274 conn->log_send_message_(msg.message_name(), msg.dump());
275 return 1; // Return non-zero to indicate "success" for logging
276 }
277#endif
278
279 // Calculate size
280 uint32_t calculated_size = 0;
281 msg.calculate_size(calculated_size);
282
283 // Cache frame sizes to avoid repeated virtual calls
284 const uint8_t header_padding = conn->helper_->frame_header_padding();
285 const uint8_t footer_size = conn->helper_->frame_footer_size();
286
287 // Calculate total size with padding for buffer allocation
288 size_t total_calculated_size = calculated_size + header_padding + footer_size;
289
290 // Check if it fits
291 if (total_calculated_size > remaining_size) {
292 return 0; // Doesn't fit
293 }
294
295 // Allocate buffer space - pass payload size, allocation functions add header/footer space
296 ProtoWriteBuffer buffer = is_single ? conn->allocate_single_message_buffer(calculated_size)
297 : conn->allocate_batch_message_buffer(calculated_size);
298
299 // Get buffer size after allocation (which includes header padding)
300 std::vector<uint8_t> &shared_buf = conn->parent_->get_shared_buffer_ref();
301 size_t size_before_encode = shared_buf.size();
302
303 // Encode directly into buffer
304 msg.encode(buffer);
305
306 // Calculate actual encoded size (not including header that was already added)
307 size_t actual_payload_size = shared_buf.size() - size_before_encode;
308
309 // Return actual total size (header + actual payload + footer)
310 size_t actual_total_size = header_padding + actual_payload_size + footer_size;
311
312 // Verify that calculate_size() returned the correct value
313 assert(calculated_size == actual_payload_size);
314 return static_cast<uint16_t>(actual_total_size);
315}
316
317#ifdef USE_BINARY_SENSOR
318bool APIConnection::send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor) {
319 return this->send_message_smart_(binary_sensor, &APIConnection::try_send_binary_sensor_state,
320 BinarySensorStateResponse::MESSAGE_TYPE, BinarySensorStateResponse::ESTIMATED_SIZE);
321}
322
323uint16_t APIConnection::try_send_binary_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
324 bool is_single) {
325 auto *binary_sensor = static_cast<binary_sensor::BinarySensor *>(entity);
327 resp.state = binary_sensor->state;
328 resp.missing_state = !binary_sensor->has_state();
329 fill_entity_state_base(binary_sensor, resp);
330 return encode_message_to_buffer(resp, BinarySensorStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
331}
332
333uint16_t APIConnection::try_send_binary_sensor_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
334 bool is_single) {
335 auto *binary_sensor = static_cast<binary_sensor::BinarySensor *>(entity);
337 msg.device_class = binary_sensor->get_device_class();
338 msg.is_status_binary_sensor = binary_sensor->is_status_binary_sensor();
339 msg.unique_id = get_default_unique_id("binary_sensor", binary_sensor);
340 fill_entity_info_base(binary_sensor, msg);
341 return encode_message_to_buffer(msg, ListEntitiesBinarySensorResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
342}
343#endif
344
345#ifdef USE_COVER
346bool APIConnection::send_cover_state(cover::Cover *cover) {
347 return this->send_message_smart_(cover, &APIConnection::try_send_cover_state, CoverStateResponse::MESSAGE_TYPE,
348 CoverStateResponse::ESTIMATED_SIZE);
349}
350uint16_t APIConnection::try_send_cover_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
351 bool is_single) {
352 auto *cover = static_cast<cover::Cover *>(entity);
354 auto traits = cover->get_traits();
355 msg.legacy_state =
356 (cover->position == cover::COVER_OPEN) ? enums::LEGACY_COVER_STATE_OPEN : enums::LEGACY_COVER_STATE_CLOSED;
357 msg.position = cover->position;
358 if (traits.get_supports_tilt())
359 msg.tilt = cover->tilt;
360 msg.current_operation = static_cast<enums::CoverOperation>(cover->current_operation);
361 fill_entity_state_base(cover, msg);
362 return encode_message_to_buffer(msg, CoverStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
363}
364uint16_t APIConnection::try_send_cover_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
365 bool is_single) {
366 auto *cover = static_cast<cover::Cover *>(entity);
368 auto traits = cover->get_traits();
369 msg.assumed_state = traits.get_is_assumed_state();
370 msg.supports_position = traits.get_supports_position();
371 msg.supports_tilt = traits.get_supports_tilt();
372 msg.supports_stop = traits.get_supports_stop();
373 msg.device_class = cover->get_device_class();
374 msg.unique_id = get_default_unique_id("cover", cover);
375 fill_entity_info_base(cover, msg);
376 return encode_message_to_buffer(msg, ListEntitiesCoverResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
377}
378void APIConnection::cover_command(const CoverCommandRequest &msg) {
379 ENTITY_COMMAND_MAKE_CALL(cover::Cover, cover, cover)
380 if (msg.has_legacy_command) {
381 switch (msg.legacy_command) {
382 case enums::LEGACY_COVER_COMMAND_OPEN:
383 call.set_command_open();
384 break;
385 case enums::LEGACY_COVER_COMMAND_CLOSE:
386 call.set_command_close();
387 break;
388 case enums::LEGACY_COVER_COMMAND_STOP:
389 call.set_command_stop();
390 break;
391 }
392 }
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())
422 msg.preset_mode = fan->preset_mode;
423 fill_entity_state_base(fan, msg);
424 return encode_message_to_buffer(msg, FanStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
425}
426uint16_t APIConnection::try_send_fan_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
427 bool is_single) {
428 auto *fan = static_cast<fan::Fan *>(entity);
430 auto traits = fan->get_traits();
431 msg.supports_oscillation = traits.supports_oscillation();
432 msg.supports_speed = traits.supports_speed();
433 msg.supports_direction = traits.supports_direction();
434 msg.supported_speed_count = traits.supported_speed_count();
435 for (auto const &preset : traits.supported_preset_modes())
436 msg.supported_preset_modes.push_back(preset);
437 msg.unique_id = get_default_unique_id("fan", fan);
438 fill_entity_info_base(fan, msg);
439 return encode_message_to_buffer(msg, ListEntitiesFanResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
440}
441void APIConnection::fan_command(const FanCommandRequest &msg) {
442 ENTITY_COMMAND_MAKE_CALL(fan::Fan, fan, fan)
443 if (msg.has_state)
444 call.set_state(msg.state);
445 if (msg.has_oscillating)
446 call.set_oscillating(msg.oscillating);
447 if (msg.has_speed_level) {
448 // Prefer level
449 call.set_speed(msg.speed_level);
450 }
451 if (msg.has_direction)
452 call.set_direction(static_cast<fan::FanDirection>(msg.direction));
453 if (msg.has_preset_mode)
454 call.set_preset_mode(msg.preset_mode);
455 call.perform();
456}
457#endif
458
459#ifdef USE_LIGHT
460bool APIConnection::send_light_state(light::LightState *light) {
461 return this->send_message_smart_(light, &APIConnection::try_send_light_state, LightStateResponse::MESSAGE_TYPE,
462 LightStateResponse::ESTIMATED_SIZE);
463}
464uint16_t APIConnection::try_send_light_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
465 bool is_single) {
466 auto *light = static_cast<light::LightState *>(entity);
468 auto traits = light->get_traits();
469 auto values = light->remote_values;
470 auto color_mode = values.get_color_mode();
471 resp.state = values.is_on();
472 resp.color_mode = static_cast<enums::ColorMode>(color_mode);
473 resp.brightness = values.get_brightness();
474 resp.color_brightness = values.get_color_brightness();
475 resp.red = values.get_red();
476 resp.green = values.get_green();
477 resp.blue = values.get_blue();
478 resp.white = values.get_white();
479 resp.color_temperature = values.get_color_temperature();
480 resp.cold_white = values.get_cold_white();
481 resp.warm_white = values.get_warm_white();
482 if (light->supports_effects())
483 resp.effect = light->get_effect_name();
484 fill_entity_state_base(light, resp);
485 return encode_message_to_buffer(resp, LightStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
486}
487uint16_t APIConnection::try_send_light_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
488 bool is_single) {
489 auto *light = static_cast<light::LightState *>(entity);
491 auto traits = light->get_traits();
492 for (auto mode : traits.get_supported_color_modes())
493 msg.supported_color_modes.push_back(static_cast<enums::ColorMode>(mode));
494 msg.legacy_supports_brightness = traits.supports_color_capability(light::ColorCapability::BRIGHTNESS);
495 msg.legacy_supports_rgb = traits.supports_color_capability(light::ColorCapability::RGB);
497 msg.legacy_supports_rgb && (traits.supports_color_capability(light::ColorCapability::WHITE) ||
498 traits.supports_color_capability(light::ColorCapability::COLD_WARM_WHITE));
500 traits.supports_color_capability(light::ColorCapability::COLD_WARM_WHITE);
502 msg.min_mireds = traits.get_min_mireds();
503 msg.max_mireds = traits.get_max_mireds();
504 }
505 if (light->supports_effects()) {
506 msg.effects.emplace_back("None");
507 for (auto *effect : light->get_effects()) {
508 msg.effects.push_back(effect->get_name());
509 }
510 }
511 msg.unique_id = get_default_unique_id("light", light);
512 fill_entity_info_base(light, msg);
513 return encode_message_to_buffer(msg, ListEntitiesLightResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
514}
515void APIConnection::light_command(const LightCommandRequest &msg) {
516 ENTITY_COMMAND_MAKE_CALL(light::LightState, light, light)
517 if (msg.has_state)
518 call.set_state(msg.state);
519 if (msg.has_brightness)
520 call.set_brightness(msg.brightness);
521 if (msg.has_color_mode)
522 call.set_color_mode(static_cast<light::ColorMode>(msg.color_mode));
523 if (msg.has_color_brightness)
524 call.set_color_brightness(msg.color_brightness);
525 if (msg.has_rgb) {
526 call.set_red(msg.red);
527 call.set_green(msg.green);
528 call.set_blue(msg.blue);
529 }
530 if (msg.has_white)
531 call.set_white(msg.white);
532 if (msg.has_color_temperature)
533 call.set_color_temperature(msg.color_temperature);
534 if (msg.has_cold_white)
535 call.set_cold_white(msg.cold_white);
536 if (msg.has_warm_white)
537 call.set_warm_white(msg.warm_white);
538 if (msg.has_transition_length)
539 call.set_transition_length(msg.transition_length);
540 if (msg.has_flash_length)
541 call.set_flash_length(msg.flash_length);
542 if (msg.has_effect)
543 call.set_effect(msg.effect);
544 call.perform();
545}
546#endif
547
548#ifdef USE_SENSOR
549bool APIConnection::send_sensor_state(sensor::Sensor *sensor) {
550 return this->send_message_smart_(sensor, &APIConnection::try_send_sensor_state, SensorStateResponse::MESSAGE_TYPE,
551 SensorStateResponse::ESTIMATED_SIZE);
552}
553
554uint16_t APIConnection::try_send_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
555 bool is_single) {
556 auto *sensor = static_cast<sensor::Sensor *>(entity);
558 resp.state = sensor->state;
559 resp.missing_state = !sensor->has_state();
560 fill_entity_state_base(sensor, resp);
561 return encode_message_to_buffer(resp, SensorStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
562}
563
564uint16_t APIConnection::try_send_sensor_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
565 bool is_single) {
566 auto *sensor = static_cast<sensor::Sensor *>(entity);
568 msg.unit_of_measurement = sensor->get_unit_of_measurement();
569 msg.accuracy_decimals = sensor->get_accuracy_decimals();
570 msg.force_update = sensor->get_force_update();
571 msg.device_class = sensor->get_device_class();
572 msg.state_class = static_cast<enums::SensorStateClass>(sensor->get_state_class());
573 msg.unique_id = sensor->unique_id();
574 if (msg.unique_id.empty())
575 msg.unique_id = get_default_unique_id("sensor", sensor);
576 fill_entity_info_base(sensor, msg);
577 return encode_message_to_buffer(msg, ListEntitiesSensorResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
578}
579#endif
580
581#ifdef USE_SWITCH
582bool APIConnection::send_switch_state(switch_::Switch *a_switch) {
583 return this->send_message_smart_(a_switch, &APIConnection::try_send_switch_state, SwitchStateResponse::MESSAGE_TYPE,
584 SwitchStateResponse::ESTIMATED_SIZE);
585}
586
587uint16_t APIConnection::try_send_switch_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
588 bool is_single) {
589 auto *a_switch = static_cast<switch_::Switch *>(entity);
591 resp.state = a_switch->state;
592 fill_entity_state_base(a_switch, resp);
593 return encode_message_to_buffer(resp, SwitchStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
594}
595
596uint16_t APIConnection::try_send_switch_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
597 bool is_single) {
598 auto *a_switch = static_cast<switch_::Switch *>(entity);
600 msg.assumed_state = a_switch->assumed_state();
601 msg.device_class = a_switch->get_device_class();
602 msg.unique_id = get_default_unique_id("switch", a_switch);
603 fill_entity_info_base(a_switch, msg);
604 return encode_message_to_buffer(msg, ListEntitiesSwitchResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
605}
606void APIConnection::switch_command(const SwitchCommandRequest &msg) {
607 ENTITY_COMMAND_GET(switch_::Switch, a_switch, switch)
608
609 if (msg.state) {
610 a_switch->turn_on();
611 } else {
612 a_switch->turn_off();
613 }
614}
615#endif
616
617#ifdef USE_TEXT_SENSOR
618bool APIConnection::send_text_sensor_state(text_sensor::TextSensor *text_sensor) {
619 return this->send_message_smart_(text_sensor, &APIConnection::try_send_text_sensor_state,
620 TextSensorStateResponse::MESSAGE_TYPE, TextSensorStateResponse::ESTIMATED_SIZE);
621}
622
623uint16_t APIConnection::try_send_text_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
624 bool is_single) {
625 auto *text_sensor = static_cast<text_sensor::TextSensor *>(entity);
627 resp.state = text_sensor->state;
628 resp.missing_state = !text_sensor->has_state();
629 fill_entity_state_base(text_sensor, resp);
630 return encode_message_to_buffer(resp, TextSensorStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
631}
632uint16_t APIConnection::try_send_text_sensor_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
633 bool is_single) {
634 auto *text_sensor = static_cast<text_sensor::TextSensor *>(entity);
636 msg.device_class = text_sensor->get_device_class();
637 msg.unique_id = text_sensor->unique_id();
638 if (msg.unique_id.empty())
639 msg.unique_id = get_default_unique_id("text_sensor", text_sensor);
640 fill_entity_info_base(text_sensor, msg);
641 return encode_message_to_buffer(msg, ListEntitiesTextSensorResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
642}
643#endif
644
645#ifdef USE_CLIMATE
646bool APIConnection::send_climate_state(climate::Climate *climate) {
647 return this->send_message_smart_(climate, &APIConnection::try_send_climate_state, ClimateStateResponse::MESSAGE_TYPE,
648 ClimateStateResponse::ESTIMATED_SIZE);
649}
650uint16_t APIConnection::try_send_climate_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
651 bool is_single) {
652 auto *climate = static_cast<climate::Climate *>(entity);
654 fill_entity_state_base(climate, resp);
655 auto traits = climate->get_traits();
656 resp.mode = static_cast<enums::ClimateMode>(climate->mode);
657 resp.action = static_cast<enums::ClimateAction>(climate->action);
658 if (traits.get_supports_current_temperature())
659 resp.current_temperature = climate->current_temperature;
660 if (traits.get_supports_two_point_target_temperature()) {
661 resp.target_temperature_low = climate->target_temperature_low;
662 resp.target_temperature_high = climate->target_temperature_high;
663 } else {
664 resp.target_temperature = climate->target_temperature;
665 }
666 if (traits.get_supports_fan_modes() && climate->fan_mode.has_value())
667 resp.fan_mode = static_cast<enums::ClimateFanMode>(climate->fan_mode.value());
668 if (!traits.get_supported_custom_fan_modes().empty() && climate->custom_fan_mode.has_value())
669 resp.custom_fan_mode = climate->custom_fan_mode.value();
670 if (traits.get_supports_presets() && climate->preset.has_value()) {
671 resp.preset = static_cast<enums::ClimatePreset>(climate->preset.value());
672 }
673 if (!traits.get_supported_custom_presets().empty() && climate->custom_preset.has_value())
674 resp.custom_preset = climate->custom_preset.value();
675 if (traits.get_supports_swing_modes())
676 resp.swing_mode = static_cast<enums::ClimateSwingMode>(climate->swing_mode);
677 if (traits.get_supports_current_humidity())
678 resp.current_humidity = climate->current_humidity;
679 if (traits.get_supports_target_humidity())
680 resp.target_humidity = climate->target_humidity;
681 return encode_message_to_buffer(resp, ClimateStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
682}
683uint16_t APIConnection::try_send_climate_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
684 bool is_single) {
685 auto *climate = static_cast<climate::Climate *>(entity);
687 auto traits = climate->get_traits();
688 msg.supports_current_temperature = traits.get_supports_current_temperature();
689 msg.supports_current_humidity = traits.get_supports_current_humidity();
690 msg.supports_two_point_target_temperature = traits.get_supports_two_point_target_temperature();
691 msg.supports_target_humidity = traits.get_supports_target_humidity();
692 for (auto mode : traits.get_supported_modes())
693 msg.supported_modes.push_back(static_cast<enums::ClimateMode>(mode));
694 msg.visual_min_temperature = traits.get_visual_min_temperature();
695 msg.visual_max_temperature = traits.get_visual_max_temperature();
696 msg.visual_target_temperature_step = traits.get_visual_target_temperature_step();
697 msg.visual_current_temperature_step = traits.get_visual_current_temperature_step();
698 msg.visual_min_humidity = traits.get_visual_min_humidity();
699 msg.visual_max_humidity = traits.get_visual_max_humidity();
700 msg.legacy_supports_away = traits.supports_preset(climate::CLIMATE_PRESET_AWAY);
701 msg.supports_action = traits.get_supports_action();
702 for (auto fan_mode : traits.get_supported_fan_modes())
703 msg.supported_fan_modes.push_back(static_cast<enums::ClimateFanMode>(fan_mode));
704 for (auto const &custom_fan_mode : traits.get_supported_custom_fan_modes())
706 for (auto preset : traits.get_supported_presets())
707 msg.supported_presets.push_back(static_cast<enums::ClimatePreset>(preset));
708 for (auto const &custom_preset : traits.get_supported_custom_presets())
710 for (auto swing_mode : traits.get_supported_swing_modes())
711 msg.supported_swing_modes.push_back(static_cast<enums::ClimateSwingMode>(swing_mode));
712 msg.unique_id = get_default_unique_id("climate", climate);
713 fill_entity_info_base(climate, msg);
714 return encode_message_to_buffer(msg, ListEntitiesClimateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
715}
716void APIConnection::climate_command(const ClimateCommandRequest &msg) {
717 ENTITY_COMMAND_MAKE_CALL(climate::Climate, climate, climate)
718 if (msg.has_mode)
719 call.set_mode(static_cast<climate::ClimateMode>(msg.mode));
721 call.set_target_temperature(msg.target_temperature);
723 call.set_target_temperature_low(msg.target_temperature_low);
725 call.set_target_temperature_high(msg.target_temperature_high);
726 if (msg.has_target_humidity)
727 call.set_target_humidity(msg.target_humidity);
728 if (msg.has_fan_mode)
729 call.set_fan_mode(static_cast<climate::ClimateFanMode>(msg.fan_mode));
730 if (msg.has_custom_fan_mode)
731 call.set_fan_mode(msg.custom_fan_mode);
732 if (msg.has_preset)
733 call.set_preset(static_cast<climate::ClimatePreset>(msg.preset));
734 if (msg.has_custom_preset)
735 call.set_preset(msg.custom_preset);
736 if (msg.has_swing_mode)
737 call.set_swing_mode(static_cast<climate::ClimateSwingMode>(msg.swing_mode));
738 call.perform();
739}
740#endif
741
742#ifdef USE_NUMBER
743bool APIConnection::send_number_state(number::Number *number) {
744 return this->send_message_smart_(number, &APIConnection::try_send_number_state, NumberStateResponse::MESSAGE_TYPE,
745 NumberStateResponse::ESTIMATED_SIZE);
746}
747
748uint16_t APIConnection::try_send_number_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
749 bool is_single) {
750 auto *number = static_cast<number::Number *>(entity);
752 resp.state = number->state;
753 resp.missing_state = !number->has_state();
754 fill_entity_state_base(number, resp);
755 return encode_message_to_buffer(resp, NumberStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
756}
757
758uint16_t APIConnection::try_send_number_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
759 bool is_single) {
760 auto *number = static_cast<number::Number *>(entity);
762 msg.unit_of_measurement = number->traits.get_unit_of_measurement();
763 msg.mode = static_cast<enums::NumberMode>(number->traits.get_mode());
764 msg.device_class = number->traits.get_device_class();
765 msg.min_value = number->traits.get_min_value();
766 msg.max_value = number->traits.get_max_value();
767 msg.step = number->traits.get_step();
768 msg.unique_id = get_default_unique_id("number", number);
769 fill_entity_info_base(number, msg);
770 return encode_message_to_buffer(msg, ListEntitiesNumberResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
771}
772void APIConnection::number_command(const NumberCommandRequest &msg) {
773 ENTITY_COMMAND_MAKE_CALL(number::Number, number, number)
774 call.set_value(msg.state);
775 call.perform();
776}
777#endif
778
779#ifdef USE_DATETIME_DATE
780bool APIConnection::send_date_state(datetime::DateEntity *date) {
781 return this->send_message_smart_(date, &APIConnection::try_send_date_state, DateStateResponse::MESSAGE_TYPE,
782 DateStateResponse::ESTIMATED_SIZE);
783}
784uint16_t APIConnection::try_send_date_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
785 bool is_single) {
786 auto *date = static_cast<datetime::DateEntity *>(entity);
788 resp.missing_state = !date->has_state();
789 resp.year = date->year;
790 resp.month = date->month;
791 resp.day = date->day;
792 fill_entity_state_base(date, resp);
793 return encode_message_to_buffer(resp, DateStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
794}
795uint16_t APIConnection::try_send_date_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
796 bool is_single) {
797 auto *date = static_cast<datetime::DateEntity *>(entity);
799 msg.unique_id = get_default_unique_id("date", date);
800 fill_entity_info_base(date, msg);
801 return encode_message_to_buffer(msg, ListEntitiesDateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
802}
803void APIConnection::date_command(const DateCommandRequest &msg) {
804 ENTITY_COMMAND_MAKE_CALL(datetime::DateEntity, date, date)
805 call.set_date(msg.year, msg.month, msg.day);
806 call.perform();
807}
808#endif
809
810#ifdef USE_DATETIME_TIME
811bool APIConnection::send_time_state(datetime::TimeEntity *time) {
812 return this->send_message_smart_(time, &APIConnection::try_send_time_state, TimeStateResponse::MESSAGE_TYPE,
813 TimeStateResponse::ESTIMATED_SIZE);
814}
815uint16_t APIConnection::try_send_time_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
816 bool is_single) {
817 auto *time = static_cast<datetime::TimeEntity *>(entity);
819 resp.missing_state = !time->has_state();
820 resp.hour = time->hour;
821 resp.minute = time->minute;
822 resp.second = time->second;
823 fill_entity_state_base(time, resp);
824 return encode_message_to_buffer(resp, TimeStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
825}
826uint16_t APIConnection::try_send_time_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
827 bool is_single) {
828 auto *time = static_cast<datetime::TimeEntity *>(entity);
830 msg.unique_id = get_default_unique_id("time", time);
831 fill_entity_info_base(time, msg);
832 return encode_message_to_buffer(msg, ListEntitiesTimeResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
833}
834void APIConnection::time_command(const TimeCommandRequest &msg) {
835 ENTITY_COMMAND_MAKE_CALL(datetime::TimeEntity, time, time)
836 call.set_time(msg.hour, msg.minute, msg.second);
837 call.perform();
838}
839#endif
840
841#ifdef USE_DATETIME_DATETIME
842bool APIConnection::send_datetime_state(datetime::DateTimeEntity *datetime) {
843 return this->send_message_smart_(datetime, &APIConnection::try_send_datetime_state,
844 DateTimeStateResponse::MESSAGE_TYPE, DateTimeStateResponse::ESTIMATED_SIZE);
845}
846uint16_t APIConnection::try_send_datetime_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
847 bool is_single) {
848 auto *datetime = static_cast<datetime::DateTimeEntity *>(entity);
850 resp.missing_state = !datetime->has_state();
851 if (datetime->has_state()) {
852 ESPTime state = datetime->state_as_esptime();
853 resp.epoch_seconds = state.timestamp;
854 }
855 fill_entity_state_base(datetime, resp);
856 return encode_message_to_buffer(resp, DateTimeStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
857}
858uint16_t APIConnection::try_send_datetime_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
859 bool is_single) {
860 auto *datetime = static_cast<datetime::DateTimeEntity *>(entity);
862 msg.unique_id = get_default_unique_id("datetime", datetime);
863 fill_entity_info_base(datetime, msg);
864 return encode_message_to_buffer(msg, ListEntitiesDateTimeResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
865}
866void APIConnection::datetime_command(const DateTimeCommandRequest &msg) {
867 ENTITY_COMMAND_MAKE_CALL(datetime::DateTimeEntity, datetime, datetime)
868 call.set_datetime(msg.epoch_seconds);
869 call.perform();
870}
871#endif
872
873#ifdef USE_TEXT
874bool APIConnection::send_text_state(text::Text *text) {
875 return this->send_message_smart_(text, &APIConnection::try_send_text_state, TextStateResponse::MESSAGE_TYPE,
876 TextStateResponse::ESTIMATED_SIZE);
877}
878
879uint16_t APIConnection::try_send_text_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
880 bool is_single) {
881 auto *text = static_cast<text::Text *>(entity);
883 resp.state = text->state;
884 resp.missing_state = !text->has_state();
885 fill_entity_state_base(text, resp);
886 return encode_message_to_buffer(resp, TextStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
887}
888
889uint16_t APIConnection::try_send_text_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
890 bool is_single) {
891 auto *text = static_cast<text::Text *>(entity);
893 msg.mode = static_cast<enums::TextMode>(text->traits.get_mode());
894 msg.min_length = text->traits.get_min_length();
895 msg.max_length = text->traits.get_max_length();
896 msg.pattern = text->traits.get_pattern();
897 msg.unique_id = get_default_unique_id("text", text);
898 fill_entity_info_base(text, msg);
899 return encode_message_to_buffer(msg, ListEntitiesTextResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
900}
901void APIConnection::text_command(const TextCommandRequest &msg) {
902 ENTITY_COMMAND_MAKE_CALL(text::Text, text, text)
903 call.set_value(msg.state);
904 call.perform();
905}
906#endif
907
908#ifdef USE_SELECT
909bool APIConnection::send_select_state(select::Select *select) {
910 return this->send_message_smart_(select, &APIConnection::try_send_select_state, SelectStateResponse::MESSAGE_TYPE,
911 SelectStateResponse::ESTIMATED_SIZE);
912}
913
914uint16_t APIConnection::try_send_select_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
915 bool is_single) {
916 auto *select = static_cast<select::Select *>(entity);
918 resp.state = select->state;
919 resp.missing_state = !select->has_state();
920 fill_entity_state_base(select, resp);
921 return encode_message_to_buffer(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 for (const auto &option : select->traits.get_options())
929 msg.options.push_back(option);
930 msg.unique_id = get_default_unique_id("select", select);
931 fill_entity_info_base(select, msg);
932 return encode_message_to_buffer(msg, ListEntitiesSelectResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
933}
934void APIConnection::select_command(const SelectCommandRequest &msg) {
935 ENTITY_COMMAND_MAKE_CALL(select::Select, select, select)
936 call.set_option(msg.state);
937 call.perform();
938}
939#endif
940
941#ifdef USE_BUTTON
942uint16_t APIConnection::try_send_button_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
943 bool is_single) {
944 auto *button = static_cast<button::Button *>(entity);
946 msg.device_class = button->get_device_class();
947 msg.unique_id = get_default_unique_id("button", button);
948 fill_entity_info_base(button, msg);
949 return encode_message_to_buffer(msg, ListEntitiesButtonResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
950}
952 ENTITY_COMMAND_GET(button::Button, button, button)
953 button->press();
954}
955#endif
956
957#ifdef USE_LOCK
958bool APIConnection::send_lock_state(lock::Lock *a_lock) {
959 return this->send_message_smart_(a_lock, &APIConnection::try_send_lock_state, LockStateResponse::MESSAGE_TYPE,
960 LockStateResponse::ESTIMATED_SIZE);
961}
962
963uint16_t APIConnection::try_send_lock_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
964 bool is_single) {
965 auto *a_lock = static_cast<lock::Lock *>(entity);
967 resp.state = static_cast<enums::LockState>(a_lock->state);
968 fill_entity_state_base(a_lock, resp);
969 return encode_message_to_buffer(resp, LockStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
970}
971
972uint16_t APIConnection::try_send_lock_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
973 bool is_single) {
974 auto *a_lock = static_cast<lock::Lock *>(entity);
976 msg.assumed_state = a_lock->traits.get_assumed_state();
977 msg.supports_open = a_lock->traits.get_supports_open();
978 msg.requires_code = a_lock->traits.get_requires_code();
979 msg.unique_id = get_default_unique_id("lock", a_lock);
980 fill_entity_info_base(a_lock, msg);
981 return encode_message_to_buffer(msg, ListEntitiesLockResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
982}
983void APIConnection::lock_command(const LockCommandRequest &msg) {
984 ENTITY_COMMAND_GET(lock::Lock, a_lock, lock)
985
986 switch (msg.command) {
987 case enums::LOCK_UNLOCK:
988 a_lock->unlock();
989 break;
990 case enums::LOCK_LOCK:
991 a_lock->lock();
992 break;
993 case enums::LOCK_OPEN:
994 a_lock->open();
995 break;
996 }
997}
998#endif
999
1000#ifdef USE_VALVE
1001bool APIConnection::send_valve_state(valve::Valve *valve) {
1002 return this->send_message_smart_(valve, &APIConnection::try_send_valve_state, ValveStateResponse::MESSAGE_TYPE,
1003 ValveStateResponse::ESTIMATED_SIZE);
1004}
1005uint16_t APIConnection::try_send_valve_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
1006 bool is_single) {
1007 auto *valve = static_cast<valve::Valve *>(entity);
1008 ValveStateResponse resp;
1009 resp.position = valve->position;
1010 resp.current_operation = static_cast<enums::ValveOperation>(valve->current_operation);
1011 fill_entity_state_base(valve, resp);
1012 return encode_message_to_buffer(resp, ValveStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
1013}
1014uint16_t APIConnection::try_send_valve_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
1015 bool is_single) {
1016 auto *valve = static_cast<valve::Valve *>(entity);
1018 auto traits = valve->get_traits();
1019 msg.device_class = valve->get_device_class();
1020 msg.assumed_state = traits.get_is_assumed_state();
1021 msg.supports_position = traits.get_supports_position();
1022 msg.supports_stop = traits.get_supports_stop();
1023 msg.unique_id = get_default_unique_id("valve", valve);
1024 fill_entity_info_base(valve, msg);
1025 return encode_message_to_buffer(msg, ListEntitiesValveResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
1026}
1027void APIConnection::valve_command(const ValveCommandRequest &msg) {
1028 ENTITY_COMMAND_MAKE_CALL(valve::Valve, valve, valve)
1029 if (msg.has_position)
1030 call.set_position(msg.position);
1031 if (msg.stop)
1032 call.set_command_stop();
1033 call.perform();
1034}
1035#endif
1036
1037#ifdef USE_MEDIA_PLAYER
1038bool APIConnection::send_media_player_state(media_player::MediaPlayer *media_player) {
1039 return this->send_message_smart_(media_player, &APIConnection::try_send_media_player_state,
1040 MediaPlayerStateResponse::MESSAGE_TYPE, MediaPlayerStateResponse::ESTIMATED_SIZE);
1041}
1042uint16_t APIConnection::try_send_media_player_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
1043 bool is_single) {
1044 auto *media_player = static_cast<media_player::MediaPlayer *>(entity);
1048 : media_player->state;
1049 resp.state = static_cast<enums::MediaPlayerState>(report_state);
1050 resp.volume = media_player->volume;
1051 resp.muted = media_player->is_muted();
1052 fill_entity_state_base(media_player, resp);
1053 return encode_message_to_buffer(resp, MediaPlayerStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
1054}
1055uint16_t APIConnection::try_send_media_player_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
1056 bool is_single) {
1057 auto *media_player = static_cast<media_player::MediaPlayer *>(entity);
1059 auto traits = media_player->get_traits();
1060 msg.supports_pause = traits.get_supports_pause();
1061 for (auto &supported_format : traits.get_supported_formats()) {
1062 MediaPlayerSupportedFormat media_format;
1063 media_format.format = supported_format.format;
1064 media_format.sample_rate = supported_format.sample_rate;
1065 media_format.num_channels = supported_format.num_channels;
1066 media_format.purpose = static_cast<enums::MediaPlayerFormatPurpose>(supported_format.purpose);
1067 media_format.sample_bytes = supported_format.sample_bytes;
1068 msg.supported_formats.push_back(media_format);
1069 }
1070 msg.unique_id = get_default_unique_id("media_player", media_player);
1071 fill_entity_info_base(media_player, msg);
1072 return encode_message_to_buffer(msg, ListEntitiesMediaPlayerResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
1073}
1074void APIConnection::media_player_command(const MediaPlayerCommandRequest &msg) {
1075 ENTITY_COMMAND_MAKE_CALL(media_player::MediaPlayer, media_player, media_player)
1076 if (msg.has_command) {
1077 call.set_command(static_cast<media_player::MediaPlayerCommand>(msg.command));
1078 }
1079 if (msg.has_volume) {
1080 call.set_volume(msg.volume);
1081 }
1082 if (msg.has_media_url) {
1083 call.set_media_url(msg.media_url);
1084 }
1085 if (msg.has_announcement) {
1086 call.set_announcement(msg.announcement);
1087 }
1088 call.perform();
1089}
1090#endif
1091
1092#ifdef USE_CAMERA
1093void APIConnection::set_camera_state(std::shared_ptr<camera::CameraImage> image) {
1094 if (!this->flags_.state_subscription)
1095 return;
1096 if (!this->image_reader_)
1097 return;
1098 if (this->image_reader_->available())
1099 return;
1100 if (image->was_requested_by(esphome::camera::API_REQUESTER) || image->was_requested_by(esphome::camera::IDLE))
1101 this->image_reader_->set_image(std::move(image));
1102}
1103uint16_t APIConnection::try_send_camera_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
1104 bool is_single) {
1105 auto *camera = static_cast<camera::Camera *>(entity);
1107 msg.unique_id = get_default_unique_id("camera", camera);
1108 fill_entity_info_base(camera, msg);
1109 return encode_message_to_buffer(msg, ListEntitiesCameraResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
1110}
1111void APIConnection::camera_image(const CameraImageRequest &msg) {
1112 if (camera::Camera::instance() == nullptr)
1113 return;
1114
1115 if (msg.single)
1117 if (msg.stream) {
1119
1120 App.scheduler.set_timeout(this->parent_, "api_camera_stop_stream", CAMERA_STOP_STREAM,
1122 }
1123}
1124#endif
1125
1126#ifdef USE_HOMEASSISTANT_TIME
1127void APIConnection::on_get_time_response(const GetTimeResponse &value) {
1130}
1131#endif
1132
1133#ifdef USE_BLUETOOTH_PROXY
1134void APIConnection::subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) {
1136}
1137void APIConnection::unsubscribe_bluetooth_le_advertisements(const UnsubscribeBluetoothLEAdvertisementsRequest &msg) {
1139}
1140bool APIConnection::send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &msg) {
1141 if (this->client_api_version_major_ < 1 || this->client_api_version_minor_ < 7) {
1143 for (auto &service : resp.service_data) {
1144 service.legacy_data.assign(service.data.begin(), service.data.end());
1145 service.data.clear();
1146 }
1147 for (auto &manufacturer_data : resp.manufacturer_data) {
1148 manufacturer_data.legacy_data.assign(manufacturer_data.data.begin(), manufacturer_data.data.end());
1149 manufacturer_data.data.clear();
1150 }
1151 return this->send_message(resp);
1152 }
1153 return this->send_message(msg);
1154}
1155void APIConnection::bluetooth_device_request(const BluetoothDeviceRequest &msg) {
1157}
1158void APIConnection::bluetooth_gatt_read(const BluetoothGATTReadRequest &msg) {
1160}
1161void APIConnection::bluetooth_gatt_write(const BluetoothGATTWriteRequest &msg) {
1163}
1164void APIConnection::bluetooth_gatt_read_descriptor(const BluetoothGATTReadDescriptorRequest &msg) {
1166}
1167void APIConnection::bluetooth_gatt_write_descriptor(const BluetoothGATTWriteDescriptorRequest &msg) {
1169}
1170void APIConnection::bluetooth_gatt_get_services(const BluetoothGATTGetServicesRequest &msg) {
1172}
1173
1174void APIConnection::bluetooth_gatt_notify(const BluetoothGATTNotifyRequest &msg) {
1176}
1177
1185
1186void APIConnection::bluetooth_scanner_set_mode(const BluetoothScannerSetModeRequest &msg) {
1188 msg.mode == enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_ACTIVE);
1189}
1190#endif
1191
1192#ifdef USE_VOICE_ASSISTANT
1193bool APIConnection::check_voice_assistant_api_connection_() const {
1194 return voice_assistant::global_voice_assistant != nullptr &&
1196}
1197
1198void APIConnection::subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) {
1201 }
1202}
1203void APIConnection::on_voice_assistant_response(const VoiceAssistantResponse &msg) {
1204 if (!this->check_voice_assistant_api_connection_()) {
1205 return;
1206 }
1207
1208 if (msg.error) {
1210 return;
1211 }
1212 if (msg.port == 0) {
1213 // Use API Audio
1215 } else {
1216 struct sockaddr_storage storage;
1217 socklen_t len = sizeof(storage);
1218 this->helper_->getpeername((struct sockaddr *) &storage, &len);
1220 }
1221};
1222void APIConnection::on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg) {
1223 if (this->check_voice_assistant_api_connection_()) {
1225 }
1226}
1227void APIConnection::on_voice_assistant_audio(const VoiceAssistantAudio &msg) {
1228 if (this->check_voice_assistant_api_connection_()) {
1230 }
1231};
1232void APIConnection::on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &msg) {
1233 if (this->check_voice_assistant_api_connection_()) {
1235 }
1236};
1237
1238void APIConnection::on_voice_assistant_announce_request(const VoiceAssistantAnnounceRequest &msg) {
1239 if (this->check_voice_assistant_api_connection_()) {
1241 }
1242}
1243
1244VoiceAssistantConfigurationResponse APIConnection::voice_assistant_get_configuration(
1247 if (!this->check_voice_assistant_api_connection_()) {
1248 return resp;
1249 }
1250
1252 for (auto &wake_word : config.available_wake_words) {
1253 VoiceAssistantWakeWord resp_wake_word;
1254 resp_wake_word.id = wake_word.id;
1255 resp_wake_word.wake_word = wake_word.wake_word;
1256 for (const auto &lang : wake_word.trained_languages) {
1257 resp_wake_word.trained_languages.push_back(lang);
1258 }
1259 resp.available_wake_words.push_back(std::move(resp_wake_word));
1260 }
1261 for (auto &wake_word_id : config.active_wake_words) {
1262 resp.active_wake_words.push_back(wake_word_id);
1263 }
1264 resp.max_active_wake_words = config.max_active_wake_words;
1265 return resp;
1266}
1267
1268void APIConnection::voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) {
1269 if (this->check_voice_assistant_api_connection_()) {
1271 }
1272}
1273
1274#endif
1275
1276#ifdef USE_ALARM_CONTROL_PANEL
1277bool APIConnection::send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) {
1278 return this->send_message_smart_(a_alarm_control_panel, &APIConnection::try_send_alarm_control_panel_state,
1279 AlarmControlPanelStateResponse::MESSAGE_TYPE,
1280 AlarmControlPanelStateResponse::ESTIMATED_SIZE);
1281}
1282uint16_t APIConnection::try_send_alarm_control_panel_state(EntityBase *entity, APIConnection *conn,
1283 uint32_t remaining_size, bool is_single) {
1284 auto *a_alarm_control_panel = static_cast<alarm_control_panel::AlarmControlPanel *>(entity);
1286 resp.state = static_cast<enums::AlarmControlPanelState>(a_alarm_control_panel->get_state());
1287 fill_entity_state_base(a_alarm_control_panel, resp);
1288 return encode_message_to_buffer(resp, AlarmControlPanelStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
1289}
1290uint16_t APIConnection::try_send_alarm_control_panel_info(EntityBase *entity, APIConnection *conn,
1291 uint32_t remaining_size, bool is_single) {
1292 auto *a_alarm_control_panel = static_cast<alarm_control_panel::AlarmControlPanel *>(entity);
1294 msg.supported_features = a_alarm_control_panel->get_supported_features();
1295 msg.requires_code = a_alarm_control_panel->get_requires_code();
1296 msg.requires_code_to_arm = a_alarm_control_panel->get_requires_code_to_arm();
1297 msg.unique_id = get_default_unique_id("alarm_control_panel", a_alarm_control_panel);
1298 fill_entity_info_base(a_alarm_control_panel, msg);
1299 return encode_message_to_buffer(msg, ListEntitiesAlarmControlPanelResponse::MESSAGE_TYPE, conn, remaining_size,
1300 is_single);
1301}
1302void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) {
1303 ENTITY_COMMAND_MAKE_CALL(alarm_control_panel::AlarmControlPanel, a_alarm_control_panel, alarm_control_panel)
1304 switch (msg.command) {
1305 case enums::ALARM_CONTROL_PANEL_DISARM:
1306 call.disarm();
1307 break;
1308 case enums::ALARM_CONTROL_PANEL_ARM_AWAY:
1309 call.arm_away();
1310 break;
1311 case enums::ALARM_CONTROL_PANEL_ARM_HOME:
1312 call.arm_home();
1313 break;
1314 case enums::ALARM_CONTROL_PANEL_ARM_NIGHT:
1315 call.arm_night();
1316 break;
1317 case enums::ALARM_CONTROL_PANEL_ARM_VACATION:
1318 call.arm_vacation();
1319 break;
1320 case enums::ALARM_CONTROL_PANEL_ARM_CUSTOM_BYPASS:
1321 call.arm_custom_bypass();
1322 break;
1323 case enums::ALARM_CONTROL_PANEL_TRIGGER:
1324 call.pending();
1325 break;
1326 }
1327 call.set_code(msg.code);
1328 call.perform();
1329}
1330#endif
1331
1332#ifdef USE_EVENT
1333void APIConnection::send_event(event::Event *event, const std::string &event_type) {
1334 this->schedule_message_(event, MessageCreator(event_type), EventResponse::MESSAGE_TYPE,
1335 EventResponse::ESTIMATED_SIZE);
1336}
1337uint16_t APIConnection::try_send_event_response(event::Event *event, const std::string &event_type, APIConnection *conn,
1338 uint32_t remaining_size, bool is_single) {
1339 EventResponse resp;
1340 resp.event_type = event_type;
1341 fill_entity_state_base(event, resp);
1342 return encode_message_to_buffer(resp, EventResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
1343}
1344
1345uint16_t APIConnection::try_send_event_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
1346 bool is_single) {
1347 auto *event = static_cast<event::Event *>(entity);
1349 msg.device_class = event->get_device_class();
1350 for (const auto &event_type : event->get_event_types())
1351 msg.event_types.push_back(event_type);
1352 msg.unique_id = get_default_unique_id("event", event);
1353 fill_entity_info_base(event, msg);
1354 return encode_message_to_buffer(msg, ListEntitiesEventResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
1355}
1356#endif
1357
1358#ifdef USE_UPDATE
1359bool APIConnection::send_update_state(update::UpdateEntity *update) {
1360 return this->send_message_smart_(update, &APIConnection::try_send_update_state, UpdateStateResponse::MESSAGE_TYPE,
1361 UpdateStateResponse::ESTIMATED_SIZE);
1362}
1363uint16_t APIConnection::try_send_update_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
1364 bool is_single) {
1365 auto *update = static_cast<update::UpdateEntity *>(entity);
1367 resp.missing_state = !update->has_state();
1368 if (update->has_state()) {
1370 if (update->update_info.has_progress) {
1371 resp.has_progress = true;
1372 resp.progress = update->update_info.progress;
1373 }
1374 resp.current_version = update->update_info.current_version;
1375 resp.latest_version = update->update_info.latest_version;
1376 resp.title = update->update_info.title;
1377 resp.release_summary = update->update_info.summary;
1378 resp.release_url = update->update_info.release_url;
1379 }
1380 fill_entity_state_base(update, resp);
1381 return encode_message_to_buffer(resp, UpdateStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
1382}
1383uint16_t APIConnection::try_send_update_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
1384 bool is_single) {
1385 auto *update = static_cast<update::UpdateEntity *>(entity);
1387 msg.device_class = update->get_device_class();
1388 msg.unique_id = get_default_unique_id("update", update);
1389 fill_entity_info_base(update, msg);
1390 return encode_message_to_buffer(msg, ListEntitiesUpdateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
1391}
1392void APIConnection::update_command(const UpdateCommandRequest &msg) {
1393 ENTITY_COMMAND_GET(update::UpdateEntity, update, update)
1394
1395 switch (msg.command) {
1396 case enums::UPDATE_COMMAND_UPDATE:
1397 update->perform();
1398 break;
1399 case enums::UPDATE_COMMAND_CHECK:
1400 update->check();
1401 break;
1402 case enums::UPDATE_COMMAND_NONE:
1403 ESP_LOGE(TAG, "UPDATE_COMMAND_NONE not handled; confirm command is correct");
1404 break;
1405 default:
1406 ESP_LOGW(TAG, "Unknown update command: %" PRIu32, msg.command);
1407 break;
1408 }
1409}
1410#endif
1411
1412bool APIConnection::try_send_log_message(int level, const char *tag, const char *line, size_t message_len) {
1413 if (this->flags_.log_subscription < level)
1414 return false;
1415
1416 // Pre-calculate message size to avoid reallocations
1417 uint32_t msg_size = 0;
1418
1419 // Add size for level field (field ID 1, varint type)
1420 // 1 byte for field tag + size of the level varint
1421 msg_size += 1 + api::ProtoSize::varint(static_cast<uint32_t>(level));
1422
1423 // Add size for string field (field ID 3, string type)
1424 // 1 byte for field tag + size of length varint + string length
1425 msg_size += 1 + api::ProtoSize::varint(static_cast<uint32_t>(message_len)) + message_len;
1426
1427 // Create a pre-sized buffer
1428 auto buffer = this->create_buffer(msg_size);
1429
1430 // Encode the message (SubscribeLogsResponse)
1431 buffer.encode_uint32(1, static_cast<uint32_t>(level)); // LogLevel level = 1
1432 buffer.encode_string(3, line, message_len); // string message = 3
1433
1434 // SubscribeLogsResponse - 29
1435 return this->send_buffer(buffer, SubscribeLogsResponse::MESSAGE_TYPE);
1436}
1437
1438HelloResponse APIConnection::hello(const HelloRequest &msg) {
1439 this->client_info_ = msg.client_info;
1440 this->client_peername_ = this->helper_->getpeername();
1441 this->helper_->set_log_info(this->get_client_combined_info());
1442 this->client_api_version_major_ = msg.api_version_major;
1443 this->client_api_version_minor_ = msg.api_version_minor;
1444 ESP_LOGV(TAG, "Hello from client: '%s' | %s | API Version %" PRIu32 ".%" PRIu32, this->client_info_.c_str(),
1445 this->client_peername_.c_str(), this->client_api_version_major_, this->client_api_version_minor_);
1446
1447 HelloResponse resp;
1448 resp.api_version_major = 1;
1449 resp.api_version_minor = 10;
1450 resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")";
1451 resp.name = App.get_name();
1452
1453 this->flags_.connection_state = static_cast<uint8_t>(ConnectionState::CONNECTED);
1454 return resp;
1455}
1456ConnectResponse APIConnection::connect(const ConnectRequest &msg) {
1457 bool correct = true;
1458#ifdef USE_API_PASSWORD
1459 correct = this->parent_->check_password(msg.password);
1460#endif
1461
1462 ConnectResponse resp;
1463 // bool invalid_password = 1;
1464 resp.invalid_password = !correct;
1465 if (correct) {
1466 ESP_LOGD(TAG, "%s connected", this->get_client_combined_info().c_str());
1467 this->flags_.connection_state = static_cast<uint8_t>(ConnectionState::AUTHENTICATED);
1468#ifdef USE_API_CLIENT_CONNECTED_TRIGGER
1469 this->parent_->get_client_connected_trigger()->trigger(this->client_info_, this->client_peername_);
1470#endif
1471#ifdef USE_HOMEASSISTANT_TIME
1473 this->send_time_request();
1474 }
1475#endif
1476 }
1477 return resp;
1478}
1479DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
1480 DeviceInfoResponse resp{};
1481#ifdef USE_API_PASSWORD
1482 resp.uses_password = this->parent_->uses_password();
1483#else
1484 resp.uses_password = false;
1485#endif
1486 resp.name = App.get_name();
1487 resp.friendly_name = App.get_friendly_name();
1488 resp.suggested_area = App.get_area();
1489 resp.mac_address = get_mac_address_pretty();
1490 resp.esphome_version = ESPHOME_VERSION;
1491 resp.compilation_time = App.get_compilation_time();
1492#if defined(USE_ESP8266) || defined(USE_ESP32)
1493 resp.manufacturer = "Espressif";
1494#elif defined(USE_RP2040)
1495 resp.manufacturer = "Raspberry Pi";
1496#elif defined(USE_BK72XX)
1497 resp.manufacturer = "Beken";
1498#elif defined(USE_LN882X)
1499 resp.manufacturer = "Lightning";
1500#elif defined(USE_RTL87XX)
1501 resp.manufacturer = "Realtek";
1502#elif defined(USE_HOST)
1503 resp.manufacturer = "Host";
1504#endif
1505 resp.model = ESPHOME_BOARD;
1506#ifdef USE_DEEP_SLEEP
1507 resp.has_deep_sleep = deep_sleep::global_has_deep_sleep;
1508#endif
1509#ifdef ESPHOME_PROJECT_NAME
1510 resp.project_name = ESPHOME_PROJECT_NAME;
1511 resp.project_version = ESPHOME_PROJECT_VERSION;
1512#endif
1513#ifdef USE_WEBSERVER
1514 resp.webserver_port = USE_WEBSERVER_PORT;
1515#endif
1516#ifdef USE_BLUETOOTH_PROXY
1517 resp.legacy_bluetooth_proxy_version = bluetooth_proxy::global_bluetooth_proxy->get_legacy_version();
1518 resp.bluetooth_proxy_feature_flags = bluetooth_proxy::global_bluetooth_proxy->get_feature_flags();
1520#endif
1521#ifdef USE_VOICE_ASSISTANT
1522 resp.legacy_voice_assistant_version = voice_assistant::global_voice_assistant->get_legacy_version();
1523 resp.voice_assistant_feature_flags = voice_assistant::global_voice_assistant->get_feature_flags();
1524#endif
1525#ifdef USE_API_NOISE
1526 resp.api_encryption_supported = true;
1527#endif
1528#ifdef USE_DEVICES
1529 for (auto const &device : App.get_devices()) {
1530 DeviceInfo device_info;
1531 device_info.device_id = device->get_device_id();
1532 device_info.name = device->get_name();
1533 device_info.area_id = device->get_area_id();
1534 resp.devices.push_back(device_info);
1535 }
1536#endif
1537#ifdef USE_AREAS
1538 for (auto const &area : App.get_areas()) {
1539 AreaInfo area_info;
1540 area_info.area_id = area->get_area_id();
1541 area_info.name = area->get_name();
1542 resp.areas.push_back(area_info);
1543 }
1544#endif
1545 return resp;
1546}
1547void APIConnection::on_home_assistant_state_response(const HomeAssistantStateResponse &msg) {
1548 for (auto &it : this->parent_->get_state_subs()) {
1549 if (it.entity_id == msg.entity_id && it.attribute.value() == msg.attribute) {
1550 it.callback(msg.state);
1551 }
1552 }
1553}
1554#ifdef USE_API_SERVICES
1555void APIConnection::execute_service(const ExecuteServiceRequest &msg) {
1556 bool found = false;
1557 for (auto *service : this->parent_->get_user_services()) {
1558 if (service->execute_service(msg)) {
1559 found = true;
1560 }
1561 }
1562 if (!found) {
1563 ESP_LOGV(TAG, "Could not find service");
1564 }
1565}
1566#endif
1567#ifdef USE_API_NOISE
1568NoiseEncryptionSetKeyResponse APIConnection::noise_encryption_set_key(const NoiseEncryptionSetKeyRequest &msg) {
1569 psk_t psk{};
1571 if (base64_decode(msg.key, psk.data(), msg.key.size()) != psk.size()) {
1572 ESP_LOGW(TAG, "Invalid encryption key length");
1573 resp.success = false;
1574 return resp;
1575 }
1576
1577 if (!this->parent_->save_noise_psk(psk, true)) {
1578 ESP_LOGW(TAG, "Failed to save encryption key");
1579 resp.success = false;
1580 return resp;
1581 }
1582
1583 resp.success = true;
1584 return resp;
1585}
1586#endif
1587void APIConnection::subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) {
1588 state_subs_at_ = 0;
1589}
1590bool APIConnection::try_to_clear_buffer(bool log_out_of_space) {
1591 if (this->flags_.remove)
1592 return false;
1593 if (this->helper_->can_write_without_blocking())
1594 return true;
1595 delay(0);
1596 APIError err = this->helper_->loop();
1597 if (err != APIError::OK) {
1598 on_fatal_error();
1599 ESP_LOGW(TAG, "%s: Socket operation failed: %s errno=%d", this->get_client_combined_info().c_str(),
1600 api_error_to_str(err), errno);
1601 return false;
1602 }
1603 if (this->helper_->can_write_without_blocking())
1604 return true;
1605 if (log_out_of_space) {
1606 ESP_LOGV(TAG, "Cannot send message because of TCP buffer space");
1607 }
1608 return false;
1609}
1610bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) {
1611 if (!this->try_to_clear_buffer(message_type != SubscribeLogsResponse::MESSAGE_TYPE)) { // SubscribeLogsResponse
1612 return false;
1613 }
1614
1615 APIError err = this->helper_->write_protobuf_packet(message_type, buffer);
1616 if (err == APIError::WOULD_BLOCK)
1617 return false;
1618 if (err != APIError::OK) {
1619 on_fatal_error();
1620 if (err == APIError::SOCKET_WRITE_FAILED && errno == ECONNRESET) {
1621 ESP_LOGW(TAG, "%s: Connection reset", this->get_client_combined_info().c_str());
1622 } else {
1623 ESP_LOGW(TAG, "%s: Packet write failed %s errno=%d", this->get_client_combined_info().c_str(),
1624 api_error_to_str(err), errno);
1625 }
1626 return false;
1627 }
1628 // Do not set last_traffic_ on send
1629 return true;
1630}
1631void APIConnection::on_unauthenticated_access() {
1632 this->on_fatal_error();
1633 ESP_LOGD(TAG, "%s requested access without authentication", this->get_client_combined_info().c_str());
1634}
1635void APIConnection::on_no_setup_connection() {
1636 this->on_fatal_error();
1637 ESP_LOGD(TAG, "%s requested access without full connection", this->get_client_combined_info().c_str());
1638}
1639void APIConnection::on_fatal_error() {
1640 this->helper_->close();
1641 this->flags_.remove = true;
1642}
1643
1644void APIConnection::DeferredBatch::add_item(EntityBase *entity, MessageCreator creator, uint8_t message_type,
1645 uint8_t estimated_size) {
1646 // Check if we already have a message of this type for this entity
1647 // This provides deduplication per entity/message_type combination
1648 // O(n) but optimized for RAM and not performance.
1649 for (auto &item : items) {
1650 if (item.entity == entity && item.message_type == message_type) {
1651 // Clean up old creator before replacing
1652 item.creator.cleanup(message_type);
1653 // Move assign the new creator
1654 item.creator = std::move(creator);
1655 return;
1656 }
1657 }
1658
1659 // No existing item found, add new one
1660 items.emplace_back(entity, std::move(creator), message_type, estimated_size);
1661}
1662
1663void APIConnection::DeferredBatch::add_item_front(EntityBase *entity, MessageCreator creator, uint8_t message_type,
1664 uint8_t estimated_size) {
1665 // Insert at front for high priority messages (no deduplication check)
1666 items.insert(items.begin(), BatchItem(entity, std::move(creator), message_type, estimated_size));
1667}
1668
1669bool APIConnection::schedule_batch_() {
1670 if (!this->flags_.batch_scheduled) {
1671 this->flags_.batch_scheduled = true;
1672 this->deferred_batch_.batch_start_time = App.get_loop_component_start_time();
1673 }
1674 return true;
1675}
1676
1677ProtoWriteBuffer APIConnection::allocate_single_message_buffer(uint16_t size) { return this->create_buffer(size); }
1678
1679ProtoWriteBuffer APIConnection::allocate_batch_message_buffer(uint16_t size) {
1680 ProtoWriteBuffer result = this->prepare_message_buffer(size, this->flags_.batch_first_message);
1681 this->flags_.batch_first_message = false;
1682 return result;
1683}
1684
1685void APIConnection::process_batch_() {
1686 if (this->deferred_batch_.empty()) {
1687 this->flags_.batch_scheduled = false;
1688 return;
1689 }
1690
1691 // Try to clear buffer first
1692 if (!this->try_to_clear_buffer(true)) {
1693 // Can't write now, we'll try again later
1694 return;
1695 }
1696
1697 size_t num_items = this->deferred_batch_.size();
1698
1699 // Fast path for single message - allocate exact size needed
1700 if (num_items == 1) {
1701 const auto &item = this->deferred_batch_[0];
1702
1703 // Let the creator calculate size and encode if it fits
1704 uint16_t payload_size =
1705 item.creator(item.entity, this, std::numeric_limits<uint16_t>::max(), true, item.message_type);
1706
1707 if (payload_size > 0 &&
1708 this->send_buffer(ProtoWriteBuffer{&this->parent_->get_shared_buffer_ref()}, item.message_type)) {
1709#ifdef HAS_PROTO_MESSAGE_DUMP
1710 // Log messages after send attempt for VV debugging
1711 // It's safe to use the buffer for logging at this point regardless of send result
1712 this->log_batch_item_(item);
1713#endif
1714 this->clear_batch_();
1715 } else if (payload_size == 0) {
1716 // Message too large
1717 ESP_LOGW(TAG, "Message too large to send: type=%u", item.message_type);
1718 this->clear_batch_();
1719 }
1720 return;
1721 }
1722
1723 // Pre-allocate storage for packet info
1724 std::vector<PacketInfo> packet_info;
1725 packet_info.reserve(num_items);
1726
1727 // Cache these values to avoid repeated virtual calls
1728 const uint8_t header_padding = this->helper_->frame_header_padding();
1729 const uint8_t footer_size = this->helper_->frame_footer_size();
1730
1731 // Initialize buffer and tracking variables
1732 this->parent_->get_shared_buffer_ref().clear();
1733
1734 // Pre-calculate exact buffer size needed based on message types
1735 uint32_t total_estimated_size = 0;
1736 for (size_t i = 0; i < this->deferred_batch_.size(); i++) {
1737 const auto &item = this->deferred_batch_[i];
1738 total_estimated_size += item.estimated_size;
1739 }
1740
1741 // Calculate total overhead for all messages
1742 uint32_t total_overhead = (header_padding + footer_size) * num_items;
1743
1744 // Reserve based on estimated size (much more accurate than 24-byte worst-case)
1745 this->parent_->get_shared_buffer_ref().reserve(total_estimated_size + total_overhead);
1746 this->flags_.batch_first_message = true;
1747
1748 size_t items_processed = 0;
1749 uint16_t remaining_size = std::numeric_limits<uint16_t>::max();
1750
1751 // Track where each message's header padding begins in the buffer
1752 // For plaintext: this is where the 6-byte header padding starts
1753 // For noise: this is where the 7-byte header padding starts
1754 // The actual message data follows after the header padding
1755 uint32_t current_offset = 0;
1756
1757 // Process items and encode directly to buffer
1758 for (size_t i = 0; i < this->deferred_batch_.size(); i++) {
1759 const auto &item = this->deferred_batch_[i];
1760 // Try to encode message
1761 // The creator will calculate overhead to determine if the message fits
1762 uint16_t payload_size = item.creator(item.entity, this, remaining_size, false, item.message_type);
1763
1764 if (payload_size == 0) {
1765 // Message won't fit, stop processing
1766 break;
1767 }
1768
1769 // Message was encoded successfully
1770 // payload_size is header_padding + actual payload size + footer_size
1771 uint16_t proto_payload_size = payload_size - header_padding - footer_size;
1772 packet_info.emplace_back(item.message_type, current_offset, proto_payload_size);
1773
1774 // Update tracking variables
1775 items_processed++;
1776 // After first message, set remaining size to MAX_BATCH_PACKET_SIZE to avoid fragmentation
1777 if (items_processed == 1) {
1778 remaining_size = MAX_BATCH_PACKET_SIZE;
1779 }
1780 remaining_size -= payload_size;
1781 // Calculate where the next message's header padding will start
1782 // Current buffer size + footer space (that prepare_message_buffer will add for this message)
1783 current_offset = this->parent_->get_shared_buffer_ref().size() + footer_size;
1784 }
1785
1786 if (items_processed == 0) {
1787 this->deferred_batch_.clear();
1788 return;
1789 }
1790
1791 // Add footer space for the last message (for Noise protocol MAC)
1792 if (footer_size > 0) {
1793 auto &shared_buf = this->parent_->get_shared_buffer_ref();
1794 shared_buf.resize(shared_buf.size() + footer_size);
1795 }
1796
1797 // Send all collected packets
1798 APIError err =
1799 this->helper_->write_protobuf_packets(ProtoWriteBuffer{&this->parent_->get_shared_buffer_ref()}, packet_info);
1800 if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
1801 on_fatal_error();
1802 if (err == APIError::SOCKET_WRITE_FAILED && errno == ECONNRESET) {
1803 ESP_LOGW(TAG, "%s: Connection reset during batch write", this->get_client_combined_info().c_str());
1804 } else {
1805 ESP_LOGW(TAG, "%s: Batch write failed %s errno=%d", this->get_client_combined_info().c_str(),
1806 api_error_to_str(err), errno);
1807 }
1808 }
1809
1810#ifdef HAS_PROTO_MESSAGE_DUMP
1811 // Log messages after send attempt for VV debugging
1812 // It's safe to use the buffer for logging at this point regardless of send result
1813 for (size_t i = 0; i < items_processed; i++) {
1814 const auto &item = this->deferred_batch_[i];
1815 this->log_batch_item_(item);
1816 }
1817#endif
1818
1819 // Handle remaining items more efficiently
1820 if (items_processed < this->deferred_batch_.size()) {
1821 // Remove processed items from the beginning with proper cleanup
1822 this->deferred_batch_.remove_front(items_processed);
1823 // Reschedule for remaining items
1824 this->schedule_batch_();
1825 } else {
1826 // All items processed
1827 this->clear_batch_();
1828 }
1829}
1830
1831uint16_t APIConnection::MessageCreator::operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
1832 bool is_single, uint8_t message_type) const {
1833#ifdef USE_EVENT
1834 // Special case: EventResponse uses string pointer
1835 if (message_type == EventResponse::MESSAGE_TYPE) {
1836 auto *e = static_cast<event::Event *>(entity);
1837 return APIConnection::try_send_event_response(e, *data_.string_ptr, conn, remaining_size, is_single);
1838 }
1839#endif
1840
1841 // All other message types use function pointers
1842 return data_.function_ptr(entity, conn, remaining_size, is_single);
1843}
1844
1845uint16_t APIConnection::try_send_list_info_done(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
1846 bool is_single) {
1848 return encode_message_to_buffer(resp, ListEntitiesDoneResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
1849}
1850
1851uint16_t APIConnection::try_send_disconnect_request(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
1852 bool is_single) {
1854 return encode_message_to_buffer(req, DisconnectRequest::MESSAGE_TYPE, conn, remaining_size, is_single);
1855}
1856
1857uint16_t APIConnection::try_send_ping_request(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
1858 bool is_single) {
1859 PingRequest req;
1860 return encode_message_to_buffer(req, PingRequest::MESSAGE_TYPE, conn, remaining_size, is_single);
1861}
1862
1863} // namespace api
1864} // namespace esphome
1865#endif
BedjetMode mode
BedJet operating mode.
const std::string & get_friendly_name() const
Get the friendly name of this Application set by pre_setup().
const std::vector< Area * > & get_areas()
std::string get_compilation_time() const
const char * get_area() const
Get the area of this Application set by pre_setup().
const std::string & get_name() const
Get the name of this Application set by pre_setup().
const std::vector< Device * > & get_devices()
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.
std::string get_object_id() const
struct esphome::api::APIConnection::APIFlags flags_
std::unique_ptr< APIFrameHelper > helper_
ProtoWriteBuffer allocate_batch_message_buffer(uint16_t size)
APIConnection(std::unique_ptr< socket::Socket > socket, APIServer *parent)
ProtoWriteBuffer allocate_single_message_buffer(uint16_t size)
void button_command(const ButtonCommandRequest &msg) override
void log_send_message_(const char *name, const std::string &dump)
std::shared_ptr< APINoiseContext > get_noise_ctx()
Definition api_server.h:56
std::vector< uint8_t > & get_shared_buffer_ref()
Definition api_server.h:51
enums::AlarmControlPanelStateCommand command
Definition api_pb2.h:2494
enums::AlarmControlPanelState state
Definition api_pb2.h:2478
std::vector< BluetoothServiceData > service_data
Definition api_pb2.h:1741
std::vector< BluetoothServiceData > manufacturer_data
Definition api_pb2.h:1742
enums::BluetoothScannerMode mode
Definition api_pb2.h:2209
enums::ClimateSwingMode swing_mode
Definition api_pb2.h:1345
enums::ClimateFanMode fan_mode
Definition api_pb2.h:1343
enums::ClimatePreset preset
Definition api_pb2.h:1349
enums::ClimateFanMode fan_mode
Definition api_pb2.h:1310
enums::ClimateSwingMode swing_mode
Definition api_pb2.h:1311
enums::ClimateAction action
Definition api_pb2.h:1309
enums::ClimatePreset preset
Definition api_pb2.h:1313
enums::LegacyCoverCommand legacy_command
Definition api_pb2.h:642
enums::LegacyCoverState legacy_state
Definition api_pb2.h:622
enums::CoverOperation current_operation
Definition api_pb2.h:625
enums::FanDirection direction
Definition api_pb2.h:713
enums::FanDirection direction
Definition api_pb2.h:688
enums::ColorMode color_mode
Definition api_pb2.h:761
std::vector< enums::ClimatePreset > supported_presets
Definition api_pb2.h:1281
std::vector< enums::ClimateSwingMode > supported_swing_modes
Definition api_pb2.h:1279
std::vector< enums::ClimateFanMode > supported_fan_modes
Definition api_pb2.h:1278
std::vector< enums::ClimateMode > supported_modes
Definition api_pb2.h:1272
std::vector< std::string > supported_custom_presets
Definition api_pb2.h:1282
std::vector< std::string > supported_custom_fan_modes
Definition api_pb2.h:1280
std::vector< std::string > event_types
Definition api_pb2.h:2678
std::vector< std::string > supported_preset_modes
Definition api_pb2.h:669
std::vector< std::string > effects
Definition api_pb2.h:743
std::vector< enums::ColorMode > supported_color_modes
Definition api_pb2.h:736
std::vector< MediaPlayerSupportedFormat > supported_formats
Definition api_pb2.h:1647
std::vector< std::string > options
Definition api_pb2.h:1428
enums::SensorStateClass state_class
Definition api_pb2.h:834
enums::LockCommand command
Definition api_pb2.h:1575
enums::MediaPlayerCommand command
Definition api_pb2.h:1682
enums::MediaPlayerState state
Definition api_pb2.h:1663
enums::MediaPlayerFormatPurpose purpose
Definition api_pb2.h:1627
virtual void encode(ProtoWriteBuffer buffer) const
Definition proto.h:326
virtual const char * message_name() const
Definition proto.h:333
std::string dump() const
Definition proto.cpp:85
virtual void calculate_size(uint32_t &total_size) const
Definition proto.h:329
static uint32_t varint(uint32_t value)
ProtoSize class for Protocol Buffer serialization size calculation.
Definition proto.h:368
enums::UpdateCommand command
Definition api_pb2.h:2858
enums::ValveOperation current_operation
Definition api_pb2.h:2732
std::vector< VoiceAssistantWakeWord > available_wake_words
Definition api_pb2.h:2425
std::vector< std::string > active_wake_words
Definition api_pb2.h:2426
std::vector< std::string > active_wake_words
Definition api_pb2.h:2443
std::vector< std::string > trained_languages
Definition api_pb2.h:2395
Base class for all binary_sensor-type classes.
void bluetooth_gatt_read(const api::BluetoothGATTReadRequest &msg)
void bluetooth_gatt_send_services(const api::BluetoothGATTGetServicesRequest &msg)
void bluetooth_device_request(const api::BluetoothDeviceRequest &msg)
void bluetooth_gatt_write_descriptor(const api::BluetoothGATTWriteDescriptorRequest &msg)
void subscribe_api_connection(api::APIConnection *api_connection, uint32_t flags)
void unsubscribe_api_connection(api::APIConnection *api_connection)
void bluetooth_gatt_read_descriptor(const api::BluetoothGATTReadDescriptorRequest &msg)
void bluetooth_gatt_write(const api::BluetoothGATTWriteRequest &msg)
void bluetooth_gatt_notify(const api::BluetoothGATTNotifyRequest &msg)
Base class for all buttons.
Definition button.h:29
Abstract camera base class.
Definition camera.h:57
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:168
Base class for all cover devices.
Definition cover.h:111
This class represents the communication layer between the front-end MQTT layer and the hardware outpu...
Definition light_state.h:66
Base class for all locks.
Definition lock.h:103
Base-class for all numbers.
Definition number.h:39
Base-class for all selects.
Definition select.h:31
Base-class for all sensors.
Definition sensor.h:62
Base class for all switches.
Definition switch.h:39
Base-class for all text inputs.
Definition text.h:24
Base class for all valve devices.
Definition valve.h:105
void on_timer_event(const api::VoiceAssistantTimerEventResponse &msg)
void on_audio(const api::VoiceAssistantAudio &msg)
void client_subscription(api::APIConnection *client, bool subscribe)
void on_event(const api::VoiceAssistantEventResponse &msg)
void on_announce(const api::VoiceAssistantAnnounceRequest &msg)
api::APIConnection * get_api_connection() const
void on_set_configuration(const std::vector< std::string > &active_wake_words)
ClimateSwingMode swing_mode
Definition climate.h:11
uint8_t custom_preset
Definition climate.h:9
ClimateFanMode fan_mode
Definition climate.h:3
ClimatePreset preset
Definition climate.h:8
uint8_t custom_fan_mode
Definition climate.h:4
bool state
Definition fan.h:0
uint32_t socklen_t
Definition headers.h:97
std::string get_default_unique_id(const std::string &component_type, EntityBase *entity)
std::array< uint8_t, 32 > psk_t
const char * api_error_to_str(APIError err)
BluetoothProxy * global_bluetooth_proxy
ClimatePreset
Enum for all preset modes.
@ CLIMATE_PRESET_AWAY
Device is in away preset.
ClimateSwingMode
Enum for all modes a climate swing can be in.
ClimateMode
Enum for all modes a climate device can be in.
const float COVER_OPEN
Definition cover.cpp:9
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
@ BRIGHTNESS
Master brightness of the light can be controlled.
@ RGB
Color can be controlled using RGB format (includes a brightness control for the color).
@ COLOR_TEMPERATURE
Color temperature can be controlled.
@ WHITE
Brightness of white channel can be controlled separately from other channels.
@ COLD_WARM_WHITE
Brightness of cold and warm white output can be controlled.
VoiceAssistant * global_voice_assistant
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
std::string size_t len
Definition helpers.h:232
std::string get_mac_address_pretty()
Get the device MAC address as a string, in colon-separated uppercase hex notation.
Definition helpers.cpp:591
void IRAM_ATTR HOT delay(uint32_t ms)
Definition core.cpp:29
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:431
A more user-friendly version of struct tm from time.h.
Definition time.h:15
std::vector< uint8_t > container
uint32_t payload_size()