diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index f31922a39b..112f64f2b8 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -70,6 +70,8 @@ service APIConnection { rpc alarm_control_panel_command (AlarmControlPanelCommandRequest) returns (void) {} + rpc zwave_proxy_subscribe(ZWaveProxySubscribeRequest) returns (void) {} + rpc zwave_proxy_unsubscribe(ZWaveProxyUnsubscribeRequest) returns (void) {} rpc zwave_proxy_from_device(ZWaveProxyFromDeviceRequest) returns (void) {} rpc zwave_proxy_to_device(ZWaveProxyToDeviceRequest) returns (void) {} } @@ -2289,6 +2291,8 @@ message ZWaveProxyFromDeviceRequest { option (source) = SOURCE_CLIENT; option (ifdef) = "USE_ZWAVE_PROXY"; option (no_delay) = true; + + string data = 1; } message ZWaveProxyFromDeviceResponse { @@ -2296,8 +2300,6 @@ message ZWaveProxyFromDeviceResponse { option (source) = SOURCE_SERVER; option (ifdef) = "USE_ZWAVE_PROXY"; option (no_delay) = true; - - string data = 1; } message ZWaveProxyToDeviceRequest { @@ -2315,3 +2317,17 @@ message ZWaveProxyToDeviceResponse { option (ifdef) = "USE_ZWAVE_PROXY"; option (no_delay) = true; } + +message ZWaveProxySubscribeRequest { + option (id) = 132; + option (source) = SOURCE_CLIENT; + option (ifdef) = "USE_ZWAVE_PROXY"; + + uint32 flags = 1; +} + +message ZWaveProxyUnsubscribeRequest { + option (id) = 133; + 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 a17cba6c98..f0436c5c0a 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -1207,7 +1207,18 @@ void APIConnection::voice_assistant_set_configuration(const VoiceAssistantSetCon #endif #ifdef USE_ZWAVE_PROXY -void APIConnection::zwave_proxy_from_device(const ZWaveProxyFromDeviceRequest &msg) {} +void APIConnection::zwave_proxy_subscribe(const ZWaveProxySubscribeRequest &msg) { + zwave_proxy::global_zwave_proxy->subscribe_api_connection(this, msg.flags); +} + +void APIConnection::zwave_proxy_unsubscribe(const ZWaveProxyUnsubscribeRequest &msg) { + zwave_proxy::global_zwave_proxy->unsubscribe_api_connection(this); +} + +void APIConnection::zwave_proxy_from_device(const ZWaveProxyFromDeviceRequest &msg) { + this->send_message(msg, ZWaveProxyFromDeviceRequest::MESSAGE_TYPE); +} + void APIConnection::zwave_proxy_to_device(const ZWaveProxyToDeviceRequest &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 f67ac7f422..ff26b87081 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -172,6 +172,8 @@ class APIConnection final : public APIServerConnection { #endif #ifdef USE_ZWAVE_PROXY + void zwave_proxy_subscribe(const ZWaveProxySubscribeRequest &msg) override; + void zwave_proxy_unsubscribe(const ZWaveProxyUnsubscribeRequest &msg) override; void zwave_proxy_from_device(const ZWaveProxyFromDeviceRequest &msg) override; void zwave_proxy_to_device(const ZWaveProxyToDeviceRequest &msg) override; #endif diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index c93f779fee..93a4244117 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -3008,8 +3008,16 @@ bool UpdateCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { } #endif #ifdef USE_ZWAVE_PROXY -void ZWaveProxyFromDeviceResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->data_ref_); } -void ZWaveProxyFromDeviceResponse::calculate_size(ProtoSize &size) const { size.add_length(1, this->data_ref_.size()); } +bool ZWaveProxyFromDeviceRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { + switch (field_id) { + case 1: + this->data = value.as_string(); + break; + default: + return false; + } + return true; +} bool ZWaveProxyToDeviceRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 1: @@ -3020,6 +3028,16 @@ bool ZWaveProxyToDeviceRequest::decode_length(uint32_t field_id, ProtoLengthDeli } return true; } +bool ZWaveProxySubscribeRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: + this->flags = value.as_uint32(); + break; + default: + return false; + } + return true; +} #endif } // namespace esphome::api diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index c6ef5c3c4c..d5b05b2c9f 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -2914,30 +2914,28 @@ class UpdateCommandRequest final : public CommandProtoMessage { }; #endif #ifdef USE_ZWAVE_PROXY -class ZWaveProxyFromDeviceRequest final : public ProtoMessage { +class ZWaveProxyFromDeviceRequest final : public ProtoDecodableMessage { public: static constexpr uint8_t MESSAGE_TYPE = 128; - static constexpr uint8_t ESTIMATED_SIZE = 0; + static constexpr uint8_t ESTIMATED_SIZE = 9; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "z_wave_proxy_from_device_request"; } #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 ZWaveProxyFromDeviceResponse final : public ProtoMessage { public: static constexpr uint8_t MESSAGE_TYPE = 129; - static constexpr uint8_t ESTIMATED_SIZE = 9; + static constexpr uint8_t ESTIMATED_SIZE = 0; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "z_wave_proxy_from_device_response"; } #endif - StringRef data_ref_{}; - void set_data(const StringRef &ref) { this->data_ref_ = ref; } - 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 @@ -2972,6 +2970,34 @@ class ZWaveProxyToDeviceResponse final : public ProtoMessage { protected: }; +class ZWaveProxySubscribeRequest final : public ProtoDecodableMessage { + public: + static constexpr uint8_t MESSAGE_TYPE = 132; + static constexpr uint8_t ESTIMATED_SIZE = 4; +#ifdef HAS_PROTO_MESSAGE_DUMP + const char *message_name() const override { return "z_wave_proxy_subscribe_request"; } +#endif + uint32_t flags{0}; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; +class ZWaveProxyUnsubscribeRequest final : public ProtoMessage { + public: + static constexpr uint8_t MESSAGE_TYPE = 133; + static constexpr uint8_t ESTIMATED_SIZE = 0; +#ifdef HAS_PROTO_MESSAGE_DUMP + const char *message_name() const override { return "z_wave_proxy_unsubscribe_request"; } +#endif +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: +}; #endif } // namespace esphome::api diff --git a/esphome/components/api/api_pb2_dump.cpp b/esphome/components/api/api_pb2_dump.cpp index 982d410c53..258225f8cb 100644 --- a/esphome/components/api/api_pb2_dump.cpp +++ b/esphome/components/api/api_pb2_dump.cpp @@ -2101,10 +2101,12 @@ void UpdateCommandRequest::dump_to(std::string &out) const { } #endif #ifdef USE_ZWAVE_PROXY -void ZWaveProxyFromDeviceRequest::dump_to(std::string &out) const { out.append("ZWaveProxyFromDeviceRequest {}"); } -void ZWaveProxyFromDeviceResponse::dump_to(std::string &out) const { dump_field(out, "data", this->data_ref_); } +void ZWaveProxyFromDeviceRequest::dump_to(std::string &out) const { dump_field(out, "data", this->data); } +void ZWaveProxyFromDeviceResponse::dump_to(std::string &out) const { out.append("ZWaveProxyFromDeviceResponse {}"); } void ZWaveProxyToDeviceRequest::dump_to(std::string &out) const { dump_field(out, "data", this->data); } void ZWaveProxyToDeviceResponse::dump_to(std::string &out) const { out.append("ZWaveProxyToDeviceResponse {}"); } +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 } // namespace esphome::api diff --git a/esphome/components/api/api_pb2_service.cpp b/esphome/components/api/api_pb2_service.cpp index d997a032e4..9410b3d86a 100644 --- a/esphome/components/api/api_pb2_service.cpp +++ b/esphome/components/api/api_pb2_service.cpp @@ -599,7 +599,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, #ifdef USE_ZWAVE_PROXY case ZWaveProxyFromDeviceRequest::MESSAGE_TYPE: { ZWaveProxyFromDeviceRequest msg; - // Empty message: no decode needed + msg.decode(msg_data, msg_size); #ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "on_z_wave_proxy_from_device_request: %s", msg.dump().c_str()); #endif @@ -617,6 +617,28 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, this->on_z_wave_proxy_to_device_request(msg); break; } +#endif +#ifdef USE_ZWAVE_PROXY + case ZWaveProxySubscribeRequest::MESSAGE_TYPE: { + ZWaveProxySubscribeRequest msg; + msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "on_z_wave_proxy_subscribe_request: %s", msg.dump().c_str()); +#endif + this->on_z_wave_proxy_subscribe_request(msg); + break; + } +#endif +#ifdef USE_ZWAVE_PROXY + case ZWaveProxyUnsubscribeRequest::MESSAGE_TYPE: { + ZWaveProxyUnsubscribeRequest msg; + // Empty message: no decode needed +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "on_z_wave_proxy_unsubscribe_request: %s", msg.dump().c_str()); +#endif + this->on_z_wave_proxy_unsubscribe_request(msg); + break; + } #endif default: break; @@ -932,6 +954,20 @@ void APIServerConnection::on_alarm_control_panel_command_request(const AlarmCont } #endif #ifdef USE_ZWAVE_PROXY +void APIServerConnection::on_z_wave_proxy_subscribe_request(const ZWaveProxySubscribeRequest &msg) { + if (this->check_authenticated_()) { + this->zwave_proxy_subscribe(msg); + } +} +#endif +#ifdef USE_ZWAVE_PROXY +void APIServerConnection::on_z_wave_proxy_unsubscribe_request(const ZWaveProxyUnsubscribeRequest &msg) { + if (this->check_authenticated_()) { + this->zwave_proxy_unsubscribe(msg); + } +} +#endif +#ifdef USE_ZWAVE_PROXY void APIServerConnection::on_z_wave_proxy_from_device_request(const ZWaveProxyFromDeviceRequest &msg) { if (this->check_authenticated_()) { this->zwave_proxy_from_device(msg); diff --git a/esphome/components/api/api_pb2_service.h b/esphome/components/api/api_pb2_service.h index 1d0952ec8e..11f9b876b6 100644 --- a/esphome/components/api/api_pb2_service.h +++ b/esphome/components/api/api_pb2_service.h @@ -214,6 +214,12 @@ class APIServerConnectionBase : public ProtoService { virtual void on_z_wave_proxy_to_device_request(const ZWaveProxyToDeviceRequest &value){}; #endif +#ifdef USE_ZWAVE_PROXY + virtual void on_z_wave_proxy_subscribe_request(const ZWaveProxySubscribeRequest &value){}; +#endif +#ifdef USE_ZWAVE_PROXY + virtual void on_z_wave_proxy_unsubscribe_request(const ZWaveProxyUnsubscribeRequest &value){}; +#endif protected: void read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override; }; @@ -341,6 +347,12 @@ class APIServerConnection : public APIServerConnectionBase { #ifdef USE_ALARM_CONTROL_PANEL virtual void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) = 0; #endif +#ifdef USE_ZWAVE_PROXY + virtual void zwave_proxy_subscribe(const ZWaveProxySubscribeRequest &msg) = 0; +#endif +#ifdef USE_ZWAVE_PROXY + virtual void zwave_proxy_unsubscribe(const ZWaveProxyUnsubscribeRequest &msg) = 0; +#endif #ifdef USE_ZWAVE_PROXY virtual void zwave_proxy_from_device(const ZWaveProxyFromDeviceRequest &msg) = 0; #endif @@ -469,6 +481,12 @@ class APIServerConnection : public APIServerConnectionBase { #ifdef USE_ALARM_CONTROL_PANEL void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) override; #endif +#ifdef USE_ZWAVE_PROXY + void on_z_wave_proxy_subscribe_request(const ZWaveProxySubscribeRequest &msg) override; +#endif +#ifdef USE_ZWAVE_PROXY + void on_z_wave_proxy_unsubscribe_request(const ZWaveProxyUnsubscribeRequest &msg) override; +#endif #ifdef USE_ZWAVE_PROXY void on_z_wave_proxy_from_device_request(const ZWaveProxyFromDeviceRequest &msg) override; #endif diff --git a/esphome/components/zwave_proxy/__init__.py b/esphome/components/zwave_proxy/__init__.py index f6b6ead42e..d93e1a904c 100644 --- a/esphome/components/zwave_proxy/__init__.py +++ b/esphome/components/zwave_proxy/__init__.py @@ -3,7 +3,7 @@ from esphome.components import uart import esphome.config_validation as cv from esphome.const import CONF_ID -DEPENDENCIES = ["uart"] +DEPENDENCIES = ["api", "uart"] zwave_proxy_ns = cg.esphome_ns.namespace("zwave_proxy") ZWaveProxy = zwave_proxy_ns.class_("ZWaveProxy", cg.Component, uart.UARTDevice) diff --git a/esphome/components/zwave_proxy/zwave_proxy.cpp b/esphome/components/zwave_proxy/zwave_proxy.cpp index 2e9f266c6b..acfa330226 100644 --- a/esphome/components/zwave_proxy/zwave_proxy.cpp +++ b/esphome/components/zwave_proxy/zwave_proxy.cpp @@ -28,8 +28,11 @@ void ZWaveProxy::loop() { return; } if (this->parse_byte_(byte)) { - // TODO: send frame to client(s)... - ESP_LOGD(TAG, "Frame complete"); + this->outgoing_request_.data = std::string((const char *) this->buffer_, this->buffer_index_); + ESP_LOGD(TAG, "Sending frame...%s", YESNO(this->api_connection_ != nullptr)); + if (this->api_connection_ != nullptr) { + this->api_connection_->send_message(this->outgoing_request_, api::ZWaveProxyFromDeviceRequest::MESSAGE_TYPE); + } } } this->status_clear_warning(); @@ -37,6 +40,22 @@ void ZWaveProxy::loop() { void ZWaveProxy::dump_config() { ESP_LOGCONFIG(TAG, "Z-Wave Proxy"); } +void ZWaveProxy::subscribe_api_connection(api::APIConnection *api_connection, uint32_t flags) { + if (this->api_connection_ != nullptr) { + ESP_LOGE(TAG, "Only one API subscription is allowed at a time"); + return; + } + this->api_connection_ = api_connection; +} + +void ZWaveProxy::unsubscribe_api_connection(api::APIConnection *api_connection) { + if (this->api_connection_ != api_connection) { + ESP_LOGV(TAG, "API connection is not subscribed"); + return; + } + 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()); diff --git a/esphome/components/zwave_proxy/zwave_proxy.h b/esphome/components/zwave_proxy/zwave_proxy.h index 3c69002890..04112848e8 100644 --- a/esphome/components/zwave_proxy/zwave_proxy.h +++ b/esphome/components/zwave_proxy/zwave_proxy.h @@ -1,5 +1,7 @@ #pragma once +#include "esphome/components/api/api_connection.h" +#include "esphome/components/api/api_pb2.h" #include "esphome/core/component.h" #include "esphome/components/uart/uart.h" @@ -37,6 +39,10 @@ class ZWaveProxy : public uart::UARTDevice, public Component { void loop() override; void dump_config() override; + void subscribe_api_connection(api::APIConnection *api_connection, uint32_t flags); + void unsubscribe_api_connection(api::APIConnection *api_connection); + api::APIConnection *get_api_connection() { return this->api_connection_; } + uint32_t get_feature_flags() const { return ZWaveProxyFeature::FEATURE_ZWAVE_PROXY_ENABLED; } void send_frame(const std::string &data); @@ -47,11 +53,16 @@ class ZWaveProxy : public uart::UARTDevice, public Component { void parse_start_(uint8_t byte); bool response_handler_(); + 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 ZWaveParsingState parsing_state_{ZWAVE_PARSING_STATE_WAIT_START}; + + // Pre-allocated response message - always ready to send + api::ZWaveProxyFromDeviceRequest outgoing_request_; }; extern ZWaveProxy *global_zwave_proxy; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)