diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index d202486cfa..104da3037d 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -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) = "FixedVector"]; bool disabled_by_default = 7; EntityCategory entity_category = 8; uint32 device_id = 9 [(field_ifdef) = "USE_DEVICES"]; diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index ed49498176..7213a56a09 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -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 *options{}; + const FixedVector *options{}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP diff --git a/esphome/components/copy/select/copy_select.cpp b/esphome/components/copy/select/copy_select.cpp index bdcbd0b42c..af180c0bcb 100644 --- a/esphome/components/copy/select/copy_select.cpp +++ b/esphome/components/copy/select/copy_select.cpp @@ -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()); + // Copy options from source select + this->traits.copy_options(source_->traits.get_options()); if (source_->has_state()) this->publish_state(source_->state); diff --git a/esphome/components/lvgl/lvgl_esphome.cpp b/esphome/components/lvgl/lvgl_esphome.cpp index 7a32691b53..94766691b8 100644 --- a/esphome/components/lvgl/lvgl_esphome.cpp +++ b/esphome/components/lvgl/lvgl_esphome.cpp @@ -300,11 +300,11 @@ void LvSelectable::set_selected_text(const std::string &text, lv_anim_enable_t a } } -void LvSelectable::set_options(std::vector options) { +void LvSelectable::set_options(std::initializer_list options) { auto index = this->get_selected_index(); if (index >= options.size()) index = options.size() - 1; - this->options_ = std::move(options); + this->options_ = options; this->set_option_string(join_string(this->options_).c_str()); lv_event_send(this->obj, LV_EVENT_REFRESH, nullptr); this->set_selected_index(index, LV_ANIM_OFF); diff --git a/esphome/components/lvgl/lvgl_esphome.h b/esphome/components/lvgl/lvgl_esphome.h index 3ae67e8a0b..d18d954400 100644 --- a/esphome/components/lvgl/lvgl_esphome.h +++ b/esphome/components/lvgl/lvgl_esphome.h @@ -358,12 +358,12 @@ 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 get_options() { return this->options_; } - void set_options(std::vector options); + const FixedVector &get_options() { return this->options_; } + void set_options(std::initializer_list options); protected: virtual void set_option_string(const char *options) = 0; - std::vector options_{}; + FixedVector options_{}; }; #ifdef USE_LVGL_DROPDOWN diff --git a/esphome/components/lvgl/select/lvgl_select.h b/esphome/components/lvgl/select/lvgl_select.h index a0e60295a6..69bad7a12b 100644 --- a/esphome/components/lvgl/select/lvgl_select.h +++ b/esphome/components/lvgl/select/lvgl_select.h @@ -53,7 +53,10 @@ 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_() { + // Copy options from lvgl widget to select traits + this->traits.copy_options(this->widget_->get_options()); + } LvSelectable *widget_; lv_anim_enable_t anim_; diff --git a/esphome/components/select/select_traits.cpp b/esphome/components/select/select_traits.cpp index a8cd4290c8..497bffbe01 100644 --- a/esphome/components/select/select_traits.cpp +++ b/esphome/components/select/select_traits.cpp @@ -3,9 +3,9 @@ namespace esphome { namespace select { -void SelectTraits::set_options(std::vector options) { this->options_ = std::move(options); } +void SelectTraits::set_options(std::initializer_list options) { this->options_ = options; } -const std::vector &SelectTraits::get_options() const { return this->options_; } +const FixedVector &SelectTraits::get_options() const { return this->options_; } } // namespace select } // namespace esphome diff --git a/esphome/components/select/select_traits.h b/esphome/components/select/select_traits.h index 128066dd6b..826ef14924 100644 --- a/esphome/components/select/select_traits.h +++ b/esphome/components/select/select_traits.h @@ -1,18 +1,21 @@ #pragma once -#include #include +#include +#include "esphome/core/helpers.h" namespace esphome { namespace select { class SelectTraits { public: - void set_options(std::vector options); - const std::vector &get_options() const; + void set_options(std::initializer_list options); + const FixedVector &get_options() const; + /// Copy options from another SelectTraits (for copy_select, lvgl) + void copy_options(const FixedVector &other) { this->options_.copy_from(other); } protected: - std::vector options_; + FixedVector options_; }; } // namespace select diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 9b0591c9c5..5329ca4266 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -246,6 +246,17 @@ template class FixedVector { return *this; } + /// Copy another FixedVector (explicit copy for special cases like copy_select) + /// Creates exact duplicate of source vector + void copy_from(const FixedVector &other) { + cleanup_(); + reset_(); + init(other.size()); + for (const auto &item : other) { + push_back(item); + } + } + // Allocate capacity - can be called multiple times to reinit void init(size_t n) { cleanup_(); @@ -304,6 +315,11 @@ template 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 +333,12 @@ template 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) + /// Returns reference to element at index i + /// Behavior for out of bounds access matches std::vector::at() (undefined on embedded) + 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_; }