Freigeben über


Windows Phone 8.1 for Developers–Bluetooth LE Battery Service

This blog post is part of a series about how Windows Phone 8.1 affects developers. This blog describes how to program agains the BLE battery service and is written by Jimmy Engström at Apeoholic and was originally posted here.

Some devices exposes a battery service that can supply us with the current (no pun intended) battery level.
I started this blog series working with only Windows phone in mind, but then I realized if it is possible to achieve the same results with an universal app of course that’s the route I should take.

Capabilities

To be able to communicate with Bluetooth low energy (or Bluetooth Smart, as it’s also called) you need to add a capability to your app.
This can’t be done from a GUI, you need to edit the package.appmanifest manually and add the following lines of code just above </Package>.
Don’t forget to do that in both your Windows 8 and Windows Phone projects.

   <Capabilities>
    <m2:DeviceCapability Name="bluetooth.genericAttributeProfile">
      <m2:Device Id="any">
        <m2:Function Type="name:battery"/>
      </m2:Device>
    </m2:DeviceCapability>
  </Capabilities>

Now you are are all set to start coding =)

Battery Level Service

The battery service must implement read, and notify is optional.

What that means is that you can always assume that you will be able to read the battery level (if your device implements the battery service, but you have to check if it supports Notify  (a good developer should always check Ler med tungan ute ).

image

The value it returns is a byte with a value from 0 to 100 representing a percentage of the current charge (0 being fully discharged).

The property

This is just a standard property with a backing store.

  private int _BatteryLevel;
        public int BatteryLevel
        {
            get
            {
                return _BatteryLevel;
            }
            set
            {
                _BatteryLevel = value;
                OnPropertyChanged();
            }
        }

The interesting part is how we handle OnPropertyChanged to try to avoid “The application called an interface that was marshalled for a different thread”.

I found a very neat piece of code here.

My implementation looks like this:

         public event PropertyChangedEventHandler PropertyChanged;

        private readonly CoreDispatcher _dispatcher;
        protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            if (_dispatcher == null || _dispatcher.HasThreadAccess)
            {
                var eventHandler = this.PropertyChanged;
                if (eventHandler != null)
                {
                    eventHandler(this,
                        new PropertyChangedEventArgs(propertyName));
                }
            }
            else
            {
                IAsyncAction doNotAwait =
                    _dispatcher.RunAsync(CoreDispatcherPriority.Normal,
                    () => OnPropertyChanged(propertyName));
            }
        }
  public BatteryServicePage()
        {
            if (!DesignMode.DesignModeEnabled)
            {
                _dispatcher = CoreWindow.GetForCurrentThread().Dispatcher;
            }
          ........
        }



Reading a value

I have written a simple method that reads a value from the service and if it supports notification it will also start a subscription on those notifications.

  public async Task<byte[]> GetValue(Guid gattCharacteristicUuids)
        {
            try
            {
                var gattDeviceService = await GattDeviceService.FromIdAsync(Device.Id);
                if (gattDeviceService != null)
                {
                    var characteristics = gattDeviceService.GetCharacteristics(gattCharacteristicUuids).First();

                    //If the characteristic supports Notify then tell it to notify us.
                    try
                    {
                        if (characteristics.CharacteristicProperties.HasFlag(GattCharacteristicProperties.Notify))
                        {
                            characteristics.ValueChanged += characteristics_ValueChanged;
                            await characteristics.WriteClientCharacteristicConfigurationDescriptorAsync(GattClientCharacteristicConfigurationDescriptorValue.Notify);
                        }
                    }
                    catch { }

                    //Read
                    if (characteristics.CharacteristicProperties.HasFlag(GattCharacteristicProperties.Read))
                    {
                        var result = await characteristics.ReadValueAsync(BluetoothCacheMode.Uncached);

                        if (result.Status == GattCommunicationStatus.Success)
                        {
                            byte[] forceData = new byte[result.Value.Length];
                            DataReader.FromBuffer(result.Value).ReadBytes(forceData);
                            return forceData;
                        }
                        else
                        {
                            await new MessageDialog(result.Status.ToString()).ShowAsync();
                        }
                    }
                }
                else 
                {
                    await new MessageDialog("Access to the device has been denied =(").ShowAsync();
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.Message);
            }
            return null;
        }

You can simply call the method like this:

 BatteryLevel = Convert.ToInt32((await GetValue(GattCharacteristicUuids.BatteryLevel))[0]);

You also need a callback method that can handle the notifications.

   void characteristics_ValueChanged(GattCharacteristic sender, GattValueChangedEventArgs args)
        {
            byte[] data = new byte[args.CharacteristicValue.Length];
            Windows.Storage.Streams.DataReader.FromBuffer(args.CharacteristicValue).ReadBytes(data);

           //Update properties
            if (sender.Uuid == GattCharacteristicUuids.BatteryLevel)
            {
                BatteryLevel = Convert.ToInt32(data[0]);
            }
        }

Now your app can retrieve battery level from a Bluetooth LE device. How awesome is that?

BatteryDemo.zip (289.18 kb)

Comments

  • Anonymous
    February 11, 2016
    Hi i used you code to communicate with sensor tag (CC2560 from tI) but when doing this i have the same issue justusing debug console indicating Le thread 0x3638 s'est arrêté avec le code 0 (0x0). Le thread 0x4510 s'est arrêté avec le code 0 (0x0). Have you an idea ?

  • Anonymous
    February 11, 2016
    Hi please note i did the same test between windows 10 using the bluetooth from my notebook and windows phone 8.1 but the issue felle not be present on my lumia 640, strange ... your advice ?