Windows UWP 发现后连接到 BLE 设备
Windows UWP connect to BLE device after discovery
我正在使用 BluetoothLEAdvertisementWatcher
查找附近的 BLE 设备并且运行良好。找到它们后,我想通过 GATT 连接和 read/write 数据。但是在获得 BluetoothLEAdvertisement
(https://msdn.microsoft.com/de-de/library/windows/apps/windows.devices.bluetooth.genericattributeprofile).
之后,我不知道如何使用 API
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();
}
}
我找到了使用 DeviceInformation.FindAllAsync
而不是 BluetoothLEAdvertisementWatcher
的样本,但这些样本无法正常工作/找不到任何设备。
更新
经过一段时间的挖掘,我发现了以下方法。但不幸的是,配对失败了。该设备只是一个带有 BLE 扩展板的 Arduino。我绝对可以连接 Android 和 iOS。因此,UWP 一定有可能以某种方式实现。 :/
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);
}
更新#2
我现在可以发现并配对(不稳定,但目前还可以),但是
var service = await GattDeviceService.FromIdAsync(args.Id);
抛出以下异常
System.IO.FileNotFoundException:
The system cannot find the file specified. (Exception from HRESULT: 0x80070002)
我不知道为什么。
基本上你的答案部分包含在问题中。本质上,您使用 BluetoothLEAdvertisementWatcher 仅用于查找设备,基本上它们就像信标一样工作。
并且您不应该仅使用此 API 来连接这些设备。要连接设备,您必须使用 DeviceInformation.FindAllAsync(),并且要让它向您显示任何设备,您需要先将它们配对。
无论如何,如果您有兴趣从某些特定的 BLE 特性中获取数据,您可以尝试使用 GattCharacteristicNotificationTrigger, for full example & a bit of additional explanations see my blog。
更新(5/5/16):"Element Not Found" 错误问题似乎只在蓝牙设置屏幕不是 open/scanning 时才会发生。我不记得在 10586.218 之前是这种情况,但我没有检查过。显然,并非所有问题都在更新中得到解决。
更新 (4/29/16):10586.218 windows 更新似乎解决了与从未配对过的设备配对的问题机(或 phone)之前。我在这里概述的过程和 Gerard Wilkinson 在他的回答中的示例代码现在应该更一致地工作了。
如果您足够幸运能够使用它,则需要等待相当长的时间才能安装驱动程序。我通过同时拥有 BluetoothLEAdvertisementWatcher 和 DeviceWatcher 运行 来做到这一点。
在启动配对之前,保存从 FromBluetoothAddressAsync() 获得的 BluetoothLEDevice 的 DeviceInformation,然后 Dispose() BluetoothLEDevice。这个很重要。如果不这样做,配对后它将看不到 Gatt 服务。
然后等待 DeviceWatcher 看到配对的设备。这可能需要几分钟时间,但通常会在设备安装进度条(在蓝牙控制面板中)达到 100% 之前完成。如果 FromIdAsync 仍然失败,通常意味着存在驱动程序安装错误。您可以取消配对,然后重新进行配对过程。这通常对我有用。
虽然很不稳定,但似乎取决于机器的蓝牙芯片组和驱动程序。我经常在使用 FromBluetoothAddress 时遇到“未找到元素”错误,但如果它过去了,配对通常会在第一次或第二次尝试时起作用。
PairAsync 和 UnpairAsync 也需要发布到 UI 线程。如果它无法弹出一个蓝色对话框请求授权,您将获得异常。您可以使用保存的 UI SynchronizationContext 中的 Post() 或带有异步委托的 Windows.ApplicationModel.Core.CoreApplication.MainView.Dispatcher.RunAsync() 来执行此操作。
我在论坛上看到 MS 员工的多篇帖子说 FromBluetoothAddressAsync() 仅适用于配对设备。情况并非如此,但它存在错误,并且如果设备过去至少手动配对过一次,则效果最好。
更新 04/17 - 创作者更新
Microsoft 刚刚更新了他们的蓝牙 API。我们现在有未配对的 BLE 设备通信!
目前他们的文档很少,但这是简化得多的新结构:
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);
};
现在好多了。正如我所说,目前几乎没有文档,我有一个奇怪的问题,我的 ValueChanged 回调在 30 秒左右后停止被调用,尽管这似乎是一个单独的范围问题。
更新 2 - 有些奇怪
在尝试新的创作者更新后,在构建 BLE 应用程序时需要考虑更多事项。
- 您不再需要 运行 UI 线程上的蓝牙内容。如果没有配对,BLE 似乎没有任何权限 windows,因此不再需要在 UI 线程上 运行。
- 您可能会发现您的应用程序在一段时间后停止从设备接收更新。这是一个范围界定问题,其中不应该处理对象。在上面的代码中,如果您在
charac
上收听 ValueChanged
,您可能会遇到此问题。这是因为 GattCharacteristic
在它应该被处理之前就被处理掉了,将特性设置为全局特性而不是依赖于它被复制进来。
- 断开连接似乎有点问题。退出应用程序不会终止连接。因此,请确保您使用
App.xml.cs
OnSuspended
回调来终止您的连接。否则你会进入一种奇怪的状态,其中 Windows 似乎保持(并继续阅读!!)BLE 连接。
嗯,它有它的怪癖,但它确实有效!
旧答案
根据 Jason 关于设备需要配对才能发现其服务的正确答案,下面是一些解决此问题的示例代码:
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}");
}
这里要注意的关键事项是:
- DeviceWatcher 需要添加和更新的属性设置才能工作。
- 您需要捕获在尝试查询未配对或尚未准备好的服务时发生的异常 FileNotFound。
杰拉德威尔金森的回答是正确的。为了让生活更轻松,我使用 Reactive Extensions() 将它变成了一个可等待的方法。欢迎任何评论。
因此,一旦您找到使用 BluetoothLEAdvertisementWatcher 的设备并与之配对,您就可以使用它来启用 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;
}
我正在使用 BluetoothLEAdvertisementWatcher
查找附近的 BLE 设备并且运行良好。找到它们后,我想通过 GATT 连接和 read/write 数据。但是在获得 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();
}
}
我找到了使用 DeviceInformation.FindAllAsync
而不是 BluetoothLEAdvertisementWatcher
的样本,但这些样本无法正常工作/找不到任何设备。
更新
经过一段时间的挖掘,我发现了以下方法。但不幸的是,配对失败了。该设备只是一个带有 BLE 扩展板的 Arduino。我绝对可以连接 Android 和 iOS。因此,UWP 一定有可能以某种方式实现。 :/
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);
}
更新#2
我现在可以发现并配对(不稳定,但目前还可以),但是
var service = await GattDeviceService.FromIdAsync(args.Id);
抛出以下异常
System.IO.FileNotFoundException: The system cannot find the file specified. (Exception from HRESULT: 0x80070002)
我不知道为什么。
基本上你的答案部分包含在问题中。本质上,您使用 BluetoothLEAdvertisementWatcher 仅用于查找设备,基本上它们就像信标一样工作。
并且您不应该仅使用此 API 来连接这些设备。要连接设备,您必须使用 DeviceInformation.FindAllAsync(),并且要让它向您显示任何设备,您需要先将它们配对。
无论如何,如果您有兴趣从某些特定的 BLE 特性中获取数据,您可以尝试使用 GattCharacteristicNotificationTrigger, for full example & a bit of additional explanations see my blog。
更新(5/5/16):"Element Not Found" 错误问题似乎只在蓝牙设置屏幕不是 open/scanning 时才会发生。我不记得在 10586.218 之前是这种情况,但我没有检查过。显然,并非所有问题都在更新中得到解决。
更新 (4/29/16):10586.218 windows 更新似乎解决了与从未配对过的设备配对的问题机(或 phone)之前。我在这里概述的过程和 Gerard Wilkinson 在他的回答中的示例代码现在应该更一致地工作了。
如果您足够幸运能够使用它,则需要等待相当长的时间才能安装驱动程序。我通过同时拥有 BluetoothLEAdvertisementWatcher 和 DeviceWatcher 运行 来做到这一点。
在启动配对之前,保存从 FromBluetoothAddressAsync() 获得的 BluetoothLEDevice 的 DeviceInformation,然后 Dispose() BluetoothLEDevice。这个很重要。如果不这样做,配对后它将看不到 Gatt 服务。
然后等待 DeviceWatcher 看到配对的设备。这可能需要几分钟时间,但通常会在设备安装进度条(在蓝牙控制面板中)达到 100% 之前完成。如果 FromIdAsync 仍然失败,通常意味着存在驱动程序安装错误。您可以取消配对,然后重新进行配对过程。这通常对我有用。
虽然很不稳定,但似乎取决于机器的蓝牙芯片组和驱动程序。我经常在使用 FromBluetoothAddress 时遇到“未找到元素”错误,但如果它过去了,配对通常会在第一次或第二次尝试时起作用。
PairAsync 和 UnpairAsync 也需要发布到 UI 线程。如果它无法弹出一个蓝色对话框请求授权,您将获得异常。您可以使用保存的 UI SynchronizationContext 中的 Post() 或带有异步委托的 Windows.ApplicationModel.Core.CoreApplication.MainView.Dispatcher.RunAsync() 来执行此操作。
我在论坛上看到 MS 员工的多篇帖子说 FromBluetoothAddressAsync() 仅适用于配对设备。情况并非如此,但它存在错误,并且如果设备过去至少手动配对过一次,则效果最好。
更新 04/17 - 创作者更新
Microsoft 刚刚更新了他们的蓝牙 API。我们现在有未配对的 BLE 设备通信!
目前他们的文档很少,但这是简化得多的新结构:
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);
};
现在好多了。正如我所说,目前几乎没有文档,我有一个奇怪的问题,我的 ValueChanged 回调在 30 秒左右后停止被调用,尽管这似乎是一个单独的范围问题。
更新 2 - 有些奇怪
在尝试新的创作者更新后,在构建 BLE 应用程序时需要考虑更多事项。
- 您不再需要 运行 UI 线程上的蓝牙内容。如果没有配对,BLE 似乎没有任何权限 windows,因此不再需要在 UI 线程上 运行。
- 您可能会发现您的应用程序在一段时间后停止从设备接收更新。这是一个范围界定问题,其中不应该处理对象。在上面的代码中,如果您在
charac
上收听ValueChanged
,您可能会遇到此问题。这是因为GattCharacteristic
在它应该被处理之前就被处理掉了,将特性设置为全局特性而不是依赖于它被复制进来。 - 断开连接似乎有点问题。退出应用程序不会终止连接。因此,请确保您使用
App.xml.cs
OnSuspended
回调来终止您的连接。否则你会进入一种奇怪的状态,其中 Windows 似乎保持(并继续阅读!!)BLE 连接。
嗯,它有它的怪癖,但它确实有效!
旧答案
根据 Jason 关于设备需要配对才能发现其服务的正确答案,下面是一些解决此问题的示例代码:
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}");
}
这里要注意的关键事项是:
- DeviceWatcher 需要添加和更新的属性设置才能工作。
- 您需要捕获在尝试查询未配对或尚未准备好的服务时发生的异常 FileNotFound。
杰拉德威尔金森的回答是正确的。为了让生活更轻松,我使用 Reactive Extensions() 将它变成了一个可等待的方法。欢迎任何评论。
因此,一旦您找到使用 BluetoothLEAdvertisementWatcher 的设备并与之配对,您就可以使用它来启用 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;
}