I'm using BluetoothLEAdvertisementWatcher
to find nearby BLE devices and it's working well. After finding them I want to connect and read/write data via GATT. But I can't figure out how to use the API after getting the BluetoothLEAdvertisement
(https://msdn.microsoft.com/de-de/library/windows/apps/windows.devices.bluetooth.genericattributeprofile).
public class Adapter
{
private readonly BluetoothLEAdvertisementWatcher _bleWatcher = new BluetoothLEAdvertisementWatcher();
public Adapter()
{
_bleWatcher.Received += BleWatcherOnReceived;
}
private void BleWatcherOnReceived(BluetoothLEAdvertisementWatcher sender, BluetoothLEAdvertisementReceivedEventArgs args)
{
// how to connect?
// I know, it's the wrong place to to this, but this is just an example
}
public void StartScanningForDevices(Guid[] serviceUuids)
{
_blewatcher.advertisementfilter.advertisement.serviceuuids.clear();
foreach (var uuid in serviceuuids)
{
_blewatcher.advertisementfilter.advertisement.serviceuuids.add(uuid);
}
_blewatcher.start();
}
}
I've found Samples that are using DeviceInformation.FindAllAsync
instead of BluetoothLEAdvertisementWatcher
but these are not working / finding any device.
UPDATE
After digging around some time, I found the following way. But unfortunately, the pairing fails. The device is just an Arduino with a BLE shield. I can definitely connect with Android and iOS. So it must be possible with UWP somehow. :/
private void BleWatcherOnReceived(BluetoothLEAdvertisementWatcher sender, BluetoothLEAdvertisementReceivedEventArgs args)
{
var dev = await BluetoothLEDevice.FromBluetoothAddressAsync(args.BluetoothAddress);
// dev.DeviceInformation.Pairing.CanPair is true
// dpr.Status is Failed
DevicePairingResult dpr = await dev.DeviceInformation.Pairing.PairAsync(DevicePairingProtectionLevel.None);
var service = await GattDeviceService.FromIdAsync(dev.DeviceInformation.Id);
}
UPDATE #2
I'm now able to discover and pair (unstable, but ok for now), but
var service = await GattDeviceService.FromIdAsync(args.Id);
throws the following Exception
System.IO.FileNotFoundException: The system cannot find the file specified. (Exception from HRESULT: 0x80070002)
I have no clue why.
UPDATE 04/17 - CREATORS UPDATE
Microsoft have just updated their Bluetooth APIs. We now have unpaired BLE device communication!
They have very little documentation up at the moment but here is the much simplified new structure:
BleWatcher = new BluetoothLEAdvertisementWatcher
{
ScanningMode = BluetoothLEScanningMode.Active
};
BleWatcher.Start();
BleWatcher.Received += async (w, btAdv) => {
var device = await BluetoothLEDevice.FromBluetoothAddressAsync(btAdv.BluetoothAddress);
Debug.WriteLine($"BLEWATCHER Found: {device.name}");
// SERVICES!!
var gatt = await device.GetGattServicesAsync();
Debug.WriteLine($"{device.Name} Services: {gatt.Services.Count}, {gatt.Status}, {gatt.ProtocolError}");
// CHARACTERISTICS!!
var characs = await gatt.Services.Single(s => s.Uuid == SAMPLESERVICEUUID).GetCharacteristicsAsync();
var charac = characs.Single(c => c.Uuid == SAMPLECHARACUUID);
await charac.WriteValueAsync(SOMEDATA);
};
Much better now. As I said there is next to no documentation at the moment, I have a weird issue where my ValueChanged callback stops being called after 30 seconds or so, though that seems to be a separate scoping issue.
UPDATE 2 - SOME WEIRDNESS
After some more playing around with the new creators update there are a few more things to consider when building BLE apps.
ValueChanged
on the charac
you may hit this issue. This is because the GattCharacteristic
is disposed of before it should be, set the characteristic as a global rather than relying on it being copied in.App.xml.cs
OnSuspended
callback to terminate your connections. Otherwise you get in a bit of a weird state where Windows seems to maintain (and keep reading!!) the BLE connection.Well it has its quirks but it works!
OLD ANSWER
Following on from Jason's correct answer about devices needing to be paired to have their services be discovered, here is some sample code to address this:
private void SetupBluetooth()
{
Watcher = new BluetoothLEAdvertisementWatcher { ScanningMode = BluetoothLEScanningMode.Active };
Watcher.Received += DeviceFound;
DeviceWatcher = DeviceInformation.CreateWatcher();
DeviceWatcher.Added += DeviceAdded;
DeviceWatcher.Updated += DeviceUpdated;
StartScanning();
}
private void StartScanning()
{
Watcher.Start();
DeviceWatcher.Start();
}
private void StopScanning()
{
Watcher.Stop();
DeviceWatcher.Stop();
}
private async void DeviceFound(BluetoothLEAdvertisementWatcher watcher, BluetoothLEAdvertisementReceivedEventArgs btAdv)
{
if (_devices.Contains(btAdv.Advertisement.LocalName))
{
await Dispatcher.RunAsync(CoreDispatcherPriority.Low, async () =>
{
Debug.WriteLine($"---------------------- {btAdv.Advertisement.LocalName} ----------------------");
Debug.WriteLine($"Advertisement Data: {btAdv.Advertisement.ServiceUuids.Count}");
var device = await BluetoothLEDevice.FromBluetoothAddressAsync(btAdv.BluetoothAddress);
var result = await device.DeviceInformation.Pairing.PairAsync(DevicePairingProtectionLevel.None);
Debug.WriteLine($"Pairing Result: {result.Status}");
Debug.WriteLine($"Connected Data: {device.GattServices.Count}");
});
}
}
private async void DeviceAdded(DeviceWatcher watcher, DeviceInformation device)
{
if (_devices.Contains(device.Name))
{
try
{
var service = await GattDeviceService.FromIdAsync(device.Id);
Debug.WriteLine("Opened Service!!");
}
catch
{
Debug.WriteLine("Failed to open service.");
}
}
}
private void DeviceUpdated(DeviceWatcher watcher, DeviceInformationUpdate update)
{
Debug.WriteLine($"Device updated: {update.Id}");
}
The key things to note here are:
UPDATE (5/5/16): The "Element Not Found" error issue seems to only happen when the bluetooth settings screen isn't open/scanning. I don't remember that being the case before 10586.218 but I haven't checked. Obviously, not every issue is fixed in the update.
UPDATE (4/29/16): The 10586.218 windows update appears to have fixed the problem of pairing with a device that has never been paired to the machine (or phone) before. The process I've outlined here and Gerard Wilkinson's sample code in his answer should work more consistently now.
If you are lucky enough to get this to work, it requires waiting a considerable amount of time for the driver to install. I've done it by having both BluetoothLEAdvertisementWatcher and a DeviceWatcher running simultaneously.
Save the DeviceInformation from the BluetoothLEDevice that you get from FromBluetoothAddressAsync() then Dispose() the BluetoothLEDevice before initiating pairing. This is important. If you don't, it won't see the Gatt Services after pairing.
Then wait for the DeviceWatcher to see the paired device. It can take minutes but you'll usually get it before the progress bar for device installation (in the Bluetooth control panel) gets to 100%. If FromIdAsync still fails, it usually means there was a driver installation error. You can unpair and then do the pairing process over again. That usually works for me.
It's very unstable, though, and it seems to be dependent on which Bluetooth chipset and driver the machine has. I often get an Element Not Found error with FromBluetoothAddress but if it gets past there, pairing usually works on the first or second try.
PairAsync and UnpairAsync also need to be posted to the UI thread. If it isn't able to pop up a blue dialog asking for authorization, you'll get exceptions. You can use Post() from a saved UI SynchronizationContext or Windows.ApplicationModel.Core.CoreApplication.MainView.Dispatcher.RunAsync() with an async delegate to do this.
I've seen multiple posts from MS employees on the forums saying FromBluetoothAddressAsync() only works for paired devices. This isn't the case but it is buggy and seems to work best if the device has been paired manually at least once in the past.
Gerard Wilkinson's answer is correct. To make life easier, I turned it into an awaitable method using Reactive Extensions (). Any comments are welcome.
So once you found the device using the BluetoothLEAdvertisementWatcher and paired with it, you can use this to enable GATTServices.
private async Task<GattDeviceService> GetGATTServiceAsync(string deviceName)
{
//devicewatcher is abused to trigger connection
var deviceWatcher = DeviceInformation.CreateWatcher(); //trick to enable GATT
var addedSource = Observable.FromEventPattern(deviceWatcher, nameof(deviceWatcher.Added))
.Select(pattern => ((DeviceInformation)pattern.EventArgs));
var updatedSource = Observable.FromEventPattern(deviceWatcher, nameof(deviceWatcher.Updated))
.Select(pattern =>
{
var update = ((DeviceInformationUpdate)pattern.EventArgs);
return Observable.FromAsync(() => DeviceInformation.CreateFromIdAsync(update.Id).AsTask());
}).Concat();
var source = addedSource.Merge(updatedSource);
source.Publish().Connect(); //make sure the event handlers are attached before starting the device watcher
deviceWatcher.Start();
var result = await source.Where(di => di.Name == deviceName) //find the relevant device
.Select(di => Observable.FromAsync(() => GattDeviceService.FromIdAsync(di.Id).AsTask())) //get all services from the device
.Concat() //necessary because of the async method in the previous statement
.Where(service => service.Uuid == SERVICE_UUID) //get the service with the right UUID
.Retry() //GattDeviceService.FromIdAsync can throw exceptions
.FirstAsync();
deviceWatcher.Stop();
return result;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With