Nikolay Vasilev
Nikolay Vasilev

Reputation: 31

Why is BluetoothLEDevice.GattServices Empty

I am trying to communicate with a peripheral device without pairing it to Windows and I am using BluetoothLEAdvertisementWatcher to scan for devices in range. This is my WatcherOnReceived method:

 async private void WatcherOnReceived(BluetoothLEAdvertisementWatcher sender, BluetoothLEAdvertisementReceivedEventArgs args)
    {
        BluetoothLEDevice device = null;
        BluetoothDevice basicDevice = null;
        GattDeviceService services = null;

        if (args.Advertisement.LocalName != "Nexus 6")
            return;

        _watcher.Stop();

        device = await BluetoothLEDevice.FromBluetoothAddressAsync(args.BluetoothAddress);
        device.GattServicesChanged += Device_GattServicesChanged;
        //basicDevice = await BluetoothDevice.FromBluetoothAddressAsync(args.BluetoothAddress);
        //services = await GattDeviceService.FromIdAsync(device.DeviceId);

        lock (m_syncObj)
        {
            Debug.WriteLine("");
            Debug.WriteLine("----------- DEVICE --------------");

            Debug.WriteLine(args.ToString());

            Debug.WriteLine(args.Advertisement.DataSections.Count);

            foreach (var item in args.Advertisement.DataSections)
            {
                var data = new byte[item.Data.Length];
                using (var reader = DataReader.FromBuffer(item.Data))
                {
                    reader.ReadBytes(data);
                }

                Debug.WriteLine("Manufacturer data: " + BitConverter.ToString(data));

                //Debug.WriteLine("Data : " + item.Data.ToString());
                //Debug.WriteLine("Data capacity: " + item.Data.Capacity);
                Debug.WriteLine("Data Type: " + item.DataType);
            }

            foreach (var md in args.Advertisement.ManufacturerData)
            {
                var data = new byte[md.Data.Length];
                using (var reader = DataReader.FromBuffer(md.Data))
                {
                    reader.ReadBytes(data);
                }
                Debug.WriteLine("Manufacturer data: " + BitConverter.ToString(data));
            }

            foreach (Guid id in args.Advertisement.ServiceUuids)
            {
                Debug.WriteLine("UUIDs: " + id.ToString() + " Count: " + args.Advertisement.ServiceUuids.Count);
                //services = device.GetGattService(id);
            }

            Debug.WriteLine("Receive event...");
            Debug.WriteLine("BluetoothAddress: " + args.BluetoothAddress.ToString("X"));
            Debug.WriteLine("Advertisement.LocalName: " + args.Advertisement.LocalName);
            Debug.WriteLine("AdvertisementType: " + args.AdvertisementType);
            Debug.WriteLine("RawSignalStrengthInDBm: " + args.RawSignalStrengthInDBm);

            if (device != null)
            {
                Debug.WriteLine("Bluetooth Device: " + device.Name);
                Debug.WriteLine("Bluetooth Device conn status: " + device.ConnectionStatus);
                Debug.WriteLine("Bluetooth DeviceId: " + device.DeviceId);
                Debug.WriteLine("Bluetooth GettServices Count: " + device.GattServices.Count);
            }
        }   
    }

When a device is received I successfully create the BluetoothLEDevice from the args.BlutoothAddress but the device.GattServices are always empty and thus I can not use them to communicate with the device. Is the problem in the device or in the Windows API and what else can I try?

Upvotes: 3

Views: 6176

Answers (1)

Gerard Wilkinson
Gerard Wilkinson

Reputation: 1541

UPDATE 04/17 - CREATORS UPDATE Microsoft have just updated their Bluetooth APIs. We now have unpaired BLE device communication!

They have very little documentation up at the moment but here is the much simplified new structure:

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

Much better now. As I said there is next to no documentation at the moment, I have a weird issue where my ValueChanged callback stops being called after 30 seconds or so, though that seems to be a separate scoping issue.

UPDATE 2 - SOME WEIRDNESS

After some more playing around with the new creators update there are a few more things to consider when building BLE apps.

  • You no longer need to run the Bluetooth stuff on the UI thread. There doesn't seem to be any permissions windows for BLE without pairing so no longer necessary to run on UI thread. You may find that your application stops receiving updates from the device after a period of time. This is a scoping issue where objects are being disposed of that shouldn't. In the code above if you were listening to ValueChanged on the charac you may hit this issue. This is because the GattCharacteristic is disposed of before it should be, set the characteristic as a global rather than relying on it being copied in. Disconnecting seems to be a bit broken. Quitting an app does not terminate connections. As such make sure you use the App.xml.cs OnSuspended callback to terminate your connections. Otherwise you get in a bit of a weird state where Windows seems to maintain (and keep reading!!) the BLE connection. Well it has its quirks but it works!

  • This is still an issue with Windows Universal apps, the initial issue is that a device must be paired (not bonded) for GattServices to be discovered. However they also need to be discovered using Windows Devices rather than the BLE API. Microsoft are aware and are working on a new BLE API which does not require pairing but frankly their BLE support is pretty useless until this is ready.

  • Try pairing the device manually in Control Panel then list the services again. For some reason in Windows Universal Apps you can only list the Gatt Services for paired devices despite one of the advantages of BLE being that you do not need to pair with the device before using its services.

You can pair the device programmatically however depending on the platform the application is being run on this requires UI prompts. Therefore interrogating BLE services in the background is in-feasible. This needs to be fixed as it really stunts BLE support in UWP.

Its weird but works!

OLD ANSWER

Here is some sample code with a working BLE device connection:

    private void StartWatcher()
    {
        ConnectedDevices = new List<string>();

        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 async void DeviceFound(BluetoothLEAdvertisementWatcher watcher, BluetoothLEAdvertisementReceivedEventArgs btAdv)
    {
        if (!ConnectedDevices.Contains(btAdv.Advertisement.LocalName) && _devices.Contains(btAdv.Advertisement.LocalName))
        {
            await Dispatcher.RunAsync(CoreDispatcherPriority.Low, async () =>
            {
                var device = await BluetoothLEDevice.FromBluetoothAddressAsync(btAdv.BluetoothAddress);
                if (device.GattServices.Any())
                {
                    ConnectedDevices.Add(device.Name);
                    device.ConnectionStatusChanged += (sender, args) =>
                    {
                        ConnectedDevices.Remove(sender.Name);
                    };
                    SetupWaxStream(device);
                } else if (device.DeviceInformation.Pairing.CanPair && !device.DeviceInformation.Pairing.IsPaired)
                {
                    await device.DeviceInformation.Pairing.PairAsync(DevicePairingProtectionLevel.None);
                }
            });
        }
    }

    private async void DeviceAdded(DeviceWatcher watcher, DeviceInformation device)
    {
        if (_devices.Contains(device.Name))
        {
            try
            {
                var service = await GattDeviceService.FromIdAsync(device.Id);
                var characteristics = service.GetAllCharacteristics();
            }
            catch
            {
                Debug.WriteLine("Failed to open service.");
            }
        }
    }

    private async void DeviceUpdated(DeviceWatcher watcher, DeviceInformationUpdate update)
    {
        var device = await DeviceInformation.CreateFromIdAsync(update.Id);
        if (_devices.Contains(device.Name))
        {
            try
            {
                var service = await GattDeviceService.FromIdAsync(device.Id);
                var characteristics = service.GetAllCharacteristics();
            }
            catch
            {
                Debug.WriteLine("Failed to open service.");
            }
        }
    }

Upvotes: 3

Related Questions