From 65db614f22869a971a30ccfd38981f9f92a79aa3 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 8 Aug 2026 02:43:09 -0500 Subject: [PATCH 1/2] Pin the scanner-state callback slot semantics --- .../test_scanner_state_callback.cpp | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 tests/components/ble_device_base/test_scanner_state_callback.cpp diff --git a/tests/components/ble_device_base/test_scanner_state_callback.cpp b/tests/components/ble_device_base/test_scanner_state_callback.cpp new file mode 100644 index 0000000000..7515b2f38e --- /dev/null +++ b/tests/components/ble_device_base/test_scanner_state_callback.cpp @@ -0,0 +1,52 @@ +#include + +#include + +#include "esphome/components/ble_device_base/ble_hub.h" + +namespace esphome::ble_device_base::testing { + +// Pins the ScannerStateCallback slot semantics, mirroring test_raw_callback: +// a default-constructed slot is "no subscriber", a set slot delivers the +// state, and a new registration replaces the old. +namespace { + +struct CapturingSubscriber { + ScannerState last{ScannerState::IDLE}; + int calls{0}; + + static void trampoline(void *self, ScannerState state) { + auto *sub = static_cast(self); + sub->last = state; + sub->calls++; + } +}; + +} // namespace + +TEST(ScannerStateCallback, DefaultConstructedSlotIsNotSet) { + const ScannerStateCallback callback{}; + EXPECT_FALSE(callback.is_set()); +} + +TEST(ScannerStateCallback, SubscriberSeesState) { + CapturingSubscriber subscriber; + ScannerStateCallback callback{&subscriber, CapturingSubscriber::trampoline}; + ASSERT_TRUE(callback.is_set()); + callback.invoke(ScannerState::RUNNING); + EXPECT_EQ(subscriber.calls, 1); + EXPECT_EQ(subscriber.last, ScannerState::RUNNING); +} + +TEST(ScannerStateCallback, NewSubscriberReplacesOld) { + CapturingSubscriber first; + CapturingSubscriber second; + ScannerStateCallback callback{&first, CapturingSubscriber::trampoline}; + callback = {&second, CapturingSubscriber::trampoline}; + callback.invoke(ScannerState::STOPPED); + EXPECT_EQ(first.calls, 0); + EXPECT_EQ(second.calls, 1); + EXPECT_EQ(second.last, ScannerState::STOPPED); +} + +} // namespace esphome::ble_device_base::testing From 389d3629c29b26c6c610293fa07cf9645930f957 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 8 Aug 2026 02:43:45 -0500 Subject: [PATCH 2/2] Make the adapter MAC accessor const --- esphome/components/esp32_ble/ble.cpp | 2 +- esphome/components/esp32_ble/ble.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/esp32_ble/ble.cpp b/esphome/components/esp32_ble/ble.cpp index 456f02de9d..a7b3330bd6 100644 --- a/esphome/components/esp32_ble/ble.cpp +++ b/esphome/components/esp32_ble/ble.cpp @@ -674,7 +674,7 @@ void ESP32BLE::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gat } #endif -void ESP32BLE::get_mac_msb_first(uint8_t out[6]) { +void ESP32BLE::get_mac_msb_first(uint8_t out[6]) const { // The running stack owns the address (on hosted controllers it lives in // the remote chip's efuse); null before init becomes all-zero. const uint8_t *mac = esp_bt_dev_get_address(); diff --git a/esphome/components/esp32_ble/ble.h b/esphome/components/esp32_ble/ble.h index 43c9584c3c..45cfd8ee71 100644 --- a/esphome/components/esp32_ble/ble.h +++ b/esphome/components/esp32_ble/ble.h @@ -109,7 +109,7 @@ class ESP32BLE final : public Component { void loop() override; void dump_config() override; /// Adapter MAC in printable (MSB-first) order; all-zero until the stack is up. - void get_mac_msb_first(uint8_t out[6]); + void get_mac_msb_first(uint8_t out[6]) const; float get_setup_priority() const override; void set_name(const char *name) { this->name_ = name; }