diff --git a/esphome/components/ble_client/automation_gatt.h b/esphome/components/ble_client/automation_gatt.h index 98f70401eb..f4e5db4a5c 100644 --- a/esphome/components/ble_client/automation_gatt.h +++ b/esphome/components/ble_client/automation_gatt.h @@ -97,10 +97,14 @@ template class BLEClientWriteAction final : public Action // response-less path can complete synchronously inside the call, so the // handle is armed before the backend is touched. bool write(const uint8_t *data, size_t len) { - if (!this->resolved_ || !this->ble_client_->connected()) { + if (!this->ble_client_->connected()) { esph_log_w(Automation::TAG, "Cannot write to BLE characteristic - not connected"); return false; } + if (!this->resolved_) { + esph_log_w(Automation::TAG, "Cannot write to BLE characteristic - characteristic was not resolved"); + return false; + } #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE char hex_buf[format_hex_pretty_size(BLE_WRITE_MAX_LOG_BYTES)]; esph_log_vv(Automation::TAG, "Will write %d bytes: %s", len, format_hex_pretty_to(hex_buf, data, len)); diff --git a/esphome/components/ble_client/ble_client_gatt.cpp b/esphome/components/ble_client/ble_client_gatt.cpp index 2938fc899b..41a647cf20 100644 --- a/esphome/components/ble_client/ble_client_gatt.cpp +++ b/esphome/components/ble_client/ble_client_gatt.cpp @@ -74,10 +74,12 @@ void BLEClient::disconnect() { return; // A deliberate teardown's failure report must not feed the backoff. this->cancel_requested_ = true; - if (this->backend_->gatt_disconnect() != 0) { - // Refused synchronously: the backend is already down and no report will - // come; settle through the normal path so waiters resolve. - this->on_connection_state(false, 0, 0); + int err = this->backend_->gatt_disconnect(); + if (err != 0) { + // Refused synchronously (states diverged; the backend is already down): + // settle through the normal path, carrying the real code so the log is + // distinguishable from a clean teardown. + this->on_connection_state(false, 0, err); } } diff --git a/esphome/components/bluetooth_connection/__init__.py b/esphome/components/bluetooth_connection/__init__.py index d2dfd70b50..858dfa91f5 100644 --- a/esphome/components/bluetooth_connection/__init__.py +++ b/esphome/components/bluetooth_connection/__init__.py @@ -154,11 +154,13 @@ def _ledger() -> _SlotLedger: return CORE.data[DOMAIN] -def consume_gatt_slot(consumer: str, count: int = 1): - """Validator claiming GATT connection slots — the one spelling for every - claimant (the proxy per configured slot, dedicated backends once). The - neutral ledger feeds the platform cap check in FINAL_VALIDATE_SCHEMA; - esp32 additionally charges the controller's connection budget.""" +def consume_gatt_slot( + consumer: str, count: int = 1 +) -> Callable[[ConfigType], ConfigType]: + """Validator claiming GATT connection slots for the neutral ledger (the + platform cap check in FINAL_VALIDATE_SCHEMA); esp32 additionally charges + the controller's connection budget. The proxy's esp32 arm still charges + the controller directly - route it through here when it migrates.""" def validator(config: ConfigType) -> ConfigType: _ledger().consumers.extend([consumer] * count)