mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 15:12:06 +00:00 
			
		
		
		
	Implement zero-copy API for bluetooth_proxy writes
This is the same as https://github.com/esphome/esphome/pull/10836 for Bluetooth proxy writes. This avoids the copy since all the messages live on the stack anyways and there are no lifetime concerns Doing bluetooth first since there is a wider test case vs zwave
This commit is contained in:
		| @@ -1465,7 +1465,7 @@ message BluetoothDeviceRequest { | |||||||
|  |  | ||||||
|   uint64 address = 1; |   uint64 address = 1; | ||||||
|   BluetoothDeviceRequestType request_type = 2; |   BluetoothDeviceRequestType request_type = 2; | ||||||
|   bool has_address_type = 3; |   bool has_address_type = 3;  // Deprecated, should be removed in 2027.8 - https://github.com/esphome/esphome/pull/10318 | ||||||
|   uint32 address_type = 4; |   uint32 address_type = 4; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -1571,7 +1571,7 @@ message BluetoothGATTWriteRequest { | |||||||
|   uint32 handle = 2; |   uint32 handle = 2; | ||||||
|   bool response = 3; |   bool response = 3; | ||||||
|  |  | ||||||
|   bytes data = 4; |   bytes data = 4 [(pointer_to_buffer) = true]; | ||||||
| } | } | ||||||
|  |  | ||||||
| message BluetoothGATTReadDescriptorRequest { | message BluetoothGATTReadDescriptorRequest { | ||||||
| @@ -1591,7 +1591,7 @@ message BluetoothGATTWriteDescriptorRequest { | |||||||
|   uint64 address = 1; |   uint64 address = 1; | ||||||
|   uint32 handle = 2; |   uint32 handle = 2; | ||||||
|  |  | ||||||
|   bytes data = 3; |   bytes data = 3 [(pointer_to_buffer) = true]; | ||||||
| } | } | ||||||
|  |  | ||||||
| message BluetoothGATTNotifyRequest { | message BluetoothGATTNotifyRequest { | ||||||
| @@ -2292,7 +2292,7 @@ message ZWaveProxyFrame { | |||||||
|   option (ifdef) = "USE_ZWAVE_PROXY"; |   option (ifdef) = "USE_ZWAVE_PROXY"; | ||||||
|   option (no_delay) = true; |   option (no_delay) = true; | ||||||
|  |  | ||||||
|   bytes data = 1 [(pointer_to_buffer) = true]; |   bytes data = 1 [(fixed_array_size) = 257]; | ||||||
| } | } | ||||||
|  |  | ||||||
| enum ZWaveProxyRequestType { | enum ZWaveProxyRequestType { | ||||||
|   | |||||||
| @@ -2028,9 +2028,12 @@ bool BluetoothGATTWriteRequest::decode_varint(uint32_t field_id, ProtoVarInt val | |||||||
| } | } | ||||||
| bool BluetoothGATTWriteRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { | bool BluetoothGATTWriteRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { | ||||||
|   switch (field_id) { |   switch (field_id) { | ||||||
|     case 4: |     case 4: { | ||||||
|       this->data = value.as_string(); |       // Use raw data directly to avoid allocation | ||||||
|  |       this->data = value.data(); | ||||||
|  |       this->data_len = value.size(); | ||||||
|       break; |       break; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -2064,9 +2067,12 @@ bool BluetoothGATTWriteDescriptorRequest::decode_varint(uint32_t field_id, Proto | |||||||
| } | } | ||||||
| bool BluetoothGATTWriteDescriptorRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { | bool BluetoothGATTWriteDescriptorRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { | ||||||
|   switch (field_id) { |   switch (field_id) { | ||||||
|     case 3: |     case 3: { | ||||||
|       this->data = value.as_string(); |       // Use raw data directly to avoid allocation | ||||||
|  |       this->data = value.data(); | ||||||
|  |       this->data_len = value.size(); | ||||||
|       break; |       break; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -3029,9 +3035,12 @@ bool UpdateCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { | |||||||
| bool ZWaveProxyFrame::decode_length(uint32_t field_id, ProtoLengthDelimited value) { | bool ZWaveProxyFrame::decode_length(uint32_t field_id, ProtoLengthDelimited value) { | ||||||
|   switch (field_id) { |   switch (field_id) { | ||||||
|     case 1: { |     case 1: { | ||||||
|       // Use raw data directly to avoid allocation |       const std::string &data_str = value.as_string(); | ||||||
|       this->data = value.data(); |       this->data_len = data_str.size(); | ||||||
|       this->data_len = value.size(); |       if (this->data_len > 257) { | ||||||
|  |         this->data_len = 257; | ||||||
|  |       } | ||||||
|  |       memcpy(this->data, data_str.data(), this->data_len); | ||||||
|       break; |       break; | ||||||
|     } |     } | ||||||
|     default: |     default: | ||||||
|   | |||||||
| @@ -1985,14 +1985,15 @@ class BluetoothGATTReadResponse final : public ProtoMessage { | |||||||
| class BluetoothGATTWriteRequest final : public ProtoDecodableMessage { | class BluetoothGATTWriteRequest final : public ProtoDecodableMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint8_t MESSAGE_TYPE = 75; |   static constexpr uint8_t MESSAGE_TYPE = 75; | ||||||
|   static constexpr uint8_t ESTIMATED_SIZE = 19; |   static constexpr uint8_t ESTIMATED_SIZE = 29; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   const char *message_name() const override { return "bluetooth_gatt_write_request"; } |   const char *message_name() const override { return "bluetooth_gatt_write_request"; } | ||||||
| #endif | #endif | ||||||
|   uint64_t address{0}; |   uint64_t address{0}; | ||||||
|   uint32_t handle{0}; |   uint32_t handle{0}; | ||||||
|   bool response{false}; |   bool response{false}; | ||||||
|   std::string data{}; |   const uint8_t *data{nullptr}; | ||||||
|  |   uint16_t data_len{0}; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   void dump_to(std::string &out) const override; |   void dump_to(std::string &out) const override; | ||||||
| #endif | #endif | ||||||
| @@ -2020,13 +2021,14 @@ class BluetoothGATTReadDescriptorRequest final : public ProtoDecodableMessage { | |||||||
| class BluetoothGATTWriteDescriptorRequest final : public ProtoDecodableMessage { | class BluetoothGATTWriteDescriptorRequest final : public ProtoDecodableMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint8_t MESSAGE_TYPE = 77; |   static constexpr uint8_t MESSAGE_TYPE = 77; | ||||||
|   static constexpr uint8_t ESTIMATED_SIZE = 17; |   static constexpr uint8_t ESTIMATED_SIZE = 27; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   const char *message_name() const override { return "bluetooth_gatt_write_descriptor_request"; } |   const char *message_name() const override { return "bluetooth_gatt_write_descriptor_request"; } | ||||||
| #endif | #endif | ||||||
|   uint64_t address{0}; |   uint64_t address{0}; | ||||||
|   uint32_t handle{0}; |   uint32_t handle{0}; | ||||||
|   std::string data{}; |   const uint8_t *data{nullptr}; | ||||||
|  |   uint16_t data_len{0}; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   void dump_to(std::string &out) const override; |   void dump_to(std::string &out) const override; | ||||||
| #endif | #endif | ||||||
| @@ -2929,11 +2931,11 @@ class UpdateCommandRequest final : public CommandProtoMessage { | |||||||
| class ZWaveProxyFrame final : public ProtoDecodableMessage { | class ZWaveProxyFrame final : public ProtoDecodableMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint8_t MESSAGE_TYPE = 128; |   static constexpr uint8_t MESSAGE_TYPE = 128; | ||||||
|   static constexpr uint8_t ESTIMATED_SIZE = 19; |   static constexpr uint8_t ESTIMATED_SIZE = 33; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   const char *message_name() const override { return "z_wave_proxy_frame"; } |   const char *message_name() const override { return "z_wave_proxy_frame"; } | ||||||
| #endif | #endif | ||||||
|   const uint8_t *data{nullptr}; |   uint8_t data[257]{}; | ||||||
|   uint16_t data_len{0}; |   uint16_t data_len{0}; | ||||||
|   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; | ||||||
|   | |||||||
| @@ -1649,7 +1649,7 @@ void BluetoothGATTWriteRequest::dump_to(std::string &out) const { | |||||||
|   dump_field(out, "handle", this->handle); |   dump_field(out, "handle", this->handle); | ||||||
|   dump_field(out, "response", this->response); |   dump_field(out, "response", this->response); | ||||||
|   out.append("  data: "); |   out.append("  data: "); | ||||||
|   out.append(format_hex_pretty(reinterpret_cast<const uint8_t *>(this->data.data()), this->data.size())); |   out.append(format_hex_pretty(this->data, this->data_len)); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
| } | } | ||||||
| void BluetoothGATTReadDescriptorRequest::dump_to(std::string &out) const { | void BluetoothGATTReadDescriptorRequest::dump_to(std::string &out) const { | ||||||
| @@ -1662,7 +1662,7 @@ void BluetoothGATTWriteDescriptorRequest::dump_to(std::string &out) const { | |||||||
|   dump_field(out, "address", this->address); |   dump_field(out, "address", this->address); | ||||||
|   dump_field(out, "handle", this->handle); |   dump_field(out, "handle", this->handle); | ||||||
|   out.append("  data: "); |   out.append("  data: "); | ||||||
|   out.append(format_hex_pretty(reinterpret_cast<const uint8_t *>(this->data.data()), this->data.size())); |   out.append(format_hex_pretty(this->data, this->data_len)); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
| } | } | ||||||
| void BluetoothGATTNotifyRequest::dump_to(std::string &out) const { | void BluetoothGATTNotifyRequest::dump_to(std::string &out) const { | ||||||
|   | |||||||
| @@ -61,14 +61,14 @@ void ZWaveProxy::loop() { | |||||||
|       } |       } | ||||||
|       ESP_LOGV(TAG, "Sending to client: %s", YESNO(this->api_connection_ != nullptr)); |       ESP_LOGV(TAG, "Sending to client: %s", YESNO(this->api_connection_ != nullptr)); | ||||||
|       if (this->api_connection_ != nullptr) { |       if (this->api_connection_ != nullptr) { | ||||||
|         // Zero-copy: point directly to our buffer |         // minimize copying to reduce CPU overhead | ||||||
|         this->outgoing_proto_msg_.data = this->buffer_.data(); |  | ||||||
|         if (this->in_bootloader_) { |         if (this->in_bootloader_) { | ||||||
|           this->outgoing_proto_msg_.data_len = this->buffer_index_; |           this->outgoing_proto_msg_.data_len = this->buffer_index_; | ||||||
|         } else { |         } else { | ||||||
|           // If this is a data frame, use frame length indicator + 2 (for SoF + checksum), else assume 1 for ACK/NAK/CAN |           // 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; |           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); |         this->api_connection_->send_message(this->outgoing_proto_msg_, api::ZWaveProxyFrame::MESSAGE_TYPE); | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
| @@ -228,9 +228,7 @@ void ZWaveProxy::parse_start_(uint8_t byte) { | |||||||
|   } |   } | ||||||
|   // Forward response (ACK/NAK/CAN) back to client for processing |   // Forward response (ACK/NAK/CAN) back to client for processing | ||||||
|   if (this->api_connection_ != nullptr) { |   if (this->api_connection_ != nullptr) { | ||||||
|     // Store single byte in buffer and point to it |     this->outgoing_proto_msg_.data[0] = byte; | ||||||
|     this->buffer_[0] = byte; |  | ||||||
|     this->outgoing_proto_msg_.data = this->buffer_.data(); |  | ||||||
|     this->outgoing_proto_msg_.data_len = 1; |     this->outgoing_proto_msg_.data_len = 1; | ||||||
|     this->api_connection_->send_message(this->outgoing_proto_msg_, api::ZWaveProxyFrame::MESSAGE_TYPE); |     this->api_connection_->send_message(this->outgoing_proto_msg_, api::ZWaveProxyFrame::MESSAGE_TYPE); | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -11,8 +11,6 @@ | |||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace zwave_proxy { | namespace zwave_proxy { | ||||||
|  |  | ||||||
| static constexpr size_t MAX_ZWAVE_FRAME_SIZE = 257;  // Maximum Z-Wave frame size |  | ||||||
|  |  | ||||||
| enum ZWaveResponseTypes : uint8_t { | enum ZWaveResponseTypes : uint8_t { | ||||||
|   ZWAVE_FRAME_TYPE_ACK = 0x06, |   ZWAVE_FRAME_TYPE_ACK = 0x06, | ||||||
|   ZWAVE_FRAME_TYPE_CAN = 0x18, |   ZWAVE_FRAME_TYPE_CAN = 0x18, | ||||||
| @@ -65,11 +63,11 @@ class ZWaveProxy : public uart::UARTDevice, public Component { | |||||||
|  |  | ||||||
|   api::APIConnection *api_connection_{nullptr};  // Current subscribed client |   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, 4> home_id_{0, 0, 0, 0};                      // Fixed buffer for home ID | ||||||
|   std::array<uint8_t, MAX_ZWAVE_FRAME_SIZE> buffer_;  // Fixed buffer for incoming data |   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 buffer_index_{0};                                         // Index for populating the data buffer | ||||||
|   uint8_t end_frame_after_{0};                        // Payload reception ends after this index |   uint8_t end_frame_after_{0};                                      // Payload reception ends after this index | ||||||
|   uint8_t last_response_{0};                          // Last response type sent |   uint8_t last_response_{0};                                        // Last response type sent | ||||||
|   ZWaveParsingState parsing_state_{ZWAVE_PARSING_STATE_WAIT_START}; |   ZWaveParsingState parsing_state_{ZWAVE_PARSING_STATE_WAIT_START}; | ||||||
|   bool in_bootloader_{false};  // True if the device is detected to be in bootloader mode |   bool in_bootloader_{false};  // True if the device is detected to be in bootloader mode | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user