Skip to content

Windows: Updated 128-bit characteristic UUID not reflected after firmware change (possible GATT cache issue?) #1940

@silaswaxter

Description

@silaswaxter

First off — thank you for the great library.

Description

I'm encountering an issue on Windows where a modified 128-bit characteristic UUID is not reflected in Bleak after updating my peripheral firmware. The service UUID appears correctly, but the characteristic UUID remains stuck at its previous value.

This looks like a Windows GATT caching issue, but I want to confirm whether:

  1. This is expected OS behavior.
  2. Bleak provides a way to force fresh service discovery.
  3. The previously merged Windows caching fixes (e.g., feat/windows-caching-issue) should already handle this case.

Peripheral

Custom embedded device (Nordic nRF52 running Zephyr).

  • Originally:
    • Service UUID: 562326d3-a8e8-43c4-b783-daa2145291f1
    • Characteristic UUID: 562326d4-a8e8-43c4-b783-daa2145291f1

I then changed the characteristic UUID.

  • Updated:
    • Service UUID: 562326d3-a8e8-43c4-b783-daa2145291f1
    • Characteristic UUID: 5c64d0cf-3ea7-42c7-9e92-844e3b345402

This is verified using nRF Connect (via nRF USB dongle), which correctly shows the updated UUID; see image below.

Environment

  • bleak 2.1.1
  • Windows 11
  • Python 3.14.3

Supporting Info

Image
"""
Scan/Discovery
--------------

Example showing how to scan for BLE devices.

Updated on 2019-03-25 by hbldh <henrik.blidh@nedomkull.com>

"""

import argparse
import asyncio
import logging
from contextlib import asynccontextmanager

from bleak import BleakScanner, BleakClient

logger = logging.getLogger(__name__)


class Args(argparse.Namespace):
    debug: bool

async def active_scan(duration_s: int):
    devices = await BleakScanner.discover(
        return_adv=True,
        timeout=duration_s,
        scanning_mode="active",
    )

    for device_data, advertisement_data in devices.values():
        logger.info("=" * 40)
        logger.info(device_data)
        logger.info(advertisement_data)

class DeviceNotFoundError(Exception):
    """Raised when a BLE device cannot be found."""
    pass

@asynccontextmanager
async def connect_to_device(device_name: str, timeout_s: int = 5):
    device = await BleakScanner.find_device_by_name(
        name=device_name,
        timeout=timeout_s,
        scanning_mode="active",
    )
    if device is None:
        raise DeviceNotFoundError(f"Failed to find device with name '{device_name}'")

    async with BleakClient(device) as client:
        yield client

async def main(args: Args):
    logger.debug("entering main")

    # scan_time_s = 2
    # async def scanner_worker():
    #     while True:
    #         ble_min_advertisement_period_s = 0.020
    #         await active_scan(duration_s=ble_min_advertisement_period_s)
    # try:
    #     await asyncio.wait_for(scanner_worker(), timeout=scan_time_s)
    # except asyncio.TimeoutError:
    #     logger.debug(f"Scan stopped after {scan_time_s} seconds")

    async with connect_to_device("Silas ROM") as client:
        logger.info(client.is_connected)
        for service in client.services:
            logger.info(f"service w/ desc='{service.description}', uuid='{service.uuid}'")

            for char in service.characteristics:
                logger.info(f"  Characteristic: uuid='{char.uuid}', props={char.properties}")
                # Find the Characteristic User Description (CUD) descriptor
                for descriptor in char.descriptors:
                    logger.info(f"    Descriptor: uuid='{descriptor.uuid}'")
                    # if descriptor.uuid == "2901":  # Standard CUD UUID
                    #     cud_value = await client.read_gatt_descriptor(descriptor.handle)
                    #     logger.info(f"      CUD: {cud_value.decode('utf-8')}")
        await asyncio.sleep(10)


if __name__ == "__main__":
    # parse cli args
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "-d",
        "--debug",
        action="store_true",
        help="sets the logging level to debug",
    )   
    args = parser.parse_args(namespace=Args())

    # setup logging
    log_level = logging.DEBUG if args.debug else logging.INFO
    logging.basicConfig(
        level=log_level,
        format="%(asctime)-15s %(name)-8s %(levelname)s: %(message)s",
    )


    asyncio.run(main(args))
> python .\main.py
2026-02-21 22:15:18,074 __main__ INFO: True
2026-02-21 22:15:18,074 __main__ INFO: service w/ desc='Generic Attribute Profile', uuid='00001801-0000-1000-8000-00805f9b34fb'
2026-02-21 22:15:18,075 __main__ INFO:   Characteristic: uuid='00002a05-0000-1000-8000-00805f9b34fb', props=['indicate']
2026-02-21 22:15:18,075 __main__ INFO:     Descriptor: uuid='00002902-0000-1000-8000-00805f9b34fb'
2026-02-21 22:15:18,075 __main__ INFO: service w/ desc='Generic Access Profile', uuid='00001800-0000-1000-8000-00805f9b34fb'
2026-02-21 22:15:18,075 __main__ INFO:   Characteristic: uuid='00002a00-0000-1000-8000-00805f9b34fb', props=['read']
2026-02-21 22:15:18,075 __main__ INFO:   Characteristic: uuid='00002a01-0000-1000-8000-00805f9b34fb', props=['read']
2026-02-21 22:15:18,075 __main__ INFO:   Characteristic: uuid='00002a04-0000-1000-8000-00805f9b34fb', props=['read']
2026-02-21 22:15:18,075 __main__ INFO: service w/ desc='Unknown', uuid='562326d3-a8e8-43c4-b783-daa2145291f1'
2026-02-21 22:15:18,075 __main__ INFO:   Characteristic: uuid='562326d4-a8e8-43c4-b783-daa2145291f1', props=['read']

Metadata

Metadata

Assignees

No one assigned

    Labels

    Backend: WinRTIssues or PRs relating to the WinRT backendmore info requiredIssues does not have a reproducible test case, has insufficent logs or otherwise needs more feedback

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions