2

I am using C++/winRT UWP to discover and connect to Bluetooth Low Energy devices. I am using the advertisment watcher to look for advertisements from devices I can support. This works.

Then I pick one to connect to. The connection procedure is a little weird by my way of thinking but according to the microsoft docs one Calls this FromBluetoothAddressAsync() with the BluetoothAddress and two things happen; one gets the BluetoothLEDevice AND a connection attempt is made. One needs to register a handler for the connection status changed event BUT you can't do that until you get the BluetoothLEDevice.

Is there a timing issue causing the exception? Has the connection already happened BEFORE I get the BluetoothLEDevice object? Below is the code and below that is the log:

void BtleHandler::connectToDevice(BluetoothLEAdvertisementReceivedEventArgs eventArgs)
{
    OutputDebugStringA("Connect to device called\n");
    // My God this async stuff took me a while to figure out! See https://msdn.microsoft.com/en-us/magazine/mt846728.aspx

    IAsyncOperation<Windows::Devices::Bluetooth::BluetoothLEDevice> async =  // assuming the address type is how I am to behave ..
     BluetoothLEDevice::FromBluetoothAddressAsync(eventArgs.BluetoothAddress(), BluetoothAddressType::Random);
    bluetoothLEDevice = async.get();
    OutputDebugStringA("BluetoothLEDevice returned\n");
    bluetoothLEDevice.ConnectionStatusChanged({ this, &BtleHandler::onConnectionStatusChanged });

    // This method not only gives you the device but it also initiates a connection
}

The above code generates the following log:

New advertisment/scanResponse with UUID 00001809-0000-1000-8000-00805F9B34FB
New ad/scanResponse with name Philips ear thermometer and UUID 00001809-0000-1000-8000-00805F9B34FB
Connect to device called 
ERROR here--> onecoreuap\drivers\wdm\bluetooth\user\winrt\common\bluetoothutilities.cpp(509)\Windows.Devices.Bluetooth.dll!03BEFDD6: (caller: 03BFB977) ReturnHr(1) tid(144) 80070490 Element not found.
ERROR here--> onecoreuap\drivers\wdm\bluetooth\user\winrt\device\bluetoothledevice.cpp(428)\Windows.Devices.Bluetooth.dll!03BFB9B7: (caller: 03BFAF01) ReturnHr(2) tid(144) 80070490 Element not found.
BluetoothLEDevice returned
Exception thrown at 0x0F5CDF2F (WindowsBluetoothAdapter.dll) in BtleScannerTest.exe: 0xC0000005: Access violation reading location 0x00000000.

It sure looks like there is a timing issue. But if it is, I have no idea how to resolve it. I cannot register for the event if I don't have a BluetoothLEDevice object! I cannot figure out a way to get the BluetoothLEDevice object without invoking a connection.

================================ UPDATE =============================

Changed the methods to IAsyncAction and used co_await as suggested by @IInspectable. No difference. The problem is clearly that the registered handler is out of scope or something is wrong with it. I tried a get_strong() instead of a 'this' in the registration, but the compiler would not accept it (said identifier 'get_strong()' is undefined). However, if I commented out the registration, no exception is thrown but I still get these log messages

onecoreuap\drivers\wdm\bluetooth\user\winrt\common\bluetoothutilities.cpp(509)\Windows.Devices.Bluetooth.dll!0F27FDD6: (caller: 0F28B977) ReturnHr(3) tid(253c) 80070490 Element not found.
onecoreuap\drivers\wdm\bluetooth\user\winrt\device\bluetoothledevice.cpp(428)\Windows.Devices.Bluetooth.dll!0F28B9B7: (caller: 0F28AF01) ReturnHr(4) tid(253c) 80070490 Element not found.

But the program continues to run an I continue to discover and connect. But since I can't get the connection event it is kind of useless at this stage.

