2017-10-21 332 views
1

因此,我正在设计一个应用程序,用于连接到定制设计的压力传感器的Windows笔记本电脑。应用程序与设备配对,然后每隔10毫秒从设备接收通知。然后由于某种原因,通信停止。我知道这是我的应用程序而不是设备的问题,因为当我连接到手机时,我没有这个问题。UWP蓝牙低功耗应用早期断开连接

这里就是我创建devicewatcher和发现设备的主网页:

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&clcid=0x409 

namespace BLEInterfaceTest 
{ 
    /// <summary> 
    /// An empty page that can be used on its own or navigated to within a Frame. 
    /// </summary> 
    public sealed partial class MainPage : Page 
    { 
     private DeviceWatcher deviceWatcher; 
     private ObservableCollection<DeviceInformation> deviceList = new ObservableCollection<DeviceInformation>(); 

    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, 
       () => { deviceList.Remove(toRemove); }); 
     } 
    } 

    private async void DeviceWatcher_Added(DeviceWatcher sender, DeviceInformation args) 
    { 
     await this.Dispatcher.RunAsync(
      Windows.UI.Core.CoreDispatcherPriority.Normal, 
      () => { deviceList.Add(args); }); 
    } 

    private void deviceListView_ItemClick(object sender, ItemClickEventArgs e) 
    { 
     this.Frame.Navigate(typeof(DevicePage), e.ClickedItem); 
    } 
    } 
}' 

这下一个代码是其中压力传感器被连接到,并且其中数据被从设备中读取的页面。

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 
{ 
    /// <summary> 
    /// An empty page that can be used on its own or navigated to within a Frame. 
    /// </summary> 
    public sealed partial class DevicePage : Page 
    { 
     private DeviceInformation device { get; set; } 
     private PressureSensor pSensor { get; set; } 
     public static DateTime startTime { get; set; } 
     public ObservableCollection<DataPoint> PressureData = new ObservableCollection<DataPoint>(); 
     public static ObservableCollection<DataPoint> 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 && pSensor.MorePacketsAvailable) 
      { 
       int[] values = await pSensor.GetPressure(); 

       int packetNumber = values[0]; 

       if (packetNumber > packetNum) 
       { 
        packetNum = packetNumber; 

        txtValue1.Text = Convert.ToString(values[1]); 
        txtValue2.Text = Convert.ToString(values[5]); 

        for (int i = 1; i < 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 && 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<string>() { ".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(); 
      } 
     } 
    } 
} 

这里就是我定义处理该设备方面,我国SensorBase的和PressureSensor类:

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<byte[]> 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<byte[]>(20); 
     } 

     public bool MorePacketsAvailable 
     { 
      get 
      { 
       if (fifoBuffer.Count > 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<byte[]> 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<int[]> 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 < 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 }); 
     } 
    } 
} 

这里是数据点类,我使用组织从压力传感器接收的数据:

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

我对此进行了广泛的研究,并且我能找到的所有内容都是如何启动断开连接的帮助。我有相反的问题。我发现的一页表明,问题可能是由于设备未正确存储邦定状态而引起的,但我已检查并确实初始化设备以保存邦定状态。

有趣的是,如果我没有将设备与计算机配对,然后尝试从中读取信息,那么我没有问题。连接永远不会随机停止。但是当我这样做时,计算机不会收到传感器设备发送的每个数据包。它将接收一个或两个数据包,然后跳过五个或六个数据包。如果我配对设备,那么我会收到每个数据包,但连接将被随机切断。

所以我的问题是两倍,我猜。设备配对时如何阻止连接切断?或者,有没有办法让应用程序在未配对时接收每个数据包?

UPDATE

我发现我应包括我的传感器的外围的情况下,误差是在该代码的更多信息。在开始设计嵌入式版本之前,我正在设计一款此传感器的快速原型。为此,我使用RedBearLabs的BLE Nano 1作为用户友好的原型。我正在使用在线MBED编译器编程此设备。我已经包含了nRF51822和BLE_API库来处理蓝牙低能量通信。

