diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 6b19f2026a..5d8d59b1f8 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -69,6 +69,9 @@ service APIConnection { rpc voice_assistant_set_configuration(VoiceAssistantSetConfiguration) returns (void) {} rpc alarm_control_panel_command (AlarmControlPanelCommandRequest) returns (void) {} + + rpc zwave_proxy_read(ZWaveProxyReadRequest) returns (void) {} + rpc zwave_proxy_write(ZWaveProxyWriteRequest) returns (void) {} } @@ -255,6 +258,9 @@ message DeviceInfoResponse { // Top-level area info to phase out suggested_area AreaInfo area = 22 [(field_ifdef) = "USE_AREAS"]; + + // Indicates if Z-Wave proxy support is available and features supported + uint32 zwave_proxy_feature_flags = 23 [(field_ifdef) = "USE_ZWAVE_PROXY"]; } message ListEntitiesRequest { @@ -2275,3 +2281,37 @@ message UpdateCommandRequest { UpdateCommand command = 2; uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"]; } + +// ==================== Z-WAVE ==================== + +message ZWaveProxyReadRequest { + option (id) = 128; + option (source) = SOURCE_CLIENT; + option (ifdef) = "USE_ZWAVE_PROXY"; + option (no_delay) = true; +} + +message ZWaveProxyReadResponse { + option (id) = 129; + option (source) = SOURCE_SERVER; + option (ifdef) = "USE_ZWAVE_PROXY"; + option (no_delay) = true; + + string data = 1; +} + +message ZWaveProxyWriteRequest { + option (id) = 130; + option (source) = SOURCE_CLIENT; + option (ifdef) = "USE_ZWAVE_PROXY"; + option (no_delay) = true; + + string data = 1; +} + +message ZWaveProxyWriteResponse { + option (id) = 131; + option (source) = SOURCE_SERVER; + option (ifdef) = "USE_ZWAVE_PROXY"; + option (no_delay) = true; +} diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index ced0f489be..7c4abbe47f 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -30,6 +30,9 @@ #ifdef USE_VOICE_ASSISTANT #include "esphome/components/voice_assistant/voice_assistant.h" #endif +#ifdef USE_ZWAVE_PROXY +#include "esphome/components/zwave_proxy/zwave_proxy.h" +#endif namespace esphome::api { @@ -1203,7 +1206,13 @@ void APIConnection::voice_assistant_set_configuration(const VoiceAssistantSetCon voice_assistant::global_voice_assistant->on_set_configuration(msg.active_wake_words); } } +#endif +#ifdef USE_ZWAVE_PROXY +void APIConnection::zwave_proxy_read(const ZWaveProxyReadRequest &msg) {} +void APIConnection::zwave_proxy_write(const ZWaveProxyWriteRequest &msg) { + zwave_proxy::global_zwave_proxy->send_frame(msg.data); +} #endif #ifdef USE_ALARM_CONTROL_PANEL @@ -1468,6 +1477,9 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) { #ifdef USE_VOICE_ASSISTANT resp.voice_assistant_feature_flags = voice_assistant::global_voice_assistant->get_feature_flags(); #endif +#ifdef USE_ZWAVE_PROXY + resp.zwave_proxy_feature_flags = zwave_proxy::global_zwave_proxy->get_feature_flags(); +#endif #ifdef USE_API_NOISE resp.api_encryption_supported = true; #endif diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index f711502746..e07ba315f4 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -171,6 +171,11 @@ class APIConnection final : public APIServerConnection { void voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) override; #endif +#ifdef USE_ZWAVE_PROXY + void zwave_proxy_read(const ZWaveProxyReadRequest &msg) override; + void zwave_proxy_write(const ZWaveProxyWriteRequest &msg) override; +#endif + #ifdef USE_ALARM_CONTROL_PANEL bool send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel); void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) override; diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 476e3c88d0..2154de05b9 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -127,6 +127,9 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const { #ifdef USE_AREAS buffer.encode_message(22, this->area); #endif +#ifdef USE_ZWAVE_PROXY + buffer.encode_uint32(23, this->zwave_proxy_feature_flags); +#endif } void DeviceInfoResponse::calculate_size(ProtoSize &size) const { #ifdef USE_API_PASSWORD @@ -179,6 +182,9 @@ void DeviceInfoResponse::calculate_size(ProtoSize &size) const { #ifdef USE_AREAS size.add_message_object(2, this->area); #endif +#ifdef USE_ZWAVE_PROXY + size.add_uint32(2, this->zwave_proxy_feature_flags); +#endif } #ifdef USE_BINARY_SENSOR void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer buffer) const { @@ -3001,5 +3007,19 @@ bool UpdateCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { return true; } #endif +#ifdef USE_ZWAVE_PROXY +void ZWaveProxyReadResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->data_ref_); } +void ZWaveProxyReadResponse::calculate_size(ProtoSize &size) const { size.add_length(1, this->data_ref_.size()); } +bool ZWaveProxyWriteRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { + switch (field_id) { + case 1: + this->data = value.as_string(); + 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 abdf0e6121..5712c139e2 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -490,7 +490,7 @@ class DeviceInfo final : public ProtoMessage { class DeviceInfoResponse final : public ProtoMessage { public: static constexpr uint8_t MESSAGE_TYPE = 10; - static constexpr uint8_t ESTIMATED_SIZE = 247; + static constexpr uint8_t ESTIMATED_SIZE = 252; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "device_info_response"; } #endif @@ -550,6 +550,9 @@ class DeviceInfoResponse final : public ProtoMessage { #endif #ifdef USE_AREAS AreaInfo area{}; +#endif +#ifdef USE_ZWAVE_PROXY + uint32_t zwave_proxy_feature_flags{0}; #endif void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; @@ -2910,5 +2913,65 @@ class UpdateCommandRequest final : public CommandProtoMessage { bool decode_varint(uint32_t field_id, ProtoVarInt value) override; }; #endif +#ifdef USE_ZWAVE_PROXY +class ZWaveProxyReadRequest final : public ProtoMessage { + public: + static constexpr uint8_t MESSAGE_TYPE = 128; + static constexpr uint8_t ESTIMATED_SIZE = 0; +#ifdef HAS_PROTO_MESSAGE_DUMP + const char *message_name() const override { return "z_wave_proxy_read_request"; } +#endif +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: +}; +class ZWaveProxyReadResponse final : public ProtoMessage { + 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_read_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 + + protected: +}; +class ZWaveProxyWriteRequest final : public ProtoDecodableMessage { + public: + static constexpr uint8_t MESSAGE_TYPE = 130; + static constexpr uint8_t ESTIMATED_SIZE = 9; +#ifdef HAS_PROTO_MESSAGE_DUMP + const char *message_name() const override { return "z_wave_proxy_write_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 ZWaveProxyWriteResponse final : public ProtoMessage { + public: + static constexpr uint8_t MESSAGE_TYPE = 131; + static constexpr uint8_t ESTIMATED_SIZE = 0; +#ifdef HAS_PROTO_MESSAGE_DUMP + const char *message_name() const override { return "z_wave_proxy_write_response"; } +#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 7af322f96d..660852423b 100644 --- a/esphome/components/api/api_pb2_dump.cpp +++ b/esphome/components/api/api_pb2_dump.cpp @@ -749,6 +749,9 @@ void DeviceInfoResponse::dump_to(std::string &out) const { this->area.dump_to(out); out.append("\n"); #endif +#ifdef USE_ZWAVE_PROXY + dump_field(out, "zwave_proxy_feature_flags", this->zwave_proxy_feature_flags); +#endif } void ListEntitiesRequest::dump_to(std::string &out) const { out.append("ListEntitiesRequest {}"); } void ListEntitiesDoneResponse::dump_to(std::string &out) const { out.append("ListEntitiesDoneResponse {}"); } @@ -2097,6 +2100,12 @@ void UpdateCommandRequest::dump_to(std::string &out) const { #endif } #endif +#ifdef USE_ZWAVE_PROXY +void ZWaveProxyReadRequest::dump_to(std::string &out) const { out.append("ZWaveProxyReadRequest {}"); } +void ZWaveProxyReadResponse::dump_to(std::string &out) const { dump_field(out, "data", this->data_ref_); } +void ZWaveProxyWriteRequest::dump_to(std::string &out) const { dump_field(out, "data", this->data); } +void ZWaveProxyWriteResponse::dump_to(std::string &out) const { out.append("ZWaveProxyWriteResponse {}"); } +#endif } // namespace esphome::api diff --git a/esphome/components/api/api_pb2_service.cpp b/esphome/components/api/api_pb2_service.cpp index 6b7b8b9ebd..a85150285f 100644 --- a/esphome/components/api/api_pb2_service.cpp +++ b/esphome/components/api/api_pb2_service.cpp @@ -595,6 +595,28 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, this->on_bluetooth_scanner_set_mode_request(msg); break; } +#endif +#ifdef USE_ZWAVE_PROXY + case ZWaveProxyReadRequest::MESSAGE_TYPE: { + ZWaveProxyReadRequest msg; + // Empty message: no decode needed +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "on_z_wave_proxy_read_request: %s", msg.dump().c_str()); +#endif + this->on_z_wave_proxy_read_request(msg); + break; + } +#endif +#ifdef USE_ZWAVE_PROXY + case ZWaveProxyWriteRequest::MESSAGE_TYPE: { + ZWaveProxyWriteRequest msg; + msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "on_z_wave_proxy_write_request: %s", msg.dump().c_str()); +#endif + this->on_z_wave_proxy_write_request(msg); + break; + } #endif default: break; @@ -909,5 +931,19 @@ void APIServerConnection::on_alarm_control_panel_command_request(const AlarmCont } } #endif +#ifdef USE_ZWAVE_PROXY +void APIServerConnection::on_z_wave_proxy_read_request(const ZWaveProxyReadRequest &msg) { + if (this->check_authenticated_()) { + this->zwave_proxy_read(msg); + } +} +#endif +#ifdef USE_ZWAVE_PROXY +void APIServerConnection::on_z_wave_proxy_write_request(const ZWaveProxyWriteRequest &msg) { + if (this->check_authenticated_()) { + this->zwave_proxy_write(msg); + } +} +#endif } // namespace esphome::api diff --git a/esphome/components/api/api_pb2_service.h b/esphome/components/api/api_pb2_service.h index 6172e33bf6..05009a368a 100644 --- a/esphome/components/api/api_pb2_service.h +++ b/esphome/components/api/api_pb2_service.h @@ -206,6 +206,14 @@ 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_read_request(const ZWaveProxyReadRequest &value){}; +#endif + +#ifdef USE_ZWAVE_PROXY + virtual void on_z_wave_proxy_write_request(const ZWaveProxyWriteRequest &value){}; +#endif + protected: void read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override; }; @@ -332,6 +340,12 @@ class APIServerConnection : public APIServerConnectionBase { #endif #ifdef USE_ALARM_CONTROL_PANEL virtual void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) = 0; +#endif +#ifdef USE_ZWAVE_PROXY + virtual void zwave_proxy_read(const ZWaveProxyReadRequest &msg) = 0; +#endif +#ifdef USE_ZWAVE_PROXY + virtual void zwave_proxy_write(const ZWaveProxyWriteRequest &msg) = 0; #endif protected: void on_hello_request(const HelloRequest &msg) override; @@ -455,6 +469,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_read_request(const ZWaveProxyReadRequest &msg) override; +#endif +#ifdef USE_ZWAVE_PROXY + void on_z_wave_proxy_write_request(const ZWaveProxyWriteRequest &msg) override; +#endif }; } // namespace esphome::api diff --git a/esphome/components/zwave_proxy/__init__.py b/esphome/components/zwave_proxy/__init__.py index 8adf39d28d..f6b6ead42e 100644 --- a/esphome/components/zwave_proxy/__init__.py +++ b/esphome/components/zwave_proxy/__init__.py @@ -23,3 +23,4 @@ async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await uart.register_uart_device(var, config) + cg.add_define("USE_ZWAVE_PROXY") diff --git a/esphome/components/zwave_proxy/zwave_proxy.cpp b/esphome/components/zwave_proxy/zwave_proxy.cpp index fe67705407..2e9f266c6b 100644 --- a/esphome/components/zwave_proxy/zwave_proxy.cpp +++ b/esphome/components/zwave_proxy/zwave_proxy.cpp @@ -7,6 +7,8 @@ namespace zwave_proxy { static const char *TAG = "zwave_proxy"; +ZWaveProxy::ZWaveProxy() { global_zwave_proxy = this; } + void ZWaveProxy::setup() { // Get capabilities command sent once here just to test communication for component development uint8_t get_capabilities_cmd[] = {0x01, 0x03, 0x00, 0x07, 0xfb}; @@ -25,19 +27,27 @@ void ZWaveProxy::loop() { this->status_set_warning("Failed reading from UART"); return; } - this->parse_byte_(byte); + if (this->parse_byte_(byte)) { + // TODO: send frame to client(s)... + ESP_LOGD(TAG, "Frame complete"); + } } this->status_clear_warning(); } void ZWaveProxy::dump_config() { ESP_LOGCONFIG(TAG, "Z-Wave Proxy"); } -void ZWaveProxy::send_frame(std::vector &data) { +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::parse_byte_(uint8_t byte) { +bool ZWaveProxy::parse_byte_(uint8_t byte) { // Basic parsing logic for received frames switch (this->parsing_state_) { case ZWAVE_PARSING_STATE_WAIT_START: @@ -81,6 +91,7 @@ void ZWaveProxy::parse_byte_(uint8_t byte) { } else { this->parsing_state_ = ZWAVE_PARSING_STATE_SEND_ACK; ESP_LOGD(TAG, "Received frame: %s", format_hex_pretty(this->buffer_, this->buffer_index_).c_str()); + return true; } break; case ZWAVE_PARSING_STATE_SEND_ACK: @@ -90,6 +101,7 @@ void ZWaveProxy::parse_byte_(uint8_t byte) { ESP_LOGD(TAG, "Received unknown byte: 0x%02X", byte); break; } + return false; } void ZWaveProxy::parse_start_(uint8_t byte) { @@ -139,5 +151,7 @@ bool ZWaveProxy::response_handler_() { return true; } +ZWaveProxy *global_zwave_proxy = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + } // namespace zwave_proxy } // namespace esphome diff --git a/esphome/components/zwave_proxy/zwave_proxy.h b/esphome/components/zwave_proxy/zwave_proxy.h index 0c9f93af95..3c69002890 100644 --- a/esphome/components/zwave_proxy/zwave_proxy.h +++ b/esphome/components/zwave_proxy/zwave_proxy.h @@ -25,16 +25,25 @@ enum ZWaveParsingState : uint8_t { ZWAVE_PARSING_STATE_SEND_NAK, }; +enum ZWaveProxyFeature : uint32_t { + FEATURE_ZWAVE_PROXY_ENABLED = 1 << 0, +}; + class ZWaveProxy : public uart::UARTDevice, public Component { public: + ZWaveProxy(); + void setup() override; void loop() override; void dump_config() override; - void send_frame(std::vector &data); + 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); protected: - void parse_byte_(uint8_t byte); + bool parse_byte_(uint8_t byte); // Returns true if frame parsing was completed (a frame is ready in the buffer) void parse_start_(uint8_t byte); bool response_handler_(); @@ -45,5 +54,7 @@ class ZWaveProxy : public uart::UARTDevice, public Component { ZWaveParsingState parsing_state_{ZWAVE_PARSING_STATE_WAIT_START}; }; +extern ZWaveProxy *global_zwave_proxy; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + } // namespace zwave_proxy } // namespace esphome diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 5df3bcf475..fce92dbf1d 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -100,6 +100,7 @@ #define USE_UART_DEBUGGER #define USE_UPDATE #define USE_VALVE +#define USE_ZWAVE_PROXY // Feature flags which do not work for zephyr #ifndef USE_ZEPHYR