如何在 Windows 10 Creators Update 上注册来自 WPF 应用程序 运行 的 BLE 通知?
How to register for BLE notifcations from a WPF app running on Windows 10 Creators Update?
我正在尝试编写一个使用 WinRT 蓝牙 LE API(Windows.Devices.Bluetooth 命名空间)的 C# 应用程序。该应用程序是 Windows 经典桌面应用程序(WPF,而非 UWP)。当 运行 Windows 10 版本早于创意者更新时,这些 API 将按预期运行。但是,当 运行 Creators Update 时,应该向蓝牙设备发送数据的 API 不起作用。具体来说,以下方法 return 成功状态代码但不通过蓝牙无线电传输任何数据(使用蓝牙流量嗅探器验证):
- GattCharacteristic.WriteClientCharacteristicConfigurationDescriptorWithResultAsync
- GattCharacteristic.WriteValueAsync
因此,任何为特征注册 ValueChanged 处理程序的尝试都不起作用。由于注册永远不会发送到蓝牙 LE 设备,因此应用程序不会收到任何通知。
我知道并非所有 UWP API 都可以在非 UWP 应用程序中使用,但我希望有人已经在这种配置下成功开发了 BLE 应用程序(或者至少现在可以确认这是不可能的) .在 Creators Update 之前,我能够连接 BLE 设备并从其读取数据和向其写入数据,并且只有在 Windows 10 的最新版本中才会出现上述问题。 (注意:示例代码中使用的异步 API 是在创意者更新中添加的。我们应用程序的先前版本使用较旧的 BLE API,但它们在 运行 创意者更新时也不起作用。)
具体来说,我的问题是:鉴于以下项目参考列表和示例代码,有什么我可以尝试在 Windows 10 运行 上实现蓝牙 LE 连接的方法吗?来自非 UWP 应用程序的创作者更新? 请注意,"convert the application to a UWP app" 的明显答案对我们不起作用,因为我们以 UWP 内部不可能的方式与其他硬件和文件交互沙箱。
项目配置了以下引用:
- System.Runtime.WindowsRuntime,位于:C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework.NETCore\v4.5\System.Runtime.WindowsRuntime.dll
- Windows,位于:C:\Program Files (x86)\Windows Kits\UnionMetadata\Facade\Windows.WinMD
- Windows.Foundation.FoundationContract,位于:C:\Program Files (x86)\Windows Kits\References.0.15063.0\Windows.Foundation.FoundationContract。 0.0.0\Windows.Foundation.FoundationContract.winmd
- Windows.Foundation.UniversalApiContract,位于:C:\Program Files (x86)\Windows Kits\References.0.15063.0\Windows.Foundation.UniversalApiContract。 0.0.0\Windows.Foundation.UniversalApiContract.winmd
以下是我的应用程序中蓝牙代码的精简版本。请注意,为清楚起见,已删除许多错误处理等,但它应该给出我正在尝试做的事情的一般概念:
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 安全性可能会阻止您的应用程序接收通知。请参考以下线程以获得解决方案。我建议使用注册表 hack。
我遇到了类似的问题,通过上述注册表更改,我的应用收到的通知很少,并且无缘无故地停止了。
在这个问题上浪费了很多时间,等待微软的补丁。
最新更新修正了这个问题。您还可以使用 Matt Beaver 在另一位发帖者引用的 link 上的说明解决该问题。
基本上是:
- 为您的应用程序指定 AppId。
- 在您的申请中致电 CoInitializeSecurity 让我们回电
我正在尝试编写一个使用 WinRT 蓝牙 LE API(Windows.Devices.Bluetooth 命名空间)的 C# 应用程序。该应用程序是 Windows 经典桌面应用程序(WPF,而非 UWP)。当 运行 Windows 10 版本早于创意者更新时,这些 API 将按预期运行。但是,当 运行 Creators Update 时,应该向蓝牙设备发送数据的 API 不起作用。具体来说,以下方法 return 成功状态代码但不通过蓝牙无线电传输任何数据(使用蓝牙流量嗅探器验证):
- GattCharacteristic.WriteClientCharacteristicConfigurationDescriptorWithResultAsync
- GattCharacteristic.WriteValueAsync
因此,任何为特征注册 ValueChanged 处理程序的尝试都不起作用。由于注册永远不会发送到蓝牙 LE 设备,因此应用程序不会收到任何通知。
我知道并非所有 UWP API 都可以在非 UWP 应用程序中使用,但我希望有人已经在这种配置下成功开发了 BLE 应用程序(或者至少现在可以确认这是不可能的) .在 Creators Update 之前,我能够连接 BLE 设备并从其读取数据和向其写入数据,并且只有在 Windows 10 的最新版本中才会出现上述问题。 (注意:示例代码中使用的异步 API 是在创意者更新中添加的。我们应用程序的先前版本使用较旧的 BLE API,但它们在 运行 创意者更新时也不起作用。)
具体来说,我的问题是:鉴于以下项目参考列表和示例代码,有什么我可以尝试在 Windows 10 运行 上实现蓝牙 LE 连接的方法吗?来自非 UWP 应用程序的创作者更新? 请注意,"convert the application to a UWP app" 的明显答案对我们不起作用,因为我们以 UWP 内部不可能的方式与其他硬件和文件交互沙箱。
项目配置了以下引用:
- System.Runtime.WindowsRuntime,位于:C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework.NETCore\v4.5\System.Runtime.WindowsRuntime.dll
- Windows,位于:C:\Program Files (x86)\Windows Kits\UnionMetadata\Facade\Windows.WinMD
- Windows.Foundation.FoundationContract,位于:C:\Program Files (x86)\Windows Kits\References.0.15063.0\Windows.Foundation.FoundationContract。 0.0.0\Windows.Foundation.FoundationContract.winmd
- Windows.Foundation.UniversalApiContract,位于:C:\Program Files (x86)\Windows Kits\References.0.15063.0\Windows.Foundation.UniversalApiContract。 0.0.0\Windows.Foundation.UniversalApiContract.winmd
以下是我的应用程序中蓝牙代码的精简版本。请注意,为清楚起见,已删除许多错误处理等,但它应该给出我正在尝试做的事情的一般概念:
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 安全性可能会阻止您的应用程序接收通知。请参考以下线程以获得解决方案。我建议使用注册表 hack。
我遇到了类似的问题,通过上述注册表更改,我的应用收到的通知很少,并且无缘无故地停止了。 在这个问题上浪费了很多时间,等待微软的补丁。
最新更新修正了这个问题。您还可以使用 Matt Beaver 在另一位发帖者引用的 link 上的说明解决该问题。
基本上是:
- 为您的应用程序指定 AppId。
- 在您的申请中致电 CoInitializeSecurity 让我们回电