mirror of
https://github.com/esphome/esphome.git
synced 2026-05-25 18:47:56 +08:00
[zwave_proxy] Clear Home ID on USB modem disconnect (#15327)
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user