Brian Reinhold
  • 2,313
  • 3
  • 27
  • 46
  • You might want to take a look at this, which worked for me: https://stackoverflow.com/questions/39602117/uwp-ble-device-pairing. Also, I've noticed actual connection in Windows occurs when you Pair and actually try to access some charactersitics, not when you get the `BluetoothLEDevice` instance. – bavaza Apr 21 '19 at 07:36
  • The devices I am using are not all pairable. I need to implement this process like I do on the Android; when the device tells me I need to pair, I will. All devices will do that by spec. Whether Windows BTLE implementation follows the spec, that remains to be seen. – Brian Reinhold Apr 22 '19 at 10:09
  • did you try adding the device from Windows settings before running the app? I've ran into issues trying to pair problematically, and at least some of them were resolved if I paired manually. – bavaza Apr 22 '19 at 14:17
  • @bavaza if I have to have the user go into the Windows settings menu to pair a device OR if the user needs to work with an unpairable device, I cannot use the Windows BLE API. In my opinion, that is seriously broken. The clientele here are often technology-challenged elderly folks. Any pairing MUST be done automatically; the toughest situation is a possible need to handle passkeys or confirmations. That is part of the spec and unavoidable. – Brian Reinhold Apr 22 '19 at 15:00
  • I was under the impression, that `get()` would re-throw any exceptions on the calling thread. Apparently, this is not the case. Using your initial synchronous implementation try evaluating the following properties, right after `get()` returns: `auto status{ async.Status() };` and `auto error_code{ async.ErrorCode() };`. This *might* help you diagnose, what caused the issue, although I believe the error code will just refer to the generic WinRT error. – IInspectable Apr 23 '19 at 10:53
  • @IInspectable I am not sure how that will solve the fact that the returned BluetoothLEDevice object is NULL. If I only register for the connection event if the object is non NULL, then there is no crash. I have also taken a sniff over the airwaves and there is no connection attempt by the central. So the method FromBluetoothAddressAsync is not even trying to connect (in addition to the fact I get no BluetoothLEDevice object). – Brian Reinhold Apr 23 '19 at 14:40
  • This wasn't meant to be a solution to your problem. It's just a one thing to try to figure out, *why* things failed. I'm assuming that `status` will be `AsyncStatus::Error` in case the object returned is empty. In that case, the `error_code` *may* hold a value, allowing you to better understand, *what* went wrong. I'm not sure how much additional error information actually crosses the ABI, but it's still a first step to analyze the issue. – IInspectable Apr 23 '19 at 14:57
  • @IInspectable I did a debug and the Status value is 0 (started). I tried it using the async.get() and the co_await. Both behave the same but I do not know how to access the status when I use the co_await. – Brian Reinhold Apr 23 '19 at 19:39

2 Answers2

1

I hate my answer. But after asynching and co-routining everything under the sun, the problem is unsolvable by me:

This method

bluetoothLEDevice = co_await BluetoothLEDevice::FromBluetoothAddressAsync(eventArgs.BluetoothAddress(), BluetoothAddressType::Random);

returns NULL. That should not happen and there is not much I can do about it. I read that as a broken BLE API.

A BTLE Central should be able to do as follows

  • Discover a device if new then:
  • If user selects connect, connect to the device
  • perform service discovery
  • read/write/enable characteristics as needed
  • handle indications/notifications

If at any time the peripheral sends a security request or insufficient authentication error, start pairing

repeat the action that caused the insufficient authentication.

On disconnect, save the paired and bonded state if the device is pairable.

On rediscovery of the device, if unpaired (not a pairable device)

  • repeat above

If paired and bonded

  • start encryption
  • work with the device; no need to re-enable or do service discovery

========================= MORE INFO ===================================

This is what the log shows when the method is called

Connect to device called
onecoreuap\drivers\wdm\bluetooth\user\winrt\common\bluetoothutilities.cpp(509)\Windows.Devices.Bluetooth.dll!0496FDD6: (caller: 0497B977) ReturnHr(1) tid(3b1c) 80070490 Element not found.
onecoreuap\drivers\wdm\bluetooth\user\winrt\device\bluetoothledevice.cpp(428)\Windows.Devices.Bluetooth.dll!0497B9B7: (caller: 0497AF01) ReturnHr(2) tid(3b1c) 80070490 Element not found.
BluetoothLEDevice returned is NULL. Can't register

