Move the esp32 proxy advertisement path to the hub raw callback

This commit is contained in:
J. Nick Koston
2026-08-08 00:00:57 -05:00
parent 745dee0734
commit de9074fb5e
5 changed files with 63 additions and 84 deletions
@@ -480,7 +480,9 @@ esp_err_t BluetoothConnection::notify_characteristic(uint16_t handle, bool enabl
}
esp32_ble_tracker::AdvertisementParserType BluetoothConnection::get_advertisement_parser_type() {
return this->proxy_->get_advertisement_parser_type();
// RAW keeps the tracker from building parsed ESPBTDevice objects for the
// proxy's connections (the proxy itself consumes the hub raw callback).
return esp32_ble_tracker::AdvertisementParserType::RAW_ADVERTISEMENTS;
}
} // namespace esphome::bluetooth_connection
@@ -374,7 +374,11 @@ async def _to_code_esp32(config: ConfigType) -> None:
await cg.register_component(var, config)
cg.add(var.set_active(config[CONF_ACTIVE]))
await esp32_ble_tracker.register_raw_ble_device(var, config)
# Advertisements arrive through the hub raw callback (installed in
# setup()); only the scanner-state listener still registers with the
# tracker directly.
tracker = await cg.get_variable(config[esp32_ble_tracker.CONF_ESP32_BLE_ID])
cg.add(var.set_parent(tracker))
await esp32_ble_tracker.register_scanner_state_listener(var, config)
# Define max connections for protobuf fixed array
@@ -32,6 +32,10 @@ void BluetoothProxy::setup() {
// Capture the configured scan mode from YAML before any API changes
this->configured_scan_active_ = this->parent_->get_scan_active();
this->hub_->set_raw_advertisement_callback({this, [](void *self, const ble_device_base::RawAdvertisement &adv) {
static_cast<BluetoothProxy *>(self)->on_raw_advertisement_(adv);
}});
}
void BluetoothProxy::on_scanner_state(esp32_ble_tracker::ScannerState state) {
@@ -68,30 +72,6 @@ void BluetoothProxy::setup() {
}});
}
void BluetoothProxy::on_raw_advertisement_(const ble_device_base::RawAdvertisement &raw) {
if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr)
return;
auto &adv = this->response_.advertisements[this->response_.advertisements_len];
// raw.mac is LSB-first; this yields the same uint64 the esp32 proxy sends.
adv.address = ble_device_base::mac_lsb_first_to_uint64(raw.mac);
adv.rssi = raw.rssi;
adv.address_type = raw.addr_type;
uint8_t length = raw.data_len > sizeof(adv.data) ? sizeof(adv.data) : static_cast<uint8_t>(raw.data_len);
adv.data_len = length;
std::memcpy(adv.data, raw.data, length);
this->response_.advertisements_len++;
ESP_LOGV(TAG, "Queuing raw packet from %02X:%02X:%02X:%02X:%02X:%02X, length %d. RSSI: %d dB", raw.mac[5], raw.mac[4],
raw.mac[3], raw.mac[2], raw.mac[1], raw.mac[0], length, raw.rssi);
// Flush if we have reached BLUETOOTH_PROXY_ADVERTISEMENT_BATCH_SIZE
if (this->response_.advertisements_len >= BLUETOOTH_PROXY_ADVERTISEMENT_BATCH_SIZE) {
this->flush_pending_advertisements_();
}
}
void BluetoothProxy::send_bluetooth_scanner_state_() {
// One read feeds both the frame and the change detector; the detector only
// advances if the frame was accepted, so a dropped send (WOULD_BLOCK on a
@@ -112,6 +92,32 @@ void BluetoothProxy::send_bluetooth_scanner_state_() {
#endif // USE_ESP32
// The hub delivers raw advertisements on the ESPHome main loop; raw.mac is
// least-significant octet first (BLE controller convention).
void BluetoothProxy::on_raw_advertisement_(const ble_device_base::RawAdvertisement &raw) {
if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr)
return;
auto &adv = this->response_.advertisements[this->response_.advertisements_len];
// raw.mac is LSB-first; this matches ble_addr_to_uint64 on esp32.
adv.address = ble_device_base::mac_lsb_first_to_uint64(raw.mac);
adv.rssi = raw.rssi;
adv.address_type = raw.addr_type;
uint8_t length = raw.data_len > sizeof(adv.data) ? sizeof(adv.data) : static_cast<uint8_t>(raw.data_len);
adv.data_len = length;
std::memcpy(adv.data, raw.data, length);
this->response_.advertisements_len++;
ESP_LOGV(TAG, "Queuing raw packet from %02X:%02X:%02X:%02X:%02X:%02X, length %d. RSSI: %d dB", raw.mac[5], raw.mac[4],
raw.mac[3], raw.mac[2], raw.mac[1], raw.mac[0], length, raw.rssi);
// Flush if we have reached BLUETOOTH_PROXY_ADVERTISEMENT_BATCH_SIZE
if (this->response_.advertisements_len >= BLUETOOTH_PROXY_ADVERTISEMENT_BATCH_SIZE) {
this->flush_pending_advertisements_();
}
}
#ifdef BLUETOOTH_CONNECTION_HAS_GATT
void BluetoothProxy::log_connection_request_ignored_(BluetoothConnection *connection, ClientState state) {
ESP_LOGW(TAG, "[%d] [%s] Connection request ignored, state: %s", connection->get_connection_index(),
@@ -135,46 +141,6 @@ void BluetoothProxy::handle_gatt_not_connected_(uint64_t address, uint16_t handl
#ifdef USE_ESP32
#ifdef USE_ESP32_BLE_DEVICE
bool BluetoothProxy::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
// This method should never be called since bluetooth_proxy always uses raw advertisements
// but we need to provide an implementation to satisfy the virtual method requirement
return false;
}
#endif
bool BluetoothProxy::parse_devices(const esp32_ble::BLEScanResult *scan_results, size_t count) {
if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr)
return false;
auto &advertisements = this->response_.advertisements;
for (size_t i = 0; i < count; i++) {
auto &result = scan_results[i];
uint8_t length = result.adv_data_len + result.scan_rsp_len;
// Fill in the data directly at current position
auto &adv = advertisements[this->response_.advertisements_len];
adv.address = esp32_ble::ble_addr_to_uint64(result.bda);
adv.rssi = result.rssi;
adv.address_type = result.ble_addr_type;
adv.data_len = length;
std::memcpy(adv.data, result.ble_adv, length);
this->response_.advertisements_len++;
ESP_LOGV(TAG, "Queuing raw packet from %02X:%02X:%02X:%02X:%02X:%02X, length %d. RSSI: %d dB", result.bda[0],
result.bda[1], result.bda[2], result.bda[3], result.bda[4], result.bda[5], length, result.rssi);
// Flush if we have reached BLUETOOTH_PROXY_ADVERTISEMENT_BATCH_SIZE
if (this->response_.advertisements_len >= BLUETOOTH_PROXY_ADVERTISEMENT_BATCH_SIZE) {
this->flush_pending_advertisements_();
}
}
return true;
}
#endif // USE_ESP32
void BluetoothProxy::log_advertisement_flush_() {
@@ -236,10 +202,6 @@ void BluetoothProxy::loop() {
}
}
esp32_ble_tracker::AdvertisementParserType BluetoothProxy::get_advertisement_parser_type() {
return esp32_ble_tracker::AdvertisementParserType::RAW_ADVERTISEMENTS;
}
#endif // USE_ESP32
#ifdef BLUETOOTH_CONNECTION_HAS_GATT
@@ -675,7 +637,6 @@ void BluetoothProxy::subscribe_api_connection(api::APIConnection *api_connection
}
this->api_connection_ = api_connection;
#ifdef USE_ESP32
this->parent_->recalculate_advertisement_parser_types();
this->send_bluetooth_scanner_state_(this->parent_->get_scanner_state());
#else
this->send_bluetooth_scanner_state_();
@@ -688,9 +649,6 @@ void BluetoothProxy::unsubscribe_api_connection(api::APIConnection *api_connecti
return;
}
this->api_connection_ = nullptr;
#ifdef USE_ESP32
this->parent_->recalculate_advertisement_parser_types();
#endif
}
void BluetoothProxy::send_device_connection(uint64_t address, bool connected, uint16_t mtu, conn_err_t error) {
@@ -73,9 +73,7 @@ enum BluetoothProxySubscriptionFlag : uint32_t {
};
#ifdef USE_ESP32
class BluetoothProxy final : public esp32_ble_tracker::ESPBTDeviceListener,
public esp32_ble_tracker::BLEScannerStateListener,
public Component {
class BluetoothProxy final : public esp32_ble_tracker::BLEScannerStateListener, public Component {
#else
class BluetoothProxy final : public Component {
#endif
@@ -86,11 +84,12 @@ class BluetoothProxy final : public Component {
public:
BluetoothProxy();
#ifdef USE_ESP32
#ifdef USE_ESP32_BLE_DEVICE
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override;
#endif
bool parse_devices(const esp32_ble::BLEScanResult *scan_results, size_t count) override;
esp32_ble_tracker::AdvertisementParserType get_advertisement_parser_type() override;
// Advertisements arrive through the hub's raw callback; the tracker is
// still typed for the esp32-only scan-mode and scanner-state calls.
void set_parent(esp32_ble_tracker::ESP32BLETracker *parent) {
this->parent_ = parent;
this->hub_ = parent;
}
#endif // USE_ESP32
void dump_config() override;
void setup() override;
@@ -221,8 +220,8 @@ class BluetoothProxy final : public Component {
void send_bluetooth_scanner_state_(esp32_ble_tracker::ScannerState state);
#else
void send_bluetooth_scanner_state_();
void on_raw_advertisement_(const ble_device_base::RawAdvertisement &raw);
#endif
void on_raw_advertisement_(const ble_device_base::RawAdvertisement &raw);
/// Caller must ensure api_connection_ is non-null and API server is connected.
void flush_pending_advertisements_() {
@@ -288,8 +287,9 @@ class BluetoothProxy final : public Component {
// Group 2: Fixed-size array of connection pointers
std::array<BluetoothConnection *, BLUETOOTH_PROXY_MAX_CONNECTIONS> connections_{};
#endif
#ifndef USE_ESP32
ble_device_base::BLEHub *hub_{nullptr};
#ifdef USE_ESP32
esp32_ble_tracker::ESP32BLETracker *parent_{nullptr};
#endif
// BLE advertisement batching
@@ -462,6 +462,21 @@ void ESP32BLETracker::print_bt_device_info(const ESPBTDevice &device) {
#endif // USE_ESP32_BLE_DEVICE
void ESP32BLETracker::process_scan_result_(const BLEScanResult &scan_result) {
// Neutral raw-advertisement subscriber (the bluetooth_proxy path).
if (this->raw_advertisement_callback_.is_set()) {
uint8_t mac_lsb[6];
// bda is MSB-first; the neutral convention is LSB-first.
for (uint8_t i = 0; i < 6; i++)
mac_lsb[i] = scan_result.bda[5 - i];
ble_device_base::RawAdvertisement adv;
adv.mac = mac_lsb;
adv.data = scan_result.ble_adv;
adv.data_len = static_cast<uint16_t>(scan_result.adv_data_len) + scan_result.scan_rsp_len;
adv.rssi = scan_result.rssi;
adv.addr_type = scan_result.ble_addr_type;
this->raw_advertisement_callback_.invoke(adv);
}
// Process raw advertisements
if (this->raw_advertisements_) {
#ifdef ESPHOME_ESP32_BLE_TRACKER_LISTENER_COUNT