mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 15:12:06 +00:00 
			
		
		
		
	Merge branch 'select_options_in_flash' into integration
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 | ||||
|   | ||||
| @@ -88,6 +88,12 @@ static void dump_field(std::string &out, const char *field_name, StringRef value | ||||
|   out.append("\n"); | ||||
| } | ||||
|  | ||||
| static void dump_field(std::string &out, const char *field_name, const char *value, int indent = 2) { | ||||
|   append_field_prefix(out, field_name, indent); | ||||
|   out.append("'").append(value).append("'"); | ||||
|   out.append("\n"); | ||||
| } | ||||
|  | ||||
| template<typename T> static void dump_field(std::string &out, const char *field_name, T value, int indent = 2) { | ||||
|   append_field_prefix(out, field_name, indent); | ||||
|   out.append(proto_enum_to_string<T>(value)); | ||||
|   | ||||
| @@ -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(); | ||||
|     FixedVector<const char *> opt_ptrs; | ||||
|     opt_ptrs.init(opts.size()); | ||||
|     for (size_t i = 0; i < opts.size(); i++) { | ||||
|       opt_ptrs[i] = opts[i].c_str(); | ||||
|     } | ||||
|     this->traits.set_options(opt_ptrs); | ||||
|   } | ||||
|  | ||||
|   LvSelectable *widget_; | ||||
|   lv_anim_enable_t anim_; | ||||
|   | ||||
| @@ -28,7 +28,7 @@ void ModbusSelect::parse_and_publish(const std::vector<uint8_t> &data) { | ||||
|  | ||||
|     if (map_it != this->mapping_.cend()) { | ||||
|       size_t idx = std::distance(this->mapping_.cbegin(), map_it); | ||||
|       new_state = this->traits.get_options()[idx]; | ||||
|       new_state = std::string(this->traits.get_options()[idx]); | ||||
|       ESP_LOGV(TAG, "Found option %s for value %lld", new_state->c_str(), value); | ||||
|     } else { | ||||
|       ESP_LOGE(TAG, "No option found for mapping %lld", value); | ||||
| @@ -41,10 +41,12 @@ void ModbusSelect::parse_and_publish(const std::vector<uint8_t> &data) { | ||||
| } | ||||
|  | ||||
| void ModbusSelect::control(const std::string &value) { | ||||
|   auto options = this->traits.get_options(); | ||||
|   auto opt_it = std::find(options.cbegin(), options.cend(), value); | ||||
|   size_t idx = std::distance(options.cbegin(), opt_it); | ||||
|   optional<int64_t> mapval = this->mapping_[idx]; | ||||
|   auto idx = this->index_of(value); | ||||
|   if (!idx.has_value()) { | ||||
|     ESP_LOGW(TAG, "Invalid option '%s'", value.c_str()); | ||||
|     return; | ||||
|   } | ||||
|   optional<int64_t> mapval = this->mapping_[idx.value()]; | ||||
|   ESP_LOGD(TAG, "Found value %lld for option '%s'", *mapval, value.c_str()); | ||||
|  | ||||
|   std::vector<uint16_t> data; | ||||
|   | ||||
| @@ -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,16 @@ | ||||
| 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_; } | ||||
| void SelectTraits::set_options(const FixedVector<const char *> &options) { | ||||
|   this->options_.init(options.size()); | ||||
|   for (size_t i = 0; i < options.size(); i++) { | ||||
|     this->options_[i] = options[i]; | ||||
|   } | ||||
| } | ||||
|  | ||||
| const FixedVector<const char *> &SelectTraits::get_options() const { return this->options_; } | ||||
|  | ||||
| }  // namespace select | ||||
| }  // namespace esphome | ||||
|   | ||||
| @@ -1,18 +1,19 @@ | ||||
| #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); | ||||
|   void set_options(const FixedVector<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()); | ||||
|   | ||||
| @@ -10,7 +10,7 @@ void TuyaSelect::setup() { | ||||
|   this->parent_->register_listener(this->select_id_, [this](const TuyaDatapoint &datapoint) { | ||||
|     uint8_t enum_value = datapoint.value_enum; | ||||
|     ESP_LOGV(TAG, "MCU reported select %u value %u", this->select_id_, enum_value); | ||||
|     auto options = this->traits.get_options(); | ||||
|     const auto &options = this->traits.get_options(); | ||||
|     auto mappings = this->mappings_; | ||||
|     auto it = std::find(mappings.cbegin(), mappings.cend(), enum_value); | ||||
|     if (it == mappings.end()) { | ||||
| @@ -49,9 +49,9 @@ void TuyaSelect::dump_config() { | ||||
|                 "  Data type: %s\n" | ||||
|                 "  Options are:", | ||||
|                 this->select_id_, this->is_int_ ? "int" : "enum"); | ||||
|   auto options = this->traits.get_options(); | ||||
|   const auto &options = this->traits.get_options(); | ||||
|   for (size_t i = 0; i < this->mappings_.size(); i++) { | ||||
|     ESP_LOGCONFIG(TAG, "    %i: %s", this->mappings_.at(i), options.at(i).c_str()); | ||||
|     ESP_LOGCONFIG(TAG, "    %i: %s", this->mappings_.at(i), options.at(i)); | ||||
|   } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -307,6 +307,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]; } | ||||
| @@ -320,6 +325,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) | ||||
|   /// Note: No exception thrown on out of bounds - caller must ensure index is valid | ||||
|   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_; } | ||||
|   | ||||
| @@ -1162,7 +1162,11 @@ class SInt64Type(TypeInfo): | ||||
|  | ||||
|  | ||||
| def _generate_array_dump_content( | ||||
|     ti, field_name: str, name: str, is_bool: bool = False | ||||
|     ti, | ||||
|     field_name: str, | ||||
|     name: str, | ||||
|     is_bool: bool = False, | ||||
|     is_const_char_ptr: bool = False, | ||||
| ) -> str: | ||||
|     """Generate dump content for array types (repeated or fixed array). | ||||
|  | ||||
| @@ -1170,7 +1174,10 @@ def _generate_array_dump_content( | ||||
|     """ | ||||
|     o = f"for (const auto {'' if is_bool else '&'}it : {field_name}) {{\n" | ||||
|     # Check if underlying type can use dump_field | ||||
|     if ti.can_use_dump_field(): | ||||
|     if is_const_char_ptr: | ||||
|         # Special case for const char* - use it directly | ||||
|         o += f'  dump_field(out, "{name}", it, 4);\n' | ||||
|     elif ti.can_use_dump_field(): | ||||
|         # For types that have dump_field overloads, use them with extra indent | ||||
|         # std::vector<bool> iterators return proxy objects, need explicit cast | ||||
|         value_expr = "static_cast<bool>(it)" if is_bool else ti.dump_field_value("it") | ||||
| @@ -1533,11 +1540,16 @@ 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) | ||||
|             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" | ||||
|             # 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"  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 += "}" | ||||
|             return o | ||||
|         o = f"for (auto {'' if self._ti_is_bool else '&'}it : this->{self.field_name}) {{\n" | ||||
| @@ -1550,10 +1562,18 @@ class RepeatedTypeInfo(TypeInfo): | ||||
|  | ||||
|     @property | ||||
|     def dump_content(self) -> str: | ||||
|         # Check if this is const char* elements | ||||
|         is_const_char_ptr = ( | ||||
|             self._use_pointer and "const char" in self._container_no_template | ||||
|         ) | ||||
|         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 | ||||
|                 self._ti, | ||||
|                 f"*this->{self.field_name}", | ||||
|                 self.name, | ||||
|                 is_bool=False, | ||||
|                 is_const_char_ptr=is_const_char_ptr, | ||||
|             ) | ||||
|         return _generate_array_dump_content( | ||||
|             self._ti, f"this->{self.field_name}", self.name, is_bool=self._ti_is_bool | ||||
| @@ -1588,9 +1608,14 @@ class RepeatedTypeInfo(TypeInfo): | ||||
|             o += f"  size.add_precalculated_size({size_expr} * {bytes_per_element});\n" | ||||
|         else: | ||||
|             # Other types need the actual value | ||||
|             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" | ||||
|             # 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" | ||||
|             o += "  }\n" | ||||
|  | ||||
|         o += "}" | ||||
| @@ -2542,6 +2567,12 @@ static void dump_field(std::string &out, const char *field_name, StringRef value | ||||
|   out.append("\\n"); | ||||
| } | ||||
|  | ||||
| static void dump_field(std::string &out, const char *field_name, const char *value, int indent = 2) { | ||||
|   append_field_prefix(out, field_name, indent); | ||||
|   out.append("'").append(value).append("'"); | ||||
|   out.append("\\n"); | ||||
| } | ||||
|  | ||||
| template<typename T> | ||||
| static void dump_field(std::string &out, const char *field_name, T value, int indent = 2) { | ||||
|   append_field_prefix(out, field_name, indent); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user