From a6f81f31859bfbafb5161cf2af383ff99a2be30c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 8 Aug 2026 00:12:50 -0500 Subject: [PATCH] Carry the address as uint64 in RawAdvertisement and unify the proxy setup --- .../bk72xx_ble_tracker/bk72xx_ble_tracker.cpp | 2 +- esphome/components/ble_device_base/ble_hub.h | 5 +- .../bluetooth_proxy/bluetooth_proxy.cpp | 57 ++++++------------- .../components/esp32_ble_tracker/__init__.py | 14 ----- .../esp32_ble_tracker/esp32_ble_tracker.cpp | 6 +- .../ln882h_ble_tracker/ln882h_ble_tracker.cpp | 7 ++- .../rp2_ble_tracker/rp2_ble_tracker.cpp | 2 +- .../ble_device_base/test_raw_callback.cpp | 6 +- 8 files changed, 32 insertions(+), 67 deletions(-) diff --git a/esphome/components/bk72xx_ble_tracker/bk72xx_ble_tracker.cpp b/esphome/components/bk72xx_ble_tracker/bk72xx_ble_tracker.cpp index c859f22c61..a58561f2de 100644 --- a/esphome/components/bk72xx_ble_tracker/bk72xx_ble_tracker.cpp +++ b/esphome/components/bk72xx_ble_tracker/bk72xx_ble_tracker.cpp @@ -159,7 +159,7 @@ void BK72xxBLETracker::dump_config() { void BK72xxBLETracker::on_scan_report(const bk72xx_ble::BLEScanReport &report) { // Raw callback (the raw-advertisement path). if (this->raw_advertisement_callback_.is_set()) { - const ble_device_base::RawAdvertisement adv{.mac = report.mac, + const ble_device_base::RawAdvertisement adv{.address = ble_device_base::mac_lsb_first_to_uint64(report.mac), .data = report.data, .data_len = report.data_len, .rssi = report.rssi, diff --git a/esphome/components/ble_device_base/ble_hub.h b/esphome/components/ble_device_base/ble_hub.h index b6fcf6f57a..d9a7731504 100644 --- a/esphome/components/ble_device_base/ble_hub.h +++ b/esphome/components/ble_device_base/ble_hub.h @@ -23,8 +23,9 @@ namespace esphome::ble_device_base { /// One raw advertisement as delivered by the controller — a borrowed view, /// valid only for the duration of the invoke() callback. struct RawAdvertisement { - /// Least-significant octet first (BLE controller convention). - const uint8_t *mac; + /// Producers convert their native byte order at the emit site, so no + /// byte-order convention crosses this contract. + uint64_t address; const uint8_t *data; uint16_t data_len; int8_t rssi; // signed dBm diff --git a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp index 75da205be4..3b50b845a8 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp @@ -26,18 +26,6 @@ BluetoothProxy::BluetoothProxy() { global_bluetooth_proxy = this; } #ifdef USE_ESP32 -void BluetoothProxy::setup() { - this->connections_free_response_.limit = BLUETOOTH_PROXY_MAX_CONNECTIONS; - this->connections_free_response_.free = BLUETOOTH_PROXY_MAX_CONNECTIONS; - - // 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(self)->on_raw_advertisement_(adv); - }}); -} - void BluetoothProxy::on_scanner_state(esp32_ble_tracker::ScannerState state) { if (this->api_connection_ != nullptr) { this->send_bluetooth_scanner_state_(state); @@ -47,8 +35,8 @@ void BluetoothProxy::on_scanner_state(esp32_ble_tracker::ScannerState state) { void BluetoothProxy::send_bluetooth_scanner_state_(esp32_ble_tracker::ScannerState state) { api::BluetoothScannerStateResponse resp; resp.state = static_cast(state); - resp.mode = this->parent_->get_scan_active() ? api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_ACTIVE - : api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_PASSIVE; + resp.mode = this->hub_->scan_active() ? api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_ACTIVE + : api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_PASSIVE; resp.configured_mode = this->configured_scan_active_ ? api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_ACTIVE : api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_PASSIVE; @@ -57,21 +45,6 @@ void BluetoothProxy::send_bluetooth_scanner_state_(esp32_ble_tracker::ScannerSta #else // !USE_ESP32 -void BluetoothProxy::setup() { - // BLUETOOTH_PROXY_MAX_CONNECTIONS is 0 on an advertisement-only proxy. - this->connections_free_response_.limit = BLUETOOTH_PROXY_MAX_CONNECTIONS; - this->connections_free_response_.free = BLUETOOTH_PROXY_MAX_CONNECTIONS; - - // Capture the configured scan mode from YAML before any API changes - this->configured_scan_active_ = this->hub_->scan_active(); - - // The hub delivers raw advertisements on the ESPHome main loop: - // mac is least-significant octet first (BLE controller convention). - this->hub_->set_raw_advertisement_callback({this, [](void *self, const ble_device_base::RawAdvertisement &adv) { - static_cast(self)->on_raw_advertisement_(adv); - }}); -} - 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 @@ -92,15 +65,26 @@ 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::setup() { + // BLUETOOTH_PROXY_MAX_CONNECTIONS is 0 on an advertisement-only proxy. + this->connections_free_response_.limit = BLUETOOTH_PROXY_MAX_CONNECTIONS; + this->connections_free_response_.free = BLUETOOTH_PROXY_MAX_CONNECTIONS; + + // Capture the configured scan mode from YAML before any API changes + this->configured_scan_active_ = this->hub_->scan_active(); + + this->hub_->set_raw_advertisement_callback({this, [](void *self, const ble_device_base::RawAdvertisement &adv) { + static_cast(self)->on_raw_advertisement_(adv); + }}); +} + +// The hub delivers raw advertisements on the ESPHome main loop. 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.address = raw.address; adv.rssi = raw.rssi; adv.address_type = raw.addr_type; uint8_t length = raw.data_len > sizeof(adv.data) ? sizeof(adv.data) : static_cast(raw.data_len); @@ -109,8 +93,7 @@ void BluetoothProxy::on_raw_advertisement_(const ble_device_base::RawAdvertiseme 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); + ESP_LOGV(TAG, "Queuing raw packet from %012llX, length %d. RSSI: %d dB", raw.address, length, raw.rssi); // Flush if we have reached BLUETOOTH_PROXY_ADVERTISEMENT_BATCH_SIZE if (this->response_.advertisements_len >= BLUETOOTH_PROXY_ADVERTISEMENT_BATCH_SIZE) { @@ -139,10 +122,6 @@ void BluetoothProxy::handle_gatt_not_connected_(uint64_t address, uint16_t handl this->send_gatt_error(address, handle, GATT_NOT_CONNECTED); } -#ifdef USE_ESP32 - -#endif // USE_ESP32 - void BluetoothProxy::log_advertisement_flush_() { ESP_LOGV(TAG, "Sent batch of %u BLE advertisements", this->response_.advertisements_len); } diff --git a/esphome/components/esp32_ble_tracker/__init__.py b/esphome/components/esp32_ble_tracker/__init__.py index b8f49d4fbd..646ce79233 100644 --- a/esphome/components/esp32_ble_tracker/__init__.py +++ b/esphome/components/esp32_ble_tracker/__init__.py @@ -374,20 +374,6 @@ async def register_client(var: cg.SafeExpType, config: ConfigType) -> cg.SafeExp return var -async def register_raw_ble_device( - var: cg.SafeExpType, config: ConfigType -) -> cg.SafeExpType: - """Register a BLE device listener that only needs raw advertisement data. - - This does NOT register the ESP_BT_DEVICE feature, meaning ESPBTDevice - will not be compiled in if this is the only registration method used. - """ - _request_listener_slot() - paren = await cg.get_variable(config[CONF_ESP32_BLE_ID]) - cg.add(paren.register_listener(var)) - return var - - async def register_raw_client( var: cg.SafeExpType, config: ConfigType ) -> cg.SafeExpType: diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index f21635fb82..cec2f230f8 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -464,12 +464,8 @@ void ESP32BLETracker::print_bt_device_info(const ESPBTDevice &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.address = esp32_ble::ble_addr_to_uint64(scan_result.bda); adv.data = scan_result.ble_adv; adv.data_len = static_cast(scan_result.adv_data_len) + scan_result.scan_rsp_len; adv.rssi = scan_result.rssi; diff --git a/esphome/components/ln882h_ble_tracker/ln882h_ble_tracker.cpp b/esphome/components/ln882h_ble_tracker/ln882h_ble_tracker.cpp index 90be341820..cddcd6c17d 100644 --- a/esphome/components/ln882h_ble_tracker/ln882h_ble_tracker.cpp +++ b/esphome/components/ln882h_ble_tracker/ln882h_ble_tracker.cpp @@ -240,8 +240,11 @@ void LN882HBLETracker::process_adv_(const uint8_t *mac, int8_t rssi, uint8_t add // Raw callback (the raw-advertisement path). Both full advertisements and // unmatched scan responses (raw_only) are forwarded. if (this->raw_advertisement_callback_.is_set()) { - const ble_device_base::RawAdvertisement adv{ - .mac = mac, .data = data, .data_len = data_len, .rssi = rssi, .addr_type = addr_type}; + const ble_device_base::RawAdvertisement adv{.address = ble_device_base::mac_lsb_first_to_uint64(mac), + .data = data, + .data_len = data_len, + .rssi = rssi, + .addr_type = addr_type}; this->raw_advertisement_callback_.invoke(adv); } diff --git a/esphome/components/rp2_ble_tracker/rp2_ble_tracker.cpp b/esphome/components/rp2_ble_tracker/rp2_ble_tracker.cpp index ed036328ae..c2bb93a32e 100644 --- a/esphome/components/rp2_ble_tracker/rp2_ble_tracker.cpp +++ b/esphome/components/rp2_ble_tracker/rp2_ble_tracker.cpp @@ -122,7 +122,7 @@ void RP2BLETracker::dump_config() { void RP2BLETracker::on_scan_report(const rp2040_ble::BLEScanReport &report) { // Raw callback (the raw-advertisement path). if (this->raw_advertisement_callback_.is_set()) { - const ble_device_base::RawAdvertisement adv{.mac = report.mac, + const ble_device_base::RawAdvertisement adv{.address = ble_device_base::mac_lsb_first_to_uint64(report.mac), .data = report.data, .data_len = report.data_len, .rssi = report.rssi, diff --git a/tests/components/ble_device_base/test_raw_callback.cpp b/tests/components/ble_device_base/test_raw_callback.cpp index cd18c3db59..c28edfb7ef 100644 --- a/tests/components/ble_device_base/test_raw_callback.cpp +++ b/tests/components/ble_device_base/test_raw_callback.cpp @@ -47,12 +47,12 @@ struct CapturingSubscriber { }; // Device AA:BB:CC:DD:EE:FF — controller order delivers FF first. -const uint8_t MAC_LSB_FIRST[6] = {0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa}; +constexpr uint64_t TEST_ADDRESS = 0xAABBCCDDEEFFULL; const uint8_t ADV_DATA[4] = {0x02, 0x01, 0x06, 0x00}; RawAdvertisement make_test_adv() { return RawAdvertisement{ - .mac = MAC_LSB_FIRST, .data = ADV_DATA, .data_len = sizeof(ADV_DATA), .rssi = -63, .addr_type = 1}; + .address = TEST_ADDRESS, .data = ADV_DATA, .data_len = sizeof(ADV_DATA), .rssi = -63, .addr_type = 1}; } } // namespace @@ -70,7 +70,7 @@ TEST(RawAdvertisementCallback, SubscriberSeesFieldsUnchanged) { hub.emit(make_test_adv()); ASSERT_EQ(subscriber.calls, 1); - EXPECT_EQ(subscriber.last.mac, MAC_LSB_FIRST); + EXPECT_EQ(subscriber.last.address, TEST_ADDRESS); EXPECT_EQ(subscriber.last.data, ADV_DATA); EXPECT_EQ(subscriber.last.data_len, sizeof(ADV_DATA)); EXPECT_EQ(subscriber.last.rssi, -63);