mirror of
https://github.com/esphome/esphome.git
synced 2026-06-24 16:04:55 +00:00
[esp32_ble_server] Fix duplicate Device Information Service with string UUIDs (#16784)
This commit is contained in:
committed by
Jesse Hills
parent
5662e1b7cd
commit
bcf5606b31
@@ -62,6 +62,26 @@ MANUFACTURER_NAME_CHARACTERISTIC_UUID = 0x2A29
|
||||
MODEL_CHARACTERISTIC_UUID = 0x2A24
|
||||
FIRMWARE_VERSION_CHARACTERISTIC_UUID = 0x2A26
|
||||
|
||||
# Suffix of the Bluetooth Base UUID used to expand 16/32 bit UUIDs to 128 bit.
|
||||
_BASE_UUID_SUFFIX = "-0000-1000-8000-00805F9B34FB"
|
||||
|
||||
|
||||
def uuid_is(uuid: int | str, uuid16: int) -> bool:
|
||||
"""Return True if a validated UUID refers to the given 16-bit short UUID.
|
||||
|
||||
A service/characteristic UUID may be an ``int`` (from ``cv.hex_uint32_t``) or an
|
||||
uppercase string in 16, 32 or 128 bit form (from ``bt_uuid``), so every
|
||||
representation of the same UUID must be considered equivalent.
|
||||
"""
|
||||
if isinstance(uuid, int):
|
||||
return uuid == uuid16
|
||||
return uuid.upper() in (
|
||||
f"{uuid16:04X}",
|
||||
f"{uuid16:08X}",
|
||||
f"{uuid16:08X}{_BASE_UUID_SUFFIX}",
|
||||
)
|
||||
|
||||
|
||||
# Core key to store the global configuration
|
||||
KEY_NOTIFY_REQUIRED = "notify_required"
|
||||
KEY_SET_VALUE = "set_value"
|
||||
@@ -195,7 +215,7 @@ def create_description_cud(char_config):
|
||||
return char_config
|
||||
# If the config displays a description, there cannot be a descriptor with the CUD UUID
|
||||
for desc in char_config[CONF_DESCRIPTORS]:
|
||||
if desc[CONF_UUID] == CUD_DESCRIPTOR_UUID:
|
||||
if uuid_is(desc[CONF_UUID], CUD_DESCRIPTOR_UUID):
|
||||
raise cv.Invalid(
|
||||
f"Characteristic {char_config[CONF_UUID]} has a description, but a CUD descriptor is already present"
|
||||
)
|
||||
@@ -218,7 +238,7 @@ def create_notify_cccd(char_config):
|
||||
return char_config
|
||||
# If the CCCD descriptor is already present, return the config
|
||||
for desc in char_config[CONF_DESCRIPTORS]:
|
||||
if desc[CONF_UUID] == CCCD_DESCRIPTOR_UUID:
|
||||
if uuid_is(desc[CONF_UUID], CCCD_DESCRIPTOR_UUID):
|
||||
# Check if the WRITE property is set
|
||||
if not desc[CONF_WRITE]:
|
||||
raise cv.Invalid(
|
||||
@@ -244,7 +264,7 @@ def create_device_information_service(config):
|
||||
# If there is already a device information service,
|
||||
# there cannot be CONF_MODEL, CONF_MANUFACTURER or CONF_FIRMWARE_VERSION properties
|
||||
for service in config[CONF_SERVICES]:
|
||||
if service[CONF_UUID] == DEVICE_INFORMATION_SERVICE_UUID:
|
||||
if uuid_is(service[CONF_UUID], DEVICE_INFORMATION_SERVICE_UUID):
|
||||
if (
|
||||
CONF_MODEL in config
|
||||
or CONF_MANUFACTURER in config
|
||||
@@ -592,7 +612,7 @@ async def to_code(config):
|
||||
)
|
||||
for char_conf in service_config[CONF_CHARACTERISTICS]:
|
||||
await to_code_characteristic(service_var, char_conf)
|
||||
if service_config[CONF_UUID] == DEVICE_INFORMATION_SERVICE_UUID:
|
||||
if uuid_is(service_config[CONF_UUID], DEVICE_INFORMATION_SERVICE_UUID):
|
||||
cg.add(var.set_device_information_service(service_var))
|
||||
else:
|
||||
cg.add(var.enqueue_start_service(service_var))
|
||||
|
||||
0
tests/component_tests/esp32_ble_server/__init__.py
Normal file
0
tests/component_tests/esp32_ble_server/__init__.py
Normal file
@@ -0,0 +1,47 @@
|
||||
"""Tests for esp32_ble_server configuration helpers."""
|
||||
|
||||
import pytest
|
||||
|
||||
from esphome.components.esp32_ble_server import (
|
||||
CCCD_DESCRIPTOR_UUID,
|
||||
CUD_DESCRIPTOR_UUID,
|
||||
DEVICE_INFORMATION_SERVICE_UUID,
|
||||
uuid_is,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"uuid",
|
||||
[
|
||||
DEVICE_INFORMATION_SERVICE_UUID, # int form (cv.hex_uint32_t)
|
||||
"180A", # 16 bit short form (bt_uuid)
|
||||
"180a", # lowercase is normalized by bt_uuid but guard anyway
|
||||
"0000180A", # 32 bit form
|
||||
"0000180A-0000-1000-8000-00805F9B34FB", # full 128 bit form
|
||||
],
|
||||
)
|
||||
def test_uuid_is_matches_all_representations(uuid) -> None:
|
||||
"""All representations of the same 16 bit UUID must compare equal."""
|
||||
assert uuid_is(uuid, DEVICE_INFORMATION_SERVICE_UUID)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"uuid",
|
||||
[
|
||||
0x1818, # Cycling Power Service (different int)
|
||||
"1818", # different 16 bit short form
|
||||
"0000180B", # adjacent UUID
|
||||
"0000180A-0000-1000-8000-00805F9B34FC", # wrong base UUID suffix
|
||||
],
|
||||
)
|
||||
def test_uuid_is_rejects_other_uuids(uuid) -> None:
|
||||
"""A different UUID must not be mistaken for the device information service."""
|
||||
assert not uuid_is(uuid, DEVICE_INFORMATION_SERVICE_UUID)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("uuid16", [CUD_DESCRIPTOR_UUID, CCCD_DESCRIPTOR_UUID])
|
||||
def test_uuid_is_matches_descriptor_short_strings(uuid16) -> None:
|
||||
"""Reserved descriptor UUIDs match whether given as int or short string."""
|
||||
assert uuid_is(uuid16, uuid16)
|
||||
assert uuid_is(f"{uuid16:04X}", uuid16)
|
||||
assert uuid_is(f"{uuid16:08X}", uuid16)
|
||||
Reference in New Issue
Block a user