diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 7b2a0665d7..51db6dea9c 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -70,7 +70,7 @@ service APIConnection { rpc alarm_control_panel_command (AlarmControlPanelCommandRequest) returns (void) {} - rpc zwave_proxy_frame_to_device(ZWaveProxyFrameToDevice) returns (void) {} + rpc zwave_proxy_frame(ZWaveProxyFrame) returns (void) {} rpc zwave_proxy_subscribe(ZWaveProxySubscribeRequest) returns (void) {} rpc zwave_proxy_unsubscribe(ZWaveProxyUnsubscribeRequest) returns (void) {} } @@ -2287,26 +2287,17 @@ message UpdateCommandRequest { // ==================== Z-WAVE ==================== -message ZWaveProxyFrameFromDevice { +message ZWaveProxyFrame { option (id) = 128; - option (source) = SOURCE_SERVER; + option (source) = SOURCE_BOTH; option (ifdef) = "USE_ZWAVE_PROXY"; option (no_delay) = true; - string data = 1; -} - -message ZWaveProxyFrameToDevice { - option (id) = 129; - option (source) = SOURCE_CLIENT; - option (ifdef) = "USE_ZWAVE_PROXY"; - option (no_delay) = true; - - string data = 1; + bytes data = 1 [(fixed_array_size) = 257]; } message ZWaveProxySubscribeRequest { - option (id) = 130; + option (id) = 129; option (source) = SOURCE_CLIENT; option (ifdef) = "USE_ZWAVE_PROXY"; @@ -2314,7 +2305,7 @@ message ZWaveProxySubscribeRequest { } message ZWaveProxyUnsubscribeRequest { - option (id) = 131; + option (id) = 130; option (source) = SOURCE_CLIENT; option (ifdef) = "USE_ZWAVE_PROXY"; } diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index aceb0a9da1..615696d84b 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -1213,7 +1213,7 @@ void APIConnection::voice_assistant_set_configuration(const VoiceAssistantSetCon #endif #ifdef USE_ZWAVE_PROXY -void APIConnection::zwave_proxy_frame_to_device(const ZWaveProxyFrameToDevice &msg) { +void APIConnection::zwave_proxy_frame(const ZWaveProxyFrame &msg) { zwave_proxy::global_zwave_proxy->send_frame(msg.data); } diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index 64547b84da..0a2c5e375a 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -172,7 +172,7 @@ class APIConnection final : public APIServerConnection { #endif #ifdef USE_ZWAVE_PROXY - void zwave_proxy_frame_to_device(const ZWaveProxyFrameToDevice &msg) override; + void zwave_proxy_frame(const ZWaveProxyFrame &msg) override; void zwave_proxy_subscribe(const ZWaveProxySubscribeRequest &msg) override; void zwave_proxy_unsubscribe(const ZWaveProxyUnsubscribeRequest &msg) override; #endif diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index e4ad8d591b..86677779f3 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -3026,18 +3026,24 @@ bool UpdateCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { } #endif #ifdef USE_ZWAVE_PROXY -void ZWaveProxyFrameFromDevice::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->data_ref_); } -void ZWaveProxyFrameFromDevice::calculate_size(ProtoSize &size) const { size.add_length(1, this->data_ref_.size()); } -bool ZWaveProxyFrameToDevice::decode_length(uint32_t field_id, ProtoLengthDelimited value) { +bool ZWaveProxyFrame::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { - case 1: - this->data = value.as_string(); + 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); break; + } default: return false; } return true; } +void ZWaveProxyFrame::encode(ProtoWriteBuffer buffer) const { buffer.encode_bytes(1, this->data, this->data_len); } +void ZWaveProxyFrame::calculate_size(ProtoSize &size) const { size.add_length(1, this->data_len); } bool ZWaveProxySubscribeRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 1: diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 581e514ee5..0bce0b5813 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -2919,41 +2919,27 @@ class UpdateCommandRequest final : public CommandProtoMessage { }; #endif #ifdef USE_ZWAVE_PROXY -class ZWaveProxyFrameFromDevice final : public ProtoMessage { +class ZWaveProxyFrame final : public ProtoDecodableMessage { public: static constexpr uint8_t MESSAGE_TYPE = 128; - static constexpr uint8_t ESTIMATED_SIZE = 9; + static constexpr uint8_t ESTIMATED_SIZE = 33; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "z_wave_proxy_frame_from_device"; } + const char *message_name() const override { return "z_wave_proxy_frame"; } #endif - StringRef data_ref_{}; - void set_data(const StringRef &ref) { this->data_ref_ = ref; } + uint8_t data[257]{}; + uint16_t data_len{0}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif - protected: -}; -class ZWaveProxyFrameToDevice final : public ProtoDecodableMessage { - public: - static constexpr uint8_t MESSAGE_TYPE = 129; - static constexpr uint8_t ESTIMATED_SIZE = 9; -#ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "z_wave_proxy_frame_to_device"; } -#endif - std::string data{}; -#ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; -#endif - protected: bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; }; class ZWaveProxySubscribeRequest final : public ProtoDecodableMessage { public: - static constexpr uint8_t MESSAGE_TYPE = 130; + static constexpr uint8_t MESSAGE_TYPE = 129; static constexpr uint8_t ESTIMATED_SIZE = 4; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "z_wave_proxy_subscribe_request"; } @@ -2968,7 +2954,7 @@ class ZWaveProxySubscribeRequest final : public ProtoDecodableMessage { }; class ZWaveProxyUnsubscribeRequest final : public ProtoMessage { public: - static constexpr uint8_t MESSAGE_TYPE = 131; + static constexpr uint8_t MESSAGE_TYPE = 130; static constexpr uint8_t ESTIMATED_SIZE = 0; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "z_wave_proxy_unsubscribe_request"; } diff --git a/esphome/components/api/api_pb2_dump.cpp b/esphome/components/api/api_pb2_dump.cpp index 0d18f4f51b..572aed576b 100644 --- a/esphome/components/api/api_pb2_dump.cpp +++ b/esphome/components/api/api_pb2_dump.cpp @@ -2112,8 +2112,12 @@ void UpdateCommandRequest::dump_to(std::string &out) const { } #endif #ifdef USE_ZWAVE_PROXY -void ZWaveProxyFrameFromDevice::dump_to(std::string &out) const { dump_field(out, "data", this->data_ref_); } -void ZWaveProxyFrameToDevice::dump_to(std::string &out) const { dump_field(out, "data", this->data); } +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 ZWaveProxySubscribeRequest::dump_to(std::string &out) const { dump_field(out, "flags", this->flags); } void ZWaveProxyUnsubscribeRequest::dump_to(std::string &out) const { out.append("ZWaveProxyUnsubscribeRequest {}"); } #endif diff --git a/esphome/components/api/api_pb2_service.cpp b/esphome/components/api/api_pb2_service.cpp index 796355c3a6..20b68d524e 100644 --- a/esphome/components/api/api_pb2_service.cpp +++ b/esphome/components/api/api_pb2_service.cpp @@ -597,13 +597,13 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, } #endif #ifdef USE_ZWAVE_PROXY - case ZWaveProxyFrameToDevice::MESSAGE_TYPE: { - ZWaveProxyFrameToDevice msg; + case ZWaveProxyFrame::MESSAGE_TYPE: { + ZWaveProxyFrame msg; msg.decode(msg_data, msg_size); #ifdef HAS_PROTO_MESSAGE_DUMP - ESP_LOGVV(TAG, "on_z_wave_proxy_frame_to_device: %s", msg.dump().c_str()); + ESP_LOGVV(TAG, "on_z_wave_proxy_frame: %s", msg.dump().c_str()); #endif - this->on_z_wave_proxy_frame_to_device(msg); + this->on_z_wave_proxy_frame(msg); break; } #endif @@ -943,9 +943,9 @@ void APIServerConnection::on_alarm_control_panel_command_request(const AlarmCont } #endif #ifdef USE_ZWAVE_PROXY -void APIServerConnection::on_z_wave_proxy_frame_to_device(const ZWaveProxyFrameToDevice &msg) { +void APIServerConnection::on_z_wave_proxy_frame(const ZWaveProxyFrame &msg) { if (this->check_authenticated_()) { - this->zwave_proxy_frame_to_device(msg); + this->zwave_proxy_frame(msg); } } #endif diff --git a/esphome/components/api/api_pb2_service.h b/esphome/components/api/api_pb2_service.h index c5e4f0f599..a2c77103ee 100644 --- a/esphome/components/api/api_pb2_service.h +++ b/esphome/components/api/api_pb2_service.h @@ -206,9 +206,8 @@ class APIServerConnectionBase : public ProtoService { #ifdef USE_UPDATE virtual void on_update_command_request(const UpdateCommandRequest &value){}; #endif - #ifdef USE_ZWAVE_PROXY - virtual void on_z_wave_proxy_frame_to_device(const ZWaveProxyFrameToDevice &value){}; + virtual void on_z_wave_proxy_frame(const ZWaveProxyFrame &value){}; #endif #ifdef USE_ZWAVE_PROXY virtual void on_z_wave_proxy_subscribe_request(const ZWaveProxySubscribeRequest &value){}; @@ -344,7 +343,7 @@ class APIServerConnection : public APIServerConnectionBase { virtual void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) = 0; #endif #ifdef USE_ZWAVE_PROXY - virtual void zwave_proxy_frame_to_device(const ZWaveProxyFrameToDevice &msg) = 0; + virtual void zwave_proxy_frame(const ZWaveProxyFrame &msg) = 0; #endif #ifdef USE_ZWAVE_PROXY virtual void zwave_proxy_subscribe(const ZWaveProxySubscribeRequest &msg) = 0; @@ -475,7 +474,7 @@ class APIServerConnection : public APIServerConnectionBase { void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) override; #endif #ifdef USE_ZWAVE_PROXY - void on_z_wave_proxy_frame_to_device(const ZWaveProxyFrameToDevice &msg) override; + void on_z_wave_proxy_frame(const ZWaveProxyFrame &msg) override; #endif #ifdef USE_ZWAVE_PROXY void on_z_wave_proxy_subscribe_request(const ZWaveProxySubscribeRequest &msg) override; diff --git a/esphome/components/zwave_proxy/zwave_proxy.cpp b/esphome/components/zwave_proxy/zwave_proxy.cpp index ebc36c82db..4d04358752 100644 --- a/esphome/components/zwave_proxy/zwave_proxy.cpp +++ b/esphome/components/zwave_proxy/zwave_proxy.cpp @@ -25,10 +25,12 @@ void ZWaveProxy::loop() { return; } if (this->parse_byte_(byte)) { - ESP_LOGD(TAG, "Sending frame: %s", YESNO(this->api_connection_ != nullptr)); + ESP_LOGD(TAG, "Sending to client: %s", YESNO(this->api_connection_ != nullptr)); if (this->api_connection_ != nullptr) { - this->outgoing_proto_msg_.set_data(StringRef((const char *) this->buffer_, this->buffer_index_)); - this->api_connection_->send_message(this->outgoing_proto_msg_, api::ZWaveProxyFrameFromDevice::MESSAGE_TYPE); + // minimize copying to reduce CPU overhead + 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_, this->outgoing_proto_msg_.data_len); + this->api_connection_->send_message(this->outgoing_proto_msg_, api::ZWaveProxyFrame::MESSAGE_TYPE); } } } @@ -53,14 +55,20 @@ void ZWaveProxy::unsubscribe_api_connection(api::APIConnection *api_connection) this->api_connection_ = nullptr; } -void ZWaveProxy::send_frame(const std::string &data) { - ESP_LOGD(TAG, "Sending: %s", format_hex_pretty(data).c_str()); - this->write_array((uint8_t *) data.data(), data.size()); -} - -void ZWaveProxy::send_frame(const std::vector &data) { - ESP_LOGD(TAG, "Sending: %s", format_hex_pretty(data).c_str()); - this->write_array(data); +void ZWaveProxy::send_frame(const uint8_t *data, size_t length) { + if (!length) { + if (data[0] == ZWAVE_FRAME_TYPE_START) { + length = data[1] + 2; // data[1] is payload length, not including SoF + checksum + } else { + length = 1; // assume ACK/NAK/CAN + } + } + if (length == 1 && data[0] == this->last_response_) { + ESP_LOGW(TAG, "Skipping sending duplicate response: 0x%02X", data[0]); + return; + } + ESP_LOGD(TAG, "Sending: %s", format_hex_pretty(data, length).c_str()); + this->write_array(data, length); } bool ZWaveProxy::parse_byte_(uint8_t byte) { @@ -152,29 +160,30 @@ 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_.set_data(StringRef(&byte, 1)); - this->api_connection_->send_message(this->outgoing_proto_msg_, api::ZWaveProxyFrameFromDevice::MESSAGE_TYPE); + 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); } } bool ZWaveProxy::response_handler_() { - uint8_t response_byte = 0; switch (this->parsing_state_) { case ZWAVE_PARSING_STATE_SEND_ACK: - response_byte = ZWAVE_FRAME_TYPE_ACK; + this->last_response_ = ZWAVE_FRAME_TYPE_ACK; break; case ZWAVE_PARSING_STATE_SEND_CAN: - response_byte = ZWAVE_FRAME_TYPE_CAN; + this->last_response_ = ZWAVE_FRAME_TYPE_CAN; break; case ZWAVE_PARSING_STATE_SEND_NAK: - response_byte = ZWAVE_FRAME_TYPE_NAK; + this->last_response_ = ZWAVE_FRAME_TYPE_NAK; break; default: return false; // No response handled } - ESP_LOGD(TAG, "Sending %s (0x%02X)", response_byte == ZWAVE_FRAME_TYPE_ACK ? "ACK" : "NAK/CAN", response_byte); - this->write_byte(response_byte); + ESP_LOGD(TAG, "Sending %s (0x%02X)", this->last_response_ == ZWAVE_FRAME_TYPE_ACK ? "ACK" : "NAK/CAN", + this->last_response_); + this->write_byte(this->last_response_); this->parsing_state_ = ZWAVE_PARSING_STATE_WAIT_START; return true; } diff --git a/esphome/components/zwave_proxy/zwave_proxy.h b/esphome/components/zwave_proxy/zwave_proxy.h index b663fe36c7..f61437a4ff 100644 --- a/esphome/components/zwave_proxy/zwave_proxy.h +++ b/esphome/components/zwave_proxy/zwave_proxy.h @@ -44,8 +44,7 @@ class ZWaveProxy : public uart::UARTDevice, public Component { uint32_t get_feature_flags() const { return ZWaveProxyFeature::FEATURE_ZWAVE_PROXY_ENABLED; } - void send_frame(const std::string &data); - void send_frame(const std::vector &data); + void send_frame(const uint8_t *data, size_t length = 0); protected: bool parse_byte_(uint8_t byte); // Returns true if frame parsing was completed (a frame is ready in the buffer) @@ -54,14 +53,15 @@ class ZWaveProxy : public uart::UARTDevice, public Component { api::APIConnection *api_connection_{nullptr}; // Current subscribed client - uint8_t buffer_[257]; // Fixed buffer for incoming data: max length = 255 + 2 (start of frame and checksum) - uint8_t buffer_index_{0}; // Index for populating the data buffer - uint8_t checksum_{0}; // Checksum of the frame being parsed - uint8_t end_frame_after_{0}; // Payload reception ends after this index + uint8_t buffer_[sizeof(api::ZWaveProxyFrame::data)]; // Fixed buffer for incoming data + uint8_t buffer_index_{0}; // Index for populating the data buffer + uint8_t checksum_{0}; // Checksum of the frame being parsed + 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}; // Pre-allocated message - always ready to send - api::ZWaveProxyFrameFromDevice outgoing_proto_msg_; + api::ZWaveProxyFrame outgoing_proto_msg_; }; extern ZWaveProxy *global_zwave_proxy; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)