mirror of
https://github.com/esphome/esphome.git
synced 2025-09-22 13:12:22 +01:00
[zwave_proxy] Send Home ID in DeviceInfoResponse (#10798)
Co-authored-by: J. Nick Koston <nick@home-assistant.io> Co-authored-by: J. Nick Koston <nick+github@koston.org> Co-authored-by: AlCalzone <d.griesel@gmx.net> Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
@@ -260,6 +260,7 @@ message DeviceInfoResponse {
|
||||
|
||||
// Indicates if Z-Wave proxy support is available and features supported
|
||||
uint32 zwave_proxy_feature_flags = 23 [(field_ifdef) = "USE_ZWAVE_PROXY"];
|
||||
uint32 zwave_home_id = 24 [(field_ifdef) = "USE_ZWAVE_PROXY"];
|
||||
}
|
||||
|
||||
message ListEntitiesRequest {
|
||||
|
@@ -1474,6 +1474,7 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
|
||||
#endif
|
||||
#ifdef USE_ZWAVE_PROXY
|
||||
resp.zwave_proxy_feature_flags = zwave_proxy::global_zwave_proxy->get_feature_flags();
|
||||
resp.zwave_home_id = zwave_proxy::global_zwave_proxy->get_home_id();
|
||||
#endif
|
||||
#ifdef USE_API_NOISE
|
||||
resp.api_encryption_supported = true;
|
||||
|
@@ -132,6 +132,9 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
#ifdef USE_ZWAVE_PROXY
|
||||
buffer.encode_uint32(23, this->zwave_proxy_feature_flags);
|
||||
#endif
|
||||
#ifdef USE_ZWAVE_PROXY
|
||||
buffer.encode_uint32(24, this->zwave_home_id);
|
||||
#endif
|
||||
}
|
||||
void DeviceInfoResponse::calculate_size(ProtoSize &size) const {
|
||||
#ifdef USE_API_PASSWORD
|
||||
@@ -187,6 +190,9 @@ void DeviceInfoResponse::calculate_size(ProtoSize &size) const {
|
||||
#ifdef USE_ZWAVE_PROXY
|
||||
size.add_uint32(2, this->zwave_proxy_feature_flags);
|
||||
#endif
|
||||
#ifdef USE_ZWAVE_PROXY
|
||||
size.add_uint32(2, this->zwave_home_id);
|
||||
#endif
|
||||
}
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
|
@@ -498,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 = 252;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 257;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "device_info_response"; }
|
||||
#endif
|
||||
@@ -561,6 +561,9 @@ class DeviceInfoResponse final : public ProtoMessage {
|
||||
#endif
|
||||
#ifdef USE_ZWAVE_PROXY
|
||||
uint32_t zwave_proxy_feature_flags{0};
|
||||
#endif
|
||||
#ifdef USE_ZWAVE_PROXY
|
||||
uint32_t zwave_home_id{0};
|
||||
#endif
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
|
@@ -769,6 +769,9 @@ void DeviceInfoResponse::dump_to(std::string &out) const {
|
||||
#ifdef USE_ZWAVE_PROXY
|
||||
dump_field(out, "zwave_proxy_feature_flags", this->zwave_proxy_feature_flags);
|
||||
#endif
|
||||
#ifdef USE_ZWAVE_PROXY
|
||||
dump_field(out, "zwave_home_id", this->zwave_home_id);
|
||||
#endif
|
||||
}
|
||||
void ListEntitiesRequest::dump_to(std::string &out) const { out.append("ListEntitiesRequest {}"); }
|
||||
void ListEntitiesDoneResponse::dump_to(std::string &out) const { out.append("ListEntitiesDoneResponse {}"); }
|
||||
|
@@ -8,8 +8,26 @@ namespace zwave_proxy {
|
||||
|
||||
static const char *const TAG = "zwave_proxy";
|
||||
|
||||
static constexpr uint8_t ZWAVE_COMMAND_GET_NETWORK_IDS = 0x20;
|
||||
// GET_NETWORK_IDS response: [SOF][LENGTH][TYPE][CMD][HOME_ID(4)][NODE_ID][...]
|
||||
static constexpr uint8_t ZWAVE_COMMAND_TYPE_RESPONSE = 0x01; // Response type field value
|
||||
static constexpr uint8_t ZWAVE_MIN_GET_NETWORK_IDS_LENGTH = 9; // TYPE + CMD + HOME_ID(4) + NODE_ID + checksum
|
||||
|
||||
static uint8_t calculate_frame_checksum(const uint8_t *data, uint8_t length) {
|
||||
// Calculate Z-Wave frame checksum
|
||||
// XOR all bytes between SOF and checksum position (exclusive)
|
||||
// Initial value is 0xFF per Z-Wave protocol specification
|
||||
uint8_t checksum = 0xFF;
|
||||
for (uint8_t i = 1; i < length - 1; i++) {
|
||||
checksum ^= data[i];
|
||||
}
|
||||
return checksum;
|
||||
}
|
||||
|
||||
ZWaveProxy::ZWaveProxy() { global_zwave_proxy = this; }
|
||||
|
||||
void ZWaveProxy::setup() { this->send_simple_command_(ZWAVE_COMMAND_GET_NETWORK_IDS); }
|
||||
|
||||
void ZWaveProxy::loop() {
|
||||
if (this->response_handler_()) {
|
||||
ESP_LOGV(TAG, "Handled late response");
|
||||
@@ -26,6 +44,21 @@ void ZWaveProxy::loop() {
|
||||
return;
|
||||
}
|
||||
if (this->parse_byte_(byte)) {
|
||||
// Check if this is a GET_NETWORK_IDS response frame
|
||||
// Frame format: [SOF][LENGTH][TYPE][CMD][HOME_ID(4)][NODE_ID][...]
|
||||
// We verify:
|
||||
// - buffer_[0]: Start of frame marker (0x01)
|
||||
// - buffer_[1]: Length field must be >= 9 to contain all required data
|
||||
// - buffer_[2]: Command type (0x01 for response)
|
||||
// - buffer_[3]: Command ID (0x20 for GET_NETWORK_IDS)
|
||||
if (this->buffer_[3] == ZWAVE_COMMAND_GET_NETWORK_IDS && this->buffer_[2] == ZWAVE_COMMAND_TYPE_RESPONSE &&
|
||||
this->buffer_[1] >= ZWAVE_MIN_GET_NETWORK_IDS_LENGTH && this->buffer_[0] == ZWAVE_FRAME_TYPE_START) {
|
||||
// Extract the 4-byte Home ID starting at offset 4
|
||||
// The frame parser has already validated the checksum and ensured all bytes are present
|
||||
std::memcpy(this->home_id_.data(), this->buffer_.data() + 4, this->home_id_.size());
|
||||
ESP_LOGI(TAG, "Home ID: %s",
|
||||
format_hex_pretty(this->home_id_.data(), this->home_id_.size(), ':', false).c_str());
|
||||
}
|
||||
ESP_LOGV(TAG, "Sending to client: %s", YESNO(this->api_connection_ != nullptr));
|
||||
if (this->api_connection_ != nullptr) {
|
||||
// minimize copying to reduce CPU overhead
|
||||
@@ -35,7 +68,7 @@ void ZWaveProxy::loop() {
|
||||
// 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);
|
||||
std::memcpy(this->outgoing_proto_msg_.data, this->buffer_.data(), this->outgoing_proto_msg_.data_len);
|
||||
this->api_connection_->send_message(this->outgoing_proto_msg_, api::ZWaveProxyFrame::MESSAGE_TYPE);
|
||||
}
|
||||
}
|
||||
@@ -77,6 +110,15 @@ void ZWaveProxy::send_frame(const uint8_t *data, size_t length) {
|
||||
this->write_array(data, length);
|
||||
}
|
||||
|
||||
void ZWaveProxy::send_simple_command_(const uint8_t command_id) {
|
||||
// Send a simple Z-Wave command with no parameters
|
||||
// Frame format: [SOF][LENGTH][TYPE][CMD][CHECKSUM]
|
||||
// Where LENGTH=0x03 (3 bytes: TYPE + CMD + CHECKSUM)
|
||||
uint8_t cmd[] = {0x01, 0x03, 0x00, command_id, 0x00};
|
||||
cmd[4] = calculate_frame_checksum(cmd, sizeof(cmd));
|
||||
this->send_frame(cmd, sizeof(cmd));
|
||||
}
|
||||
|
||||
bool ZWaveProxy::parse_byte_(uint8_t byte) {
|
||||
bool frame_completed = false;
|
||||
// Basic parsing logic for received frames
|
||||
@@ -94,43 +136,40 @@ bool ZWaveProxy::parse_byte_(uint8_t 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:
|
||||
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);
|
||||
auto checksum = calculate_frame_checksum(this->buffer_.data(), this->buffer_index_);
|
||||
ESP_LOGVV(TAG, "CHECKSUM Received: 0x%02X - Calculated: 0x%02X", byte, checksum);
|
||||
if (checksum != byte) {
|
||||
ESP_LOGW(TAG, "Bad checksum: expected 0x%02X, got 0x%02X", 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());
|
||||
ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(this->buffer_.data(), 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) {
|
||||
@@ -151,7 +190,6 @@ bool ZWaveProxy::parse_byte_(uint8_t byte) {
|
||||
|
||||
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:
|
||||
|
@@ -3,8 +3,11 @@
|
||||
#include "esphome/components/api/api_connection.h"
|
||||
#include "esphome/components/api/api_pb2.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/components/uart/uart.h"
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace esphome {
|
||||
namespace zwave_proxy {
|
||||
|
||||
@@ -38,6 +41,7 @@ class ZWaveProxy : public uart::UARTDevice, public Component {
|
||||
public:
|
||||
ZWaveProxy();
|
||||
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
void dump_config() override;
|
||||
|
||||
@@ -45,21 +49,25 @@ class ZWaveProxy : public uart::UARTDevice, public Component {
|
||||
api::APIConnection *get_api_connection() { return this->api_connection_; }
|
||||
|
||||
uint32_t get_feature_flags() const { return ZWaveProxyFeature::FEATURE_ZWAVE_PROXY_ENABLED; }
|
||||
uint32_t get_home_id() {
|
||||
return encode_uint32(this->home_id_[0], this->home_id_[1], this->home_id_[2], this->home_id_[3]);
|
||||
}
|
||||
|
||||
void send_frame(const uint8_t *data, size_t length);
|
||||
|
||||
protected:
|
||||
void send_simple_command_(uint8_t command_id);
|
||||
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
|
||||
std::array<uint8_t, 4> home_id_{0, 0, 0, 0}; // Fixed buffer for home ID
|
||||
std::array<uint8_t, sizeof(api::ZWaveProxyFrame::data)> buffer_; // Fixed buffer for incoming data
|
||||
uint8_t buffer_index_{0}; // Index for populating the data buffer
|
||||
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
|
||||
|
||||
|
Reference in New Issue
Block a user