vixez
vixez

Reputation: 857

FromBluetoothAddressAsync throws 'System.IO.FileNotFoundException' in mscorlib.ni.dll

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!

Screenshots: When it fails

When it works

Upvotes: 0

Views: 1398

Answers (2)

Carter
Carter

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

Emil
Emil

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

Related Questions