ESPHome 2025.10.0-dev
Loading...
Searching...
No Matches
zwave_proxy.cpp
Go to the documentation of this file.
1#include "zwave_proxy.h"
4#include "esphome/core/log.h"
5#include "esphome/core/util.h"
6
7namespace esphome {
8namespace zwave_proxy {
9
10static const char *const TAG = "zwave_proxy";
11
12static constexpr uint8_t ZWAVE_COMMAND_GET_NETWORK_IDS = 0x20;
13// GET_NETWORK_IDS response: [SOF][LENGTH][TYPE][CMD][HOME_ID(4)][NODE_ID][...]
14static constexpr uint8_t ZWAVE_COMMAND_TYPE_RESPONSE = 0x01; // Response type field value
15static constexpr uint8_t ZWAVE_MIN_GET_NETWORK_IDS_LENGTH = 9; // TYPE + CMD + HOME_ID(4) + NODE_ID + checksum
16static constexpr uint32_t HOME_ID_TIMEOUT_MS = 100; // Timeout for waiting for home ID during setup
17
18static uint8_t calculate_frame_checksum(const uint8_t *data, uint8_t length) {
19 // Calculate Z-Wave frame checksum
20 // XOR all bytes between SOF and checksum position (exclusive)
21 // Initial value is 0xFF per Z-Wave protocol specification
22 uint8_t checksum = 0xFF;
23 for (uint8_t i = 1; i < length - 1; i++) {
24 checksum ^= data[i];
25 }
26 return checksum;
27}
28
30
33 this->send_simple_command_(ZWAVE_COMMAND_GET_NETWORK_IDS);
34}
35
37 // Set up before API so home ID is ready when API starts
39}
40
42 // If we already have the home ID, we can proceed
43 if (this->home_id_ready_) {
44 return true;
45 }
46
47 // Handle any pending responses
48 if (this->response_handler_()) {
49 ESP_LOGV(TAG, "Handled response during setup");
50 }
51
52 // Process UART data to check for home ID
53 this->process_uart_();
54
55 // Check if we got the home ID after processing
56 if (this->home_id_ready_) {
57 return true;
58 }
59
60 // Wait up to HOME_ID_TIMEOUT_MS for home ID response
61 const uint32_t now = App.get_loop_component_start_time();
62 if (now - this->setup_time_ > HOME_ID_TIMEOUT_MS) {
63 ESP_LOGW(TAG, "Timeout reading Home ID during setup");
64 return true; // Proceed anyway after timeout
65 }
66
67 return false; // Keep waiting
68}
69
71 if (this->response_handler_()) {
72 ESP_LOGV(TAG, "Handled late response");
73 }
74 if (this->api_connection_ != nullptr && (!this->api_connection_->is_connection_setup() || !api_is_connected())) {
75 ESP_LOGW(TAG, "Subscriber disconnected");
76 this->api_connection_ = nullptr; // Unsubscribe if disconnected
77 }
78
79 this->process_uart_();
81}
82
84 while (this->available()) {
85 uint8_t byte;
86 if (!this->read_byte(&byte)) {
87 this->status_set_warning("UART read failed");
88 return;
89 }
90 if (this->parse_byte_(byte)) {
91 // Check if this is a GET_NETWORK_IDS response frame
92 // Frame format: [SOF][LENGTH][TYPE][CMD][HOME_ID(4)][NODE_ID][...]
93 // We verify:
94 // - buffer_[0]: Start of frame marker (0x01)
95 // - buffer_[1]: Length field must be >= 9 to contain all required data
96 // - buffer_[2]: Command type (0x01 for response)
97 // - buffer_[3]: Command ID (0x20 for GET_NETWORK_IDS)
98 if (this->buffer_[3] == ZWAVE_COMMAND_GET_NETWORK_IDS && this->buffer_[2] == ZWAVE_COMMAND_TYPE_RESPONSE &&
99 this->buffer_[1] >= ZWAVE_MIN_GET_NETWORK_IDS_LENGTH && this->buffer_[0] == ZWAVE_FRAME_TYPE_START) {
100 // Extract the 4-byte Home ID starting at offset 4
101 // The frame parser has already validated the checksum and ensured all bytes are present
102 std::memcpy(this->home_id_.data(), this->buffer_.data() + 4, this->home_id_.size());
103 this->home_id_ready_ = true;
104 ESP_LOGI(TAG, "Home ID: %s",
105 format_hex_pretty(this->home_id_.data(), this->home_id_.size(), ':', false).c_str());
106 }
107 ESP_LOGV(TAG, "Sending to client: %s", YESNO(this->api_connection_ != nullptr));
108 if (this->api_connection_ != nullptr) {
109 // Zero-copy: point directly to our buffer
110 this->outgoing_proto_msg_.data = this->buffer_.data();
111 if (this->in_bootloader_) {
113 } else {
114 // If this is a data frame, use frame length indicator + 2 (for SoF + checksum), else assume 1 for ACK/NAK/CAN
115 this->outgoing_proto_msg_.data_len = this->buffer_[0] == ZWAVE_FRAME_TYPE_START ? this->buffer_[1] + 2 : 1;
116 }
118 }
119 }
120 }
121}
122
123void ZWaveProxy::dump_config() { ESP_LOGCONFIG(TAG, "Z-Wave Proxy"); }
124
126 switch (type) {
128 if (this->api_connection_ != nullptr) {
129 ESP_LOGE(TAG, "Only one API subscription is allowed at a time");
130 return;
131 }
132 this->api_connection_ = api_connection;
133 ESP_LOGV(TAG, "API connection is now subscribed");
134 break;
136 if (this->api_connection_ != api_connection) {
137 ESP_LOGV(TAG, "API connection is not subscribed");
138 return;
139 }
140 this->api_connection_ = nullptr;
141 break;
142 default:
143 ESP_LOGW(TAG, "Unknown request type: %d", type);
144 break;
145 }
146}
147
148void ZWaveProxy::send_frame(const uint8_t *data, size_t length) {
149 if (length == 1 && data[0] == this->last_response_) {
150 ESP_LOGV(TAG, "Skipping sending duplicate response: 0x%02X", data[0]);
151 return;
152 }
153 ESP_LOGVV(TAG, "Sending: %s", format_hex_pretty(data, length).c_str());
154 this->write_array(data, length);
155}
156
157void ZWaveProxy::send_simple_command_(const uint8_t command_id) {
158 // Send a simple Z-Wave command with no parameters
159 // Frame format: [SOF][LENGTH][TYPE][CMD][CHECKSUM]
160 // Where LENGTH=0x03 (3 bytes: TYPE + CMD + CHECKSUM)
161 uint8_t cmd[] = {0x01, 0x03, 0x00, command_id, 0x00};
162 cmd[4] = calculate_frame_checksum(cmd, sizeof(cmd));
163 this->send_frame(cmd, sizeof(cmd));
164}
165
166bool ZWaveProxy::parse_byte_(uint8_t byte) {
167 bool frame_completed = false;
168 // Basic parsing logic for received frames
169 switch (this->parsing_state_) {
171 this->parse_start_(byte);
172 break;
174 if (!byte) {
175 ESP_LOGW(TAG, "Invalid LENGTH: %u", byte);
177 return false;
178 }
179 ESP_LOGVV(TAG, "Received LENGTH: %u", byte);
180 this->end_frame_after_ = this->buffer_index_ + byte;
181 ESP_LOGVV(TAG, "Calculated EOF: %u", this->end_frame_after_);
182 this->buffer_[this->buffer_index_++] = byte;
184 break;
186 this->buffer_[this->buffer_index_++] = byte;
187 ESP_LOGVV(TAG, "Received TYPE: 0x%02X", byte);
189 break;
191 this->buffer_[this->buffer_index_++] = byte;
192 ESP_LOGVV(TAG, "Received COMMAND ID: 0x%02X", byte);
194 break;
196 this->buffer_[this->buffer_index_++] = byte;
197 ESP_LOGVV(TAG, "Received PAYLOAD: 0x%02X", byte);
198 if (this->buffer_index_ >= this->end_frame_after_) {
200 }
201 break;
203 this->buffer_[this->buffer_index_++] = byte;
204 auto checksum = calculate_frame_checksum(this->buffer_.data(), this->buffer_index_);
205 ESP_LOGVV(TAG, "CHECKSUM Received: 0x%02X - Calculated: 0x%02X", byte, checksum);
206 if (checksum != byte) {
207 ESP_LOGW(TAG, "Bad checksum: expected 0x%02X, got 0x%02X", checksum, byte);
209 } else {
211 ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(this->buffer_.data(), this->buffer_index_).c_str());
212 frame_completed = true;
213 }
214 this->response_handler_();
215 break;
216 }
218 this->buffer_[this->buffer_index_++] = byte;
219 if (!byte) {
221 frame_completed = true;
222 }
223 break;
226 break; // Should not happen, handled in loop()
227 default:
228 ESP_LOGW(TAG, "Bad parsing state; resetting");
230 break;
231 }
232 return frame_completed;
233}
234
235void ZWaveProxy::parse_start_(uint8_t byte) {
236 this->buffer_index_ = 0;
238 switch (byte) {
240 ESP_LOGVV(TAG, "Received START");
241 if (this->in_bootloader_) {
242 ESP_LOGD(TAG, "Exited bootloader mode");
243 this->in_bootloader_ = false;
244 }
245 this->buffer_[this->buffer_index_++] = byte;
247 return;
249 ESP_LOGVV(TAG, "Received BL_MENU");
250 if (!this->in_bootloader_) {
251 ESP_LOGD(TAG, "Entered bootloader mode");
252 this->in_bootloader_ = true;
253 }
254 this->buffer_[this->buffer_index_++] = byte;
256 return;
258 ESP_LOGVV(TAG, "Received BL_BEGIN_UPLOAD");
259 break;
261 ESP_LOGVV(TAG, "Received ACK");
262 break;
264 ESP_LOGW(TAG, "Received NAK");
265 break;
267 ESP_LOGW(TAG, "Received CAN");
268 break;
269 default:
270 ESP_LOGW(TAG, "Unrecognized START: 0x%02X", byte);
271 return;
272 }
273 // Forward response (ACK/NAK/CAN) back to client for processing
274 if (this->api_connection_ != nullptr) {
275 // Store single byte in buffer and point to it
276 this->buffer_[0] = byte;
277 this->outgoing_proto_msg_.data = this->buffer_.data();
280 }
281}
282
284 switch (this->parsing_state_) {
287 break;
290 break;
293 break;
294 default:
295 return false; // No response handled
296 }
297
298 ESP_LOGVV(TAG, "Sending %s (0x%02X)", this->last_response_ == ZWAVE_FRAME_TYPE_ACK ? "ACK" : "NAK/CAN",
299 this->last_response_);
300 this->write_byte(this->last_response_);
302 return true;
303}
304
305ZWaveProxy *global_zwave_proxy = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
306
307} // namespace zwave_proxy
308} // namespace esphome
uint8_t checksum
Definition bl0906.h:3
uint32_t IRAM_ATTR HOT get_loop_component_start_time() const
Get the cached time in milliseconds from when the current component started its loop execution.
void status_set_warning(const char *message=nullptr)
void status_clear_warning()
bool is_connection_setup() override
bool send_message(const ProtoMessage &msg, uint8_t message_type)
static constexpr uint8_t MESSAGE_TYPE
Definition api_pb2.h:2936
bool read_byte(uint8_t *data)
Definition uart.h:35
void write_byte(uint8_t data)
Definition uart.h:19
void write_array(const uint8_t *data, size_t len)
Definition uart.h:27
void zwave_proxy_request(api::APIConnection *api_connection, api::enums::ZWaveProxyRequestType type)
api::ZWaveProxyFrame outgoing_proto_msg_
Definition zwave_proxy.h:70
void send_frame(const uint8_t *data, size_t length)
void send_simple_command_(uint8_t command_id)
ZWaveParsingState parsing_state_
Definition zwave_proxy.h:82
std::array< uint8_t, MAX_ZWAVE_FRAME_SIZE > buffer_
Definition zwave_proxy.h:71
std::array< uint8_t, 4 > home_id_
Definition zwave_proxy.h:72
api::APIConnection * api_connection_
Definition zwave_proxy.h:75
float get_setup_priority() const override
uint8_t type
@ ZWAVE_PROXY_REQUEST_TYPE_SUBSCRIBE
Definition api_pb2.h:281
@ ZWAVE_PROXY_REQUEST_TYPE_UNSUBSCRIBE
Definition api_pb2.h:282
const float BEFORE_CONNECTION
For components that should be initialized after WiFi and before API is connected.
Definition component.cpp:55
ZWaveProxy * global_zwave_proxy
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
bool api_is_connected()
Return whether the node has at least one client connected to the native API.
Definition util.cpp:17
std::string format_hex_pretty(const uint8_t *data, size_t length, char separator, bool show_length)
Format a byte array in pretty-printed, human-readable hex format.
Definition helpers.cpp:292
Application App
Global storage of Application pointer - only one Application can exist.
uint16_t length
Definition tt21100.cpp:0