mirror of
https://github.com/esphome/esphome.git
synced 2026-03-04 03:38:20 -07:00
optimize
This commit is contained in:
@@ -18,10 +18,10 @@ _DEFAULT_HW_INITIAL_TIMEOUT = 1600
|
||||
_DEFAULT_HW_MIN_TIMEOUT = 400
|
||||
_DEFAULT_HW_MAX_TIMEOUT = 3200
|
||||
|
||||
# Optimized ACK timeout values for USB CDC ACM paths (~20-30 ms round-trip)
|
||||
_DEFAULT_USB_INITIAL_TIMEOUT = 200
|
||||
_DEFAULT_USB_MIN_TIMEOUT = 50
|
||||
_DEFAULT_USB_MAX_TIMEOUT = 500
|
||||
# Optimized ACK timeout values for USB CDC ACM paths (~3-5 ms round-trip with RX callback)
|
||||
_DEFAULT_USB_INITIAL_TIMEOUT = 30
|
||||
_DEFAULT_USB_MIN_TIMEOUT = 15
|
||||
_DEFAULT_USB_MAX_TIMEOUT = 200
|
||||
|
||||
zigbee_proxy_ns = cg.esphome_ns.namespace("zigbee_proxy")
|
||||
ZigbeeProxy = zigbee_proxy_ns.class_("ZigbeeProxy", cg.Component, uart.UARTDevice)
|
||||
@@ -47,13 +47,13 @@ CONFIG_SCHEMA = cv.All(
|
||||
esp8266=512,
|
||||
default=1024,
|
||||
),
|
||||
# When usb_uart_id is present the component selects USB-optimized ACK
|
||||
# timeout defaults (~20-30 ms round-trip) instead of the hardware UART
|
||||
# defaults (~2-5 ms round-trip). Explicit timeout keys always win.
|
||||
# When usb_uart_id is present the component registers an RX callback
|
||||
# for zero-wakeup-cycle data delivery and selects USB-optimized ACK
|
||||
# timeout defaults. Explicit timeout keys always win.
|
||||
cv.Optional(CONF_USB_UART_ID): cv.use_id(usb_uart.USBUartChannel),
|
||||
cv.Optional(CONF_INITIAL_TIMEOUT): cv.int_range(min=100, max=10000),
|
||||
cv.Optional(CONF_MIN_TIMEOUT): cv.int_range(min=100, max=5000),
|
||||
cv.Optional(CONF_MAX_TIMEOUT): cv.int_range(min=500, max=10000),
|
||||
cv.Optional(CONF_INITIAL_TIMEOUT): cv.int_range(min=10, max=10000),
|
||||
cv.Optional(CONF_MIN_TIMEOUT): cv.int_range(min=10, max=5000),
|
||||
cv.Optional(CONF_MAX_TIMEOUT): cv.int_range(min=50, max=10000),
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
@@ -78,10 +78,15 @@ async def to_code(config):
|
||||
cg.add_define("ZIGBEE_PROXY_BUFFER_SIZE", config[CONF_BUFFER_SIZE])
|
||||
|
||||
# Select timeout defaults based on UART transport type.
|
||||
# USB CDC ACM has higher round-trip latency (~20-30 ms) than hardware UART
|
||||
# (~2-5 ms), so the adaptive ACK timeout algorithm needs different starting
|
||||
# points to avoid unnecessary stalls at boot.
|
||||
# USB CDC ACM with the RX callback has ~3-5 ms round-trip latency; hardware
|
||||
# UART is similar (~2-5 ms). Different defaults are kept so that future
|
||||
# non-callback USB paths still get conservative starting values.
|
||||
is_usb = CONF_USB_UART_ID in config
|
||||
if is_usb:
|
||||
cg.add_define("USE_ZIGBEE_PROXY_USB_UART")
|
||||
usb_ch = await cg.get_variable(config[CONF_USB_UART_ID])
|
||||
cg.add(var.set_usb_uart_channel(usb_ch))
|
||||
|
||||
initial_timeout = config.get(
|
||||
CONF_INITIAL_TIMEOUT,
|
||||
_DEFAULT_USB_INITIAL_TIMEOUT if is_usb else _DEFAULT_HW_INITIAL_TIMEOUT,
|
||||
|
||||
@@ -31,8 +31,8 @@ static const uint16_t CRC_TABLE[256] = {
|
||||
0x1CE0, 0x0CC1, 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74,
|
||||
0x2E93, 0x3EB2, 0x0ED1, 0x1EF0};
|
||||
|
||||
uint16_t ZigbeeProxy::calculate_crc_(const uint8_t *data, size_t length) {
|
||||
uint16_t crc = ASH_CRC_INIT;
|
||||
uint16_t ZigbeeProxy::calculate_crc_(const uint8_t *data, size_t length, uint16_t init) {
|
||||
uint16_t crc = init;
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
crc = (crc << 8) ^ CRC_TABLE[(crc >> 8) ^ data[i]];
|
||||
}
|
||||
@@ -241,8 +241,6 @@ bool ZigbeeProxy::parse_byte_(uint8_t byte) {
|
||||
this->parsing_state_ = ParsingState::WAIT_DATA;
|
||||
ESP_LOGV(TAG, "Frame start detected (control byte 0x%02X)", byte);
|
||||
}
|
||||
// Check for bootloader patterns
|
||||
this->check_bootloader_mode_(&byte, 1);
|
||||
break;
|
||||
|
||||
case ParsingState::WAIT_CONTROL:
|
||||
@@ -275,8 +273,9 @@ bool ZigbeeProxy::parse_byte_(uint8_t byte) {
|
||||
if (this->validate_frame_crc_()) {
|
||||
this->parse_control_byte_(this->rx_buffer_[0]);
|
||||
} else {
|
||||
// CRC failed - log frame contents for debugging
|
||||
ESP_LOGW(TAG, "CRC failed, frame (%u bytes): %s", this->rx_buffer_index_,
|
||||
// CRC failed - WARN logs byte count only; hex dump at VERBOSE to avoid heap allocation in production
|
||||
ESP_LOGW(TAG, "CRC failed (%u bytes)", this->rx_buffer_index_);
|
||||
ESP_LOGV(TAG, "CRC failed frame: %s",
|
||||
format_hex_pretty(this->rx_buffer_.data(), this->rx_buffer_index_).c_str());
|
||||
this->send_nak_frame_(this->rx_sequence_);
|
||||
}
|
||||
@@ -354,13 +353,6 @@ size_t ZigbeeProxy::build_frame_(uint8_t *output, const uint8_t *data, size_t le
|
||||
output[pos++] = control;
|
||||
}
|
||||
|
||||
// Prepare CRC calculation buffer (control + data)
|
||||
uint8_t crc_buffer[MAX_ASH_FRAME_SIZE];
|
||||
crc_buffer[0] = control;
|
||||
if (length > 0) {
|
||||
memcpy(crc_buffer + 1, data, length);
|
||||
}
|
||||
|
||||
// Add data payload with stuffing
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
uint8_t byte = data[i];
|
||||
@@ -373,8 +365,11 @@ size_t ZigbeeProxy::build_frame_(uint8_t *output, const uint8_t *data, size_t le
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate CRC
|
||||
uint16_t crc = this->calculate_crc_(crc_buffer, 1 + length);
|
||||
// Calculate CRC incrementally over control byte then data (avoids a MAX_ASH_FRAME_SIZE stack copy)
|
||||
uint16_t crc = this->calculate_crc_(&control, 1);
|
||||
if (length > 0) {
|
||||
crc = this->calculate_crc_(data, length, crc);
|
||||
}
|
||||
|
||||
// Add CRC with stuffing (big-endian)
|
||||
uint8_t crc_high = (crc >> 8) & 0xFF;
|
||||
|
||||
@@ -11,6 +11,10 @@
|
||||
#include "esphome/components/wifi/wifi_component.h"
|
||||
#endif
|
||||
|
||||
#ifdef USE_ZIGBEE_PROXY_USB_UART
|
||||
#include "esphome/components/usb_uart/usb_uart.h"
|
||||
#endif
|
||||
|
||||
namespace esphome::zigbee_proxy {
|
||||
|
||||
static const char *const TAG = "zigbee_proxy";
|
||||
@@ -184,6 +188,13 @@ void ZigbeeProxy::set_timeout_config(uint32_t initial_ms, uint32_t min_ms, uint3
|
||||
ESP_LOGV(TAG, "Timeout config updated: initial=%u, min=%u, max=%u", initial_ms, min_ms, max_ms);
|
||||
}
|
||||
|
||||
#ifdef USE_ZIGBEE_PROXY_USB_UART
|
||||
void ZigbeeProxy::set_usb_uart_channel(usb_uart::USBUartChannel *channel) {
|
||||
channel->set_rx_callback([this]() { this->process_uart_(); });
|
||||
ESP_LOGD(TAG, "Registered USB UART RX callback for low-latency processing");
|
||||
}
|
||||
#endif
|
||||
|
||||
// ASH Protocol State Machine
|
||||
void ZigbeeProxy::reset_ash_protocol_() {
|
||||
ESP_LOGV(TAG, "Resetting ASH protocol");
|
||||
@@ -232,17 +243,15 @@ void ZigbeeProxy::handle_rstack_frame_(const uint8_t *data, size_t length) {
|
||||
ESP_LOGV(TAG, "Received RSTACK, starting EZSP initialization");
|
||||
this->ash_state_ = AshState::CONNECTED;
|
||||
|
||||
// Non-blocking drain: give the NCP 10 ms to settle and flush any stale bytes.
|
||||
// yield() lets the USB task deliver buffered bytes into the ring buffer so they
|
||||
// can be consumed here rather than contaminating the next EZSP exchange.
|
||||
uint32_t drain_deadline = millis() + 10;
|
||||
while (millis() < drain_deadline) {
|
||||
while (this->available()) {
|
||||
uint8_t discard;
|
||||
this->read_byte(&discard);
|
||||
ESP_LOGV(TAG, "Draining post-RSTACK byte: 0x%02X", discard);
|
||||
}
|
||||
yield();
|
||||
// Drain any stale bytes that arrived before the RSTACK (e.g. leftover
|
||||
// UART FIFO bytes on HW UART, or a partial prior frame on USB CDC).
|
||||
// For USB CDC the input_buffer_ is already fully up-to-date at this point
|
||||
// (the RX callback just moved all pending chunks into it), so this loop
|
||||
// completes immediately rather than spinning with yield().
|
||||
while (this->available()) {
|
||||
uint8_t discard;
|
||||
this->read_byte(&discard);
|
||||
ESP_LOGV(TAG, "Draining post-RSTACK byte: 0x%02X", discard);
|
||||
}
|
||||
|
||||
this->boot_state_ = BootState::SEND_VERSION;
|
||||
@@ -329,7 +338,6 @@ bool ZigbeeProxy::send_ack_frame_(uint8_t ack_num) {
|
||||
uint8_t frame[8];
|
||||
size_t length = this->build_frame_(frame, nullptr, 0, AshFrameType::ACK, 0, ack_num);
|
||||
this->write_array(frame, length);
|
||||
this->flush();
|
||||
this->last_ack_sent_ = ack_num;
|
||||
ESP_LOGV(TAG, "Sent ACK for frame %d", ack_num);
|
||||
return true;
|
||||
@@ -339,7 +347,6 @@ bool ZigbeeProxy::send_nak_frame_(uint8_t ack_num) {
|
||||
uint8_t frame[8];
|
||||
size_t length = this->build_frame_(frame, nullptr, 0, AshFrameType::NAK, 0, ack_num);
|
||||
this->write_array(frame, length);
|
||||
this->flush();
|
||||
ESP_LOGW(TAG, "Sent NAK for frame %d", ack_num);
|
||||
return true;
|
||||
}
|
||||
@@ -368,7 +375,6 @@ bool ZigbeeProxy::send_data_frame_(const uint8_t *data, size_t length, bool retr
|
||||
|
||||
// Send frame
|
||||
this->write_array(this->tx_buffer_.data(), frame_length);
|
||||
this->flush();
|
||||
|
||||
// Start ACK timer
|
||||
this->tx_buffer_pending_ = true;
|
||||
@@ -431,7 +437,6 @@ void ZigbeeProxy::handle_retransmission_() {
|
||||
|
||||
// Resend the pending frame
|
||||
this->write_array(this->tx_pending_buffer_.data(), this->tx_pending_length_);
|
||||
this->flush();
|
||||
this->start_ack_timer_();
|
||||
}
|
||||
|
||||
@@ -749,14 +754,7 @@ void ZigbeeProxy::handle_network_params_response_(const uint8_t *data, size_t le
|
||||
}
|
||||
|
||||
bool ZigbeeProxy::set_ieee_address_(const uint8_t *new_address) {
|
||||
bool changed = false;
|
||||
|
||||
for (size_t i = 0; i < ZIGBEE_IEEE_ADDR_SIZE; i++) {
|
||||
if (this->network_info_.ieee_address[i] != new_address[i]) {
|
||||
changed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
bool changed = memcmp(this->network_info_.ieee_address.data(), new_address, ZIGBEE_IEEE_ADDR_SIZE) != 0;
|
||||
|
||||
if (changed) {
|
||||
memcpy(this->network_info_.ieee_address.data(), new_address, ZIGBEE_IEEE_ADDR_SIZE);
|
||||
@@ -917,11 +915,10 @@ void ZigbeeProxy::client_send_rstack_frame_(uint8_t reset_code) {
|
||||
}
|
||||
|
||||
void ZigbeeProxy::client_send_data_frame_(const uint8_t *data, size_t length) {
|
||||
uint8_t frame[MAX_ASH_FRAME_SIZE];
|
||||
size_t frame_length =
|
||||
this->build_frame_(frame, data, length, AshFrameType::DATA, this->client_tx_sequence_, this->client_rx_sequence_);
|
||||
size_t frame_length = this->build_frame_(this->client_tx_buffer_.data(), data, length, AshFrameType::DATA,
|
||||
this->client_tx_sequence_, this->client_rx_sequence_);
|
||||
this->client_tx_sequence_ = (this->client_tx_sequence_ + 1) & ASH_MAX_SEQUENCE;
|
||||
this->client_send_raw_frame_(frame, frame_length);
|
||||
this->client_send_raw_frame_(this->client_tx_buffer_.data(), frame_length);
|
||||
ESP_LOGV(TAG, "Sent client DATA frame, payload %u bytes", length);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,16 @@
|
||||
|
||||
#include <array>
|
||||
|
||||
// Forward-declare USBUartChannel so the set_usb_uart_channel() setter can be declared
|
||||
// without pulling usb_uart.h into every translation unit that includes this header.
|
||||
// USE_ZIGBEE_PROXY_USB_UART is defined by the Python to_code() only when usb_uart_id
|
||||
// is present in the YAML, ensuring the header is actually in the build path.
|
||||
#ifdef USE_ZIGBEE_PROXY_USB_UART
|
||||
namespace esphome::usb_uart {
|
||||
class USBUartChannel;
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace esphome::zigbee_proxy {
|
||||
|
||||
// Timeout configuration structure
|
||||
@@ -83,6 +93,14 @@ class ZigbeeProxy : public uart::UARTDevice, public Component {
|
||||
void set_min_timeout(uint32_t timeout_ms) { this->timeout_config_.min_timeout_ms = timeout_ms; }
|
||||
void set_max_timeout(uint32_t timeout_ms) { this->timeout_config_.max_timeout_ms = timeout_ms; }
|
||||
|
||||
#ifdef USE_ZIGBEE_PROXY_USB_UART
|
||||
/// Called from generated code when usb_uart_id is configured.
|
||||
/// Registers an RX callback on the channel so incoming bytes are processed
|
||||
/// immediately in the same USBUartComponent::loop() iteration they arrive,
|
||||
/// without waiting for the next ZigbeeProxy::loop() call.
|
||||
void set_usb_uart_channel(usb_uart::USBUartChannel *channel);
|
||||
#endif
|
||||
|
||||
protected:
|
||||
// ASH Protocol State Machine
|
||||
void reset_ash_protocol_();
|
||||
@@ -99,7 +117,7 @@ class ZigbeeProxy : public uart::UARTDevice, public Component {
|
||||
bool validate_frame_crc_();
|
||||
size_t build_frame_(uint8_t *output, const uint8_t *data, size_t length, AshFrameType type, uint8_t frame_num = 0,
|
||||
uint8_t ack_num = 0, bool retx = false);
|
||||
uint16_t calculate_crc_(const uint8_t *data, size_t length);
|
||||
uint16_t calculate_crc_(const uint8_t *data, size_t length, uint16_t init = ASH_CRC_INIT);
|
||||
|
||||
// Sequence number management
|
||||
void increment_tx_sequence_() { this->tx_sequence_ = (this->tx_sequence_ + 1) & ASH_MAX_SEQUENCE; }
|
||||
|
||||
Reference in New Issue
Block a user