Since the BluetoothLEDevice is NULL, I do not attempt to register.

================= MORE INFO ===================

I should also add that taking an over-the-air sniff reveals that there is never a connection event. Though the method is supposed to initiate a connection as well as return the BluetoothLEDevice object, it ends up doing neither. My guess is that the method requires more pre-use setup of the system that only the DeviceWatcher does. The AdvertisementWatcher probably does not.

Brian Reinhold
  • 2,313
  • 3
  • 27
  • 46
-1

In BLE you always have to wait for every operation to complete.
I am not an expert in C++, but in C# the async connection procedure returns a bool if it was successful.
In C++ the IAsyncOperation does not have a return type, so there is no way to know if the connection procedure was successful or completed.
You will have to await the IAsyncOperation and make sure that you have a BluetoothLEDevice object, before you attach the event handler.
To await an IAsyncOperation there is a question/answer on how to await anIAsyncOperation: How to wait for an IAsyncAction? How to wait for an IAsyncAction?

GrooverFromHolland
  • 971
  • 1
  • 11
  • 18
  • 1
    The OP *is* waiting for `FromBluetoothAddressAsync` to complete, there's a `get()` call on the following line. That's a synchronous wait. The `IAsyncOperation` does not have a return type, true, it *is* the return type. And it's a class template, too, so the call to `get()` returns an object of type `BluetoothLEDevice`. If the code reaches the line after the `get()` call, you know that it has run to completion and was successful. Also, the Q&A you linked to uses C++/CX, not C++/WinRT. Using C++/WinRT, you can use C++20 coroutines to await asynchronous operations, almost like `await` in C#. – IInspectable Apr 20 '19 at 13:25
  • @IInspectable is it possible, then, that due to the wait for completion to get the BluetoothLEDevice object that I do not register a handler in time to handle the connection event? – Brian Reinhold Apr 20 '19 at 14:06
  • I don't know, what's going wrong here, although I would certainly make the event handler asynchronous, too, by changing its return type from `void` to `IAsyncAction`, and replacing the synchronous wait with a `co_await` expression (`bluetoothLEDevice = co_await async;`). You'll have to enable coroutine support in the compiler (using the `/await` switch), and `#include ` if you haven't done so already. You can also consider using [trace-points](https://blogs.msdn.microsoft.com/abhinaba/2007/06/21/using-trace-points-in-visual-studio/) in place of `OutputDebugString`. – IInspectable Apr 20 '19 at 14:54
  • @IInspectable I have tried EVERYTHING to get coroutines working and have failed miserably. I tried with something simple like getting the BluetoothAdapter. I followed every example combination given by Kenny Kerr and though I got several combinations to build, in all cases the co_await was just ignored (with or without I got the same result ; an ASSERT exception because of waiting on a UI thread). What I did NOT know was the /await switch (clearly I had the header). I did not see that switch anywhere in the articles or docs. Maybe give that a try ... spent 7 hrs yesterday getting nowhere. – Brian Reinhold Apr 21 '19 at 13:09
  • 1
    @IInspectable I added the /await switch (I think) to no avail. I still get the same error: WINRT_ASSERT(!is_sta()); due to blocking on a UI thread. My co_await is ignored: bluetoothAdapter = co_await BluetoothAdapter::GetDefaultAsync(); in the method I call IAsyncOperation WindowsBluetoothAdapter::GetBluetoothAdapter() – Brian Reinhold Apr 21 '19 at 13:19
  • 1
    That's not enough information to comment on what's causing issues. Regardless, coroutines work, I have been using them for... forever. You should probably ask a new question, if you are having trouble getting them to work. – IInspectable Apr 21 '19 at 13:38