I am attempting to write a C# app that uses the WinRT Bluetooth LE APIs (the Windows.Devices.Bluetooth namespace). The app is a Windows Classic Desktop application (WPF, not UWP). When running a version of Windows 10 prior to the Creators Update, these APIs function as expected. However, when running the Creators Update, the APIs that should send data to a Bluetooth device do not function. Specifically, the following methods return Success status codes but do not transmit any data over the Bluetooth radio (as verified using a Bluetooth traffic sniffer):
As a result, any attempt to register a ValueChanged handler for a characteristic do not function. Since the registration is never sent to the Bluetooth LE device, no notifications are received by the application.
I understand that not all UWP APIs can be used from a non-UWP app, but it is my hope that someone has successfully developed a BLE app in such a configuration (or could at least confirm it is impossible now). I am able to connect and read data from and write data to a BLE device prior to the Creators Update, and it is only in this latest version of Windows 10 the problem mentioned above manifests. (Note: The Async APIs used in the sample code were added in the Creators Update. The prior version of our application used the older BLE APIs, but they also do not function when running the Creators Update.)
Specifically, my question is: given the following project reference list and sample code, is there anything I can try to get working Bluetooth LE connectivity on Windows 10 running the Creators Update from a non-UWP application? Note that the obvious answer of "convert the application to a UWP app" does not work for us, because we interact with other hardware and files in a way that is not possible inside the UWP sandbox.
The project was configured with the following references:
Following is a very stripped-down version of the Bluetooth code from my application. Note that a lot of error handling and such has been removed for clarity, but it should give a general idea of what I'm trying to do:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Devices.Bluetooth;
using Windows.Devices.Bluetooth.GenericAttributeProfile;
using Windows.Devices.Enumeration;
using Windows.Foundation;
using Windows.Storage.Streams;
using System.Threading;
namespace BLEMinimumApp
{
class Program
{
private List<string> foundDevices = new List<string>(5);
static void Main(string[] args)
{
new Program().Execute();
}
private void Execute()
{
Console.WriteLine("Starting device watcher...");
string[] requestedProperties = { "System.Devices.Aep.IsConnected" };
String query = "";
//query for Bluetooth LE devices
query += "(System.Devices.Aep.ProtocolId:=\"{bb7bb05e-5972-42b5-94fc-76eaa7084d49}\")";
//query for devices with controllers' name
query += " AND (System.ItemNameDisplay:=\"GPLeft\" OR System.ItemNameDisplay:=\"GPRight\")";
var deviceWatcher = DeviceInformation.CreateWatcher(query, requestedProperties, DeviceInformationKind.AssociationEndpoint);
deviceWatcher.Added += DeviceWatcher_OnAdded;
deviceWatcher.Start();
Console.ReadLine();
}
private async void DeviceWatcher_OnAdded(DeviceWatcher sender, DeviceInformation deviceInfo)
{
lock (foundDevices)
{
if (foundDevices.Contains(deviceInfo.Name))
{
return;
}
foundDevices.Add(deviceInfo.Name);
}
Console.WriteLine($"[{deviceInfo.Name}] DeviceWatcher_OnAdded...");
await ConnectTo(deviceInfo);
}
private async Task ConnectTo(DeviceInformation deviceInfo)
{
try
{
// get the device
BluetoothLEDevice device = await BluetoothLEDevice.FromIdAsync(deviceInfo.Id);
Console.WriteLine($"[{device.Name}] Device found: connectionStatus={device?.ConnectionStatus}");
// get the GATT service
Thread.Sleep(150);
Console.WriteLine($"[{device.Name}] Get GATT Services");
var gattServicesResult = await device.GetGattServicesForUuidAsync(new Guid("<GUID REMOVED FOR SO POST"));
Console.WriteLine($"[{device.Name}] GATT services result: status={gattServicesResult?.Status}, count={gattServicesResult?.Services?.Count}, cx={device.ConnectionStatus}");
if (gattServicesResult == null
|| gattServicesResult.Status != GattCommunicationStatus.Success
|| gattServicesResult.Services == null
|| gattServicesResult.Services?.Count < 1)
{
Console.WriteLine($"[{device.Name}] Failed to find GATT service.");
return;
}
var service = gattServicesResult.Services[0];
Console.WriteLine($"[{device?.Name}] GATT service found: gattDeviceService={service.Uuid}");
// get the GATT characteristic
Thread.Sleep(150);
Console.WriteLine($"[{device.Name}] Get GATT characteristics");
var gattCharacteristicsResult = await service.GetCharacteristicsForUuidAsync(new Guid("<GUID REMOVED FOR SO POST>"));
Console.WriteLine($"[{device.Name}] GATT Characteristics result: status={gattCharacteristicsResult?.Status}, count={gattCharacteristicsResult?.Characteristics?.Count}, cx={device.ConnectionStatus}");
if (gattCharacteristicsResult == null
|| gattCharacteristicsResult.Status != GattCommunicationStatus.Success
|| gattCharacteristicsResult.Characteristics == null
|| gattCharacteristicsResult.Characteristics?.Count < 1)
{
Console.WriteLine($"[{device.Name}] Failed to find GATT characteristic.");
return;
}
var characteristic = gattCharacteristicsResult.Characteristics[0];
// register for notifications
Thread.Sleep(150);
characteristic.ValueChanged += (sender, args) =>
{
Console.WriteLine($"[{device.Name}] Received notification containing {args.CharacteristicValue.Length} bytes");
};
Console.WriteLine($"[{device.Name}] Writing CCCD...");
GattWriteResult result =
await characteristic.WriteClientCharacteristicConfigurationDescriptorWithResultAsync(GattClientCharacteristicConfigurationDescriptorValue.Notify);
Console.WriteLine($"[{device?.Name}] Characteristics write result: status={result.Status}, protocolError={result.ProtocolError}");
// send configuration to device
await SendConfiguration(device, characteristic);
}
catch (Exception ex) when((uint) ex.HResult == 0x800710df)
{
Console.WriteLine("bluetooth error 1");
// ERROR_DEVICE_NOT_AVAILABLE because the Bluetooth radio is not on.
}
}
private async Task SendConfiguration(BluetoothLEDevice device, GattCharacteristic characteristic)
{
if (characteristic != null)
{
var writer = new DataWriter();
// CONFIGURATION REMOVED, but this code writes device-specific bytes to the DataWriter
await SendMessage(device, characteristic, writer.DetachBuffer());
}
}
private async Task SendMessage(BluetoothLEDevice device, GattCharacteristic characteristic, IBuffer message)
{
if (characteristic != null && device.ConnectionStatus.Equals(BluetoothConnectionStatus.Connected) && message != null)
{
Console.WriteLine($"[{device.Name}] Sending message...");
GattCommunicationStatus result = await characteristic.WriteValueAsync(message);
Console.WriteLine($"[{device.Name}] Result: {result}");
}
}
}
}
COM Security might be preventing your app from receiving notifications. Please refer following thread for a resolution. I suggest to use registry hack.
I had a similar issue and with above registry change my app receives few notifications and stops with no apparent reason whatsoever. Wasted lot of time on this issue and waiting for a patch from Microsoft.
The latest update corrects this. You can also workaround the issue using the instruction from Matt Beaver on the link referenced by the other poster.
Basically either:
User contributions licensed under CC BY-SA 3.0