From cfa1f5795eadc667058ffac2ac0a95b26287cb7f Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Thu, 14 Aug 2025 01:39:13 -0500 Subject: [PATCH] Initial commit --- esphome/components/zwave_proxy/__init__.py | 25 +++ .../components/zwave_proxy/zwave_proxy.cpp | 143 ++++++++++++++++++ esphome/components/zwave_proxy/zwave_proxy.h | 49 ++++++ 3 files changed, 217 insertions(+) create mode 100644 esphome/components/zwave_proxy/__init__.py create mode 100644 esphome/components/zwave_proxy/zwave_proxy.cpp create mode 100644 esphome/components/zwave_proxy/zwave_proxy.h diff --git a/esphome/components/zwave_proxy/__init__.py b/esphome/components/zwave_proxy/__init__.py new file mode 100644 index 0000000000..8adf39d28d --- /dev/null +++ b/esphome/components/zwave_proxy/__init__.py @@ -0,0 +1,25 @@ +import esphome.codegen as cg +from esphome.components import uart +import esphome.config_validation as cv +from esphome.const import CONF_ID + +DEPENDENCIES = ["uart"] + +zwave_proxy_ns = cg.esphome_ns.namespace("zwave_proxy") +ZWaveProxy = zwave_proxy_ns.class_("ZWaveProxy", cg.Component, uart.UARTDevice) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(ZWaveProxy), + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(uart.UART_DEVICE_SCHEMA) +) + + +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) diff --git a/esphome/components/zwave_proxy/zwave_proxy.cpp b/esphome/components/zwave_proxy/zwave_proxy.cpp new file mode 100644 index 0000000000..fe67705407 --- /dev/null +++ b/esphome/components/zwave_proxy/zwave_proxy.cpp @@ -0,0 +1,143 @@ +#include "zwave_proxy.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace zwave_proxy { + +static const char *TAG = "zwave_proxy"; + +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}; + ESP_LOGD(TAG, "Sending: %s", format_hex_pretty(get_capabilities_cmd, sizeof(get_capabilities_cmd)).c_str()); + this->write_array(get_capabilities_cmd, sizeof(get_capabilities_cmd)); +} + +void ZWaveProxy::loop() { + if (this->response_handler_()) { + return; // If a response was handled, exit early to avoid a CAN + } + + while (this->available()) { + uint8_t byte; + if (!this->read_byte(&byte)) { + this->status_set_warning("Failed reading from UART"); + return; + } + this->parse_byte_(byte); + } + this->status_clear_warning(); +} + +void ZWaveProxy::dump_config() { ESP_LOGCONFIG(TAG, "Z-Wave Proxy"); } + +void ZWaveProxy::send_frame(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) { + // 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: + ESP_LOGD(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_LOGD(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_LOGD(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_LOGD(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_LOGD(TAG, "Received frame: %s", format_hex_pretty(this->buffer_, this->buffer_index_).c_str()); + } + break; + case ZWAVE_PARSING_STATE_SEND_ACK: + case ZWAVE_PARSING_STATE_SEND_NAK: + break; // Should not happen, handled in loop() + default: + ESP_LOGD(TAG, "Received unknown byte: 0x%02X", byte); + break; + } +} + +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_LOGD(TAG, "Received START"); + this->buffer_[this->buffer_index_++] = byte; + this->parsing_state_ = ZWAVE_PARSING_STATE_WAIT_LENGTH; + break; + case ZWAVE_FRAME_TYPE_ACK: + ESP_LOGD(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, "Unexpected type: 0x%02X", byte); + break; + } +} + +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; + break; + case ZWAVE_PARSING_STATE_SEND_CAN: + response_byte = ZWAVE_FRAME_TYPE_CAN; + break; + case ZWAVE_PARSING_STATE_SEND_NAK: + response_byte = 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); + this->parsing_state_ = ZWAVE_PARSING_STATE_WAIT_START; + return true; +} + +} // namespace zwave_proxy +} // namespace esphome diff --git a/esphome/components/zwave_proxy/zwave_proxy.h b/esphome/components/zwave_proxy/zwave_proxy.h new file mode 100644 index 0000000000..0c9f93af95 --- /dev/null +++ b/esphome/components/zwave_proxy/zwave_proxy.h @@ -0,0 +1,49 @@ +#pragma once + +#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, +}; + +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, +}; + +class ZWaveProxy : public uart::UARTDevice, public Component { + public: + void setup() override; + void loop() override; + void dump_config() override; + + void send_frame(std::vector &data); + + protected: + void parse_byte_(uint8_t byte); + void parse_start_(uint8_t byte); + bool response_handler_(); + + 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}; +}; + +} // namespace zwave_proxy +} // namespace esphome