1
0
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:
J. Nick Koston
2025-09-22 23:44:09 -05:00
parent 6a91df841b
commit 85b5b859b5
6 changed files with 38 additions and 31 deletions

View File

@@ -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 {

View File

@@ -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:

View File

@@ -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;

View File

@@ -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 {

View File

@@ -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);
} }

View File

@@ -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