mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +00:00 
			
		
		
		
	[zwave_proxy] New component (#10762)
Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
		| @@ -66,6 +66,9 @@ service APIConnection { | ||||
|   rpc voice_assistant_set_configuration(VoiceAssistantSetConfiguration) returns (void) {} | ||||
|  | ||||
|   rpc alarm_control_panel_command (AlarmControlPanelCommandRequest) returns (void) {} | ||||
|  | ||||
|   rpc zwave_proxy_frame(ZWaveProxyFrame) returns (void) {} | ||||
|   rpc zwave_proxy_request(ZWaveProxyRequest) returns (void) {} | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -254,6 +257,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 { | ||||
| @@ -2276,3 +2282,26 @@ message UpdateCommandRequest { | ||||
|   UpdateCommand command = 2; | ||||
|   uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"]; | ||||
| } | ||||
|  | ||||
| // ==================== Z-WAVE ==================== | ||||
|  | ||||
| message ZWaveProxyFrame { | ||||
|   option (id) = 128; | ||||
|   option (source) = SOURCE_BOTH; | ||||
|   option (ifdef) = "USE_ZWAVE_PROXY"; | ||||
|   option (no_delay) = true; | ||||
|  | ||||
|   bytes data = 1 [(fixed_array_size) = 257]; | ||||
| } | ||||
|  | ||||
| enum ZWaveProxyRequestType { | ||||
|   ZWAVE_PROXY_REQUEST_TYPE_SUBSCRIBE = 0; | ||||
|   ZWAVE_PROXY_REQUEST_TYPE_UNSUBSCRIBE = 1; | ||||
| } | ||||
| message ZWaveProxyRequest { | ||||
|   option (id) = 129; | ||||
|   option (source) = SOURCE_CLIENT; | ||||
|   option (ifdef) = "USE_ZWAVE_PROXY"; | ||||
|  | ||||
|   ZWaveProxyRequestType type = 1; | ||||
| } | ||||
|   | ||||
| @@ -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,16 @@ 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_frame(const ZWaveProxyFrame &msg) { | ||||
|   zwave_proxy::global_zwave_proxy->send_frame(msg.data, msg.data_len); | ||||
| } | ||||
|  | ||||
| void APIConnection::zwave_proxy_request(const ZWaveProxyRequest &msg) { | ||||
|   zwave_proxy::global_zwave_proxy->zwave_proxy_request(this, msg.type); | ||||
| } | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_ALARM_CONTROL_PANEL | ||||
| @@ -1460,6 +1472,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 | ||||
|   | ||||
| @@ -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_frame(const ZWaveProxyFrame &msg) override; | ||||
|   void zwave_proxy_request(const ZWaveProxyRequest &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; | ||||
|   | ||||
| @@ -129,6 +129,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 | ||||
| @@ -181,6 +184,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 { | ||||
| @@ -3013,5 +3019,35 @@ bool UpdateCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { | ||||
|   return true; | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_ZWAVE_PROXY | ||||
| bool ZWaveProxyFrame::decode_length(uint32_t field_id, ProtoLengthDelimited value) { | ||||
|   switch (field_id) { | ||||
|     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 ZWaveProxyRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { | ||||
|   switch (field_id) { | ||||
|     case 1: | ||||
|       this->type = static_cast<enums::ZWaveProxyRequestType>(value.as_uint32()); | ||||
|       break; | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
|   return true; | ||||
| } | ||||
| #endif | ||||
|  | ||||
| }  // namespace esphome::api | ||||
|   | ||||
| @@ -276,6 +276,12 @@ enum UpdateCommand : uint32_t { | ||||
|   UPDATE_COMMAND_CHECK = 2, | ||||
| }; | ||||
| #endif | ||||
| #ifdef USE_ZWAVE_PROXY | ||||
| enum ZWaveProxyRequestType : uint32_t { | ||||
|   ZWAVE_PROXY_REQUEST_TYPE_SUBSCRIBE = 0, | ||||
|   ZWAVE_PROXY_REQUEST_TYPE_UNSUBSCRIBE = 1, | ||||
| }; | ||||
| #endif | ||||
|  | ||||
| }  // namespace enums | ||||
|  | ||||
| @@ -492,7 +498,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 | ||||
| @@ -552,6 +558,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; | ||||
| @@ -2913,5 +2922,40 @@ class UpdateCommandRequest final : public CommandProtoMessage { | ||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||
| }; | ||||
| #endif | ||||
| #ifdef USE_ZWAVE_PROXY | ||||
| class ZWaveProxyFrame final : public ProtoDecodableMessage { | ||||
|  public: | ||||
|   static constexpr uint8_t MESSAGE_TYPE = 128; | ||||
|   static constexpr uint8_t ESTIMATED_SIZE = 33; | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   const char *message_name() const override { return "z_wave_proxy_frame"; } | ||||
| #endif | ||||
|   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: | ||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||
| }; | ||||
| class ZWaveProxyRequest final : public ProtoDecodableMessage { | ||||
|  public: | ||||
|   static constexpr uint8_t MESSAGE_TYPE = 129; | ||||
|   static constexpr uint8_t ESTIMATED_SIZE = 2; | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   const char *message_name() const override { return "z_wave_proxy_request"; } | ||||
| #endif | ||||
|   enums::ZWaveProxyRequestType type{}; | ||||
| #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; | ||||
| }; | ||||
| #endif | ||||
|  | ||||
| }  // namespace esphome::api | ||||
|   | ||||
| @@ -655,6 +655,18 @@ template<> const char *proto_enum_to_string<enums::UpdateCommand>(enums::UpdateC | ||||
|   } | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_ZWAVE_PROXY | ||||
| template<> const char *proto_enum_to_string<enums::ZWaveProxyRequestType>(enums::ZWaveProxyRequestType value) { | ||||
|   switch (value) { | ||||
|     case enums::ZWAVE_PROXY_REQUEST_TYPE_SUBSCRIBE: | ||||
|       return "ZWAVE_PROXY_REQUEST_TYPE_SUBSCRIBE"; | ||||
|     case enums::ZWAVE_PROXY_REQUEST_TYPE_UNSUBSCRIBE: | ||||
|       return "ZWAVE_PROXY_REQUEST_TYPE_UNSUBSCRIBE"; | ||||
|     default: | ||||
|       return "UNKNOWN"; | ||||
|   } | ||||
| } | ||||
| #endif | ||||
|  | ||||
| void HelloRequest::dump_to(std::string &out) const { | ||||
|   MessageDumpHelper helper(out, "HelloRequest"); | ||||
| @@ -754,6 +766,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 {}"); } | ||||
| @@ -2107,6 +2122,18 @@ void UpdateCommandRequest::dump_to(std::string &out) const { | ||||
| #endif | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_ZWAVE_PROXY | ||||
| 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 ZWaveProxyRequest::dump_to(std::string &out) const { | ||||
|   MessageDumpHelper helper(out, "ZWaveProxyRequest"); | ||||
|   dump_field(out, "type", static_cast<enums::ZWaveProxyRequestType>(this->type)); | ||||
| } | ||||
| #endif | ||||
|  | ||||
| }  // namespace esphome::api | ||||
|  | ||||
|   | ||||
| @@ -588,6 +588,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 ZWaveProxyFrame::MESSAGE_TYPE: { | ||||
|       ZWaveProxyFrame msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_z_wave_proxy_frame: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_z_wave_proxy_frame(msg); | ||||
|       break; | ||||
|     } | ||||
| #endif | ||||
| #ifdef USE_ZWAVE_PROXY | ||||
|     case ZWaveProxyRequest::MESSAGE_TYPE: { | ||||
|       ZWaveProxyRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_z_wave_proxy_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_z_wave_proxy_request(msg); | ||||
|       break; | ||||
|     } | ||||
| #endif | ||||
|     default: | ||||
|       break; | ||||
| @@ -899,5 +921,19 @@ void APIServerConnection::on_alarm_control_panel_command_request(const AlarmCont | ||||
|   } | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_ZWAVE_PROXY | ||||
| void APIServerConnection::on_z_wave_proxy_frame(const ZWaveProxyFrame &msg) { | ||||
|   if (this->check_authenticated_()) { | ||||
|     this->zwave_proxy_frame(msg); | ||||
|   } | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_ZWAVE_PROXY | ||||
| void APIServerConnection::on_z_wave_proxy_request(const ZWaveProxyRequest &msg) { | ||||
|   if (this->check_authenticated_()) { | ||||
|     this->zwave_proxy_request(msg); | ||||
|   } | ||||
| } | ||||
| #endif | ||||
|  | ||||
| }  // namespace esphome::api | ||||
|   | ||||
| @@ -207,6 +207,12 @@ 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(const ZWaveProxyFrame &value){}; | ||||
| #endif | ||||
| #ifdef USE_ZWAVE_PROXY | ||||
|   virtual void on_z_wave_proxy_request(const ZWaveProxyRequest &value){}; | ||||
| #endif | ||||
|  protected: | ||||
|   void read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override; | ||||
| @@ -335,6 +341,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_frame(const ZWaveProxyFrame &msg) = 0; | ||||
| #endif | ||||
| #ifdef USE_ZWAVE_PROXY | ||||
|   virtual void zwave_proxy_request(const ZWaveProxyRequest &msg) = 0; | ||||
| #endif | ||||
|  protected: | ||||
|   void on_hello_request(const HelloRequest &msg) override; | ||||
| @@ -459,6 +471,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_frame(const ZWaveProxyFrame &msg) override; | ||||
| #endif | ||||
| #ifdef USE_ZWAVE_PROXY | ||||
|   void on_z_wave_proxy_request(const ZWaveProxyRequest &msg) override; | ||||
| #endif | ||||
| }; | ||||
|  | ||||
| }  // namespace esphome::api | ||||
|   | ||||
							
								
								
									
										43
									
								
								esphome/components/zwave_proxy/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								esphome/components/zwave_proxy/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | ||||
| import esphome.codegen as cg | ||||
| from esphome.components import uart | ||||
| import esphome.config_validation as cv | ||||
| from esphome.const import CONF_ID, CONF_POWER_SAVE_MODE, CONF_WIFI | ||||
| import esphome.final_validate as fv | ||||
|  | ||||
| CODEOWNERS = ["@kbx81"] | ||||
| DEPENDENCIES = ["api", "uart"] | ||||
|  | ||||
| zwave_proxy_ns = cg.esphome_ns.namespace("zwave_proxy") | ||||
| ZWaveProxy = zwave_proxy_ns.class_("ZWaveProxy", cg.Component, uart.UARTDevice) | ||||
|  | ||||
|  | ||||
| def final_validate(config): | ||||
|     full_config = fv.full_config.get() | ||||
|     if (wifi_conf := full_config.get(CONF_WIFI)) and ( | ||||
|         wifi_conf.get(CONF_POWER_SAVE_MODE).lower() != "none" | ||||
|     ): | ||||
|         raise cv.Invalid( | ||||
|             f"{CONF_WIFI} {CONF_POWER_SAVE_MODE} must be set to 'none' when using Z-Wave proxy" | ||||
|         ) | ||||
|  | ||||
|     return config | ||||
|  | ||||
|  | ||||
| CONFIG_SCHEMA = ( | ||||
|     cv.Schema( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(ZWaveProxy), | ||||
|         } | ||||
|     ) | ||||
|     .extend(cv.COMPONENT_SCHEMA) | ||||
|     .extend(uart.UART_DEVICE_SCHEMA) | ||||
| ) | ||||
|  | ||||
| FINAL_VALIDATE_SCHEMA = final_validate | ||||
|  | ||||
|  | ||||
| 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") | ||||
							
								
								
									
										224
									
								
								esphome/components/zwave_proxy/zwave_proxy.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										224
									
								
								esphome/components/zwave_proxy/zwave_proxy.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,224 @@ | ||||
| #include "zwave_proxy.h" | ||||
| #include "esphome/core/helpers.h" | ||||
| #include "esphome/core/log.h" | ||||
| #include "esphome/core/util.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace zwave_proxy { | ||||
|  | ||||
| static const char *const TAG = "zwave_proxy"; | ||||
|  | ||||
| ZWaveProxy::ZWaveProxy() { global_zwave_proxy = this; } | ||||
|  | ||||
| void ZWaveProxy::loop() { | ||||
|   if (this->response_handler_()) { | ||||
|     ESP_LOGV(TAG, "Handled late response"); | ||||
|   } | ||||
|   if (this->api_connection_ != nullptr && (!this->api_connection_->is_connection_setup() || !api_is_connected())) { | ||||
|     ESP_LOGW(TAG, "Subscriber disconnected"); | ||||
|     this->api_connection_ = nullptr;  // Unsubscribe if disconnected | ||||
|   } | ||||
|  | ||||
|   while (this->available()) { | ||||
|     uint8_t byte; | ||||
|     if (!this->read_byte(&byte)) { | ||||
|       this->status_set_warning("UART read failed"); | ||||
|       return; | ||||
|     } | ||||
|     if (this->parse_byte_(byte)) { | ||||
|       ESP_LOGV(TAG, "Sending to client: %s", YESNO(this->api_connection_ != nullptr)); | ||||
|       if (this->api_connection_ != nullptr) { | ||||
|         // 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_, this->outgoing_proto_msg_.data_len); | ||||
|         this->api_connection_->send_message(this->outgoing_proto_msg_, api::ZWaveProxyFrame::MESSAGE_TYPE); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|   this->status_clear_warning(); | ||||
| } | ||||
|  | ||||
| void ZWaveProxy::dump_config() { ESP_LOGCONFIG(TAG, "Z-Wave Proxy"); } | ||||
|  | ||||
| void ZWaveProxy::zwave_proxy_request(api::APIConnection *api_connection, api::enums::ZWaveProxyRequestType type) { | ||||
|   switch (type) { | ||||
|     case api::enums::ZWAVE_PROXY_REQUEST_TYPE_SUBSCRIBE: | ||||
|       if (this->api_connection_ != nullptr) { | ||||
|         ESP_LOGE(TAG, "Only one API subscription is allowed at a time"); | ||||
|         return; | ||||
|       } | ||||
|       this->api_connection_ = api_connection; | ||||
|       ESP_LOGV(TAG, "API connection is now subscribed"); | ||||
|       break; | ||||
|     case api::enums::ZWAVE_PROXY_REQUEST_TYPE_UNSUBSCRIBE: | ||||
|       if (this->api_connection_ != api_connection) { | ||||
|         ESP_LOGV(TAG, "API connection is not subscribed"); | ||||
|         return; | ||||
|       } | ||||
|       this->api_connection_ = nullptr; | ||||
|       break; | ||||
|     default: | ||||
|       ESP_LOGW(TAG, "Unknown request type: %d", type); | ||||
|       break; | ||||
|   } | ||||
| } | ||||
|  | ||||
| void ZWaveProxy::send_frame(const uint8_t *data, size_t length) { | ||||
|   if (length == 1 && data[0] == this->last_response_) { | ||||
|     ESP_LOGV(TAG, "Skipping sending duplicate response: 0x%02X", data[0]); | ||||
|     return; | ||||
|   } | ||||
|   ESP_LOGVV(TAG, "Sending: %s", format_hex_pretty(data, length).c_str()); | ||||
|   this->write_array(data, length); | ||||
| } | ||||
|  | ||||
| bool ZWaveProxy::parse_byte_(uint8_t byte) { | ||||
|   bool frame_completed = false; | ||||
|   // Basic parsing logic for received frames | ||||
|   switch (this->parsing_state_) { | ||||
|     case ZWAVE_PARSING_STATE_WAIT_START: | ||||
|       this->parse_start_(byte); | ||||
|       break; | ||||
|     case ZWAVE_PARSING_STATE_WAIT_LENGTH: | ||||
|       if (!byte) { | ||||
|         ESP_LOGW(TAG, "Invalid LENGTH: %u", byte); | ||||
|         this->parsing_state_ = ZWAVE_PARSING_STATE_SEND_NAK; | ||||
|         return false; | ||||
|       } | ||||
|       ESP_LOGVV(TAG, "Received LENGTH: %u", byte); | ||||
|       this->end_frame_after_ = this->buffer_index_ + byte; | ||||
|       ESP_LOGVV(TAG, "Calculated EOF: %u", this->end_frame_after_); | ||||
|       this->buffer_[this->buffer_index_++] = byte; | ||||
|       this->checksum_ ^= byte; | ||||
|       this->parsing_state_ = ZWAVE_PARSING_STATE_WAIT_TYPE; | ||||
|       break; | ||||
|     case ZWAVE_PARSING_STATE_WAIT_TYPE: | ||||
|       this->buffer_[this->buffer_index_++] = byte; | ||||
|       ESP_LOGVV(TAG, "Received TYPE: 0x%02X", byte); | ||||
|       this->checksum_ ^= byte; | ||||
|       this->parsing_state_ = ZWAVE_PARSING_STATE_WAIT_COMMAND_ID; | ||||
|       break; | ||||
|     case ZWAVE_PARSING_STATE_WAIT_COMMAND_ID: | ||||
|       this->buffer_[this->buffer_index_++] = byte; | ||||
|       ESP_LOGVV(TAG, "Received COMMAND ID: 0x%02X", byte); | ||||
|       this->checksum_ ^= byte; | ||||
|       this->parsing_state_ = ZWAVE_PARSING_STATE_WAIT_PAYLOAD; | ||||
|       break; | ||||
|     case ZWAVE_PARSING_STATE_WAIT_PAYLOAD: | ||||
|       this->buffer_[this->buffer_index_++] = byte; | ||||
|       this->checksum_ ^= byte; | ||||
|       ESP_LOGVV(TAG, "Received PAYLOAD: 0x%02X", byte); | ||||
|       if (this->buffer_index_ >= this->end_frame_after_) { | ||||
|         this->parsing_state_ = ZWAVE_PARSING_STATE_WAIT_CHECKSUM; | ||||
|       } | ||||
|       break; | ||||
|     case ZWAVE_PARSING_STATE_WAIT_CHECKSUM: | ||||
|       this->buffer_[this->buffer_index_++] = byte; | ||||
|       ESP_LOGVV(TAG, "Received CHECKSUM: 0x%02X", byte); | ||||
|       ESP_LOGV(TAG, "Calculated CHECKSUM: 0x%02X", this->checksum_); | ||||
|       if (this->checksum_ != byte) { | ||||
|         ESP_LOGW(TAG, "Bad checksum: expected 0x%02X, got 0x%02X", this->checksum_, byte); | ||||
|         this->parsing_state_ = ZWAVE_PARSING_STATE_SEND_NAK; | ||||
|       } else { | ||||
|         this->parsing_state_ = ZWAVE_PARSING_STATE_SEND_ACK; | ||||
|         ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(this->buffer_, this->buffer_index_).c_str()); | ||||
|         frame_completed = true; | ||||
|       } | ||||
|       this->response_handler_(); | ||||
|       break; | ||||
|     case ZWAVE_PARSING_STATE_READ_BL_MENU: | ||||
|       this->buffer_[this->buffer_index_++] = byte; | ||||
|       if (!byte) { | ||||
|         this->parsing_state_ = ZWAVE_PARSING_STATE_WAIT_START; | ||||
|         frame_completed = true; | ||||
|       } | ||||
|       break; | ||||
|     case ZWAVE_PARSING_STATE_SEND_ACK: | ||||
|     case ZWAVE_PARSING_STATE_SEND_NAK: | ||||
|       break;  // Should not happen, handled in loop() | ||||
|     default: | ||||
|       ESP_LOGW(TAG, "Bad parsing state; resetting"); | ||||
|       this->parsing_state_ = ZWAVE_PARSING_STATE_WAIT_START; | ||||
|       break; | ||||
|   } | ||||
|   return frame_completed; | ||||
| } | ||||
|  | ||||
| void ZWaveProxy::parse_start_(uint8_t byte) { | ||||
|   this->buffer_index_ = 0; | ||||
|   this->checksum_ = 0xFF; | ||||
|   this->parsing_state_ = ZWAVE_PARSING_STATE_WAIT_START; | ||||
|   switch (byte) { | ||||
|     case ZWAVE_FRAME_TYPE_START: | ||||
|       ESP_LOGVV(TAG, "Received START"); | ||||
|       if (this->in_bootloader_) { | ||||
|         ESP_LOGD(TAG, "Exited bootloader mode"); | ||||
|         this->in_bootloader_ = false; | ||||
|       } | ||||
|       this->buffer_[this->buffer_index_++] = byte; | ||||
|       this->parsing_state_ = ZWAVE_PARSING_STATE_WAIT_LENGTH; | ||||
|       return; | ||||
|     case ZWAVE_FRAME_TYPE_BL_MENU: | ||||
|       ESP_LOGVV(TAG, "Received BL_MENU"); | ||||
|       if (!this->in_bootloader_) { | ||||
|         ESP_LOGD(TAG, "Entered bootloader mode"); | ||||
|         this->in_bootloader_ = true; | ||||
|       } | ||||
|       this->buffer_[this->buffer_index_++] = byte; | ||||
|       this->parsing_state_ = ZWAVE_PARSING_STATE_READ_BL_MENU; | ||||
|       return; | ||||
|     case ZWAVE_FRAME_TYPE_BL_BEGIN_UPLOAD: | ||||
|       ESP_LOGVV(TAG, "Received BL_BEGIN_UPLOAD"); | ||||
|       break; | ||||
|     case ZWAVE_FRAME_TYPE_ACK: | ||||
|       ESP_LOGVV(TAG, "Received ACK"); | ||||
|       break; | ||||
|     case ZWAVE_FRAME_TYPE_NAK: | ||||
|       ESP_LOGW(TAG, "Received NAK"); | ||||
|       break; | ||||
|     case ZWAVE_FRAME_TYPE_CAN: | ||||
|       ESP_LOGW(TAG, "Received CAN"); | ||||
|       break; | ||||
|     default: | ||||
|       ESP_LOGW(TAG, "Unrecognized START: 0x%02X", byte); | ||||
|       return; | ||||
|   } | ||||
|   // Forward response (ACK/NAK/CAN) back to client for processing | ||||
|   if (this->api_connection_ != nullptr) { | ||||
|     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_() { | ||||
|   switch (this->parsing_state_) { | ||||
|     case ZWAVE_PARSING_STATE_SEND_ACK: | ||||
|       this->last_response_ = ZWAVE_FRAME_TYPE_ACK; | ||||
|       break; | ||||
|     case ZWAVE_PARSING_STATE_SEND_CAN: | ||||
|       this->last_response_ = ZWAVE_FRAME_TYPE_CAN; | ||||
|       break; | ||||
|     case ZWAVE_PARSING_STATE_SEND_NAK: | ||||
|       this->last_response_ = ZWAVE_FRAME_TYPE_NAK; | ||||
|       break; | ||||
|     default: | ||||
|       return false;  // No response handled | ||||
|   } | ||||
|  | ||||
|   ESP_LOGVV(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; | ||||
| } | ||||
|  | ||||
| ZWaveProxy *global_zwave_proxy = nullptr;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) | ||||
|  | ||||
| }  // namespace zwave_proxy | ||||
| }  // namespace esphome | ||||
							
								
								
									
										73
									
								
								esphome/components/zwave_proxy/zwave_proxy.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								esphome/components/zwave_proxy/zwave_proxy.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,73 @@ | ||||
| #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" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace zwave_proxy { | ||||
|  | ||||
| enum ZWaveResponseTypes : uint8_t { | ||||
|   ZWAVE_FRAME_TYPE_ACK = 0x06, | ||||
|   ZWAVE_FRAME_TYPE_CAN = 0x18, | ||||
|   ZWAVE_FRAME_TYPE_NAK = 0x15, | ||||
|   ZWAVE_FRAME_TYPE_START = 0x01, | ||||
|   ZWAVE_FRAME_TYPE_BL_MENU = 0x0D, | ||||
|   ZWAVE_FRAME_TYPE_BL_BEGIN_UPLOAD = 0x43, | ||||
| }; | ||||
|  | ||||
| enum ZWaveParsingState : uint8_t { | ||||
|   ZWAVE_PARSING_STATE_WAIT_START, | ||||
|   ZWAVE_PARSING_STATE_WAIT_LENGTH, | ||||
|   ZWAVE_PARSING_STATE_WAIT_TYPE, | ||||
|   ZWAVE_PARSING_STATE_WAIT_COMMAND_ID, | ||||
|   ZWAVE_PARSING_STATE_WAIT_PAYLOAD, | ||||
|   ZWAVE_PARSING_STATE_WAIT_CHECKSUM, | ||||
|   ZWAVE_PARSING_STATE_SEND_ACK, | ||||
|   ZWAVE_PARSING_STATE_SEND_CAN, | ||||
|   ZWAVE_PARSING_STATE_SEND_NAK, | ||||
|   ZWAVE_PARSING_STATE_READ_BL_MENU, | ||||
| }; | ||||
|  | ||||
| enum ZWaveProxyFeature : uint32_t { | ||||
|   FEATURE_ZWAVE_PROXY_ENABLED = 1 << 0, | ||||
| }; | ||||
|  | ||||
| class ZWaveProxy : public uart::UARTDevice, public Component { | ||||
|  public: | ||||
|   ZWaveProxy(); | ||||
|  | ||||
|   void loop() override; | ||||
|   void dump_config() override; | ||||
|  | ||||
|   void zwave_proxy_request(api::APIConnection *api_connection, api::enums::ZWaveProxyRequestType type); | ||||
|   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 uint8_t *data, size_t length); | ||||
|  | ||||
|  protected: | ||||
|   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_(); | ||||
|  | ||||
|   api::APIConnection *api_connection_{nullptr};  // Current subscribed client | ||||
|  | ||||
|   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}; | ||||
|   bool in_bootloader_{false};  // True if the device is detected to be in bootloader mode | ||||
|  | ||||
|   // Pre-allocated message - always ready to send | ||||
|   api::ZWaveProxyFrame outgoing_proto_msg_; | ||||
| }; | ||||
|  | ||||
| extern ZWaveProxy *global_zwave_proxy;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) | ||||
|  | ||||
| }  // namespace zwave_proxy | ||||
| }  // namespace esphome | ||||
| @@ -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 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user