From 6c29bbc506875f884fb0445788ccb8db5d030a16 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 10 Aug 2026 18:05:41 -0500 Subject: [PATCH] Initiate cached connections at medium parameters and dispose stale successes --- .../bluetooth_connection_hub.cpp | 8 ++--- .../bluetooth_connection_rp2.cpp | 35 +++++++++++-------- .../bluetooth_connection_rp2.h | 4 +-- 3 files changed, 26 insertions(+), 21 deletions(-) diff --git a/esphome/components/bluetooth_connection/bluetooth_connection_hub.cpp b/esphome/components/bluetooth_connection/bluetooth_connection_hub.cpp index b913bb9a55..08139bec6d 100644 --- a/esphome/components/bluetooth_connection/bluetooth_connection_hub.cpp +++ b/esphome/components/bluetooth_connection/bluetooth_connection_hub.cpp @@ -103,10 +103,10 @@ void BluetoothConnection::on_connection_state(bool connected, uint16_t mtu, int if (this->connection_type_ == ConnectionType::V3_WITH_CACHE) { // The API client has the services cached; never discover them. No // discovery phase needs the fast interval, so settle straight into the - // shared steady-state parameters. On esp32 the backend already set the - // same values as prefer-params before opening, so this request is - // usually redundant there - kept because rp2 has no prefer-params and - // the explicit update is its only path to the steady-state interval. + // shared steady-state parameters. Both backends already open cached + // connections with these values (esp32 prefer-params, rp2 initiating + // params), so this request is normally redundant - kept as a backstop + // in case the initial parameters were negotiated away. this->state_ = ClientState::ESTABLISHED; int param_err = this->backend_->update_connection_params(ble_device_base::MEDIUM_MIN_CONN_INTERVAL, ble_device_base::MEDIUM_MAX_CONN_INTERVAL, 0, diff --git a/esphome/components/bluetooth_connection/bluetooth_connection_rp2.cpp b/esphome/components/bluetooth_connection/bluetooth_connection_rp2.cpp index eff07fb5a0..08aa4689c2 100644 --- a/esphome/components/bluetooth_connection/bluetooth_connection_rp2.cpp +++ b/esphome/components/bluetooth_connection/bluetooth_connection_rp2.cpp @@ -163,11 +163,15 @@ void RP2GattClient::hci_packet_handler(uint8_t type, uint16_t channel, uint8_t * // Addressed completion for a peer the owner is not connecting to: a // success delayed past a cancel and an ownership handoff (the cancel // idles the stack's request immediately) must not stamp the old - // procedure's link onto the new owner. Drop; the owner's own - // completion follows. Zero-address (cancel) completions need no such - // guard: BTstack only emits them while its request state is idle, and - // a new owner re-arms that state when it claims the token, so a stale - // cancel completion is swallowed by the stack, never re-attributed. + // procedure's link onto the new owner. Zero-address (cancel) + // completions need no such guard: BTstack only emits them while its + // request state is idle, and a new owner re-arms that state when it + // claims the token, so a stale cancel completion is swallowed by the + // stack, never re-attributed. A successful stale link still needs + // disposal (same hazard as the unowned branch below). + if (status == 0) { + gap_disconnect(con_handle); + } break; } connect_owner = nullptr; @@ -541,14 +545,6 @@ void RP2GattClient::handle_event_(const RP2GattEvent &event) { this->state_ = EngineState::READY; // Scanning resumes and runs alongside the established connection. this->release_scan_inhibit_(); - if (this->connection_type_ == ble_device_base::ConnectionType::V3_WITH_CACHE) { - // Cached connections never run discovery, so nothing consumes the - // FAST interval; settle to MEDIUM now (esp32 parity). Sustained - // FAST intervals starve WiFi on the shared CYW43 radio. - BluetoothLock lock; - gap_update_connection_parameters(this->con_handle_, MEDIUM_MIN_CONN_INTERVAL, MEDIUM_MAX_CONN_INTERVAL, 0, - MEDIUM_CONN_TIMEOUT); - } this->listener_->on_connection_state(true, this->mtu_, 0); } break; @@ -969,8 +965,17 @@ int RP2GattClient::try_gap_connect_() { if (connect_owner != nullptr) { status = ERROR_CODE_COMMAND_DISALLOWED; } else { - gap_set_connection_parameters(CONN_SCAN_INTERVAL, CONN_SCAN_WINDOW, FAST_MIN_CONN_INTERVAL, - FAST_MAX_CONN_INTERVAL, 0, FAST_CONN_TIMEOUT, CONN_CE_MIN, CONN_CE_MAX); + // esp32 parity: cached connections come up at MEDIUM already (nothing + // consumes the fast interval without a discovery phase), so there is no + // post-connect update procedure to race or silently lose; sustained + // FAST intervals also starve WiFi on the shared CYW43 radio. + // Without-cache runs FAST for discovery and steps down in + // finish_discovery_. + bool cached = this->connection_type_ == ble_device_base::ConnectionType::V3_WITH_CACHE; + gap_set_connection_parameters(CONN_SCAN_INTERVAL, CONN_SCAN_WINDOW, + cached ? MEDIUM_MIN_CONN_INTERVAL : FAST_MIN_CONN_INTERVAL, + cached ? MEDIUM_MAX_CONN_INTERVAL : FAST_MAX_CONN_INTERVAL, 0, + cached ? MEDIUM_CONN_TIMEOUT : FAST_CONN_TIMEOUT, CONN_CE_MIN, CONN_CE_MAX); status = gap_connect(this->peer_addr_, this->peer_addr_type_); if (status == 0) { connect_owner = this; diff --git a/esphome/components/bluetooth_connection/bluetooth_connection_rp2.h b/esphome/components/bluetooth_connection/bluetooth_connection_rp2.h index 5de198391f..4d407269b6 100644 --- a/esphome/components/bluetooth_connection/bluetooth_connection_rp2.h +++ b/esphome/components/bluetooth_connection/bluetooth_connection_rp2.h @@ -105,8 +105,8 @@ class RP2GattClient final : public Component, int pair(); int update_connection_params(uint16_t min_interval, uint16_t max_interval, uint16_t latency, uint16_t timeout); ble_device_base::GattServiceTable get_service_table(); - // Cached connections settle straight to MEDIUM parameters on link-up - // (esp32 parity); FAST is reserved for connect and discovery. + // Cached connections initiate at MEDIUM parameters (esp32 parity); FAST is + // reserved for the discovery phase of uncached connects. void set_connection_type(ble_device_base::ConnectionType ct) { this->connection_type_ = ct; } void release_services();