mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 06:33:51 +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; | ||||
|   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; | ||||
| } | ||||
|  | ||||
| @@ -1571,7 +1571,7 @@ message BluetoothGATTWriteRequest { | ||||
|   uint32 handle = 2; | ||||
|   bool response = 3; | ||||
|  | ||||
|   bytes data = 4; | ||||
|   bytes data = 4 [(pointer_to_buffer) = true]; | ||||
| } | ||||
|  | ||||
| message BluetoothGATTReadDescriptorRequest { | ||||
| @@ -1591,7 +1591,7 @@ message BluetoothGATTWriteDescriptorRequest { | ||||
|   uint64 address = 1; | ||||
|   uint32 handle = 2; | ||||
|  | ||||
|   bytes data = 3; | ||||
|   bytes data = 3 [(pointer_to_buffer) = true]; | ||||
| } | ||||
|  | ||||
| message BluetoothGATTNotifyRequest { | ||||
| @@ -2292,7 +2292,7 @@ message ZWaveProxyFrame { | ||||
|   option (ifdef) = "USE_ZWAVE_PROXY"; | ||||
|   option (no_delay) = true; | ||||
|  | ||||
|   bytes data = 1 [(pointer_to_buffer) = true]; | ||||
|   bytes data = 1 [(fixed_array_size) = 257]; | ||||
| } | ||||
|  | ||||
| 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) { | ||||
|   switch (field_id) { | ||||
|     case 4: | ||||
|       this->data = value.as_string(); | ||||
|     case 4: { | ||||
|       // Use raw data directly to avoid allocation | ||||
|       this->data = value.data(); | ||||
|       this->data_len = value.size(); | ||||
|       break; | ||||
|     } | ||||
|     default: | ||||
|       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) { | ||||
|   switch (field_id) { | ||||
|     case 3: | ||||
|       this->data = value.as_string(); | ||||
|     case 3: { | ||||
|       // Use raw data directly to avoid allocation | ||||
|       this->data = value.data(); | ||||
|       this->data_len = value.size(); | ||||
|       break; | ||||
|     } | ||||
|     default: | ||||
|       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) { | ||||
|   switch (field_id) { | ||||
|     case 1: { | ||||
|       // Use raw data directly to avoid allocation | ||||
|       this->data = value.data(); | ||||
|       this->data_len = value.size(); | ||||
|       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); | ||||
|       break; | ||||
|     } | ||||
|     default: | ||||
|   | ||||
| @@ -1985,14 +1985,15 @@ class BluetoothGATTReadResponse final : public ProtoMessage { | ||||
| class BluetoothGATTWriteRequest final : public ProtoDecodableMessage { | ||||
|  public: | ||||
|   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 | ||||
|   const char *message_name() const override { return "bluetooth_gatt_write_request"; } | ||||
| #endif | ||||
|   uint64_t address{0}; | ||||
|   uint32_t handle{0}; | ||||
|   bool response{false}; | ||||
|   std::string data{}; | ||||
|   const uint8_t *data{nullptr}; | ||||
|   uint16_t data_len{0}; | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   void dump_to(std::string &out) const override; | ||||
| #endif | ||||
| @@ -2020,13 +2021,14 @@ class BluetoothGATTReadDescriptorRequest final : public ProtoDecodableMessage { | ||||
| class BluetoothGATTWriteDescriptorRequest final : public ProtoDecodableMessage { | ||||
|  public: | ||||
|   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 | ||||
|   const char *message_name() const override { return "bluetooth_gatt_write_descriptor_request"; } | ||||
| #endif | ||||
|   uint64_t address{0}; | ||||
|   uint32_t handle{0}; | ||||
|   std::string data{}; | ||||
|   const uint8_t *data{nullptr}; | ||||
|   uint16_t data_len{0}; | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   void dump_to(std::string &out) const override; | ||||
| #endif | ||||
| @@ -2929,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 = 19; | ||||
|   static constexpr uint8_t ESTIMATED_SIZE = 33; | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   const char *message_name() const override { return "z_wave_proxy_frame"; } | ||||
| #endif | ||||
|   const uint8_t *data{nullptr}; | ||||
|   uint8_t data[257]{}; | ||||
|   uint16_t data_len{0}; | ||||
|   void encode(ProtoWriteBuffer buffer) 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, "response", this->response); | ||||
|   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"); | ||||
| } | ||||
| 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, "handle", this->handle); | ||||
|   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"); | ||||
| } | ||||
| 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)); | ||||
|       if (this->api_connection_ != nullptr) { | ||||
|         // Zero-copy: point directly to our buffer | ||||
|         this->outgoing_proto_msg_.data = this->buffer_.data(); | ||||
|         // minimize copying to reduce CPU overhead | ||||
|         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,9 +228,7 @@ void ZWaveProxy::parse_start_(uint8_t byte) { | ||||
|   } | ||||
|   // Forward response (ACK/NAK/CAN) back to client for processing | ||||
|   if (this->api_connection_ != nullptr) { | ||||
|     // 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[0] = byte; | ||||
|     this->outgoing_proto_msg_.data_len = 1; | ||||
|     this->api_connection_->send_message(this->outgoing_proto_msg_, api::ZWaveProxyFrame::MESSAGE_TYPE); | ||||
|   } | ||||
|   | ||||
| @@ -11,8 +11,6 @@ | ||||
| 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, | ||||
| @@ -65,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, MAX_ZWAVE_FRAME_SIZE> 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, 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 | ||||
|   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