mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 15:12:06 +00:00 
			
		
		
		
	[api] Eliminate heap allocations when populating repeated fields from containers (#9948)
This commit is contained in:
		| @@ -419,7 +419,7 @@ message ListEntitiesFanResponse { | ||||
|   bool disabled_by_default = 9; | ||||
|   string icon = 10 [(field_ifdef) = "USE_ENTITY_ICON"]; | ||||
|   EntityCategory entity_category = 11; | ||||
|   repeated string supported_preset_modes = 12; | ||||
|   repeated string supported_preset_modes = 12 [(container_pointer) = "std::set"]; | ||||
|   uint32 device_id = 13 [(field_ifdef) = "USE_DEVICES"]; | ||||
| } | ||||
| // Deprecated in API version 1.6 - only used in deprecated fields | ||||
| @@ -500,7 +500,7 @@ message ListEntitiesLightResponse { | ||||
|   string name = 3; | ||||
|   reserved 4; // Deprecated: was string unique_id | ||||
|  | ||||
|   repeated ColorMode supported_color_modes = 12; | ||||
|   repeated ColorMode supported_color_modes = 12 [(container_pointer) = "std::set<light::ColorMode>"]; | ||||
|   // next four supports_* are for legacy clients, newer clients should use color modes | ||||
|   // Deprecated in API version 1.6 | ||||
|   bool legacy_supports_brightness = 5 [deprecated=true]; | ||||
| @@ -966,7 +966,7 @@ message ListEntitiesClimateResponse { | ||||
|  | ||||
|   bool supports_current_temperature = 5; | ||||
|   bool supports_two_point_target_temperature = 6; | ||||
|   repeated ClimateMode supported_modes = 7; | ||||
|   repeated ClimateMode supported_modes = 7 [(container_pointer) = "std::set<climate::ClimateMode>"]; | ||||
|   float visual_min_temperature = 8; | ||||
|   float visual_max_temperature = 9; | ||||
|   float visual_target_temperature_step = 10; | ||||
| @@ -975,11 +975,11 @@ message ListEntitiesClimateResponse { | ||||
|   // Deprecated in API version 1.5 | ||||
|   bool legacy_supports_away = 11 [deprecated=true]; | ||||
|   bool supports_action = 12; | ||||
|   repeated ClimateFanMode supported_fan_modes = 13; | ||||
|   repeated ClimateSwingMode supported_swing_modes = 14; | ||||
|   repeated string supported_custom_fan_modes = 15; | ||||
|   repeated ClimatePreset supported_presets = 16; | ||||
|   repeated string supported_custom_presets = 17; | ||||
|   repeated ClimateFanMode supported_fan_modes = 13 [(container_pointer) = "std::set<climate::ClimateFanMode>"]; | ||||
|   repeated ClimateSwingMode supported_swing_modes = 14 [(container_pointer) = "std::set<climate::ClimateSwingMode>"]; | ||||
|   repeated string supported_custom_fan_modes = 15 [(container_pointer) = "std::set"]; | ||||
|   repeated ClimatePreset supported_presets = 16 [(container_pointer) = "std::set<climate::ClimatePreset>"]; | ||||
|   repeated string supported_custom_presets = 17 [(container_pointer) = "std::set"]; | ||||
|   bool disabled_by_default = 18; | ||||
|   string icon = 19 [(field_ifdef) = "USE_ENTITY_ICON"]; | ||||
|   EntityCategory entity_category = 20; | ||||
| @@ -1119,7 +1119,7 @@ message ListEntitiesSelectResponse { | ||||
|   reserved 4; // Deprecated: was string unique_id | ||||
|  | ||||
|   string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON"]; | ||||
|   repeated string options = 6; | ||||
|   repeated string options = 6 [(container_pointer) = "std::vector"]; | ||||
|   bool disabled_by_default = 7; | ||||
|   EntityCategory entity_category = 8; | ||||
|   uint32 device_id = 9 [(field_ifdef) = "USE_DEVICES"]; | ||||
| @@ -1844,7 +1844,7 @@ message VoiceAssistantConfigurationResponse { | ||||
|   option (ifdef) = "USE_VOICE_ASSISTANT"; | ||||
|  | ||||
|   repeated VoiceAssistantWakeWord available_wake_words = 1; | ||||
|   repeated string active_wake_words = 2; | ||||
|   repeated string active_wake_words = 2 [(container_pointer) = "std::vector"]; | ||||
|   uint32 max_active_wake_words = 3; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -413,8 +413,7 @@ uint16_t APIConnection::try_send_fan_info(EntityBase *entity, APIConnection *con | ||||
|   msg.supports_speed = traits.supports_speed(); | ||||
|   msg.supports_direction = traits.supports_direction(); | ||||
|   msg.supported_speed_count = traits.supported_speed_count(); | ||||
|   for (auto const &preset : traits.supported_preset_modes()) | ||||
|     msg.supported_preset_modes.push_back(preset); | ||||
|   msg.supported_preset_modes = &traits.supported_preset_modes_for_api_(); | ||||
|   return fill_and_encode_entity_info(fan, msg, ListEntitiesFanResponse::MESSAGE_TYPE, conn, remaining_size, is_single); | ||||
| } | ||||
| void APIConnection::fan_command(const FanCommandRequest &msg) { | ||||
| @@ -470,8 +469,7 @@ uint16_t APIConnection::try_send_light_info(EntityBase *entity, APIConnection *c | ||||
|   auto *light = static_cast<light::LightState *>(entity); | ||||
|   ListEntitiesLightResponse msg; | ||||
|   auto traits = light->get_traits(); | ||||
|   for (auto mode : traits.get_supported_color_modes()) | ||||
|     msg.supported_color_modes.push_back(static_cast<enums::ColorMode>(mode)); | ||||
|   msg.supported_color_modes = &traits.get_supported_color_modes_for_api_(); | ||||
|   if (traits.supports_color_capability(light::ColorCapability::COLOR_TEMPERATURE) || | ||||
|       traits.supports_color_capability(light::ColorCapability::COLD_WARM_WHITE)) { | ||||
|     msg.min_mireds = traits.get_min_mireds(); | ||||
| @@ -657,8 +655,7 @@ uint16_t APIConnection::try_send_climate_info(EntityBase *entity, APIConnection | ||||
|   msg.supports_current_humidity = traits.get_supports_current_humidity(); | ||||
|   msg.supports_two_point_target_temperature = traits.get_supports_two_point_target_temperature(); | ||||
|   msg.supports_target_humidity = traits.get_supports_target_humidity(); | ||||
|   for (auto mode : traits.get_supported_modes()) | ||||
|     msg.supported_modes.push_back(static_cast<enums::ClimateMode>(mode)); | ||||
|   msg.supported_modes = &traits.get_supported_modes_for_api_(); | ||||
|   msg.visual_min_temperature = traits.get_visual_min_temperature(); | ||||
|   msg.visual_max_temperature = traits.get_visual_max_temperature(); | ||||
|   msg.visual_target_temperature_step = traits.get_visual_target_temperature_step(); | ||||
| @@ -666,16 +663,11 @@ uint16_t APIConnection::try_send_climate_info(EntityBase *entity, APIConnection | ||||
|   msg.visual_min_humidity = traits.get_visual_min_humidity(); | ||||
|   msg.visual_max_humidity = traits.get_visual_max_humidity(); | ||||
|   msg.supports_action = traits.get_supports_action(); | ||||
|   for (auto fan_mode : traits.get_supported_fan_modes()) | ||||
|     msg.supported_fan_modes.push_back(static_cast<enums::ClimateFanMode>(fan_mode)); | ||||
|   for (auto const &custom_fan_mode : traits.get_supported_custom_fan_modes()) | ||||
|     msg.supported_custom_fan_modes.push_back(custom_fan_mode); | ||||
|   for (auto preset : traits.get_supported_presets()) | ||||
|     msg.supported_presets.push_back(static_cast<enums::ClimatePreset>(preset)); | ||||
|   for (auto const &custom_preset : traits.get_supported_custom_presets()) | ||||
|     msg.supported_custom_presets.push_back(custom_preset); | ||||
|   for (auto swing_mode : traits.get_supported_swing_modes()) | ||||
|     msg.supported_swing_modes.push_back(static_cast<enums::ClimateSwingMode>(swing_mode)); | ||||
|   msg.supported_fan_modes = &traits.get_supported_fan_modes_for_api_(); | ||||
|   msg.supported_custom_fan_modes = &traits.get_supported_custom_fan_modes_for_api_(); | ||||
|   msg.supported_presets = &traits.get_supported_presets_for_api_(); | ||||
|   msg.supported_custom_presets = &traits.get_supported_custom_presets_for_api_(); | ||||
|   msg.supported_swing_modes = &traits.get_supported_swing_modes_for_api_(); | ||||
|   return fill_and_encode_entity_info(climate, msg, ListEntitiesClimateResponse::MESSAGE_TYPE, conn, remaining_size, | ||||
|                                      is_single); | ||||
| } | ||||
| @@ -881,8 +873,7 @@ uint16_t APIConnection::try_send_select_info(EntityBase *entity, APIConnection * | ||||
|                                              bool is_single) { | ||||
|   auto *select = static_cast<select::Select *>(entity); | ||||
|   ListEntitiesSelectResponse msg; | ||||
|   for (const auto &option : select->traits.get_options()) | ||||
|     msg.options.push_back(option); | ||||
|   msg.options = &select->traits.get_options(); | ||||
|   return fill_and_encode_entity_info(select, msg, ListEntitiesSelectResponse::MESSAGE_TYPE, conn, remaining_size, | ||||
|                                      is_single); | ||||
| } | ||||
| @@ -1197,9 +1188,7 @@ bool APIConnection::send_voice_assistant_get_configuration_response(const VoiceA | ||||
|       resp_wake_word.trained_languages.push_back(lang); | ||||
|     } | ||||
|   } | ||||
|   for (auto &wake_word_id : config.active_wake_words) { | ||||
|     resp.active_wake_words.push_back(wake_word_id); | ||||
|   } | ||||
|   resp.active_wake_words = &config.active_wake_words; | ||||
|   resp.max_active_wake_words = config.max_active_wake_words; | ||||
|   return this->send_message(resp, VoiceAssistantConfigurationResponse::MESSAGE_TYPE); | ||||
| } | ||||
|   | ||||
| @@ -28,4 +28,30 @@ extend google.protobuf.FieldOptions { | ||||
|     optional string field_ifdef = 1042; | ||||
|     optional uint32 fixed_array_size = 50007; | ||||
|     optional bool no_zero_copy = 50008 [default=false]; | ||||
|  | ||||
|     // container_pointer: Zero-copy optimization for repeated fields. | ||||
|     // | ||||
|     // When container_pointer is set on a repeated field, the generated message will | ||||
|     // store a pointer to an existing container instead of copying the data into the | ||||
|     // message's own repeated field. This eliminates heap allocations and improves performance. | ||||
|     // | ||||
|     // Requirements for safe usage: | ||||
|     // 1. The source container must remain valid until the message is encoded | ||||
|     // 2. Messages must be encoded immediately (which ESPHome does by default) | ||||
|     // 3. The container type must match the field type exactly | ||||
|     // | ||||
|     // Supported container types: | ||||
|     // - "std::vector<T>" for most repeated fields | ||||
|     // - "std::set<T>" for unique/sorted data | ||||
|     // - Full type specification required for enums (e.g., "std::set<climate::ClimateMode>") | ||||
|     // | ||||
|     // Example usage in .proto file: | ||||
|     //   repeated string supported_modes = 12 [(container_pointer) = "std::set"]; | ||||
|     //   repeated ColorMode color_modes = 13 [(container_pointer) = "std::set<light::ColorMode>"]; | ||||
|     // | ||||
|     // The corresponding C++ code must provide const reference access to a container | ||||
|     // that matches the specified type and remains valid during message encoding. | ||||
|     // This is typically done through methods returning const T& or special accessor | ||||
|     // methods like get_options() or supported_modes_for_api_(). | ||||
|     optional string container_pointer = 50001; | ||||
| } | ||||
|   | ||||
| @@ -331,7 +331,7 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const { | ||||
|   buffer.encode_string(10, this->icon_ref_); | ||||
| #endif | ||||
|   buffer.encode_uint32(11, static_cast<uint32_t>(this->entity_category)); | ||||
|   for (auto &it : this->supported_preset_modes) { | ||||
|   for (const auto &it : *this->supported_preset_modes) { | ||||
|     buffer.encode_string(12, it, true); | ||||
|   } | ||||
| #ifdef USE_DEVICES | ||||
| @@ -351,8 +351,8 @@ void ListEntitiesFanResponse::calculate_size(ProtoSize &size) const { | ||||
|   size.add_length(1, this->icon_ref_.size()); | ||||
| #endif | ||||
|   size.add_uint32(1, static_cast<uint32_t>(this->entity_category)); | ||||
|   if (!this->supported_preset_modes.empty()) { | ||||
|     for (const auto &it : this->supported_preset_modes) { | ||||
|   if (!this->supported_preset_modes->empty()) { | ||||
|     for (const auto &it : *this->supported_preset_modes) { | ||||
|       size.add_length_force(1, it.size()); | ||||
|     } | ||||
|   } | ||||
| @@ -447,7 +447,7 @@ void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const { | ||||
|   buffer.encode_string(1, this->object_id_ref_); | ||||
|   buffer.encode_fixed32(2, this->key); | ||||
|   buffer.encode_string(3, this->name_ref_); | ||||
|   for (auto &it : this->supported_color_modes) { | ||||
|   for (const auto &it : *this->supported_color_modes) { | ||||
|     buffer.encode_uint32(12, static_cast<uint32_t>(it), true); | ||||
|   } | ||||
|   buffer.encode_float(9, this->min_mireds); | ||||
| @@ -468,8 +468,8 @@ void ListEntitiesLightResponse::calculate_size(ProtoSize &size) const { | ||||
|   size.add_length(1, this->object_id_ref_.size()); | ||||
|   size.add_fixed32(1, this->key); | ||||
|   size.add_length(1, this->name_ref_.size()); | ||||
|   if (!this->supported_color_modes.empty()) { | ||||
|     for (const auto &it : this->supported_color_modes) { | ||||
|   if (!this->supported_color_modes->empty()) { | ||||
|     for (const auto &it : *this->supported_color_modes) { | ||||
|       size.add_uint32_force(1, static_cast<uint32_t>(it)); | ||||
|     } | ||||
|   } | ||||
| @@ -1064,26 +1064,26 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const { | ||||
|   buffer.encode_string(3, this->name_ref_); | ||||
|   buffer.encode_bool(5, this->supports_current_temperature); | ||||
|   buffer.encode_bool(6, this->supports_two_point_target_temperature); | ||||
|   for (auto &it : this->supported_modes) { | ||||
|   for (const auto &it : *this->supported_modes) { | ||||
|     buffer.encode_uint32(7, static_cast<uint32_t>(it), true); | ||||
|   } | ||||
|   buffer.encode_float(8, this->visual_min_temperature); | ||||
|   buffer.encode_float(9, this->visual_max_temperature); | ||||
|   buffer.encode_float(10, this->visual_target_temperature_step); | ||||
|   buffer.encode_bool(12, this->supports_action); | ||||
|   for (auto &it : this->supported_fan_modes) { | ||||
|   for (const auto &it : *this->supported_fan_modes) { | ||||
|     buffer.encode_uint32(13, static_cast<uint32_t>(it), true); | ||||
|   } | ||||
|   for (auto &it : this->supported_swing_modes) { | ||||
|   for (const auto &it : *this->supported_swing_modes) { | ||||
|     buffer.encode_uint32(14, static_cast<uint32_t>(it), true); | ||||
|   } | ||||
|   for (auto &it : this->supported_custom_fan_modes) { | ||||
|   for (const auto &it : *this->supported_custom_fan_modes) { | ||||
|     buffer.encode_string(15, it, true); | ||||
|   } | ||||
|   for (auto &it : this->supported_presets) { | ||||
|   for (const auto &it : *this->supported_presets) { | ||||
|     buffer.encode_uint32(16, static_cast<uint32_t>(it), true); | ||||
|   } | ||||
|   for (auto &it : this->supported_custom_presets) { | ||||
|   for (const auto &it : *this->supported_custom_presets) { | ||||
|     buffer.encode_string(17, it, true); | ||||
|   } | ||||
|   buffer.encode_bool(18, this->disabled_by_default); | ||||
| @@ -1106,8 +1106,8 @@ void ListEntitiesClimateResponse::calculate_size(ProtoSize &size) const { | ||||
|   size.add_length(1, this->name_ref_.size()); | ||||
|   size.add_bool(1, this->supports_current_temperature); | ||||
|   size.add_bool(1, this->supports_two_point_target_temperature); | ||||
|   if (!this->supported_modes.empty()) { | ||||
|     for (const auto &it : this->supported_modes) { | ||||
|   if (!this->supported_modes->empty()) { | ||||
|     for (const auto &it : *this->supported_modes) { | ||||
|       size.add_uint32_force(1, static_cast<uint32_t>(it)); | ||||
|     } | ||||
|   } | ||||
| @@ -1115,28 +1115,28 @@ void ListEntitiesClimateResponse::calculate_size(ProtoSize &size) const { | ||||
|   size.add_float(1, this->visual_max_temperature); | ||||
|   size.add_float(1, this->visual_target_temperature_step); | ||||
|   size.add_bool(1, this->supports_action); | ||||
|   if (!this->supported_fan_modes.empty()) { | ||||
|     for (const auto &it : this->supported_fan_modes) { | ||||
|   if (!this->supported_fan_modes->empty()) { | ||||
|     for (const auto &it : *this->supported_fan_modes) { | ||||
|       size.add_uint32_force(1, static_cast<uint32_t>(it)); | ||||
|     } | ||||
|   } | ||||
|   if (!this->supported_swing_modes.empty()) { | ||||
|     for (const auto &it : this->supported_swing_modes) { | ||||
|   if (!this->supported_swing_modes->empty()) { | ||||
|     for (const auto &it : *this->supported_swing_modes) { | ||||
|       size.add_uint32_force(1, static_cast<uint32_t>(it)); | ||||
|     } | ||||
|   } | ||||
|   if (!this->supported_custom_fan_modes.empty()) { | ||||
|     for (const auto &it : this->supported_custom_fan_modes) { | ||||
|   if (!this->supported_custom_fan_modes->empty()) { | ||||
|     for (const auto &it : *this->supported_custom_fan_modes) { | ||||
|       size.add_length_force(1, it.size()); | ||||
|     } | ||||
|   } | ||||
|   if (!this->supported_presets.empty()) { | ||||
|     for (const auto &it : this->supported_presets) { | ||||
|   if (!this->supported_presets->empty()) { | ||||
|     for (const auto &it : *this->supported_presets) { | ||||
|       size.add_uint32_force(2, static_cast<uint32_t>(it)); | ||||
|     } | ||||
|   } | ||||
|   if (!this->supported_custom_presets.empty()) { | ||||
|     for (const auto &it : this->supported_custom_presets) { | ||||
|   if (!this->supported_custom_presets->empty()) { | ||||
|     for (const auto &it : *this->supported_custom_presets) { | ||||
|       size.add_length_force(2, it.size()); | ||||
|     } | ||||
|   } | ||||
| @@ -1371,7 +1371,7 @@ void ListEntitiesSelectResponse::encode(ProtoWriteBuffer buffer) const { | ||||
| #ifdef USE_ENTITY_ICON | ||||
|   buffer.encode_string(5, this->icon_ref_); | ||||
| #endif | ||||
|   for (auto &it : this->options) { | ||||
|   for (const auto &it : *this->options) { | ||||
|     buffer.encode_string(6, it, true); | ||||
|   } | ||||
|   buffer.encode_bool(7, this->disabled_by_default); | ||||
| @@ -1387,8 +1387,8 @@ void ListEntitiesSelectResponse::calculate_size(ProtoSize &size) const { | ||||
| #ifdef USE_ENTITY_ICON | ||||
|   size.add_length(1, this->icon_ref_.size()); | ||||
| #endif | ||||
|   if (!this->options.empty()) { | ||||
|     for (const auto &it : this->options) { | ||||
|   if (!this->options->empty()) { | ||||
|     for (const auto &it : *this->options) { | ||||
|       size.add_length_force(1, it.size()); | ||||
|     } | ||||
|   } | ||||
| @@ -2334,15 +2334,15 @@ void VoiceAssistantConfigurationResponse::encode(ProtoWriteBuffer buffer) const | ||||
|   for (auto &it : this->available_wake_words) { | ||||
|     buffer.encode_message(1, it, true); | ||||
|   } | ||||
|   for (auto &it : this->active_wake_words) { | ||||
|   for (const auto &it : *this->active_wake_words) { | ||||
|     buffer.encode_string(2, it, true); | ||||
|   } | ||||
|   buffer.encode_uint32(3, this->max_active_wake_words); | ||||
| } | ||||
| void VoiceAssistantConfigurationResponse::calculate_size(ProtoSize &size) const { | ||||
|   size.add_repeated_message(1, this->available_wake_words); | ||||
|   if (!this->active_wake_words.empty()) { | ||||
|     for (const auto &it : this->active_wake_words) { | ||||
|   if (!this->active_wake_words->empty()) { | ||||
|     for (const auto &it : *this->active_wake_words) { | ||||
|       size.add_length_force(1, it.size()); | ||||
|     } | ||||
|   } | ||||
|   | ||||
| @@ -6,6 +6,7 @@ | ||||
| #include "esphome/core/string_ref.h" | ||||
|  | ||||
| #include "proto.h" | ||||
| #include "api_pb2_includes.h" | ||||
|  | ||||
| namespace esphome::api { | ||||
|  | ||||
| @@ -703,7 +704,7 @@ class ListEntitiesFanResponse : public InfoResponseProtoMessage { | ||||
|   bool supports_speed{false}; | ||||
|   bool supports_direction{false}; | ||||
|   int32_t supported_speed_count{0}; | ||||
|   std::vector<std::string> supported_preset_modes{}; | ||||
|   const std::set<std::string> *supported_preset_modes{}; | ||||
|   void encode(ProtoWriteBuffer buffer) const override; | ||||
|   void calculate_size(ProtoSize &size) const override; | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| @@ -768,7 +769,7 @@ class ListEntitiesLightResponse : public InfoResponseProtoMessage { | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   const char *message_name() const override { return "list_entities_light_response"; } | ||||
| #endif | ||||
|   std::vector<enums::ColorMode> supported_color_modes{}; | ||||
|   const std::set<light::ColorMode> *supported_color_modes{}; | ||||
|   float min_mireds{0.0f}; | ||||
|   float max_mireds{0.0f}; | ||||
|   std::vector<std::string> effects{}; | ||||
| @@ -1319,16 +1320,16 @@ class ListEntitiesClimateResponse : public InfoResponseProtoMessage { | ||||
| #endif | ||||
|   bool supports_current_temperature{false}; | ||||
|   bool supports_two_point_target_temperature{false}; | ||||
|   std::vector<enums::ClimateMode> supported_modes{}; | ||||
|   const std::set<climate::ClimateMode> *supported_modes{}; | ||||
|   float visual_min_temperature{0.0f}; | ||||
|   float visual_max_temperature{0.0f}; | ||||
|   float visual_target_temperature_step{0.0f}; | ||||
|   bool supports_action{false}; | ||||
|   std::vector<enums::ClimateFanMode> supported_fan_modes{}; | ||||
|   std::vector<enums::ClimateSwingMode> supported_swing_modes{}; | ||||
|   std::vector<std::string> supported_custom_fan_modes{}; | ||||
|   std::vector<enums::ClimatePreset> supported_presets{}; | ||||
|   std::vector<std::string> supported_custom_presets{}; | ||||
|   const std::set<climate::ClimateFanMode> *supported_fan_modes{}; | ||||
|   const std::set<climate::ClimateSwingMode> *supported_swing_modes{}; | ||||
|   const std::set<std::string> *supported_custom_fan_modes{}; | ||||
|   const std::set<climate::ClimatePreset> *supported_presets{}; | ||||
|   const std::set<std::string> *supported_custom_presets{}; | ||||
|   float visual_current_temperature_step{0.0f}; | ||||
|   bool supports_current_humidity{false}; | ||||
|   bool supports_target_humidity{false}; | ||||
| @@ -1475,7 +1476,7 @@ class ListEntitiesSelectResponse : public InfoResponseProtoMessage { | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   const char *message_name() const override { return "list_entities_select_response"; } | ||||
| #endif | ||||
|   std::vector<std::string> options{}; | ||||
|   const std::vector<std::string> *options{}; | ||||
|   void encode(ProtoWriteBuffer buffer) const override; | ||||
|   void calculate_size(ProtoSize &size) const override; | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| @@ -2448,7 +2449,7 @@ class VoiceAssistantConfigurationResponse : public ProtoMessage { | ||||
|   const char *message_name() const override { return "voice_assistant_configuration_response"; } | ||||
| #endif | ||||
|   std::vector<VoiceAssistantWakeWord> available_wake_words{}; | ||||
|   std::vector<std::string> active_wake_words{}; | ||||
|   const std::vector<std::string> *active_wake_words{}; | ||||
|   uint32_t max_active_wake_words{0}; | ||||
|   void encode(ProtoWriteBuffer buffer) const override; | ||||
|   void calculate_size(ProtoSize &size) const override; | ||||
|   | ||||
| @@ -830,7 +830,7 @@ void ListEntitiesFanResponse::dump_to(std::string &out) const { | ||||
|   dump_field(out, "icon", this->icon_ref_); | ||||
| #endif | ||||
|   dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category)); | ||||
|   for (const auto &it : this->supported_preset_modes) { | ||||
|   for (const auto &it : *this->supported_preset_modes) { | ||||
|     dump_field(out, "supported_preset_modes", it, 4); | ||||
|   } | ||||
| #ifdef USE_DEVICES | ||||
| @@ -873,7 +873,7 @@ void ListEntitiesLightResponse::dump_to(std::string &out) const { | ||||
|   dump_field(out, "object_id", this->object_id_ref_); | ||||
|   dump_field(out, "key", this->key); | ||||
|   dump_field(out, "name", this->name_ref_); | ||||
|   for (const auto &it : this->supported_color_modes) { | ||||
|   for (const auto &it : *this->supported_color_modes) { | ||||
|     dump_field(out, "supported_color_modes", static_cast<enums::ColorMode>(it), 4); | ||||
|   } | ||||
|   dump_field(out, "min_mireds", this->min_mireds); | ||||
| @@ -1189,26 +1189,26 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const { | ||||
|   dump_field(out, "name", this->name_ref_); | ||||
|   dump_field(out, "supports_current_temperature", this->supports_current_temperature); | ||||
|   dump_field(out, "supports_two_point_target_temperature", this->supports_two_point_target_temperature); | ||||
|   for (const auto &it : this->supported_modes) { | ||||
|   for (const auto &it : *this->supported_modes) { | ||||
|     dump_field(out, "supported_modes", static_cast<enums::ClimateMode>(it), 4); | ||||
|   } | ||||
|   dump_field(out, "visual_min_temperature", this->visual_min_temperature); | ||||
|   dump_field(out, "visual_max_temperature", this->visual_max_temperature); | ||||
|   dump_field(out, "visual_target_temperature_step", this->visual_target_temperature_step); | ||||
|   dump_field(out, "supports_action", this->supports_action); | ||||
|   for (const auto &it : this->supported_fan_modes) { | ||||
|   for (const auto &it : *this->supported_fan_modes) { | ||||
|     dump_field(out, "supported_fan_modes", static_cast<enums::ClimateFanMode>(it), 4); | ||||
|   } | ||||
|   for (const auto &it : this->supported_swing_modes) { | ||||
|   for (const auto &it : *this->supported_swing_modes) { | ||||
|     dump_field(out, "supported_swing_modes", static_cast<enums::ClimateSwingMode>(it), 4); | ||||
|   } | ||||
|   for (const auto &it : this->supported_custom_fan_modes) { | ||||
|   for (const auto &it : *this->supported_custom_fan_modes) { | ||||
|     dump_field(out, "supported_custom_fan_modes", it, 4); | ||||
|   } | ||||
|   for (const auto &it : this->supported_presets) { | ||||
|   for (const auto &it : *this->supported_presets) { | ||||
|     dump_field(out, "supported_presets", static_cast<enums::ClimatePreset>(it), 4); | ||||
|   } | ||||
|   for (const auto &it : this->supported_custom_presets) { | ||||
|   for (const auto &it : *this->supported_custom_presets) { | ||||
|     dump_field(out, "supported_custom_presets", it, 4); | ||||
|   } | ||||
|   dump_field(out, "disabled_by_default", this->disabled_by_default); | ||||
| @@ -1321,7 +1321,7 @@ void ListEntitiesSelectResponse::dump_to(std::string &out) const { | ||||
| #ifdef USE_ENTITY_ICON | ||||
|   dump_field(out, "icon", this->icon_ref_); | ||||
| #endif | ||||
|   for (const auto &it : this->options) { | ||||
|   for (const auto &it : *this->options) { | ||||
|     dump_field(out, "options", it, 4); | ||||
|   } | ||||
|   dump_field(out, "disabled_by_default", this->disabled_by_default); | ||||
| @@ -1786,7 +1786,7 @@ void VoiceAssistantConfigurationResponse::dump_to(std::string &out) const { | ||||
|     it.dump_to(out); | ||||
|     out.append("\n"); | ||||
|   } | ||||
|   for (const auto &it : this->active_wake_words) { | ||||
|   for (const auto &it : *this->active_wake_words) { | ||||
|     dump_field(out, "active_wake_words", it, 4); | ||||
|   } | ||||
|   dump_field(out, "max_active_wake_words", this->max_active_wake_words); | ||||
|   | ||||
							
								
								
									
										34
									
								
								esphome/components/api/api_pb2_includes.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								esphome/components/api/api_pb2_includes.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/defines.h" | ||||
|  | ||||
| // This file provides includes needed by the generated protobuf code | ||||
| // when using pointer optimizations for component-specific types | ||||
|  | ||||
| #ifdef USE_CLIMATE | ||||
| #include "esphome/components/climate/climate_mode.h" | ||||
| #include "esphome/components/climate/climate_traits.h" | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_LIGHT | ||||
| #include "esphome/components/light/light_traits.h" | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_FAN | ||||
| #include "esphome/components/fan/fan_traits.h" | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_SELECT | ||||
| #include "esphome/components/select/select_traits.h" | ||||
| #endif | ||||
|  | ||||
| // Standard library includes that might be needed | ||||
| #include <set> | ||||
| #include <vector> | ||||
| #include <string> | ||||
|  | ||||
| namespace esphome::api { | ||||
|  | ||||
| // This file only provides includes, no actual code | ||||
|  | ||||
| }  // namespace esphome::api | ||||
| @@ -5,6 +5,13 @@ | ||||
| #include <set> | ||||
|  | ||||
| namespace esphome { | ||||
|  | ||||
| #ifdef USE_API | ||||
| namespace api { | ||||
| class APIConnection; | ||||
| }  // namespace api | ||||
| #endif | ||||
|  | ||||
| namespace climate { | ||||
|  | ||||
| /** This class contains all static data for climate devices. | ||||
| @@ -173,6 +180,23 @@ class ClimateTraits { | ||||
|   void set_visual_max_humidity(float visual_max_humidity) { this->visual_max_humidity_ = visual_max_humidity; } | ||||
|  | ||||
|  protected: | ||||
| #ifdef USE_API | ||||
|   // The API connection is a friend class to access internal methods | ||||
|   friend class api::APIConnection; | ||||
|   // These methods return references to internal data structures. | ||||
|   // They are used by the API to avoid copying data when encoding messages. | ||||
|   // Warning: Do not use these methods outside of the API connection code. | ||||
|   // They return references to internal data that can be invalidated. | ||||
|   const std::set<ClimateMode> &get_supported_modes_for_api_() const { return this->supported_modes_; } | ||||
|   const std::set<ClimateFanMode> &get_supported_fan_modes_for_api_() const { return this->supported_fan_modes_; } | ||||
|   const std::set<std::string> &get_supported_custom_fan_modes_for_api_() const { | ||||
|     return this->supported_custom_fan_modes_; | ||||
|   } | ||||
|   const std::set<climate::ClimatePreset> &get_supported_presets_for_api_() const { return this->supported_presets_; } | ||||
|   const std::set<std::string> &get_supported_custom_presets_for_api_() const { return this->supported_custom_presets_; } | ||||
|   const std::set<ClimateSwingMode> &get_supported_swing_modes_for_api_() const { return this->supported_swing_modes_; } | ||||
| #endif | ||||
|  | ||||
|   void set_mode_support_(climate::ClimateMode mode, bool supported) { | ||||
|     if (supported) { | ||||
|       this->supported_modes_.insert(mode); | ||||
|   | ||||
| @@ -4,6 +4,13 @@ | ||||
| #pragma once | ||||
|  | ||||
| namespace esphome { | ||||
|  | ||||
| #ifdef USE_API | ||||
| namespace api { | ||||
| class APIConnection; | ||||
| }  // namespace api | ||||
| #endif | ||||
|  | ||||
| namespace fan { | ||||
|  | ||||
| class FanTraits { | ||||
| @@ -36,6 +43,15 @@ class FanTraits { | ||||
|   bool supports_preset_modes() const { return !this->preset_modes_.empty(); } | ||||
|  | ||||
|  protected: | ||||
| #ifdef USE_API | ||||
|   // The API connection is a friend class to access internal methods | ||||
|   friend class api::APIConnection; | ||||
|   // This method returns a reference to the internal preset modes set. | ||||
|   // It is used by the API to avoid copying data when encoding messages. | ||||
|   // Warning: Do not use this method outside of the API connection code. | ||||
|   // It returns a reference to internal data that can be invalidated. | ||||
|   const std::set<std::string> &supported_preset_modes_for_api_() const { return this->preset_modes_; } | ||||
| #endif | ||||
|   bool oscillation_{false}; | ||||
|   bool speed_{false}; | ||||
|   bool direction_{false}; | ||||
|   | ||||
| @@ -5,6 +5,13 @@ | ||||
| #include <set> | ||||
|  | ||||
| namespace esphome { | ||||
|  | ||||
| #ifdef USE_API | ||||
| namespace api { | ||||
| class APIConnection; | ||||
| }  // namespace api | ||||
| #endif | ||||
|  | ||||
| namespace light { | ||||
|  | ||||
| /// This class is used to represent the capabilities of a light. | ||||
| @@ -52,6 +59,16 @@ class LightTraits { | ||||
|   void set_max_mireds(float max_mireds) { this->max_mireds_ = max_mireds; } | ||||
|  | ||||
|  protected: | ||||
| #ifdef USE_API | ||||
|   // The API connection is a friend class to access internal methods | ||||
|   friend class api::APIConnection; | ||||
|   // This method returns a reference to the internal color modes set. | ||||
|   // It is used by the API to avoid copying data when encoding messages. | ||||
|   // Warning: Do not use this method outside of the API connection code. | ||||
|   // It returns a reference to internal data that can be invalidated. | ||||
|   const std::set<ColorMode> &get_supported_color_modes_for_api_() const { return this->supported_color_modes_; } | ||||
| #endif | ||||
|  | ||||
|   std::set<ColorMode> supported_color_modes_{}; | ||||
|   float min_mireds_{0}; | ||||
|   float max_mireds_{0}; | ||||
|   | ||||
| @@ -5,7 +5,7 @@ namespace select { | ||||
|  | ||||
| void SelectTraits::set_options(std::vector<std::string> options) { this->options_ = std::move(options); } | ||||
|  | ||||
| std::vector<std::string> SelectTraits::get_options() const { return this->options_; } | ||||
| const std::vector<std::string> &SelectTraits::get_options() const { return this->options_; } | ||||
|  | ||||
| }  // namespace select | ||||
| }  // namespace esphome | ||||
|   | ||||
| @@ -9,7 +9,7 @@ namespace select { | ||||
| class SelectTraits { | ||||
|  public: | ||||
|   void set_options(std::vector<std::string> options); | ||||
|   std::vector<std::string> get_options() const; | ||||
|   const std::vector<std::string> &get_options() const; | ||||
|  | ||||
|  protected: | ||||
|   std::vector<std::string> options_; | ||||
|   | ||||
| @@ -1170,6 +1170,10 @@ class FixedArrayRepeatedType(TypeInfo): | ||||
| class RepeatedTypeInfo(TypeInfo): | ||||
|     def __init__(self, field: descriptor.FieldDescriptorProto) -> None: | ||||
|         super().__init__(field) | ||||
|         # Check if this is a pointer field by looking for container_pointer option | ||||
|         self._container_type = get_field_opt(field, pb.container_pointer, "") | ||||
|         self._use_pointer = bool(self._container_type) | ||||
|  | ||||
|         # For repeated fields, we need to get the base type info | ||||
|         # but we can't call create_field_type_info as it would cause recursion | ||||
|         # So we extract just the type creation logic | ||||
| @@ -1185,6 +1189,14 @@ class RepeatedTypeInfo(TypeInfo): | ||||
|  | ||||
|     @property | ||||
|     def cpp_type(self) -> str: | ||||
|         if self._use_pointer and self._container_type: | ||||
|             # For pointer fields, use the specified container type | ||||
|             # If the container type already includes the element type (e.g., std::set<climate::ClimateMode>) | ||||
|             # use it as-is, otherwise append the element type | ||||
|             if "<" in self._container_type and ">" in self._container_type: | ||||
|                 return f"const {self._container_type}*" | ||||
|             else: | ||||
|                 return f"const {self._container_type}<{self._ti.cpp_type}>*" | ||||
|         return f"std::vector<{self._ti.cpp_type}>" | ||||
|  | ||||
|     @property | ||||
| @@ -1205,6 +1217,9 @@ class RepeatedTypeInfo(TypeInfo): | ||||
|  | ||||
|     @property | ||||
|     def decode_varint_content(self) -> str: | ||||
|         # Pointer fields don't support decoding | ||||
|         if self._use_pointer: | ||||
|             return None | ||||
|         content = self._ti.decode_varint | ||||
|         if content is None: | ||||
|             return None | ||||
| @@ -1214,6 +1229,9 @@ class RepeatedTypeInfo(TypeInfo): | ||||
|  | ||||
|     @property | ||||
|     def decode_length_content(self) -> str: | ||||
|         # Pointer fields don't support decoding | ||||
|         if self._use_pointer: | ||||
|             return None | ||||
|         content = self._ti.decode_length | ||||
|         if content is None and isinstance(self._ti, MessageType): | ||||
|             # Special handling for non-template message decoding | ||||
| @@ -1226,6 +1244,9 @@ class RepeatedTypeInfo(TypeInfo): | ||||
|  | ||||
|     @property | ||||
|     def decode_32bit_content(self) -> str: | ||||
|         # Pointer fields don't support decoding | ||||
|         if self._use_pointer: | ||||
|             return None | ||||
|         content = self._ti.decode_32bit | ||||
|         if content is None: | ||||
|             return None | ||||
| @@ -1235,6 +1256,9 @@ class RepeatedTypeInfo(TypeInfo): | ||||
|  | ||||
|     @property | ||||
|     def decode_64bit_content(self) -> str: | ||||
|         # Pointer fields don't support decoding | ||||
|         if self._use_pointer: | ||||
|             return None | ||||
|         content = self._ti.decode_64bit | ||||
|         if content is None: | ||||
|             return None | ||||
| @@ -1249,16 +1273,31 @@ class RepeatedTypeInfo(TypeInfo): | ||||
|  | ||||
|     @property | ||||
|     def encode_content(self) -> str: | ||||
|         o = f"for (auto {'' if self._ti_is_bool else '&'}it : this->{self.field_name}) {{\n" | ||||
|         if isinstance(self._ti, EnumType): | ||||
|             o += f"  buffer.{self._ti.encode_func}({self.number}, static_cast<uint32_t>(it), true);\n" | ||||
|         if self._use_pointer: | ||||
|             # For pointer fields, just dereference (pointer should never be null in our use case) | ||||
|             o = f"for (const auto &it : *this->{self.field_name}) {{\n" | ||||
|             if isinstance(self._ti, EnumType): | ||||
|                 o += f"  buffer.{self._ti.encode_func}({self.number}, static_cast<uint32_t>(it), true);\n" | ||||
|             else: | ||||
|                 o += f"  buffer.{self._ti.encode_func}({self.number}, it, true);\n" | ||||
|             o += "}" | ||||
|             return o | ||||
|         else: | ||||
|             o += f"  buffer.{self._ti.encode_func}({self.number}, it, true);\n" | ||||
|         o += "}" | ||||
|         return o | ||||
|             o = f"for (auto {'' if self._ti_is_bool else '&'}it : this->{self.field_name}) {{\n" | ||||
|             if isinstance(self._ti, EnumType): | ||||
|                 o += f"  buffer.{self._ti.encode_func}({self.number}, static_cast<uint32_t>(it), true);\n" | ||||
|             else: | ||||
|                 o += f"  buffer.{self._ti.encode_func}({self.number}, it, true);\n" | ||||
|             o += "}" | ||||
|             return o | ||||
|  | ||||
|     @property | ||||
|     def dump_content(self) -> str: | ||||
|         if self._use_pointer: | ||||
|             # For pointer fields, dereference and use the existing helper | ||||
|             return _generate_array_dump_content( | ||||
|                 self._ti, f"*this->{self.field_name}", self.name, is_bool=False | ||||
|             ) | ||||
|         return _generate_array_dump_content( | ||||
|             self._ti, f"this->{self.field_name}", self.name, is_bool=self._ti_is_bool | ||||
|         ) | ||||
| @@ -1269,30 +1308,34 @@ class RepeatedTypeInfo(TypeInfo): | ||||
|     def get_size_calculation(self, name: str, force: bool = False) -> str: | ||||
|         # For repeated fields, we always need to pass force=True to the underlying type's calculation | ||||
|         # This is because the encode method always sets force=true for repeated fields | ||||
|  | ||||
|         # Handle message types separately as they use a dedicated helper | ||||
|         if isinstance(self._ti, MessageType): | ||||
|             # For repeated messages, use the dedicated helper that handles iteration internally | ||||
|             field_id_size = self._ti.calculate_field_id_size() | ||||
|             o = f"size.add_repeated_message({field_id_size}, {name});" | ||||
|             return o | ||||
|             container = f"*{name}" if self._use_pointer else name | ||||
|             return f"size.add_repeated_message({field_id_size}, {container});" | ||||
|  | ||||
|         # For other repeated types, use the underlying type's size calculation with force=True | ||||
|         o = f"if (!{name}.empty()) {{\n" | ||||
|         # For non-message types, generate size calculation with iteration | ||||
|         container_ref = f"*{name}" if self._use_pointer else name | ||||
|         empty_check = f"{name}->empty()" if self._use_pointer else f"{name}.empty()" | ||||
|  | ||||
|         # Check if this is a fixed-size type by seeing if it has a fixed byte count | ||||
|         o = f"if (!{empty_check}) {{\n" | ||||
|  | ||||
|         # Check if this is a fixed-size type | ||||
|         num_bytes = self._ti.get_fixed_size_bytes() | ||||
|         if num_bytes is not None: | ||||
|             # Fixed types have constant size per element, so we can multiply | ||||
|             # Fixed types have constant size per element | ||||
|             field_id_size = self._ti.calculate_field_id_size() | ||||
|             # Pre-calculate the total bytes per element | ||||
|             bytes_per_element = field_id_size + num_bytes | ||||
|             o += ( | ||||
|                 f"  size.add_precalculated_size({name}.size() * {bytes_per_element});\n" | ||||
|             ) | ||||
|             size_expr = f"{name}->size()" if self._use_pointer else f"{name}.size()" | ||||
|             o += f"  size.add_precalculated_size({size_expr} * {bytes_per_element});\n" | ||||
|         else: | ||||
|             # Other types need the actual value | ||||
|             o += f"  for (const auto {'' if self._ti_is_bool else '&'}it : {name}) {{\n" | ||||
|             auto_ref = "" if self._ti_is_bool else "&" | ||||
|             o += f"  for (const auto {auto_ref}it : {container_ref}) {{\n" | ||||
|             o += f"    {self._ti.get_size_calculation('it', True)}\n" | ||||
|             o += "  }\n" | ||||
|  | ||||
|         o += "}" | ||||
|         return o | ||||
|  | ||||
| @@ -2080,6 +2123,7 @@ def main() -> None: | ||||
|     d = descriptor.FileDescriptorSet.FromString(proto_content) | ||||
|  | ||||
|     file = d.file[0] | ||||
|  | ||||
|     content = FILE_HEADER | ||||
|     content += """\ | ||||
| #pragma once | ||||
| @@ -2088,7 +2132,10 @@ def main() -> None: | ||||
| #include "esphome/core/string_ref.h" | ||||
|  | ||||
| #include "proto.h" | ||||
| #include "api_pb2_includes.h" | ||||
| """ | ||||
|  | ||||
|     content += """ | ||||
| namespace esphome::api { | ||||
|  | ||||
| """ | ||||
|   | ||||
		Reference in New Issue
	
	Block a user