【问题标题】:Windows UWP connect to BLE device after discovery发现后 Windows UWP 连接到 BLE 设备
【发布时间】:2016-05-27 01:06:42
【问题描述】:

我正在使用BluetoothLEAdvertisementWatcher 查找附近的 BLE 设备,它运行良好。找到它们后,我想通过 GATT 连接和读/写数据。但是拿到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: 该系统找不到指定的文件。 (HRESULT 异常:0x80070002)

我不知道为什么。

【问题讨论】:

  • 也一直在努力解决这个问题。我在 Raspberry Pi 2 上运行 Windows 10 IOT。我的第一个问题是我在 appxmanifest 中缺少 DeviceCapability 标签。那不会让我得到一个BluetoothLEDevice。在我修复了我被困在同一个地方之后(上面的更新#2)。我尝试使用代码配对设备,但总是失败。几乎要放弃自己了。
  • 答案很简单:你不能!!!!不知何故,谁实现了 API 认为首先手动配对设备然后在您的代码中使用它会更好......互联网上有多个关于这个问题的示例和帖子......不幸的是,我刚刚写完在 Raspberry PI 3 的 raspbian OS 上使用 node.js 的示例应用程序,我只是想掌握我的主要开发技能:C# 和 Windows,但似乎我必须回到 Linux :( 配对和 GATT 发现是在在 Raspbian 上使用 node.js 的 3 行代码...... UWP 10 SDK 的一大耻辱。对不起!
  • 我在 Windows 10 Creators Update 上遇到了 GattDeviceService.FromIdAsync 的问题 - 当与经典桌面应用程序一起使用时,它永远不会返回。所以也要小心那个

标签: c# bluetooth-lowenergy win-universal-app windows-10-universal gatt


【解决方案1】:

更新 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 似乎没有任何权限窗口,因此不再需要在 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 需要同时设置Added 和Updated 属性才能工作。
  • 您需要捕获在尝试询问未配对或尚未准备好的服务时发生的异常 FileNotFound。

【讨论】:

  • 感谢您的回答,这对您有很大帮助。我将 devicewatcher 部分变成了可等待的方法。在下面查看我的答案
  • 感谢“创作者”更新。在 Creators 上,这个新的未配对设备通信代码在 UWP 应用程序中完美运行,但我很难让它在 Windows 桌面 exe 中运行。在 Creators 中作为桌面 exe 运行时,甚至我的原始代码(类似于原始帖子)也停止工作。我在这里提出了一个关于 stackoverflow.com/q/43568096/3187714 的新问题
  • 查看微软论坛上的这个帖子,15063 SDK 中蓝牙 API 的安全漏洞可能会导致这些问题:social.msdn.microsoft.com/Forums/en-US/…
  • 要“终止您的连接”,您只是在拥有它的对象上调用 Dispose,还是我缺少终止调用?
  • 我尝试过使用旧的 windows gatt api 和运行时 API(“Creators update”),我得到了“对象已被处理”的问题,我知道它与这一点有关到更新 2 中提到的第二点,但是应该如何将功能设置为全局呢?这是与此问题相关的帖子:stackoverflow.com/questions/67934095/…
【解决方案2】:

杰拉德威尔金森的回答是正确的。为了让生活更轻松,我使用 Reactive Extensions () 将它变成了一个可等待的方法。欢迎任何cmets。

因此,一旦您使用 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;
}

【讨论】:

    【解决方案3】:

    更新(2016 年 5 月 5 日):“未找到元素”错误问题似乎仅在蓝牙设置屏幕未打开/扫描时发生。我不记得在 10586.218 之前是这种情况,但我没有检查过。显然,并非所有问题都在更新中得到解决。

    更新(2016 年 4 月 29 日):10586.218 Windows 更新似乎解决了与以前从未与机器(或手机)配对过的设备配对的问题。我在这里概述的过程和 Gerard Wilkinson 在他的回答中的示例代码现在应该更加一致。

    如果您有幸让它工作,则需要等待相当长的时间才能安装驱动程序。我已经通过同时运行 BluetoothLEAdvertisementWatcher 和 DeviceWatcher 来做到这一点。

    保存您从 FromBluetoothAddressAsync() 获得的 BluetoothLEDevice 中的 DeviceInformation,然后在启动配对之前 Dispose() BluetoothLEDevice。这个很重要。否则,配对后将看不到 Gatt 服务。

    然后等待 DeviceWatcher 看到配对的设备。这可能需要几分钟,但您通常会在设备安装进度条(在蓝牙控制面板中)达到 100% 之前获得它。如果 FromIdAsync 仍然失败,这通常意味着存在驱动程序安装错误。您可以取消配对,然后重新执行配对过程。这通常对我有用。

    不过,它非常不稳定,而且似乎取决于机器的蓝牙芯片组和驱动程序。我经常收到 FromBluetoothAddress 的 Element Not Found 错误,但如果它超过了那里,配对通常在第一次或第二次尝试时起作用。

    PairAsync 和 UnpairAsync 也需要发布到 UI 线程。如果它无法弹出请求授权的蓝色对话框,则会出现异常。您可以使用保存的 UI SynchronizationContext 中的 Post() 或带有异步委托的 Windows.ApplicationModel.Core.CoreApplication.MainView.Dispatcher.RunAsync() 来执行此操作。

    我在论坛上看到 MS 员工的多个帖子说 FromBluetoothAddressAsync() 仅适用于配对设备。情况并非如此,但它有问题,而且如果设备过去至少手动配对过一次,它似乎效果最好。

    【讨论】:

    • 这是正确答案。我已经提出了一些示例代码,它们可以用于发现 BLE 服务。
    • 我陷入了“找不到元素”异常;你找到解决办法了吗?
    • "保存从蓝牙LEDevice 获得的DeviceInformation,你从FromBluetoothAddressAsync() 然后Dispose() 开始配对之前的BluetoothLEDevice。这很重要。如果你不这样做,它将看不到Gatt 服务配对后。”这是这个答案中最关键的部分,IMO。我能够在不使用DeviceWatcher 的情况下让它工作。从保存的DeviceInformation 中获取设备的 ID,配对后使用BluetoothLEDevice.FromIdAsync(id)。然后,您可以通过BluetoothLEDevice API 访问 GATT 服务。 (注:Windows 10 IoT Core - 10.0.14936.1000)
    【解决方案4】:

    基本上,您的答案部分包含在问题中。本质上,您使用 BluetoothLEAdvertisementWatcher 仅用于查找设备,基本上它们就像信标一样工作。

    而且您不应该仅使用此 API 来连接这些设备。要连接设备,您必须使用 DeviceInformation.FindAllAsync(),并让它向您显示任何设备,您需要先将它们配对。

    无论如何,如果您有兴趣从某些特定的 BLE 特性中获取数据,您可以尝试使用GattCharacteristicNotificationTrigger,以获取完整示例和一些额外的解释see my blog。

    【讨论】:

    • 我不得不承认我没有尝试过这种配对方式,但我会在我相当长的待办事项列表中添加它
    猜你喜欢
    • 2014-05-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-07
    • 1970-01-01
    相关资源
    最近更新 更多