Как зарегистрироваться для уведомлений BLE из приложения WPF, работающего в Windows 10 Creators Update?

Я пытаюсь написать приложение на C#, которое использует WinRT Bluetooth LE APIs (пространство имен Windows.Devices.Bluetooth). Приложение представляет собой приложение Windows Classic Desktop (WPF, а не UWP). При запуске версии Windows 10 до обновления Creators, эти API работают должным образом. Однако при запуске обновления Creators API, которые должны отправлять данные на устройство Bluetooth, не работают. В частности, следующие методы возвращают коды состояния Успешно, но не передают данные по радио Bluetooth (как проверено с использованием анализатора трафика Bluetooth):

  • GattCharacteristic.WriteClientCharacteristicConfigurationDescriptorWithResultAsync
  • GattCharacteristic.WriteValueAsync

В результате, любая попытка зарегистрировать обработчик ValueChanged для характеристики не работает. Поскольку регистрация никогда не отправляется на устройство Bluetooth LE, приложение не получает никаких уведомлений.

Я понимаю, что не все API-интерфейсы UWP можно использовать из приложений, не относящихся к UWP, но я надеюсь, что кто-то успешно разработал приложение BLE в такой конфигурации (или, по крайней мере, может подтвердить, что это невозможно сейчас). Я могу подключаться и считывать данные с BLE-устройства и записывать их до обновления Creators, и только в этой последней версии Windows 10 проблема, о которой говорилось выше, проявляется. (Примечание. API-интерфейсы Async, использованные в примере кода, были добавлены в обновлении Creators. В предыдущей версии нашего приложения использовались более старые API-интерфейсы BLE, но они также не работают при запуске обновления Creators.)

В частности, у меня такой вопрос: учитывая следующий список ссылок на проект и пример кода, могу ли я что-нибудь предпринять, чтобы установить работоспособность подключения Bluetooth LE в Windows 10, используя обновление Creators Update из приложения, не предназначенного для UWP? Обратите внимание, что очевидный ответ "преобразовать приложение в приложение UWP" не работает для нас, потому что мы взаимодействуем с другим оборудованием и файлами так, как это невозможно в изолированной программной среде UWP.

Проект был настроен со следующими ссылками:

  • System.Runtime.WindowsRuntime, находится в: C:\Program Files (x86)\ Справочные сборки \ Microsoft \ Framework.NETCore \ v4.5 \ System.Runtime.WindowsRuntime.dll
  • Windows находится в: C:\Program Files (x86)\ Windows Kits \ 10 \ UnionMetadata \ Facade \ Windows.WinMD
  • Windows.Foundation.FoundationContract, находится в: C:\Program Files (x86)\ Windows Kits \ 10 \ References \ 10.0.15063.0 \ Windows.Foundation.FoundationContract \ 3.0.0.0 \ Windows.Foundation.FoundationContract.winmd
  • Windows.Foundation.UniversalApiContract, находится в: C:\Program Files (x86)\Windows Kits\10\References\10.0.15063.0\Windows.Foundation.UniversalApiContract\4.0.0.0\Windows.Foundation.UniversalApiContract.winmd

Ниже приведена очень урезанная версия кода Bluetooth из моего приложения. Обратите внимание, что много ошибок и тому подобное было удалено для ясности, но это должно дать общее представление о том, что я пытаюсь сделать:

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}");
            }
        }
    }
}

2 ответа

Решение

COM Security может мешать вашему приложению получать уведомления. Пожалуйста, обратитесь к следующей теме для решения. Я предлагаю использовать реестр взломать.

https://social.msdn.microsoft.com/Forums/en-US/58da3fdb-a0e1-4161-8af3-778b6839f4e1/bluetooth-bluetoothledevicefromidasync-does-not-complete-on-10015063?forum=wdk

У меня была похожая проблема, и с вышеуказанным изменением реестра мое приложение получает несколько уведомлений и останавливается без видимой причины. Потрачено много времени на эту проблему и ожидание патча от Microsoft.

Последнее обновление исправляет это. Вы также можете обойти проблему, используя инструкцию Мэтта Бивера по ссылке, на которую ссылается другой автор.

В основном либо:

  1. Укажите AppId для вашего приложения.
  2. Позвоните в ваше приложение CoInitializeSecurity, чтобы мы могли перезвонить
Другие вопросы по тегам