#pragma once #include "esphome/core/defines.h" #ifdef USE_API #include "api_frame_helper.h" #ifdef USE_API_NOISE #include "api_frame_helper_noise.h" #endif #ifdef USE_API_PLAINTEXT #include "api_frame_helper_plaintext.h" #endif #include "api_pb2.h" #include "api_pb2_service.h" #include "api_server.h" #include "esphome/core/application.h" #include "esphome/core/component.h" #ifdef USE_ESP32_CRASH_HANDLER #include "esphome/components/esp32/crash_handler.h" #endif #ifdef USE_RP2040_CRASH_HANDLER #include "esphome/components/rp2040/crash_handler.h" #endif #include "esphome/core/entity_base.h" #include "esphome/core/string_ref.h" #include #include #include namespace esphome { class ComponentIterator; } // namespace esphome namespace esphome::api { // Keepalive timeout in milliseconds static constexpr uint32_t KEEPALIVE_TIMEOUT_MS = 60000; // Maximum number of entities to process in a single batch during initial state/info sending // API 1.14+ clients compute object_id client-side, so messages are smaller and we can fit more per batch // TODO: Remove MAX_INITIAL_PER_BATCH_LEGACY before 2026.7.0 - all clients should support API 1.14 by then static constexpr size_t MAX_INITIAL_PER_BATCH_LEGACY = 24; // For clients < API 1.14 (includes object_id) static constexpr size_t MAX_INITIAL_PER_BATCH = 34; // For clients >= API 1.14 (no object_id) // Verify MAX_MESSAGES_PER_BATCH (defined in api_frame_helper.h) can hold the initial batch static_assert(MAX_MESSAGES_PER_BATCH >= MAX_INITIAL_PER_BATCH, "MAX_MESSAGES_PER_BATCH must be >= MAX_INITIAL_PER_BATCH"); class APIConnection final : public APIServerConnectionBase { public: friend class APIServer; friend class ListEntitiesIterator; APIConnection(std::unique_ptr socket, APIServer *parent); ~APIConnection(); void start(); void loop(); protected: // read_message_ is defined here (instead of in APIServerConnectionBase) so the // compiler can devirtualize and inline on_* handler calls within this final class. void read_message_(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data); // Auth helpers defined here (not in ProtoService) so the compiler can // devirtualize is_connection_setup()/on_no_setup_connection() calls // within this final class. inline bool check_connection_setup_() { if (!this->is_connection_setup()) { this->on_no_setup_connection(); return false; } return true; } inline bool check_authenticated_() { return this->check_connection_setup_(); } public: bool send_list_info_done() { return this->schedule_message_(nullptr, ListEntitiesDoneResponse::MESSAGE_TYPE, ListEntitiesDoneResponse::ESTIMATED_SIZE); } #ifdef USE_BINARY_SENSOR bool send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor); #endif #ifdef USE_COVER bool send_cover_state(cover::Cover *cover); void on_cover_command_request(const CoverCommandRequest &msg); #endif #ifdef USE_FAN bool send_fan_state(fan::Fan *fan); void on_fan_command_request(const FanCommandRequest &msg); #endif #ifdef USE_LIGHT bool send_light_state(light::LightState *light); void on_light_command_request(const LightCommandRequest &msg); #endif #ifdef USE_SENSOR bool send_sensor_state(sensor::Sensor *sensor); #endif #ifdef USE_SWITCH bool send_switch_state(switch_::Switch *a_switch); void on_switch_command_request(const SwitchCommandRequest &msg); #endif #ifdef USE_TEXT_SENSOR bool send_text_sensor_state(text_sensor::TextSensor *text_sensor); #endif #ifdef USE_CAMERA void set_camera_state(std::shared_ptr image); void on_camera_image_request(const CameraImageRequest &msg); #endif #ifdef USE_CLIMATE bool send_climate_state(climate::Climate *climate); void on_climate_command_request(const ClimateCommandRequest &msg); #endif #ifdef USE_NUMBER bool send_number_state(number::Number *number); void on_number_command_request(const NumberCommandRequest &msg); #endif #ifdef USE_DATETIME_DATE bool send_date_state(datetime::DateEntity *date); void on_date_command_request(const DateCommandRequest &msg); #endif #ifdef USE_DATETIME_TIME bool send_time_state(datetime::TimeEntity *time); void on_time_command_request(const TimeCommandRequest &msg); #endif #ifdef USE_DATETIME_DATETIME bool send_datetime_state(datetime::DateTimeEntity *datetime); void on_date_time_command_request(const DateTimeCommandRequest &msg); #endif #ifdef USE_TEXT bool send_text_state(text::Text *text); void on_text_command_request(const TextCommandRequest &msg); #endif #ifdef USE_SELECT bool send_select_state(select::Select *select); void on_select_command_request(const SelectCommandRequest &msg); #endif #ifdef USE_BUTTON void on_button_command_request(const ButtonCommandRequest &msg); #endif #ifdef USE_LOCK bool send_lock_state(lock::Lock *a_lock); void on_lock_command_request(const LockCommandRequest &msg); #endif #ifdef USE_VALVE bool send_valve_state(valve::Valve *valve); void on_valve_command_request(const ValveCommandRequest &msg); #endif #ifdef USE_MEDIA_PLAYER bool send_media_player_state(media_player::MediaPlayer *media_player); void on_media_player_command_request(const MediaPlayerCommandRequest &msg); #endif bool try_send_log_message(int level, const char *tag, const char *line, size_t message_len); #ifdef USE_API_HOMEASSISTANT_SERVICES void send_homeassistant_action(const HomeassistantActionRequest &call) { if (!this->flags_.service_call_subscription) return; this->send_message(call); } #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES void on_homeassistant_action_response(const HomeassistantActionResponse &msg); #endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES #endif // USE_API_HOMEASSISTANT_SERVICES #ifdef USE_BLUETOOTH_PROXY void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg); void on_unsubscribe_bluetooth_le_advertisements_request(); void on_bluetooth_device_request(const BluetoothDeviceRequest &msg); void on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &msg); void on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &msg); void on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &msg); void on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &msg); void on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &msg); void on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &msg); void on_subscribe_bluetooth_connections_free_request(); void on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &msg); void on_bluetooth_set_connection_params_request(const BluetoothSetConnectionParamsRequest &msg); #endif #ifdef USE_HOMEASSISTANT_TIME void send_time_request() { GetTimeRequest req; this->send_message(req); } #endif #ifdef USE_VOICE_ASSISTANT void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg); void on_voice_assistant_response(const VoiceAssistantResponse &msg); void on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg); void on_voice_assistant_audio(const VoiceAssistantAudio &msg); void on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &msg); void on_voice_assistant_announce_request(const VoiceAssistantAnnounceRequest &msg); void on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &msg); void on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg); #endif #ifdef USE_ZWAVE_PROXY void on_z_wave_proxy_frame(const ZWaveProxyFrame &msg); void on_z_wave_proxy_request(const ZWaveProxyRequest &msg); #endif #ifdef USE_ALARM_CONTROL_PANEL bool send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel); void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg); #endif #ifdef USE_WATER_HEATER bool send_water_heater_state(water_heater::WaterHeater *water_heater); void on_water_heater_command_request(const WaterHeaterCommandRequest &msg); #endif #ifdef USE_IR_RF void on_infrared_rf_transmit_raw_timings_request(const InfraredRFTransmitRawTimingsRequest &msg); void send_infrared_rf_receive_event(const InfraredRFReceiveEvent &msg); #endif #ifdef USE_SERIAL_PROXY void on_serial_proxy_configure_request(const SerialProxyConfigureRequest &msg); void on_serial_proxy_write_request(const SerialProxyWriteRequest &msg); void on_serial_proxy_set_modem_pins_request(const SerialProxySetModemPinsRequest &msg); void on_serial_proxy_get_modem_pins_request(const SerialProxyGetModemPinsRequest &msg); void on_serial_proxy_request(const SerialProxyRequest &msg); void send_serial_proxy_data(const SerialProxyDataReceived &msg); #endif #ifdef USE_EVENT void send_event(event::Event *event); #endif #ifdef USE_UPDATE bool send_update_state(update::UpdateEntity *update); void on_update_command_request(const UpdateCommandRequest &msg); #endif void on_disconnect_response(); void on_ping_response() { // we initiated ping this->flags_.sent_ping = false; } #ifdef USE_API_HOMEASSISTANT_STATES void on_home_assistant_state_response(const HomeAssistantStateResponse &msg); #endif #ifdef USE_HOMEASSISTANT_TIME void on_get_time_response(const GetTimeResponse &value); #endif void on_hello_request(const HelloRequest &msg); void on_disconnect_request(); void on_ping_request(); void on_device_info_request(); void on_list_entities_request() { this->begin_iterator_(ActiveIterator::LIST_ENTITIES); } void on_subscribe_states_request() { this->flags_.state_subscription = true; // Start initial state iterator only if no iterator is active // If list_entities is running, we'll start initial_state when it completes if (this->active_iterator_ == ActiveIterator::NONE) { this->begin_iterator_(ActiveIterator::INITIAL_STATE); } } void on_subscribe_logs_request(const SubscribeLogsRequest &msg) { this->flags_.log_subscription = msg.level; if (msg.dump_config) App.schedule_dump_config(); #ifdef USE_ESP32_CRASH_HANDLER esp32::crash_handler_log(); #endif #ifdef USE_RP2040_CRASH_HANDLER rp2040::crash_handler_log(); #endif } #ifdef USE_API_HOMEASSISTANT_SERVICES void on_subscribe_homeassistant_services_request() { this->flags_.service_call_subscription = true; } #endif #ifdef USE_API_HOMEASSISTANT_STATES void on_subscribe_home_assistant_states_request(); #endif #ifdef USE_API_USER_DEFINED_ACTIONS void on_execute_service_request(const ExecuteServiceRequest &msg); #ifdef USE_API_USER_DEFINED_ACTION_RESPONSES void send_execute_service_response(uint32_t call_id, bool success, StringRef error_message); #ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON void send_execute_service_response(uint32_t call_id, bool success, StringRef error_message, const uint8_t *response_data, size_t response_data_len); #endif // USE_API_USER_DEFINED_ACTION_RESPONSES_JSON #endif // USE_API_USER_DEFINED_ACTION_RESPONSES #endif #ifdef USE_API_NOISE void on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg); #endif bool is_authenticated() { return static_cast(this->flags_.connection_state) == ConnectionState::AUTHENTICATED; } bool is_connection_setup() { return static_cast(this->flags_.connection_state) == ConnectionState::CONNECTED || this->is_authenticated(); } bool is_marked_for_removal() const { return this->flags_.remove; } uint8_t get_log_subscription_level() const { return this->flags_.log_subscription; } // Get client API version for feature detection bool client_supports_api_version(uint16_t major, uint16_t minor) const { return this->client_api_version_major_ > major || (this->client_api_version_major_ == major && this->client_api_version_minor_ >= minor); } void on_fatal_error(); void on_no_setup_connection(); // Function pointer type for type-erased message encoding using MessageEncodeFn = void (*)(const void *, ProtoWriteBuffer &); // Function pointer type for type-erased size calculation using CalculateSizeFn = uint32_t (*)(const void *); template bool send_message(const T &msg) { if constexpr (T::ESTIMATED_SIZE == 0) { return this->send_message_(0, T::MESSAGE_TYPE, &encode_msg_noop, &msg); } else { return this->send_message_(msg.calculate_size(), T::MESSAGE_TYPE, &proto_encode_msg, &msg); } } void prepare_first_message_buffer(APIBuffer &shared_buf, size_t header_padding, size_t total_size) { shared_buf.clear(); // Reserve space for header padding + message + footer // - Header padding: space for protocol headers (7 bytes for Noise, 6 for Plaintext) // - Footer: space for MAC (16 bytes for Noise, 0 for Plaintext) // Reserve full size but only set initial size to header padding // so message encoding starts at the correct position shared_buf.reserve_and_resize(total_size, header_padding); } // Convenience overload - computes frame overhead internally void prepare_first_message_buffer(APIBuffer &shared_buf, size_t payload_size) { const uint8_t header_padding = this->helper_->frame_header_padding(); const uint8_t footer_size = this->helper_->frame_footer_size(); this->prepare_first_message_buffer(shared_buf, header_padding, payload_size + header_padding + footer_size); } bool try_to_clear_buffer(bool log_out_of_space) { if (this->flags_.remove) return false; if (this->helper_->can_write_without_blocking()) return true; return this->try_to_clear_buffer_slow_(log_out_of_space); } bool send_buffer(ProtoWriteBuffer buffer, uint8_t message_type); const char *get_name() const { return this->helper_->get_client_name(); } /// Get peer name (IP address) into caller-provided buffer, returns buf for convenience const char *get_peername_to(std::span buf) const { return this->helper_->get_peername_to(buf); } protected: bool try_to_clear_buffer_slow_(bool log_out_of_space); // Helper function to handle authentication completion void complete_authentication_(); // Pattern B helpers: send response and return success/failure bool send_hello_response_(const HelloRequest &msg); bool send_disconnect_response_(); bool send_ping_response_(); bool send_device_info_response_(); #ifdef USE_API_NOISE bool send_noise_encryption_set_key_response_(const NoiseEncryptionSetKeyRequest &msg); #endif #ifdef USE_BLUETOOTH_PROXY bool send_subscribe_bluetooth_connections_free_response_(); #endif #ifdef USE_VOICE_ASSISTANT bool send_voice_assistant_get_configuration_response_(const VoiceAssistantConfigurationRequest &msg); #endif #ifdef USE_CAMERA void try_send_camera_image_(); #endif #ifdef USE_API_HOMEASSISTANT_STATES void process_state_subscriptions_(); #endif // Size thunk — converts void* back to concrete type for direct calculate_size() call template static uint32_t calc_size(const void *msg) { return static_cast(msg)->calculate_size(); } // Shared no-op encode thunk for empty messages (ESTIMATED_SIZE == 0) static void encode_msg_noop(const void *, ProtoWriteBuffer &) {} // Non-template buffer management for send_message bool send_message_(uint32_t payload_size, uint8_t message_type, MessageEncodeFn encode_fn, const void *msg); // Non-template buffer management for batch encoding static uint16_t encode_to_buffer(uint32_t calculated_size, MessageEncodeFn encode_fn, const void *msg, APIConnection *conn, uint32_t remaining_size); // Thin template wrapper — computes size, delegates buffer work to non-template helper template static uint16_t encode_message_to_buffer(T &msg, APIConnection *conn, uint32_t remaining_size) { if constexpr (T::ESTIMATED_SIZE == 0) { return encode_to_buffer(0, &encode_msg_noop, &msg, conn, remaining_size); } else { return encode_to_buffer(msg.calculate_size(), &proto_encode_msg, &msg, conn, remaining_size); } } // Non-template core — fills state fields and encodes static uint16_t fill_and_encode_entity_state(EntityBase *entity, StateResponseProtoMessage &msg, CalculateSizeFn size_fn, MessageEncodeFn encode_fn, APIConnection *conn, uint32_t remaining_size); // Thin template wrapper template static uint16_t fill_and_encode_entity_state(EntityBase *entity, T &msg, APIConnection *conn, uint32_t remaining_size) { return fill_and_encode_entity_state(entity, msg, &calc_size, &proto_encode_msg, conn, remaining_size); } // Non-template core — fills info fields, allocates buffers, and encodes static uint16_t fill_and_encode_entity_info(EntityBase *entity, InfoResponseProtoMessage &msg, CalculateSizeFn size_fn, MessageEncodeFn encode_fn, APIConnection *conn, uint32_t remaining_size); // Thin template wrapper template static uint16_t fill_and_encode_entity_info(EntityBase *entity, T &msg, APIConnection *conn, uint32_t remaining_size) { return fill_and_encode_entity_info(entity, msg, &calc_size, &proto_encode_msg, conn, remaining_size); } // Non-template core — fills device_class, then delegates to fill_and_encode_entity_info static uint16_t fill_and_encode_entity_info_with_device_class(EntityBase *entity, InfoResponseProtoMessage &msg, StringRef &device_class_field, CalculateSizeFn size_fn, MessageEncodeFn encode_fn, APIConnection *conn, uint32_t remaining_size); // Thin template wrapper template static uint16_t fill_and_encode_entity_info_with_device_class(EntityBase *entity, T &msg, StringRef &device_class_field, APIConnection *conn, uint32_t remaining_size) { return fill_and_encode_entity_info_with_device_class(entity, msg, device_class_field, &calc_size, &proto_encode_msg, conn, remaining_size); } #ifdef USE_VOICE_ASSISTANT // Helper to check voice assistant validity and connection ownership inline bool check_voice_assistant_api_connection_() const; #endif // Get the max batch size based on client API version // API 1.14+ clients don't receive object_id, so messages are smaller and more fit per batch // TODO: Remove this method before 2026.7.0 and use MAX_INITIAL_PER_BATCH directly size_t get_max_batch_size_() const { return this->client_supports_api_version(1, 14) ? MAX_INITIAL_PER_BATCH : MAX_INITIAL_PER_BATCH_LEGACY; } // Send keepalive ping or disconnect unresponsive client. // Cold path — extracted from loop() to reduce instruction cache pressure. void __attribute__((noinline)) check_keepalive_(uint32_t now); // Process active iterator (list_entities/initial_state) during connection setup. // Extracted from loop() — only runs during initial handshake, NONE in steady state. void __attribute__((noinline)) process_active_iterator_(); // Helper method to process multiple entities from an iterator in a batch. // Takes ComponentIterator base class reference to avoid duplicate template instantiations. void process_iterator_batch_(ComponentIterator &iterator); #ifdef USE_BINARY_SENSOR static uint16_t try_send_binary_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size); static uint16_t try_send_binary_sensor_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size); #endif #ifdef USE_COVER static uint16_t try_send_cover_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size); static uint16_t try_send_cover_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size); #endif #ifdef USE_FAN static uint16_t try_send_fan_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size); static uint16_t try_send_fan_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size); #endif #ifdef USE_LIGHT static uint16_t try_send_light_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size); static uint16_t try_send_light_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size); #endif #ifdef USE_SENSOR static uint16_t try_send_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size); static uint16_t try_send_sensor_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size); #endif #ifdef USE_SWITCH static uint16_t try_send_switch_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size); static uint16_t try_send_switch_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size); #endif #ifdef USE_TEXT_SENSOR static uint16_t try_send_text_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size); static uint16_t try_send_text_sensor_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size); #endif #ifdef USE_CLIMATE static uint16_t try_send_climate_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size); static uint16_t try_send_climate_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size); #endif #ifdef USE_NUMBER static uint16_t try_send_number_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size); static uint16_t try_send_number_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size); #endif #ifdef USE_DATETIME_DATE static uint16_t try_send_date_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size); static uint16_t try_send_date_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size); #endif #ifdef USE_DATETIME_TIME static uint16_t try_send_time_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size); static uint16_t try_send_time_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size); #endif #ifdef USE_DATETIME_DATETIME static uint16_t try_send_datetime_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size); static uint16_t try_send_datetime_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size); #endif #ifdef USE_TEXT static uint16_t try_send_text_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size); static uint16_t try_send_text_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size); #endif #ifdef USE_SELECT static uint16_t try_send_select_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size); static uint16_t try_send_select_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size); #endif #ifdef USE_BUTTON static uint16_t try_send_button_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size); #endif #ifdef USE_LOCK static uint16_t try_send_lock_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size); static uint16_t try_send_lock_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size); #endif #ifdef USE_VALVE static uint16_t try_send_valve_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size); static uint16_t try_send_valve_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size); #endif #ifdef USE_MEDIA_PLAYER static uint16_t try_send_media_player_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size); static uint16_t try_send_media_player_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size); #endif #ifdef USE_ALARM_CONTROL_PANEL static uint16_t try_send_alarm_control_panel_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size); static uint16_t try_send_alarm_control_panel_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size); #endif #ifdef USE_WATER_HEATER static uint16_t try_send_water_heater_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size); static uint16_t try_send_water_heater_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size); #endif #ifdef USE_INFRARED static uint16_t try_send_infrared_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size); #endif #ifdef USE_EVENT static uint16_t try_send_event_response(event::Event *event, StringRef event_type, APIConnection *conn, uint32_t remaining_size); static uint16_t try_send_event_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size); #endif #ifdef USE_UPDATE static uint16_t try_send_update_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size); static uint16_t try_send_update_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size); #endif #ifdef USE_CAMERA static uint16_t try_send_camera_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size); #endif // Method for ListEntitiesDone batching static uint16_t try_send_list_info_done(EntityBase *entity, APIConnection *conn, uint32_t remaining_size); // Method for DisconnectRequest batching static uint16_t try_send_disconnect_request(EntityBase *entity, APIConnection *conn, uint32_t remaining_size); // Batch message method for ping requests static uint16_t try_send_ping_request(EntityBase *entity, APIConnection *conn, uint32_t remaining_size); // === Optimal member ordering for 32-bit systems === // Group 1: Pointers (4 bytes each on 32-bit) #if defined(USE_API_NOISE) && defined(USE_API_PLAINTEXT) std::unique_ptr helper_; #elif defined(USE_API_NOISE) std::unique_ptr helper_; #elif defined(USE_API_PLAINTEXT) std::unique_ptr helper_; #endif APIServer *parent_; // Group 2: Iterator union (saves ~16 bytes vs separate iterators) // These iterators are never active simultaneously - list_entities runs to completion // before initial_state begins, so we use a union with explicit construction/destruction. enum class ActiveIterator : uint8_t { NONE, LIST_ENTITIES, INITIAL_STATE }; union IteratorUnion { ListEntitiesIterator list_entities; InitialStateIterator initial_state; // Constructor/destructor do nothing - use placement new/explicit destructor IteratorUnion() {} ~IteratorUnion() {} } iterator_storage_; // Helper methods for iterator lifecycle management void destroy_active_iterator_(); void begin_iterator_(ActiveIterator type); #ifdef USE_CAMERA std::unique_ptr image_reader_; #endif // Group 3: 4-byte types uint32_t last_traffic_; #ifdef USE_API_HOMEASSISTANT_STATES int state_subs_at_ = -1; #endif // Function pointer type for message encoding using MessageCreatorPtr = uint16_t (*)(EntityBase *, APIConnection *, uint32_t remaining_size); // Generic batching mechanism for both state updates and entity info struct DeferredBatch { // Sentinel value for unused aux_data_index static constexpr uint8_t AUX_DATA_UNUSED = std::numeric_limits::max(); struct BatchItem { EntityBase *entity; // 4 bytes - Entity pointer uint8_t message_type; // 1 byte - Message type for protocol and dispatch uint8_t estimated_size; // 1 byte - Estimated message size (max 255 bytes) uint8_t aux_data_index{AUX_DATA_UNUSED}; // 1 byte - For events: index into entity's event_types // 1 byte padding }; std::vector items; uint32_t batch_start_time{0}; // No pre-allocation - log connections never use batching, and for // connections that do, buffers are released after initial sync anyway // Add item to the batch (with deduplication) void add_item(EntityBase *entity, uint8_t message_type, uint8_t estimated_size, uint8_t aux_data_index = AUX_DATA_UNUSED); // Add item to the front of the batch (for high priority messages like ping) void add_item_front(EntityBase *entity, uint8_t message_type, uint8_t estimated_size); // Single push_back site to avoid duplicate _M_realloc_insert instantiation void push_item(const BatchItem &item); // Clear all items void clear() { items.clear(); batch_start_time = 0; } // Remove processed items from the front — noinline to keep memmove out of warm callers void remove_front(size_t count) __attribute__((noinline)) { items.erase(items.begin(), items.begin() + count); } bool empty() const { return items.empty(); } size_t size() const { return items.size(); } const BatchItem &operator[](size_t index) const { return items[index]; } // Release excess capacity - only releases if items already empty void release_buffer() { // Safe to call: batch is processed before release_buffer is called, // and if any items remain (partial processing), we must not clear them. // Use swap trick since shrink_to_fit() is non-binding and may be ignored. if (items.empty()) { std::vector().swap(items); } } }; // DeferredBatch here (16 bytes, 4-byte aligned) DeferredBatch deferred_batch_; // ConnectionState enum for type safety enum class ConnectionState : uint8_t { WAITING_FOR_HELLO = 0, CONNECTED = 1, AUTHENTICATED = 2, }; // Group 5: Pack all small members together to minimize padding // This group starts at a 4-byte boundary after DeferredBatch struct APIFlags { // Connection state only needs 2 bits (3 states) uint8_t connection_state : 2; // Log subscription needs 3 bits (log levels 0-7) uint8_t log_subscription : 3; // Boolean flags (1 bit each) uint8_t remove : 1; uint8_t state_subscription : 1; uint8_t sent_ping : 1; uint8_t service_call_subscription : 1; uint8_t next_close : 1; uint8_t batch_scheduled : 1; uint8_t batch_first_message : 1; // For batch buffer allocation uint8_t should_try_send_immediately : 1; // True after initial states are sent #ifdef HAS_PROTO_MESSAGE_DUMP uint8_t log_only_mode : 1; #endif } flags_{}; // 2 bytes total // 2-byte types immediately after flags_ (no padding between them) uint16_t client_api_version_major_{0}; uint16_t client_api_version_minor_{0}; // 1-byte type to fill padding ActiveIterator active_iterator_{ActiveIterator::NONE}; // Total: 2 (flags) + 2 + 2 + 1 = 7 bytes, then 1 byte padding to next 4-byte boundary uint32_t get_batch_delay_ms_() const; // Message will use 8 more bytes than the minimum size, and typical // MTU is 1500. Sometimes users will see as low as 1460 MTU. // If its IPv6 the header is 40 bytes, and if its IPv4 // the header is 20 bytes. So we have 1460 - 40 = 1420 bytes // available for the payload. But we also need to add the size of // the protobuf overhead, which is 8 bytes. // // To be safe we pick 1390 bytes as the maximum size // to send in one go. This is the maximum size of a single packet // that can be sent over the network. // This is to avoid fragmentation of the packet. static constexpr size_t MAX_BATCH_PACKET_SIZE = 1390; // MTU bool schedule_batch_(); void process_batch_(); void process_batch_multi_(APIBuffer &shared_buf, size_t num_items, uint8_t header_padding, uint8_t footer_size) __attribute__((noinline)); void clear_batch_() { this->deferred_batch_.clear(); this->flags_.batch_scheduled = false; } // Dispatch message encoding based on message_type - replaces function pointer storage // Switch assigns pointer, single call site for smaller code size uint16_t dispatch_message_(const DeferredBatch::BatchItem &item, uint32_t remaining_size, bool batch_first); #ifdef HAS_PROTO_MESSAGE_DUMP void log_batch_item_(const DeferredBatch::BatchItem &item) { this->flags_.log_only_mode = true; this->dispatch_message_(item, MAX_BATCH_PACKET_SIZE, true); this->flags_.log_only_mode = false; } #endif // Helper to check if a message type should bypass batching // Returns true if: // 1. It's an UpdateStateResponse (always send immediately to handle cases where // the main loop is blocked, e.g., during OTA updates) // 2. It's an EventResponse (events are edge-triggered - every occurrence matters) // 3. OR: User has opted into immediate sending (should_try_send_immediately = true // AND batch_delay = 0) inline bool should_send_immediately_(uint8_t message_type) const { return ( #ifdef USE_UPDATE message_type == UpdateStateResponse::MESSAGE_TYPE || #endif #ifdef USE_EVENT message_type == EventResponse::MESSAGE_TYPE || #endif (this->flags_.should_try_send_immediately && this->get_batch_delay_ms_() == 0)); } // Helper method to send a message either immediately or via batching // Tries immediate send if should_send_immediately_() returns true and buffer has space // Falls back to batching if immediate send fails or isn't applicable bool send_message_smart_(EntityBase *entity, uint8_t message_type, uint8_t estimated_size, uint8_t aux_data_index = DeferredBatch::AUX_DATA_UNUSED); // Helper function to schedule a deferred message with known message type bool schedule_message_(EntityBase *entity, uint8_t message_type, uint8_t estimated_size, uint8_t aux_data_index = DeferredBatch::AUX_DATA_UNUSED) { this->deferred_batch_.add_item(entity, message_type, estimated_size, aux_data_index); return this->schedule_batch_(); } // Helper function to schedule a high priority message at the front of the batch bool schedule_message_front_(EntityBase *entity, uint8_t message_type, uint8_t estimated_size) { this->deferred_batch_.add_item_front(entity, message_type, estimated_size); return this->schedule_batch_(); } // Helper function to log client messages with name and peername void log_client_(int level, const LogString *message); // Helper function to log API errors with errno void log_warning_(const LogString *message, APIError err); // Helper to handle fatal errors with logging inline void fatal_error_with_log_(const LogString *message, APIError err) { this->on_fatal_error(); this->log_warning_(message, err); } }; } // namespace esphome::api #endif