mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 15:12:06 +00:00 
			
		
		
		
	Implement zero-copy API for zwave_proxy
This commit is contained in:
		| @@ -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 { | ||||
|   | ||||
| @@ -32,6 +32,13 @@ extend google.protobuf.FieldOptions { | ||||
|     optional string fixed_array_size_define = 50010; | ||||
|     optional string fixed_array_with_length_define = 50011; | ||||
|  | ||||
|     // pointer_to_buffer: Use pointer instead of array for fixed-size byte fields | ||||
|     // When set, the field will be declared as a pointer (const uint8_t *data) | ||||
|     // instead of an array (uint8_t data[N]). This allows zero-copy on decode | ||||
|     // by pointing directly to the protobuf buffer. The buffer must remain valid | ||||
|     // until the message is processed (which is guaranteed for stack-allocated messages). | ||||
|     optional bool pointer_to_buffer = 50012 [default=false]; | ||||
|  | ||||
|     // container_pointer: Zero-copy optimization for repeated fields. | ||||
|     // | ||||
|     // When container_pointer is set on a repeated field, the generated message will | ||||
|   | ||||
| @@ -3029,12 +3029,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: | ||||
|   | ||||
| @@ -2929,11 +2929,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; | ||||
|   | ||||
| @@ -2126,12 +2126,7 @@ void UpdateCommandRequest::dump_to(std::string &out) const { | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_ZWAVE_PROXY | ||||
| void ZWaveProxyFrame::dump_to(std::string &out) const { | ||||
|   MessageDumpHelper helper(out, "ZWaveProxyFrame"); | ||||
|   out.append("  data: "); | ||||
|   out.append(format_hex_pretty(this->data, this->data_len)); | ||||
|   out.append("\n"); | ||||
| } | ||||
| void ZWaveProxyFrame::dump_to(std::string &out) const { dump_field(out, "data", this->data); } | ||||
| void ZWaveProxyRequest::dump_to(std::string &out) const { | ||||
|   MessageDumpHelper helper(out, "ZWaveProxyRequest"); | ||||
|   dump_field(out, "type", static_cast<enums::ZWaveProxyRequestType>(this->type)); | ||||
|   | ||||
| @@ -182,6 +182,10 @@ class ProtoLengthDelimited { | ||||
|   explicit ProtoLengthDelimited(const uint8_t *value, size_t length) : value_(value), length_(length) {} | ||||
|   std::string as_string() const { return std::string(reinterpret_cast<const char *>(this->value_), this->length_); } | ||||
|  | ||||
|   // Direct access to raw data without string allocation | ||||
|   const uint8_t *data() const { return this->value_; } | ||||
|   size_t size() const { return this->length_; } | ||||
|  | ||||
|   /** | ||||
|    * Decode the length-delimited data into an existing ProtoDecodableMessage instance. | ||||
|    * | ||||
|   | ||||
| @@ -61,14 +61,14 @@ void ZWaveProxy::loop() { | ||||
|       } | ||||
|       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); | ||||
|       } | ||||
|     } | ||||
| @@ -228,7 +228,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); | ||||
|   } | ||||
|   | ||||
| @@ -63,11 +63,11 @@ class ZWaveProxy : public uart::UARTDevice, public Component { | ||||
|  | ||||
|   api::APIConnection *api_connection_{nullptr};  // Current subscribed client | ||||
|  | ||||
|   std::array<uint8_t, 4> home_id_{0, 0, 0, 0};                      // Fixed buffer for home ID | ||||
|   std::array<uint8_t, sizeof(api::ZWaveProxyFrame::data)> buffer_;  // Fixed buffer for incoming data | ||||
|   uint8_t buffer_index_{0};                                         // Index for populating the data buffer | ||||
|   uint8_t end_frame_after_{0};                                      // Payload reception ends after this index | ||||
|   uint8_t last_response_{0};                                        // Last response type sent | ||||
|   std::array<uint8_t, 4> home_id_{0, 0, 0, 0};  // Fixed buffer for home ID | ||||
|   std::array<uint8_t, 257> buffer_;             // Fixed buffer for incoming data | ||||
|   uint8_t buffer_index_{0};                     // Index for populating the data buffer | ||||
|   uint8_t end_frame_after_{0};                  // Payload reception ends after this index | ||||
|   uint8_t last_response_{0};                    // Last response type sent | ||||
|   ZWaveParsingState parsing_state_{ZWAVE_PARSING_STATE_WAIT_START}; | ||||
|   bool in_bootloader_{false};  // True if the device is detected to be in bootloader mode | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user