Reputation: 857
I'm developing an app that connects ta a BLE beacon, for this I use the BluetoothLEAdvertisementWatcher API. When I receive an advertisement I want to connect to the device to read the GATT characteristics.
So I start a BLEwatcher
BluetoothLEAdvertisementWatcher watcher;
watcher.Received += OnAdvertisementReceived;
watcher.Stopped += OnAdvertisementWatcherStopped;
watcher.Start();
Then I try to access the device
private async void OnAdvertisementReceived(BluetoothLEAdvertisementWatcher watcher, BluetoothLEAdvertisementReceivedEventArgs eventArgs)
{
var address = eventArgs.BluetoothAddress;
BluetoothLEDevice device = await BluetoothLEDevice.FromBluetoothAddressAsync(eventArgs.BluetoothAddress);
Debug.WriteLine(device.Name + " - " + device.DeviceId);
....
This fails with (at FromBluetoothAddressAsync line)
An exception of type 'System.IO.FileNotFoundException' occurred in mscorlib.ni.dll but was not handled in user code
Additional information: The system cannot find the file specified. (Exception from HRESULT: 0x80070002)
The funny thing is: if I open the system's bluetooth devices window it works fine!
So when I open the bluetooth devices window and run the app the error is not thrown, when I close the bluetooth devices window it throws the error.
Note that it always throws this error in a background task.
Apparently it does work on build 10.0.10586.218. I found this online from someone with the same issue:
LUMIA 950, Windows 10, 1511, 10.0.14332.1001
Exception thrown on FromIdAsync(): 'System.IO.FileNotFoundException' in mscorlib.ni.dll
LUMIA 730, Windows 10, 1511, 10.0.10586.218
Exception thrown on FindAllAsync(): 'System.ArgumentException'
LUMIA 920, Windows 10, 1511, 10.0.10586.218
No Error!
Upvotes: 0
Views: 1398
Reputation: 3093
The Problem
This error is caused by calling FromBluetoothAddressAsync
in a non-UI thread. Windows 10 requires user consent when an app tries to access a Bluetooth peripheral. When FromBluetoothAddressAsync
is called, Windows tries to create a consent dialogue. Because this is not a UI thread, the consent dialogue cannot appear and an exception is thrown.
Solution
First, you should make the BluetoothLEAdvertisementWatcher
more specific to the peripheral that you're searching for. If you don't, then your app will see every single Bluetooth LE device and prompt for user consent on every one- a terrible user experience.
There are multiple ways to filter advertisements. You can do it manually in your OnAdvertisementReceived
method using properties from eventArgs
or you can set filters in the BluetoothLEAdvertisementWatcher
itself. For example, to find Bluetooth LE devices that have a heart rate monitor, do:
watcher.AdvertisementFilter.Advertisement.ServiceUuids.Add(GattServiceUuids.HeartRate);
To make a call on a UI thread, you need to use a dispatcher. You can set up a dispatcher using the following code:
// Variable in your class
CoreDispatcher dispatcher;
// Init for your class
public MainPage() {
...
// Init the dispatcher, this must be done on the main thread
this.dispatcher = CoreWindow.GetForCurrentThread().Dispatcher;
...
}
Then, when you receive an advertisement, you can do your own filtering then tell the dispatcher to connect to the Bluetooth LE device on the main thread.
private async void ConnectToBTDevice(BluetoothLEAdvertisementReceivedEventArgs eventArgs) {
// This will cause a consent prompt if the user hasn't already consented
var btDevice = await BluetoothLEDevice.FromBluetoothAddressAsync(eventArgs.BluetoothAddress);
}
private bool Filter(BluetoothLEAdvertisementReceivedEventArgs eventArgs) {
// Return true if the advertisement has the name we're looking for
return eventArgs.Advertisement.LocalName != null && eventArgs.Advertisement.LocalName.Equals("Peripheral Name");
}
private void OnAdvertisementReceived(BluetoothLEAdvertisementWatcher watcher, BluetoothLEAdvertisementReceivedEventArgs eventArgs) {
// Do our own filtering (for example)
if(!this.Filter(eventArgs)) return;
// Connect to the Bluetooth device in the UI (main) thread
this.dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => this.ConnectToBTDevice(eventArgs));
}
Upvotes: 2
Reputation: 18482
Aren't the BLE Windows APIs lovely and the nice (un)documented exceptions they give you?
You shouldn't use AdvertisementWatcher if you have the intent to pair to a device I guess. Instead use a DeviceInformation watcher with a specific selector for BLE-devices that contain both paired and non-paired devices. That will give a ready-to-use DeviceInformation object you can pair with.
The sample code for that can be seen at https://github.com/Microsoft/Windows-universal-samples/tree/master/Samples/DeviceEnumerationAndPairing, scenario number 8.
Upvotes: 0