Use a BLE keyring as smart button

0

I have a BLE keyring that has a button (and apparently also various sensors). I want my C# application to react to button presses.

My issues:

  • I don't know which Characteristic I should subscribe to.
  • Even when subscribing to all characteristics notifications, the ValueChanged callback is never called.
  • After a while I get an exception with message : "The device does not recognize the command. (Exception from HRESULT: 0x80070016)". UPDATE: this seems to be caused by unreliable connection. Circumvented with a simple try/catch.

Here's the result of BTHGattDump :

Microsoft Bluetooth GATT database viewer v1.00 Copyright (c) Microsoft Corp.
Selected device - GABLYS
Device Address - e7be9c955801  (STATIC)
[Service] Handle=0x0001 Type=0x1800(GAP)
    [Characteristic] Handle=0x0002 ValueHandle=0x0003 Type=0x2a00(Device Name) Properties=(Read/Write)
        [Value] GABLYS
    [Characteristic] Handle=0x0004 ValueHandle=0x0005 Type=0x2a01(Appearance) Properties=(Read)
        [Value] [0000]
    [Characteristic] Handle=0x0006 ValueHandle=0x0007 Type=0x2a04(Peripheral Preferred Connection Parameters) Properties=(Read)
        [Value] [1000300000006400]
[Service] Handle=0x0008 Type=0x1801(GATT)
[Service] Handle=0x000c Type=0x180f(Battery)
    [Characteristic] Handle=0x000d ValueHandle=0x000e Type=0x2a19(Battery Level) Properties=(Read/Notify)
        [Value] [64]
        [Descriptor]  Handle=0x000f Type=0x2902(Client Configuration)
            [Value]  No subscription
[Service] Handle=0x0010 Type=0x1803(Link Loss)
    [Characteristic] Handle=0x0011 ValueHandle=0x0012 Type=0x2a06(Alert Level) Properties=(Read/Write)
        [Value] [00]
[Service] Handle=0x0013 Type=0x1802(Immediate Alert)
    [Characteristic] Handle=0x0014 ValueHandle=0x0015 Type=0x2a06(Alert Level) Properties=(WriteWithoutResponse)
[Service] Handle=0x0016 Type=4f172801-1867-a896-28c0-1bfbc156fa45
    [Characteristic] Handle=0x0017 ValueHandle=0x0018 Type=4f172491-1867-a896-28c0-1bfbc156fa45 Properties=(Read/Notify)
        [Value] [01]
        [Descriptor]  Handle=0x0019 Type=0x2902(Client Configuration)
            [Value]  No subscription
    [Characteristic] Handle=0x001a ValueHandle=0x001b Type=4f172492-1867-a896-28c0-1bfbc156fa45 Properties=(Read/Notify)
        [Value] [00]
        [Descriptor]  Handle=0x001c Type=0x2902(Client Configuration)
            [Value]  No subscription
[Service] Handle=0x001d Type=b0ad1523-99b2-7e1d-fc0d-6d399e1edf02
    [Characteristic] Handle=0x001e ValueHandle=0x001f Type=b0ad1524-99b2-7e1d-fc0d-6d399e1edf02 Properties=(Read/Notify)
        [Value] [00]
        [Descriptor]  Handle=0x0020 Type=0x2902(Client Configuration)
            [Value]  No subscription
    [Characteristic] Handle=0x0021 ValueHandle=0x0022 Type=b0ad1525-99b2-7e1d-fc0d-6d399e1edf02 Properties=(Read/Write)
        [Value] [00]
    [Characteristic] Handle=0x0023 ValueHandle=0x0024 Type=b0ad1526-99b2-7e1d-fc0d-6d399e1edf02 Properties=(Read/Write)
        [Value] [00]
[Service] Handle=0x0025 Type=89943300-2d54-b8cb-3af2-212144c5ca13
    [Characteristic] Handle=0x0026 ValueHandle=0x0027 Type=89943301-2d54-b8cb-3af2-212144c5ca13 Properties=(Read/Write)
        [Value] [00]
    [Characteristic] Handle=0x0028 ValueHandle=0x0029 Type=89943302-2d54-b8cb-3af2-212144c5ca13 Properties=(Read/Notify)
        [Value] [00]
        [Descriptor]  Handle=0x002a Type=0x2902(Client Configuration)
            [Value]  No subscription
    [Characteristic] Handle=0x002b ValueHandle=0x002c Type=89943304-2d54-b8cb-3af2-212144c5ca13 Properties=(Read/Write)
        [Value] [00]

My code (based on HeartbeatFg from DrJukka) :

