hbldh / bleak

A cross platform Bluetooth Low Energy Client for Python using asyncio
MIT License
1.56k stars 275 forks source link

Dual-Mode Device Connects over BR/EDR (BleakClientBlueZDBus) #1521

Open ekenyon676 opened 2 months ago

ekenyon676 commented 2 months ago

Description

I am trying to use Bleak with a dual-mode (i.e. LE & BR/EDR) device that supports CTKD and has been paired with my computer. Since BlueZ generally tries to connect via BR/EDR when connect is called (see the API doc), I can't read the characteristics of the device using code similar to examples/service_explorer.py. I created a custom backend that connects a second time, and that works for me, but it seems like something that maybe could/should be handled in BleakClientBlueZDBus.

Since I'm assuming other people will face this issue using Bleak with dual-mode devices, I'd like to get input on a solution that doesn't rely on a custom backend.

What I Did

I'm new to asyncio, so please let me know if this solution could be improved, but using this as my custom backend allows me to connect via both BR/EDR and LE, which allows me to read the characteristics of the device.

from typing import Any, Coroutine
from dbus_fast.message import Message

from bleak.backends.bluezdbus.client import BleakClientBlueZDBus
from bleak.backends.bluezdbus import defs
from bleak.backends.bluezdbus.utils import assert_reply

class BleakClientBlueZDBusDualDevice(BleakClientBlueZDBus):
    async def connect(self, dangerous_use_bleak_cache: bool = False, **kwargs) -> Coroutine[Any, Any, bool]:
        await super().connect(dangerous_use_bleak_cache, **kwargs)

        reply = await self._bus.call(
            Message(
                destination=defs.BLUEZ_SERVICE,
                interface=defs.DEVICE_INTERFACE,
                path=self._device_path,
                member="Connect",
            )
        )

        assert reply is not None
        assert_reply(reply)

Logs

When I use BleakClientBlueZDBus as the backend, every read returns:

Error: [org.bluez.Error.Failed] Not connected

When I use BleakClientBlueZDBusDualDevice as the backend, I can read the values of the characteristics, as expected.

dlech commented 2 months ago

Hmm... There doesn't seem like a deterministic way to figure out which mode will connect. So even if this works for your specific device, I worry that this change could break other dual-mode devices that are currently working because calling connect twice would then result in it being connected in BR/EDR mode when LE connects on the first call.

If there was a property that indicated which mode was connected, then we could use that to decide if we should call connect a second time or not, but I couldn't find such a property.

If you control the firmware of the device, you could use different addresses for BR/EDR and LE as a workaround.

Also see:

ekenyon676 commented 2 months ago

Thanks for taking a look. Yeah, I couldn't find any such properties either. Thanks for pointing me to https://github.com/bluez/bluez/issues/511. That gives me some good background information, I hadn't found it in my searches.

Unfortunately because I need to be paired, my computer has the IRK for the device, which resolves the random address to the public address. I don't think there's a way of preventing it from using that.

Do you see any problems with the custom backend I posted above?

dlech commented 2 months ago

Do you see any problems with the custom backend I posted above?

The second call to Connect could cause ServicesResolved to go false again so you might need to wait for it to go true again before returning to ensure all services have been discovered.