From 96108a1277ab5a282ca60785a3e6a308c9ddc081 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 6 Dec 2025 14:34:02 -0600 Subject: [PATCH] [select] Add zero-copy support for API select commands --- esphome/components/api/api.proto | 2 +- esphome/components/api/api_connection.cpp | 2 +- esphome/components/api/api_pb2.cpp | 7 +++++-- esphome/components/api/api_pb2.h | 5 +++-- esphome/components/api/api_pb2_dump.cpp | 4 +++- esphome/components/select/select.cpp | 6 ++---- esphome/components/select/select.h | 5 +++-- esphome/components/select/select_call.cpp | 10 +++------- esphome/components/select/select_call.h | 10 ++++++---- 9 files changed, 27 insertions(+), 24 deletions(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 3fc2e1fed8..2534ad0b1f 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -1195,7 +1195,7 @@ message SelectCommandRequest { option (base_class) = "CommandProtoMessage"; fixed32 key = 1; - string state = 2; + string state = 2 [(pointer_to_buffer) = true]; uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"]; } diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index f0428546de..18d80c46df 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -902,7 +902,7 @@ uint16_t APIConnection::try_send_select_info(EntityBase *entity, APIConnection * } void APIConnection::select_command(const SelectCommandRequest &msg) { ENTITY_COMMAND_MAKE_CALL(select::Select, select, select) - call.set_option(msg.state); + call.set_option(reinterpret_cast(msg.state), msg.state_len); call.perform(); } #endif diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index a3da6591f4..128f82fe7f 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -1569,9 +1569,12 @@ bool SelectCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { } bool SelectCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { - case 2: - this->state = value.as_string(); + case 2: { + // Use raw data directly to avoid allocation + this->state = value.data(); + this->state_len = value.size(); break; + } default: return false; } diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 7e41cd8a22..49f1ea3c52 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -1604,11 +1604,12 @@ class SelectStateResponse final : public StateResponseProtoMessage { class SelectCommandRequest final : public CommandProtoMessage { public: static constexpr uint8_t MESSAGE_TYPE = 54; - static constexpr uint8_t ESTIMATED_SIZE = 18; + static constexpr uint8_t ESTIMATED_SIZE = 28; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "select_command_request"; } #endif - std::string state{}; + const uint8_t *state{nullptr}; + uint16_t state_len{0}; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif diff --git a/esphome/components/api/api_pb2_dump.cpp b/esphome/components/api/api_pb2_dump.cpp index 59fc1367fe..ca69d1ff00 100644 --- a/esphome/components/api/api_pb2_dump.cpp +++ b/esphome/components/api/api_pb2_dump.cpp @@ -1453,7 +1453,9 @@ void SelectStateResponse::dump_to(std::string &out) const { void SelectCommandRequest::dump_to(std::string &out) const { MessageDumpHelper helper(out, "SelectCommandRequest"); dump_field(out, "key", this->key); - dump_field(out, "state", this->state); + out.append(" state: "); + out.append(format_hex_pretty(this->state, this->state_len)); + out.append("\n"); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif diff --git a/esphome/components/select/select.cpp b/esphome/components/select/select.cpp index 3ec413f167..4fc4d79b08 100644 --- a/esphome/components/select/select.cpp +++ b/esphome/components/select/select.cpp @@ -56,12 +56,10 @@ size_t Select::size() const { return options.size(); } -optional Select::index_of(const std::string &option) const { return this->index_of(option.c_str()); } - -optional Select::index_of(const char *option) const { +optional Select::index_of(const char *option, size_t len) const { const auto &options = traits.get_options(); for (size_t i = 0; i < options.size(); i++) { - if (strcmp(options[i], option) == 0) { + if (strncmp(options[i], option, len) == 0 && options[i][len] == '\0') { return i; } } diff --git a/esphome/components/select/select.h b/esphome/components/select/select.h index c4d7412d50..63707f6bd6 100644 --- a/esphome/components/select/select.h +++ b/esphome/components/select/select.h @@ -62,8 +62,9 @@ class Select : public EntityBase { size_t size() const; /// Find the (optional) index offset of the provided option value. - optional index_of(const std::string &option) const; - optional index_of(const char *option) const; + optional index_of(const char *option, size_t len) const; + optional index_of(const std::string &option) const { return this->index_of(option.data(), option.size()); } + optional index_of(const char *option) const { return this->index_of(option, strlen(option)); } /// Return the (optional) index offset of the currently active option. optional active_index() const; diff --git a/esphome/components/select/select_call.cpp b/esphome/components/select/select_call.cpp index aecfed0d64..2ff99c961d 100644 --- a/esphome/components/select/select_call.cpp +++ b/esphome/components/select/select_call.cpp @@ -6,9 +6,7 @@ namespace esphome::select { static const char *const TAG = "select"; -SelectCall &SelectCall::set_option(const std::string &option) { return this->with_option(option); } - -SelectCall &SelectCall::set_option(const char *option) { return this->with_option(option); } +SelectCall &SelectCall::set_option(const char *option, size_t len) { return this->with_option(option, len); } SelectCall &SelectCall::set_index(size_t index) { return this->with_index(index); } @@ -32,12 +30,10 @@ SelectCall &SelectCall::with_cycle(bool cycle) { return *this; } -SelectCall &SelectCall::with_option(const std::string &option) { return this->with_option(option.c_str()); } - -SelectCall &SelectCall::with_option(const char *option) { +SelectCall &SelectCall::with_option(const char *option, size_t len) { this->operation_ = SELECT_OP_SET; // Find the option index - this validates the option exists - this->index_ = this->parent_->index_of(option); + this->index_ = this->parent_->index_of(option, len); return *this; } diff --git a/esphome/components/select/select_call.h b/esphome/components/select/select_call.h index b31d890ef6..c9abbc69a0 100644 --- a/esphome/components/select/select_call.h +++ b/esphome/components/select/select_call.h @@ -20,8 +20,9 @@ class SelectCall { explicit SelectCall(Select *parent) : parent_(parent) {} void perform(); - SelectCall &set_option(const std::string &option); - SelectCall &set_option(const char *option); + SelectCall &set_option(const char *option, size_t len); + SelectCall &set_option(const std::string &option) { return this->set_option(option.data(), option.size()); } + SelectCall &set_option(const char *option) { return this->set_option(option, strlen(option)); } SelectCall &set_index(size_t index); SelectCall &select_next(bool cycle); @@ -31,8 +32,9 @@ class SelectCall { SelectCall &with_operation(SelectOperation operation); SelectCall &with_cycle(bool cycle); - SelectCall &with_option(const std::string &option); - SelectCall &with_option(const char *option); + SelectCall &with_option(const char *option, size_t len); + SelectCall &with_option(const std::string &option) { return this->with_option(option.data(), option.size()); } + SelectCall &with_option(const char *option) { return this->with_option(option, strlen(option)); } SelectCall &with_index(size_t index); protected: