web-dev-qa-db-fra.com

Windows UWP se connecter au périphérique BLE après la découverte

J'utilise BluetoothLEAdvertisementWatcher pour rechercher des périphériques BLE à proximité et cela fonctionne bien. Après les avoir trouvées, je souhaite connecter et lire/écrire des données via le GATT. Mais je ne peux pas comprendre comment utiliser l'API après avoir obtenu la 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();
    }
}

J'ai trouvé des exemples qui utilisent DeviceInformation.FindAllAsync au lieu de BluetoothLEAdvertisementWatcher mais ceux-ci ne fonctionnent pas/ne trouvent aucun périphérique.

METTRE À JOUR

Après avoir creusé quelque temps, j'ai trouvé le chemin suivant. Mais malheureusement, le jumelage échoue. L'appareil est juste un Arduino avec un bouclier BLE. Je peux certainement me connecter avec Android et iOS. Cela doit donc être possible avec 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);
}

UPDATE # 2

Je suis maintenant capable de découvrir et d'appairer (instable, mais ok pour l'instant), mais 

var service = await GattDeviceService.FromIdAsync(args.Id);

lève l'exception suivante

System.IO.FileNotFoundException: Le système ne peut pas trouver le fichier spécifié. (Exception de HRESULT: 0x80070002)

Je ne sais pas pourquoi.

18
Sven-Michael Stübe

UPDATE 04/17 - CREATORS UPDATE

Microsoft vient de mettre à jour ses API Bluetooth. Nous avons maintenant une communication de périphérique BLE non appariée!

Ils ont très peu de documentation pour le moment, mais voici la nouvelle structure beaucoup plus simplifiée:

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

Bien mieux maintenant. Comme je l'ai dit, il n'y a presque aucune documentation pour le moment, j'ai un problème étrange dans lequel mon rappel ValueChanged cesse d'être appelé après 30 secondes environ, bien que cela semble être un problème de portée distinct.

MISE À JOUR 2 - QUELQUES ESPRITS

Après quelques essais supplémentaires avec la mise à jour des nouveaux créateurs, vous devez prendre en compte d'autres éléments lors de la création d'applications BLE.

  • Vous n'avez plus besoin d'exécuter les commandes Bluetooth sur le fil de l'interface utilisateur. Il ne semble pas exister de fenêtre d'autorisation pour BLE sans association, il n'est donc plus nécessaire de s'exécuter sur le thread d'interface utilisateur.
  • Vous constaterez peut-être que votre application cesse de recevoir les mises à jour de l'appareil après un certain temps. Il s'agit d'un problème de portée où les objets sont éliminés et qui ne devraient pas. Dans le code ci-dessus, si vous écoutiez ValueChanged sur la charac, vous pouvez vous heurter à ce problème. En effet, la GattCharacteristic étant supprimée avant qu'elle ne le soit, définissez la caractéristique comme globale plutôt que de la copier.
  • Déconnecter semble être un peu cassé. Quitter une application ne met pas fin aux connexions. En tant que tel, veillez à utiliser le rappel App.xml.csOnSuspended pour mettre fin à vos connexions. Sinon, vous entrez dans un état un peu étrange où Windows semble maintenir (et continuer à lire !!) la connexion BLE.

Et bien ça a ses bizarreries mais ça marche!

ANCIENNE REPONSE

Pour faire suite à la réponse correcte de Jason sur les périphériques devant être associés pour que leurs services soient découverts, voici un exemple de code permettant de résoudre ce problème:

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

Les points clés à noter ici sont:

  • DeviceWatcher nécessite que les propriétés ajoutées et mises à jour soient définies pour fonctionner.
  • Vous devez intercepter l'exception FileNotFound qui survient lors d'une tentative d'interrogation d'un service qui n'est pas couplé ou qui n'est pas encore prêt.
17
Gerard Wilkinson

UPDATE (05/05/16) : Le problème d'erreur "Élément introuvable" ne semble se produire que lorsque l'écran des paramètres Bluetooth n'est pas ouvert/en cours d'analyse. Je ne me souviens pas que c'était le cas avant 10586.218 mais je n'ai pas vérifié. Évidemment, tous les problèmes ne sont pas résolus dans la mise à jour.

UPDATE (4/29/16) : La mise à jour Windows 10586.218 semble avoir résolu le problème de couplage avec un périphérique qui n'avait encore jamais été couplé à la machine (ou au téléphone). Le processus que j'ai décrit ici et l'exemple de code de Gerard Wilkinson dans sa réponse devraient fonctionner de manière plus cohérente maintenant.

Si vous avez la chance de le faire fonctionner, l'installation du pilote nécessite un temps considérable. Je l'ai fait en ayant BluetoothLEAdvertisementWatcher et un DeviceWatcher exécutés simultanément.

Sauvegardez les informations DeviceInformation de BluetoothLEDevice que vous obtenez de FromBluetoothAddressAsync (), puis de Dispose () de BluetoothLEDevice avant de lancer le couplage. C'est important. Sinon, il ne verra pas les services Gatt après le couplage.

Attendez ensuite que DeviceWatcher voit le périphérique couplé. Cela peut prendre quelques minutes, mais vous l'obtiendrez généralement avant que la barre de progression de l'installation du périphérique (dans le panneau de configuration Bluetooth) atteigne 100%. Si FromIdAsync échoue toujours, cela signifie généralement qu’il ya eu une erreur d’installation du pilote. Vous pouvez dissocier, puis recommencer le processus de couplage. Cela fonctionne généralement pour moi.

C'est très instable, cependant, et cela semble dépendre du chipset Bluetooth et du pilote dont dispose la machine. Je reçois souvent une erreur Element Not Found avec FromBluetoothAddress, mais si elle passe au-delà, l'appariement fonctionne généralement au premier ou au deuxième essai.

PairAsync et UnpairAsync doivent également être publiés sur le fil de l'interface utilisateur. S'il ne parvient pas à afficher une boîte de dialogue bleue demandant une autorisation, vous obtiendrez des exceptions. Vous pouvez utiliser Post () à partir d'une interface utilisateur enregistrée SynchronizationContext ou Windows.ApplicationModel.Core.CoreApplication.MainView.Dispatcher.RunAsync () avec un délégué asynchrone pour le faire.

J'ai vu plusieurs messages d'employés de MS sur les forums disant que FromBluetoothAddressAsync () ne fonctionne que pour les appareils couplés. Ce n’est pas le cas, mais c’est un bug qui semble fonctionner mieux si l’appareil a été couplé manuellement au moins une fois dans le passé.

5
Jason S. Clary

La réponse de Gerard Wilkinson est correcte. Pour rendre la vie plus facile, je l’ai transformée en une méthode attendue en utilisant Reactive Extensions (). Tous les commentaires sont les bienvenus.

Ainsi, une fois que vous avez trouvé le périphérique utilisant BluetoothLEAdvertisementWatcher et associé à celui-ci, vous pouvez l'utiliser pour activer 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
LanderV

En gros, la réponse est en partie incluse dans les questions. En substance, vous utilisez BluetoothLEAdvertisementWatcher pour rechercher les périphériques uniquement. En gros, ils fonctionnent comme des balises.

Et vous n'êtes pas censé connecter ces périphériques en utilisant uniquement cette API. Pour connecter les périphériques, vous devez utiliser DeviceInformation.FindAllAsync () et pour le faire afficher tous les périphériques, vous devez d'abord les apparier. 

Quoi qu'il en soit, si vous souhaitez obtenir des données de certaines caractéristiques BLE spécifiques, vous pouvez essayer d'utiliser GattCharacteristicNotificationTrigger , pour un exemple complet et quelques explications supplémentaires voir mon blog .

0
Dr.Jukka