From 365e3afa9b1c0f6832b760472c49a45fc64d594e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 23 Sep 2025 15:12:54 -0500 Subject: [PATCH] Implement zero-copy API for zwave_proxy (#10836) Co-authored-by: Keith Burzinski --- esphome/components/api/api.proto | 2 +- esphome/components/api/api_pb2.cpp | 9 +++------ esphome/components/api/api_pb2.h | 4 ++-- esphome/components/zwave_proxy/zwave_proxy.cpp | 8 +++++--- esphome/components/zwave_proxy/zwave_proxy.h | 6 ++++-- 5 files changed, 15 insertions(+), 14 deletions(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index c59ccc6e29..26f679e2d7 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -2292,7 +2292,7 @@ message ZWaveProxyFrame { option (ifdef) = "USE_ZWAVE_PROXY"; option (no_delay) = true; - bytes data = 1 [(fixed_array_size) = 257]; + bytes data = 1 [(pointer_to_buffer) = true]; } enum ZWaveProxyRequestType { diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 08384c6869..4388a13ca1 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -3035,12 +3035,9 @@ bool UpdateCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { bool ZWaveProxyFrame::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 1: { - const std::string &data_str = value.as_string(); - this->data_len = data_str.size(); - if (this->data_len > 257) { - this->data_len = 257; - } - memcpy(this->data, data_str.data(), this->data_len); + // Use raw data directly to avoid allocation + this->data = value.data(); + this->data_len = value.size(); break; } default: diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index adc06ad5b7..b6531b37a6 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -2931,11 +2931,11 @@ class UpdateCommandRequest final : public CommandProtoMessage { class ZWaveProxyFrame final : public ProtoDecodableMessage { public: static constexpr uint8_t MESSAGE_TYPE = 128; - static constexpr uint8_t ESTIMATED_SIZE = 33; + static constexpr uint8_t ESTIMATED_SIZE = 19; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "z_wave_proxy_frame"; } #endif - uint8_t data[257]{}; + const uint8_t *data{nullptr}; uint16_t data_len{0}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; diff --git a/esphome/components/zwave_proxy/zwave_proxy.cpp b/esphome/components/zwave_proxy/zwave_proxy.cpp index a894899dc4..feaf6e2d42 100644 --- a/esphome/components/zwave_proxy/zwave_proxy.cpp +++ b/esphome/components/zwave_proxy/zwave_proxy.cpp @@ -106,14 +106,14 @@ void ZWaveProxy::process_uart_() { } ESP_LOGV(TAG, "Sending to client: %s", YESNO(this->api_connection_ != nullptr)); if (this->api_connection_ != nullptr) { - // minimize copying to reduce CPU overhead + // Zero-copy: point directly to our buffer + this->outgoing_proto_msg_.data = this->buffer_.data(); if (this->in_bootloader_) { this->outgoing_proto_msg_.data_len = this->buffer_index_; } else { // If this is a data frame, use frame length indicator + 2 (for SoF + checksum), else assume 1 for ACK/NAK/CAN this->outgoing_proto_msg_.data_len = this->buffer_[0] == ZWAVE_FRAME_TYPE_START ? this->buffer_[1] + 2 : 1; } - std::memcpy(this->outgoing_proto_msg_.data, this->buffer_.data(), this->outgoing_proto_msg_.data_len); this->api_connection_->send_message(this->outgoing_proto_msg_, api::ZWaveProxyFrame::MESSAGE_TYPE); } } @@ -272,7 +272,9 @@ void ZWaveProxy::parse_start_(uint8_t byte) { } // Forward response (ACK/NAK/CAN) back to client for processing if (this->api_connection_ != nullptr) { - this->outgoing_proto_msg_.data[0] = byte; + // Store single byte in buffer and point to it + this->buffer_[0] = byte; + this->outgoing_proto_msg_.data = this->buffer_.data(); this->outgoing_proto_msg_.data_len = 1; this->api_connection_->send_message(this->outgoing_proto_msg_, api::ZWaveProxyFrame::MESSAGE_TYPE); } diff --git a/esphome/components/zwave_proxy/zwave_proxy.h b/esphome/components/zwave_proxy/zwave_proxy.h index 68bec4e7ce..ea6837888b 100644 --- a/esphome/components/zwave_proxy/zwave_proxy.h +++ b/esphome/components/zwave_proxy/zwave_proxy.h @@ -11,6 +11,8 @@ namespace esphome { namespace zwave_proxy { +static constexpr size_t MAX_ZWAVE_FRAME_SIZE = 257; // Maximum Z-Wave frame size + enum ZWaveResponseTypes : uint8_t { ZWAVE_FRAME_TYPE_ACK = 0x06, ZWAVE_FRAME_TYPE_CAN = 0x18, @@ -66,8 +68,8 @@ class ZWaveProxy : public uart::UARTDevice, public Component { // Pre-allocated message - always ready to send api::ZWaveProxyFrame outgoing_proto_msg_; - std::array buffer_; // Fixed buffer for incoming data - std::array home_id_{0, 0, 0, 0}; // Fixed buffer for home ID + std::array buffer_; // Fixed buffer for incoming data + std::array home_id_{0, 0, 0, 0}; // Fixed buffer for home ID // Pointers and 32-bit values (aligned together) api::APIConnection *api_connection_{nullptr}; // Current subscribed client