mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +00:00 
			
		
		
		
	Reduce API memory footprint through bitfield consolidation and type sizing (#9252)
This commit is contained in:
		| @@ -110,9 +110,10 @@ CONFIG_SCHEMA = cv.All( | |||||||
|             ): ACTIONS_SCHEMA, |             ): ACTIONS_SCHEMA, | ||||||
|             cv.Exclusive(CONF_ACTIONS, group_of_exclusion=CONF_ACTIONS): ACTIONS_SCHEMA, |             cv.Exclusive(CONF_ACTIONS, group_of_exclusion=CONF_ACTIONS): ACTIONS_SCHEMA, | ||||||
|             cv.Optional(CONF_ENCRYPTION): _encryption_schema, |             cv.Optional(CONF_ENCRYPTION): _encryption_schema, | ||||||
|             cv.Optional( |             cv.Optional(CONF_BATCH_DELAY, default="100ms"): cv.All( | ||||||
|                 CONF_BATCH_DELAY, default="100ms" |                 cv.positive_time_period_milliseconds, | ||||||
|             ): cv.positive_time_period_milliseconds, |                 cv.Range(max=cv.TimePeriod(milliseconds=65535)), | ||||||
|  |             ), | ||||||
|             cv.Optional(CONF_ON_CLIENT_CONNECTED): automation.validate_automation( |             cv.Optional(CONF_ON_CLIENT_CONNECTED): automation.validate_automation( | ||||||
|                 single=True |                 single=True | ||||||
|             ), |             ), | ||||||
|   | |||||||
| @@ -93,21 +93,21 @@ APIConnection::~APIConnection() { | |||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void APIConnection::log_batch_item_(const DeferredBatch::BatchItem &item) { | void APIConnection::log_batch_item_(const DeferredBatch::BatchItem &item) { | ||||||
|   // Set log-only mode |   // Set log-only mode | ||||||
|   this->log_only_mode_ = true; |   this->flags_.log_only_mode = true; | ||||||
|  |  | ||||||
|   // Call the creator - it will create the message and log it via encode_message_to_buffer |   // Call the creator - it will create the message and log it via encode_message_to_buffer | ||||||
|   item.creator(item.entity, this, std::numeric_limits<uint16_t>::max(), true, item.message_type); |   item.creator(item.entity, this, std::numeric_limits<uint16_t>::max(), true, item.message_type); | ||||||
|  |  | ||||||
|   // Clear log-only mode |   // Clear log-only mode | ||||||
|   this->log_only_mode_ = false; |   this->flags_.log_only_mode = false; | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| void APIConnection::loop() { | void APIConnection::loop() { | ||||||
|   if (this->next_close_) { |   if (this->flags_.next_close) { | ||||||
|     // requested a disconnect |     // requested a disconnect | ||||||
|     this->helper_->close(); |     this->helper_->close(); | ||||||
|     this->remove_ = true; |     this->flags_.remove = true; | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -148,15 +148,14 @@ void APIConnection::loop() { | |||||||
|         } else { |         } else { | ||||||
|           this->read_message(0, buffer.type, nullptr); |           this->read_message(0, buffer.type, nullptr); | ||||||
|         } |         } | ||||||
|         if (this->remove_) |         if (this->flags_.remove) | ||||||
|           return; |           return; | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   // Process deferred batch if scheduled |   // Process deferred batch if scheduled | ||||||
|   if (this->deferred_batch_.batch_scheduled && |   if (this->flags_.batch_scheduled && now - this->deferred_batch_.batch_start_time >= this->get_batch_delay_ms_()) { | ||||||
|       now - this->deferred_batch_.batch_start_time >= this->get_batch_delay_ms_()) { |  | ||||||
|     this->process_batch_(); |     this->process_batch_(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -166,7 +165,7 @@ void APIConnection::loop() { | |||||||
|     this->initial_state_iterator_.advance(); |     this->initial_state_iterator_.advance(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (this->sent_ping_) { |   if (this->flags_.sent_ping) { | ||||||
|     // Disconnect if not responded within 2.5*keepalive |     // Disconnect if not responded within 2.5*keepalive | ||||||
|     if (now - this->last_traffic_ > KEEPALIVE_DISCONNECT_TIMEOUT) { |     if (now - this->last_traffic_ > KEEPALIVE_DISCONNECT_TIMEOUT) { | ||||||
|       on_fatal_error(); |       on_fatal_error(); | ||||||
| @@ -174,13 +173,13 @@ void APIConnection::loop() { | |||||||
|     } |     } | ||||||
|   } else if (now - this->last_traffic_ > KEEPALIVE_TIMEOUT_MS) { |   } else if (now - this->last_traffic_ > KEEPALIVE_TIMEOUT_MS) { | ||||||
|     ESP_LOGVV(TAG, "Sending keepalive PING"); |     ESP_LOGVV(TAG, "Sending keepalive PING"); | ||||||
|     this->sent_ping_ = this->send_message(PingRequest()); |     this->flags_.sent_ping = this->send_message(PingRequest()); | ||||||
|     if (!this->sent_ping_) { |     if (!this->flags_.sent_ping) { | ||||||
|       // If we can't send the ping request directly (tx_buffer full), |       // If we can't send the ping request directly (tx_buffer full), | ||||||
|       // schedule it at the front of the batch so it will be sent with priority |       // schedule it at the front of the batch so it will be sent with priority | ||||||
|       ESP_LOGW(TAG, "Buffer full, ping queued"); |       ESP_LOGW(TAG, "Buffer full, ping queued"); | ||||||
|       this->schedule_message_front_(nullptr, &APIConnection::try_send_ping_request, PingRequest::MESSAGE_TYPE); |       this->schedule_message_front_(nullptr, &APIConnection::try_send_ping_request, PingRequest::MESSAGE_TYPE); | ||||||
|       this->sent_ping_ = true;  // Mark as sent to avoid scheduling multiple pings |       this->flags_.sent_ping = true;  // Mark as sent to avoid scheduling multiple pings | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -240,13 +239,13 @@ DisconnectResponse APIConnection::disconnect(const DisconnectRequest &msg) { | |||||||
|   // don't close yet, we still need to send the disconnect response |   // don't close yet, we still need to send the disconnect response | ||||||
|   // close will happen on next loop |   // close will happen on next loop | ||||||
|   ESP_LOGD(TAG, "%s disconnected", this->get_client_combined_info().c_str()); |   ESP_LOGD(TAG, "%s disconnected", this->get_client_combined_info().c_str()); | ||||||
|   this->next_close_ = true; |   this->flags_.next_close = true; | ||||||
|   DisconnectResponse resp; |   DisconnectResponse resp; | ||||||
|   return resp; |   return resp; | ||||||
| } | } | ||||||
| void APIConnection::on_disconnect_response(const DisconnectResponse &value) { | void APIConnection::on_disconnect_response(const DisconnectResponse &value) { | ||||||
|   this->helper_->close(); |   this->helper_->close(); | ||||||
|   this->remove_ = true; |   this->flags_.remove = true; | ||||||
| } | } | ||||||
|  |  | ||||||
| // Encodes a message to the buffer and returns the total number of bytes used, | // Encodes a message to the buffer and returns the total number of bytes used, | ||||||
| @@ -255,7 +254,7 @@ uint16_t APIConnection::encode_message_to_buffer(ProtoMessage &msg, uint16_t mes | |||||||
|                                                  uint32_t remaining_size, bool is_single) { |                                                  uint32_t remaining_size, bool is_single) { | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   // If in log-only mode, just log and return |   // If in log-only mode, just log and return | ||||||
|   if (conn->log_only_mode_) { |   if (conn->flags_.log_only_mode) { | ||||||
|     conn->log_send_message_(msg.message_name(), msg.dump()); |     conn->log_send_message_(msg.message_name(), msg.dump()); | ||||||
|     return 1;  // Return non-zero to indicate "success" for logging |     return 1;  // Return non-zero to indicate "success" for logging | ||||||
|   } |   } | ||||||
| @@ -1118,7 +1117,7 @@ void APIConnection::media_player_command(const MediaPlayerCommandRequest &msg) { | |||||||
|  |  | ||||||
| #ifdef USE_ESP32_CAMERA | #ifdef USE_ESP32_CAMERA | ||||||
| void APIConnection::set_camera_state(std::shared_ptr<esp32_camera::CameraImage> image) { | void APIConnection::set_camera_state(std::shared_ptr<esp32_camera::CameraImage> image) { | ||||||
|   if (!this->state_subscription_) |   if (!this->flags_.state_subscription) | ||||||
|     return; |     return; | ||||||
|   if (this->image_reader_.available()) |   if (this->image_reader_.available()) | ||||||
|     return; |     return; | ||||||
| @@ -1459,7 +1458,7 @@ void APIConnection::update_command(const UpdateCommandRequest &msg) { | |||||||
| #endif | #endif | ||||||
|  |  | ||||||
| bool APIConnection::try_send_log_message(int level, const char *tag, const char *line) { | bool APIConnection::try_send_log_message(int level, const char *tag, const char *line) { | ||||||
|   if (this->log_subscription_ < level) |   if (this->flags_.log_subscription < level) | ||||||
|     return false; |     return false; | ||||||
|  |  | ||||||
|   // Pre-calculate message size to avoid reallocations |   // Pre-calculate message size to avoid reallocations | ||||||
| @@ -1500,7 +1499,7 @@ HelloResponse APIConnection::hello(const HelloRequest &msg) { | |||||||
|   resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")"; |   resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")"; | ||||||
|   resp.name = App.get_name(); |   resp.name = App.get_name(); | ||||||
|  |  | ||||||
|   this->connection_state_ = ConnectionState::CONNECTED; |   this->flags_.connection_state = static_cast<uint8_t>(ConnectionState::CONNECTED); | ||||||
|   return resp; |   return resp; | ||||||
| } | } | ||||||
| ConnectResponse APIConnection::connect(const ConnectRequest &msg) { | ConnectResponse APIConnection::connect(const ConnectRequest &msg) { | ||||||
| @@ -1511,7 +1510,7 @@ ConnectResponse APIConnection::connect(const ConnectRequest &msg) { | |||||||
|   resp.invalid_password = !correct; |   resp.invalid_password = !correct; | ||||||
|   if (correct) { |   if (correct) { | ||||||
|     ESP_LOGD(TAG, "%s connected", this->get_client_combined_info().c_str()); |     ESP_LOGD(TAG, "%s connected", this->get_client_combined_info().c_str()); | ||||||
|     this->connection_state_ = ConnectionState::AUTHENTICATED; |     this->flags_.connection_state = static_cast<uint8_t>(ConnectionState::AUTHENTICATED); | ||||||
|     this->parent_->get_client_connected_trigger()->trigger(this->client_info_, this->client_peername_); |     this->parent_->get_client_connected_trigger()->trigger(this->client_info_, this->client_peername_); | ||||||
| #ifdef USE_HOMEASSISTANT_TIME | #ifdef USE_HOMEASSISTANT_TIME | ||||||
|     if (homeassistant::global_homeassistant_time != nullptr) { |     if (homeassistant::global_homeassistant_time != nullptr) { | ||||||
| @@ -1625,7 +1624,7 @@ void APIConnection::subscribe_home_assistant_states(const SubscribeHomeAssistant | |||||||
|   state_subs_at_ = 0; |   state_subs_at_ = 0; | ||||||
| } | } | ||||||
| bool APIConnection::try_to_clear_buffer(bool log_out_of_space) { | bool APIConnection::try_to_clear_buffer(bool log_out_of_space) { | ||||||
|   if (this->remove_) |   if (this->flags_.remove) | ||||||
|     return false; |     return false; | ||||||
|   if (this->helper_->can_write_without_blocking()) |   if (this->helper_->can_write_without_blocking()) | ||||||
|     return true; |     return true; | ||||||
| @@ -1675,7 +1674,7 @@ void APIConnection::on_no_setup_connection() { | |||||||
| } | } | ||||||
| void APIConnection::on_fatal_error() { | void APIConnection::on_fatal_error() { | ||||||
|   this->helper_->close(); |   this->helper_->close(); | ||||||
|   this->remove_ = true; |   this->flags_.remove = true; | ||||||
| } | } | ||||||
|  |  | ||||||
| void APIConnection::DeferredBatch::add_item(EntityBase *entity, MessageCreator creator, uint16_t message_type) { | void APIConnection::DeferredBatch::add_item(EntityBase *entity, MessageCreator creator, uint16_t message_type) { | ||||||
| @@ -1700,8 +1699,8 @@ void APIConnection::DeferredBatch::add_item_front(EntityBase *entity, MessageCre | |||||||
| } | } | ||||||
|  |  | ||||||
| bool APIConnection::schedule_batch_() { | bool APIConnection::schedule_batch_() { | ||||||
|   if (!this->deferred_batch_.batch_scheduled) { |   if (!this->flags_.batch_scheduled) { | ||||||
|     this->deferred_batch_.batch_scheduled = true; |     this->flags_.batch_scheduled = true; | ||||||
|     this->deferred_batch_.batch_start_time = App.get_loop_component_start_time(); |     this->deferred_batch_.batch_start_time = App.get_loop_component_start_time(); | ||||||
|   } |   } | ||||||
|   return true; |   return true; | ||||||
| @@ -1710,14 +1709,14 @@ bool APIConnection::schedule_batch_() { | |||||||
| ProtoWriteBuffer APIConnection::allocate_single_message_buffer(uint16_t size) { return this->create_buffer(size); } | ProtoWriteBuffer APIConnection::allocate_single_message_buffer(uint16_t size) { return this->create_buffer(size); } | ||||||
|  |  | ||||||
| ProtoWriteBuffer APIConnection::allocate_batch_message_buffer(uint16_t size) { | ProtoWriteBuffer APIConnection::allocate_batch_message_buffer(uint16_t size) { | ||||||
|   ProtoWriteBuffer result = this->prepare_message_buffer(size, this->batch_first_message_); |   ProtoWriteBuffer result = this->prepare_message_buffer(size, this->flags_.batch_first_message); | ||||||
|   this->batch_first_message_ = false; |   this->flags_.batch_first_message = false; | ||||||
|   return result; |   return result; | ||||||
| } | } | ||||||
|  |  | ||||||
| void APIConnection::process_batch_() { | void APIConnection::process_batch_() { | ||||||
|   if (this->deferred_batch_.empty()) { |   if (this->deferred_batch_.empty()) { | ||||||
|     this->deferred_batch_.batch_scheduled = false; |     this->flags_.batch_scheduled = false; | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -1770,7 +1769,7 @@ void APIConnection::process_batch_() { | |||||||
|  |  | ||||||
|   // Reserve based on estimated size (much more accurate than 24-byte worst-case) |   // Reserve based on estimated size (much more accurate than 24-byte worst-case) | ||||||
|   this->parent_->get_shared_buffer_ref().reserve(total_estimated_size + total_overhead); |   this->parent_->get_shared_buffer_ref().reserve(total_estimated_size + total_overhead); | ||||||
|   this->batch_first_message_ = true; |   this->flags_.batch_first_message = true; | ||||||
|  |  | ||||||
|   size_t items_processed = 0; |   size_t items_processed = 0; | ||||||
|   uint16_t remaining_size = std::numeric_limits<uint16_t>::max(); |   uint16_t remaining_size = std::numeric_limits<uint16_t>::max(); | ||||||
|   | |||||||
| @@ -107,7 +107,7 @@ class APIConnection : public APIServerConnection { | |||||||
| #endif | #endif | ||||||
|   bool try_send_log_message(int level, const char *tag, const char *line); |   bool try_send_log_message(int level, const char *tag, const char *line); | ||||||
|   void send_homeassistant_service_call(const HomeassistantServiceResponse &call) { |   void send_homeassistant_service_call(const HomeassistantServiceResponse &call) { | ||||||
|     if (!this->service_call_subscription_) |     if (!this->flags_.service_call_subscription) | ||||||
|       return; |       return; | ||||||
|     this->send_message(call); |     this->send_message(call); | ||||||
|   } |   } | ||||||
| @@ -164,7 +164,7 @@ class APIConnection : public APIServerConnection { | |||||||
|   void on_disconnect_response(const DisconnectResponse &value) override; |   void on_disconnect_response(const DisconnectResponse &value) override; | ||||||
|   void on_ping_response(const PingResponse &value) override { |   void on_ping_response(const PingResponse &value) override { | ||||||
|     // we initiated ping |     // we initiated ping | ||||||
|     this->sent_ping_ = false; |     this->flags_.sent_ping = false; | ||||||
|   } |   } | ||||||
|   void on_home_assistant_state_response(const HomeAssistantStateResponse &msg) override; |   void on_home_assistant_state_response(const HomeAssistantStateResponse &msg) override; | ||||||
| #ifdef USE_HOMEASSISTANT_TIME | #ifdef USE_HOMEASSISTANT_TIME | ||||||
| @@ -177,16 +177,16 @@ class APIConnection : public APIServerConnection { | |||||||
|   DeviceInfoResponse device_info(const DeviceInfoRequest &msg) override; |   DeviceInfoResponse device_info(const DeviceInfoRequest &msg) override; | ||||||
|   void list_entities(const ListEntitiesRequest &msg) override { this->list_entities_iterator_.begin(); } |   void list_entities(const ListEntitiesRequest &msg) override { this->list_entities_iterator_.begin(); } | ||||||
|   void subscribe_states(const SubscribeStatesRequest &msg) override { |   void subscribe_states(const SubscribeStatesRequest &msg) override { | ||||||
|     this->state_subscription_ = true; |     this->flags_.state_subscription = true; | ||||||
|     this->initial_state_iterator_.begin(); |     this->initial_state_iterator_.begin(); | ||||||
|   } |   } | ||||||
|   void subscribe_logs(const SubscribeLogsRequest &msg) override { |   void subscribe_logs(const SubscribeLogsRequest &msg) override { | ||||||
|     this->log_subscription_ = msg.level; |     this->flags_.log_subscription = msg.level; | ||||||
|     if (msg.dump_config) |     if (msg.dump_config) | ||||||
|       App.schedule_dump_config(); |       App.schedule_dump_config(); | ||||||
|   } |   } | ||||||
|   void subscribe_homeassistant_services(const SubscribeHomeassistantServicesRequest &msg) override { |   void subscribe_homeassistant_services(const SubscribeHomeassistantServicesRequest &msg) override { | ||||||
|     this->service_call_subscription_ = true; |     this->flags_.service_call_subscription = true; | ||||||
|   } |   } | ||||||
|   void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) override; |   void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) override; | ||||||
|   GetTimeResponse get_time(const GetTimeRequest &msg) override { |   GetTimeResponse get_time(const GetTimeRequest &msg) override { | ||||||
| @@ -198,9 +198,12 @@ class APIConnection : public APIServerConnection { | |||||||
|   NoiseEncryptionSetKeyResponse noise_encryption_set_key(const NoiseEncryptionSetKeyRequest &msg) override; |   NoiseEncryptionSetKeyResponse noise_encryption_set_key(const NoiseEncryptionSetKeyRequest &msg) override; | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|   bool is_authenticated() override { return this->connection_state_ == ConnectionState::AUTHENTICATED; } |   bool is_authenticated() override { | ||||||
|  |     return static_cast<ConnectionState>(this->flags_.connection_state) == ConnectionState::AUTHENTICATED; | ||||||
|  |   } | ||||||
|   bool is_connection_setup() override { |   bool is_connection_setup() override { | ||||||
|     return this->connection_state_ == ConnectionState ::CONNECTED || this->is_authenticated(); |     return static_cast<ConnectionState>(this->flags_.connection_state) == ConnectionState::CONNECTED || | ||||||
|  |            this->is_authenticated(); | ||||||
|   } |   } | ||||||
|   void on_fatal_error() override; |   void on_fatal_error() override; | ||||||
|   void on_unauthenticated_access() override; |   void on_unauthenticated_access() override; | ||||||
| @@ -423,49 +426,28 @@ class APIConnection : public APIServerConnection { | |||||||
|   static uint16_t try_send_ping_request(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, |   static uint16_t try_send_ping_request(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||||
|                                         bool is_single); |                                         bool is_single); | ||||||
|  |  | ||||||
|   // Pointers first (4 bytes each, naturally aligned) |   // === Optimal member ordering for 32-bit systems === | ||||||
|  |  | ||||||
|  |   // Group 1: Pointers (4 bytes each on 32-bit) | ||||||
|   std::unique_ptr<APIFrameHelper> helper_; |   std::unique_ptr<APIFrameHelper> helper_; | ||||||
|   APIServer *parent_; |   APIServer *parent_; | ||||||
|  |  | ||||||
|   // 4-byte aligned types |   // Group 2: Larger objects (must be 4-byte aligned) | ||||||
|   uint32_t last_traffic_; |   // These contain vectors/pointers internally, so putting them early ensures good alignment | ||||||
|   int state_subs_at_ = -1; |  | ||||||
|  |  | ||||||
|   // Strings (12 bytes each on 32-bit) |  | ||||||
|   std::string client_info_; |  | ||||||
|   std::string client_peername_; |  | ||||||
|  |  | ||||||
|   // 2-byte aligned types |  | ||||||
|   uint16_t client_api_version_major_{0}; |  | ||||||
|   uint16_t client_api_version_minor_{0}; |  | ||||||
|  |  | ||||||
|   // Group all 1-byte types together to minimize padding |  | ||||||
|   enum class ConnectionState : uint8_t { |  | ||||||
|     WAITING_FOR_HELLO, |  | ||||||
|     CONNECTED, |  | ||||||
|     AUTHENTICATED, |  | ||||||
|   } connection_state_{ConnectionState::WAITING_FOR_HELLO}; |  | ||||||
|   uint8_t log_subscription_{ESPHOME_LOG_LEVEL_NONE}; |  | ||||||
|   bool remove_{false}; |  | ||||||
|   bool state_subscription_{false}; |  | ||||||
|   bool sent_ping_{false}; |  | ||||||
|   bool service_call_subscription_{false}; |  | ||||||
|   bool next_close_ = false; |  | ||||||
|   // 7 bytes used, 1 byte padding |  | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP |  | ||||||
|   // When true, encode_message_to_buffer will only log, not encode |  | ||||||
|   bool log_only_mode_{false}; |  | ||||||
| #endif |  | ||||||
|   uint8_t ping_retries_{0}; |  | ||||||
|   // 8 bytes used, no padding needed |  | ||||||
|  |  | ||||||
|   // Larger objects at the end |  | ||||||
|   InitialStateIterator initial_state_iterator_; |   InitialStateIterator initial_state_iterator_; | ||||||
|   ListEntitiesIterator list_entities_iterator_; |   ListEntitiesIterator list_entities_iterator_; | ||||||
| #ifdef USE_ESP32_CAMERA | #ifdef USE_ESP32_CAMERA | ||||||
|   esp32_camera::CameraImageReader image_reader_; |   esp32_camera::CameraImageReader image_reader_; | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|  |   // Group 3: Strings (12 bytes each on 32-bit, 4-byte aligned) | ||||||
|  |   std::string client_info_; | ||||||
|  |   std::string client_peername_; | ||||||
|  |  | ||||||
|  |   // Group 4: 4-byte types | ||||||
|  |   uint32_t last_traffic_; | ||||||
|  |   int state_subs_at_ = -1; | ||||||
|  |  | ||||||
|   // Function pointer type for message encoding |   // Function pointer type for message encoding | ||||||
|   using MessageCreatorPtr = uint16_t (*)(EntityBase *, APIConnection *, uint32_t remaining_size, bool is_single); |   using MessageCreatorPtr = uint16_t (*)(EntityBase *, APIConnection *, uint32_t remaining_size, bool is_single); | ||||||
|  |  | ||||||
| @@ -575,7 +557,6 @@ class APIConnection : public APIServerConnection { | |||||||
|  |  | ||||||
|     std::vector<BatchItem> items; |     std::vector<BatchItem> items; | ||||||
|     uint32_t batch_start_time{0}; |     uint32_t batch_start_time{0}; | ||||||
|     bool batch_scheduled{false}; |  | ||||||
|  |  | ||||||
|     DeferredBatch() { |     DeferredBatch() { | ||||||
|       // Pre-allocate capacity for typical batch sizes to avoid reallocation |       // Pre-allocate capacity for typical batch sizes to avoid reallocation | ||||||
| @@ -588,13 +569,47 @@ class APIConnection : public APIServerConnection { | |||||||
|     void add_item_front(EntityBase *entity, MessageCreator creator, uint16_t message_type); |     void add_item_front(EntityBase *entity, MessageCreator creator, uint16_t message_type); | ||||||
|     void clear() { |     void clear() { | ||||||
|       items.clear(); |       items.clear(); | ||||||
|       batch_scheduled = false; |  | ||||||
|       batch_start_time = 0; |       batch_start_time = 0; | ||||||
|     } |     } | ||||||
|     bool empty() const { return items.empty(); } |     bool empty() const { return items.empty(); } | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|  |   // DeferredBatch here (16 bytes, 4-byte aligned) | ||||||
|   DeferredBatch deferred_batch_; |   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 | ||||||
|  | #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}; | ||||||
|  |   // Total: 2 (flags) + 2 + 2 = 6 bytes, then 2 bytes padding to next 4-byte boundary | ||||||
|  |  | ||||||
|   uint32_t get_batch_delay_ms_() const; |   uint32_t get_batch_delay_ms_() const; | ||||||
|   // Message will use 8 more bytes than the minimum size, and typical |   // Message will use 8 more bytes than the minimum size, and typical | ||||||
|   // MTU is 1500. Sometimes users will see as low as 1460 MTU. |   // MTU is 1500. Sometimes users will see as low as 1460 MTU. | ||||||
| @@ -612,9 +627,6 @@ class APIConnection : public APIServerConnection { | |||||||
|   bool schedule_batch_(); |   bool schedule_batch_(); | ||||||
|   void process_batch_(); |   void process_batch_(); | ||||||
|  |  | ||||||
|   // State for batch buffer allocation |  | ||||||
|   bool batch_first_message_{false}; |  | ||||||
|  |  | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   void log_batch_item_(const DeferredBatch::BatchItem &item); |   void log_batch_item_(const DeferredBatch::BatchItem &item); | ||||||
| #endif | #endif | ||||||
|   | |||||||
| @@ -104,7 +104,7 @@ void APIServer::setup() { | |||||||
|         return; |         return; | ||||||
|       } |       } | ||||||
|       for (auto &c : this->clients_) { |       for (auto &c : this->clients_) { | ||||||
|         if (!c->remove_) |         if (!c->flags_.remove) | ||||||
|           c->try_send_log_message(level, tag, message); |           c->try_send_log_message(level, tag, message); | ||||||
|       } |       } | ||||||
|     }); |     }); | ||||||
| @@ -116,7 +116,7 @@ void APIServer::setup() { | |||||||
|     esp32_camera::global_esp32_camera->add_image_callback( |     esp32_camera::global_esp32_camera->add_image_callback( | ||||||
|         [this](const std::shared_ptr<esp32_camera::CameraImage> &image) { |         [this](const std::shared_ptr<esp32_camera::CameraImage> &image) { | ||||||
|           for (auto &c : this->clients_) { |           for (auto &c : this->clients_) { | ||||||
|             if (!c->remove_) |             if (!c->flags_.remove) | ||||||
|               c->set_camera_state(image); |               c->set_camera_state(image); | ||||||
|           } |           } | ||||||
|         }); |         }); | ||||||
| @@ -176,7 +176,7 @@ void APIServer::loop() { | |||||||
|   while (client_index < this->clients_.size()) { |   while (client_index < this->clients_.size()) { | ||||||
|     auto &client = this->clients_[client_index]; |     auto &client = this->clients_[client_index]; | ||||||
|  |  | ||||||
|     if (!client->remove_) { |     if (!client->flags_.remove) { | ||||||
|       // Common case: process active client |       // Common case: process active client | ||||||
|       client->loop(); |       client->loop(); | ||||||
|       client_index++; |       client_index++; | ||||||
| @@ -431,7 +431,7 @@ void APIServer::set_port(uint16_t port) { this->port_ = port; } | |||||||
|  |  | ||||||
| void APIServer::set_password(const std::string &password) { this->password_ = password; } | void APIServer::set_password(const std::string &password) { this->password_ = password; } | ||||||
|  |  | ||||||
| void APIServer::set_batch_delay(uint32_t batch_delay) { this->batch_delay_ = batch_delay; } | void APIServer::set_batch_delay(uint16_t batch_delay) { this->batch_delay_ = batch_delay; } | ||||||
|  |  | ||||||
| void APIServer::send_homeassistant_service_call(const HomeassistantServiceResponse &call) { | void APIServer::send_homeassistant_service_call(const HomeassistantServiceResponse &call) { | ||||||
|   for (auto &client : this->clients_) { |   for (auto &client : this->clients_) { | ||||||
| @@ -502,7 +502,7 @@ bool APIServer::save_noise_psk(psk_t psk, bool make_active) { | |||||||
| #ifdef USE_HOMEASSISTANT_TIME | #ifdef USE_HOMEASSISTANT_TIME | ||||||
| void APIServer::request_time() { | void APIServer::request_time() { | ||||||
|   for (auto &client : this->clients_) { |   for (auto &client : this->clients_) { | ||||||
|     if (!client->remove_ && client->is_authenticated()) |     if (!client->flags_.remove && client->is_authenticated()) | ||||||
|       client->send_time_request(); |       client->send_time_request(); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -40,8 +40,8 @@ class APIServer : public Component, public Controller { | |||||||
|   void set_port(uint16_t port); |   void set_port(uint16_t port); | ||||||
|   void set_password(const std::string &password); |   void set_password(const std::string &password); | ||||||
|   void set_reboot_timeout(uint32_t reboot_timeout); |   void set_reboot_timeout(uint32_t reboot_timeout); | ||||||
|   void set_batch_delay(uint32_t batch_delay); |   void set_batch_delay(uint16_t batch_delay); | ||||||
|   uint32_t get_batch_delay() const { return batch_delay_; } |   uint16_t get_batch_delay() const { return batch_delay_; } | ||||||
|  |  | ||||||
|   // Get reference to shared buffer for API connections |   // Get reference to shared buffer for API connections | ||||||
|   std::vector<uint8_t> &get_shared_buffer_ref() { return shared_write_buffer_; } |   std::vector<uint8_t> &get_shared_buffer_ref() { return shared_write_buffer_; } | ||||||
| @@ -150,7 +150,6 @@ class APIServer : public Component, public Controller { | |||||||
|  |  | ||||||
|   // 4-byte aligned types |   // 4-byte aligned types | ||||||
|   uint32_t reboot_timeout_{300000}; |   uint32_t reboot_timeout_{300000}; | ||||||
|   uint32_t batch_delay_{100}; |  | ||||||
|  |  | ||||||
|   // Vectors and strings (12 bytes each on 32-bit) |   // Vectors and strings (12 bytes each on 32-bit) | ||||||
|   std::vector<std::unique_ptr<APIConnection>> clients_; |   std::vector<std::unique_ptr<APIConnection>> clients_; | ||||||
| @@ -161,8 +160,9 @@ class APIServer : public Component, public Controller { | |||||||
|  |  | ||||||
|   // Group smaller types together |   // Group smaller types together | ||||||
|   uint16_t port_{6053}; |   uint16_t port_{6053}; | ||||||
|  |   uint16_t batch_delay_{100}; | ||||||
|   bool shutting_down_ = false; |   bool shutting_down_ = false; | ||||||
|   // 3 bytes used, 1 byte padding |   // 5 bytes used, 3 bytes padding | ||||||
|  |  | ||||||
| #ifdef USE_API_NOISE | #ifdef USE_API_NOISE | ||||||
|   std::shared_ptr<APINoiseContext> noise_ctx_ = std::make_shared<APINoiseContext>(); |   std::shared_ptr<APINoiseContext> noise_ctx_ = std::make_shared<APINoiseContext>(); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user