mirror of
https://github.com/esphome/esphome.git
synced 2026-08-29 17:16:45 +00:00
Flip the esp32 proxy onto the hub wrapper
This commit is contained in:
@@ -5,19 +5,12 @@ user-facing configuration; the proxy's codegen declares and registers the
|
||||
connection instances.
|
||||
"""
|
||||
|
||||
import functools
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.config_helpers import filter_source_files_from_platform
|
||||
from esphome.const import PLATFORM_RP2, PlatformFramework
|
||||
from esphome.core import CORE
|
||||
|
||||
|
||||
def AUTO_LOAD() -> list[str]:
|
||||
"""The esp32 connection header includes esp32_ble_client, so the closure
|
||||
must be self-satisfying; no target platform (tooling) gets the union."""
|
||||
if CORE.is_esp32 or CORE.target_platform is None:
|
||||
return ["ble_device_base", "esp32_ble_client"]
|
||||
return ["ble_device_base"]
|
||||
|
||||
|
||||
@@ -41,17 +34,6 @@ BluedroidGattClient = bluetooth_connection_ns.class_(
|
||||
)
|
||||
|
||||
|
||||
@functools.cache
|
||||
def esp32_connection_class() -> cg.MockObjClass:
|
||||
"""Lazy: importing esp32_ble_client registers esp32-only automations as
|
||||
an import side effect, which must not leak into other platforms."""
|
||||
from esphome.components import esp32_ble_client
|
||||
|
||||
return bluetooth_connection_ns.class_(
|
||||
"BluetoothConnection", esp32_ble_client.BLEClientBase
|
||||
)
|
||||
|
||||
|
||||
FILTER_SOURCE_FILES = filter_source_files_from_platform(
|
||||
{
|
||||
"bluetooth_connection_bluedroid.cpp": {
|
||||
@@ -68,6 +50,8 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform(
|
||||
"bluetooth_connection_hub.cpp": {
|
||||
PlatformFramework.RP2_ARDUINO,
|
||||
PlatformFramework.LN882X_ARDUINO,
|
||||
PlatformFramework.ESP32_ARDUINO,
|
||||
PlatformFramework.ESP32_IDF,
|
||||
},
|
||||
"bluetooth_connection_rp2.cpp": {PlatformFramework.RP2_ARDUINO},
|
||||
}
|
||||
|
||||
@@ -12,7 +12,9 @@
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifndef CONFIG_ESP_HOSTED_ENABLE_BT_BLUEDROID
|
||||
#include <esp_bt.h>
|
||||
#endif
|
||||
#include <esp_gatt_common_api.h>
|
||||
|
||||
#include <cstring>
|
||||
@@ -342,7 +344,7 @@ void BluedroidGattClient::stream_service_batch(BluetoothConnection &conn) {
|
||||
resp.services.emplace_back();
|
||||
auto &service_resp = resp.services.back();
|
||||
fill_gatt_uuid(service_resp.uuid, service_resp.short_uuid,
|
||||
esp32_ble_tracker::ESPBTUUID::from_uuid(service_result.uuid), use_efficient_uuids);
|
||||
ble_device_base::ESPBTUUID::from_uuid(service_result.uuid), use_efficient_uuids);
|
||||
service_resp.handle = service_result.start_handle;
|
||||
|
||||
if (total_char_count > 0) {
|
||||
@@ -368,7 +370,7 @@ void BluedroidGattClient::stream_service_batch(BluetoothConnection &conn) {
|
||||
service_resp.characteristics.emplace_back();
|
||||
auto &characteristic_resp = service_resp.characteristics.back();
|
||||
fill_gatt_uuid(characteristic_resp.uuid, characteristic_resp.short_uuid,
|
||||
esp32_ble_tracker::ESPBTUUID::from_uuid(char_result.uuid), use_efficient_uuids);
|
||||
ble_device_base::ESPBTUUID::from_uuid(char_result.uuid), use_efficient_uuids);
|
||||
characteristic_resp.handle = char_result.char_handle;
|
||||
characteristic_resp.properties = char_result.properties;
|
||||
|
||||
@@ -396,7 +398,7 @@ void BluedroidGattClient::stream_service_batch(BluetoothConnection &conn) {
|
||||
characteristic_resp.descriptors.emplace_back();
|
||||
auto &descriptor_resp = characteristic_resp.descriptors.back();
|
||||
fill_gatt_uuid(descriptor_resp.uuid, descriptor_resp.short_uuid,
|
||||
esp32_ble_tracker::ESPBTUUID::from_uuid(desc_result.uuid), use_efficient_uuids);
|
||||
ble_device_base::ESPBTUUID::from_uuid(desc_result.uuid), use_efficient_uuids);
|
||||
descriptor_resp.handle = desc_result.handle;
|
||||
desc_offset++;
|
||||
}
|
||||
@@ -439,7 +441,7 @@ void BluedroidGattClient::handle_open_evt_(esp_ble_gattc_cb_param_t *param) {
|
||||
this->report_connection_state_(false, param->open.status);
|
||||
return;
|
||||
}
|
||||
if (this->shim_.disconnect_scheduled()) {
|
||||
if (this->shim_.disconnect_pending()) {
|
||||
// Earliest point conn_id_ exists; keep it set so CLOSE_EVT still matches.
|
||||
this->unconditional_disconnect_();
|
||||
return;
|
||||
|
||||
@@ -1,482 +1,36 @@
|
||||
#include "bluetooth_connection_esp32.h"
|
||||
// Address-scoped Bluedroid maintenance shared by every esp32 proxy build,
|
||||
// including advertisement-only ones where no GATT backend is compiled.
|
||||
|
||||
#include "esphome/components/api/api_pb2.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "esphome/components/bluetooth_proxy/bluetooth_proxy.h"
|
||||
#include "bluetooth_connection.h"
|
||||
|
||||
#include <esp_bt_device.h>
|
||||
#include <esp_gattc_api.h>
|
||||
|
||||
namespace esphome::bluetooth_connection {
|
||||
|
||||
namespace espbt = esphome::esp32_ble_tracker;
|
||||
|
||||
using ble_device_base::ESPBTUUID;
|
||||
|
||||
static const char *const TAG = "bluetooth_connection";
|
||||
namespace {
|
||||
void address_to_bda(uint64_t address, esp_bd_addr_t &bda) {
|
||||
for (uint8_t i = 0; i < 6; i++) {
|
||||
bda[i] = (address >> ((5 - i) * 8)) & 0xFF;
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
conn_err_t unpair_device(uint64_t address) {
|
||||
esp_bd_addr_t bd_addr;
|
||||
ble_device_base::uint64_to_mac_msb_first(address, bd_addr);
|
||||
return esp_ble_remove_bond_device(bd_addr);
|
||||
esp_bd_addr_t bda;
|
||||
address_to_bda(address, bda);
|
||||
return esp_ble_remove_bond_device(bda);
|
||||
}
|
||||
|
||||
conn_err_t clear_gatt_cache(uint64_t address) {
|
||||
esp_bd_addr_t bd_addr;
|
||||
ble_device_base::uint64_to_mac_msb_first(address, bd_addr);
|
||||
return esp_ble_gattc_cache_clean(bd_addr);
|
||||
}
|
||||
|
||||
void BluetoothConnection::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "BLE Connection:");
|
||||
BLEClientBase::dump_config();
|
||||
}
|
||||
|
||||
void BluetoothConnection::set_address(uint64_t address) {
|
||||
// Keep the proxy's pre-allocated connections-free message in step
|
||||
this->proxy_->update_address_slot_(this->address_, address);
|
||||
// Call parent implementation to actually set the address
|
||||
BLEClientBase::set_address(address);
|
||||
}
|
||||
|
||||
void BluetoothConnection::loop() {
|
||||
BLEClientBase::loop();
|
||||
|
||||
// Early return if no active connection
|
||||
if (this->address_ == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle service discovery if in valid range
|
||||
if (this->send_service_ >= 0 && this->send_service_ <= this->service_count_) {
|
||||
this->send_service_for_discovery_();
|
||||
}
|
||||
|
||||
// Check if we should disable the loop
|
||||
// - For V3_WITH_CACHE: Services are never sent, disable after INIT state
|
||||
// - For V3_WITHOUT_CACHE: Disable only after service discovery is complete
|
||||
// (send_service_ == DONE_SENDING_SERVICES, which is only set after services are sent)
|
||||
// Never disable while DISCONNECTING — BLEClientBase::loop() needs to keep running so the
|
||||
// 10s safety timeout can force IDLE if CLOSE_EVT is never delivered.
|
||||
if (this->state() != espbt::ClientState::INIT && this->state() != espbt::ClientState::DISCONNECTING &&
|
||||
(this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE ||
|
||||
this->send_service_ == DONE_SENDING_SERVICES)) {
|
||||
this->disable_loop();
|
||||
}
|
||||
}
|
||||
|
||||
void BluetoothConnection::on_disconnect_complete(esp_err_t reason) {
|
||||
// Called from both the CLOSE_EVT handler and the DISCONNECTING safety timeout in the
|
||||
// base class. Free the proxy slot, notify the API client, and reset send_service_.
|
||||
// address_ may already be 0 if reset_connection_ ran earlier on this teardown.
|
||||
if (this->address_ == 0) {
|
||||
return;
|
||||
}
|
||||
ESP_LOGD(TAG, "[%d] [%s] Close, reason=0x%02x, freeing slot", this->connection_index_, this->address_str_, reason);
|
||||
this->reset_connection_(reason);
|
||||
}
|
||||
|
||||
void BluetoothConnection::reset_connection_(esp_err_t reason) { this->proxy_->reset_connection_slot_(this, reason); }
|
||||
|
||||
void BluetoothConnection::send_service_for_discovery_() {
|
||||
if (this->send_service_ >= this->service_count_) {
|
||||
this->send_service_ = DONE_SENDING_SERVICES;
|
||||
this->proxy_->send_gatt_services_done(this->address_);
|
||||
this->release_services();
|
||||
return;
|
||||
}
|
||||
|
||||
// Early return if no API connection
|
||||
auto *api_conn = this->proxy_->get_api_connection();
|
||||
if (api_conn == nullptr) {
|
||||
this->send_service_ = DONE_SENDING_SERVICES;
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if client supports efficient UUIDs
|
||||
bool use_efficient_uuids = this->proxy_->client_supports_efficient_uuids();
|
||||
|
||||
// Prepare response
|
||||
api::BluetoothGATTGetServicesResponse resp;
|
||||
resp.address = this->address_;
|
||||
|
||||
// Dynamic batching based on actual size
|
||||
// Keep running total of actual message size
|
||||
size_t current_size = resp.calculate_size();
|
||||
int16_t batch_start = this->send_service_;
|
||||
|
||||
while (this->send_service_ < this->service_count_) {
|
||||
esp_gattc_service_elem_t service_result;
|
||||
uint16_t service_count = 1;
|
||||
esp_gatt_status_t service_status = esp_ble_gattc_get_service(this->gattc_if_, this->conn_id_, nullptr,
|
||||
&service_result, &service_count, this->send_service_);
|
||||
|
||||
if (service_status != ESP_GATT_OK || service_count == 0) {
|
||||
ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_service %s, status=%d, service_count=%d, offset=%d",
|
||||
this->connection_index_, this->address_str(), service_status != ESP_GATT_OK ? "error" : "missing",
|
||||
service_status, service_count, this->send_service_);
|
||||
this->send_service_ = DONE_SENDING_SERVICES;
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the number of characteristics BEFORE adding to response
|
||||
uint16_t total_char_count = 0;
|
||||
esp_gatt_status_t char_count_status =
|
||||
esp_ble_gattc_get_attr_count(this->gattc_if_, this->conn_id_, ESP_GATT_DB_CHARACTERISTIC,
|
||||
service_result.start_handle, service_result.end_handle, 0, &total_char_count);
|
||||
|
||||
if (char_count_status != ESP_GATT_OK) {
|
||||
this->log_connection_error_("esp_ble_gattc_get_attr_count", char_count_status);
|
||||
this->send_service_ = DONE_SENDING_SERVICES;
|
||||
return;
|
||||
}
|
||||
|
||||
// If this service likely won't fit, send current batch (unless it's the first)
|
||||
size_t estimated_size = estimate_service_size(total_char_count, use_efficient_uuids);
|
||||
if (!resp.services.empty() && (current_size + estimated_size > MAX_PACKET_SIZE)) {
|
||||
// This service likely won't fit, send current batch
|
||||
break;
|
||||
}
|
||||
|
||||
// Now add the service since we know it will likely fit
|
||||
resp.services.emplace_back();
|
||||
auto &service_resp = resp.services.back();
|
||||
|
||||
fill_gatt_uuid(service_resp.uuid, service_resp.short_uuid, ESPBTUUID::from_uuid(service_result.uuid),
|
||||
use_efficient_uuids);
|
||||
|
||||
service_resp.handle = service_result.start_handle;
|
||||
|
||||
if (total_char_count > 0) {
|
||||
// Initialize FixedVector with exact count and process characteristics
|
||||
service_resp.characteristics.init(total_char_count);
|
||||
uint16_t char_offset = 0;
|
||||
esp_gattc_char_elem_t char_result;
|
||||
// Bound by total_char_count: the vector is sized for it, and a malicious peripheral
|
||||
// can make enumeration return more entries than the count query reported
|
||||
while (char_offset < total_char_count) { // characteristics
|
||||
uint16_t char_count = 1;
|
||||
esp_gatt_status_t char_status =
|
||||
esp_ble_gattc_get_all_char(this->gattc_if_, this->conn_id_, service_result.start_handle,
|
||||
service_result.end_handle, &char_result, &char_count, char_offset);
|
||||
if (char_status == ESP_GATT_INVALID_OFFSET || char_status == ESP_GATT_NOT_FOUND) {
|
||||
break;
|
||||
}
|
||||
if (char_status != ESP_GATT_OK) {
|
||||
this->log_connection_error_("esp_ble_gattc_get_all_char", char_status);
|
||||
this->send_service_ = DONE_SENDING_SERVICES;
|
||||
return;
|
||||
}
|
||||
if (char_count == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
service_resp.characteristics.emplace_back();
|
||||
auto &characteristic_resp = service_resp.characteristics.back();
|
||||
fill_gatt_uuid(characteristic_resp.uuid, characteristic_resp.short_uuid, ESPBTUUID::from_uuid(char_result.uuid),
|
||||
use_efficient_uuids);
|
||||
characteristic_resp.handle = char_result.char_handle;
|
||||
characteristic_resp.properties = char_result.properties;
|
||||
char_offset++;
|
||||
|
||||
// Get the number of descriptors directly with one call
|
||||
uint16_t total_desc_count = 0;
|
||||
esp_gatt_status_t desc_count_status = esp_ble_gattc_get_attr_count(
|
||||
this->gattc_if_, this->conn_id_, ESP_GATT_DB_DESCRIPTOR, 0, 0, char_result.char_handle, &total_desc_count);
|
||||
|
||||
if (desc_count_status != ESP_GATT_OK) {
|
||||
this->log_connection_error_("esp_ble_gattc_get_attr_count", desc_count_status);
|
||||
this->send_service_ = DONE_SENDING_SERVICES;
|
||||
return;
|
||||
}
|
||||
if (total_desc_count == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Initialize FixedVector with exact count and process descriptors
|
||||
characteristic_resp.descriptors.init(total_desc_count);
|
||||
uint16_t desc_offset = 0;
|
||||
esp_gattc_descr_elem_t desc_result;
|
||||
while (desc_offset < total_desc_count) { // descriptors
|
||||
uint16_t desc_count = 1;
|
||||
esp_gatt_status_t desc_status = esp_ble_gattc_get_all_descr(
|
||||
this->gattc_if_, this->conn_id_, char_result.char_handle, &desc_result, &desc_count, desc_offset);
|
||||
if (desc_status == ESP_GATT_INVALID_OFFSET || desc_status == ESP_GATT_NOT_FOUND) {
|
||||
break;
|
||||
}
|
||||
if (desc_status != ESP_GATT_OK) {
|
||||
this->log_connection_error_("esp_ble_gattc_get_all_descr", desc_status);
|
||||
this->send_service_ = DONE_SENDING_SERVICES;
|
||||
return;
|
||||
}
|
||||
if (desc_count == 0) {
|
||||
break; // No more descriptors
|
||||
}
|
||||
|
||||
characteristic_resp.descriptors.emplace_back();
|
||||
auto &descriptor_resp = characteristic_resp.descriptors.back();
|
||||
fill_gatt_uuid(descriptor_resp.uuid, descriptor_resp.short_uuid, ESPBTUUID::from_uuid(desc_result.uuid),
|
||||
use_efficient_uuids);
|
||||
descriptor_resp.handle = desc_result.handle;
|
||||
desc_offset++;
|
||||
}
|
||||
}
|
||||
} // end if (total_char_count > 0)
|
||||
|
||||
if (close_service_batch(resp, current_size, this->send_service_, this->connection_index_, this->address_str()) !=
|
||||
BatchClose::CONTINUE) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Send the message with dynamically batched services; on a failed send,
|
||||
// rewind the cursor so the batch is retried instead of silently skipped.
|
||||
if (!api_conn->send_message(resp)) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] Failed to send service batch, retrying", this->connection_index_, this->address_str_);
|
||||
this->send_service_ = batch_start;
|
||||
}
|
||||
}
|
||||
|
||||
void BluetoothConnection::log_connection_error_(const char *operation, esp_gatt_status_t status) {
|
||||
ESP_LOGE(TAG, "[%d] [%s] %s error, status=%d", this->connection_index_, this->address_str(), operation, status);
|
||||
}
|
||||
|
||||
void BluetoothConnection::log_connection_warning_(const char *operation, esp_err_t err) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] %s failed, err=%d", this->connection_index_, this->address_str(), operation, err);
|
||||
}
|
||||
|
||||
void BluetoothConnection::log_gatt_not_connected_(const char *action, const char *type) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] Cannot %s GATT %s, not connected.", this->connection_index_, this->address_str(), action,
|
||||
type);
|
||||
}
|
||||
|
||||
void BluetoothConnection::log_gatt_operation_error_(const char *operation, uint16_t handle, esp_gatt_status_t status) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] Error %s for handle 0x%2X, status=%d", this->connection_index_, this->address_str(),
|
||||
operation, handle, status);
|
||||
}
|
||||
|
||||
esp_err_t BluetoothConnection::check_and_log_error_(const char *operation, esp_err_t err) {
|
||||
if (err != ESP_OK) {
|
||||
this->log_connection_warning_(operation, err);
|
||||
return err;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) {
|
||||
if (!BLEClientBase::gattc_event_handler(event, gattc_if, param))
|
||||
return false;
|
||||
|
||||
switch (event) {
|
||||
case ESP_GATTC_DISCONNECT_EVT: {
|
||||
// Don't reset connection yet - wait for CLOSE_EVT to ensure controller has freed resources
|
||||
// This prevents race condition where we mark slot as free before controller cleanup is complete
|
||||
ESP_LOGD(TAG, "[%d] [%s] Disconnect, reason=0x%02x", this->connection_index_, this->address_str_,
|
||||
param->disconnect.reason);
|
||||
// Send disconnection notification but don't free the slot yet
|
||||
this->proxy_->send_device_connection(this->address_, false, 0, param->disconnect.reason);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_OPEN_EVT: {
|
||||
if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) {
|
||||
this->reset_connection_(param->open.status);
|
||||
} else if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) {
|
||||
this->proxy_->send_device_connection(this->address_, true, this->mtu_);
|
||||
this->proxy_->send_connections_free();
|
||||
}
|
||||
this->seen_mtu_or_services_ = false;
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_CFG_MTU_EVT:
|
||||
case ESP_GATTC_SEARCH_CMPL_EVT: {
|
||||
if (!this->seen_mtu_or_services_) {
|
||||
// We don't know if we will get the MTU or the services first, so
|
||||
// only send the device connection true if we have already received
|
||||
// the services.
|
||||
this->seen_mtu_or_services_ = true;
|
||||
break;
|
||||
}
|
||||
this->proxy_->send_device_connection(this->address_, true, this->mtu_);
|
||||
this->proxy_->send_connections_free();
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_READ_DESCR_EVT:
|
||||
case ESP_GATTC_READ_CHAR_EVT: {
|
||||
if (param->read.status != ESP_GATT_OK) {
|
||||
this->log_gatt_operation_error_("reading char/descriptor", param->read.handle, param->read.status);
|
||||
this->proxy_->send_gatt_error(this->address_, param->read.handle, param->read.status);
|
||||
break;
|
||||
}
|
||||
auto *api_connection = this->proxy_->get_api_connection();
|
||||
if (api_connection == nullptr)
|
||||
break;
|
||||
api::BluetoothGATTReadResponse resp;
|
||||
resp.address = this->address_;
|
||||
resp.handle = param->read.handle;
|
||||
resp.set_data(param->read.value, param->read.value_len);
|
||||
api_connection->send_message(resp);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_WRITE_CHAR_EVT:
|
||||
case ESP_GATTC_WRITE_DESCR_EVT: {
|
||||
if (param->write.status != ESP_GATT_OK) {
|
||||
this->log_gatt_operation_error_("writing char/descriptor", param->write.handle, param->write.status);
|
||||
this->proxy_->send_gatt_error(this->address_, param->write.handle, param->write.status);
|
||||
break;
|
||||
}
|
||||
auto *api_connection = this->proxy_->get_api_connection();
|
||||
if (api_connection == nullptr)
|
||||
break;
|
||||
api::BluetoothGATTWriteResponse resp;
|
||||
resp.address = this->address_;
|
||||
resp.handle = param->write.handle;
|
||||
api_connection->send_message(resp);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_UNREG_FOR_NOTIFY_EVT: {
|
||||
if (param->unreg_for_notify.status != ESP_GATT_OK) {
|
||||
this->log_gatt_operation_error_("unregistering notifications", param->unreg_for_notify.handle,
|
||||
param->unreg_for_notify.status);
|
||||
this->proxy_->send_gatt_error(this->address_, param->unreg_for_notify.handle, param->unreg_for_notify.status);
|
||||
break;
|
||||
}
|
||||
auto *api_connection = this->proxy_->get_api_connection();
|
||||
if (api_connection == nullptr)
|
||||
break;
|
||||
api::BluetoothGATTNotifyResponse resp;
|
||||
resp.address = this->address_;
|
||||
resp.handle = param->unreg_for_notify.handle;
|
||||
api_connection->send_message(resp);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
|
||||
if (param->reg_for_notify.status != ESP_GATT_OK) {
|
||||
this->log_gatt_operation_error_("registering notifications", param->reg_for_notify.handle,
|
||||
param->reg_for_notify.status);
|
||||
this->proxy_->send_gatt_error(this->address_, param->reg_for_notify.handle, param->reg_for_notify.status);
|
||||
break;
|
||||
}
|
||||
auto *api_connection = this->proxy_->get_api_connection();
|
||||
if (api_connection == nullptr)
|
||||
break;
|
||||
api::BluetoothGATTNotifyResponse resp;
|
||||
resp.address = this->address_;
|
||||
resp.handle = param->reg_for_notify.handle;
|
||||
api_connection->send_message(resp);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_NOTIFY_EVT: {
|
||||
ESP_LOGV(TAG, "[%d] [%s] ESP_GATTC_NOTIFY_EVT: handle=0x%2X", this->connection_index_, this->address_str_,
|
||||
param->notify.handle);
|
||||
auto *api_connection = this->proxy_->get_api_connection();
|
||||
if (api_connection == nullptr)
|
||||
break;
|
||||
api::BluetoothGATTNotifyDataResponse resp;
|
||||
resp.address = this->address_;
|
||||
resp.handle = param->notify.handle;
|
||||
resp.set_data(param->notify.value, param->notify.value_len);
|
||||
api_connection->send_message(resp);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void BluetoothConnection::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
|
||||
BLEClientBase::gap_event_handler(event, param);
|
||||
|
||||
switch (event) {
|
||||
case ESP_GAP_BLE_AUTH_CMPL_EVT:
|
||||
if (memcmp(param->ble_security.auth_cmpl.bd_addr, this->remote_bda_, 6) != 0)
|
||||
break;
|
||||
if (param->ble_security.auth_cmpl.success) {
|
||||
this->proxy_->send_device_pairing(this->address_, true);
|
||||
} else {
|
||||
this->proxy_->send_device_pairing(this->address_, false, param->ble_security.auth_cmpl.fail_reason);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t BluetoothConnection::read_characteristic(uint16_t handle) {
|
||||
if (!this->connected()) {
|
||||
this->log_gatt_not_connected_("read", "characteristic");
|
||||
return GATT_NOT_CONNECTED;
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, "[%d] [%s] Reading GATT characteristic handle %d", this->connection_index_, this->address_str_, handle);
|
||||
|
||||
esp_err_t err = esp_ble_gattc_read_char(this->gattc_if_, this->conn_id_, handle, ESP_GATT_AUTH_REQ_NONE);
|
||||
return this->check_and_log_error_("esp_ble_gattc_read_char", err);
|
||||
}
|
||||
|
||||
esp_err_t BluetoothConnection::write_characteristic(uint16_t handle, const uint8_t *data, size_t length,
|
||||
bool response) {
|
||||
if (!this->connected()) {
|
||||
this->log_gatt_not_connected_("write", "characteristic");
|
||||
return GATT_NOT_CONNECTED;
|
||||
}
|
||||
ESP_LOGV(TAG, "[%d] [%s] Writing GATT characteristic handle %d", this->connection_index_, this->address_str_, handle);
|
||||
|
||||
// ESP-IDF's API requires a non-const uint8_t* but it doesn't modify the data
|
||||
// The BTC layer immediately copies the data to its own buffer (see btc_gattc.c)
|
||||
// const_cast is safe here and was previously hidden by a C-style cast
|
||||
esp_err_t err =
|
||||
esp_ble_gattc_write_char(this->gattc_if_, this->conn_id_, handle, length, const_cast<uint8_t *>(data),
|
||||
response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
return this->check_and_log_error_("esp_ble_gattc_write_char", err);
|
||||
}
|
||||
|
||||
esp_err_t BluetoothConnection::read_descriptor(uint16_t handle) {
|
||||
if (!this->connected()) {
|
||||
this->log_gatt_not_connected_("read", "descriptor");
|
||||
return GATT_NOT_CONNECTED;
|
||||
}
|
||||
ESP_LOGV(TAG, "[%d] [%s] Reading GATT descriptor handle %d", this->connection_index_, this->address_str_, handle);
|
||||
|
||||
esp_err_t err = esp_ble_gattc_read_char_descr(this->gattc_if_, this->conn_id_, handle, ESP_GATT_AUTH_REQ_NONE);
|
||||
return this->check_and_log_error_("esp_ble_gattc_read_char_descr", err);
|
||||
}
|
||||
|
||||
esp_err_t BluetoothConnection::write_descriptor(uint16_t handle, const uint8_t *data, size_t length, bool response) {
|
||||
if (!this->connected()) {
|
||||
this->log_gatt_not_connected_("write", "descriptor");
|
||||
return GATT_NOT_CONNECTED;
|
||||
}
|
||||
ESP_LOGV(TAG, "[%d] [%s] Writing GATT descriptor handle %d", this->connection_index_, this->address_str_, handle);
|
||||
|
||||
// ESP-IDF's API requires a non-const uint8_t* but it doesn't modify the data
|
||||
// The BTC layer immediately copies the data to its own buffer (see btc_gattc.c)
|
||||
// const_cast is safe here and was previously hidden by a C-style cast
|
||||
esp_err_t err = esp_ble_gattc_write_char_descr(
|
||||
this->gattc_if_, this->conn_id_, handle, length, const_cast<uint8_t *>(data),
|
||||
response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
return this->check_and_log_error_("esp_ble_gattc_write_char_descr", err);
|
||||
}
|
||||
|
||||
esp_err_t BluetoothConnection::notify_characteristic(uint16_t handle, bool enable) {
|
||||
if (!this->connected()) {
|
||||
this->log_gatt_not_connected_("notify", "characteristic");
|
||||
return GATT_NOT_CONNECTED;
|
||||
}
|
||||
|
||||
if (enable) {
|
||||
ESP_LOGV(TAG, "[%d] [%s] Registering for GATT characteristic notifications handle %d", this->connection_index_,
|
||||
this->address_str_, handle);
|
||||
esp_err_t err = esp_ble_gattc_register_for_notify(this->gattc_if_, this->remote_bda_, handle);
|
||||
return this->check_and_log_error_("esp_ble_gattc_register_for_notify", err);
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, "[%d] [%s] Unregistering for GATT characteristic notifications handle %d", this->connection_index_,
|
||||
this->address_str_, handle);
|
||||
esp_err_t err = esp_ble_gattc_unregister_for_notify(this->gattc_if_, this->remote_bda_, handle);
|
||||
return this->check_and_log_error_("esp_ble_gattc_unregister_for_notify", err);
|
||||
esp_bd_addr_t bda;
|
||||
address_to_bda(address, bda);
|
||||
esp_ble_gattc_cache_clean(bda);
|
||||
return CONN_OK;
|
||||
}
|
||||
|
||||
} // namespace esphome::bluetooth_connection
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "esphome/components/esp32_ble_client/ble_client_base.h"
|
||||
|
||||
#include "bluetooth_connection.h"
|
||||
|
||||
namespace esphome::bluetooth_proxy {
|
||||
class BluetoothProxy;
|
||||
} // namespace esphome::bluetooth_proxy
|
||||
|
||||
namespace esphome::bluetooth_connection {
|
||||
|
||||
class BluetoothConnection final : public esp32_ble_client::BLEClientBase {
|
||||
public:
|
||||
void dump_config() override;
|
||||
void loop() override;
|
||||
bool gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) override;
|
||||
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override;
|
||||
// The proxy's connections never consume parsed ESPBTDevice objects.
|
||||
bool wants_parsed_advertisements() override { return false; }
|
||||
|
||||
esp_err_t read_characteristic(uint16_t handle);
|
||||
esp_err_t write_characteristic(uint16_t handle, const uint8_t *data, size_t length, bool response);
|
||||
esp_err_t read_descriptor(uint16_t handle);
|
||||
esp_err_t write_descriptor(uint16_t handle, const uint8_t *data, size_t length, bool response);
|
||||
|
||||
esp_err_t notify_characteristic(uint16_t handle, bool enable);
|
||||
|
||||
esp_err_t update_connection_params(uint16_t min_interval, uint16_t max_interval, uint16_t latency, uint16_t timeout) {
|
||||
return this->update_conn_params_(min_interval, max_interval, latency, timeout, "custom");
|
||||
}
|
||||
|
||||
bool has_gatt_services() const { return this->service_count_ != 0; }
|
||||
|
||||
/// Start connecting: record the API address type and hand the client to the
|
||||
/// tracker's promote loop (it pauses the scan and opens the connection).
|
||||
void initiate_connection(uint8_t address_type) {
|
||||
this->set_remote_addr_type(static_cast<esp_ble_addr_type_t>(address_type));
|
||||
this->set_state(esp32_ble_tracker::ClientState::DISCOVERED);
|
||||
}
|
||||
|
||||
void set_address(uint64_t address) override;
|
||||
|
||||
protected:
|
||||
friend class bluetooth_proxy::BluetoothProxy;
|
||||
|
||||
void on_disconnect_complete(esp_err_t reason) override;
|
||||
|
||||
void send_service_for_discovery_();
|
||||
void reset_connection_(esp_err_t reason);
|
||||
void log_connection_error_(const char *operation, esp_gatt_status_t status);
|
||||
void log_connection_warning_(const char *operation, esp_err_t err);
|
||||
void log_gatt_not_connected_(const char *action, const char *type);
|
||||
void log_gatt_operation_error_(const char *operation, uint16_t handle, esp_gatt_status_t status);
|
||||
esp_err_t check_and_log_error_(const char *operation, esp_err_t err);
|
||||
|
||||
// Memory optimized layout for 32-bit systems
|
||||
// Group 1: Pointers (4 bytes each, naturally aligned)
|
||||
bluetooth_proxy::BluetoothProxy *proxy_;
|
||||
|
||||
// Group 2: 2-byte types
|
||||
int16_t send_service_{INIT_SENDING_SERVICES}; // see bluetooth_connection.h cursor states
|
||||
|
||||
// Group 3: 1-byte types
|
||||
bool seen_mtu_or_services_{false};
|
||||
// 1 byte used, 1 byte padding
|
||||
};
|
||||
|
||||
} // namespace esphome::bluetooth_connection
|
||||
|
||||
#endif // USE_ESP32
|
||||
@@ -1,7 +1,7 @@
|
||||
// Hub-platform connection wrapper (USE_RP2 hub builds today).
|
||||
#include "bluetooth_connection_hub.h"
|
||||
|
||||
#if !defined(USE_ESP32) && defined(USE_BLE_GATT_CLIENT)
|
||||
#ifdef USE_BLE_GATT_CLIENT
|
||||
|
||||
#include "esphome/components/api/api_pb2.h"
|
||||
#include "esphome/components/bluetooth_proxy/bluetooth_proxy.h"
|
||||
@@ -433,4 +433,4 @@ void BluetoothConnection::send_service_for_discovery_() {
|
||||
|
||||
} // namespace esphome::bluetooth_connection
|
||||
|
||||
#endif // !USE_ESP32 && USE_BLE_GATT_CLIENT
|
||||
#endif // USE_BLE_GATT_CLIENT
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
#if !defined(USE_ESP32) && defined(USE_BLE_GATT_CLIENT)
|
||||
#ifdef USE_BLE_GATT_CLIENT
|
||||
|
||||
#include "bluetooth_connection.h"
|
||||
|
||||
@@ -52,10 +52,11 @@ class BluetoothConnection final {
|
||||
bool is_paired() const { return this->paired_; }
|
||||
void set_unpaired() { this->paired_ = false; }
|
||||
conn_err_t pair() { return this->backend_->pair(); }
|
||||
// A backend disconnect() is a single call that also cancels an in-progress
|
||||
// connect; there is no deferred-disconnect state to track.
|
||||
bool disconnect_pending() const { return false; }
|
||||
void cancel_pending_disconnect() {}
|
||||
// Backends with deferred-disconnect state (bluedroid) answer through the
|
||||
// detected forwards; the rest have nothing to track. Templates so the
|
||||
// discarded branch is not odr-checked against backends without the methods.
|
||||
bool disconnect_pending() const { return disconnect_pending_(this->backend_); }
|
||||
void cancel_pending_disconnect() { cancel_pending_(this->backend_); }
|
||||
|
||||
void set_address(uint64_t address);
|
||||
uint64_t get_address() const { return this->address_; }
|
||||
@@ -65,7 +66,12 @@ class BluetoothConnection final {
|
||||
ClientState state() const { return this->state_; }
|
||||
void set_state(ClientState st) { this->state_ = st; }
|
||||
bool connected() const { return this->state_ == ClientState::ESTABLISHED; }
|
||||
void set_connection_type(ConnectionType ct) { this->connection_type_ = ct; }
|
||||
void set_connection_type(ConnectionType ct) {
|
||||
this->connection_type_ = ct;
|
||||
// The bluedroid backend branches on the type itself (prefer-params and
|
||||
// the with-cache report at OPEN_EVT).
|
||||
forward_connection_type_(this->backend_, ct);
|
||||
}
|
||||
// Latched at discovery completion rather than read from the backend table:
|
||||
// streaming frees the table, and this must stay true for the connection's
|
||||
// lifetime (esp32 parity — a repeat GetServices is silently ignored there,
|
||||
@@ -102,6 +108,23 @@ class BluetoothConnection final {
|
||||
// response in place from its stack cache; the rest use the table streamer.
|
||||
// Template so the discarded branch is not odr-checked against backends
|
||||
// that lack the method.
|
||||
template<typename Backend> static bool disconnect_pending_(Backend *backend) {
|
||||
if constexpr (requires { backend->disconnect_pending(); }) {
|
||||
return backend->disconnect_pending();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
template<typename Backend> static void cancel_pending_(Backend *backend) {
|
||||
if constexpr (requires { backend->cancel_pending_disconnect(); }) {
|
||||
backend->cancel_pending_disconnect();
|
||||
}
|
||||
}
|
||||
template<typename Backend> static void forward_connection_type_(Backend *backend, ConnectionType ct) {
|
||||
if constexpr (requires { backend->set_connection_type(ct); }) {
|
||||
backend->set_connection_type(ct);
|
||||
}
|
||||
}
|
||||
template<typename Backend> void stream_pending_(Backend *backend) {
|
||||
if constexpr (requires { backend->stream_service_batch(*this); }) {
|
||||
backend->stream_service_batch(*this);
|
||||
@@ -146,4 +169,4 @@ static_assert(ble_device_base::GattClientEventSinkContract<BluetoothConnection>,
|
||||
|
||||
} // namespace esphome::bluetooth_connection
|
||||
|
||||
#endif // !USE_ESP32 && USE_BLE_GATT_CLIENT
|
||||
#endif // USE_BLE_GATT_CLIENT
|
||||
|
||||
@@ -27,7 +27,7 @@ def AUTO_LOAD(config: ConfigType | None = None) -> list[str]:
|
||||
target platform set, so it takes one of the concrete branches.
|
||||
"""
|
||||
if CORE.is_esp32:
|
||||
return ["bluetooth_connection", "esp32_ble_client", "esp32_ble_tracker"]
|
||||
return ["bluetooth_connection", "esp32_ble_tracker"]
|
||||
if CORE.target_platform in _HUB_PLATFORMS:
|
||||
return ["ble_device_base", "bluetooth_connection"]
|
||||
# No target platform, or one this component does not support: tooling
|
||||
@@ -36,7 +36,6 @@ def AUTO_LOAD(config: ConfigType | None = None) -> list[str]:
|
||||
return [
|
||||
"ble_device_base",
|
||||
"bluetooth_connection",
|
||||
"esp32_ble_client",
|
||||
"esp32_ble_tracker",
|
||||
]
|
||||
|
||||
@@ -86,10 +85,12 @@ def _esp32_config_schema() -> cv.All:
|
||||
f"update _IDF_MAX_CONNECTIONS in bluetooth_proxy/__init__.py"
|
||||
)
|
||||
|
||||
BluetoothConnection = bluetooth_connection.esp32_connection_class()
|
||||
CONNECTION_SCHEMA = esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BluetoothConnection),
|
||||
cv.GenerateID(): cv.declare_id(bluetooth_connection.HubBluetoothConnection),
|
||||
cv.GenerateID(CONF_BACKEND_ID): cv.declare_id(
|
||||
bluetooth_connection.BluedroidGattClient
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
@@ -386,10 +387,16 @@ async def _to_code_esp32(config: ConfigType) -> None:
|
||||
cg.add_define("BLUETOOTH_PROXY_MAX_CONNECTIONS", connection_count)
|
||||
|
||||
for connection_conf in config.get(CONF_CONNECTIONS, []):
|
||||
connection_var = cg.new_Pvariable(connection_conf[CONF_ID])
|
||||
await cg.register_component(connection_var, connection_conf)
|
||||
cg.add(var.register_connection(connection_var))
|
||||
await esp32_ble_tracker.register_raw_client(connection_var, connection_conf)
|
||||
ble_device_base.request_gatt_client()
|
||||
backend = cg.new_Pvariable(connection_conf[CONF_BACKEND_ID])
|
||||
await cg.register_component(backend, connection_conf)
|
||||
# The tracker promote loop drives connect timing through the shim.
|
||||
await esp32_ble_tracker.register_raw_client(
|
||||
backend.tracker_client(), connection_conf
|
||||
)
|
||||
connection = cg.new_Pvariable(connection_conf[CONF_ID])
|
||||
cg.add(connection.set_backend(backend))
|
||||
cg.add(var.register_connection(connection))
|
||||
|
||||
if config.get(CONF_CACHE_SERVICES):
|
||||
add_idf_sdkconfig_option("CONFIG_BT_GATTC_CACHE_NVS_FLASH", True)
|
||||
|
||||
@@ -132,13 +132,6 @@ void BluetoothProxy::log_advertisement_flush_() {
|
||||
}
|
||||
|
||||
void BluetoothProxy::dump_config() {
|
||||
#ifdef USE_ESP32
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"Bluetooth Proxy:\n"
|
||||
" Active: %s\n"
|
||||
" Connections: %d",
|
||||
YESNO(this->active_), this->connection_count_);
|
||||
#else
|
||||
// Print configured facts. dump_config runs right after setup, before the
|
||||
// radio is up, so live scan state would always read "stopped" here — the
|
||||
// loop's BluetoothScannerStateResponse carries the changing value instead.
|
||||
@@ -162,32 +155,8 @@ void BluetoothProxy::dump_config() {
|
||||
" Adapter MAC: %s",
|
||||
scan_mode, mac_out);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
void BluetoothProxy::loop() {
|
||||
// Run advertisement flush / connection cleanup every 100ms
|
||||
uint32_t now = App.get_loop_component_start_time();
|
||||
if (now - this->last_advertisement_flush_time_ < 100)
|
||||
return;
|
||||
this->last_advertisement_flush_time_ = now;
|
||||
|
||||
if (api::global_api_server->is_connected() && this->api_connection_ != nullptr) {
|
||||
this->flush_pending_advertisements_();
|
||||
return;
|
||||
}
|
||||
for (uint8_t i = 0; i < this->connection_count_; i++) {
|
||||
auto *connection = this->connections_[i];
|
||||
if (connection->get_address() != 0 && !connection->disconnect_pending()) {
|
||||
connection->disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // USE_ESP32
|
||||
|
||||
#ifdef BLUETOOTH_CONNECTION_HAS_GATT
|
||||
|
||||
// maybe_unused: in a passive proxy (active: false) MAX is 0, the body is removed, and connection is unused.
|
||||
@@ -200,11 +169,8 @@ void BluetoothProxy::register_connection([[maybe_unused]] BluetoothConnection *c
|
||||
ESP_LOGE(TAG, "Connection registry full, dropping registration");
|
||||
return;
|
||||
}
|
||||
#ifndef USE_ESP32
|
||||
// esp32 assigns connection_index_ in BLEClientBase::setup(); the hub
|
||||
// class has no Component lifecycle, so the index is assigned here.
|
||||
// The hub wrapper has no Component lifecycle, so the index is assigned here.
|
||||
connection->connection_index_ = this->connection_count_;
|
||||
#endif
|
||||
this->connections_[this->connection_count_++] = connection;
|
||||
connection->proxy_ = this;
|
||||
#endif
|
||||
@@ -486,6 +452,29 @@ void BluetoothProxy::bluetooth_scanner_set_mode(bool active) {
|
||||
|
||||
#else // !USE_ESP32
|
||||
|
||||
void BluetoothProxy::bluetooth_scanner_set_mode(bool active) {
|
||||
if (this->hub_->scan_active() != active) {
|
||||
ESP_LOGD(TAG, "Setting scanner mode to %s", active ? "active" : "passive");
|
||||
if (!this->hub_->request_scan_mode(active)) {
|
||||
// Passive-only controller asked for active scanning; the state report
|
||||
// below carries the real, unchanged mode so the subscriber does not
|
||||
// assume the change happened.
|
||||
ESP_LOGW(TAG, "Scanner mode %s not supported by this tracker", active ? "active" : "passive");
|
||||
}
|
||||
}
|
||||
#ifndef USE_BLE_SCANNER_STATE_CALLBACK
|
||||
if (this->api_connection_ != nullptr) {
|
||||
// Reports the mode change; the sender also refreshes last_scan_running_, so
|
||||
// a failed restart (scan_running_ dropped by the tracker) is not reported
|
||||
// again by loop() on the next tick. A push hub reports the restart's
|
||||
// transitions (mode rides along) instead.
|
||||
this->send_polled_scanner_state_();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif // USE_ESP32
|
||||
|
||||
void BluetoothProxy::loop() {
|
||||
#ifdef BLUETOOTH_CONNECTION_HAS_GATT
|
||||
// Stream pending service-discovery batches every iteration (esp32 parity:
|
||||
@@ -508,7 +497,7 @@ void BluetoothProxy::loop() {
|
||||
// (disconnect() on an already-disconnecting backend is a no-op).
|
||||
for (uint8_t i = 0; i < this->connection_count_; i++) {
|
||||
auto *connection = this->connections_[i];
|
||||
if (connection->get_address() != 0) {
|
||||
if (connection->get_address() != 0 && !connection->disconnect_pending()) {
|
||||
connection->disconnect();
|
||||
}
|
||||
}
|
||||
@@ -595,29 +584,6 @@ void BluetoothProxy::bluetooth_set_connection_params(const api::BluetoothSetConn
|
||||
|
||||
#endif // !BLUETOOTH_CONNECTION_HAS_GATT
|
||||
|
||||
void BluetoothProxy::bluetooth_scanner_set_mode(bool active) {
|
||||
if (this->hub_->scan_active() != active) {
|
||||
ESP_LOGD(TAG, "Setting scanner mode to %s", active ? "active" : "passive");
|
||||
if (!this->hub_->request_scan_mode(active)) {
|
||||
// Passive-only controller asked for active scanning; the state report
|
||||
// below carries the real, unchanged mode so the subscriber does not
|
||||
// assume the change happened.
|
||||
ESP_LOGW(TAG, "Scanner mode %s not supported by this tracker", active ? "active" : "passive");
|
||||
}
|
||||
}
|
||||
#ifndef USE_BLE_SCANNER_STATE_CALLBACK
|
||||
if (this->api_connection_ != nullptr) {
|
||||
// Reports the mode change; the sender also refreshes last_scan_running_, so
|
||||
// a failed restart (scan_running_ dropped by the tracker) is not reported
|
||||
// again by loop() on the next tick. A push hub reports the restart's
|
||||
// transitions (mode rides along) instead.
|
||||
this->send_polled_scanner_state_();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif // USE_ESP32
|
||||
|
||||
void BluetoothProxy::subscribe_api_connection(api::APIConnection *api_connection, uint32_t flags) {
|
||||
if (this->api_connection_ != nullptr && this->api_connection_ != api_connection) {
|
||||
// A previous subscriber still holds the slot. This is almost always a stale
|
||||
|
||||
@@ -17,9 +17,7 @@
|
||||
|
||||
#include "esphome/components/ble_device_base/ble_hub_impl.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#include "esphome/components/bluetooth_connection/bluetooth_connection_esp32.h"
|
||||
#elif defined(USE_BLE_GATT_CLIENT)
|
||||
#ifdef USE_BLE_GATT_CLIENT
|
||||
#include "esphome/components/bluetooth_connection/bluetooth_connection_hub.h"
|
||||
#endif
|
||||
|
||||
|
||||
@@ -306,6 +306,8 @@
|
||||
#define USE_ESP32_BLE_SERVER_ON_CONNECT
|
||||
#define USE_ESP32_BLE_SERVER_ON_DISCONNECT
|
||||
#define USE_ESP32_BLE_TRACKER
|
||||
#define USE_BLE_GATT_CLIENT
|
||||
#define ESPHOME_BLE_GATT_CLIENT_COUNT 1
|
||||
#define ESPHOME_ESP32_BLE_TRACKER_LISTENER_COUNT 1
|
||||
#define ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT 1
|
||||
#define ESPHOME_BLE_DEVICE_BASE_LISTENER_COUNT 1
|
||||
|
||||
@@ -109,6 +109,8 @@ def test_esp32_bluetooth_proxy_requests_client_slots_only(
|
||||
generate_main(component_config_path("esp32_bluetooth_proxy.yaml"))
|
||||
assert get_define_value("ESPHOME_ESP32_BLE_TRACKER_LISTENER_COUNT") is None
|
||||
assert get_define_value("ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT") == "3"
|
||||
# One neutral GATT backend slot per connection (the hub-model flip).
|
||||
assert get_define_value("ESPHOME_BLE_GATT_CLIENT_COUNT") == "3"
|
||||
|
||||
|
||||
def test_counts_reset_between_compiles(
|
||||
|
||||
@@ -178,17 +178,11 @@ def test_rp2_rejects_esp32_only_keys_by_name(
|
||||
|
||||
|
||||
def test_bluetooth_connection_auto_load_covers_its_includes() -> None:
|
||||
# The esp32 connection header includes esp32_ble_client; the auto load
|
||||
# must satisfy that closure itself (regression: it once relied on the
|
||||
# consumer's auto loads).
|
||||
_set_platform("esp32")
|
||||
assert "esp32_ble_client" in bluetooth_connection.AUTO_LOAD()
|
||||
_set_platform("rp2")
|
||||
assert bluetooth_connection.AUTO_LOAD() == ["ble_device_base"]
|
||||
# No target platform (tooling resolving the manifest): the union, so
|
||||
# dependency closures stay complete for build_codeowners and friends.
|
||||
_set_platform(None)
|
||||
assert bluetooth_connection.AUTO_LOAD() == ["ble_device_base", "esp32_ble_client"]
|
||||
# Every backend builds on ble_device_base alone; the Bluedroid backend
|
||||
# talks to IDF directly, so esp32_ble_client is no longer in the closure.
|
||||
for platform in ("esp32", "rp2", None):
|
||||
_set_platform(platform)
|
||||
assert bluetooth_connection.AUTO_LOAD() == ["ble_device_base"]
|
||||
|
||||
|
||||
def test_every_registered_hub_platform_has_a_schema_arm() -> None:
|
||||
|
||||
Reference in New Issue
Block a user