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 | ||||
|  | ||||
|   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; | ||||
|   EntityCategory entity_category = 8; | ||||
|   uint32 device_id = 9 [(field_ifdef) = "USE_DEVICES"]; | ||||
|   | ||||
| @@ -1475,8 +1475,8 @@ void ListEntitiesSelectResponse::encode(ProtoWriteBuffer buffer) const { | ||||
| #ifdef USE_ENTITY_ICON | ||||
|   buffer.encode_string(5, this->icon_ref_); | ||||
| #endif | ||||
|   for (const auto &it : *this->options) { | ||||
|     buffer.encode_string(6, it, true); | ||||
|   for (const char *it : *this->options) { | ||||
|     buffer.encode_string(6, it, strlen(it), true); | ||||
|   } | ||||
|   buffer.encode_bool(7, this->disabled_by_default); | ||||
|   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()); | ||||
| #endif | ||||
|   if (!this->options->empty()) { | ||||
|     for (const auto &it : *this->options) { | ||||
|       size.add_length_force(1, it.size()); | ||||
|     for (const char *it : *this->options) { | ||||
|       size.add_length_force(1, strlen(it)); | ||||
|     } | ||||
|   } | ||||
|   size.add_bool(1, this->disabled_by_default); | ||||
|   | ||||
| @@ -1534,7 +1534,7 @@ class ListEntitiesSelectResponse final : public InfoResponseProtoMessage { | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   const char *message_name() const override { return "list_entities_select_response"; } | ||||
| #endif | ||||
|   const std::vector<std::string> *options{}; | ||||
|   const FixedVector<const char *> *options{}; | ||||
|   void encode(ProtoWriteBuffer buffer) const override; | ||||
|   void calculate_size(ProtoSize &size) const override; | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   | ||||
| @@ -9,7 +9,8 @@ static const char *const TAG = "copy.select"; | ||||
| void CopySelect::setup() { | ||||
|   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()) | ||||
|     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; | ||||
|   void set_selected_text(const std::string &text, lv_anim_enable_t anim); | ||||
|   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); | ||||
|  | ||||
|  protected: | ||||
|   | ||||
| @@ -53,7 +53,17 @@ class LVGLSelect : public select::Select, public Component { | ||||
|     this->widget_->set_selected_text(value, this->anim_); | ||||
|     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_; | ||||
|   lv_anim_enable_t anim_; | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| #include "select.h" | ||||
| #include "esphome/core/log.h" | ||||
| #include <cstring> | ||||
|  | ||||
| namespace esphome { | ||||
| namespace select { | ||||
| @@ -35,7 +36,7 @@ size_t Select::size() const { | ||||
| optional<size_t> Select::index_of(const std::string &option) const { | ||||
|   const auto &options = traits.get_options(); | ||||
|   for (size_t i = 0; i < options.size(); i++) { | ||||
|     if (options[i] == option) { | ||||
|     if (strcmp(options[i], option.c_str()) == 0) { | ||||
|       return i; | ||||
|     } | ||||
|   } | ||||
| @@ -53,7 +54,7 @@ optional<size_t> Select::active_index() const { | ||||
| optional<std::string> Select::at(size_t index) const { | ||||
|   if (this->has_index(index)) { | ||||
|     const auto &options = traits.get_options(); | ||||
|     return options.at(index); | ||||
|     return std::string(options.at(index)); | ||||
|   } else { | ||||
|     return {}; | ||||
|   } | ||||
|   | ||||
| @@ -3,9 +3,9 @@ | ||||
| namespace esphome { | ||||
| 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 esphome | ||||
|   | ||||
| @@ -1,18 +1,18 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include <vector> | ||||
| #include <string> | ||||
| #include "esphome/core/helpers.h" | ||||
| #include <initializer_list> | ||||
|  | ||||
| namespace esphome { | ||||
| namespace select { | ||||
|  | ||||
| class SelectTraits { | ||||
|  public: | ||||
|   void set_options(std::vector<std::string> options); | ||||
|   const std::vector<std::string> &get_options() const; | ||||
|   void set_options(std::initializer_list<const char *> options); | ||||
|   const FixedVector<const char *> &get_options() const; | ||||
|  | ||||
|  protected: | ||||
|   std::vector<std::string> options_; | ||||
|   FixedVector<const char *> options_; | ||||
| }; | ||||
|  | ||||
| }  // namespace select | ||||
|   | ||||
| @@ -22,7 +22,7 @@ void TemplateSelect::setup() { | ||||
|       ESP_LOGD(TAG, "State from initial (could not load stored index): %s", value.c_str()); | ||||
|     } else if (!this->has_index(index)) { | ||||
|       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 { | ||||
|       value = this->at(index).value(); | ||||
|       ESP_LOGD(TAG, "State from restore: %s", value.c_str()); | ||||
|   | ||||
| @@ -304,6 +304,11 @@ template<typename T> class FixedVector { | ||||
|     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) | ||||
|   /// Caller must ensure vector is not empty (size() > 0) | ||||
|   T &back() { return data_[size_ - 1]; } | ||||
| @@ -317,6 +322,11 @@ template<typename T> class FixedVector { | ||||
|   T &operator[](size_t i) { 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 | ||||
|   T *begin() { return data_; } | ||||
|   T *end() { return data_ + size_; } | ||||
|   | ||||
| @@ -1533,6 +1533,11 @@ class RepeatedTypeInfo(TypeInfo): | ||||
|     def encode_content(self) -> str: | ||||
|         if self._use_pointer: | ||||
|             # For pointer fields, just dereference (pointer should never be null in our use case) | ||||
|             # Special handling for const char* elements (when container_no_template contains "const char") | ||||
|             if "const char" in self._container_no_template: | ||||
|                 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: | ||||
|                 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" | ||||
| @@ -1588,6 +1593,11 @@ class RepeatedTypeInfo(TypeInfo): | ||||
|             o += f"  size.add_precalculated_size({size_expr} * {bytes_per_element});\n" | ||||
|         else: | ||||
|             # Other types need the actual value | ||||
|             # Special handling for const char* elements | ||||
|             if self._use_pointer and "const char" in self._container_no_template: | ||||
|                 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" | ||||
|   | ||||
		Reference in New Issue
	
	Block a user