From 579a918ed0d6181378111f9dfa27bbc8db902f05 Mon Sep 17 00:00:00 2001 From: James G Date: Wed, 23 Jun 2021 14:18:05 -0700 Subject: [PATCH] integrated upf65 updates (#55) --- BluetoothLEExplorer/BluetoothLEExplorer.sln | 1 + .../BluetoothLEExplorer/App.xaml.cs | 2 +- .../BluetoothLEExplorer.csproj | 42 +- .../Models/DisposableObservableCollection.cs | 7 +- .../Models/GattSampleContext.cs | 170 +- .../ObservableBluetoothLEAdvertisement.cs | 311 +++ ...bservableBluetoothLEAdvertisementFilter.cs | 133 ++ .../Models/ObservableBluetoothLEBeacon.cs | 139 ++ .../Models/ObservableBluetoothLEDevice.cs | 1667 +++++++++-------- .../Models/ObservableGattCharacteristics.cs | 1 - .../Models/ObservableGattDescriptors.cs | 5 +- .../Models/ObservableGattDeviceService.cs | 83 +- .../BluetoothLEExplorer/Package.appxmanifest | 2 +- .../AdvertisementDataTypeService.cs | 59 + .../Services/Converters/Converters.cs | 112 ++ .../GattUuidsService/GattUuidsService.cs | 2 +- ...AdvertisementBeaconDetailsPageViewModel.cs | 32 + .../AdvertisementBeaconPageViewModel.cs | 252 +++ .../AdvertisementMonitorPageViewModel.cs | 310 +++ .../ViewModels/AdvertisementPageViewModel.cs | 45 + .../ViewModels/BeaconViewModel.cs | 89 - .../ViewModels/CharacteristicPageViewModel.cs | 69 +- .../ViewModels/DeviceServicesPageViewModel.cs | 36 +- .../ViewModels/DiscoverViewModel.cs | 1 + .../ViewModels/GenericGattServiceViewModel.cs | 23 +- .../ViewModels/ServicePageViewModel.cs | 2 +- .../Views/AdvertisementBeaconDetailsPage.xaml | 90 + .../AdvertisementBeaconDetailsPage.xaml.cs | 30 + .../Views/AdvertisementBeaconPage.xaml | 216 +++ .../Views/AdvertisementBeaconPage.xaml.cs | 52 + .../Views/AdvertisementMonitorPage.xaml | 210 +++ .../Views/AdvertisementMonitorPage.xaml.cs | 30 + .../Views/AdvertisementPage.xaml | 163 ++ .../Views/AdvertisementPage.xaml.cs | 22 + .../BluetoothLEExplorer/Views/Beacon.xaml | 124 -- .../BluetoothLEExplorer/Views/Beacon.xaml.cs | 24 - .../Views/CharacteristicPage.xaml | 1 + .../Views/DeviceServicesPage.xaml | 38 +- .../Views/DeviceServicesPage.xaml.cs | 7 +- .../BluetoothLEExplorer/Views/Discover.xaml | 1 - .../Views/ServicePage.xaml | 5 +- .../Views/ServicePage.xaml.cs | 2 +- .../AlertNotificationServicePage.xaml | 7 +- .../Views/Services/BatteryServicePage.xaml | 3 - .../Services/BloodPressureServicePage.xaml | 6 +- .../Services/CurrentTimeServicePage.xaml | 7 +- .../Views/Services/HeartRateServicePage.xaml | 6 +- .../Views/Services/MicrosoftServicePage.xaml | 14 +- .../Views/SettingsPage.xaml.cs | 8 - .../BluetoothLEExplorer/Views/Shell.xaml | 62 +- .../BluetoothLEExplorerUnitTests.csproj | 4 +- .../GattHelper/Converters/GattConvert.cs | 23 +- .../GattHelper/GattHelper.csproj | 4 +- .../GattServicesLibrary.csproj | 9 +- .../GattServicesLibrary/GenericGattService.cs | 79 +- .../Services/MicrosoftService.cs | 8 + .../SortedObservableCollection.csproj | 9 +- .../SortedObservableCollection/project.json | 2 +- 58 files changed, 3686 insertions(+), 1175 deletions(-) create mode 100644 BluetoothLEExplorer/BluetoothLEExplorer/Models/ObservableBluetoothLEAdvertisement.cs create mode 100644 BluetoothLEExplorer/BluetoothLEExplorer/Models/ObservableBluetoothLEAdvertisementFilter.cs create mode 100644 BluetoothLEExplorer/BluetoothLEExplorer/Models/ObservableBluetoothLEBeacon.cs create mode 100644 BluetoothLEExplorer/BluetoothLEExplorer/Services/AdvertisementService/AdvertisementDataTypeService.cs create mode 100644 BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/AdvertisementBeaconDetailsPageViewModel.cs create mode 100644 BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/AdvertisementBeaconPageViewModel.cs create mode 100644 BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/AdvertisementMonitorPageViewModel.cs create mode 100644 BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/AdvertisementPageViewModel.cs delete mode 100644 BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/BeaconViewModel.cs create mode 100644 BluetoothLEExplorer/BluetoothLEExplorer/Views/AdvertisementBeaconDetailsPage.xaml create mode 100644 BluetoothLEExplorer/BluetoothLEExplorer/Views/AdvertisementBeaconDetailsPage.xaml.cs create mode 100644 BluetoothLEExplorer/BluetoothLEExplorer/Views/AdvertisementBeaconPage.xaml create mode 100644 BluetoothLEExplorer/BluetoothLEExplorer/Views/AdvertisementBeaconPage.xaml.cs create mode 100644 BluetoothLEExplorer/BluetoothLEExplorer/Views/AdvertisementMonitorPage.xaml create mode 100644 BluetoothLEExplorer/BluetoothLEExplorer/Views/AdvertisementMonitorPage.xaml.cs create mode 100644 BluetoothLEExplorer/BluetoothLEExplorer/Views/AdvertisementPage.xaml create mode 100644 BluetoothLEExplorer/BluetoothLEExplorer/Views/AdvertisementPage.xaml.cs delete mode 100644 BluetoothLEExplorer/BluetoothLEExplorer/Views/Beacon.xaml delete mode 100644 BluetoothLEExplorer/BluetoothLEExplorer/Views/Beacon.xaml.cs diff --git a/BluetoothLEExplorer/BluetoothLEExplorer.sln b/BluetoothLEExplorer/BluetoothLEExplorer.sln index c99e463..83fc278 100644 --- a/BluetoothLEExplorer/BluetoothLEExplorer.sln +++ b/BluetoothLEExplorer/BluetoothLEExplorer.sln @@ -99,6 +99,7 @@ Global {AD1CBE3C-A68A-4E02-8001-E02B815560EE}.Release|x86.Deploy.0 = Release|x86 {6F503DF9-71C9-4340-90BB-D9AA14ADB686}.Debug|Any CPU.ActiveCfg = Debug|x86 {6F503DF9-71C9-4340-90BB-D9AA14ADB686}.Debug|Any CPU.Build.0 = Debug|x86 + {6F503DF9-71C9-4340-90BB-D9AA14ADB686}.Debug|Any CPU.Deploy.0 = Debug|x86 {6F503DF9-71C9-4340-90BB-D9AA14ADB686}.Debug|ARM.ActiveCfg = Debug|ARM {6F503DF9-71C9-4340-90BB-D9AA14ADB686}.Debug|ARM.Build.0 = Debug|ARM {6F503DF9-71C9-4340-90BB-D9AA14ADB686}.Debug|ARM.Deploy.0 = Debug|ARM diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/App.xaml.cs b/BluetoothLEExplorer/BluetoothLEExplorer/App.xaml.cs index 08371b3..205c80c 100644 --- a/BluetoothLEExplorer/BluetoothLEExplorer/App.xaml.cs +++ b/BluetoothLEExplorer/BluetoothLEExplorer/App.xaml.cs @@ -103,7 +103,7 @@ private void App_Resuming(object sender, object e) } } - private void App_UnhandledException(object sender, UnhandledExceptionEventArgs e) + private void App_UnhandledException(object sender, Windows.UI.Xaml.UnhandledExceptionEventArgs e) { showDialog(e.Exception.Message + "\n\n" + e.Exception.StackTrace); } diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/BluetoothLEExplorer.csproj b/BluetoothLEExplorer/BluetoothLEExplorer/BluetoothLEExplorer.csproj index 632070f..1128987 100644 --- a/BluetoothLEExplorer/BluetoothLEExplorer/BluetoothLEExplorer.csproj +++ b/BluetoothLEExplorer/BluetoothLEExplorer/BluetoothLEExplorer.csproj @@ -11,8 +11,8 @@ BluetoothLEExplorer en-US UAP - 10.0.17134.0 - 10.0.15063.0 + 10.0.19041.0 + 10.0.18362.0 14 true 512 @@ -113,6 +113,9 @@ + + + @@ -120,6 +123,7 @@ + @@ -127,6 +131,9 @@ + + + @@ -134,7 +141,7 @@ - + @@ -144,6 +151,19 @@ + + AdvertisementBeaconDetailsPage.xaml + + + AdvertisementPage.xaml + + + AdvertisementMonitorPage.xaml + + + MSBuild:Compile + Designer + ServicePage.xaml @@ -165,8 +185,8 @@ BatteryServicePage.xaml - - Beacon.xaml + + AdvertisementBeaconPage.xaml Busy.xaml @@ -242,9 +262,17 @@ MSBuild:Compile PreserveNewest - + + Designer MSBuild:Compile + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile MSBuild:Compile @@ -270,7 +298,7 @@ MSBuild:Compile Designer - + MSBuild:Compile Designer diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Models/DisposableObservableCollection.cs b/BluetoothLEExplorer/BluetoothLEExplorer/Models/DisposableObservableCollection.cs index a2279aa..ed367ad 100644 --- a/BluetoothLEExplorer/BluetoothLEExplorer/Models/DisposableObservableCollection.cs +++ b/BluetoothLEExplorer/BluetoothLEExplorer/Models/DisposableObservableCollection.cs @@ -1,12 +1,9 @@ -// +// // Copyright (c) Microsoft Corporation. All rights reserved. // //---------------------------------------------------------------------------------------------- using System; -using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; using System.Collections.ObjectModel; namespace BluetoothLEExplorer.Models @@ -60,4 +57,4 @@ public void Dispose() temp.Dispose(); } } -} +} \ No newline at end of file diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Models/GattSampleContext.cs b/BluetoothLEExplorer/BluetoothLEExplorer/Models/GattSampleContext.cs index d7933bc..5587a65 100644 --- a/BluetoothLEExplorer/BluetoothLEExplorer/Models/GattSampleContext.cs +++ b/BluetoothLEExplorer/BluetoothLEExplorer/Models/GattSampleContext.cs @@ -18,6 +18,9 @@ using Windows.Foundation.Metadata; using System.Threading; +using Template10.Common; +using Windows.Devices.Bluetooth.GenericAttributeProfile; +using Windows.Storage.Streams; namespace BluetoothLEExplorer.Models { @@ -46,6 +49,10 @@ public class GattSampleContext : INotifyPropertyChanged /// public DisposableObservableCollection BluetoothLEDevices { get; set; } = new DisposableObservableCollection(); + private SemaphoreSlim AdvertisementsLock = new SemaphoreSlim(1, 1); + + public ObservableDictionary Advertisements { get; set; } = new ObservableDictionary(); + /// /// Gets or sets the selected bluetooth device /// @@ -56,6 +63,8 @@ public class GattSampleContext : INotifyPropertyChanged /// public ObservableGattDeviceService SelectedService { get; set; } = null; + public ObservableBluetoothLEAdvertisement SelectedAdvertisement { get; set; } = null; + /// /// Gets or sets the selected characteristic /// @@ -91,6 +100,8 @@ public class GattSampleContext : INotifyPropertyChanged /// private BluetoothLEAdvertisementWatcher advertisementWatcher; + private GattReliableWriteTransaction transaction; + /// /// We need to cache all DeviceInformation objects we get as they may /// get updated in the future. The update may make them eligible to be put on @@ -168,6 +179,25 @@ private set } } + private bool advertisementWatcherStarted = false; + + public bool AdvertisementWatcherStarted + { + get + { + return advertisementWatcherStarted; + } + + private set + { + if (advertisementWatcherStarted != value) + { + advertisementWatcherStarted = value; + OnPropertyChanged(new PropertyChangedEventArgs("AdvertisementWatcherStarted")); + } + } + } + /// /// Source for /// @@ -233,6 +263,30 @@ public bool IsSecureConnectionSupported } } + private bool isTransactionInProgress = false; + + public bool IsTransactionInProgress + { + get + { + return isTransactionInProgress; + } + + private set + { + if (isTransactionInProgress != value) + { + isTransactionInProgress = value; + OnPropertyChanged(new PropertyChangedEventArgs("IsTransactionInProgress")); + } + } + } + + public String AdvertisementContentFilter { get; set; } = ""; + + public ObservableCollection Beacons { get; set; } = new ObservableCollection(); + public ObservableBluetoothLEBeacon SelectedBeacon { get; set; } = null; + /// /// Prevents a default instance of the class from being created. /// @@ -277,11 +331,10 @@ private async void Init() devNodeWatcher.Start(); - return; + advertisementWatcher = new BluetoothLEAdvertisementWatcher(); + advertisementWatcher.Received += AdvertisementWatcher_Received; } - - private async void DevNodeWatcher_Added(DeviceWatcher sender, DeviceInformation args) { try @@ -434,15 +487,14 @@ public void StartEnumeration() deviceWatcher.EnumerationCompleted += DeviceWatcher_EnumerationCompleted; deviceWatcher.Stopped += DeviceWatcher_Stopped; - advertisementWatcher = new BluetoothLEAdvertisementWatcher(); - advertisementWatcher.Received += AdvertisementWatcher_Received; - ClearAllDevices(); deviceWatcher.Start(); - advertisementWatcher.Start(); IsEnumerating = true; EnumerationFinished = false; + + UpdateAdvertisementFilter(new BluetoothLEAdvertisementFilter()); + StartAdvertisementWatcher(BluetoothLEScanningMode.Active); } /// @@ -450,6 +502,8 @@ public void StartEnumeration() /// public void StopEnumeration() { + StopAdvertisementWatcher(); + if (deviceWatcher != null) { // Unregister the event handlers. @@ -459,19 +513,73 @@ public void StopEnumeration() deviceWatcher.EnumerationCompleted -= DeviceWatcher_EnumerationCompleted; deviceWatcher.Stopped -= DeviceWatcher_Stopped; - advertisementWatcher.Received -= AdvertisementWatcher_Received; - // Stop the watchers deviceWatcher.Stop(); deviceWatcher = null; - advertisementWatcher.Stop(); - advertisementWatcher = null; IsEnumerating = false; EnumerationFinished = false; } } + public void StartAdvertisementWatcher(BluetoothLEScanningMode scanningMode) + { + if (!AdvertisementWatcherStarted) + { + Advertisements.Clear(); + advertisementWatcher.ScanningMode = scanningMode; + if (ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 10)) + { + advertisementWatcher.AllowExtendedAdvertisements = true; + } + advertisementWatcher.Start(); + AdvertisementWatcherStarted = true; + } + } + + public void StopAdvertisementWatcher() + { + if (AdvertisementWatcherStarted) + { + advertisementWatcher.Stop(); + AdvertisementWatcherStarted = false; + } + } + + public void UpdateAdvertisementFilter(BluetoothLEAdvertisementFilter filter) + { + advertisementWatcher.AdvertisementFilter = filter; + if (AdvertisementWatcherStarted) + { + advertisementWatcher.Stop(); + Advertisements.Clear(); + advertisementWatcher.Start(); + } + } + + public void CreateTransaction() + { + transaction = new GattReliableWriteTransaction(); + IsTransactionInProgress = true; + } + + public async void CommitTransaction() + { + var result = await transaction.CommitWithResultAsync(); + if (result.Status == GattCommunicationStatus.Success) + { + + } + + transaction = null; + IsTransactionInProgress = false; + } + + public void WriteTransaction(GattCharacteristic characteristic, IBuffer value) + { + transaction.WriteValue(characteristic, value); + } + /// /// Updates device metadata based on advertisement received /// @@ -481,9 +589,9 @@ private async void AdvertisementWatcher_Received(BluetoothLEAdvertisementWatcher { try { + await AddAdvertisementToList(args); await BluetoothLEDevicesLock.WaitAsync(); - foreach (ObservableBluetoothLEDevice d in BluetoothLEDevices) { if (d.BluetoothAddressAsUlong == args.BluetoothAddress) @@ -492,14 +600,7 @@ await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatch Windows.UI.Core.CoreDispatcherPriority.Normal, () => { - if (args.Advertisement.ServiceUuids != null) - { - d.ServiceCount = args.Advertisement.ServiceUuids.Count(); - } - else - { - Debug.WriteLine("Observed ADVs without ServicesUuids at UPF61"); - } + d.Update(args); }); } } @@ -552,7 +653,7 @@ private async void DeviceWatcher_Updated(DeviceWatcher sender, DeviceInformation { ObservableBluetoothLEDevice dev; - // Need to lock as another DeviceWatcher might be modifying BluetoothLEDevices + // Need to lock as another DeviceWatcher might be modifying BluetoothLEDevices try { await BluetoothLEDevicesLock.WaitAsync(); @@ -624,7 +725,7 @@ private async void DeviceWatcher_Removed(DeviceWatcher sender, DeviceInformation try { - // Need to lock as another DeviceWatcher might be modifying BluetoothLEDevices + // Need to lock as another DeviceWatcher might be modifying BluetoothLEDevices await BluetoothLEDevicesLock.WaitAsync(); // Find the corresponding DeviceInformation in the collection and remove it. @@ -693,10 +794,10 @@ private async Task AddDeviceToList(DeviceInformation deviceInfo) (bool)dev.DeviceInfo.Properties["System.Devices.Aep.IsConnected"])) || ((dev.DeviceInfo.Properties.Keys.Contains("System.Devices.Aep.IsPaired") && (bool)dev.DeviceInfo.Properties["System.Devices.Aep.IsPaired"])); - + if (shouldDisplay) { - // Need to lock as another DeviceWatcher might be modifying BluetoothLEDevices + // Need to lock as another DeviceWatcher might be modifying BluetoothLEDevices try { await BluetoothLEDevicesLock.WaitAsync(); @@ -731,6 +832,27 @@ await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatch } } + private async Task AddAdvertisementToList(BluetoothLEAdvertisementReceivedEventArgs advertisementEvent) + { + try + { + await AdvertisementsLock.WaitAsync(); + + await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync( + Windows.UI.Core.CoreDispatcherPriority.Normal, + () => + { + var advertisement = new ObservableBluetoothLEAdvertisement(advertisementEvent); + + Advertisements[advertisement.InternalHashString] = advertisement; + }); + } + finally + { + AdvertisementsLock.Release(); + } + } + /// /// Executes when device watcher has stopped /// diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Models/ObservableBluetoothLEAdvertisement.cs b/BluetoothLEExplorer/BluetoothLEExplorer/Models/ObservableBluetoothLEAdvertisement.cs new file mode 100644 index 0000000..e2b0902 --- /dev/null +++ b/BluetoothLEExplorer/BluetoothLEExplorer/Models/ObservableBluetoothLEAdvertisement.cs @@ -0,0 +1,311 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +//---------------------------------------------------------------------------------------------- +using System; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using BluetoothLEExplorer.Services.DispatcherService; +using Windows.Devices.Bluetooth; +using Windows.Devices.Bluetooth.Advertisement; +using Windows.Devices.Bluetooth.GenericAttributeProfile; +using Windows.Devices.Enumeration; +using Windows.UI.Popups; +using Windows.UI.Xaml.Media.Imaging; +using Windows.Foundation.Metadata; +using System.Collections; +using System.Collections.Generic; +using GattHelper.Converters; +using BluetoothLEExplorer.Services.AdvertisementHelpers; +using Windows.UI.Xaml.Media.Animation; + +namespace BluetoothLEExplorer.Models +{ + public class ObservableBluetoothLEAdvertisementSection : INotifyPropertyChanged + { + public string TypeAsString + { + get; + private set; + } + + public string TypeAsDisplayString + { + get; + private set; + } + + public string DataAsString + { + get; + private set; + } + + public string DataAsDisplayString + { + get; + private set; + } + + public ObservableBluetoothLEAdvertisementSection(BluetoothLEAdvertisementDataSection section) + { + TypeAsString = section.DataType.ToString("X2"); + TypeAsDisplayString = AdvertisementDataTypeHelper.ConvertSectionTypeToString(section.DataType); + + DataAsString = GattConvert.ToHexString(section.Data); + if (section.DataType == BluetoothLEAdvertisementDataTypes.Flags) + { + var flagsInt = GattConvert.ToInt32(section.Data); + DataAsDisplayString = ((BluetoothLEAdvertisementFlags)Enum.ToObject(typeof(BluetoothLEAdvertisementFlags), flagsInt)).ToString(); + } + else if (section.DataType == BluetoothLEAdvertisementDataTypes.CompleteLocalName || + section.DataType == BluetoothLEAdvertisementDataTypes.ShortenedLocalName) + { + DataAsDisplayString = GattConvert.ToUTF8String(section.Data); + } + else if (section.DataType == BluetoothLEAdvertisementDataTypes.TxPowerLevel) + { + var txPowerLevel = GattConvert.ToInt16(section.Data); + DataAsDisplayString = txPowerLevel.ToString(); + } + else + { + DataAsDisplayString = ""; + } + } + + /// + /// Event to notify when this object has changed + /// + public event PropertyChangedEventHandler PropertyChanged; + + /// + /// Executes when this class changes + /// + /// + private void OnPropertyChanged(PropertyChangedEventArgs e) + { + if (PropertyChanged != null) + { + PropertyChanged(this, e); + } + } + } + + public class ObservableBluetoothLEAdvertisement : INotifyPropertyChanged + { + public class RssiComparer : IComparer + { + public int Compare(object x, object y) + { + var a = x as ObservableBluetoothLEAdvertisement; + var b = y as ObservableBluetoothLEAdvertisement; + + if (a == null || b == null) + { + throw new InvalidOperationException("Compared objects are not ObservableBluetoothLEAdvertisement"); + } + + // If they're equal + if (SmoothValue(a.Rssi) == SmoothValue(b.Rssi)) + { + return 0; + } + + if (a.Rssi < b.Rssi) + { + return 1; + } + else + { + return -1; + } + } + + private short SmoothValue(short value) + { + var remainder = (value % 10); + if (remainder >= 5) + { + return (short)(value - remainder + 10); + } + return (short)(value - remainder); + } + } + + /// + /// Gets the bluetooth address of this device as a string + /// + public string AddressAsString + { + get; + private set; + } + + public BluetoothAddressType AddressType { get; private set; } = BluetoothAddressType.Unspecified; + + public BluetoothLEAdvertisementType Type + { + get; + private set; + } + + public Int16 Rssi + { + get; + private set; + } + + public bool Anonymous { get; private set; } = false; + + public bool Connectable { get; private set; } = false; + public bool Scannable { get; private set; } = false; + public bool Directed { get; private set; } = false; + public bool ScanResponse { get; private set; } = false; + + public Nullable TxPower { get; private set; } = null; + + public String PayloadAsString + { + get + { + String payload = ""; + foreach (var section in DataSections) + { + payload += String.Format("{0}-{1}-", section.TypeAsString, section.DataAsString); + } + return payload.TrimEnd('-'); + } + } + + public String InternalHashString + { + get; + private set; + } + + public DateTime Timestamp + { + get; + private set; + } + + public ObservableCollection DataSections { get; } = new ObservableCollection(); + + public ObservableBluetoothLEAdvertisement(BluetoothLEAdvertisementReceivedEventArgs advertisementEvent) + { + AddressAsString = advertisementEvent.BluetoothAddress.ToString("X12"); + if (ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 10)) + { + AddressType = advertisementEvent.BluetoothAddressType; + Anonymous = advertisementEvent.IsAnonymous; + Connectable = advertisementEvent.IsConnectable; + Scannable = advertisementEvent.IsScannable; + Directed = advertisementEvent.IsDirected; + ScanResponse = advertisementEvent.IsScanResponse; + TxPower = advertisementEvent.TransmitPowerLevelInDBm; + } + else + { + if (advertisementEvent.AdvertisementType == BluetoothLEAdvertisementType.ConnectableDirected) + { + Connectable = true; + Directed = true; + } + else if (advertisementEvent.AdvertisementType == BluetoothLEAdvertisementType.ConnectableUndirected) + { + Connectable = true; + } + else if (advertisementEvent.AdvertisementType == BluetoothLEAdvertisementType.ScannableUndirected) + { + Scannable = true; + } + else if (advertisementEvent.AdvertisementType == BluetoothLEAdvertisementType.ScanResponse) + { + ScanResponse = true; + } + } + + Type = advertisementEvent.AdvertisementType; + Rssi = advertisementEvent.RawSignalStrengthInDBm; + Timestamp = advertisementEvent.Timestamp.LocalDateTime; + + foreach (var section in advertisementEvent.Advertisement.DataSections) + { + DataSections.Add(new ObservableBluetoothLEAdvertisementSection(section)); + } + + InternalHashString = AddressAsString + PayloadAsString; + } + + /// + /// Event to notify when this object has changed + /// + public event PropertyChangedEventHandler PropertyChanged; + + public void Update(ObservableBluetoothLEAdvertisement other) + { + if (AddressType != other.AddressType) + { + AddressType = other.AddressType; + OnPropertyChanged(new PropertyChangedEventArgs("AddressType")); + } + + if (TxPower != other.TxPower) + { + TxPower = other.TxPower; + OnPropertyChanged(new PropertyChangedEventArgs("TxPower")); + } + + if (Type != other.Type) + { + Type = other.Type; + OnPropertyChanged(new PropertyChangedEventArgs("Type")); + } + + if (Rssi != other.Rssi) + { + Rssi = other.Rssi; + OnPropertyChanged(new PropertyChangedEventArgs("Rssi")); + } + + if (Timestamp != other.Timestamp) + { + Timestamp = other.Timestamp; + OnPropertyChanged(new PropertyChangedEventArgs("Timestamp")); + } + + Anonymous = other.Anonymous; + Connectable = other.Connectable; + Scannable = other.Scannable; + Directed = other.Directed; + ScanResponse = other.ScanResponse; + } + + public override int GetHashCode() + { + return InternalHashString.GetHashCode(); + } + + public override bool Equals(object obj) + { + return InternalHashString.Equals(((ObservableBluetoothLEAdvertisement)obj).InternalHashString); + } + + /// + /// Executes when this class changes + /// + /// + private void OnPropertyChanged(PropertyChangedEventArgs e) + { + if (PropertyChanged != null) + { + PropertyChanged(this, e); + } + } + } +} diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Models/ObservableBluetoothLEAdvertisementFilter.cs b/BluetoothLEExplorer/BluetoothLEExplorer/Models/ObservableBluetoothLEAdvertisementFilter.cs new file mode 100644 index 0000000..1646dee --- /dev/null +++ b/BluetoothLEExplorer/BluetoothLEExplorer/Models/ObservableBluetoothLEAdvertisementFilter.cs @@ -0,0 +1,133 @@ +using System; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Windows.Devices.Bluetooth; +using Windows.Devices.Bluetooth.Advertisement; +using Windows.Foundation.Metadata; +using Windows.Storage.Streams; +using GattHelper.Converters; +using BluetoothLEExplorer.Services.AdvertisementHelpers; +using Windows.Security.Cryptography; + +namespace BluetoothLEExplorer.Models +{ + public class ObservableBluetoothLEAdvertisementFilter : INotifyPropertyChanged + { + public enum DataFormatType + { + NotSet, + Hex, + String, + } + + public string Name + { + get; + private set; + } + + public byte SectionType + { + get; + private set; + } + + public byte SectionOffset + { + get; + set; + } + + public string SectionDataString + { + get; + set; + } + + public bool DataSectionRawFilter + { + get + { + return true; + } + } + + private DataFormatType sectionDataFormat = DataFormatType.Hex; + + public DataFormatType SectionDataFormat + { + get + { + return sectionDataFormat; + } + + set + { + if (value == DataFormatType.NotSet) + { + return; + } + + if (sectionDataFormat != value) + { + sectionDataFormat = value; + OnPropertyChanged(new PropertyChangedEventArgs("SectionDataFormat")); + } + } + } + + public ObservableBluetoothLEAdvertisementFilter(byte sectionType) + { + SectionType = sectionType; + Name = AdvertisementDataTypeHelper.ConvertSectionTypeToString(sectionType); + } + + /// + /// Event to notify when this object has changed + /// + public event PropertyChangedEventHandler PropertyChanged; + + /// + /// Executes when this class changes + /// + /// + private void OnPropertyChanged(PropertyChangedEventArgs e) + { + if (PropertyChanged != null) + { + PropertyChanged(this, e); + } + } + + public BluetoothLEAdvertisementBytePattern GetBytePattern() + { + var pattern = new BluetoothLEAdvertisementBytePattern(); + pattern.DataType = SectionType; + pattern.Offset = SectionOffset; + if (SectionDataString != null && SectionDataString.Length > 0) + { + if (sectionDataFormat == DataFormatType.Hex) + { + // pad the value if we've received odd number of bytes + if (SectionDataString.Length % 2 == 1) + { + pattern.Data = GattConvert.ToIBufferFromHexString("0" + SectionDataString); + } + else + { + pattern.Data = GattConvert.ToIBufferFromHexString(SectionDataString); + } + } + else if (sectionDataFormat == DataFormatType.String) + { + pattern.Data = CryptographicBuffer.ConvertStringToBinary(SectionDataString, BinaryStringEncoding.Utf8); + } + } + return pattern; + } + } +} diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Models/ObservableBluetoothLEBeacon.cs b/BluetoothLEExplorer/BluetoothLEExplorer/Models/ObservableBluetoothLEBeacon.cs new file mode 100644 index 0000000..f4a6596 --- /dev/null +++ b/BluetoothLEExplorer/BluetoothLEExplorer/Models/ObservableBluetoothLEBeacon.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Diagnostics; +using System.Runtime.InteropServices.WindowsRuntime; +using Windows.Devices.Bluetooth; +using Windows.Devices.Bluetooth.Advertisement; +using Windows.Foundation.Metadata; +using GattHelper.Converters; + +namespace BluetoothLEExplorer.Models +{ + + public class ObservableBluetoothLEBeacon : INotifyPropertyChanged + { + private GattSampleContext Context { get; set; } + + private BluetoothLEAdvertisementPublisher publisher; + + public String Name { get; private set; } + + private BluetoothLEAdvertisementPublisherStatus status = BluetoothLEAdvertisementPublisherStatus.Created; + + bool isPublishing = false; + + public bool IsPublishing + { + get + { + return isPublishing; + } + + set + { + if (value != isPublishing) + { + if (value) + { + Start(); + } + else + { + Stop(); + } + } + } + } + + public bool IsReady { get; private set; } = true; + + public event PropertyChangedEventHandler PropertyChanged; + + public ObservableBluetoothLEBeacon( + byte[] payload, + bool useExtendedFormat, + bool isAnonymous, + bool includeTxPower, + Int16? txPower) + { + Context = GattSampleContext.Context; + + var payloadString = GattConvert.ToHexString(payload.AsBuffer()); + Name = payloadString.Substring(0, Math.Min(8, payloadString.Length)); + + var advertisement = new BluetoothLEAdvertisement(); + advertisement.ManufacturerData.Add(new BluetoothLEManufacturerData(0x0006, payload.AsBuffer())); + + publisher = new BluetoothLEAdvertisementPublisher(advertisement); + + if (ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 10)) + { + publisher.UseExtendedAdvertisement = useExtendedFormat; + publisher.IsAnonymous = isAnonymous; + publisher.IncludeTransmitPowerLevel = includeTxPower; + publisher.PreferredTransmitPowerLevelInDBm = txPower; + } + + publisher.StatusChanged += Publisher_StatusChanged; + } + + private void Publisher_StatusChanged(BluetoothLEAdvertisementPublisher sender, BluetoothLEAdvertisementPublisherStatusChangedEventArgs args) + { + if (args.Status == BluetoothLEAdvertisementPublisherStatus.Started) + { + if (!isPublishing) + { + isPublishing = true; + OnPropertyChanged("IsPublishing"); + } + IsReady = true; + } + else if (args.Status == BluetoothLEAdvertisementPublisherStatus.Stopped || + args.Status == BluetoothLEAdvertisementPublisherStatus.Aborted) + { + if (isPublishing) + { + isPublishing = false; + OnPropertyChanged("IsPublishing"); + } + IsReady = true; + } + } + + public void Start() + { + IsReady = false; + publisher.Start(); + } + + public void Stop() + { + IsReady = false; + publisher.Stop(); + } + + public void Destroy() + { + Stop(); + Context.Beacons.Remove(this); + } + + private async void OnPropertyChanged(string propertyName) + { + try + { + await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync( + Windows.UI.Core.CoreDispatcherPriority.Normal, + () => { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); }); + } + catch (Exception e) + { + Debug.Fail(String.Format("Failed to update property '{0}' due to {1}", propertyName, e.ToString())); + } + } + } +} diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Models/ObservableBluetoothLEDevice.cs b/BluetoothLEExplorer/BluetoothLEExplorer/Models/ObservableBluetoothLEDevice.cs index 850a2bd..b85e348 100644 --- a/BluetoothLEExplorer/BluetoothLEExplorer/Models/ObservableBluetoothLEDevice.cs +++ b/BluetoothLEExplorer/BluetoothLEExplorer/Models/ObservableBluetoothLEDevice.cs @@ -1,803 +1,864 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// -//---------------------------------------------------------------------------------------------- -using System; -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using BluetoothLEExplorer.Services.DispatcherService; -using Windows.Devices.Bluetooth; -using Windows.Devices.Bluetooth.GenericAttributeProfile; -using Windows.Devices.Enumeration; -using Windows.UI.Popups; -using Windows.UI.Xaml.Media.Imaging; -using Windows.Foundation.Metadata; -using System.Collections; -using System.Collections.Generic; -using BluetoothLEExplorer.Services.GattUuidHelpers; - -namespace BluetoothLEExplorer.Models -{ - /// - /// Wrapper around to make it easier to use - /// - public class ObservableBluetoothLEDevice : INotifyPropertyChanged, IEquatable, IDisposable - { - /// - /// Compares RSSI values between ObservableBluetoothLEDevice. Sorts based on closest to furthest where 0 is unknown - /// and is sorted as furthest away - /// - public class RSSIComparer : IComparer - { - public int Compare(object x, object y) - { - ObservableBluetoothLEDevice a = x as ObservableBluetoothLEDevice; - ObservableBluetoothLEDevice b = y as ObservableBluetoothLEDevice; - - if( a == null || b == null) - { - throw new InvalidOperationException("Compared objects are not ObservableBluetoothLEDevice"); - } - - // If they're equal - if(a.RSSI == b.RSSI) - { - return 0; - } - - // RSSI == 0 means we don't know it. Always make that the end. - if(b.RSSI == 0) - { - return -1; - } - - if(a.RSSI < b.RSSI || a.rssi == 0) - { - return 1; - } - else - { - return -1; - } - } - } - - /// - /// Source for - /// - private BluetoothLEDevice bluetoothLEDevice; - - /// - /// Gets the bluetooth device this class wraps - /// - public BluetoothLEDevice BluetoothLEDevice - { - get - { - return bluetoothLEDevice; - } - - private set - { - bluetoothLEDevice = value; - OnPropertyChanged(new PropertyChangedEventArgs("BluetoothLEDevice")); - } - } - - /// - /// Source for - /// - private BitmapImage glyph; - - /// - /// Gets or sets the glyph of this bluetooth device - /// - public BitmapImage Glyph - { - get - { - return glyph; - } - - set - { - glyph = value; - OnPropertyChanged(new PropertyChangedEventArgs("Glyph")); - } - } - - /// - /// Source for - /// - private DeviceInformation deviceInfo; - - /// - /// Gets the device information for the device this class wraps - /// - public DeviceInformation DeviceInfo - { - get - { - return deviceInfo; - } - - private set - { - deviceInfo = value; - OnPropertyChanged(new PropertyChangedEventArgs("DeviceInfo")); - } - } - - /// - /// Source for - /// - private bool isConnected; - - /// - /// Gets or sets a value indicating whether this device is connected - /// - public bool IsConnected - { - get - { - return isConnected; - } - - set - { - if (isConnected != value) - { - isConnected = value; - OnPropertyChanged(new PropertyChangedEventArgs("IsConnected")); - } - } - } - - /// - /// Gets if the device is connectable - /// - public bool IsConnectable - { - get - { - return DeviceInfo.Properties.Keys.Contains("System.Devices.Aep.Bluetooth.Le.IsConnectable") && - (bool)DeviceInfo.Properties["System.Devices.Aep.Bluetooth.Le.IsConnectable"]; - } - } - - /// - /// Source for - /// - private DateTime lastSeenTime; - - /// - /// Gets or sets a value indicating the last time an advertisement was seen from the device - /// - public DateTime LastSeenTime - { - get - { - return lastSeenTime; - } - - set - { - if (lastSeenTime != value) - { - lastSeenTime = value; - OnPropertyChanged(new PropertyChangedEventArgs("LastSeenTime")); - } - } - } - - /// - /// Source for - /// - private bool isPaired; - - /// - /// Gets or sets a value indicating whether this device is paired - /// - public bool IsPaired - { - get - { - return isPaired; - } - - set - { - if (isPaired != value) - { - isPaired = value; - OnPropertyChanged(new PropertyChangedEventArgs("IsPaired")); - } - } - } - - /// - /// Source for - /// - private bool canPair; - - /// - /// Gets or sets a value indicating whether this device is paired - /// - public bool CanPair - { - get - { - return canPair; - } - - set - { - if (canPair != value) - { - canPair = value; - OnPropertyChanged(new PropertyChangedEventArgs("CanPair")); - } - } - } - - private bool isSecureConnection; - - public bool IsSecureConnection - { - get - { - return isSecureConnection; - } - - set - { - if (isSecureConnection != value) - { - isSecureConnection = value; - OnPropertyChanged(new PropertyChangedEventArgs("IsSecureConnection")); - } - } - } - - // Make this variable static so we only query IsPropertyPresent once - private static bool isSecureConnectionSupported = ApiInformation.IsPropertyPresent("Windows.Devices.Bluetooth.BluetoothLEDevice", "WasSecureConnectionUsedForPairing"); - - /// - /// Source for - /// - private DisposableObservableCollection services = new DisposableObservableCollection(); - - /// - /// Gets the services this device supports - /// - public DisposableObservableCollection Services - { - get - { - return services; - } - - private set - { - services = value; - OnPropertyChanged(new PropertyChangedEventArgs("Services")); - } - } - - /// - /// Source for - /// - private int serviceCount; - - /// - /// Gets or sets the number of services this device has - /// - public int ServiceCount - { - get - { - return serviceCount; - } - - set - { - if (serviceCount < value) - { - serviceCount = value; - OnPropertyChanged(new PropertyChangedEventArgs("ServiceCount")); - } - } - } - - /// - /// Source for - /// - private string name; - - /// - /// Gets the name of this device - /// - public string Name - { - get - { - return name; - } - - private set - { - if (name != value) - { - name = value; - OnPropertyChanged(new PropertyChangedEventArgs("Name")); - } - } - } - - /// - /// Source for - /// - private string errorText; - - /// - /// Gets the error text when connecting to this device fails - /// - public string ErrorText - { - get - { - return errorText; - } - - private set - { - errorText = value; - OnPropertyChanged(new PropertyChangedEventArgs("ErrorText")); - } - } - - private Queue RssiValue = new Queue(10); - - /// - /// Source for - /// - private int rssi; - - /// - /// Gets the RSSI value of this device - /// - public int RSSI - { - get - { - return rssi; - } - - private set - { - if (RssiValue.Count >= 10) - { - RssiValue.Dequeue(); - } - RssiValue.Enqueue(value); - - int newValue = (int)Math.Round(RssiValue.Average(), 0); - - if (rssi != newValue) - { - rssi = newValue; - OnPropertyChanged(new PropertyChangedEventArgs("RSSI")); - } - } - } - - private string bluetoothAddressAsString; - /// - /// Gets the bluetooth address of this device as a string - /// - public string BluetoothAddressAsString - { - get - { - return bluetoothAddressAsString; - } - - private set - { - if(bluetoothAddressAsString != value) - { - bluetoothAddressAsString = value; - OnPropertyChanged(new PropertyChangedEventArgs("BluetoothAddressAsString")); - } - } - } - - private ulong bluetoothAddressAsUlong; - /// - /// Gets the bluetooth address of this device - /// - public ulong BluetoothAddressAsUlong - { - get - { - return bluetoothAddressAsUlong; - } - - private set - { - if (bluetoothAddressAsUlong != value) - { - bluetoothAddressAsUlong = value; - OnPropertyChanged(new PropertyChangedEventArgs("BluetoothAddressAsUlong")); - } - } - } - - /// - /// Releases references to Services and the BluetoothLEDevice - /// - public void Dispose() - { - Services.Clear(); - var temp = bluetoothLEDevice; - - BluetoothLEDevice = null; - - if (temp != null) - { - temp.ConnectionStatusChanged -= BluetoothLEDevice_ConnectionStatusChanged; - temp.NameChanged -= BluetoothLEDevice_NameChanged; - temp.Dispose(); - } - IsConnected = false; - } - - /// - /// Initializes a new instance of the class. - /// - /// The device info that describes this bluetooth device"/> - public ObservableBluetoothLEDevice(DeviceInformation deviceInfo) - { - DeviceInfo = deviceInfo; - Name = DeviceInfo.Name; - - string ret = String.Empty; - - if(DeviceInfo.Properties.ContainsKey("System.Devices.Aep.DeviceAddress")) - { - BluetoothAddressAsString = ret = DeviceInfo.Properties["System.Devices.Aep.DeviceAddress"].ToString(); - BluetoothAddressAsUlong = Convert.ToUInt64(BluetoothAddressAsString.Replace(":", String.Empty), 16); - } - - IsPaired = DeviceInfo.Pairing.IsPaired; - CanPair = DeviceInfo.Pairing.CanPair; - - LoadGlyph(); - - this.PropertyChanged += ObservableBluetoothLEDevice_PropertyChanged; - } - - private void ObservableBluetoothLEDevice_PropertyChanged(object sender, PropertyChangedEventArgs e) - { - if (e.PropertyName == "DeviceInfo") - { - if (DeviceInfo.Properties.ContainsKey("System.Devices.Aep.Bluetooth.LastSeenTime") && (DeviceInfo.Properties["System.Devices.Aep.Bluetooth.LastSeenTime"] != null)) - { - LastSeenTime = ((System.DateTimeOffset)DeviceInfo.Properties["System.Devices.Aep.Bluetooth.LastSeenTime"]).UtcDateTime; - } - - if (DeviceInfo.Properties.ContainsKey("System.Devices.Aep.SignalStrength") && (DeviceInfo.Properties["System.Devices.Aep.SignalStrength"] != null)) - { - RSSI = (int)DeviceInfo.Properties["System.Devices.Aep.SignalStrength"]; - } - } - } - - /// - /// result of finding all the services - /// - private GattDeviceServicesResult result; - - /// - /// Event to notify when this object has changed - /// - public event PropertyChangedEventHandler PropertyChanged; - - /// - /// Connect to this bluetooth device - /// - /// Connection task - public async Task Connect() - { - bool ret = false; - string debugMsg = String.Format("Connect: "); - - Debug.WriteLine(debugMsg + "Entering"); - - await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunTaskAsync(async () => - { - Debug.WriteLine(debugMsg + "In UI thread"); - try - { - if (bluetoothLEDevice == null) - { - Debug.WriteLine(debugMsg + "Calling BluetoothLEDevice.FromIdAsync"); - BluetoothLEDevice = await BluetoothLEDevice.FromIdAsync(DeviceInfo.Id); - - if (bluetoothLEDevice == null) - { - ret = false; - Debug.WriteLine(debugMsg + "BluetoothLEDevice is null"); - - MessageDialog dialog = new MessageDialog("No permission to access device", "Connection error"); - await dialog.ShowAsync(); - return; - } - else - { - // Setup our event handlers and view model properties - BluetoothLEDevice.ConnectionStatusChanged += BluetoothLEDevice_ConnectionStatusChanged; - BluetoothLEDevice.NameChanged += BluetoothLEDevice_NameChanged; - } - } - else - { - Debug.WriteLine(debugMsg + "Previously connected, not calling BluetoothLEDevice.FromIdAsync"); - } - - Debug.WriteLine(debugMsg + "BluetoothLEDevice is " + BluetoothLEDevice.Name); - - Name = bluetoothLEDevice.Name; - CanPair = DeviceInfo.Pairing.CanPair; - IsPaired = DeviceInfo.Pairing.IsPaired; - IsConnected = BluetoothLEDevice.ConnectionStatus == BluetoothConnectionStatus.Connected; - - UpdateSecureConnectionStatus(); - - // Get all the services for this device - CancellationTokenSource GetGattServicesAsyncTokenSource = new CancellationTokenSource(5000); - - BluetoothCacheMode cacheMode = BluetoothLEExplorer.Services.SettingsServices.SettingsService.Instance.UseCaching ? BluetoothCacheMode.Cached : BluetoothCacheMode.Uncached; - - // In case we connected before, clear the service list and recreate it - Services.Clear(); - - var GetGattServicesAsyncTask = Task.Run(() => BluetoothLEDevice.GetGattServicesAsync(cacheMode), GetGattServicesAsyncTokenSource.Token); - - result = await GetGattServicesAsyncTask.Result; - - if (result.Status == GattCommunicationStatus.Success) - { - System.Diagnostics.Debug.WriteLine(debugMsg + "GetGattServiceAsync SUCCESS"); - foreach (var serv in result.Services) - { - if (!GattServiceUuidHelper.IsReserved(serv.Uuid)) - { - ObservableGattDeviceService temp = new ObservableGattDeviceService(serv); - // This isn't awaited so that the user can disconnect while the services are still being enumerated - temp.Initialize(); - Services.Add(temp); - } - else - { - serv.Dispose(); - } - } - - ServiceCount = Services.Count(); - ret = true; - } - else if (result.Status == GattCommunicationStatus.ProtocolError) - { - ErrorText = debugMsg + "GetGattServiceAsync Error: Protocol Error - " + result.ProtocolError.Value; - System.Diagnostics.Debug.WriteLine(ErrorText); - string msg = "Connection protocol error: " + result.ProtocolError.Value.ToString(); - var messageDialog = new MessageDialog(msg, "Connection failures"); - await messageDialog.ShowAsync(); - - } - else if (result.Status == GattCommunicationStatus.Unreachable) - { - ErrorText = debugMsg + "GetGattServiceAsync Error: Unreachable"; - System.Diagnostics.Debug.WriteLine(ErrorText); - string msg = "Device unreachable"; - var messageDialog = new MessageDialog(msg, "Connection failures"); - await messageDialog.ShowAsync(); - } - } - catch (Exception ex) - { - Debug.WriteLine(debugMsg + "Exception - " + ex.Message); - string msg = String.Format("Message:\n{0}\n\nInnerException:\n{1}\n\nStack:\n{2}", ex.Message, ex.InnerException, ex.StackTrace); - - var messageDialog = new MessageDialog(msg, "Exception"); - await messageDialog.ShowAsync(); - - // Debugger break here so we can catch unknown exceptions - Debugger.Break(); - } - }); - - if (ret) - { - Debug.WriteLine(debugMsg + "Exiting (0)"); - } - else - { - Debug.WriteLine(debugMsg + "Exiting (-1)"); - } - - return ret; - } - - public async Task DoInAppPairing() - { - Debug.WriteLine("Trying in app pairing"); - - // BT_Code: Pair the currently selected device. - DevicePairingResult result = await DeviceInfo.Pairing.PairAsync(); - - CanPair = DeviceInfo.Pairing.CanPair; - IsPaired = DeviceInfo.Pairing.IsPaired; - - Debug.WriteLine($"Pairing result: {result.Status.ToString()}"); - - if (result.Status == DevicePairingResultStatus.Paired || - result.Status == DevicePairingResultStatus.AlreadyPaired) - { - return true; - } - else - { - MessageDialog d = new MessageDialog("Pairing error", result.Status.ToString()); - await d.ShowAsync(); - return false; - } - } - - /// - /// Executes when the name of this devices changes - /// - /// - /// - private async void BluetoothLEDevice_NameChanged(BluetoothLEDevice sender, object args) - { - await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync( - Windows.UI.Core.CoreDispatcherPriority.Normal, - () => - { - try - { - Name = BluetoothLEDevice.Name; - } - catch - { - Name = "Unknown (exception)"; - } - - }); - } - - /// - /// Executes when the connection state changes - /// - /// - /// - private async void BluetoothLEDevice_ConnectionStatusChanged(BluetoothLEDevice sender, object args) - { - await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync( - Windows.UI.Core.CoreDispatcherPriority.Normal, - () => - { - IsPaired = DeviceInfo.Pairing.IsPaired; - CanPair = DeviceInfo.Pairing.CanPair; - try - { - IsConnected = bluetoothLEDevice.ConnectionStatus == BluetoothConnectionStatus.Connected; - UpdateSecureConnectionStatus(); - } - catch - { - IsConnected = false; - } - }); - } - - /// - /// Load the glyph for this device - /// - private async void LoadGlyph() - { - await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync( - Windows.UI.Core.CoreDispatcherPriority.Normal, - async () => - { - try - { - DeviceThumbnail deviceThumbnail = await DeviceInfo.GetGlyphThumbnailAsync(); - BitmapImage glyphBitmapImage = new BitmapImage(); - await glyphBitmapImage.SetSourceAsync(deviceThumbnail); - Glyph = glyphBitmapImage; - } - catch (Exception) - { - Glyph = null; - } - }); - } - - /// - /// Executes when a property is changed - /// - /// - private void OnPropertyChanged(PropertyChangedEventArgs e) - { - if (PropertyChanged != null) - { - PropertyChanged(this, e); - } - } - - /// - /// Overrides the ToString function to return the name of the device - /// - /// Name of this characteristic - public override string ToString() - { - return this.name; - } - - /// - /// Compares this device to other bluetooth devices by checking the id - /// - /// - /// true for equal - bool IEquatable.Equals(ObservableBluetoothLEDevice other) - { - if (other == null) - { - return false; - } - - if (this.DeviceInfo.Id == other.DeviceInfo.Id) - { - return true; - } - else - { - return false; - } - } - - /// - /// Updates this device's deviceInformation - /// - /// - public void Update(DeviceInformationUpdate deviceUpdate) - { - DeviceInfo.Update(deviceUpdate); - - OnPropertyChanged(new PropertyChangedEventArgs("DeviceInfo")); - } - - /// - /// Helper method which checks to see if the Secure Connection APIs are supported - /// and updates the current status. - /// - private void UpdateSecureConnectionStatus() - { - if (isSecureConnectionSupported) - { - IsSecureConnection = bluetoothLEDevice.WasSecureConnectionUsedForPairing; - } - else - { - IsSecureConnection = false; - } - } - } -} +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +//---------------------------------------------------------------------------------------------- +using System; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using BluetoothLEExplorer.Services.DispatcherService; +using Windows.Devices.Bluetooth; +using Windows.Devices.Bluetooth.Advertisement; +using Windows.Devices.Bluetooth.GenericAttributeProfile; +using Windows.Devices.Enumeration; +using Windows.UI.Popups; +using Windows.UI.Xaml.Media.Imaging; +using Windows.Foundation.Metadata; +using System.Collections; +using System.Collections.Generic; +using BluetoothLEExplorer.Services.GattUuidHelpers; + +namespace BluetoothLEExplorer.Models +{ + /// + /// Wrapper around to make it easier to use + /// + public class ObservableBluetoothLEDevice : INotifyPropertyChanged, IEquatable, IDisposable + { + /// + /// Compares RSSI values between ObservableBluetoothLEDevice. Sorts based on closest to furthest where 0 is unknown + /// and is sorted as furthest away + /// + public class RSSIComparer : IComparer + { + public int Compare(object x, object y) + { + ObservableBluetoothLEDevice a = x as ObservableBluetoothLEDevice; + ObservableBluetoothLEDevice b = y as ObservableBluetoothLEDevice; + + if( a == null || b == null) + { + throw new InvalidOperationException("Compared objects are not ObservableBluetoothLEDevice"); + } + + // If they're equal + if(a.RSSI == b.RSSI) + { + return 0; + } + + // RSSI == 0 means we don't know it. Always make that the end. + if(b.RSSI == 0) + { + return -1; + } + + if(a.RSSI < b.RSSI || a.rssi == 0) + { + return 1; + } + else + { + return -1; + } + } + } + + /// + /// Source for + /// + private BluetoothLEDevice bluetoothLEDevice; + + /// + /// Gets the bluetooth device this class wraps + /// + public BluetoothLEDevice BluetoothLEDevice + { + get + { + return bluetoothLEDevice; + } + + private set + { + bluetoothLEDevice = value; + OnPropertyChanged(new PropertyChangedEventArgs("BluetoothLEDevice")); + } + } + + /// + /// Source for + /// + private BitmapImage glyph; + + /// + /// Gets or sets the glyph of this bluetooth device + /// + public BitmapImage Glyph + { + get + { + return glyph; + } + + set + { + glyph = value; + OnPropertyChanged(new PropertyChangedEventArgs("Glyph")); + } + } + + /// + /// Source for + /// + private DeviceInformation deviceInfo; + + /// + /// Gets the device information for the device this class wraps + /// + public DeviceInformation DeviceInfo + { + get + { + return deviceInfo; + } + + private set + { + deviceInfo = value; + OnPropertyChanged(new PropertyChangedEventArgs("DeviceInfo")); + } + } + + /// + /// Source for + /// + private bool isConnected; + + /// + /// Gets or sets a value indicating whether this device is connected + /// + public bool IsConnected + { + get + { + return isConnected; + } + + set + { + if (isConnected != value) + { + isConnected = value; + OnPropertyChanged(new PropertyChangedEventArgs("IsConnected")); + } + } + } + + /// + /// Gets if the device is connectable + /// + public bool IsConnectable + { + get + { + return DeviceInfo.Properties.Keys.Contains("System.Devices.Aep.Bluetooth.Le.IsConnectable") && + (bool)DeviceInfo.Properties["System.Devices.Aep.Bluetooth.Le.IsConnectable"]; + } + } + + /// + /// Source for + /// + private DateTime lastSeenTime; + + /// + /// Gets or sets a value indicating the last time an advertisement was seen from the device + /// + public DateTime LastSeenTime + { + get + { + return lastSeenTime; + } + + set + { + if (lastSeenTime != value) + { + lastSeenTime = value; + OnPropertyChanged(new PropertyChangedEventArgs("LastSeenTime")); + } + } + } + + /// + /// Source for + /// + private bool isPaired; + + /// + /// Gets or sets a value indicating whether this device is paired + /// + public bool IsPaired + { + get + { + return isPaired; + } + + set + { + if (isPaired != value) + { + isPaired = value; + OnPropertyChanged(new PropertyChangedEventArgs("IsPaired")); + } + } + } + + /// + /// Source for + /// + private bool canPair; + + /// + /// Gets or sets a value indicating whether this device is paired + /// + public bool CanPair + { + get + { + return canPair; + } + + set + { + if (canPair != value) + { + canPair = value; + OnPropertyChanged(new PropertyChangedEventArgs("CanPair")); + } + } + } + + private bool isSecureConnection; + + public bool IsSecureConnection + { + get + { + return isSecureConnection; + } + + set + { + if (isSecureConnection != value) + { + isSecureConnection = value; + OnPropertyChanged(new PropertyChangedEventArgs("IsSecureConnection")); + } + } + } + + // Make this variable static so we only query IsPropertyPresent once + private static bool isSecureConnectionSupported = ApiInformation.IsPropertyPresent("Windows.Devices.Bluetooth.BluetoothLEDevice", "WasSecureConnectionUsedForPairing"); + + private ObservableCollection advertisements = new ObservableCollection(); + + public ObservableCollection Advertisements + { + get + { + return advertisements; + } + } + + /// + /// Source for + /// + private DisposableObservableCollection services = new DisposableObservableCollection(); + + /// + /// Gets the services this device supports + /// + public DisposableObservableCollection Services + { + get + { + return services; + } + + private set + { + services = value; + OnPropertyChanged(new PropertyChangedEventArgs("Services")); + } + } + + /// + /// Source for + /// + private int serviceCount; + + /// + /// Gets or sets the number of services this device has + /// + public int ServiceCount + { + get + { + return serviceCount; + } + + private set + { + if (serviceCount < value) + { + serviceCount = value; + OnPropertyChanged(new PropertyChangedEventArgs("ServiceCount")); + } + } + } + + private int advertisementServiceCount; + + public int AdvertisementServiceCount + { + get + { + return advertisementServiceCount; + } + + private set + { + if (advertisementServiceCount < value) + { + advertisementServiceCount = value; + OnPropertyChanged(new PropertyChangedEventArgs("AdvertisementServiceCount")); + } + } + } + + /// + /// Source for + /// + private string name; + + /// + /// Gets the name of this device + /// + public string Name + { + get + { + return name; + } + + private set + { + if (name != value) + { + name = value; + OnPropertyChanged(new PropertyChangedEventArgs("Name")); + } + } + } + + /// + /// Source for + /// + private string errorText; + + /// + /// Gets the error text when connecting to this device fails + /// + public string ErrorText + { + get + { + return errorText; + } + + private set + { + errorText = value; + OnPropertyChanged(new PropertyChangedEventArgs("ErrorText")); + } + } + + private Queue RssiValue = new Queue(10); + + /// + /// Source for + /// + private int rssi; + + /// + /// Gets the RSSI value of this device + /// + public int RSSI + { + get + { + return rssi; + } + + private set + { + if (RssiValue.Count >= 10) + { + RssiValue.Dequeue(); + } + RssiValue.Enqueue(value); + + int newValue = (int)Math.Round(RssiValue.Average(), 0); + + if (rssi != newValue) + { + rssi = newValue; + OnPropertyChanged(new PropertyChangedEventArgs("RSSI")); + } + } + } + + private string bluetoothAddressAsString; + /// + /// Gets the bluetooth address of this device as a string + /// + public string BluetoothAddressAsString + { + get + { + return bluetoothAddressAsString; + } + + private set + { + if(bluetoothAddressAsString != value) + { + bluetoothAddressAsString = value; + OnPropertyChanged(new PropertyChangedEventArgs("BluetoothAddressAsString")); + } + } + } + + private ulong bluetoothAddressAsUlong; + /// + /// Gets the bluetooth address of this device + /// + public ulong BluetoothAddressAsUlong + { + get + { + return bluetoothAddressAsUlong; + } + + private set + { + if (bluetoothAddressAsUlong != value) + { + bluetoothAddressAsUlong = value; + OnPropertyChanged(new PropertyChangedEventArgs("BluetoothAddressAsUlong")); + } + } + } + + /// + /// Releases references to Services and the BluetoothLEDevice + /// + public void Dispose() + { + Services.Clear(); + var temp = bluetoothLEDevice; + BluetoothLEDevice = null; + + if (temp != null) + { + // Tear down event handlers + temp.ConnectionStatusChanged -= BluetoothLEDevice_ConnectionStatusChanged; + temp.NameChanged -= BluetoothLEDevice_NameChanged; + temp.GattServicesChanged -= BluetoothLEDevice_GattServicesChanged; + temp.Dispose(); + } + + IsConnected = false; + } + + /// + /// Initializes a new instance of the class. + /// + /// The device info that describes this bluetooth device"/> + public ObservableBluetoothLEDevice(DeviceInformation deviceInfo) + { + DeviceInfo = deviceInfo; + Name = DeviceInfo.Name; + + string ret = String.Empty; + + if (DeviceInfo.Properties.ContainsKey("System.Devices.Aep.DeviceAddress")) + { + BluetoothAddressAsString = ret = DeviceInfo.Properties["System.Devices.Aep.DeviceAddress"].ToString(); + BluetoothAddressAsUlong = Convert.ToUInt64(BluetoothAddressAsString.Replace(":", String.Empty), 16); + } + + IsPaired = DeviceInfo.Pairing.IsPaired; + CanPair = DeviceInfo.Pairing.CanPair; + + LoadGlyph(); + + this.PropertyChanged += ObservableBluetoothLEDevice_PropertyChanged; + } + + private void ObservableBluetoothLEDevice_PropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == "DeviceInfo") + { + if (DeviceInfo.Properties.ContainsKey("System.Devices.Aep.Bluetooth.LastSeenTime") && (DeviceInfo.Properties["System.Devices.Aep.Bluetooth.LastSeenTime"] != null)) + { + LastSeenTime = ((System.DateTimeOffset)DeviceInfo.Properties["System.Devices.Aep.Bluetooth.LastSeenTime"]).UtcDateTime; + } + } + } + + /// + /// result of finding all the services + /// + private GattDeviceServicesResult result; + + /// + /// Event to notify when this object has changed + /// + public event PropertyChangedEventHandler PropertyChanged; + + /// + /// Connect to this bluetooth device + /// + /// Connection task + public async Task Connect() + { + bool ret = false; + string debugMsg = String.Format("Connect: "); + + Debug.WriteLine(debugMsg + "Entering"); + + await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunTaskAsync(async () => + { + Debug.WriteLine(debugMsg + "In UI thread"); + try + { + if (bluetoothLEDevice == null) + { + Debug.WriteLine(debugMsg + "Calling BluetoothLEDevice.FromIdAsync"); + BluetoothLEDevice = await BluetoothLEDevice.FromIdAsync(DeviceInfo.Id); + } + else + { + Debug.WriteLine(debugMsg + "Previously connected, not calling BluetoothLEDevice.FromIdAsync"); + } + + if (bluetoothLEDevice == null) + { + ret = false; + Debug.WriteLine(debugMsg + "BluetoothLEDevice is null"); + + MessageDialog dialog = new MessageDialog("No permission to access device", "Connection error"); + await dialog.ShowAsync(); + } + else + { + Debug.WriteLine(debugMsg + "BluetoothLEDevice is " + BluetoothLEDevice.Name); + + // Setup our event handlers and view model properties + BluetoothLEDevice.ConnectionStatusChanged += BluetoothLEDevice_ConnectionStatusChanged; + BluetoothLEDevice.NameChanged += BluetoothLEDevice_NameChanged; + BluetoothLEDevice.GattServicesChanged += BluetoothLEDevice_GattServicesChanged; + + IsPaired = DeviceInfo.Pairing.IsPaired; + CanPair = DeviceInfo.Pairing.CanPair; + IsConnected = BluetoothLEDevice.ConnectionStatus == BluetoothConnectionStatus.Connected; + Name = BluetoothLEDevice.Name; + + UpdateSecureConnectionStatus(); + + BluetoothCacheMode cacheMode = BluetoothLEExplorer.Services.SettingsServices.SettingsService.Instance.UseCaching ? BluetoothCacheMode.Cached : BluetoothCacheMode.Uncached; + + ret = await GetAllPrimaryServices(cacheMode); + } + } + catch (Exception ex) + { + Debug.WriteLine(debugMsg + "Exception - " + ex.Message); + string msg = String.Format("Message:\n{0}\n\nInnerException:\n{1}\n\nStack:\n{2}", ex.Message, ex.InnerException, ex.StackTrace); + + var messageDialog = new MessageDialog(msg, "Exception"); + await messageDialog.ShowAsync(); + + // Debugger break here so we can catch unknown exceptions + Debugger.Break(); + } + }); + + if (ret) + { + Debug.WriteLine(debugMsg + "Exiting (0)"); + } + else + { + Debug.WriteLine(debugMsg + "Exiting (-1)"); + } + + return ret; + } + + private async Task GetAllPrimaryServices(BluetoothCacheMode cacheMode) + { + var succeeded = false; + string debugMsg = String.Format("GetAllPrimaryServices: "); + + Debug.WriteLine(debugMsg + "Entering"); + + // Get all the services for this device + var result = await BluetoothLEDevice.GetGattServicesAsync(cacheMode); + var returnedServices = new DisposableObservableCollection(); + + if (result.Status == GattCommunicationStatus.Success) + { + System.Diagnostics.Debug.WriteLine(debugMsg + "GetGattServiceAsync SUCCESS"); + foreach (var serv in result.Services) + { + if (!GattServiceUuidHelper.IsReserved(serv.Uuid)) + { + var temp = new ObservableGattDeviceService(serv); + // This isn't awaited so that the user can disconnect while the services are still being enumerated + temp.Initialize(); + returnedServices.Add(temp); + } + else + { + serv.Dispose(); + } + } + + succeeded = true; + } + else if (result.Status == GattCommunicationStatus.ProtocolError) + { + ErrorText = debugMsg + "GetGattServiceAsync Error: Protocol Error - " + result.ProtocolError.Value; + System.Diagnostics.Debug.WriteLine(ErrorText); + string msg = "Connection protocol error: " + result.ProtocolError.Value.ToString(); + var messageDialog = new MessageDialog(msg, "Connection failures"); + await messageDialog.ShowAsync(); + } + else if (result.Status == GattCommunicationStatus.Unreachable) + { + ErrorText = debugMsg + "GetGattServiceAsync Error: Unreachable"; + System.Diagnostics.Debug.WriteLine(ErrorText); + string msg = "Device unreachable"; + var messageDialog = new MessageDialog(msg, "Connection failures"); + await messageDialog.ShowAsync(); + } + + lock (Services) + { + // In case we connected before, clear the service list and recreate it + Services.Clear(); + Services = returnedServices; + ServiceCount = Services.Count(); + } + + return succeeded; + } + + public async Task DoInAppPairing() + { + Debug.WriteLine("Trying in app pairing"); + + // BT_Code: Pair the currently selected device. + DevicePairingResult result = await DeviceInfo.Pairing.PairAsync(); + + CanPair = DeviceInfo.Pairing.CanPair; + IsPaired = DeviceInfo.Pairing.IsPaired; + + Debug.WriteLine($"Pairing result: {result.Status.ToString()}"); + + if (result.Status == DevicePairingResultStatus.Paired || + result.Status == DevicePairingResultStatus.AlreadyPaired) + { + return true; + } + else + { + MessageDialog d = new MessageDialog("Pairing error", result.Status.ToString()); + await d.ShowAsync(); + return false; + } + } + + /// + /// Executes when the name of this devices changes + /// + /// + /// + private async void BluetoothLEDevice_NameChanged(BluetoothLEDevice sender, object args) + { + await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync( + Windows.UI.Core.CoreDispatcherPriority.Normal, + () => + { + try + { + Name = BluetoothLEDevice.Name; + } + catch + { + Name = "Unknown (exception)"; + } + }); + } + + private async void BluetoothLEDevice_GattServicesChanged(BluetoothLEDevice sender, object args) + { + await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunTaskAsync(async () => + { + // If we received a service change. If we are in the middle of discovery services, + // we can ignore the service change. + await GetAllPrimaryServices(IsPaired ? BluetoothCacheMode.Cached : BluetoothCacheMode.Uncached); + }); + } + + /// + /// Executes when the connection state changes + /// + /// + /// + private async void BluetoothLEDevice_ConnectionStatusChanged(BluetoothLEDevice sender, object args) + { + await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync( + Windows.UI.Core.CoreDispatcherPriority.Normal, + () => + { + IsPaired = DeviceInfo.Pairing.IsPaired; + CanPair = DeviceInfo.Pairing.CanPair; + try + { + IsConnected = bluetoothLEDevice.ConnectionStatus == BluetoothConnectionStatus.Connected; + UpdateSecureConnectionStatus(); + } + catch + { + IsConnected = false; + } + }); + } + + /// + /// Load the glyph for this device + /// + private async void LoadGlyph() + { + await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync( + Windows.UI.Core.CoreDispatcherPriority.Normal, + async () => + { + try + { + DeviceThumbnail deviceThumbnail = await DeviceInfo.GetGlyphThumbnailAsync(); + BitmapImage glyphBitmapImage = new BitmapImage(); + await glyphBitmapImage.SetSourceAsync(deviceThumbnail); + Glyph = glyphBitmapImage; + } + catch (Exception) + { + Glyph = null; + } + }); + } + + /// + /// Executes when a property is changed + /// + /// + private void OnPropertyChanged(PropertyChangedEventArgs e) + { + if (PropertyChanged != null) + { + PropertyChanged(this, e); + } + } + + /// + /// Overrides the ToString function to return the name of the device + /// + /// Name of this characteristic + public override string ToString() + { + return this.name; + } + + /// + /// Compares this device to other bluetooth devices by checking the id + /// + /// + /// true for equal + bool IEquatable.Equals(ObservableBluetoothLEDevice other) + { + if (other == null) + { + return false; + } + + if (this.DeviceInfo.Id == other.DeviceInfo.Id) + { + return true; + } + else + { + return false; + } + } + + /// + /// Updates this device's deviceInformation + /// + /// + public void Update(DeviceInformationUpdate deviceUpdate) + { + DeviceInfo.Update(deviceUpdate); + + OnPropertyChanged(new PropertyChangedEventArgs("DeviceInfo")); + } + + /// + /// Helper method which checks to see if the Secure Connection APIs are supported + /// and updates the current status. + /// + private void UpdateSecureConnectionStatus() + { + if (isSecureConnectionSupported) + { + IsSecureConnection = bluetoothLEDevice.WasSecureConnectionUsedForPairing; + } + else + { + IsSecureConnection = false; + } + } + + public void Update(BluetoothLEAdvertisementReceivedEventArgs advertisementEvent) + { + // Update our current RSSI for the device. + RSSI = advertisementEvent.RawSignalStrengthInDBm; + + if (advertisementEvent.Advertisement.ServiceUuids != null) + { + AdvertisementServiceCount = advertisementEvent.Advertisement.ServiceUuids.Count(); + } + + Advertisements.Add(new ObservableBluetoothLEAdvertisement(advertisementEvent)); + } + } +} diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Models/ObservableGattCharacteristics.cs b/BluetoothLEExplorer/BluetoothLEExplorer/Models/ObservableGattCharacteristics.cs index aaeaf1e..c0e7c07 100644 --- a/BluetoothLEExplorer/BluetoothLEExplorer/Models/ObservableGattCharacteristics.cs +++ b/BluetoothLEExplorer/BluetoothLEExplorer/Models/ObservableGattCharacteristics.cs @@ -320,7 +320,6 @@ public ObservableGattCharacteristics(GattCharacteristic characteristic, Observab { Characteristic = characteristic; Parent = parent; - Name = GattCharacteristicUuidHelper.ConvertUuidToName(characteristic.Uuid); UUID = characteristic.Uuid.ToString(); } diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Models/ObservableGattDescriptors.cs b/BluetoothLEExplorer/BluetoothLEExplorer/Models/ObservableGattDescriptors.cs index 57cec96..da1cad3 100644 --- a/BluetoothLEExplorer/BluetoothLEExplorer/Models/ObservableGattDescriptors.cs +++ b/BluetoothLEExplorer/BluetoothLEExplorer/Models/ObservableGattDescriptors.cs @@ -166,13 +166,12 @@ public ObservableGattDescriptors(GattDescriptor descriptor, ObservableGattCharac { Descriptor = descriptor; Parent = parent; + Name = GattDescriptorUuidHelper.ConvertUuidToName(descriptor.Uuid); + UUID = descriptor.Uuid.ToString(); } public async Task Initialize() { - Name = GattDescriptorUuidHelper.ConvertUuidToName(descriptor.Uuid); - UUID = descriptor.Uuid.ToString(); - await ReadValueAsync(); PropertyChanged += ObservableGattDescriptors_PropertyChanged; } diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Models/ObservableGattDeviceService.cs b/BluetoothLEExplorer/BluetoothLEExplorer/Models/ObservableGattDeviceService.cs index 400beae..f88e6db 100644 --- a/BluetoothLEExplorer/BluetoothLEExplorer/Models/ObservableGattDeviceService.cs +++ b/BluetoothLEExplorer/BluetoothLEExplorer/Models/ObservableGattDeviceService.cs @@ -160,6 +160,25 @@ public string UUID } } + private ushort attributeHandle; + + public ushort AttributeHandle + { + get + { + return attributeHandle; + } + + set + { + if (attributeHandle != value) + { + attributeHandle = value; + OnPropertyChanged(new PropertyChangedEventArgs("AttributeHandle")); + } + } + } + /// /// Determines if the SelectedCharacteristic_PropertyChanged has been added /// @@ -178,6 +197,28 @@ public ObservableGattDeviceService(GattDeviceService service) public async Task Initialize() { + await GetAllServiceAttributes(); + } + + private async Task GetAllServiceAttributes() + { + StringBuilder sb = new StringBuilder(); + sb.Append("ObservableGattDeviceService::GetAllServiceAttributes: "); + sb.Append(Name); + + // Request the necessary access permissions for the service and abort + // if permissions are denied. + GattOpenStatus status = await Service.OpenAsync(GattSharingMode.SharedReadAndWrite); + if (status != GattOpenStatus.Success && status != GattOpenStatus.AlreadyOpened) + { + string error = " - Error: " + status.ToString(); + Name += error; + sb.Append(error); + Debug.WriteLine(sb.ToString()); + return; + } + + await GetAllIncludedServices(); await GetAllCharacteristics(); } @@ -213,7 +254,7 @@ public async Task TurnOnAllNotifications() { if (gattchar.Characteristic.CharacteristicProperties.HasFlag(GattCharacteristicProperties.Notify)) { - if(!await gattchar.SetNotify()) + if (!await gattchar.SetNotify()) { success = false; } @@ -232,7 +273,7 @@ public async Task TurnOffAllNotifications() { if (gattchar.Characteristic.CharacteristicProperties.HasFlag(GattCharacteristicProperties.Notify)) { - if(!await gattchar.StopNotify()) + if (!await gattchar.StopNotify()) { success = false; } @@ -341,6 +382,41 @@ public bool IsIndicateSet() return true; } + private async Task GetAllIncludedServices() + { + StringBuilder sb = new StringBuilder(); + sb.Append("ObservableGattDeviceService::getAllIncludedServices: "); + sb.Append(name); + + try + { + var result = await service.GetIncludedServicesAsync(Services.SettingsServices.SettingsService.Instance.UseCaching ? BluetoothCacheMode.Cached : BluetoothCacheMode.Uncached); + if (result.Status == GattCommunicationStatus.Success) + { + sb.Append(" - getAllIncludedServices found "); + sb.Append(result.Services.Count()); + sb.Append(" services"); + Debug.WriteLine(sb); + } + else if (result.Status == GattCommunicationStatus.Unreachable) + { + sb.Append(" - getAllIncludedServices failed with Unreachable"); + Debug.WriteLine(sb.ToString()); + } + else if (result.Status == GattCommunicationStatus.ProtocolError) + { + sb.Append(" - getAllIncludedServices failed with Unreachable"); + Debug.WriteLine(sb.ToString()); + } + + } + catch (Exception ex) + { + Debug.WriteLine("getAllIncludedServices: Exception - {0}" + ex.Message); + Name += " - Exception: " + ex.Message; + } + } + /// /// Gets all the characteristics of this service /// @@ -362,11 +438,10 @@ private async Task GetAllCharacteristics() Name += error; sb.Append(error); Debug.WriteLine(sb.ToString()); - return; } - GattCharacteristicsResult result = await service.GetCharacteristicsAsync(Services.SettingsServices.SettingsService.Instance.UseCaching ? BluetoothCacheMode.Cached : BluetoothCacheMode.Uncached); + var result = await service.GetCharacteristicsAsync(Services.SettingsServices.SettingsService.Instance.UseCaching ? BluetoothCacheMode.Cached : BluetoothCacheMode.Uncached); if (result.Status == GattCommunicationStatus.Success) { diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Package.appxmanifest b/BluetoothLEExplorer/BluetoothLEExplorer/Package.appxmanifest index 3f8bfc5..d5dd1ca 100644 --- a/BluetoothLEExplorer/BluetoothLEExplorer/Package.appxmanifest +++ b/BluetoothLEExplorer/BluetoothLEExplorer/Package.appxmanifest @@ -1,6 +1,6 @@  - + Bluetooth LE Explorer diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Services/AdvertisementService/AdvertisementDataTypeService.cs b/BluetoothLEExplorer/BluetoothLEExplorer/Services/AdvertisementService/AdvertisementDataTypeService.cs new file mode 100644 index 0000000..54c201f --- /dev/null +++ b/BluetoothLEExplorer/BluetoothLEExplorer/Services/AdvertisementService/AdvertisementDataTypeService.cs @@ -0,0 +1,59 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +//---------------------------------------------------------------------------------------------- +using System; +using Windows.Devices.Bluetooth; +using Windows.Devices.Bluetooth.Advertisement; + +namespace BluetoothLEExplorer.Services.AdvertisementHelpers +{ + enum AdvertisementSectionType : byte + { + Flags = 0x01, + IncompleteService16BitUuids = 0x02, + CompleteService16BitUuids = 0x03, + IncompleteService32BitUuids = 0x04, + CompleteService32BitUuids = 0x05, + IncompleteService128BitUuids = 0x06, + CompleteService128BitUuids = 0x07, + ShortenedLocalName = 0x08, + CompleteLocalName = 0x09, + TxPowerLevel = 0x0A, + ClassOfDevice = 0x0D, + SimplePairingHashC192 = 0x0E, + SimplePairingRandomizerR192 = 0x0F, + SecurityManagerTKValues = 0x10, + SecurityManagerOutOfBandFlags = 0x11, + SlaveConnectionIntervalRange = 0x12, + ServiceSolicitation16BitUuids = 0x14, + ServiceSolicitation32BitUuids = 0x1F, + ServiceSolicitation128BitUuids = 0x15, + ServiceData16BitUuids = 0x16, + ServiceData32BitUuids = 0x20, + ServiceData128BitUuids = 0x21, + PublicTargetAddress = 0x17, + RandomTargetAddress = 0x18, + Appearance = 0x19, + AdvertisingInterval = 0x1A, + LEBluetoothDeviceAddress = 0x1B, + LERole = 0x1C, + SimplePairingHashC256 = 0x1D, + SimplePairingRandomizerR256 = 0x1E, + ThreeDimensionInformationData = 0x3D, + ManufacturerSpecificData = 0xFF, + } + + public static class AdvertisementDataTypeHelper + { + public static string ConvertSectionTypeToString(byte sectionType) + { + AdvertisementSectionType convertedSectionType; + if (Enum.TryParse(sectionType.ToString(), out convertedSectionType)) + { + return convertedSectionType.ToString(); + } + return sectionType.ToString("X2"); + } + } +} diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Services/Converters/Converters.cs b/BluetoothLEExplorer/BluetoothLEExplorer/Services/Converters/Converters.cs index be4b480..83fad9b 100644 --- a/BluetoothLEExplorer/BluetoothLEExplorer/Services/Converters/Converters.cs +++ b/BluetoothLEExplorer/BluetoothLEExplorer/Services/Converters/Converters.cs @@ -8,6 +8,10 @@ using BluetoothLEExplorer.ViewModels; using Windows.UI.Xaml; using Windows.UI.Xaml.Data; +using GattHelper.Converters; +using Windows.Networking.BackgroundTransfer; +using System.Runtime.InteropServices.WindowsRuntime; +using Windows.Storage.Streams; [module: System.Diagnostics.CodeAnalysis.SuppressMessage( "StyleCop.CSharp.DocumentationRules", @@ -21,6 +25,101 @@ namespace BluetoothLEExplorer.Services.Converters { + class ByteArrayToStringConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, string culture) + { + var byteArray = value as byte[]; + if (byteArray != null) + { + return GattConvert.ToHexString(byteArray.AsBuffer()); + } + return ""; + } + + public object ConvertBack(object value, Type targetType, object parameter, string culture) + { + var byteString = value as String; + if (byteString != null) + { + var data = GattConvert.ToIBufferFromHexString(byteString); + + return data.ToArray(); + } + return null; + } + } + + public class ScanningModeToBooleanConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, string culture) + { + Debug.WriteLine($"Convert: value = {value.ToString()}, {parameter.ToString()}"); + return value.ToString().Equals(parameter); + } + + public object ConvertBack(object value, Type targetType, object parameter, string culture) + { + Debug.Write($"ConvertBack: {value.ToString()}, "); + AdvertisementMonitorPageViewModel.MonitorScanningMode type = AdvertisementMonitorPageViewModel.MonitorScanningMode.NotSet; + if (value.Equals(true)) + { + Debug.WriteLine("value true"); + object ret = Enum.Parse(type.GetType(), parameter as string); + Debug.WriteLine($"Enum.Parse(type.GetType(), parameter as string) - {ret.ToString()}"); + return ret; + } + else + { + Debug.WriteLine("unsetting value"); + return DependencyProperty.UnsetValue; + } + } + } + + public class DataFormatTypeToBooleanConverter : IValueConverter + { + /// + /// Display type radio button converter + /// + /// + /// + /// + /// + /// return value of display type radio button converter + public object Convert(object value, Type targetType, object parameter, string culture) + { + Debug.WriteLine($"Convert: value = {value.ToString()}, {parameter.ToString()}"); + return value.ToString().Equals(parameter); + } + + /// + /// Display type radio button back converter + /// + /// + /// + /// + /// + /// Return value of display type radio button back converter + public object ConvertBack(object value, Type targetType, object parameter, string culture) + { + Debug.Write($"ConvertBack: {value.ToString()}, "); + ObservableBluetoothLEAdvertisementFilter.DataFormatType type = ObservableBluetoothLEAdvertisementFilter.DataFormatType.NotSet; + if (value.Equals(true)) + { + Debug.WriteLine("value true"); + object ret = Enum.Parse(type.GetType(), parameter as string); + Debug.WriteLine($"Enum.Parse(type.GetType(), parameter as string) - {ret.ToString()}"); + return ret; + } + else + { + Debug.WriteLine("unsetting value"); + return DependencyProperty.UnsetValue; + } + } + } + /// /// Converter to change to a boolean. /// This is used in to determine if a radio button is checked or not @@ -109,6 +208,19 @@ public object ConvertBack(object value, Type targetType, object parameter, strin } } + public class StringFormatConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, string language) + { + return string.Format(parameter as string, value); + } + + public object ConvertBack(object value, Type targetType, object parameter, string language) + { + return null; + } + } + /// /// Uses a boolean to determine if text should be crossed out /// diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Services/GattUuidsService/GattUuidsService.cs b/BluetoothLEExplorer/BluetoothLEExplorer/Services/GattUuidsService/GattUuidsService.cs index 5c33f80..f3ffb24 100644 --- a/BluetoothLEExplorer/BluetoothLEExplorer/Services/GattUuidsService/GattUuidsService.cs +++ b/BluetoothLEExplorer/BluetoothLEExplorer/Services/GattUuidsService/GattUuidsService.cs @@ -42,7 +42,7 @@ public static bool IsReserved(Guid uuid) { return true; } - + return false; } } diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/AdvertisementBeaconDetailsPageViewModel.cs b/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/AdvertisementBeaconDetailsPageViewModel.cs new file mode 100644 index 0000000..5d401c4 --- /dev/null +++ b/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/AdvertisementBeaconDetailsPageViewModel.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Collections.ObjectModel; + +using Template10.Mvvm; +using Template10.Services.NavigationService; + +using Windows.UI.Xaml.Navigation; +using Windows.Devices.Bluetooth.Advertisement; + +using BluetoothLEExplorer.Models; +using BluetoothLEExplorer.Services.AdvertisementHelpers; + + +namespace BluetoothLEExplorer.ViewModels +{ + public class AdvertisementBeaconDetailsPageViewModel : ViewModelBase + { + public void GotoSettings() => NavigationService.Navigate(typeof(Views.SettingsPage), 0); + + /// + /// Go to privacy + /// + public void GotoPrivacy() => NavigationService.Navigate(typeof(Views.SettingsPage), 1); + + /// + /// Go to about + /// + public void GotoAbout() => NavigationService.Navigate(typeof(Views.SettingsPage), 2); + } +} diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/AdvertisementBeaconPageViewModel.cs b/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/AdvertisementBeaconPageViewModel.cs new file mode 100644 index 0000000..be647c0 --- /dev/null +++ b/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/AdvertisementBeaconPageViewModel.cs @@ -0,0 +1,252 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +//---------------------------------------------------------------------------------------------- +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Collections.ObjectModel; + +using Template10.Mvvm; +using Template10.Services.NavigationService; + +using Windows.UI.Xaml.Navigation; +using Windows.Devices.Bluetooth.Advertisement; + +using BluetoothLEExplorer.Models; +using BluetoothLEExplorer.Services.AdvertisementHelpers; +using System.Windows.Input; +using GattHelper.Converters; +using System.Runtime.InteropServices.WindowsRuntime; +using System; +using Windows.Foundation.Metadata; + +namespace BluetoothLEExplorer.ViewModels +{ + public class AdvertisementBeaconPageNewBeaconViewModel : ViewModelBase + { + private bool useExtendedFormat = false; + + public bool UseExtendedFormat + { + get + { + return useExtendedFormat; + } + + set + { + if (value != useExtendedFormat) + { + Set(ref useExtendedFormat, value, "UseExtendedFormat"); + IsValid = AreParametersValid(); + } + } + } + + private bool isAnonymous = false; + + public bool IsAnonymous + { + get + { + return isAnonymous; + } + + set + { + if (value != isAnonymous) + { + Set(ref isAnonymous, value, "IsAnonymous"); + IsValid = AreParametersValid(); + } + } + } + + private bool includeTxPower = false; + + public bool IncludeTxPower + { + get + { + return includeTxPower; + } + + set + { + if (value != includeTxPower) + { + Set(ref includeTxPower, value, "IncludeTxPower"); + IsValid = AreParametersValid(); + } + } + } + + private String preferredTxPower = ""; + + public String PreferredTxPower + { + get + { + return preferredTxPower; + } + + set + { + if (value != preferredTxPower) + { + Set(ref preferredTxPower, value, "PreferredTxPower"); + IsValid = AreParametersValid(); + } + } + } + + private String payload = ""; + + public String Payload + { + get + { + return payload; + } + + set + { + if (value != payload) + { + Set(ref payload, value, "Payload"); + IsValid = AreParametersValid(); + } + } + } + + private bool isValid = false; + + public bool IsValid + { + get + { + return isValid; + } + + private set + { + if (value != isValid) + { + Set(ref isValid, value, "IsValid"); + } + } + } + + public void Reset() + { + UseExtendedFormat = false; + IsAnonymous = false; + IncludeTxPower = false; + PreferredTxPower = ""; + Payload = ""; + } + + public bool AreParametersValid() + { + if (payload == "") + { + return false; + } + else if (isAnonymous || includeTxPower) + { + if (!useExtendedFormat) + { + return false; + } + } + return true; + } + } + + /// + /// Initializes a new instance of the class. Currently unused. + /// + public class AdvertisementBeaconPageViewModel : ViewModelBase + { + private GattSampleContext Context; + + public ICommand CreateBeaconCommand + { + get + { + return new DelegateCommand((x) => { if (x != null) { CreateBeacon(x); } }); + } + } + + public ObservableCollection Beacons + { + get { return Context.Beacons; } + private set { Context.Beacons = value; } + } + + public ObservableBluetoothLEBeacon SelectedBeacon + { + get { return Context.SelectedBeacon; } + + set + { + if (Context.SelectedBeacon != value) + { + Context.SelectedBeacon = value; + if (Context.SelectedBeacon != null) + { + NavigateToBeacon(); + } + } + } + } + + public AdvertisementBeaconPageViewModel() + { + Context = GattSampleContext.Context; + Context.PropertyChanged += Context_PropertyChanged; + } + + private void Context_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) + { + } + + public void CreateBeacon(AdvertisementBeaconPageNewBeaconViewModel parameters) + { + short? preferredTxPower = null; + if (parameters.PreferredTxPower != "") + { + preferredTxPower = Convert.ToInt16(parameters.PreferredTxPower); + } + + var beacon = new ObservableBluetoothLEBeacon( + GattConvert.ToIBufferFromHexString(parameters.Payload).ToArray(), + parameters.UseExtendedFormat, + parameters.IsAnonymous, + parameters.IncludeTxPower, + preferredTxPower); + Beacons.Add(beacon); + + // Reset the parameters after creation. + parameters.Reset(); + } + + public void NavigateToBeacon() => NavigationService.Navigate(typeof(Views.AdvertisementBeaconDetailsPage)); + + /// + /// Go to settings + /// + public void GotoSettings() => NavigationService.Navigate(typeof(Views.SettingsPage), 0); + + /// + /// Go to privacy + /// + public void GotoPrivacy() => NavigationService.Navigate(typeof(Views.SettingsPage), 1); + + /// + /// Go to about + /// + public void GotoAbout() => NavigationService.Navigate(typeof(Views.SettingsPage), 2); + } +} diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/AdvertisementMonitorPageViewModel.cs b/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/AdvertisementMonitorPageViewModel.cs new file mode 100644 index 0000000..df3812a --- /dev/null +++ b/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/AdvertisementMonitorPageViewModel.cs @@ -0,0 +1,310 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using BluetoothLEExplorer.Models; +using Template10.Mvvm; +using Template10.Services.NavigationService; +using Windows.UI.Xaml.Navigation; +using Windows.Devices.Bluetooth.GenericAttributeProfile; +using Windows.Devices.Bluetooth.Advertisement; +using Windows.Security.Cryptography; +using Windows.Storage.Streams; +using Windows.UI.Popups; +using SortedObservableCollection; +using GattHelper.Converters; +using Windows.Foundation.Metadata; +using BluetoothLEExplorer.Services.AdvertisementHelpers; +using Windows.UI.Xaml.Data; +using Template10.Common; + +namespace BluetoothLEExplorer.ViewModels +{ + public class AdvertisementMonitorPageViewModel : ViewModelBase + { + public enum MonitorScanningMode + { + NotSet, + Passive, + Active, + None + } + + private MonitorScanningMode scanningMode = MonitorScanningMode.Passive; + + public MonitorScanningMode ScanningMode + { + get + { + return scanningMode; + } + + set + { + Set(ref scanningMode, value); + } + } + + /// + /// App context + /// + private GattSampleContext Context; + + public bool IsWatcherStarted + { + get + { + return Context.AdvertisementWatcherStarted; + } + } + + public List KnownDataSectionFilters { get; private set; } = new List(); + + private ObservableBluetoothLEAdvertisementFilter selectedDataSectionFilter = null; + + public ObservableBluetoothLEAdvertisementFilter SelectedDataSectionFilter + { + get + { + return selectedDataSectionFilter; + } + + set + { + ShowDataSectionRawPane = value.DataSectionRawFilter; + + Set(ref selectedDataSectionFilter, value); + } + } + + private bool showDataSectionRawPane = false; + + public bool ShowDataSectionRawPane + { + get + { + return showDataSectionRawPane; + } + + private set + { + Set(ref showDataSectionRawPane, value); + } + } + + private object advertisementsLock = new object(); + + public SortedObservableCollection AdvertisementsView { get; private set; } = new SortedObservableCollection(new ObservableBluetoothLEAdvertisement.RssiComparer(), "Rssi"); + + private ObservableBluetoothLEAdvertisement selectedAdvertisement; + + public ObservableBluetoothLEAdvertisement SelectedAdvertisement + { + get + { + return selectedAdvertisement; + } + + set + { + bool same = (selectedAdvertisement == value); + + Set(ref selectedAdvertisement, value, "SelectedAdvertisement"); + Context.SelectedAdvertisement = selectedAdvertisement; + + if ((same == false) && (value != null)) + { + ViewAdvertisement(); + } + } + } + + public String ContentFilter + { + get + { + return Context.AdvertisementContentFilter; + } + + set + { + if (Context.AdvertisementContentFilter != value) + { + Context.AdvertisementContentFilter = value; + UpdateAdvertisementView(); + RaisePropertyChanged("ContentFilter"); + } + } + } + + public AdvertisementMonitorPageViewModel() + { + Context = GattSampleContext.Context; + Context.PropertyChanged += Context_PropertyChanged; + + foreach (var value in Enum.GetValues(typeof(AdvertisementSectionType))) + { + KnownDataSectionFilters.Add(new ObservableBluetoothLEAdvertisementFilter((byte)value)); + } + } + + private void Context_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) + { + if (e.PropertyName == "AdvertisementWatcherStarted") + { + RaisePropertyChanged("IsWatcherStarted"); + } + } + + public override async Task OnNavigatedToAsync(object parameter, NavigationMode mode, IDictionary suspensionState) + { + Context.Advertisements.MapChanged += Advertisements_MapChanged; + await Task.CompletedTask; + } + + /// + /// Navigate from page + /// + /// + /// + /// Navigate from task + public override async Task OnNavigatedFromAsync(IDictionary suspensionState, bool suspending) + { + Context.Advertisements.MapChanged -= Advertisements_MapChanged; + await Task.CompletedTask; + } + + public override async Task OnNavigatingFromAsync(NavigatingEventArgs args) + { + args.Cancel = false; + Context.StopAdvertisementWatcher(); + await Task.CompletedTask; + } + + public async void ToggleWatcher() + { + await Dispatcher.DispatchAsync(() => + { + if (IsWatcherStarted == false) + { + BluetoothLEScanningMode convertedScanningMode = BluetoothLEScanningMode.Passive; + if (scanningMode == MonitorScanningMode.Active) + { + convertedScanningMode = BluetoothLEScanningMode.Active; + } + else if ((scanningMode == MonitorScanningMode.None) && + ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 10)) + { + convertedScanningMode = BluetoothLEScanningMode.None; + } + + Context.StartAdvertisementWatcher(convertedScanningMode); + } + else + { + Context.StopAdvertisementWatcher(); + } + }); + } + + public void ApplyFilter() + { + var filter = new BluetoothLEAdvertisementFilter(); + if (SelectedDataSectionFilter != null) + { + filter.BytePatterns.Add(SelectedDataSectionFilter.GetBytePattern()); + } + Context.UpdateAdvertisementFilter(filter); + } + + public void ClearFilter() + { + Context.UpdateAdvertisementFilter(new BluetoothLEAdvertisementFilter()); + } + + private void Advertisements_MapChanged(Windows.Foundation.Collections.IObservableMap sender, Windows.Foundation.Collections.IMapChangedEventArgs @event) + { + lock (advertisementsLock) + { + if ((@event.CollectionChange == Windows.Foundation.Collections.CollectionChange.ItemInserted) || + (@event.CollectionChange == Windows.Foundation.Collections.CollectionChange.ItemChanged)) + { + var advertisement = sender[@event.Key]; + + if (IsContentFilterMatch(Context.AdvertisementContentFilter, advertisement)) + { + if (!AdvertisementsView.Contains(advertisement)) + { + AdvertisementsView.Add(advertisement); + } + else + { + AdvertisementsView[AdvertisementsView.IndexOf(advertisement)].Update(advertisement); + } + } + } + else if (@event.CollectionChange == Windows.Foundation.Collections.CollectionChange.Reset) + { + AdvertisementsView.Clear(); + } + else if (@event.CollectionChange == Windows.Foundation.Collections.CollectionChange.ItemRemoved) + { + foreach (var advertisement in AdvertisementsView) + { + if (advertisement.InternalHashString == @event.Key) + { + AdvertisementsView.Remove(advertisement); + } + } + } + } + } + + private bool IsContentFilterMatch(String filter, ObservableBluetoothLEAdvertisement advertisement) + { + if (filter == "") + { + return true; + } + else if ((advertisement.AddressAsString.IndexOf(filter, StringComparison.OrdinalIgnoreCase) >= 0) || + (advertisement.PayloadAsString.IndexOf(filter, StringComparison.OrdinalIgnoreCase) >= 0)) + { + return true; + } + return false; + } + + private void UpdateAdvertisementView() + { + AdvertisementsView = new SortedObservableCollection(new ObservableBluetoothLEAdvertisement.RssiComparer(), "Rssi"); + + foreach (var advertisement in Context.Advertisements) + { + if (IsContentFilterMatch(ContentFilter, advertisement.Value)) + { + AdvertisementsView.Add(advertisement.Value); + } + } + + RaisePropertyChanged("AdvertisementsView"); + } + + public void ViewAdvertisement() => NavigationService.Navigate(typeof(Views.AdvertisementPage)); + + /// + /// Go to settings + /// + public void GotoSettings() => NavigationService.Navigate(typeof(Views.SettingsPage), 0); + + /// + /// Go to privacy + /// + public void GotoPrivacy() => NavigationService.Navigate(typeof(Views.SettingsPage), 1); + + /// + /// Go to about + /// + public void GotoAbout() => NavigationService.Navigate(typeof(Views.SettingsPage), 2); + } +} diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/AdvertisementPageViewModel.cs b/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/AdvertisementPageViewModel.cs new file mode 100644 index 0000000..089bf60 --- /dev/null +++ b/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/AdvertisementPageViewModel.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Text; +using System.Threading.Tasks; +using BluetoothLEExplorer.Models; +using Template10.Mvvm; +using Template10.Services.NavigationService; +using Windows.Devices.Bluetooth.GenericAttributeProfile; +using Windows.Security.Cryptography; +using Windows.Storage.Streams; +using Windows.UI.Xaml.Navigation; +using Windows.UI.Popups; +using GattHelper.Converters; + +namespace BluetoothLEExplorer.ViewModels +{ + public class AdvertisementPageViewModel : ViewModelBase + { + private GattSampleContext context = GattSampleContext.Context; + + /// + /// Source for + /// + private ObservableBluetoothLEAdvertisement advertisement = GattSampleContext.Context.SelectedAdvertisement; + + /// + /// Gets or sets the advertisement that this view model wraps + /// + public ObservableBluetoothLEAdvertisement Advertisement + { + get + { + return advertisement; + } + + set + { + Set(ref advertisement, value, "Advertisement"); + } + } + + + } +} diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/BeaconViewModel.cs b/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/BeaconViewModel.cs deleted file mode 100644 index 0a2c713..0000000 --- a/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/BeaconViewModel.cs +++ /dev/null @@ -1,89 +0,0 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// -//---------------------------------------------------------------------------------------------- -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Template10.Mvvm; -using Template10.Services.NavigationService; -using Windows.UI.Xaml.Navigation; - -namespace BluetoothLEExplorer.ViewModels -{ - /// - /// Initializes a new instance of the class. Currently unused. - /// - public class BeaconViewModel : ViewModelBase - { - /// - /// Initializes a new instance of the class. - /// - public BeaconViewModel() - { - if (Windows.ApplicationModel.DesignMode.DesignModeEnabled) - { - } - } - - /// - /// Navigate to page - /// - /// - /// - /// - /// Navigate to task - public override async Task OnNavigatedToAsync(object parameter, NavigationMode mode, IDictionary suspensionState) - { - if (suspensionState.Any()) - { - } - - await Task.CompletedTask; - } - - /// - /// Navigate from page - /// - /// - /// - /// Navigate from task - public override async Task OnNavigatedFromAsync(IDictionary suspensionState, bool suspending) - { - if (suspending) - { - } - - await Task.CompletedTask; - } - - /// - /// Navigate from page - /// - /// - /// Navigate from task - public override async Task OnNavigatingFromAsync(NavigatingEventArgs args) - { - args.Cancel = false; - await Task.CompletedTask; - } - - /// - /// Go to settings - /// - public void GotoSettings() => - NavigationService.Navigate(typeof(Views.SettingsPage), 0); - - /// - /// Go to privacy - /// - public void GotoPrivacy() => - NavigationService.Navigate(typeof(Views.SettingsPage), 1); - - /// - /// Go to about - /// - public void GotoAbout() => - NavigationService.Navigate(typeof(Views.SettingsPage), 2); - } -} diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/CharacteristicPageViewModel.cs b/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/CharacteristicPageViewModel.cs index 89ac87f..e47f577 100644 --- a/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/CharacteristicPageViewModel.cs +++ b/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/CharacteristicPageViewModel.cs @@ -448,6 +448,14 @@ private set } } + public bool IsTransactionInProgress + { + get + { + return context.IsTransactionInProgress; + } + } + /// /// Initializes a new instance of the class. /// @@ -467,6 +475,16 @@ public CharacteristicPageViewModel() { DisplayPresentError = Windows.UI.Xaml.Visibility.Visible; } + + context.PropertyChanged += Context_PropertyChanged; + } + + private void Context_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) + { + if (e.PropertyName == "IsTransactionInProgress") + { + this.RaisePropertyChanged("IsTransactionInProgress"); + } } /// @@ -663,6 +681,55 @@ public async void WriteValue() } } + public async void WriteTransaction() + { + if (!String.IsNullOrEmpty(ValueToWrite)) + { + IBuffer writeBuffer = null; + + if (WriteType == WriteTypes.Decimal) + { + DataWriter writer = new DataWriter(); + writer.ByteOrder = ByteOrder.LittleEndian; + writer.WriteInt32(Int32.Parse(ValueToWrite)); + writeBuffer = writer.DetachBuffer(); + } + else if (WriteType == WriteTypes.Hex) + { + try + { + // pad the value if we've received odd number of bytes + if (ValueToWrite.Length % 2 == 1) + { + writeBuffer = GattConvert.ToIBufferFromHexString("0" + ValueToWrite); + } + else + { + writeBuffer = GattConvert.ToIBufferFromHexString(ValueToWrite); + } + } + catch (Exception ex) + { + MessageDialog dialog = new MessageDialog(ex.Message, "Error"); + await dialog.ShowAsync(); + return; + } + + } + else if (WriteType == WriteTypes.UTF8) + { + writeBuffer = CryptographicBuffer.ConvertStringToBinary(ValueToWrite, + BinaryStringEncoding.Utf8); + } + + context.WriteTransaction(Characteristic.Characteristic, writeBuffer); + } + else + { + NotifyUser.Insert(0, "No data to write to device"); + } + } + /// /// Navigate to page /// @@ -695,7 +762,7 @@ await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatch continue; } - if (BluetoothLEExplorer.Services.GattUuidHelpers.GattServiceUuidHelper.IsReadOnly(Characteristic.Characteristic.Service.Uuid) && + if (GattServiceUuidHelper.IsReadOnly(Characteristic.Characteristic.Service.Uuid) && (p == GattCharacteristicProperties.Write || p == GattCharacteristicProperties.WriteWithoutResponse)) { continue; diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/DeviceServicesPageViewModel.cs b/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/DeviceServicesPageViewModel.cs index dbce636..935d992 100644 --- a/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/DeviceServicesPageViewModel.cs +++ b/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/DeviceServicesPageViewModel.cs @@ -9,6 +9,9 @@ using BluetoothLEExplorer.Views; using Template10.Mvvm; using Template10.Services.NavigationService; +using Windows.Devices.Bluetooth; +using Windows.Devices.Bluetooth.Advertisement; +using Windows.Foundation.Metadata; using Windows.UI.Xaml.Navigation; namespace BluetoothLEExplorer.ViewModels @@ -18,6 +21,7 @@ namespace BluetoothLEExplorer.ViewModels /// public class DeviceServicesPageViewModel : ViewModelBase { + /// /// App context /// @@ -40,6 +44,14 @@ public bool IsSecureConnectionSupported } } + public bool IsTransactionInProgress + { + get + { + return context.IsTransactionInProgress; + } + } + /// /// Source for /// @@ -115,6 +127,18 @@ public DeviceServicesPageViewModel() if (Windows.ApplicationModel.DesignMode.DesignModeEnabled) { } + + context.PropertyChanged += Context_PropertyChanged; + } + + public void StartTransaction() + { + context.CreateTransaction(); + } + + public void CommitTransaction() + { + context.CommitTransaction(); } /// @@ -129,8 +153,8 @@ public override async Task OnNavigatedToAsync(object parameter, NavigationMode m await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync( Windows.UI.Core.CoreDispatcherPriority.Normal, () => - { - }); + { + }); await Task.CompletedTask; } @@ -172,5 +196,13 @@ public async void Refresh() Views.Busy.SetBusy(false); } + + private void Context_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) + { + if (e.PropertyName == "IsTransactionInProgress") + { + this.RaisePropertyChanged("IsTransactionInProgress"); + } + } } } diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/DiscoverViewModel.cs b/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/DiscoverViewModel.cs index ee51b3c..fe23808 100644 --- a/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/DiscoverViewModel.cs +++ b/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/DiscoverViewModel.cs @@ -13,6 +13,7 @@ using Template10.Services.NavigationService; using Windows.UI.Xaml.Navigation; using SortedObservableCollection; +using Windows.Devices.Bluetooth.Advertisement; namespace BluetoothLEExplorer.ViewModels { diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/GenericGattServiceViewModel.cs b/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/GenericGattServiceViewModel.cs index 4259a85..df5d35a 100644 --- a/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/GenericGattServiceViewModel.cs +++ b/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/GenericGattServiceViewModel.cs @@ -7,6 +7,7 @@ using GattServicesLibrary; using Template10.Mvvm; using Windows.Devices.Bluetooth.GenericAttributeProfile; +using Windows.Storage.Streams; namespace BluetoothLEExplorer.ViewModels { @@ -78,6 +79,23 @@ public bool IsDiscoverable } } + public bool IncludeServiceData + { + get + { + return service.IncludeServiceData; + } + + set + { + if (value != service.IncludeServiceData) + { + service.IncludeServiceData = value; + RaisePropertyChanged("IncludeServiceData"); + } + } + } + /// /// Source for property /// @@ -95,12 +113,11 @@ public bool IsPublishing set { - if(value != isPublishing) + if (value != isPublishing) { - if(value == true) + if (value == true) { Start(); - } if(value == false) { diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/ServicePageViewModel.cs b/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/ServicePageViewModel.cs index 4c4fa49..0bcd59d 100644 --- a/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/ServicePageViewModel.cs +++ b/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/ServicePageViewModel.cs @@ -399,4 +399,4 @@ public override async Task OnNavigatingFromAsync(NavigatingEventArgs args) await Task.CompletedTask; } } -} +} \ No newline at end of file diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Views/AdvertisementBeaconDetailsPage.xaml b/BluetoothLEExplorer/BluetoothLEExplorer/Views/AdvertisementBeaconDetailsPage.xaml new file mode 100644 index 0000000..415ba81 --- /dev/null +++ b/BluetoothLEExplorer/BluetoothLEExplorer/Views/AdvertisementBeaconDetailsPage.xaml @@ -0,0 +1,90 @@ + + + + + + + + + + Collapsed + + + Visible + + + + + + True + + + Visible + + + Collapsed + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Views/AdvertisementBeaconDetailsPage.xaml.cs b/BluetoothLEExplorer/BluetoothLEExplorer/Views/AdvertisementBeaconDetailsPage.xaml.cs new file mode 100644 index 0000000..ddec301 --- /dev/null +++ b/BluetoothLEExplorer/BluetoothLEExplorer/Views/AdvertisementBeaconDetailsPage.xaml.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices.WindowsRuntime; +using Windows.Foundation; +using Windows.Foundation.Collections; +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; + +// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238 + +namespace BluetoothLEExplorer.Views +{ + /// + /// An empty page that can be used on its own or navigated to within a Frame. + /// + public sealed partial class AdvertisementBeaconDetailsPage : Page + { + public AdvertisementBeaconDetailsPage() + { + this.InitializeComponent(); + } + } +} diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Views/AdvertisementBeaconPage.xaml b/BluetoothLEExplorer/BluetoothLEExplorer/Views/AdvertisementBeaconPage.xaml new file mode 100644 index 0000000..893716f --- /dev/null +++ b/BluetoothLEExplorer/BluetoothLEExplorer/Views/AdvertisementBeaconPage.xaml @@ -0,0 +1,216 @@ + + + + + + + + + + True + + + Visible + + + Collapsed + + + + + + False + + + Visible + + + Collapsed + + + + + + True + + + Stop + + + Start + + + + + + 0 + + + Collapsed + + + Visible + + + + + + -1 + + + Collapsed + + + Visible + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + IsItemClickEnabled="True" + SelectionMode="Single" + ItemsSource="{x:Bind Characteristics}" + ItemClick="CharacteristicsListView_ItemClick" > @@ -146,6 +164,8 @@ + + diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Views/DeviceServicesPage.xaml.cs b/BluetoothLEExplorer/BluetoothLEExplorer/Views/DeviceServicesPage.xaml.cs index 92f3ff9..2c44c60 100644 --- a/BluetoothLEExplorer/BluetoothLEExplorer/Views/DeviceServicesPage.xaml.cs +++ b/BluetoothLEExplorer/BluetoothLEExplorer/Views/DeviceServicesPage.xaml.cs @@ -27,7 +27,7 @@ public DeviceServicesPage() } /// - /// Updates the view model with the just selected characteristic + /// Updates the view model with the just selected characteristic /// /// /// @@ -36,6 +36,11 @@ private void CharacteristicsListView_ItemClick(object sender, ItemClickEventArgs ViewModel.SelectedCharacteristic = (ObservableGattCharacteristics)e.ClickedItem; } + /// + /// Updates the view model with the just selected service + /// + /// + /// private void ServicesListView_ItemClick(object sender, ItemClickEventArgs e) { ViewModel.SelectedService = (ObservableGattDeviceService)e.ClickedItem; diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Views/Discover.xaml b/BluetoothLEExplorer/BluetoothLEExplorer/Views/Discover.xaml index d4412a1..93b1503 100644 --- a/BluetoothLEExplorer/BluetoothLEExplorer/Views/Discover.xaml +++ b/BluetoothLEExplorer/BluetoothLEExplorer/Views/Discover.xaml @@ -116,7 +116,6 @@ - diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Views/ServicePage.xaml b/BluetoothLEExplorer/BluetoothLEExplorer/Views/ServicePage.xaml index d30a22b..896d6b2 100644 --- a/BluetoothLEExplorer/BluetoothLEExplorer/Views/ServicePage.xaml +++ b/BluetoothLEExplorer/BluetoothLEExplorer/Views/ServicePage.xaml @@ -1,4 +1,4 @@ - - - + \ No newline at end of file diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Views/ServicePage.xaml.cs b/BluetoothLEExplorer/BluetoothLEExplorer/Views/ServicePage.xaml.cs index 4803125..5f3fcf0 100644 --- a/BluetoothLEExplorer/BluetoothLEExplorer/Views/ServicePage.xaml.cs +++ b/BluetoothLEExplorer/BluetoothLEExplorer/Views/ServicePage.xaml.cs @@ -36,4 +36,4 @@ private void CharacteristicsListView_ItemClick(object sender, ItemClickEventArgs ViewModel.SelectedCharacteristic = (ObservableGattCharacteristics)e.ClickedItem; } } -} +} \ No newline at end of file diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Views/Services/AlertNotificationServicePage.xaml b/BluetoothLEExplorer/BluetoothLEExplorer/Views/Services/AlertNotificationServicePage.xaml index 082f05f..40f5cc2 100644 --- a/BluetoothLEExplorer/BluetoothLEExplorer/Views/Services/AlertNotificationServicePage.xaml +++ b/BluetoothLEExplorer/BluetoothLEExplorer/Views/Services/AlertNotificationServicePage.xaml @@ -116,7 +116,12 @@ - + + + - - diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Views/Services/BloodPressureServicePage.xaml b/BluetoothLEExplorer/BluetoothLEExplorer/Views/Services/BloodPressureServicePage.xaml index 23e7cca..f33dcf0 100644 --- a/BluetoothLEExplorer/BluetoothLEExplorer/Views/Services/BloodPressureServicePage.xaml +++ b/BluetoothLEExplorer/BluetoothLEExplorer/Views/Services/BloodPressureServicePage.xaml @@ -102,7 +102,11 @@ - + + diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Views/Services/CurrentTimeServicePage.xaml b/BluetoothLEExplorer/BluetoothLEExplorer/Views/Services/CurrentTimeServicePage.xaml index 0f82333..2523047 100644 --- a/BluetoothLEExplorer/BluetoothLEExplorer/Views/Services/CurrentTimeServicePage.xaml +++ b/BluetoothLEExplorer/BluetoothLEExplorer/Views/Services/CurrentTimeServicePage.xaml @@ -89,7 +89,12 @@ - + + + - + + diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Views/Services/MicrosoftServicePage.xaml b/BluetoothLEExplorer/BluetoothLEExplorer/Views/Services/MicrosoftServicePage.xaml index d37665c..94da5c2 100644 --- a/BluetoothLEExplorer/BluetoothLEExplorer/Views/Services/MicrosoftServicePage.xaml +++ b/BluetoothLEExplorer/BluetoothLEExplorer/Views/Services/MicrosoftServicePage.xaml @@ -135,7 +135,19 @@ - + + + + + public sealed partial class SettingsPage : Page { - /// - /// Serialization service - /// - private Template10.Services.SerializationService.ISerializationService serializationService; - /// /// Initializes a new instance of the class. /// public SettingsPage() { InitializeComponent(); - serializationService = Template10.Services.SerializationService.SerializationService.Json; } /// @@ -32,8 +26,6 @@ public SettingsPage() /// protected override void OnNavigatedTo(NavigationEventArgs e) { - var index = int.Parse(serializationService.Deserialize(e.Parameter?.ToString()).ToString()); - MyPivot.SelectedIndex = index; } } } diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Views/Shell.xaml b/BluetoothLEExplorer/BluetoothLEExplorer/Views/Shell.xaml index 6b82073..8574b49 100644 --- a/BluetoothLEExplorer/BluetoothLEExplorer/Views/Shell.xaml +++ b/BluetoothLEExplorer/BluetoothLEExplorer/Views/Shell.xaml @@ -21,66 +21,68 @@ - - - + - - + + - - + + - - + + - + - - + + + + + + + + - + - - + + - diff --git a/BluetoothLEExplorer/BluetoothLEExplorerUnitTests/BluetoothLEExplorerUnitTests.csproj b/BluetoothLEExplorer/BluetoothLEExplorerUnitTests/BluetoothLEExplorerUnitTests.csproj index 5a87f75..0aece57 100644 --- a/BluetoothLEExplorer/BluetoothLEExplorerUnitTests/BluetoothLEExplorerUnitTests.csproj +++ b/BluetoothLEExplorer/BluetoothLEExplorerUnitTests/BluetoothLEExplorerUnitTests.csproj @@ -11,8 +11,8 @@ BluetoothLEExplorerUnitTests en-US UAP - 10.0.17134.0 - 10.0.15063.0 + 10.0.19041.0 + 10.0.18362.0 14 512 {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} diff --git a/BluetoothLEExplorer/GattHelper/Converters/GattConvert.cs b/BluetoothLEExplorer/GattHelper/Converters/GattConvert.cs index ae02e9e..1ffe53a 100644 --- a/BluetoothLEExplorer/GattHelper/Converters/GattConvert.cs +++ b/BluetoothLEExplorer/GattHelper/Converters/GattConvert.cs @@ -12,18 +12,25 @@ public static class GattConvert { public static IBuffer ToIBufferFromHexString(string data) { + DataWriter writer = new DataWriter(); data = data.Replace("-", ""); - int NumberChars = data.Length; - byte[] bytes = new byte[NumberChars / 2]; - - for (int i = 0; i < NumberChars; i += 2) + if (data.Length > 0) { - bytes[i / 2] = Convert.ToByte(data.Substring(i, 2), 16); + if (data.Length % 2 != 0) + { + data = "0" + data; + } + + int NumberChars = data.Length; + byte[] bytes = new byte[NumberChars / 2]; + + for (int i = 0; i < NumberChars; i += 2) + { + bytes[i / 2] = Convert.ToByte(data.Substring(i, 2), 16); + } + writer.WriteBytes(bytes); } - - DataWriter writer = new DataWriter(); - writer.WriteBytes(bytes); return writer.DetachBuffer(); } diff --git a/BluetoothLEExplorer/GattHelper/GattHelper.csproj b/BluetoothLEExplorer/GattHelper/GattHelper.csproj index 5d368a6..40e2e29 100644 --- a/BluetoothLEExplorer/GattHelper/GattHelper.csproj +++ b/BluetoothLEExplorer/GattHelper/GattHelper.csproj @@ -11,8 +11,8 @@ GattHelper en-US UAP - 10.0.17134.0 - 10.0.15063.0 + 10.0.19041.0 + 10.0.18362.0 14 512 {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} diff --git a/GattServicesLibrary/GattServicesLibrary/GattServicesLibrary.csproj b/GattServicesLibrary/GattServicesLibrary/GattServicesLibrary.csproj index 733ba26..7f14dc7 100644 --- a/GattServicesLibrary/GattServicesLibrary/GattServicesLibrary.csproj +++ b/GattServicesLibrary/GattServicesLibrary/GattServicesLibrary.csproj @@ -11,8 +11,8 @@ GattServicesLibrary en-US UAP - 10.0.17134.0 - 10.0.15063.0 + 10.0.19041.0 + 10.0.18362.0 14 512 {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} @@ -143,6 +143,11 @@ GattHelper + + + 5.3.3 + + 14.0 diff --git a/GattServicesLibrary/GattServicesLibrary/GenericGattService.cs b/GattServicesLibrary/GattServicesLibrary/GenericGattService.cs index 4807b7d..99c9039 100644 --- a/GattServicesLibrary/GattServicesLibrary/GenericGattService.cs +++ b/GattServicesLibrary/GattServicesLibrary/GenericGattService.cs @@ -10,6 +10,8 @@ using GattServicesLibrary.Helpers; using Windows.Devices.Bluetooth; using Windows.Devices.Bluetooth.GenericAttributeProfile; +using Windows.Foundation.Metadata; +using Windows.Storage.Streams; namespace GattServicesLibrary { @@ -95,6 +97,7 @@ public abstract string Name } private bool isPublishing = false; + public bool IsPublishing { get @@ -104,7 +107,7 @@ public bool IsPublishing private set { - if(value != isPublishing) + if (value != isPublishing) { isPublishing = value; OnPropertyChanged(new PropertyChangedEventArgs("IsPublishing")); @@ -112,6 +115,25 @@ private set } } + private bool isAdvertising = false; + + public bool IsAdvertising + { + get + { + return isAdvertising; + } + + private set + { + if(value != isAdvertising) + { + isAdvertising = value; + OnPropertyChanged(new PropertyChangedEventArgs("IsAdvertising")); + } + } + } + public bool IsConnectable { @@ -147,6 +169,40 @@ public bool IsDiscoverable } } + private bool includeServiceData; + + public bool IncludeServiceData + { + get + { + return includeServiceData; + } + + set + { + if (value != includeServiceData) + { + includeServiceData = value; + OnPropertyChanged(new PropertyChangedEventArgs("IncludeServiceData")); + } + } + } + + private IBuffer serviceData = null; + + public IBuffer ServiceData + { + get + { + return serviceData; + } + + set + { + serviceData = value; + } + } + /// /// Internal ServiceProvider /// @@ -177,9 +233,14 @@ protected set private void ServiceProvider_AdvertisementStatusChanged(GattServiceProvider sender, GattServiceProviderAdvertisementStatusChangedEventArgs args) { - if (args.Status != GattServiceProviderAdvertisementStatus.Started) + if ((args.Status == GattServiceProviderAdvertisementStatus.Stopped) || + (args.Status == GattServiceProviderAdvertisementStatus.Aborted)) { - IsPublishing = false; + IsAdvertising = false; + } + else + { + IsAdvertising = true; } } @@ -210,6 +271,18 @@ public virtual void Start() { try { + if (IncludeServiceData) + { + if (ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 10)) + { + ad.ServiceData = serviceData; + } + else + { + IncludeServiceData = false; + } + } + ServiceProvider.StartAdvertising(ad); IsPublishing = true; OnPropertyChanged(new PropertyChangedEventArgs("IsPublishing")); diff --git a/GattServicesLibrary/GattServicesLibrary/Services/MicrosoftService.cs b/GattServicesLibrary/GattServicesLibrary/Services/MicrosoftService.cs index 1421938..7d4ba82 100644 --- a/GattServicesLibrary/GattServicesLibrary/Services/MicrosoftService.cs +++ b/GattServicesLibrary/GattServicesLibrary/Services/MicrosoftService.cs @@ -5,10 +5,12 @@ using System; using System.ComponentModel; using System.Diagnostics; +using System.Runtime.InteropServices.WindowsRuntime; using System.Threading.Tasks; using GattServicesLibrary; using GattServicesLibrary.Helpers; using Windows.Devices.Bluetooth.GenericAttributeProfile; +using Windows.Foundation.Metadata; using Windows.Storage.Streams; namespace GattServicesLibrary.Services @@ -196,6 +198,12 @@ public GenericGattCharacteristic ReadLongCharacteristic /// Initialization Task public override async Task Init() { + var serviceData = new byte[] { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + }; + ServiceData = WindowsRuntimeBuffer.Create(serviceData, 0, serviceData.Length, serviceData.Length); + await CreateServiceProvider(MSFTServiceUuid); GattLocalCharacteristicResult result = null; diff --git a/SortedObservableCollection/SortedObservableCollection/SortedObservableCollection.csproj b/SortedObservableCollection/SortedObservableCollection/SortedObservableCollection.csproj index 1ee229f..0c059f6 100644 --- a/SortedObservableCollection/SortedObservableCollection/SortedObservableCollection.csproj +++ b/SortedObservableCollection/SortedObservableCollection/SortedObservableCollection.csproj @@ -11,8 +11,8 @@ SortedObservableCollection en-US UAP - 10.0.17134.0 - 10.0.10586.0 + 10.0.19041.0 + 10.0.18362.0 14 512 {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} @@ -111,6 +111,11 @@ + + + 5.3.3 + + 14.0 diff --git a/SortedObservableCollection/SortedObservableCollection/project.json b/SortedObservableCollection/SortedObservableCollection/project.json index 3242d45..ec51351 100644 --- a/SortedObservableCollection/SortedObservableCollection/project.json +++ b/SortedObservableCollection/SortedObservableCollection/project.json @@ -1,6 +1,6 @@ { "dependencies": { - "Microsoft.NETCore.UniversalWindowsPlatform": "5.2.3" + "Microsoft.NETCore.UniversalWindowsPlatform": "5.3.3" }, "frameworks": { "uap10.0": {}