ESPHome 2026.10.0-dev
Loading...
Searching...
No Matches
zwave_proxy.h
Go to the documentation of this file.
1#pragma once
2
4#ifdef USE_API
5
11
12#include <array>
13
14namespace esphome::zwave_proxy {
15
16static constexpr size_t MAX_ZWAVE_FRAME_SIZE = 257; // Maximum Z-Wave frame size
17static constexpr size_t ZWAVE_HOME_ID_SIZE = 4; // Z-Wave Home ID size in bytes
18
27
40
41// response_handler_()'s inline fast-path relies on SEND_ACK/CAN/NAK being contiguous in this
42// enum so a single range check (state - SEND_ACK < 3) is equivalent to three equality checks.
44 "SEND_CAN must immediately follow SEND_ACK for response_handler_ fast-path");
46 "SEND_NAK must immediately follow SEND_CAN for response_handler_ fast-path");
47
51
52class ZWaveProxy final : public uart::UARTDevice, public Component {
53 public:
54 ZWaveProxy();
55
56 void setup() override;
57 void loop() override;
58 void dump_config() override;
59 float get_setup_priority() const override;
60 bool can_proceed() override;
61
66
69 return encode_uint32(this->home_id_[0], this->home_id_[1], this->home_id_[2], this->home_id_[3]);
70 }
71
72 // Send a frame from an API client to the Z-Wave module. Frames from any connection other
73 // than the currently subscribed one are ignored.
74 void send_frame(api::APIConnection *api_connection, const uint8_t *data, size_t length);
75
76 protected:
77 void send_frame_(const uint8_t *data, size_t length); // Write a frame to the Z-Wave module
78 bool set_home_id_(const uint8_t *new_home_id); // Store a new home ID. Returns true if it changed.
79 void clear_home_id_(); // Clear home ID and notify API clients
80 void on_connection_changed_(bool connected); // Handle modem connect/disconnect transitions
81 void retry_home_id_query_(); // Retry home ID query after reconnect
82 void send_homeid_changed_msg_(api::APIConnection *conn = nullptr);
83 void send_simple_command_(uint8_t command_id);
84 bool parse_byte_(uint8_t byte); // Returns true if frame parsing was completed (a frame is ready in the buffer)
85 void parse_start_(uint8_t byte);
86 // Inline fast-path: most calls happen with parsing_state_ outside the SEND_* range, so skip the
87 // out-of-line call entirely in the hot path (e.g. every loop() tick) and only pay for the real
88 // work when a response is actually pending. ESPHOME_ALWAYS_INLINE is required because with -Os
89 // gcc otherwise clones the wrapper into a shared $isra$ outline and keeps the call8.
90 ESPHOME_ALWAYS_INLINE bool response_handler_() {
92 return false;
93 }
94 return this->response_handler_slow_();
95 }
97 // Inline fast-path: UART::available() is cheap (ring-buffer head/tail compare on most backends).
98 // On an idle loop tick we want to skip the call to process_uart_ entirely. When bytes are
99 // pending we fall into the slow path, which drains the UART with a do/while so available() is
100 // only checked once per byte — no redundant re-check on entry.
101 ESPHOME_ALWAYS_INLINE void process_uart_() {
102 if (!this->available()) {
103 return;
104 }
105 this->process_uart_slow_();
106 }
107 // Precondition: caller must guarantee available() > 0 before invoking (see inline
108 // process_uart_ above). The slow path uses do/while and would otherwise set a spurious UART
109 // warning on entry if called with no bytes pending.
110 void process_uart_slow_();
111
112 // Pre-allocated message - always ready to send
114 std::array<uint8_t, MAX_ZWAVE_FRAME_SIZE> buffer_; // Fixed buffer for incoming data
115 std::array<uint8_t, ZWAVE_HOME_ID_SIZE> home_id_{}; // Fixed buffer for home ID
116
117 // Pointers and 32-bit values (aligned together)
118 api::APIConnection *api_connection_{nullptr}; // Current subscribed client
119 uint32_t setup_time_{0}; // Time when setup() was called
120 uint32_t reconnect_time_{0}; // Timestamp of reconnect detection (0 = no pending query)
121 uint32_t frame_start_time_{0}; // Timestamp of the current frame's start byte (reception timeout)
122
123 // Small values (grouped by size to minimize padding)
124 uint16_t buffer_index_{0}; // Index for populating the data buffer
125 uint16_t end_frame_after_{0}; // Payload reception ends after this index
126 uint8_t last_response_{0}; // Last response type sent
127 uint8_t query_retries_{0}; // Number of home ID query attempts after reconnect
129 bool in_bootloader_{false}; // True if the device is detected to be in bootloader mode
130 bool home_id_ready_{false}; // True when home ID has been received from Z-Wave module
131 bool was_connected_{false}; // Previous UART connection state for edge detection
132};
133
134extern ZWaveProxy *global_zwave_proxy; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
135
136} // namespace esphome::zwave_proxy
137
138#endif // USE_API
api::ZWaveProxyFrame outgoing_proto_msg_
uint32_t get_feature_flags() const
Definition zwave_proxy.h:67
void send_homeid_changed_msg_(api::APIConnection *conn=nullptr)
bool set_home_id_(const uint8_t *new_home_id)
void send_simple_command_(uint8_t command_id)
ESPHOME_ALWAYS_INLINE void process_uart_()
void send_frame(api::APIConnection *api_connection, const uint8_t *data, size_t length)
void send_frame_(const uint8_t *data, size_t length)
void api_connection_authenticated(api::APIConnection *conn)
api::APIConnection * get_api_connection()
Definition zwave_proxy.h:65
std::array< uint8_t, MAX_ZWAVE_FRAME_SIZE > buffer_
ESPHOME_ALWAYS_INLINE bool response_handler_()
Definition zwave_proxy.h:90
api::enums::ZWaveProxyStatus zwave_proxy_request(api::APIConnection *api_connection, api::enums::ZWaveProxyRequestType type)
api::APIConnection * api_connection_
float get_setup_priority() const override
void on_connection_changed_(bool connected)
std::array< uint8_t, ZWAVE_HOME_ID_SIZE > home_id_
uint16_t type
ZWaveProxy * global_zwave_proxy
constexpr uint32_t encode_uint32(uint8_t byte1, uint8_t byte2, uint8_t byte3, uint8_t byte4)
Encode a 32-bit value given four bytes in most to least significant byte order.
Definition helpers.h:892
static void uint32_t
uint16_t length
Definition tt21100.cpp:0