Windows UWP подключается к устройству BLE после обнаружения

Я использую BluetoothLEAdvertisementWatcher для поиска ближайших устройств BLE, и он работает хорошо. После их поиска я хочу подключать и читать/записывать данные через GATT. Но я не могу понять, как использовать API после получения 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 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, но они не работают/не находят какое-либо устройство.

UPDATE

После копания в течение некоторого времени я нашел следующий способ. Но, к сожалению, спаривание терпит неудачу. Устройство - это просто Arduino с экраном BLE. Я могу определенно подключиться к 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)

Я не знаю, почему.

Ответ 1

ОБНОВЛЕНИЕ 04/17 - ОБНОВЛЕНИЕ СОЗДАТЕЛЕЙ

Microsoft только что обновила свои API Bluetooth. Теперь у нас есть непарная связь с устройством 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 нужно учитывать еще несколько вещей.

  • Вам больше не нужно запускать элементы Bluetooth в потоке пользовательского интерфейса. Кажется, что нет окон разрешений для BLE без спаривания, поэтому больше не нужно запускать в потоке пользовательского интерфейса.
  • Вы можете обнаружить, что ваше приложение перестает получать обновления с устройства через некоторое время. Это проблема определения области, в которой объекты удаляются, а это не должно. В приведенном выше коде, если вы слушаете ValueChanged на charac, вы можете столкнуться с этой проблемой. Это происходит из-за того, что GattCharacteristic удаляется до того, как он будет установлен, установите характеристику как глобальную, а не полагайтесь на ее копирование.
  • Отключение, похоже, немного сломан. Выход из приложения не прекращает подключения. Таким образом, убедитесь, что вы используете обратный вызов App.xml.cs OnSuspended для прекращения ваших соединений. В противном случае вы попадаете в странное состояние, где Windows, похоже, поддерживает (и продолжает читать!!) соединение BLE.

Ну, у него есть свои причуды, но он работает!

OLD ANSWER

Следуя правильному ответу Джейсона о том, что устройства должны быть спарены, чтобы их службы были обнаружены, вот несколько примеров кода для этого:

    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, которое возникает при попытке опросить службу, которая не спарена или еще не готова.

Ответ 2

ОБНОВЛЕНИЕ (5/5/16). Ошибка ошибки "Элемент не найден" возникает, когда экран настроек Bluetooth не открыт/сканируется. Я не помню, чтобы это было до 10586,218, но я не проверял. Очевидно, что не все проблемы исправлены в обновлении.

ОБНОВЛЕНИЕ (4/29/16). В обновлении Windows 10586.218 исправлено проблема спаривания с устройством, которое ранее не было сопряжено с машиной (или телефоном). Процесс, который я изложил здесь, и образец кода Джерарда Уилкинсона в его ответе должен работать более последовательно.

Если вам посчастливилось заставить это работать, для установки драйвера требуется значительное количество времени. Я сделал это, имея одновременно BluetoothLEAdvertiseWatcher и DeviceWatcher.

Сохраните DeviceInformation из BluetoothLEDevice, который вы получаете из FromBluetoothAddressAsync(), затем Dispose() BluetoothLEDevice перед началом спаривания. Это важно. Если вы этого не сделаете, он не увидит Gatt Services после спаривания.

Затем подождите, пока DeviceWatcher не увидит сопряженное устройство. Это может занять несколько минут, но вы обычно получите его до того, как индикатор выполнения установки устройства (в панели управления Bluetooth) достигнет 100%. Если функция FromIdAsync все еще не работает, обычно это означает, что произошла ошибка установки драйвера. Вы можете отключиться, а затем снова выполнить процесс сопряжения. Это обычно работает для меня.

Это очень нестабильно, и, похоже, он зависит от того, на котором установлен чипсет и драйвер Bluetooth. Я часто получаю ошибку Element Not Found с FromBluetoothAddress, но если она там проходит, то соединение обычно работает при первой или второй попытке.

PairAsync и UnpairAsync также должны быть отправлены в поток пользовательского интерфейса. Если он не сможет отобразить синий диалог с запросом авторизации, вы получите исключения. Вы можете использовать Post() из сохраненного пользовательского интерфейса SynchronizationContext или Windows.ApplicationModel.Core.CoreApplication.MainView.Dispatcher.RunAsync() с делегатом async для этого.

Я видел несколько сообщений от сотрудников MS на форумах, говорящих, что FromBluetoothAddressAsync() работает только для сопряженных устройств. Это не тот случай, но он глючит и, похоже, работает лучше всего, если устройство было сопряжено вручную хотя бы один раз в прошлом.

Ответ 3

Ответ Джерарда Уилкинсона правильный. Чтобы облегчить жизнь, я превратил ее в ожидаемый метод с помощью Reactive Extensions(). Любые комментарии приветствуются.

Итак, как только вы нашли устройство с помощью BluetoothLEAdvertiseWatcher и в сочетании с ним, вы можете использовать его для включения служб GATTS.

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

Ответ 4

В основном у вас есть ответ, частично включенный в вопросы. В основном вы используете BluetoothLEAdvertiseWatcher для поиска только устройств, в основном они работают так же, как маяки.

И вы не должны подключать эти устройства только с помощью этого API. Чтобы подключить устройства, вы должны использовать DeviceInformation.FindAllAsync(), и чтобы он показывал вам какие-либо устройства, вам необходимо сначала их соединить.

В любом случае, если вы заинтересованы в получении данных по некоторым конкретным характеристикам BLE, вы можете попробовать использовать GattCharacteristicNotificationTrigger для полного примера и бит дополнительных объяснений см. мой блог.