[zwave_proxy] Clear Home ID on USB modem disconnect (#15327)

This commit is contained in:
Keith Burzinski
2026-03-31 14:34:16 -05:00
committed by GitHub
parent 2449aa75af
commit 26b426bbff
4 changed files with 80 additions and 7 deletions
+4
View File
@@ -85,6 +85,10 @@ class UARTComponent {
// @return UARTFlushResult indicating whether the flush was confirmed, timed out, failed, or assumed successful.
virtual UARTFlushResult flush() = 0;
// Returns true if the underlying transport is connected and operational.
// Hardware UARTs always return true. USB-backed UARTs override to reflect actual connection state.
virtual bool is_connected() { return true; }
// Sets the maximum time to wait for TX to drain during flush().
// Only meaningful on ESP32 (IDF). Other platforms ignore this value.
// @param flush_timeout_ms Timeout in milliseconds; 0 means wait indefinitely.
+1
View File
@@ -140,6 +140,7 @@ class USBUartChannel : public uart::UARTComponent, public Parented<USBUartCompon
bool peek_byte(uint8_t *data) override;
bool read_array(uint8_t *data, size_t len) override;
size_t available() override { return this->input_buffer_.get_available(); }
bool is_connected() override { return this->initialised_.load(); }
uart::UARTFlushResult flush() override;
void check_logger_conflict() override {}
void set_parity(UARTParityOptions parity) { this->parity_ = parity; }
+69 -7
View File
@@ -22,6 +22,8 @@ static constexpr uint8_t ZWAVE_COMMAND_GET_NETWORK_IDS = 0x20;
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 constexpr uint32_t HOME_ID_TIMEOUT_MS = 100; // Timeout for waiting for home ID during setup
static constexpr uint32_t RECONNECT_DELAY_MS = 500; // Delay between home ID query attempts after reconnect
static constexpr uint8_t MAX_QUERY_RETRIES = 5; // Max attempts to query home ID after reconnect
static uint8_t calculate_frame_checksum(const uint8_t *data, uint8_t length) {
// Calculate Z-Wave frame checksum
@@ -38,7 +40,10 @@ ZWaveProxy::ZWaveProxy() { global_zwave_proxy = this; }
void ZWaveProxy::setup() {
this->setup_time_ = App.get_loop_component_start_time();
this->send_simple_command_(ZWAVE_COMMAND_GET_NETWORK_IDS);
this->was_connected_ = this->parent_->is_connected();
if (this->was_connected_) {
this->send_simple_command_(ZWAVE_COMMAND_GET_NETWORK_IDS);
}
}
float ZWaveProxy::get_setup_priority() const {
@@ -84,6 +89,14 @@ void ZWaveProxy::loop() {
this->api_connection_ = nullptr; // Unsubscribe if disconnected
}
const bool connected = this->parent_->is_connected();
if (this->was_connected_ != connected) {
this->on_connection_changed_(connected);
}
if (this->reconnect_time_ != 0) {
this->retry_home_id_query_();
}
this->process_uart_();
this->status_clear_warning();
}
@@ -167,6 +180,55 @@ void ZWaveProxy::zwave_proxy_request(api::APIConnection *api_connection, api::en
}
}
void ZWaveProxy::on_connection_changed_(bool connected) {
this->was_connected_ = connected;
if (connected) {
ESP_LOGD(TAG, "Modem reconnected");
this->parsing_state_ = ZWAVE_PARSING_STATE_WAIT_START;
this->buffer_index_ = 0;
this->last_response_ = 0;
this->in_bootloader_ = false;
// Defer the query — the modem needs time to initialize after power is applied
this->reconnect_time_ = App.get_loop_component_start_time();
this->query_retries_ = 0;
} else {
ESP_LOGW(TAG, "Modem disconnected");
this->clear_home_id_();
}
}
void ZWaveProxy::retry_home_id_query_() {
if (this->home_id_ready_) {
// Got the home ID, cancel remaining retries
this->reconnect_time_ = 0;
return;
}
if (App.get_loop_component_start_time() - this->reconnect_time_ <= RECONNECT_DELAY_MS) {
return; // Not yet time for next attempt
}
this->reconnect_time_ = App.get_loop_component_start_time(); // Reset timer for next retry
this->query_retries_++;
if (this->query_retries_ <= MAX_QUERY_RETRIES) {
ESP_LOGD(TAG, "Querying Home ID (attempt %u)", this->query_retries_);
this->send_simple_command_(ZWAVE_COMMAND_GET_NETWORK_IDS);
} else {
ESP_LOGW(TAG, "Failed to read Home ID after %u attempts", MAX_QUERY_RETRIES);
this->reconnect_time_ = 0;
}
}
void ZWaveProxy::clear_home_id_() {
static constexpr uint8_t ZERO_HOME_ID[ZWAVE_HOME_ID_SIZE] = {};
if (this->set_home_id_(ZERO_HOME_ID)) {
this->send_homeid_changed_msg_();
}
this->home_id_ready_ = false;
this->parsing_state_ = ZWAVE_PARSING_STATE_WAIT_START;
this->buffer_index_ = 0;
this->last_response_ = 0;
this->in_bootloader_ = false;
}
bool ZWaveProxy::set_home_id_(const uint8_t *new_home_id) {
if (std::memcmp(this->home_id_.data(), new_home_id, this->home_id_.size()) == 0) {
ESP_LOGV(TAG, "Home ID unchanged");
@@ -309,7 +371,7 @@ void ZWaveProxy::parse_start_(uint8_t byte) {
this->parsing_state_ = ZWAVE_PARSING_STATE_WAIT_START;
switch (byte) {
case ZWAVE_FRAME_TYPE_START:
ESP_LOGVV(TAG, "Received START");
ESP_LOGV(TAG, "Received START");
if (this->in_bootloader_) {
ESP_LOGD(TAG, "Exited bootloader mode");
this->in_bootloader_ = false;
@@ -318,7 +380,7 @@ void ZWaveProxy::parse_start_(uint8_t byte) {
this->parsing_state_ = ZWAVE_PARSING_STATE_WAIT_LENGTH;
return;
case ZWAVE_FRAME_TYPE_BL_MENU:
ESP_LOGVV(TAG, "Received BL_MENU");
ESP_LOGV(TAG, "Received BL_MENU");
if (!this->in_bootloader_) {
ESP_LOGD(TAG, "Entered bootloader mode");
this->in_bootloader_ = true;
@@ -327,16 +389,16 @@ void ZWaveProxy::parse_start_(uint8_t byte) {
this->parsing_state_ = ZWAVE_PARSING_STATE_READ_BL_MENU;
return;
case ZWAVE_FRAME_TYPE_BL_BEGIN_UPLOAD:
ESP_LOGVV(TAG, "Received BL_BEGIN_UPLOAD");
ESP_LOGV(TAG, "Received BL_BEGIN_UPLOAD");
break;
case ZWAVE_FRAME_TYPE_ACK:
ESP_LOGVV(TAG, "Received ACK");
ESP_LOGV(TAG, "Received ACK");
break;
case ZWAVE_FRAME_TYPE_NAK:
ESP_LOGW(TAG, "Received NAK");
ESP_LOGV(TAG, "Received NAK");
break;
case ZWAVE_FRAME_TYPE_CAN:
ESP_LOGW(TAG, "Received CAN");
ESP_LOGV(TAG, "Received CAN");
break;
default:
ESP_LOGW(TAG, "Unrecognized START: 0x%02X", byte);
@@ -65,6 +65,9 @@ class ZWaveProxy : public uart::UARTDevice, public Component {
protected:
bool set_home_id_(const uint8_t *new_home_id); // Store a new home ID. Returns true if it changed.
void clear_home_id_(); // Clear home ID and notify API clients
void on_connection_changed_(bool connected); // Handle modem connect/disconnect transitions
void retry_home_id_query_(); // Retry home ID query after reconnect
void send_homeid_changed_msg_(api::APIConnection *conn = nullptr);
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)
@@ -80,14 +83,17 @@ class ZWaveProxy : public uart::UARTDevice, public Component {
// Pointers and 32-bit values (aligned together)
api::APIConnection *api_connection_{nullptr}; // Current subscribed client
uint32_t setup_time_{0}; // Time when setup() was called
uint32_t reconnect_time_{0}; // Timestamp of reconnect detection (0 = no pending query)
// Small values (grouped by size to minimize padding)
uint16_t buffer_index_{0}; // Index for populating the data buffer
uint16_t end_frame_after_{0}; // Payload reception ends after this index
uint8_t last_response_{0}; // Last response type sent
uint8_t query_retries_{0}; // Number of home ID query attempts after reconnect
ZWaveParsingState parsing_state_{ZWAVE_PARSING_STATE_WAIT_START};
bool in_bootloader_{false}; // True if the device is detected to be in bootloader mode
bool home_id_ready_{false}; // True when home ID has been received from Z-Wave module
bool was_connected_{false}; // Previous UART connection state for edge detection
};
extern ZWaveProxy *global_zwave_proxy; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)