public async void InitializeServiceAsync(string deviceId)
{
    try
    {
        Deinitialize();
        _service = await GattDeviceService.FromIdAsync(deviceId);

        if (_service != null)
        {
            //we could be already connected, thus lets check that before we start monitoring for changes
            if (DeviceConnectionUpdated != null && (_service.Device.ConnectionStatus == BluetoothConnectionStatus.Connected))
            {
                DeviceConnectionUpdated(true, null);
            }

            _service.Device.ConnectionStatusChanged += OnConnectionStatusChanged;

            Subscribe(0x0016, 0x0017);

            //Let's try those once I can get at least the first one to work
            //Subscribe(0x0016, 0x001a);

            //Subscribe(0x001d, 0x001e);
            //Subscribe(0x0025, 0x0028);
        }
    }
    catch (Exception e)
    {
        System.Diagnostics.Debug.WriteLine("ERROR: Accessing your device failed." + Environment.NewLine + e.Message);

        if (DeviceConnectionUpdated != null)
        {
            DeviceConnectionUpdated(false, "Accessing device failed: " + e.Message);
        }
    }
}

public async void Subscribe(ushort serviceHandle, ushort characteristicHandle)
{
    try
    {
        var service = _service.Device.GattServices.Single(x => x.AttributeHandle == serviceHandle);
        var characteristic = service.GetAllCharacteristics().Single(x => x.AttributeHandle == characteristicHandle);

        if (characteristic.CharacteristicProperties.HasFlag(GattCharacteristicProperties.Notify))
        {
            var currentDescriptorValue = await characteristic.ReadClientCharacteristicConfigurationDescriptorAsync();

            if ((currentDescriptorValue.Status != GattCommunicationStatus.Success) || (currentDescriptorValue.ClientCharacteristicConfigurationDescriptor != GattClientCharacteristicConfigurationDescriptorValue.Notify))
            {
                await characteristic.WriteClientCharacteristicConfigurationDescriptorAsync(GattClientCharacteristicConfigurationDescriptorValue.Notify);
            }

            characteristic.ValueChanged += Oncharacteristic_ValueChanged;
        }
    }
    catch(Exception e)
    {
        Debug.WriteLine(e.Message);
    }
}

private void Oncharacteristic_ValueChanged(GattCharacteristic sender, GattValueChangedEventArgs args)
{
    System.Diagnostics.Debug.WriteLine($"Oncharacteristic_ValueChanged from : {sender.AttributeHandle}");

    var data = new byte[args.CharacteristicValue.Length];
    DataReader.FromBuffer(args.CharacteristicValue).ReadBytes(data);

    System.Diagnostics.Debug.WriteLine("Oncharacteristic_ValueChanged : " + data[0]);
}

private void OnConnectionStatusChanged(BluetoothLEDevice sender, object args)
{
    if (sender.ConnectionStatus == BluetoothConnectionStatus.Connected)
    {
        System.Diagnostics.Debug.WriteLine("Connected");
    }
    else
    {
        System.Diagnostics.Debug.WriteLine("Disconnected");
    }

    if (DeviceConnectionUpdated != null)
    {
        DeviceConnectionUpdated(sender.ConnectionStatus == BluetoothConnectionStatus.Connected, null);
    }
}

Output :

