mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 23:21:54 +00:00 
			
		
		
		
	[select] Store options in flash to reduce RAM usage
This commit is contained in:
		| @@ -1143,7 +1143,7 @@ message ListEntitiesSelectResponse { | |||||||
|   reserved 4; // Deprecated: was string unique_id |   reserved 4; // Deprecated: was string unique_id | ||||||
|  |  | ||||||
|   string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON"]; |   string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON"]; | ||||||
|   repeated string options = 6 [(container_pointer) = "std::vector"]; |   repeated string options = 6 [(container_pointer_no_template) = "FixedVector<const char *>"]; | ||||||
|   bool disabled_by_default = 7; |   bool disabled_by_default = 7; | ||||||
|   EntityCategory entity_category = 8; |   EntityCategory entity_category = 8; | ||||||
|   uint32 device_id = 9 [(field_ifdef) = "USE_DEVICES"]; |   uint32 device_id = 9 [(field_ifdef) = "USE_DEVICES"]; | ||||||
|   | |||||||
| @@ -1475,8 +1475,8 @@ void ListEntitiesSelectResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
| #ifdef USE_ENTITY_ICON | #ifdef USE_ENTITY_ICON | ||||||
|   buffer.encode_string(5, this->icon_ref_); |   buffer.encode_string(5, this->icon_ref_); | ||||||
| #endif | #endif | ||||||
|   for (const auto &it : *this->options) { |   for (const char *it : *this->options) { | ||||||
|     buffer.encode_string(6, it, true); |     buffer.encode_string(6, it, strlen(it), true); | ||||||
|   } |   } | ||||||
|   buffer.encode_bool(7, this->disabled_by_default); |   buffer.encode_bool(7, this->disabled_by_default); | ||||||
|   buffer.encode_uint32(8, static_cast<uint32_t>(this->entity_category)); |   buffer.encode_uint32(8, static_cast<uint32_t>(this->entity_category)); | ||||||
| @@ -1492,8 +1492,8 @@ void ListEntitiesSelectResponse::calculate_size(ProtoSize &size) const { | |||||||
|   size.add_length(1, this->icon_ref_.size()); |   size.add_length(1, this->icon_ref_.size()); | ||||||
| #endif | #endif | ||||||
|   if (!this->options->empty()) { |   if (!this->options->empty()) { | ||||||
|     for (const auto &it : *this->options) { |     for (const char *it : *this->options) { | ||||||
|       size.add_length_force(1, it.size()); |       size.add_length_force(1, strlen(it)); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|   size.add_bool(1, this->disabled_by_default); |   size.add_bool(1, this->disabled_by_default); | ||||||
|   | |||||||
| @@ -1534,7 +1534,7 @@ class ListEntitiesSelectResponse final : public InfoResponseProtoMessage { | |||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   const char *message_name() const override { return "list_entities_select_response"; } |   const char *message_name() const override { return "list_entities_select_response"; } | ||||||
| #endif | #endif | ||||||
|   const std::vector<std::string> *options{}; |   const FixedVector<const char *> *options{}; | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
|   void calculate_size(ProtoSize &size) const override; |   void calculate_size(ProtoSize &size) const override; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   | |||||||
| @@ -9,7 +9,8 @@ static const char *const TAG = "copy.select"; | |||||||
| void CopySelect::setup() { | void CopySelect::setup() { | ||||||
|   source_->add_on_state_callback([this](const std::string &value, size_t index) { this->publish_state(value); }); |   source_->add_on_state_callback([this](const std::string &value, size_t index) { this->publish_state(value); }); | ||||||
|  |  | ||||||
|   traits.set_options(source_->traits.get_options()); |   const auto &source_options = source_->traits.get_options(); | ||||||
|  |   traits.set_options({source_options.begin(), source_options.end()}); | ||||||
|  |  | ||||||
|   if (source_->has_state()) |   if (source_->has_state()) | ||||||
|     this->publish_state(source_->state); |     this->publish_state(source_->state); | ||||||
|   | |||||||
| @@ -358,7 +358,7 @@ class LvSelectable : public LvCompound { | |||||||
|   virtual void set_selected_index(size_t index, lv_anim_enable_t anim) = 0; |   virtual void set_selected_index(size_t index, lv_anim_enable_t anim) = 0; | ||||||
|   void set_selected_text(const std::string &text, lv_anim_enable_t anim); |   void set_selected_text(const std::string &text, lv_anim_enable_t anim); | ||||||
|   std::string get_selected_text(); |   std::string get_selected_text(); | ||||||
|   std::vector<std::string> get_options() { return this->options_; } |   const std::vector<std::string> &get_options() { return this->options_; } | ||||||
|   void set_options(std::vector<std::string> options); |   void set_options(std::vector<std::string> options); | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   | |||||||
| @@ -53,7 +53,17 @@ class LVGLSelect : public select::Select, public Component { | |||||||
|     this->widget_->set_selected_text(value, this->anim_); |     this->widget_->set_selected_text(value, this->anim_); | ||||||
|     this->publish(); |     this->publish(); | ||||||
|   } |   } | ||||||
|   void set_options_() { this->traits.set_options(this->widget_->get_options()); } |   void set_options_() { | ||||||
|  |     // Widget uses std::vector<std::string>, SelectTraits uses FixedVector<const char*> | ||||||
|  |     // Convert by extracting c_str() pointers | ||||||
|  |     const auto &opts = this->widget_->get_options(); | ||||||
|  |     std::vector<const char *> opt_ptrs; | ||||||
|  |     opt_ptrs.reserve(opts.size()); | ||||||
|  |     for (const auto &opt : opts) { | ||||||
|  |       opt_ptrs.push_back(opt.c_str()); | ||||||
|  |     } | ||||||
|  |     this->traits.set_options({opt_ptrs.begin(), opt_ptrs.end()}); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   LvSelectable *widget_; |   LvSelectable *widget_; | ||||||
|   lv_anim_enable_t anim_; |   lv_anim_enable_t anim_; | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| #include "select.h" | #include "select.h" | ||||||
| #include "esphome/core/log.h" | #include "esphome/core/log.h" | ||||||
|  | #include <cstring> | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace select { | namespace select { | ||||||
| @@ -35,7 +36,7 @@ size_t Select::size() const { | |||||||
| optional<size_t> Select::index_of(const std::string &option) const { | optional<size_t> Select::index_of(const std::string &option) const { | ||||||
|   const auto &options = traits.get_options(); |   const auto &options = traits.get_options(); | ||||||
|   for (size_t i = 0; i < options.size(); i++) { |   for (size_t i = 0; i < options.size(); i++) { | ||||||
|     if (options[i] == option) { |     if (strcmp(options[i], option.c_str()) == 0) { | ||||||
|       return i; |       return i; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| @@ -53,7 +54,7 @@ optional<size_t> Select::active_index() const { | |||||||
| optional<std::string> Select::at(size_t index) const { | optional<std::string> Select::at(size_t index) const { | ||||||
|   if (this->has_index(index)) { |   if (this->has_index(index)) { | ||||||
|     const auto &options = traits.get_options(); |     const auto &options = traits.get_options(); | ||||||
|     return options.at(index); |     return std::string(options.at(index)); | ||||||
|   } else { |   } else { | ||||||
|     return {}; |     return {}; | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -3,9 +3,9 @@ | |||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace select { | namespace select { | ||||||
|  |  | ||||||
| void SelectTraits::set_options(std::vector<std::string> options) { this->options_ = std::move(options); } | void SelectTraits::set_options(std::initializer_list<const char *> options) { this->options_ = options; } | ||||||
|  |  | ||||||
| const std::vector<std::string> &SelectTraits::get_options() const { return this->options_; } | const FixedVector<const char *> &SelectTraits::get_options() const { return this->options_; } | ||||||
|  |  | ||||||
| }  // namespace select | }  // namespace select | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|   | |||||||
| @@ -1,18 +1,18 @@ | |||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
| #include <vector> | #include "esphome/core/helpers.h" | ||||||
| #include <string> | #include <initializer_list> | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace select { | namespace select { | ||||||
|  |  | ||||||
| class SelectTraits { | class SelectTraits { | ||||||
|  public: |  public: | ||||||
|   void set_options(std::vector<std::string> options); |   void set_options(std::initializer_list<const char *> options); | ||||||
|   const std::vector<std::string> &get_options() const; |   const FixedVector<const char *> &get_options() const; | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   std::vector<std::string> options_; |   FixedVector<const char *> options_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }  // namespace select | }  // namespace select | ||||||
|   | |||||||
| @@ -22,7 +22,7 @@ void TemplateSelect::setup() { | |||||||
|       ESP_LOGD(TAG, "State from initial (could not load stored index): %s", value.c_str()); |       ESP_LOGD(TAG, "State from initial (could not load stored index): %s", value.c_str()); | ||||||
|     } else if (!this->has_index(index)) { |     } else if (!this->has_index(index)) { | ||||||
|       value = this->initial_option_; |       value = this->initial_option_; | ||||||
|       ESP_LOGD(TAG, "State from initial (restored index %d out of bounds): %s", index, value.c_str()); |       ESP_LOGD(TAG, "State from initial (restored index %zu out of bounds): %s", index, value.c_str()); | ||||||
|     } else { |     } else { | ||||||
|       value = this->at(index).value(); |       value = this->at(index).value(); | ||||||
|       ESP_LOGD(TAG, "State from restore: %s", value.c_str()); |       ESP_LOGD(TAG, "State from restore: %s", value.c_str()); | ||||||
|   | |||||||
| @@ -304,6 +304,11 @@ template<typename T> class FixedVector { | |||||||
|     return data_[size_ - 1]; |     return data_[size_ - 1]; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   /// Access first element (no bounds checking - matches std::vector behavior) | ||||||
|  |   /// Caller must ensure vector is not empty (size() > 0) | ||||||
|  |   T &front() { return data_[0]; } | ||||||
|  |   const T &front() const { return data_[0]; } | ||||||
|  |  | ||||||
|   /// Access last element (no bounds checking - matches std::vector behavior) |   /// Access last element (no bounds checking - matches std::vector behavior) | ||||||
|   /// Caller must ensure vector is not empty (size() > 0) |   /// Caller must ensure vector is not empty (size() > 0) | ||||||
|   T &back() { return data_[size_ - 1]; } |   T &back() { return data_[size_ - 1]; } | ||||||
| @@ -317,6 +322,11 @@ template<typename T> class FixedVector { | |||||||
|   T &operator[](size_t i) { return data_[i]; } |   T &operator[](size_t i) { return data_[i]; } | ||||||
|   const T &operator[](size_t i) const { return data_[i]; } |   const T &operator[](size_t i) const { return data_[i]; } | ||||||
|  |  | ||||||
|  |   /// Access element with bounds checking (matches std::vector behavior) | ||||||
|  |   /// Caller must ensure index is valid (i < size()) | ||||||
|  |   T &at(size_t i) { return data_[i]; } | ||||||
|  |   const T &at(size_t i) const { return data_[i]; } | ||||||
|  |  | ||||||
|   // Iterator support for range-based for loops |   // Iterator support for range-based for loops | ||||||
|   T *begin() { return data_; } |   T *begin() { return data_; } | ||||||
|   T *end() { return data_ + size_; } |   T *end() { return data_ + size_; } | ||||||
|   | |||||||
| @@ -1533,11 +1533,16 @@ class RepeatedTypeInfo(TypeInfo): | |||||||
|     def encode_content(self) -> str: |     def encode_content(self) -> str: | ||||||
|         if self._use_pointer: |         if self._use_pointer: | ||||||
|             # For pointer fields, just dereference (pointer should never be null in our use case) |             # 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" |             # Special handling for const char* elements (when container_no_template contains "const char") | ||||||
|             if isinstance(self._ti, EnumType): |             if "const char" in self._container_no_template: | ||||||
|                 o += f"  buffer.{self._ti.encode_func}({self.number}, static_cast<uint32_t>(it), true);\n" |                 o = f"for (const char *it : *this->{self.field_name}) {{\n" | ||||||
|  |                 o += f"  buffer.{self._ti.encode_func}({self.number}, it, strlen(it), true);\n" | ||||||
|             else: |             else: | ||||||
|                 o += f"  buffer.{self._ti.encode_func}({self.number}, it, true);\n" |                 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 += "}" |             o += "}" | ||||||
|             return o |             return o | ||||||
|         o = f"for (auto {'' if self._ti_is_bool else '&'}it : this->{self.field_name}) {{\n" |         o = f"for (auto {'' if self._ti_is_bool else '&'}it : this->{self.field_name}) {{\n" | ||||||
| @@ -1588,9 +1593,14 @@ class RepeatedTypeInfo(TypeInfo): | |||||||
|             o += f"  size.add_precalculated_size({size_expr} * {bytes_per_element});\n" |             o += f"  size.add_precalculated_size({size_expr} * {bytes_per_element});\n" | ||||||
|         else: |         else: | ||||||
|             # Other types need the actual value |             # Other types need the actual value | ||||||
|             auto_ref = "" if self._ti_is_bool else "&" |             # Special handling for const char* elements | ||||||
|             o += f"  for (const auto {auto_ref}it : {container_ref}) {{\n" |             if self._use_pointer and "const char" in self._container_no_template: | ||||||
|             o += f"    {self._ti.get_size_calculation('it', True)}\n" |                 o += f"  for (const char *it : {container_ref}) {{\n" | ||||||
|  |                 o += "    size.add_length_force(1, strlen(it));\n" | ||||||
|  |             else: | ||||||
|  |                 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 += "  }\n" | ||||||
|  |  | ||||||
|         o += "}" |         o += "}" | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user