mirror of
https://github.com/esphome/esphome.git
synced 2025-11-01 15:41:52 +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
|
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
|
||||||
|
|||||||
@@ -88,6 +88,12 @@ static void dump_field(std::string &out, const char *field_name, StringRef value
|
|||||||
out.append("\n");
|
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) {
|
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);
|
append_field_prefix(out, field_name, indent);
|
||||||
out.append(proto_enum_to_string<T>(value));
|
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;
|
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();
|
||||||
|
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_;
|
LvSelectable *widget_;
|
||||||
lv_anim_enable_t anim_;
|
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()) {
|
if (map_it != this->mapping_.cend()) {
|
||||||
size_t idx = std::distance(this->mapping_.cbegin(), map_it);
|
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);
|
ESP_LOGV(TAG, "Found option %s for value %lld", new_state->c_str(), value);
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGE(TAG, "No option found for mapping %lld", value);
|
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) {
|
void ModbusSelect::control(const std::string &value) {
|
||||||
auto options = this->traits.get_options();
|
auto idx = this->index_of(value);
|
||||||
auto opt_it = std::find(options.cbegin(), options.cend(), value);
|
if (!idx.has_value()) {
|
||||||
size_t idx = std::distance(options.cbegin(), opt_it);
|
ESP_LOGW(TAG, "Invalid option '%s'", value.c_str());
|
||||||
optional<int64_t> mapval = this->mapping_[idx];
|
return;
|
||||||
|
}
|
||||||
|
optional<int64_t> mapval = this->mapping_[idx.value()];
|
||||||
ESP_LOGD(TAG, "Found value %lld for option '%s'", *mapval, value.c_str());
|
ESP_LOGD(TAG, "Found value %lld for option '%s'", *mapval, value.c_str());
|
||||||
|
|
||||||
std::vector<uint16_t> data;
|
std::vector<uint16_t> data;
|
||||||
|
|||||||
@@ -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,16 @@
|
|||||||
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_; }
|
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 select
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|||||||
@@ -1,18 +1,19 @@
|
|||||||
#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;
|
void set_options(const FixedVector<const char *> &options);
|
||||||
|
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());
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ void TuyaSelect::setup() {
|
|||||||
this->parent_->register_listener(this->select_id_, [this](const TuyaDatapoint &datapoint) {
|
this->parent_->register_listener(this->select_id_, [this](const TuyaDatapoint &datapoint) {
|
||||||
uint8_t enum_value = datapoint.value_enum;
|
uint8_t enum_value = datapoint.value_enum;
|
||||||
ESP_LOGV(TAG, "MCU reported select %u value %u", this->select_id_, enum_value);
|
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 mappings = this->mappings_;
|
||||||
auto it = std::find(mappings.cbegin(), mappings.cend(), enum_value);
|
auto it = std::find(mappings.cbegin(), mappings.cend(), enum_value);
|
||||||
if (it == mappings.end()) {
|
if (it == mappings.end()) {
|
||||||
@@ -49,9 +49,9 @@ void TuyaSelect::dump_config() {
|
|||||||
" Data type: %s\n"
|
" Data type: %s\n"
|
||||||
" Options are:",
|
" Options are:",
|
||||||
this->select_id_, this->is_int_ ? "int" : "enum");
|
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++) {
|
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];
|
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]; }
|
||||||
@@ -320,6 +325,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)
|
||||||
|
/// 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
|
// 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_; }
|
||||||
|
|||||||
@@ -1162,7 +1162,11 @@ class SInt64Type(TypeInfo):
|
|||||||
|
|
||||||
|
|
||||||
def _generate_array_dump_content(
|
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:
|
) -> str:
|
||||||
"""Generate dump content for array types (repeated or fixed array).
|
"""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"
|
o = f"for (const auto {'' if is_bool else '&'}it : {field_name}) {{\n"
|
||||||
# Check if underlying type can use dump_field
|
# 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
|
# For types that have dump_field overloads, use them with extra indent
|
||||||
# std::vector<bool> iterators return proxy objects, need explicit cast
|
# 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")
|
value_expr = "static_cast<bool>(it)" if is_bool else ti.dump_field_value("it")
|
||||||
@@ -1533,6 +1540,11 @@ 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)
|
||||||
|
# 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"
|
o = f"for (const auto &it : *this->{self.field_name}) {{\n"
|
||||||
if isinstance(self._ti, EnumType):
|
if isinstance(self._ti, EnumType):
|
||||||
o += f" buffer.{self._ti.encode_func}({self.number}, static_cast<uint32_t>(it), true);\n"
|
o += f" buffer.{self._ti.encode_func}({self.number}, static_cast<uint32_t>(it), true);\n"
|
||||||
@@ -1550,10 +1562,18 @@ class RepeatedTypeInfo(TypeInfo):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def dump_content(self) -> str:
|
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:
|
if self._use_pointer:
|
||||||
# For pointer fields, dereference and use the existing helper
|
# For pointer fields, dereference and use the existing helper
|
||||||
return _generate_array_dump_content(
|
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(
|
return _generate_array_dump_content(
|
||||||
self._ti, f"this->{self.field_name}", self.name, is_bool=self._ti_is_bool
|
self._ti, f"this->{self.field_name}", self.name, is_bool=self._ti_is_bool
|
||||||
@@ -1588,6 +1608,11 @@ 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
|
||||||
|
# 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 "&"
|
auto_ref = "" if self._ti_is_bool else "&"
|
||||||
o += f" for (const auto {auto_ref}it : {container_ref}) {{\n"
|
o += f" for (const auto {auto_ref}it : {container_ref}) {{\n"
|
||||||
o += f" {self._ti.get_size_calculation('it', True)}\n"
|
o += f" {self._ti.get_size_calculation('it', True)}\n"
|
||||||
@@ -2542,6 +2567,12 @@ static void dump_field(std::string &out, const char *field_name, StringRef value
|
|||||||
out.append("\\n");
|
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>
|
template<typename T>
|
||||||
static void dump_field(std::string &out, const char *field_name, T value, int indent = 2) {
|
static void dump_field(std::string &out, const char *field_name, T value, int indent = 2) {
|
||||||
append_field_prefix(out, field_name, indent);
|
append_field_prefix(out, field_name, indent);
|
||||||
|
|||||||
Reference in New Issue
Block a user