FindAllAsync devices.Count : 1
Found : GABLYS, id: \\?\BTHLEDevice#{7b122568-6677-7f8c-f8e9-af0eedb36e3a}_e7be9c955801#9&ce378e&1&0032#{6e3bb679-4372-40c8-9eaa-4509df260cd8}
'HeartbeatFg.exe' (CoreCLR: CoreCLR_UWP_Domain): Loaded 'C:\Dev\Perso\BLE\BLETestStuffWindows-master\HeartbeatFg\HeartbeatFg\bin\x64\Debug\AppX\Microsoft.ApplicationInsights.PersistenceChannel.dll'. Cannot find or open the PDB file.
'HeartbeatFg.exe' (CoreCLR: CoreCLR_UWP_Domain): Loaded 'C:\Dev\Perso\BLE\BLETestStuffWindows-master\HeartbeatFg\HeartbeatFg\bin\x64\Debug\AppX\System.Threading.dll'. Symbols loaded.
'HeartbeatFg.exe' (CoreCLR: CoreCLR_UWP_Domain): Loaded 'C:\Dev\Perso\BLE\BLETestStuffWindows-master\HeartbeatFg\HeartbeatFg\bin\x64\Debug\AppX\System.Diagnostics.Tracing.dll'. Module was built without symbols.
'HeartbeatFg.exe' (CoreCLR: CoreCLR_UWP_Domain): Loaded 'C:\Dev\Perso\BLE\BLETestStuffWindows-master\HeartbeatFg\HeartbeatFg\bin\x64\Debug\AppX\System.Linq.dll'. Symbols loaded.
'HeartbeatFg.exe' (CoreCLR: CoreCLR_UWP_Domain): Loaded 'C:\Dev\Perso\BLE\BLETestStuffWindows-master\HeartbeatFg\HeartbeatFg\bin\x64\Debug\AppX\System.Globalization.dll'. Module was built without symbols.
'HeartbeatFg.exe' (CoreCLR: CoreCLR_UWP_Domain): Loaded 'C:\Dev\Perso\BLE\BLETestStuffWindows-master\HeartbeatFg\HeartbeatFg\bin\x64\Debug\AppX\System.IO.dll'. Symbols loaded.
Device GABLYS selected, now navigating to HeartBeatPage
OnNavigatedTo
'HeartbeatFg.exe' (CoreCLR: CoreCLR_UWP_Domain): Loaded 'C:\Dev\Perso\BLE\BLETestStuffWindows-master\HeartbeatFg\HeartbeatFg\bin\x64\Debug\AppX\System.Runtime.Extensions.dll'. Symbols loaded.
'HeartbeatFg.exe' (CoreCLR: CoreCLR_UWP_Domain): Loaded 'C:\Dev\Perso\BLE\BLETestStuffWindows-master\HeartbeatFg\HeartbeatFg\bin\x64\Debug\AppX\System.Reflection.dll'. Module was built without symbols.
Connected
'HeartbeatFg.exe' (CoreCLR: CoreCLR_UWP_Domain): Loaded 'C:\Dev\Perso\BLE\BLETestStuffWindows-master\HeartbeatFg\HeartbeatFg\bin\x64\Debug\AppX\System.Reflection.Extensions.dll'. Module was built without symbols.
The thread 0x3d6c has exited with code 0 (0x0).
The thread 0x34a8 has exited with code 0 (0x0).
The thread 0x368c has exited with code 0 (0x0).
The thread 0x12b4 has exited with code 0 (0x0).
Disconnected
Exception thrown: 'System.Exception' in mscorlib.ni.dll
Connected
Exception thrown: 'System.Exception' in mscorlib.ni.dll
c#
uwp
bluetooth-lowenergy
gatt
asked on Stack Overflow Jan 24, 2017 by Vinzz • edited Jan 26, 2017 by Vinzz

1 Answer

0

I don't know which Characteristic I should subscribe to.

So, you can retrieve all services and Characteristics the device supported and register ValueChanged event.

I test on TI SensorTag with the following code and it works for me. You can have a try. (I assume that your device supports button press service.)

    public async void InitializeServiceAsync(string deviceId)
    {
        try
        {
            _service = await GattDeviceService.FromIdAsync(deviceId);

            System.Diagnostics.Debug.WriteLine(_service.Device.Name);
            if (_service == null)
                return;

            var service = _service.Device.GattServices;
            foreach (var item in service)
            {
                var chars = item.GetAllCharacteristics();
                foreach (var cha in chars)
                {
                    _service = item;
                    Subscribe(item.AttributeHandle, cha.AttributeHandle);
                }
            }

        }
        catch (Exception e)
        {
            System.Diagnostics.Debug.WriteLine("ERROR: Accessing your device failed." + Environment.NewLine + e.Message);
        }
    }

    public async void Subscribe(ushort serviceHandle, ushort characteristicHandle)
    {
        try
        {
            var service = _service.Device.GattServices.Single(x => x.AttributeHandle == serviceHandle);
            var characteristic = service.GetAllCharacteristics().Single(x => x.AttributeHandle == characteristicHandle);

            if (characteristic.CharacteristicProperties.HasFlag(GattCharacteristicProperties.Notify))
            {
                System.Diagnostics.Debug.WriteLine("serviceHandle=" + serviceHandle);
                System.Diagnostics.Debug.WriteLine("characteristicHandle=" + characteristicHandle);
                await characteristic.WriteClientCharacteristicConfigurationDescriptorAsync(GattClientCharacteristicConfigurationDescriptorValue.Notify);

                System.Diagnostics.Debug.WriteLine("register value changed event");
                characteristic.ValueChanged += Oncharacteristic_ValueChanged;
            }
        }
        catch (Exception ex)
        {
            System.Diagnostics.Debug.WriteLine(ex.Message);
        }

    }

Call InitializeServiceAsync() method:

    var selector = GattDeviceService.GetDeviceSelectorFromUuid(GattServiceUuids.GenericAccess);
    var devices = await Windows.Devices.Enumeration.DeviceInformation.FindAllAsync(selector);
    InitializeServiceAsync(devices[0].Id); //Use your device id intead of "devices[0].Id"

UPDATE:

Two edits I have made:

  1. Traverse all all services and Characteristics.
  2. Remove this statement:

if ((currentDescriptorValue.Status != GattCommunicationStatus.Success) || (currentDescriptorValue.ClientCharacteristicConfigurationDescriptor != GattClientCharacteristicConfigurationDescriptorValue.Notify))

answered on Stack Overflow Jan 26, 2017 by Rita Han • edited Jan 27, 2017 by Rita Han

User contributions licensed under CC BY-SA 3.0