title: UWP Bluetooth Low Energy Application Disconnects Early
body: <p>So I am designing an application for windows laptops to connect to a custom designed pressure sensor. The application pairs to the device and then receives notifications from the device every 10 ms. Then for some reason the communication stops. I know it is a problem with my application and not with the device, because when I connect to my phone, I do not have this problem.</p>
  
  <p>Here is the main page where I create the devicewatcher and discover the device:</p>
  
  <pre><code>using System;
  using System.Collections.ObjectModel;
  using System.Collections.Generic;
  using System.Diagnostics;
  using System.IO;
  using System.Linq;
  using System.Runtime.InteropServices.WindowsRuntime;
  using Windows.Foundation;
  using Windows.Foundation.Collections;
  using Windows.UI.Core;
  using Windows.UI.Xaml;
  using Windows.UI.Xaml.Controls;
  using Windows.UI.Xaml.Controls.Primitives;
  using Windows.UI.Xaml.Data;
  using Windows.UI.Xaml.Input;
  using Windows.UI.Xaml.Media;
  using Windows.UI.Xaml.Navigation;
  using Windows.Devices.Bluetooth;
  using Windows.Devices.Enumeration;
  
  // The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=402352&amp;clcid=0x409
  
  namespace BLEInterfaceTest
  {
      /// &lt;summary&gt;
      /// An empty page that can be used on its own or navigated to within a Frame.
      /// &lt;/summary&gt;
      public sealed partial class MainPage : Page
      {
          private DeviceWatcher deviceWatcher;
          private ObservableCollection&lt;DeviceInformation&gt; deviceList = new ObservableCollection&lt;DeviceInformation&gt;();
  
      public MainPage()
      {
          this.InitializeComponent();
      }
  
      protected override void OnNavigatedTo(NavigationEventArgs e)
      {
          this.DataContext = deviceList;
          deviceListView.ItemsSource = deviceList;
          deviceWatcher = DeviceInformation.CreateWatcher(
              "System.ItemNameDisplay:~~\"Button\"",
              new string[] {
                  "System.Devices.Aep.DeviceAddress",
                  "System.Devices.Aep.IsConnected" },
              DeviceInformationKind.AssociationEndpoint);
          deviceWatcher.Added += DeviceWatcher_Added;
          deviceWatcher.Removed += DeviceWatcher_Removed;
          deviceWatcher.Start();
          base.OnNavigatedTo(e);
          SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility =
              AppViewBackButtonVisibility.Collapsed;
      }
  
      protected override void OnNavigatedFrom(NavigationEventArgs e)
      {
          deviceWatcher.Stop();
          base.OnNavigatedFrom(e);
      }
  
      private async void DeviceWatcher_Removed(DeviceWatcher sender, DeviceInformationUpdate args)
      {
          var toRemove = (from a in deviceList where a.Id == args.Id select a).FirstOrDefault();
  
          if (toRemove != null)
          {
              await this.Dispatcher.RunAsync(
                  Windows.UI.Core.CoreDispatcherPriority.Normal,
                  () =&gt; { deviceList.Remove(toRemove); });
          }
      }
  
      private async void DeviceWatcher_Added(DeviceWatcher sender, DeviceInformation args)
      {
          await this.Dispatcher.RunAsync(
              Windows.UI.Core.CoreDispatcherPriority.Normal,
              () =&gt; { deviceList.Add(args); });
      }
  
      private void deviceListView_ItemClick(object sender, ItemClickEventArgs e)
      {
          this.Frame.Navigate(typeof(DevicePage), e.ClickedItem);
      }
    }
  }'
  </code></pre>
  
  <p>This next code is the page where the pressure sensor is connected to and where data is read from the device.</p>
  
  <pre><code>using System;
  using System.Collections.Generic;
  using System.Collections.ObjectModel;
  using System.IO;
  using System.Linq;
  using System.Runtime.InteropServices.WindowsRuntime;
  using Windows.Foundation;
  using Windows.Foundation.Collections;
  using Windows.UI.Core;
  using Windows.UI.Xaml;
  using Windows.UI.Xaml.Controls;
  using Windows.UI.Xaml.Controls.Primitives;
  using Windows.UI.Xaml.Data;
  using Windows.UI.Xaml.Input;
  using Windows.UI.Xaml.Media;
  using Windows.UI.Xaml.Navigation;
  using Windows.UI.Popups;
  using Windows.Devices.Bluetooth.GenericAttributeProfile;
  using Windows.Devices.Bluetooth;
  using Windows.Devices.Enumeration;
  using Windows.Storage.Pickers;
  using Windows.Storage;
  using Windows.Storage.Streams;
  using System.Threading.Tasks;
  using Windows.ApplicationModel.Background;
  
  
  // The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238
  
  namespace BLEInterfaceTest
  {
      /// &lt;summary&gt;
      /// An empty page that can be used on its own or navigated to within a Frame.
      /// &lt;/summary&gt;
      public sealed partial class DevicePage : Page
      {
          private DeviceInformation device { get; set; }
          private PressureSensor pSensor { get; set; }
          public static DateTime startTime { get; set; }
          public ObservableCollection&lt;DataPoint&gt; PressureData = new ObservableCollection&lt;DataPoint&gt;();
          public static ObservableCollection&lt;DataPoint&gt; inbetween;
          private static TextBox txtP;
          private BluetoothLEDevice leDevice;
          private DispatcherTimer timer = new DispatcherTimer();
          private int packetNum = 0;
  
          public DevicePage()
          {
              this.InitializeComponent();
              SystemNavigationManager.GetForCurrentView().BackRequested += DevicePage_BackRequested;
              txtP = txtValue1;
              inbetween = PressureData;
          }
  
          public static void ChangeText(string text)
          {
              txtP.Text = text;
          }
  
          private async void InitializePressureSensor(GattDeviceService service)
          {
              pSensor = new PressureSensor(service, SensorUUIDs.PressureSensorUuid);
              await pSensor.EnableNotifications();
              btnStart.IsEnabled = true;
          }
  
          private async void StartRecievingData()
          {
              try
              {
                  leDevice = await BluetoothLEDevice.FromIdAsync(device.Id);
                  string selector = "(System.DeviceInterface.Bluetooth.DeviceAddress:=\"" +
                      leDevice.BluetoothAddress.ToString("X") + "\")";
                  var services = await leDevice.GetGattServicesAsync(BluetoothCacheMode.Uncached);
  
                  foreach (var service in services.Services)
                  {
                      if (service.Uuid.ToString() == SensorUUIDs.ButtonSensorServiceUuid)
                      {
                          InitializePressureSensor(service);
                      }
                  }
  
                  timer.Interval = new TimeSpan(0, 0, 0, 0, 1);
                  timer.Tick += Timer_Tick1;
                  startTime = DateTime.Now;
                  timer.Start();
              }
  
              catch (Exception ex)
              {
                  var messageDialog = new MessageDialog("An error has occured Please try again. \n" + ex.Message, "Error!");
              }
          }
  
          public async void UpdateAllData()
          {
              while (pSensor != null &amp;&amp; pSensor.MorePacketsAvailable)
              {
                  int[] values = await pSensor.GetPressure();
  
                  int packetNumber = values[0];
  
                  if (packetNumber &gt; packetNum)
                  {
                      packetNum = packetNumber;
  
                      txtValue1.Text = Convert.ToString(values[1]);
                      txtValue2.Text = Convert.ToString(values[5]);
  
                      for (int i = 1; i &lt; 5; i++)
                      {
                          PressureData.Add(new DataPoint(DateTime.Now - startTime, packetNumber, ((i-1)*2.5 + 10*packetNumber), values[i], values[i + 4]));
                      }
                  }
              }
          }
  
          private void Timer_Tick1(object sender, object e)
          {
  
              UpdateAllData();
          }
  
          private async void PairToDevice()
          {
              if (device.Pairing.CanPair)
              {
                  var customPairing = device.Pairing.Custom;
  
                  customPairing.PairingRequested += CustomPairing_PairingRequested;
  
                  var result = await customPairing.PairAsync(DevicePairingKinds.ConfirmOnly);
  
                  customPairing.PairingRequested -= CustomPairing_PairingRequested;
  
                  if ((result.Status == DevicePairingResultStatus.Paired) || (result.Status == DevicePairingResultStatus.AlreadyPaired))
                  {
                      /*while (device.Pairing.IsPaired == false)
                      {
                          device = await DeviceInformation.CreateFromIdAsync(device.Id);
                      }*/
  
                      StartRecievingData();
                  }
  
  
              }
  
              else if (device.Pairing.IsPaired)
              {
                  StartRecievingData();
              }
          }
  
          private void CustomPairing_PairingRequested(DeviceInformationCustomPairing sender, DevicePairingRequestedEventArgs args)
          {
              args.Accept();
          }
  
          protected override void OnNavigatedTo(NavigationEventArgs e)
          {
              btnSave.Content = "Save";
              btnStop.IsEnabled = false;
              btnStart.IsEnabled = false;
              this.DataContext = PressureData;
              device = (DeviceInformation)e.Parameter;
              PairToDevice();
              //StartRecievingData();
  
              base.OnNavigatedTo(e);
  
              Frame rootFrame = Window.Current.Content as Frame;
  
              if (rootFrame.CanGoBack)
              {
                  SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility =
                      AppViewBackButtonVisibility.Visible;
              }
          }
  
          private void DevicePage_BackRequested(object sender, BackRequestedEventArgs eventArgs)
          {
              Frame rootFrame = Window.Current.Content as Frame;
  
              if (rootFrame == null)
              {
                  return;
              }
  
              // Navigate back if possible, and if the event has already been handled
              if (rootFrame.CanGoBack &amp;&amp; eventArgs.Handled ==false)
              {
                  eventArgs.Handled = true;
                  rootFrame.GoBack();
              }
          }
  
          private async void btnSave_Click(object sender, RoutedEventArgs e)
          {
              timer.Stop();
              var picker = new FileSavePicker();
              picker.SuggestedStartLocation = PickerLocationId.DocumentsLibrary;
              picker.FileTypeChoices.Add("CSV", new List&lt;string&gt;() { ".csv" });
  
              StorageFile file = await picker.PickSaveFileAsync();
  
              if (file != null)
              {
                  var stream = await file.OpenAsync(FileAccessMode.ReadWrite);
  
                  using (IOutputStream outputStream = stream.GetOutputStreamAt(0))
                  {
                      using (var writer = new DataWriter(outputStream))
                      {
                          foreach (DataPoint p in PressureData)
                          {
                              string text = p.TimeStamp.ToString() + "," + p.PacketNumber.ToString() + "," + p.InternalTimestamp.ToString() + "," + p.PressureValue1.ToString() + "," + p.PressureValue2.ToString() +  "\n";
                              writer.WriteString(text);
                          }
  
                          await writer.StoreAsync();
                          await writer.FlushAsync();
                      }
                  }
  
                  stream.Dispose();
              }
          }
  
          private async void btnStart_Click(object sender, RoutedEventArgs e)
          {
              if (pSensor != null)
              {
                  btnStop.IsEnabled = true;
                  btnStart.IsEnabled = false;
  
                  startTime = DateTime.Now;
  
                  if (pSensor != null)
                  {
                      await pSensor.BeginCollecting();
                  }
              }
          }
  
          private async void btnStop_Click(object sender, RoutedEventArgs e)
          {
              btnStart.IsEnabled = true;
              btnStop.IsEnabled = false;
  
              if (pSensor != null)
              {
                  await pSensor.StopCollecting();
              }
          }
      }
  }
  </code></pre>
  
  <p>Here is where I define my SensorBase and PressureSensor class that handles the device connection:</p>
  
  <pre><code>using System;
  using System.Collections.Generic;
  using System.Linq;
  using System.Text;
  using System.Threading.Tasks;
  using Windows.Devices.Bluetooth;
  using Windows.Devices.Bluetooth.GenericAttributeProfile;
  using Windows.Storage.Streams;
  using Windows.Devices.Enumeration;
  
  namespace BLEInterfaceTest
  {
      public static class SensorUUIDs
      {
          private static readonly string _packetUuid =           "0000a043-0000-1000-8000-00805f9b34fb";
          private static readonly string _buttonSensorServiceUuid = "0000a042-0000-1000-8000-00805f9b34fb";
          private static readonly string _sensorStateUuid =         "0000a044-0000-1000-8000-00805f9b34fb";
  
          public static string PressureSensorUuid
          {
              get { return _packetUuid; }
          }
  
          public static string ButtonSensorServiceUuid
          {
              get { return _buttonSensorServiceUuid; }
          }
  
          public static string SensorStateUuid
          {
              get { return _sensorStateUuid; }
          }
      }
  
      public class SensorBase : IDisposable
      {
          protected GattDeviceService deviceService;
          protected string sensorDataUuid;
          protected Queue&lt;byte[]&gt; fifoBuffer;
          protected bool isNotificationSupported = false;
          public bool newData = false;
          private GattCharacteristic dataCharacteristic;
  
          public SensorBase(GattDeviceService dataService, string sensorDataUuid)
          {
              this.deviceService = dataService;
              this.sensorDataUuid = sensorDataUuid;
              fifoBuffer = new Queue&lt;byte[]&gt;(20);
          }
  
          public bool MorePacketsAvailable
          {
              get
              {
                  if (fifoBuffer.Count &gt; 0)
                  {
                      return true;
                  }
  
                  else
                  {
                      return false;
                  }
              }
          }
  
          public virtual async Task EnableNotifications()
          {
              GattCharacteristicsResult result = await deviceService.GetCharacteristicsAsync();
  
              foreach (var test in result.Characteristics)
              {
                  string t = test.Uuid.ToString();
              }
  
  
              isNotificationSupported = true;
              dataCharacteristic = (await deviceService.GetCharacteristicsForUuidAsync(
                  new Guid(sensorDataUuid))).Characteristics[0];
              dataCharacteristic.ValueChanged += dataCharacteristic_ValueChanged;
              GattCommunicationStatus status = await dataCharacteristic.WriteClientCharacteristicConfigurationDescriptorAsync(
                  GattClientCharacteristicConfigurationDescriptorValue.Notify);
  
              var currentDescriptorValue = await dataCharacteristic.ReadClientCharacteristicConfigurationDescriptorAsync();
  
              if (currentDescriptorValue.Status != GattCommunicationStatus.Success
                  || currentDescriptorValue.ClientCharacteristicConfigurationDescriptor != GattClientCharacteristicConfigurationDescriptorValue.Notify)
              {
                  GattCommunicationStatus status2 = await dataCharacteristic.WriteClientCharacteristicConfigurationDescriptorAsync(
                  GattClientCharacteristicConfigurationDescriptorValue.Notify);
              }
          }
  
          public virtual async Task DisableNotifications()
          {
              newData = false;
              isNotificationSupported = false;
              dataCharacteristic = (await deviceService.GetCharacteristicsForUuidAsync(
                  new Guid(sensorDataUuid))).Characteristics[0];
              dataCharacteristic.ValueChanged -= dataCharacteristic_ValueChanged;
              GattCommunicationStatus status = await dataCharacteristic.WriteClientCharacteristicConfigurationDescriptorAsync(GattClientCharacteristicConfigurationDescriptorValue.None);
          }
  
          protected async Task&lt;byte[]&gt; ReadValue()
          {
              if (!isNotificationSupported)
              {
                  if (dataCharacteristic == null)
                  {
                      dataCharacteristic = (await deviceService.GetCharacteristicsForUuidAsync(
                          new Guid(sensorDataUuid))).Characteristics[0];
                  }
  
                  GattReadResult readResult = await dataCharacteristic.ReadValueAsync();
                  byte[] data = new byte[readResult.Value.Length];
                  DataReader.FromBuffer(readResult.Value).ReadBytes(data);
  
                  fifoBuffer.Enqueue(data);
              }
  
              return fifoBuffer.Dequeue();
          }
  
          protected async Task WriteByteArray(string characteristicUuid, byte[] value)
          {
              GattCharacteristic writeCharacteristic = (await deviceService.GetCharacteristicsForUuidAsync(
                          new Guid(characteristicUuid))).Characteristics[0];
  
              var writer = new DataWriter();
              writer.WriteBytes(value);
              var res = await writeCharacteristic.WriteValueAsync(writer.DetachBuffer(), GattWriteOption.WriteWithoutResponse);
          }
  
          private void dataCharacteristic_ValueChanged(GattCharacteristic sender, GattValueChangedEventArgs args)
          {
              byte[] data = new byte[args.CharacteristicValue.Length];
              DataReader.FromBuffer(args.CharacteristicValue).ReadBytes(data);
              fifoBuffer.Enqueue(data);
              newData = true;
          }
  
          public async void Dispose()
          {
              await DisableNotifications();
          }
      }
  
      public class PressureSensor: SensorBase
      {
          public PressureSensor(GattDeviceService dataService, string sensorDataUuid)
              : base(dataService, sensorDataUuid)
          {
  
          }
  
          public async Task BeginCollecting()
          {
              await WriteByteArray(SensorUUIDs.SensorStateUuid, new byte[] { 0x01 });
          }
  
          public async Task&lt;int[]&gt; GetPressure()
          {
              byte[] data = await ReadValue();
  
              if (data != null)
              {
                  int[] values = new int[9];
  
                  values[0] = (int)BitConverter.ToInt32(data, 0);
  
                  for (int i = 1; i &lt; values.Length; i++)
                  {
                      values[i] = (int)BitConverter.ToInt16(data, 2 * i + 2);
                  }
  
                  return values;
              }
  
              else
              {
                  return new int[] { 0 };
              }
          }
  
          public async Task StopCollecting()
          {
              await WriteByteArray(SensorUUIDs.SensorStateUuid, new byte[] { 0x00 });
          }
      }
  }
  </code></pre>
  
  <p>Here is the DataPoint Class that I use to organize the data received from the pressure sensor:</p>
  
  <pre><code>using System;
  using System.Collections.Generic;
  using System.Linq;
  using System.Text;
  using System.Threading.Tasks;
  using System.ComponentModel;
  using System.Runtime.CompilerServices;
  
  namespace BLEInterfaceTest
  {
      public class DataPoint : INotifyPropertyChanged
      {
          private TimeSpan _timestamp;
          private int _packetNumber;
          private double _internalTimestamp;
          private int _pressure1;
          private int _pressure2;
  
          public event PropertyChangedEventHandler PropertyChanged;
  
          public TimeSpan TimeStamp
          {
              get { return _timestamp; }
              set
              {
                  _timestamp = value;
                  this.NotifyPropertyChanged();
              }
          }
  
          public int PacketNumber
          {
              get { return _packetNumber; }
              set
              {
                  _packetNumber = value;
                  this.NotifyPropertyChanged();
              }
          }
          public double InternalTimestamp
          {
              get { return _internalTimestamp; }
              set
              {
                  _internalTimestamp = value;
                  this.NotifyPropertyChanged();
              }
          }
  
          public int PressureValue1
          {
              get { return _pressure1; }
              set
              {
                  _pressure1 = value;
                  this.NotifyPropertyChanged();
              }
          }
  
          public int PressureValue2
          {
              get { return _pressure2; }
              set
              {
                  _pressure2 = value;
                  this.NotifyPropertyChanged();
              }
          }
  
          public DataPoint(TimeSpan time,int packetNumber, double internalTimestamp, int pressure1, int pressure2)
          {
              _timestamp = time;
              _packetNumber = packetNumber;
              _internalTimestamp = internalTimestamp;
              _pressure1 = pressure1;
              _pressure2 = pressure2;
          }
  
          private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
          {
              if (!string.IsNullOrEmpty(propertyName))
              {
                  PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
              }
          }
      }
  }
  </code></pre>
  
  <p>I have researched this extensively, and all I could find was help on how to initiate a disconnection. I have the opposite problem. One page I found stated that the problem might be caused by the device not properly storing the bonding state, but I have checked that and I did initialize the device to save the bonding state.</p>
  
  <p>Interestingly if I do not pair the device to the computer before trying to read information from it then I do not have the problem. The connection never randomly stops. But when I do this, the computer does not receive every packet of data sent from the sensor device. It will receive one or two packets and then skip five or six packets. If I pair the device then I will receive every packet but the connection will randomly cut off.</p>
  
  <p>So my question is two fold, I guess. How do I stop the connection from cutting off when the device is paired? Or alternatively, is there a way to allow the application to receive every packet of data when it is not paired?</p>
  
  <p><strong>UPDATE</strong></p>
  
  <p>I realized I should include more information on my sensor peripheral in case the error is in that code. I am currently designing a rapid prototyping of this sensor before I move on to designing the embedded version. To do this, I am using the BLE Nano 1 from RedBearLabs as a user friendly prototype. I am programing this device with the online MBED compiler. I have included the nRF51822 and BLE_API libraries to handle the bluetooth low energy communication.</p>
  
  <p><strong>UPDATE 2</strong>
  So after more research into what is causing the problem, I have found that the disconnection occurs when a connection interval and a generation 2 garbage collection occur at the same time. In UWP the garbage collector can pause the UI Thread for generation 2 collections. (see <a href="https://docs.microsoft.com/en-us/windows/uwp/debug-test-perf/improve-garbage-collection-performance" rel="nofollow noreferrer">here</a>) </p>
  
  <p>My thought is that if the thread is paused at the beginning of a connection interval, then the central is not able to initiate the connection with the peripheral and the peripheral therefore thinks the client is no longer listening (see more about <a href="https://docs.mbed.com/docs/ble-intros/en/latest/Introduction/ConnectionParameters/" rel="nofollow noreferrer">how BLE connections work</a>).</p>
  
  <p>I discovered this by finding out exactly what is necessary to get the connection back once it has randomly stopped. I started with the entire connection process and reduced it down to this:</p>
  
  <pre><code>public async Task ReconnectDevice()
  {
     GattCommunicationStatus status = await dataCharacteristic.WriteClientCharacteristicConfigurationDescriptorAsync(
        GattClientCharacteristicConfigurationDescriptorValue.Notify);
  
     await WriteByteArray(SensorUUIDs.SensorStateUuid, new byte[] { 0x01 });
  }
  </code></pre>
  
  <p>Because my BluetoothLEDevice, GattService, and GattCharacteristic objects are not disposed, all I need to do is resubscribe to notifications and write a 1 to the device so that it begins collecting data again.</p>
  
  <p>I have reduced my memory allocations in my application significantly since discovering this, and the time for a gen2 collection has decreased to an average of 5 ms. Also, the amount of time before the connection disconnects has increased to around 4-5 sec.</p>
  
  <p>UWP has a GattCharacteristicNotificationTrigger for receiving notifications in a BackgroundTask, but I have never had much success at incorporating background tasks in UWP.</p>
  
  <p>I think I will try next to incorporate the windows.devices into a WPF application where I think I will have a better chance at getting it working.</p>