[ble_device_base] [ble_client] Restore unset UUID state so notify sensors set up again (#18098)

This commit is contained in:
J. Nick Koston
2026-08-05 22:21:24 +00:00
committed by GitHub
parent 1bd94bb805
commit 26256d3d2e
5 changed files with 80 additions and 5 deletions
@@ -61,7 +61,7 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
break;
}
this->handle = chr->handle;
if (this->descr_uuid_.get_uuid().len > 0) {
if (this->descr_uuid_.is_set()) {
auto *descr = chr->get_descriptor(this->descr_uuid_);
if (descr == nullptr) {
this->status_set_warning();
@@ -61,7 +61,7 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
break;
}
this->handle = chr->handle;
if (this->descr_uuid_.get_uuid().len > 0) {
if (this->descr_uuid_.is_set()) {
auto *descr = chr->get_descriptor(this->descr_uuid_);
if (descr == nullptr) {
this->status_set_warning();
@@ -98,6 +98,8 @@ ESPBTUUID ESPBTUUID::from_raw(const char *data, size_t length) {
#ifdef USE_ESP32
ESPBTUUID ESPBTUUID::from_uuid(esp_bt_uuid_t uuid) {
if (uuid.len == 0) // the unset sentinel get_uuid() emits
return {};
if (uuid.len == ESP_UUID_LEN_16)
return ESPBTUUID::from_uint16(uuid.uuid.uuid16);
if (uuid.len == ESP_UUID_LEN_32)
@@ -108,6 +110,10 @@ ESPBTUUID ESPBTUUID::from_uuid(esp_bt_uuid_t uuid) {
esp_bt_uuid_t ESPBTUUID::get_uuid() const {
esp_bt_uuid_t ret;
switch (this->type_) {
case Type::UNSET:
ret.len = 0;
memset(&ret.uuid, 0, sizeof(ret.uuid));
break;
case Type::UUID16:
ret.len = ESP_UUID_LEN_16;
ret.uuid.uuid16 = this->uuid_.uuid16;
@@ -139,7 +145,8 @@ void ESPBTDevice::parse_scan_rst(const esp32_ble::BLEScanResult &scan_result) {
#endif // USE_ESP32
ESPBTUUID ESPBTUUID::as_128bit() const {
if (this->type_ == Type::UUID128)
// Widening an unset UUID stays unset; expanding it would produce a set 0x0000 base UUID.
if (this->type_ == Type::UNSET || this->type_ == Type::UUID128)
return *this;
uint8_t data[16];
this->to_128bit_(data);
@@ -149,6 +156,8 @@ ESPBTUUID ESPBTUUID::as_128bit() const {
bool ESPBTUUID::contains(uint8_t data1, uint8_t data2) const {
// Adjacent byte-pair search — identical semantics to esp32_ble::ESPBTUUID::contains.
switch (this->type_) {
case Type::UNSET:
return false;
case Type::UUID16:
return (this->uuid_.uuid16 >> 8) == data2 && (this->uuid_.uuid16 & 0xFF) == data1;
case Type::UUID32:
@@ -173,6 +182,9 @@ const char *ESPBTUUID::to_str(char *buf) const {
// Identical output format to esp32_ble::ESPBTUUID::to_str.
char *pos = buf;
switch (this->type_) {
case Type::UNSET:
memcpy(buf, "None", 5);
return buf;
case Type::UUID16:
*pos++ = '0';
*pos++ = 'x';
@@ -207,6 +219,7 @@ const char *ESPBTUUID::to_str(char *buf) const {
void ESPBTUUID::to_128bit_(uint8_t out[16]) const {
// Bluetooth Base UUID 00000000-0000-1000-8000-00805F9B34FB (LSB-first), with the 16/32-bit
// value placed at bytes 12..; identical expansion to esp32_ble::ESPBTUUID::as_128bit().
// Callers screen out UNSET first (operator==, as_128bit); it would expand like 0x0000.
static const uint8_t BASE[16] = {0xFB, 0x34, 0x9B, 0x5F, 0x80, 0x00, 0x00, 0x80,
0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
if (this->type_ == Type::UUID128) {
@@ -223,6 +236,8 @@ void ESPBTUUID::to_128bit_(uint8_t out[16]) const {
bool ESPBTUUID::operator==(const ESPBTUUID &other) const {
if (this->type_ == other.type_) {
switch (this->type_) {
case Type::UNSET:
return true;
case Type::UUID16:
return this->uuid_.uuid16 == other.uuid_.uuid16;
case Type::UUID32:
@@ -232,6 +247,9 @@ bool ESPBTUUID::operator==(const ESPBTUUID &other) const {
}
return false;
}
// Unset never equals a set UUID; 0x0000 is a valid value, distinct from "not configured".
if (this->type_ == Type::UNSET || other.type_ == Type::UNSET)
return false;
// Different widths: expand both to the 128-bit Bluetooth Base UUID form and compare, so a
// configured 16/32-bit UUID matches the equivalent 128-bit advertisement (esp32 parity).
uint8_t a[16];
@@ -91,8 +91,11 @@ class ESPBTUUID {
#if defined(__cpp_lib_span)
const char *to_str(std::span<char, UUID_STR_LEN> output) const { return this->to_str(output.data()); }
#endif
enum class Type : uint8_t { UUID16, UUID32, UUID128 };
// UNSET is the default-constructed state; get_uuid() reports it as len 0 (the historical sentinel).
enum class Type : uint8_t { UNSET, UUID16, UUID32, UUID128 };
Type type() const { return this->type_; }
/// True if a UUID has been configured (not default-constructed).
bool is_set() const { return this->type_ != Type::UNSET; }
uint16_t uuid16() const { return this->uuid_.uuid16; }
uint32_t uuid32() const { return this->uuid_.uuid32; }
const uint8_t *uuid128() const { return this->uuid_.uuid128; }
@@ -101,7 +104,7 @@ class ESPBTUUID {
// Expand to the 128-bit Bluetooth Base UUID byte form (out is 16 bytes, little-endian).
void to_128bit_(uint8_t out[16]) const;
Type type_{Type::UUID16};
Type type_{Type::UNSET};
union {
uint16_t uuid16;
uint32_t uuid32;
@@ -27,6 +27,60 @@ TEST(BleDeviceUuid, ThirtyTwoBitMatchesEquivalentLongForm) {
EXPECT_TRUE(u32 == u128);
}
// A default-constructed UUID is UNSET, the historical "not configured" sentinel
// (len 0 through the esp32 get_uuid() adapter).
TEST(BleDeviceUuid, DefaultConstructedIsUnset) {
const ESPBTUUID unset;
EXPECT_EQ(unset.type(), ESPBTUUID::Type::UNSET);
EXPECT_TRUE(unset == ESPBTUUID());
EXPECT_FALSE(unset == ESPBTUUID::from_uint16(0x1234));
EXPECT_FALSE(unset.contains(0x00, 0x00));
}
// Every factory yields a non-UNSET UUID, even for 0x0000: only default construction and a
// failed text parse are unset, keeping type() != UNSET equivalent to the old len > 0 check.
TEST(BleDeviceUuid, AllFactoriesProduceSetUuids) {
const uint8_t raw[16] = {0xFB, 0x34, 0x9B, 0x5F, 0x80, 0x00, 0x00, 0x80,
0x00, 0x10, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00};
EXPECT_NE(ESPBTUUID::from_uint16(0x0000).type(), ESPBTUUID::Type::UNSET);
EXPECT_NE(ESPBTUUID::from_uint32(0).type(), ESPBTUUID::Type::UNSET);
EXPECT_NE(ESPBTUUID::from_raw(raw).type(), ESPBTUUID::Type::UNSET);
EXPECT_NE(ESPBTUUID::from_raw_reversed(raw).type(), ESPBTUUID::Type::UNSET);
EXPECT_NE(ESPBTUUID::from_raw("180F", 4).type(), ESPBTUUID::Type::UNSET);
EXPECT_NE(ESPBTUUID::from_raw("0000180F", 8).type(), ESPBTUUID::Type::UNSET);
EXPECT_NE(ESPBTUUID::from_raw(reinterpret_cast<const char *>(raw), 16).type(), ESPBTUUID::Type::UNSET);
EXPECT_NE(ESPBTUUID::from_raw("6E400001-B5A3-F393-E0A9-E50E24DCCA9E").type(), ESPBTUUID::Type::UNSET);
}
// 0x0000 is a valid short UUID on real devices (esphome/aioesphomeapi#1742); an unset
// UUID must never compare equal to it. Unset equals only unset.
TEST(BleDeviceUuid, UnsetIsNotEqualToZeroUuid) {
EXPECT_FALSE(ESPBTUUID() == ESPBTUUID::from_uint16(0x0000));
EXPECT_FALSE(ESPBTUUID::from_uint16(0x0000) == ESPBTUUID());
const uint8_t base[16] = {0xFB, 0x34, 0x9B, 0x5F, 0x80, 0x00, 0x00, 0x80,
0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
EXPECT_FALSE(ESPBTUUID() == ESPBTUUID::from_raw(base));
EXPECT_TRUE(ESPBTUUID() == ESPBTUUID());
EXPECT_FALSE(ESPBTUUID().as_128bit().is_set()); // widening preserves the unset state
// A configured 0x0000 still matches its own 128-bit base UUID expansion.
EXPECT_TRUE(ESPBTUUID::from_uint16(0x0000) == ESPBTUUID::from_raw(base));
}
// is_set() is the sentinel check; an unset UUID prints as "None" instead of a
// valid-looking all-zero 128-bit UUID.
TEST(BleDeviceUuid, IsSetAndUnsetToStr) {
char buf[UUID_STR_LEN];
EXPECT_FALSE(ESPBTUUID().is_set());
EXPECT_STREQ(ESPBTUUID().to_str(buf), "None");
EXPECT_TRUE(ESPBTUUID::from_uint16(0x0000).is_set());
EXPECT_STREQ(ESPBTUUID::from_uint16(0x0000).to_str(buf), "0x0000");
}
// Text parsing of an invalid length historically produced a len-0 (unset) UUID.
TEST(BleDeviceUuid, InvalidTextFormParsesToUnset) {
EXPECT_EQ(ESPBTUUID::from_raw("nope", 3).type(), ESPBTUUID::Type::UNSET);
}
TEST(BleDeviceUuid, DifferentUuidsDoNotMatch) {
EXPECT_FALSE(ESPBTUUID::from_uint16(0x1234) == ESPBTUUID::from_uint16(0x1235));
const uint8_t raw128[16] = {0xFB, 0x34, 0x9B, 0x5F, 0x80, 0x00, 0x00, 0x80,