AttributeError: 'HaBleakScannerWrapper' object has no attribute 'find_device_by_address'

Hi there. I was wondering if anyone knows why when I use a BleakScanner in my custom components to connect to my BLE devices, I get the error AttributeError: ‘HaBleakScannerWrapper’ object has no attribute ‘find_device_by_address’ when I use the command BleakScanner.find_device_by_address(the_mac_address). The same applies to find_device_by_name. Without these, BleakScanner is a bit redundant!

These works fine outside HA using Bleak but fails in HA. What is this wrapper and why is it not including all of what it wraps? I have set in my manifest that I need bleak 0.21.0 but it seems i am being forced to use an HA modified version

Any recommendations?

Thanks

—Chris

Context for those who care - because the obvious answer is to jump BleakScanner and just use a BleakClient(the_mac_address) instead.

I discovered a package baked into HA called bleak_retry_connector* that seems to help stabilise unrelaible BLE connections (which happens for me, and is the fault of the device not HA or ESP32 thingy. Bleak_retry_connector only accepts a BLEDevice object, which is what BleakScanner returns….hence this problem…

*That package is the singularly worst documented thing I have ever seen (the cherry on the top being an issue asking please supply an example on how to use it being replied to with ‘no’ and telling the person to go and read through all the dependent repos to find a file that uses it…a task that took me two hours to actually find one!)

You have an old version of habluetooth installed.

Actually it looks like find_device_by_address is not implemented in the wrapper habluetooth/src/habluetooth/wrappers.py at 1eaa4be7fd91679e10b4b3e7e87dacbdae1b44bb · Bluetooth-Devices/habluetooth · GitHub

HA has its own api for fetching the device Bluetooth APIs | Home Assistant Developer Docs

You probably want Bluetooth APIs | Home Assistant Developer Docs

It should be possible to map find_device_by_address on the wrapper as well for compatibility with bleak in habluetooth, however its currently not implemented.

with the singularly worst documented thing I have ever seen.

- The author of the package you are ranting about

Thanks for the quick reply. Noting the links into the Bluetooth APIs give the answer, I was trying to keep my library the same as I use it in HA and outside HA (where the bluetooth APIs for HA wont work). Its not like it changes much, I’ll just add a flag for use inside or outside HA.This is only for me to use, its not something many people will want to reuse.

Regarding the documentation rant, I don’t think its obvious quite how steep the learning curve for developing a bluetooth custom component (or, indeed, a custom component at all… but thats for another day … noting I have read everything on making your first CC before saying this). It literally took two hours to find a git repo who used bleak_retry_connector to even see the basic usage. Things like finding there is a wrapper for Bleak and to find standard functions just dont work without any documentation (a few people have asked about this but there are no answers in Google or this forum it seems) and that the bluetooth APIs within HA are compatible with Bleak, and bleak_retry_connector can then sit between the bluetooth APIs and the Bleak get and set functions, is the kind of thing that would have saved me at least a day of googling and experimentation.

If you in the core team are just discovering that the wrapper doesn’t implement these functions, think how we feel…It just creates blockers that people like me who are able to write code but are very (very) much not going to call ourselves developers use as an excuse to give up! We aren’t good enough to survive the steep learning curve :slight_smile:

Regards

–Chris

The missing API seems like a bug to me, and we should fix it so you don’t have to handle it differently. Ideally, that would have been a good GitHub issue for habluetooth. I’ve gone ahead and done that in habluetooth 2.8.1.

When you ask for help and complain simultaneously, there are better paths to getting help.

Documentation contributions are needed; remember that I’m doing this in my spare time, and it’s not something I can handle alone. Please consider contributing.

Sorry totally forgot to reply

Point about contributing very much noted. I have written up a bit of an idiots guide to how I have got my (openly admittedly hacky) component to work to help others. It’s one of those situations where I probably shouldn’t be encouraging others to cut corners like I have and what I have made I am very sure is against most best practice, but I’m equally sure lots of people would do the same.

It’s very long, so might put it on something like medium as I’m not sure I’m supposed to make forum posts that long

Cheers

Chris

1 Like

Hi @bdraco I have just come back to this (was waiting for the update to get released then forgot to return to it!) and I am very confused about the error I am getting

Looking that the wrapper.py in habluetooth the find_device_by_address wrapper you added looks entirely sensible:

async def find_device_by_address(
        self, device_identifier: str, timeout: float = 10.0, **kwargs: Any
    ) -> BLEDevice | None:

However when I call for a BleakScanner in my code like this

bledevice = (await BleakScanner.find_device_by_address("C0:00:00:01:66:44"))

I get this exception

TypeError: HaBleakScannerWrapper.find_device_by_address() missing 1 required positional argument: 'device_identifier'

Any idea why? I am of course assuming that the latest release is the same as the one I am looking at in the habluetooth github repo - have these changes made it through into a release yet do you think? Or is there something that is losing this parameter between my call and the wrapper call?

Many thanks

–Chris

Looks like it needs a @classmethod decorator. Will fix it later today.

edit: fixed in Bump habluetooth to 3.1.1 by bdraco · Pull Request #117992 · home-assistant/core · GitHub

Thank you!

Me again…sorry

I’m now not getting errors when running the Bleak discovery scans. That seems to be good

However I am getting one of the no slot errors when I contact my device:

Failed to call service light/turn_on. No backend with an available connection slot that can reach address HaBleakClientWrapper, C0:00:00:01:66:44 was found

That would make sense if the device was unreachable, but I am using an ESP32 bluetooth proxy and on that (via ESPHome) I can watch the logs and see it connect to the device at the same time as this error is being shown, which is a bit strange:

[16:12:03][D][esp32_ble_tracker:266]: Starting scan...
[16:12:05][D][esp32_ble_client:306]: [0] [C0:00:00:01:66:44] Event 46
[16:12:06][D][esp32_ble_client:188]: [0] [C0:00:00:01:66:44] cfg_mtu status 0, mtu 23
[16:12:14][W][bluetooth_proxy:246]: [0] [C0:00:00:01:66:44] Connection already established

Any ideas? For reference, before I started playing with the bleakretryconnector (which I did because the device I am contacting is quite unreliable and so I’m hoping that this approach will solve that) I did have my integration working, so I am fairly confident that any issues sit within bleak/hableakwrapper upstream components.

Thanks again

–Chris

Its unexpected that HaBleakClientWrapper would appear in that message

We do stringify the input here

Is it possible that an non-string object is being passed into the connect call? Maybe there is a missing await somewhere

I’ll will have a look on Monday. It’s equally strange that I can see the ESP32 is connecting to the device at the same time as the error occurs. So something somewhere is happening properly.

I’ll have a go at debugging it, but it’s a pig to do as you can’t have a dev container and use Bluetooth proxies so I have to do it live and thus can’t have breakpoints!

Chris

From memory however, if this helps, I think the connect call takes a BLEDevice not a MAC and so, yes, a non string object is being passed. I am scanning for the presence of that MAC in the discovery scan (the one just fixed) and then passing that returned BLEDevice to the bleakretryconnector (which only takes a BLEDevice…hence the need to do the scanning call prior only in order to generate one I can use to make the subsequent bleakretryconnector call)

Chris

That error we are discussing is being generated when I call the line below - the creation of BleakClient. I can comment out everything that follows and the error still occurs, but comment this out and it doesn’t.

async with BleakClient(await self._connect_to_lampster()) as client:
   (rest of code snipped)

BleakClient takes a BLEDevice as its parameter, which is what I return from my _connect_to_lampster function. That BLEDevice is what is returned from the earlier find_device_by_address command that was just fixed (I have checked and ‘bledevice’ is indeed a BLEDevice (I f’’ printed it to debug to check its type, see below)

bledevice = (await BleakScanner.find_device_by_address("C0:00:00:01:66:44"))
2024-06-10 09:02:33.685 INFO (MainThread) [custom_components.lampster.light] BLEDevice: C0:00:00:01:66:44: Lampster

(NB just to be doubly sure, I actually checked this from my light.py by returning the bledevice object right the way back down the stack out of my libraries and intermediate functions - so I could be entirely sure it wasn’t being changed or corrupted as it was returned anywhere in my code, - so that BLEDevice is definitely what is being passed to BleakClient)

Given the error only appears when I call the BleakClient line using a valid BLEDevice object as per the definitions in bleak’s readthedocs manual for 0.22, I think the cause is somewhere in the wrapper

Regards

–Chris

Also if it helps, i just tried to call BleakClient with None as the parameter (not a real BLEDevice object) as part of my debugging and the error this time was different

Failed to call service light/turn_on. No backend with an available connection slot that can reach address None was found

(notably without HaBleakClientWrapper in the error this time)

No idea if thats useful, but thought i would mention it. I suspect this is because here it really can’t find a slot as None is not a real working device, as opposed to the rest of the time when it actually does also communicate with the real light (as shown by the ESP32 logs) even though it also gives the no slot error.

Lastly, reading your code you showed above, you str() the address_or_ble_device variable. I’m not sure why you do that (it looks like you are trying to get the MAC address?) but when you str() the BLEDevice that find_by_address returns you get the same error I reported that includes HaBleakClientWrapper, C0:00:00:01:66:44

(NB (for people reading this later and wondering why I’m not just passing the MAC to BleakClient), that a) the overall intent of all this is to use a bleakretryconnector that requires a BLEDevice as an argument, and cannot take a MAC string, and even though I have rolled that back for now given these issues, nonetheless bleakretryconnector in turn calls BleakClient so we end up with the same problem that this refers to. As such I need to resolve this BLEDevice issue. Also b) bleak docs recommend passing BLEDevice to BleakClient and not a MAC due to known issues (see BleakClient class — bleak 0.22.2 documentation)

It looks like you might have some extra () somewhere and a tuple is being created unexpectedly.

I don’t see how. The bleak find routine returns a BLEDevice (which I have proven in debugging above is returned correctly as a BLEDevice) and that is the parameter for the BleakClient call that causes the error (that in included above)

There isn’t anywhere I can see that I could use an extra bracket could exist.

I still don’t understand how str() of a BLEDevice can work in giving you back the MAC address as it appears is the intention of the code you pasted above, though I haven’t had time to read through the code on GitHub to see how if it does work, as my work got in the way (how dare my job get in the way of my home automation hobby!)

Regards

Chris

I went through the wrapper code again, and I don’t see how this could happen unless its being passed in externally as a tuple

The subclassed str values come from pyobjc on MacOS which causes problem because the type is wrong later. If its already a BLEDevice it won’t wrap it in str.

Sorry, yes fixed it. I actually spotted it when rereading the posts above to backtrack what we had done and see if I had forgotten anything, and yes a bracket had been left in when playing with the first issue.

bledevice = (await BleakScanner.find_device_by_address("C0:00:00:01:66:44"))

I had totally forgotten I had made changes to a section for that exercise and not reverted it

Interestingly when I was passing that BLEDevice back down the stack and then printing it using logger is didn’t show that it had been enclosed as a tuple

Sorry about that, need better version control when going down debugging rabbit holes

Chris

1 Like