From aceff2794899fdd86979c87aed4d32bf2f446b2b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 9 Aug 2026 14:13:50 -0500 Subject: [PATCH] Retry a dropped connections-free update Field bug on a Pico W: the slot freed device-side but the API client kept free=0 with the address still allocated. send_message drops the response when the TCP buffer is full (a boot storm makes that likely right when HA reconnects), and nothing ever resent it - the client's slot state is verbatim the last response received, so it stayed stale until reboot. The cached response is current by construction, so a pending bit retried from loop() is an idempotent resync; the subscribe-time send heals through the same bit. --- esphome/components/bluetooth_proxy/bluetooth_proxy.cpp | 10 +++++++++- esphome/components/bluetooth_proxy/bluetooth_proxy.h | 4 ++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp index ea1dacda32..af62b211bf 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp @@ -475,6 +475,11 @@ void BluetoothProxy::loop() { for (uint8_t i = 0; i < this->connection_count_; i++) { this->connections_[i]->process_pending_services(); } + if (this->connections_free_pending_ && this->api_connection_ != nullptr) { + // Resend a dropped slot-state update once the TCP buffer drains. + this->connections_free_pending_ = false; + this->send_connections_free(this->api_connection_); + } #endif // Run advertisement flush / scanner-state poll every 100ms @@ -629,7 +634,10 @@ void BluetoothProxy::send_connections_free() { } void BluetoothProxy::send_connections_free(api::APIConnection *api_connection) { - api_connection->send_message(this->connections_free_response_); + if (!api_connection->send_message(this->connections_free_response_)) { + ESP_LOGD(TAG, "Connections-free update deferred, TCP buffer full"); + this->connections_free_pending_ = true; + } } void BluetoothProxy::send_gatt_services_done(uint64_t address) { diff --git a/esphome/components/bluetooth_proxy/bluetooth_proxy.h b/esphome/components/bluetooth_proxy/bluetooth_proxy.h index 17e480b671..26f99fcca2 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_proxy.h +++ b/esphome/components/bluetooth_proxy/bluetooth_proxy.h @@ -254,6 +254,10 @@ class BluetoothProxy final : public Component { // Group 4: 1-byte types grouped together bool active_; + // A dropped send (full TCP buffer) would leave the API client with a stale + // slot state forever; the cached response is current by construction, so + // retrying it from loop() is an idempotent resync. + bool connections_free_pending_{false}; uint8_t connection_count_{0}; bool configured_scan_active_{false}; // Configured scan mode from YAML #ifndef USE_BLE_SCANNER_STATE_CALLBACK