更新2 所以经过更多的研究是什么原因造成的问题,我发现,当连接间隔和第2代垃圾收集发生在同一时间发生断线。在UWP中,垃圾收集器可以暂停第2代集合的UI线程。 (请参阅here

我的想法是,如果线程在连接间隔开始时暂停,则中央无法启动与外设和外设的连接,因此认为客户端不再监听(查看更多关于how BLE connections work)。

我发现这一点是通过找出究竟是什么是必要的,以获得连接回来,一旦它已经随机停止。我开始与整个连接过程,并减少它归结为这样:

public async Task ReconnectDevice() 
{ 
    GattCommunicationStatus status = await dataCharacteristic.WriteClientCharacteristicConfigurationDescriptorAsync(
     GattClientCharacteristicConfigurationDescriptorValue.Notify); 

    await WriteByteArray(SensorUUIDs.SensorStateUuid, new byte[] { 0x01 }); 
} 

因为我BluetoothLEDevice,GattService和GattCharacteristic对象没有设置,所有我需要做的是重新订阅通知和写1到该设备以便它再次开始收集数据。

自发现此问题以来,我的应用程序显着减少了我的内存分配,并且gen2集合的时间减少到平均5 ms。此外,连接断开之前的时间已增加到大约4-5秒。

UWP有一个GattCharacteristicNotificationTrigger用于在BackgroundTask中接收通知,但我从未在UWP中引入后台任务方面取得太多成功。

我想我会尝试接下来将windows.devices合并到一个WPF应用程序中,我认为我将有更好的机会让它工作。

回答

1

因此,经过一段时间尝试不同的想法,我终于偶然发现了一个解决方案,我的问题。我必须做出2个更改:

  1. 使用未配对连接而不是配对连接。这解决了连接突然中断的问题。

  2. 将连接间隔增加到40毫秒。出于某种原因,当我这样做时,我收到了所有的数据,不再有任何问题。低于40毫秒任何导致通信到Windows设备时丢失的信息(我不得不做出对我的传感器运行的C代码这一变化。)

我现在已经后使用的设备约2个月进行这种改变并且完全没有问题。

0

我觉得这些问题与BluetoothCacheMode Enum有关。 这表明某些蓝牙API方法是否应对系统缓存的值进行操作,或者从蓝牙设备中检索这些值。 使用BluetoothCacheMode.Uncached属性允许服务在需要时更新属性。 如果设备配对,则不需要BluetoothCacheMode(我认为BluetoothCacheMode.Cached是默认设置)。 在你的代码行:

var services = await leDevice.GetGattServicesAsync(BluetoothCacheMode.Uncached); 

可以是如果配对掉线的原因。

GetGattServicesAsync(),GetCharacteristicsAsync()和ReadValueAsync() 必须时不配对,成对的默认或BluetoothCacheMode.Cached当属性BluetoothCacheMode.Uncached。 见https://msdn.microsoft.com/en-us/library/windows/apps/dn263758.aspx

+0

感谢您的快速回复。我尝试了使用BluetoothCacheMode.Cached的程序,但仍然遇到同样的问题。无论哪种方式,我使用通知功能与我的设备和应用程序,所以我从来没有实际调用ReadValueAsync()。我只使用dataCharacteristic_ValueChanged()事件来获取更新的值并将其存储在队列中。 –

+0

in youre code you do: GattReadResult readResult = await dataCharacteristic.ReadValueAsync(); byte [] data = new byte [readResult.Value.Length]; DataReader.FromBuffer(readResult.Value).ReadBytes(data); fifoBuffer.Enqueue(data); – GrooverFromHolland

+0

你是对的。我的代码中有ReadValueAsync(),但只有在没有通知的情况下才会调用该值。如果支持通知,那么它会跳过代码的这一部分,并将最后一个值取出。在这个应用程序中,我启用了通知。我试图让我的SensorBase类尽可能模块化,以便在将来的应用程序中使用它。 –