diff --git a/BluetoothLEExplorer/BluetoothLEExplorer.sln b/BluetoothLEExplorer/BluetoothLEExplorer.sln
new file mode 100644
index 0000000..e012f60
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorer.sln
@@ -0,0 +1,125 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.26430.16
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GattServicesLibrary", "..\GattServicesLibrary\GattServicesLibrary\GattServicesLibrary.csproj", "{644F40AD-EABB-4570-B9B7-E3F8DDAA80F3}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GattHelper", "GattHelper\GattHelper.csproj", "{37C63FDD-D995-4CC2-B014-4FB323194001}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SortedObservableCollection", "SortedObservableCollection", "{92EE95F6-40B6-4E5A-97C9-E4234D17C022}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SortedObservableCollection", "..\SortedObservableCollection\SortedObservableCollection\SortedObservableCollection.csproj", "{BE79FC52-6041-4913-B0D4-66C100944904}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BluetoothLEExplorer", "BluetoothLEExplorer\BluetoothLEExplorer.csproj", "{AD1CBE3C-A68A-4E02-8001-E02B815560EE}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BluetoothLEExplorerUnitTests", "BluetoothLEExplorerUnitTests\BluetoothLEExplorerUnitTests.csproj", "{6F503DF9-71C9-4340-90BB-D9AA14ADB686}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|ARM = Debug|ARM
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|ARM = Release|ARM
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {644F40AD-EABB-4570-B9B7-E3F8DDAA80F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {644F40AD-EABB-4570-B9B7-E3F8DDAA80F3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {644F40AD-EABB-4570-B9B7-E3F8DDAA80F3}.Debug|ARM.ActiveCfg = Debug|ARM
+ {644F40AD-EABB-4570-B9B7-E3F8DDAA80F3}.Debug|ARM.Build.0 = Debug|ARM
+ {644F40AD-EABB-4570-B9B7-E3F8DDAA80F3}.Debug|x64.ActiveCfg = Debug|x64
+ {644F40AD-EABB-4570-B9B7-E3F8DDAA80F3}.Debug|x64.Build.0 = Debug|x64
+ {644F40AD-EABB-4570-B9B7-E3F8DDAA80F3}.Debug|x86.ActiveCfg = Debug|x86
+ {644F40AD-EABB-4570-B9B7-E3F8DDAA80F3}.Debug|x86.Build.0 = Debug|x86
+ {644F40AD-EABB-4570-B9B7-E3F8DDAA80F3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {644F40AD-EABB-4570-B9B7-E3F8DDAA80F3}.Release|Any CPU.Build.0 = Release|Any CPU
+ {644F40AD-EABB-4570-B9B7-E3F8DDAA80F3}.Release|ARM.ActiveCfg = Release|ARM
+ {644F40AD-EABB-4570-B9B7-E3F8DDAA80F3}.Release|ARM.Build.0 = Release|ARM
+ {644F40AD-EABB-4570-B9B7-E3F8DDAA80F3}.Release|x64.ActiveCfg = Release|x64
+ {644F40AD-EABB-4570-B9B7-E3F8DDAA80F3}.Release|x64.Build.0 = Release|x64
+ {644F40AD-EABB-4570-B9B7-E3F8DDAA80F3}.Release|x86.ActiveCfg = Release|x86
+ {644F40AD-EABB-4570-B9B7-E3F8DDAA80F3}.Release|x86.Build.0 = Release|x86
+ {37C63FDD-D995-4CC2-B014-4FB323194001}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {37C63FDD-D995-4CC2-B014-4FB323194001}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {37C63FDD-D995-4CC2-B014-4FB323194001}.Debug|ARM.ActiveCfg = Debug|ARM
+ {37C63FDD-D995-4CC2-B014-4FB323194001}.Debug|ARM.Build.0 = Debug|ARM
+ {37C63FDD-D995-4CC2-B014-4FB323194001}.Debug|x64.ActiveCfg = Debug|x64
+ {37C63FDD-D995-4CC2-B014-4FB323194001}.Debug|x64.Build.0 = Debug|x64
+ {37C63FDD-D995-4CC2-B014-4FB323194001}.Debug|x86.ActiveCfg = Debug|x86
+ {37C63FDD-D995-4CC2-B014-4FB323194001}.Debug|x86.Build.0 = Debug|x86
+ {37C63FDD-D995-4CC2-B014-4FB323194001}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {37C63FDD-D995-4CC2-B014-4FB323194001}.Release|Any CPU.Build.0 = Release|Any CPU
+ {37C63FDD-D995-4CC2-B014-4FB323194001}.Release|ARM.ActiveCfg = Release|ARM
+ {37C63FDD-D995-4CC2-B014-4FB323194001}.Release|ARM.Build.0 = Release|ARM
+ {37C63FDD-D995-4CC2-B014-4FB323194001}.Release|x64.ActiveCfg = Release|x64
+ {37C63FDD-D995-4CC2-B014-4FB323194001}.Release|x64.Build.0 = Release|x64
+ {37C63FDD-D995-4CC2-B014-4FB323194001}.Release|x86.ActiveCfg = Release|x86
+ {37C63FDD-D995-4CC2-B014-4FB323194001}.Release|x86.Build.0 = Release|x86
+ {BE79FC52-6041-4913-B0D4-66C100944904}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {BE79FC52-6041-4913-B0D4-66C100944904}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {BE79FC52-6041-4913-B0D4-66C100944904}.Debug|ARM.ActiveCfg = Debug|ARM
+ {BE79FC52-6041-4913-B0D4-66C100944904}.Debug|ARM.Build.0 = Debug|ARM
+ {BE79FC52-6041-4913-B0D4-66C100944904}.Debug|x64.ActiveCfg = Debug|x64
+ {BE79FC52-6041-4913-B0D4-66C100944904}.Debug|x64.Build.0 = Debug|x64
+ {BE79FC52-6041-4913-B0D4-66C100944904}.Debug|x86.ActiveCfg = Debug|x86
+ {BE79FC52-6041-4913-B0D4-66C100944904}.Debug|x86.Build.0 = Debug|x86
+ {BE79FC52-6041-4913-B0D4-66C100944904}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {BE79FC52-6041-4913-B0D4-66C100944904}.Release|Any CPU.Build.0 = Release|Any CPU
+ {BE79FC52-6041-4913-B0D4-66C100944904}.Release|ARM.ActiveCfg = Release|ARM
+ {BE79FC52-6041-4913-B0D4-66C100944904}.Release|ARM.Build.0 = Release|ARM
+ {BE79FC52-6041-4913-B0D4-66C100944904}.Release|x64.ActiveCfg = Release|x64
+ {BE79FC52-6041-4913-B0D4-66C100944904}.Release|x64.Build.0 = Release|x64
+ {BE79FC52-6041-4913-B0D4-66C100944904}.Release|x86.ActiveCfg = Release|x86
+ {BE79FC52-6041-4913-B0D4-66C100944904}.Release|x86.Build.0 = Release|x86
+ {AD1CBE3C-A68A-4E02-8001-E02B815560EE}.Debug|Any CPU.ActiveCfg = Debug|x86
+ {AD1CBE3C-A68A-4E02-8001-E02B815560EE}.Debug|ARM.ActiveCfg = Debug|ARM
+ {AD1CBE3C-A68A-4E02-8001-E02B815560EE}.Debug|ARM.Build.0 = Debug|ARM
+ {AD1CBE3C-A68A-4E02-8001-E02B815560EE}.Debug|ARM.Deploy.0 = Debug|ARM
+ {AD1CBE3C-A68A-4E02-8001-E02B815560EE}.Debug|x64.ActiveCfg = Debug|x64
+ {AD1CBE3C-A68A-4E02-8001-E02B815560EE}.Debug|x64.Build.0 = Debug|x64
+ {AD1CBE3C-A68A-4E02-8001-E02B815560EE}.Debug|x64.Deploy.0 = Debug|x64
+ {AD1CBE3C-A68A-4E02-8001-E02B815560EE}.Debug|x86.ActiveCfg = Debug|x86
+ {AD1CBE3C-A68A-4E02-8001-E02B815560EE}.Debug|x86.Build.0 = Debug|x86
+ {AD1CBE3C-A68A-4E02-8001-E02B815560EE}.Debug|x86.Deploy.0 = Debug|x86
+ {AD1CBE3C-A68A-4E02-8001-E02B815560EE}.Release|Any CPU.ActiveCfg = Release|x86
+ {AD1CBE3C-A68A-4E02-8001-E02B815560EE}.Release|ARM.ActiveCfg = Release|ARM
+ {AD1CBE3C-A68A-4E02-8001-E02B815560EE}.Release|ARM.Build.0 = Release|ARM
+ {AD1CBE3C-A68A-4E02-8001-E02B815560EE}.Release|ARM.Deploy.0 = Release|ARM
+ {AD1CBE3C-A68A-4E02-8001-E02B815560EE}.Release|x64.ActiveCfg = Release|x64
+ {AD1CBE3C-A68A-4E02-8001-E02B815560EE}.Release|x64.Build.0 = Release|x64
+ {AD1CBE3C-A68A-4E02-8001-E02B815560EE}.Release|x64.Deploy.0 = Release|x64
+ {AD1CBE3C-A68A-4E02-8001-E02B815560EE}.Release|x86.ActiveCfg = Release|x86
+ {AD1CBE3C-A68A-4E02-8001-E02B815560EE}.Release|x86.Build.0 = Release|x86
+ {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|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
+ {6F503DF9-71C9-4340-90BB-D9AA14ADB686}.Debug|x64.ActiveCfg = Debug|x64
+ {6F503DF9-71C9-4340-90BB-D9AA14ADB686}.Debug|x64.Build.0 = Debug|x64
+ {6F503DF9-71C9-4340-90BB-D9AA14ADB686}.Debug|x64.Deploy.0 = Debug|x64
+ {6F503DF9-71C9-4340-90BB-D9AA14ADB686}.Debug|x86.ActiveCfg = Debug|x86
+ {6F503DF9-71C9-4340-90BB-D9AA14ADB686}.Debug|x86.Build.0 = Debug|x86
+ {6F503DF9-71C9-4340-90BB-D9AA14ADB686}.Debug|x86.Deploy.0 = Debug|x86
+ {6F503DF9-71C9-4340-90BB-D9AA14ADB686}.Release|Any CPU.ActiveCfg = Release|x86
+ {6F503DF9-71C9-4340-90BB-D9AA14ADB686}.Release|ARM.ActiveCfg = Release|ARM
+ {6F503DF9-71C9-4340-90BB-D9AA14ADB686}.Release|ARM.Build.0 = Release|ARM
+ {6F503DF9-71C9-4340-90BB-D9AA14ADB686}.Release|ARM.Deploy.0 = Release|ARM
+ {6F503DF9-71C9-4340-90BB-D9AA14ADB686}.Release|x64.ActiveCfg = Release|x64
+ {6F503DF9-71C9-4340-90BB-D9AA14ADB686}.Release|x64.Build.0 = Release|x64
+ {6F503DF9-71C9-4340-90BB-D9AA14ADB686}.Release|x64.Deploy.0 = Release|x64
+ {6F503DF9-71C9-4340-90BB-D9AA14ADB686}.Release|x86.ActiveCfg = Release|x86
+ {6F503DF9-71C9-4340-90BB-D9AA14ADB686}.Release|x86.Build.0 = Release|x86
+ {6F503DF9-71C9-4340-90BB-D9AA14ADB686}.Release|x86.Deploy.0 = Release|x86
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {BE79FC52-6041-4913-B0D4-66C100944904} = {92EE95F6-40B6-4E5A-97C9-E4234D17C022}
+ EndGlobalSection
+EndGlobal
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/App.xaml b/BluetoothLEExplorer/BluetoothLEExplorer/App.xaml
new file mode 100644
index 0000000..c70291d
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorer/App.xaml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/App.xaml.cs b/BluetoothLEExplorer/BluetoothLEExplorer/App.xaml.cs
new file mode 100644
index 0000000..be980a0
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorer/App.xaml.cs
@@ -0,0 +1,153 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------------------------
+using System;
+using System.Threading.Tasks;
+using BluetoothLEExplorer.Services.SettingsServices;
+using Template10.Controls;
+using Windows.ApplicationModel.Activation;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Data;
+using Windows.UI.Popups;
+using BluetoothLEExplorer.Models;
+using BluetoothLEExplorer.ViewModels;
+using System.Diagnostics;
+
+namespace BluetoothLEExplorer
+{
+ //// Documentation on APIs used in this page:
+ //// https://github.com/Windows-XAML/Template10/wiki
+
+ ///
+ /// The application
+ ///
+ [Bindable]
+ public sealed partial class App : Template10.Common.BootStrapper
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public App()
+ {
+ InitializeComponent();
+ this.UnhandledException += App_UnhandledException;
+
+ this.Suspending += App_Suspending;
+ this.Resuming += App_Resuming;
+
+ SplashFactory = (e) => new Views.Splash(e);
+
+ #region App settings
+
+ var settings = SettingsService.Instance;
+ RequestedTheme = settings.AppTheme;
+ CacheMaxDuration = settings.CacheMaxDuration;
+ ShowShellBackButton = settings.UseShellBackButton;
+
+ #endregion
+ }
+
+ private void App_Suspending(object sender, Windows.ApplicationModel.SuspendingEventArgs e)
+ {
+ try
+ {
+ var deferral = e.SuspendingOperation.GetDeferral();
+
+ foreach(GenericGattServiceViewModel service in GattSampleContext.Context.CreatedServices)
+ {
+ string key = "Service_"+ service.Service.ServiceProvider.Service.Uuid.ToString() + "_IsPublishing";
+ bool value = service.IsPublishing;
+ SettingsService.Instance.SettingsDictionary[key] = value;
+
+ if (service.IsPublishing)
+ {
+ service.Service.ServiceProvider.StopAdvertising();
+ }
+ }
+
+ deferral.Complete();
+ }
+ catch(Exception ex)
+ {
+ Debug.WriteLine("Suspending: " + ex.Message);
+ }
+ }
+
+ private void App_Resuming(object sender, object e)
+ {
+ string[] keys = new string[SettingsService.Instance.SettingsDictionary.Keys.Count];
+
+ SettingsService.Instance.SettingsDictionary.Keys.CopyTo(keys, 0);
+
+ for (int i = 0; i < keys.Length; i++)
+ {
+ if (keys[i].Contains("Service_"))
+ {
+ string serviceUUID = keys[i].Split('_')[1];
+ bool IsPublishing = (bool)SettingsService.Instance.SettingsDictionary[keys[i]];
+
+ if (IsPublishing)
+ {
+ foreach (GenericGattServiceViewModel service in GattSampleContext.Context.CreatedServices)
+ {
+ if (serviceUUID == service.Service.ServiceProvider.Service.Uuid.ToString())
+ {
+ service.Service.Start(true);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private void App_UnhandledException(object sender, UnhandledExceptionEventArgs e)
+ {
+ showDialog(e.Exception.Message + "\n\n" + e.Exception.StackTrace);
+ }
+
+ private async void showDialog(string content)
+ {
+ MessageDialog dialog = new MessageDialog(content, "Fatal Error");
+ await dialog.ShowAsync();
+ }
+
+ ///
+ /// Application initialization
+ ///
+ ///
+ /// On initialization task
+ public override async Task OnInitializeAsync(IActivatedEventArgs args)
+ {
+ if (Window.Current.Content as ModalDialog == null)
+ {
+ // create a new frame
+ var nav = NavigationServiceFactory(BackButton.Attach, ExistingContent.Include);
+
+ // create modal root
+ Window.Current.Content = new ModalDialog
+ {
+ DisableBackButtonWhenModal = true,
+ Content = new Views.Shell(nav),
+ ModalContent = new Views.Busy(),
+ };
+ }
+
+ await Task.CompletedTask;
+ }
+
+ ///
+ /// App initialization for long running tasks
+ ///
+ ///
+ ///
+ /// On start task
+ public override async Task OnStartAsync(StartKind startKind, IActivatedEventArgs args)
+ {
+ //// long-running startup tasks go here
+
+ NavigationService.Navigate(typeof(Views.Discover));
+ await Task.CompletedTask;
+ }
+ }
+}
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Assets/BadgeLogo.scale-100.png b/BluetoothLEExplorer/BluetoothLEExplorer/Assets/BadgeLogo.scale-100.png
new file mode 100644
index 0000000..7443266
Binary files /dev/null and b/BluetoothLEExplorer/BluetoothLEExplorer/Assets/BadgeLogo.scale-100.png differ
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Assets/BadgeLogo.scale-125.png b/BluetoothLEExplorer/BluetoothLEExplorer/Assets/BadgeLogo.scale-125.png
new file mode 100644
index 0000000..d317d50
Binary files /dev/null and b/BluetoothLEExplorer/BluetoothLEExplorer/Assets/BadgeLogo.scale-125.png differ
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Assets/BadgeLogo.scale-150.png b/BluetoothLEExplorer/BluetoothLEExplorer/Assets/BadgeLogo.scale-150.png
new file mode 100644
index 0000000..ba4579a
Binary files /dev/null and b/BluetoothLEExplorer/BluetoothLEExplorer/Assets/BadgeLogo.scale-150.png differ
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Assets/BadgeLogo.scale-200.png b/BluetoothLEExplorer/BluetoothLEExplorer/Assets/BadgeLogo.scale-200.png
new file mode 100644
index 0000000..af324d0
Binary files /dev/null and b/BluetoothLEExplorer/BluetoothLEExplorer/Assets/BadgeLogo.scale-200.png differ
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Assets/BadgeLogo.scale-400.png b/BluetoothLEExplorer/BluetoothLEExplorer/Assets/BadgeLogo.scale-400.png
new file mode 100644
index 0000000..e7e3aa0
Binary files /dev/null and b/BluetoothLEExplorer/BluetoothLEExplorer/Assets/BadgeLogo.scale-400.png differ
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Assets/BadgeSource.png b/BluetoothLEExplorer/BluetoothLEExplorer/Assets/BadgeSource.png
new file mode 100644
index 0000000..aaf7f66
Binary files /dev/null and b/BluetoothLEExplorer/BluetoothLEExplorer/Assets/BadgeSource.png differ
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Assets/SplashScreen.scale-200.png b/BluetoothLEExplorer/BluetoothLEExplorer/Assets/SplashScreen.scale-200.png
new file mode 100644
index 0000000..12db6f2
Binary files /dev/null and b/BluetoothLEExplorer/BluetoothLEExplorer/Assets/SplashScreen.scale-200.png differ
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Assets/Square150x150Logo.scale-100.png b/BluetoothLEExplorer/BluetoothLEExplorer/Assets/Square150x150Logo.scale-100.png
new file mode 100644
index 0000000..76ce6dc
Binary files /dev/null and b/BluetoothLEExplorer/BluetoothLEExplorer/Assets/Square150x150Logo.scale-100.png differ
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Assets/Square150x150Logo.scale-200.png b/BluetoothLEExplorer/BluetoothLEExplorer/Assets/Square150x150Logo.scale-200.png
new file mode 100644
index 0000000..bdbcfac
Binary files /dev/null and b/BluetoothLEExplorer/BluetoothLEExplorer/Assets/Square150x150Logo.scale-200.png differ
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Assets/Square150x150Logo.scale-400.png b/BluetoothLEExplorer/BluetoothLEExplorer/Assets/Square150x150Logo.scale-400.png
new file mode 100644
index 0000000..041470b
Binary files /dev/null and b/BluetoothLEExplorer/BluetoothLEExplorer/Assets/Square150x150Logo.scale-400.png differ
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Assets/Square310x310Logo.scale-100.png b/BluetoothLEExplorer/BluetoothLEExplorer/Assets/Square310x310Logo.scale-100.png
new file mode 100644
index 0000000..1bb82f9
Binary files /dev/null and b/BluetoothLEExplorer/BluetoothLEExplorer/Assets/Square310x310Logo.scale-100.png differ
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Assets/Square310x310Logo.scale-200.png b/BluetoothLEExplorer/BluetoothLEExplorer/Assets/Square310x310Logo.scale-200.png
new file mode 100644
index 0000000..53106c0
Binary files /dev/null and b/BluetoothLEExplorer/BluetoothLEExplorer/Assets/Square310x310Logo.scale-200.png differ
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Assets/Square310x310Logo.scale-400.png b/BluetoothLEExplorer/BluetoothLEExplorer/Assets/Square310x310Logo.scale-400.png
new file mode 100644
index 0000000..c88e2cd
Binary files /dev/null and b/BluetoothLEExplorer/BluetoothLEExplorer/Assets/Square310x310Logo.scale-400.png differ
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Assets/Square44x44Logo.scale-100.png b/BluetoothLEExplorer/BluetoothLEExplorer/Assets/Square44x44Logo.scale-100.png
new file mode 100644
index 0000000..7fcbaf3
Binary files /dev/null and b/BluetoothLEExplorer/BluetoothLEExplorer/Assets/Square44x44Logo.scale-100.png differ
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Assets/Square44x44Logo.scale-200.png b/BluetoothLEExplorer/BluetoothLEExplorer/Assets/Square44x44Logo.scale-200.png
new file mode 100644
index 0000000..fff0812
Binary files /dev/null and b/BluetoothLEExplorer/BluetoothLEExplorer/Assets/Square44x44Logo.scale-200.png differ
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Assets/Square44x44Logo.scale-400.png b/BluetoothLEExplorer/BluetoothLEExplorer/Assets/Square44x44Logo.scale-400.png
new file mode 100644
index 0000000..2d162d2
Binary files /dev/null and b/BluetoothLEExplorer/BluetoothLEExplorer/Assets/Square44x44Logo.scale-400.png differ
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Assets/Square44x44Logo.targetsize-24_altform-unplated.png b/BluetoothLEExplorer/BluetoothLEExplorer/Assets/Square44x44Logo.targetsize-24_altform-unplated.png
new file mode 100644
index 0000000..d1cbfe1
Binary files /dev/null and b/BluetoothLEExplorer/BluetoothLEExplorer/Assets/Square44x44Logo.targetsize-24_altform-unplated.png differ
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Assets/Square71x71Logo.scale-100.png b/BluetoothLEExplorer/BluetoothLEExplorer/Assets/Square71x71Logo.scale-100.png
new file mode 100644
index 0000000..a50abf1
Binary files /dev/null and b/BluetoothLEExplorer/BluetoothLEExplorer/Assets/Square71x71Logo.scale-100.png differ
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Assets/Square71x71Logo.scale-200.png b/BluetoothLEExplorer/BluetoothLEExplorer/Assets/Square71x71Logo.scale-200.png
new file mode 100644
index 0000000..1edc581
Binary files /dev/null and b/BluetoothLEExplorer/BluetoothLEExplorer/Assets/Square71x71Logo.scale-200.png differ
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Assets/Square71x71Logo.scale-400.png b/BluetoothLEExplorer/BluetoothLEExplorer/Assets/Square71x71Logo.scale-400.png
new file mode 100644
index 0000000..0699f3f
Binary files /dev/null and b/BluetoothLEExplorer/BluetoothLEExplorer/Assets/Square71x71Logo.scale-400.png differ
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Assets/StoreLogo-125.png b/BluetoothLEExplorer/BluetoothLEExplorer/Assets/StoreLogo-125.png
new file mode 100644
index 0000000..532539f
Binary files /dev/null and b/BluetoothLEExplorer/BluetoothLEExplorer/Assets/StoreLogo-125.png differ
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Assets/StoreLogo-150.png b/BluetoothLEExplorer/BluetoothLEExplorer/Assets/StoreLogo-150.png
new file mode 100644
index 0000000..7ee7481
Binary files /dev/null and b/BluetoothLEExplorer/BluetoothLEExplorer/Assets/StoreLogo-150.png differ
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Assets/StoreLogo-200.png b/BluetoothLEExplorer/BluetoothLEExplorer/Assets/StoreLogo-200.png
new file mode 100644
index 0000000..b5f4017
Binary files /dev/null and b/BluetoothLEExplorer/BluetoothLEExplorer/Assets/StoreLogo-200.png differ
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Assets/StoreLogo-400.png b/BluetoothLEExplorer/BluetoothLEExplorer/Assets/StoreLogo-400.png
new file mode 100644
index 0000000..b015855
Binary files /dev/null and b/BluetoothLEExplorer/BluetoothLEExplorer/Assets/StoreLogo-400.png differ
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Assets/StoreLogo.png b/BluetoothLEExplorer/BluetoothLEExplorer/Assets/StoreLogo.png
new file mode 100644
index 0000000..de9200a
Binary files /dev/null and b/BluetoothLEExplorer/BluetoothLEExplorer/Assets/StoreLogo.png differ
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Assets/Wide310x150Logo.scale-100.png b/BluetoothLEExplorer/BluetoothLEExplorer/Assets/Wide310x150Logo.scale-100.png
new file mode 100644
index 0000000..9816fdd
Binary files /dev/null and b/BluetoothLEExplorer/BluetoothLEExplorer/Assets/Wide310x150Logo.scale-100.png differ
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Assets/Wide310x150Logo.scale-200.png b/BluetoothLEExplorer/BluetoothLEExplorer/Assets/Wide310x150Logo.scale-200.png
new file mode 100644
index 0000000..9b8a82f
Binary files /dev/null and b/BluetoothLEExplorer/BluetoothLEExplorer/Assets/Wide310x150Logo.scale-200.png differ
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Assets/Wide310x150Logo.scale-400.png b/BluetoothLEExplorer/BluetoothLEExplorer/Assets/Wide310x150Logo.scale-400.png
new file mode 100644
index 0000000..83e06fc
Binary files /dev/null and b/BluetoothLEExplorer/BluetoothLEExplorer/Assets/Wide310x150Logo.scale-400.png differ
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/BluetoothLEExplorer.csproj b/BluetoothLEExplorer/BluetoothLEExplorer/BluetoothLEExplorer.csproj
new file mode 100644
index 0000000..cccaac9
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorer/BluetoothLEExplorer.csproj
@@ -0,0 +1,317 @@
+
+
+
+
+ Debug
+ x86
+ {AD1CBE3C-A68A-4E02-8001-E02B815560EE}
+ AppContainerExe
+ Properties
+ BluetoothLEExplorer
+ BluetoothLEExplorer
+ en-US
+ UAP
+ 10.0.15063.0
+ 10.0.15063.0
+ 14
+ true
+ 512
+ {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ BluetoothExplorer_StoreKey.pfx
+ True
+ Always
+ x86|x64|arm
+ DCBC2F4C73DE671847F017A13666994B37C0EC04
+ win10-arm;win10-arm-aot;win10-x86;win10-x86-aot;win10-x64;win10-x64-aot
+
+
+ true
+ bin\ARM\Debug\
+ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
+ ;2008
+ full
+ ARM
+ false
+ prompt
+ true
+
+
+ bin\ARM\Release\
+ TRACE;NETFX_CORE;WINDOWS_UWP
+ true
+ ;2008
+ pdbonly
+ ARM
+ false
+ prompt
+ true
+ true
+
+
+ true
+ bin\x64\Debug\
+ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
+ ;2008
+ full
+ x64
+ false
+ prompt
+ true
+
+
+ bin\x64\Release\
+ TRACE;NETFX_CORE;WINDOWS_UWP
+ true
+ ;2008
+ pdbonly
+ x64
+ false
+ prompt
+ true
+ true
+
+
+ true
+ bin\x86\Debug\
+ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
+ ;2008
+ full
+ x86
+ false
+ prompt
+ true
+
+
+ bin\x86\Release\
+ TRACE;NETFX_CORE;WINDOWS_UWP
+ true
+ ;2008
+ pdbonly
+ x86
+ false
+ prompt
+ true
+ true
+
+
+
+ App.xaml
+
+
+ GattLocalCharacteristicControl.xaml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ BloodPressureServicePage.xaml
+
+
+ HeartRateServicePage.xaml
+
+
+ MicrosoftServicePage.xaml
+
+
+ BatteryServicePage.xaml
+
+
+ Beacon.xaml
+
+
+ Busy.xaml
+
+
+ CharacteristicPage.xaml
+
+
+ DeviceServicesPage.xaml
+
+
+ Discover.xaml
+
+
+ SettingsPage.xaml
+
+
+ Shell.xaml
+
+
+ Splash.xaml
+
+
+ VirtualPeripheralPage.xaml
+
+
+
+
+ Designer
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ MSBuild:Compile
+ Designer
+
+
+
+
+ Designer
+ MSBuild:Compile
+
+
+ Designer
+ MSBuild:Compile
+ PreserveNewest
+
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+ Designer
+ MSBuild:Compile
+
+
+ Designer
+ MSBuild:Compile
+
+
+ Designer
+ MSBuild:Compile
+
+
+ MSBuild:Compile
+ Designer
+
+
+
+
+
+
+
+ {644f40ad-eabb-4570-b9b7-e3f8ddaa80f3}
+ GattServicesLibrary
+
+
+ {be79fc52-6041-4913-b0d4-66c100944904}
+ SortedObservableCollection
+
+
+ {37c63fdd-d995-4cc2-b014-4fb323194001}
+ GattHelper
+
+
+
+
+ 5.3.3
+
+
+ 2.0.0
+
+
+ 10.0.2
+
+
+ 3.5.0
+
+
+ 1.1.12
+
+
+
+ 14.0
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/CustomControls/GattLocalCharacteristicControl.xaml b/BluetoothLEExplorer/BluetoothLEExplorer/CustomControls/GattLocalCharacteristicControl.xaml
new file mode 100644
index 0000000..ca7c9e4
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorer/CustomControls/GattLocalCharacteristicControl.xaml
@@ -0,0 +1,49 @@
+
+
+
+
+ Green
+ Green
+ Green
+
+
+ Red
+ Red
+ Red
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/CustomControls/GattLocalCharacteristicControl.xaml.cs b/BluetoothLEExplorer/BluetoothLEExplorer/CustomControls/GattLocalCharacteristicControl.xaml.cs
new file mode 100644
index 0000000..ddecd02
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorer/CustomControls/GattLocalCharacteristicControl.xaml.cs
@@ -0,0 +1,242 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------------------------
+using System;
+using System.ComponentModel;
+using System.Text;
+using BluetoothLEExplorer.ViewModels;
+using Windows.ApplicationModel.Core;
+using Windows.UI.Core;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.Storage.Streams;
+using Windows.Devices.Bluetooth.GenericAttributeProfile;
+using Windows.Security.Cryptography;
+using GattHelper.Converters;
+using Windows.UI.Popups;
+
+//// The User Control item template is documented at http://go.microsoft.com/fwlink/?LinkId=234236
+
+namespace BluetoothLEExplorer.CustomControls
+{
+ ///
+ /// UserControl to display a Characteristic
+ ///
+ public sealed partial class GattLocalCharacteristicControl : UserControl, INotifyPropertyChanged
+ {
+ ///
+ /// Callback when characteristic changes
+ ///
+ ///
+ ///
+ private static void OnCharacteristicPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ GattLocalCharacteristicControl control = d as GattLocalCharacteristicControl;
+ control.Characteristic = e.NewValue as GenericGattCharacteristicViewModel;
+ control.Value = GattServicesLibrary.Helpers.ValueConverter.ConvertGattCharacteristicValueToString(control.Characteristic.Characteristic);
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public GattLocalCharacteristicControl()
+ {
+ this.InitializeComponent();
+ }
+
+ ///
+ /// Executes when the characteristic changes
+ ///
+ ///
+ ///
+ private async void Characteristic_PropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(
+ CoreDispatcherPriority.Normal,
+ () =>
+ {
+ if (e.PropertyName == "Value")
+ {
+ Value = GattServicesLibrary.Helpers.ValueConverter.ConvertGattCharacteristicValueToString(Characteristic.Characteristic);
+ }
+ });
+ }
+
+ ///
+ /// Source for the property
+ ///
+ private string value;
+
+ ///
+ /// Gets or sets the value of this characteristic
+ ///
+ public string Value
+ {
+ get
+ {
+ return value;
+ }
+
+ set
+ {
+ if (value != this.value)
+ {
+ // If this is a read only control then no need to send it back to the base characteristic
+ if (this.IsReadOnly)
+ {
+ this.value = value;
+ }
+ else
+ {
+ try
+ {
+ Characteristic.SetValueFromString(value);
+ this.value = value;
+ }
+ catch (Exception ex)
+ {
+ ErrorDialog(ex.Message);
+ }
+ }
+ OnPropertyChanged(new PropertyChangedEventArgs("Value"));
+ }
+ }
+ }
+
+ async void ErrorDialog(string message)
+ {
+ MessageDialog err = new MessageDialog(message, "Error");
+ await err.ShowAsync();
+ return;
+ }
+
+ ///
+ /// Gets the visibility of the the notify button
+ ///
+ private Visibility NotifyButtonVisibility
+ {
+ get
+ {
+ if (Characteristic.HasNotifyDescriptor == true || Characteristic.HasIndicateDescriptor == true)
+ {
+ return Visibility.Visible;
+ }
+ else
+ {
+ return Visibility.Collapsed;
+ }
+ }
+ }
+
+ ///
+ /// Gets the text for the notify button
+ ///
+ private string NotifyButtonText
+ {
+ get
+ {
+ StringBuilder ret = new StringBuilder();
+ if (Characteristic.HasNotifyDescriptor)
+ {
+ ret.Append("Notify");
+ if (Characteristic.HasIndicateDescriptor)
+ {
+ ret.Append("/Indicate");
+ }
+ }
+ else if (Characteristic.HasIndicateDescriptor)
+ {
+ ret.Append("Indicate");
+ }
+
+ return ret.ToString();
+ }
+ }
+
+ ///
+ /// Gets or sets the characteristic this control wraps
+ ///
+ public GenericGattCharacteristicViewModel Characteristic
+ {
+ get
+ {
+ return (GenericGattCharacteristicViewModel)GetValue(CharacteristicProperty);
+ }
+
+ set
+ {
+ SetValue(CharacteristicProperty, value);
+
+ Characteristic.Characteristic.PropertyChanged += Characteristic_PropertyChanged;
+ }
+ }
+
+ ///
+ /// Using a DependencyProperty as the backing store for Characteristic. This enables animation, styling, binding, etc...
+ ///
+ public static readonly DependencyProperty CharacteristicProperty =
+ DependencyProperty.Register(
+ "Characteristic",
+ typeof(GenericGattCharacteristicViewModel),
+ typeof(GattLocalCharacteristicControl),
+ new PropertyMetadata(
+ null,
+ new PropertyChangedCallback(OnCharacteristicPropertyChanged)));
+
+ ///
+ /// Gets or sets the visibility
+ ///
+ public Visibility ShowValue
+ {
+ get { return (Visibility)GetValue(ShowValueProperty); }
+ set { SetValue(ShowValueProperty, Visibility); }
+ }
+
+ ///
+ /// Using a DependencyProperty as the backing store for ShowValue. This enables animation, styling, binding, etc...
+ ///
+ public static readonly DependencyProperty ShowValueProperty =
+ DependencyProperty.Register("ShowValue", typeof(Visibility), typeof(GattLocalCharacteristicControl), new PropertyMetadata(Visibility.Visible));
+
+ ///
+ /// Gets or sets the readonly field of the value text box
+ ///
+ public bool IsReadOnly
+ {
+ get
+ {
+ return (bool)GetValue(IsReadOnlyProperty);
+ }
+
+ set
+ {
+ SetValue(IsReadOnlyProperty, value);
+ }
+ }
+
+ ///
+ /// Using a DependencyProperty as the backing store for IsReadOnly
+ ///
+ public static readonly DependencyProperty IsReadOnlyProperty =
+ DependencyProperty.Register("IsReadOnly", typeof(bool), typeof(GattLocalCharacteristicControl), new PropertyMetadata(true));
+
+
+ ///
+ /// Event to notify when this object has changed
+ ///
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ ///
+ /// Property changed method
+ ///
+ /// Property that changed
+ private void OnPropertyChanged(PropertyChangedEventArgs e)
+ {
+ if (PropertyChanged != null)
+ {
+ PropertyChanged(this, e);
+ }
+ }
+ }
+}
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Help.htm b/BluetoothLEExplorer/BluetoothLEExplorer/Help.htm
new file mode 100644
index 0000000..e0b784b
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorer/Help.htm
@@ -0,0 +1,10 @@
+
+
+
+
+ Template 10
+
+
+
+
+
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Models/GattSampleContext.cs b/BluetoothLEExplorer/BluetoothLEExplorer/Models/GattSampleContext.cs
new file mode 100644
index 0000000..1d59511
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorer/Models/GattSampleContext.cs
@@ -0,0 +1,747 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------------------------
+using System;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using BluetoothLEExplorer.ViewModels;
+using Windows.Devices.Bluetooth.Advertisement;
+using Windows.Devices.Enumeration;
+using Windows.UI.Popups;
+using Windows.Foundation.Metadata;
+using System.Threading;
+
+namespace BluetoothLEExplorer.Models
+{
+ ///
+ /// Context for the entire app. This is where all app wide variables are stored
+ ///
+ public class GattSampleContext : INotifyPropertyChanged
+ {
+ ///
+ /// Gets the app context
+ ///
+ public static GattSampleContext Context { get; private set; } = new GattSampleContext();
+
+ ///
+ /// AQS search string used to find bluetooth devices
+ ///
+ private const string BTLEDeviceWatcherAQSString = "(System.Devices.Aep.ProtocolId:=\"{bb7bb05e-5972-42b5-94fc-76eaa7084d49}\")";
+
+ ///
+ /// AQS search string to find bluetooth device dev nodes
+ ///
+ private const string DevNodeBTLEDeviceWatcherAQSString = "(System.Devices.ClassGuid:=\"{e0cbf06c-cd8b-4647-bb8a-263b43f0f974}\")";
+
+ ///
+ /// Device dev node property to get battery level
+ ///
+ private const string BatteryLevelGUID = "{995EF0B0-7EB3-4A8B-B9CE-068BB3F4AF69} 10";
+
+ ///
+ /// Device dev node property to get device address
+ ///
+ private const string BluetoothDeviceAddress = "System.DeviceInterface.Bluetooth.DeviceAddress";
+
+ ///
+ /// Gets or sets the list of available bluetooth devices
+ ///
+ public ObservableCollection BluetoothLEDevices { get; set; } = new ObservableCollection();
+
+ ///
+ /// Gets or sets the selected bluetooth device
+ ///
+ public ObservableBluetoothLEDevice SelectedBluetoothLEDevice { get; set; } = null;
+
+ ///
+ /// Gets or sets the selected characteristic
+ ///
+ public ObservableGattCharacteristics SelectedCharacteristic { get; set; } = null;
+
+ ///
+ /// Lock around the . Used in the Add/Removed/Updated callbacks
+ ///
+ private SemaphoreSlim BluetoothLEDevicesLock = new SemaphoreSlim(1, 1);
+
+ ///
+ /// Lock around the . Used in the Add/Removed/Updated callbacks
+ ///
+ private SemaphoreSlim DevNodeLock = new SemaphoreSlim(1, 1);
+
+ ///
+ /// Device watcher used to find bluetooth devices
+ ///
+ private DeviceWatcher deviceWatcher;
+
+ ///
+ /// Device watcher used to find bluetooth device dev nodes
+ ///
+ private DeviceWatcher devNodeWatcher;
+
+ ///
+ /// Advertisement watcher used to find bluetooth devices
+ ///
+ private BluetoothLEAdvertisementWatcher advertisementWatcher;
+
+ ///
+ /// 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
+ /// the displayed list.
+ ///
+ private List unusedDevices = new List();
+
+ ///
+ /// All the bluetooth dev nodes on the system
+ ///
+ private List devNodes = new List();
+
+ ///
+ /// Gets or sets the list of created service
+ ///
+ public ObservableCollection CreatedServices { get; set; } = new ObservableCollection();
+
+ ///
+ /// Gets or sets the currently selected gatt server service
+ ///
+ public GenericGattServiceViewModel SelectedGattServerService { get; set; } = null;
+
+ ///
+ /// Event to notify when this object has changed
+ ///
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ ///
+ /// Source for property
+ ///
+ private bool isEnumerating = false;
+
+ ///
+ /// Gets a value indicating whether app is currently enumerating
+ ///
+ public bool IsEnumerating
+ {
+ get
+ {
+ return isEnumerating;
+ }
+
+ private set
+ {
+ if (isEnumerating != value)
+ {
+ isEnumerating = value;
+ OnPropertyChanged(new PropertyChangedEventArgs("IsEnumerating"));
+ }
+ }
+ }
+
+ ///
+ /// Source for property
+ ///
+ private bool enumorationFinished = false;
+
+ ///
+ /// Gets a value indicating whether the app is finished enumerating
+ ///
+ public bool EnumerationFinished
+ {
+ get
+ {
+ return enumorationFinished;
+ }
+
+ private set
+ {
+ if (enumorationFinished != value)
+ {
+ enumorationFinished = value;
+ OnPropertyChanged(new PropertyChangedEventArgs("EnumerationFinished"));
+ }
+ }
+ }
+
+ ///
+ /// Source for
+ ///
+ private bool isPeripheralRoleSupported = true;
+
+ ///
+ /// Gets a value indicating whether peripheral mode is supported by this device
+ ///
+ public bool IsPeripheralRoleSupported
+ {
+ get
+ {
+ return isPeripheralRoleSupported;
+ }
+
+ private set
+ {
+ if (isPeripheralRoleSupported != value)
+ {
+ isPeripheralRoleSupported = value;
+ OnPropertyChanged(new PropertyChangedEventArgs("IsPeripheralRoleSupported"));
+ }
+ }
+ }
+
+ ///
+ /// Source for
+ ///
+ private bool isCentralRoleSupported = true;
+
+ ///
+ /// Gets a value indicating whether central role is supported by this device
+ ///
+ public bool IsCentralRoleSupported
+ {
+ get
+ {
+ return isCentralRoleSupported;
+ }
+
+ private set
+ {
+ if (isCentralRoleSupported != value)
+ {
+ isCentralRoleSupported = value;
+ OnPropertyChanged(new PropertyChangedEventArgs("IsCentralRoleSupported"));
+ }
+ }
+ }
+
+ ///
+ /// Prevents a default instance of the class from being created.
+ ///
+ private GattSampleContext()
+ {
+ Init();
+ }
+
+ ///
+ /// Initializes the app context
+ ///
+ private async void Init()
+ {
+ Windows.Devices.Bluetooth.BluetoothAdapter adapter = await Windows.Devices.Bluetooth.BluetoothAdapter.GetDefaultAsync();
+
+ if(adapter == null)
+ {
+ MessageDialog msg = new MessageDialog("Error getting access to Bluetooth adaptor. Do you have a have bluetooth enabled?", "Error");
+ await msg.ShowAsync();
+
+ IsPeripheralRoleSupported = false;
+ IsCentralRoleSupported = false;
+ }
+ else
+ {
+ IsPeripheralRoleSupported = adapter.IsPeripheralRoleSupported;
+ IsCentralRoleSupported = adapter.IsCentralRoleSupported;
+ }
+
+ // Start the dev node watcher
+ string[] requestedProperties =
+ {
+ BatteryLevelGUID,
+ BluetoothDeviceAddress
+ };
+
+ devNodeWatcher =
+ DeviceInformation.CreateWatcher(
+ DevNodeBTLEDeviceWatcherAQSString,
+ requestedProperties,
+ DeviceInformationKind.Device);
+
+ devNodeWatcher.Added += DevNodeWatcher_Added;
+ devNodeWatcher.Removed += DevNodeWatcher_Removed;
+ devNodeWatcher.Updated += DevNodeWatcher_Updated;
+
+ devNodeWatcher.Start();
+
+ return;
+ }
+
+
+
+ private async void DevNodeWatcher_Added(DeviceWatcher sender, DeviceInformation args)
+ {
+ try
+ {
+ await DevNodeLock.WaitAsync();
+ devNodes.Add(args);
+ }
+ finally
+ {
+ DevNodeLock.Release();
+ }
+
+ await UpdateBatteryLevel(args);
+ }
+
+ private async void DevNodeWatcher_Removed(DeviceWatcher sender, DeviceInformationUpdate args)
+ {
+ try
+ {
+ await DevNodeLock.WaitAsync();
+
+ int index = devNodes.FindIndex(x => x.Id == args.Id);
+ devNodes.RemoveAt(index);
+ }
+ finally
+ {
+ DevNodeLock.Release();
+ }
+
+ }
+
+ private async void DevNodeWatcher_Updated(DeviceWatcher sender, DeviceInformationUpdate args)
+ {
+ DeviceInformation dev = null;
+ try
+ {
+ await DevNodeLock.WaitAsync();
+ dev = devNodes.FirstOrDefault(x => x.Id == args.Id);
+ }
+ finally
+ {
+ DevNodeLock.Release();
+ }
+
+ if(dev != null)
+ {
+ dev.Update(args);
+ await UpdateBatteryLevel(dev);
+ }
+ }
+
+ ///
+ /// Update the battery level of a ObservableBluetoothLEDevice based on DeviceInfo object
+ ///
+ /// DeviceInformation object
+ ///
+ private async Task UpdateBatteryLevel(DeviceInformation dev)
+ {
+ if (dev.Properties.Keys.Contains(BatteryLevelGUID) &&
+ dev.Properties[BatteryLevelGUID] != null &&
+ dev.Properties.Keys.Contains(BluetoothDeviceAddress) &&
+ dev.Properties[BluetoothDeviceAddress] != null)
+ {
+ try
+ {
+ await BluetoothLEDevicesLock.WaitAsync();
+
+ foreach (ObservableBluetoothLEDevice device in BluetoothLEDevices)
+ {
+ string addr = GetDelimitedAddr((string)dev.Properties[BluetoothDeviceAddress]);
+ if (device.BluetoothAddressAsString == addr)
+ {
+ await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(
+ Windows.UI.Core.CoreDispatcherPriority.Normal,
+ () =>
+ {
+ device.BatteryLevel = Convert.ToInt32((byte)dev.Properties[BatteryLevelGUID]);
+ });
+ break;
+ }
+ }
+ }
+ finally
+ {
+ BluetoothLEDevicesLock.Release();
+ }
+ }
+ }
+
+ ///
+ /// Update the battery level of a ObservableBluetoothLEDevice by searching through known dev nodes
+ ///
+ /// device to update
+ ///
+ private async Task UpdateBatteryLevel(ObservableBluetoothLEDevice dev)
+ {
+ foreach(DeviceInformation devNode in devNodes)
+ {
+ string addr = dev.BluetoothAddressAsString.Replace(":", String.Empty);
+
+ if (devNode.Properties.Keys.Contains(BatteryLevelGUID) &&
+ devNode.Properties[BatteryLevelGUID] != null &&
+ devNode.Properties.Keys.Contains(BluetoothDeviceAddress) &&
+ devNode.Properties[BluetoothDeviceAddress] != null)
+ {
+ if ((string)devNode.Properties[BluetoothDeviceAddress] == addr)
+ {
+ await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(
+ Windows.UI.Core.CoreDispatcherPriority.Normal,
+ () =>
+ {
+ dev.BatteryLevel = Convert.ToInt32((byte)devNode.Properties[BatteryLevelGUID]);
+ });
+ break;
+ }
+ }
+ }
+ }
+
+ ///
+ /// Add : to bluetooth address without them
+ ///
+ ///
+ ///
+ private string GetDelimitedAddr(string addr)
+ {
+ // Add : delimiters to raw address
+ var list = Enumerable.Range(0, addr.Length / 2).Select(i => addr.Substring(i * 2, 2)).ToList();
+ return string.Join(":", list);
+ }
+
+ ///
+ /// Starts enumeration of bluetooth device
+ ///
+ public void StartEnumeration()
+ {
+ // Additional properties we would like about the device.
+ string[] requestedProperties =
+ {
+ "System.Devices.Aep.Category",
+ "System.Devices.Aep.ContainerId",
+ "System.Devices.Aep.DeviceAddress",
+ "System.Devices.Aep.IsConnected",
+ "System.Devices.Aep.IsPaired",
+ "System.Devices.Aep.IsPresent",
+ "System.Devices.Aep.ProtocolId",
+ "System.Devices.Aep.Bluetooth.Le.IsConnectable",
+ "System.Devices.Aep.SignalStrength"
+ };
+
+ // BT_Code: Currently Bluetooth APIs don't provide a selector to get ALL devices that are both paired and non-paired.
+ deviceWatcher =
+ DeviceInformation.CreateWatcher(
+ BTLEDeviceWatcherAQSString,
+ requestedProperties,
+ DeviceInformationKind.AssociationEndpoint);
+
+ // Register event handlers before starting the watcher.
+ deviceWatcher.Added += DeviceWatcher_Added;
+ deviceWatcher.Updated += DeviceWatcher_Updated;
+ deviceWatcher.Removed += DeviceWatcher_Removed;
+ deviceWatcher.EnumerationCompleted += DeviceWatcher_EnumerationCompleted;
+ deviceWatcher.Stopped += DeviceWatcher_Stopped;
+
+ advertisementWatcher = new BluetoothLEAdvertisementWatcher();
+ advertisementWatcher.Received += AdvertisementWatcher_Received;
+
+ BluetoothLEDevices.Clear();
+
+ deviceWatcher.Start();
+ advertisementWatcher.Start();
+ IsEnumerating = true;
+ EnumerationFinished = false;
+ }
+
+ ///
+ /// Stops enumeration of bluetooth device
+ ///
+ public void StopEnumeration()
+ {
+ if (deviceWatcher != null)
+ {
+ // Unregister the event handlers.
+ deviceWatcher.Added -= DeviceWatcher_Added;
+ deviceWatcher.Updated -= DeviceWatcher_Updated;
+ deviceWatcher.Removed -= DeviceWatcher_Removed;
+ 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;
+ }
+ }
+
+ ///
+ /// Updates device metadata based on advertisement received
+ ///
+ ///
+ ///
+ private async void AdvertisementWatcher_Received(BluetoothLEAdvertisementWatcher sender, BluetoothLEAdvertisementReceivedEventArgs args)
+ {
+ try
+ {
+
+ await BluetoothLEDevicesLock.WaitAsync();
+
+ foreach (ObservableBluetoothLEDevice d in BluetoothLEDevices)
+ {
+ if (d.BluetoothAddressAsUlong == args.BluetoothAddress)
+ {
+ await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(
+ Windows.UI.Core.CoreDispatcherPriority.Normal,
+ () =>
+ {
+ d.ServiceCount = args.Advertisement.ServiceUuids.Count();
+ });
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine("AdvertisementWatcher_Received: ", ex.Message);
+ }
+ finally
+ {
+ BluetoothLEDevicesLock.Release();
+ }
+ }
+
+ ///
+ /// Callback when a new device is found
+ ///
+ ///
+ ///
+ private async void DeviceWatcher_Added(DeviceWatcher sender, DeviceInformation deviceInfo)
+ {
+ try
+ {
+ // Protect against race condition if the task runs after the app stopped the deviceWatcher.
+ if (sender == deviceWatcher)
+ {
+ await AddDeviceToList(deviceInfo);
+ }
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine("DeviceWatcher_Added: " + ex.Message);
+ }
+ }
+
+ ///
+ /// Executes when a device is updated
+ ///
+ ///
+ ///
+ private async void DeviceWatcher_Updated(DeviceWatcher sender, DeviceInformationUpdate deviceInfoUpdate)
+ {
+ DeviceInformation di = null;
+ bool addNewDI = false;
+
+ try
+ {
+ // Protect against race condition if the task runs after the app stopped the deviceWatcher.
+ if (sender == deviceWatcher)
+ {
+ ObservableBluetoothLEDevice dev;
+
+ // Need to lock as another DeviceWatcher might be modifying BluetoothLEDevices
+ try
+ {
+ await BluetoothLEDevicesLock.WaitAsync();
+ dev = BluetoothLEDevices.FirstOrDefault(device => device.DeviceInfo.Id == deviceInfoUpdate.Id);
+ if (dev != null)
+ { // Found a device in the list, updating it
+ await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(
+ Windows.UI.Core.CoreDispatcherPriority.Normal,
+ async () =>
+ {
+ dev.Update(deviceInfoUpdate);
+ await UpdateBatteryLevel(dev);
+ });
+ }
+ else
+ {
+ // Need to add this device. Can't do that here as we have the lock
+ addNewDI = true;
+ }
+ }
+ finally
+ {
+ BluetoothLEDevicesLock.Release();
+ }
+
+ if(addNewDI == true)
+ {
+ try
+ {
+ await BluetoothLEDevicesLock.WaitAsync();
+ di = unusedDevices.FirstOrDefault(device => device.Id == deviceInfoUpdate.Id);
+ if (di != null)
+ { // We found this device before.
+ unusedDevices.Remove(di);
+ di.Update(deviceInfoUpdate);
+ }
+ }
+ finally
+ {
+ BluetoothLEDevicesLock.Release();
+ }
+
+ if (di != null)
+ {
+ await AddDeviceToList(di);
+ }
+
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine("DeviceWatcher_Updated: " + ex.Message);
+ }
+ }
+
+ ///
+ /// Executes when a device is removed from enumeration
+ ///
+ ///
+ ///
+ private async void DeviceWatcher_Removed(DeviceWatcher sender, DeviceInformationUpdate deviceInfoUpdate)
+ {
+ try
+ {
+ // Protect against race condition if the task runs after the app stopped the deviceWatcher.
+ if (sender == deviceWatcher)
+ {
+ ObservableBluetoothLEDevice dev;
+
+ try
+ {
+ // Need to lock as another DeviceWatcher might be modifying BluetoothLEDevices
+ await BluetoothLEDevicesLock.WaitAsync();
+
+ // Find the corresponding DeviceInformation in the collection and remove it.
+ dev = BluetoothLEDevices.FirstOrDefault(device => device.DeviceInfo.Id == deviceInfoUpdate.Id);
+ if (dev != null)
+ { // Found it in our displayed devices
+ await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(
+ Windows.UI.Core.CoreDispatcherPriority.Normal,
+ () =>
+ {
+ Debug.Assert(BluetoothLEDevices.Remove(dev), "DeviceWatcher_Removed: Failed to remove device from list");
+ });
+ }
+ else
+ { // Did not find in diplayed list, let's check the unused list
+ DeviceInformation di = unusedDevices.FirstOrDefault(device => device.Id == deviceInfoUpdate.Id);
+
+ if (di != null)
+ { // Found in unused devices, remove it
+ Debug.Assert(unusedDevices.Remove(di), "DeviceWatcher_Removed: Failed to remove device from unused");
+ }
+ }
+ }
+ finally
+ {
+ BluetoothLEDevicesLock.Release();
+ }
+
+ }
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine("DeviceWatcher_Removed: " + ex.Message);
+ }
+ }
+
+ ///
+ /// Executes when Enumeration has finished
+ ///
+ ///
+ ///
+ private void DeviceWatcher_EnumerationCompleted(DeviceWatcher sender, object e)
+ {
+ // Protect against race condition if the task runs after the app stopped the deviceWatcher.
+ if (sender == deviceWatcher)
+ {
+ StopEnumeration();
+ EnumerationFinished = true;
+ }
+ }
+
+ ///
+ /// Adds the new or updated device to the displayed or unused list
+ ///
+ ///
+ ///
+ private async Task AddDeviceToList(DeviceInformation deviceInfo)
+ {
+ ObservableBluetoothLEDevice dev = new ObservableBluetoothLEDevice(deviceInfo);
+
+ // Let's make it connectable by default, we have error handles in case it doesn't work
+ bool shouldDisplay =
+ ((dev.DeviceInfo.Properties.Keys.Contains("System.Devices.Aep.Bluetooth.Le.IsConnectable") &&
+ (bool)dev.DeviceInfo.Properties["System.Devices.Aep.Bluetooth.Le.IsConnectable"])) ||
+ ((dev.DeviceInfo.Properties.Keys.Contains("System.Devices.Aep.IsConnected") &&
+ (bool)dev.DeviceInfo.Properties["System.Devices.Aep.IsConnected"]));
+
+ if (shouldDisplay)
+ {
+ // Need to lock as another DeviceWatcher might be modifying BluetoothLEDevices
+ try
+ {
+ await BluetoothLEDevicesLock.WaitAsync();
+
+ await UpdateBatteryLevel(dev);
+
+ if (!BluetoothLEDevices.Contains(dev))
+ {
+ await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(
+ Windows.UI.Core.CoreDispatcherPriority.Normal,
+ () =>
+ {
+ BluetoothLEDevices.Add(dev);
+ });
+ }
+ }
+ finally
+ {
+ BluetoothLEDevicesLock.Release();
+ }
+ }
+ else
+ {
+ try
+ {
+ await BluetoothLEDevicesLock.WaitAsync();
+ unusedDevices.Add(deviceInfo);
+ }
+ finally
+ {
+ BluetoothLEDevicesLock.Release();
+ }
+ }
+ }
+
+ ///
+ /// Executes when device watcher has stopped
+ ///
+ ///
+ ///
+ private void DeviceWatcher_Stopped(DeviceWatcher sender, object e)
+ {
+ // Implimented for completeness
+ }
+
+ ///
+ /// Executes when a property has changed
+ ///
+ ///
+ private void OnPropertyChanged(PropertyChangedEventArgs e)
+ {
+ if (PropertyChanged != null)
+ {
+ PropertyChanged(this, e);
+ }
+ }
+ }
+}
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Models/ObservableBluetoothLEDevice.cs b/BluetoothLEExplorer/BluetoothLEExplorer/Models/ObservableBluetoothLEDevice.cs
new file mode 100644
index 0000000..cc6ba10
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorer/Models/ObservableBluetoothLEDevice.cs
@@ -0,0 +1,679 @@
+//
+// 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;
+
+namespace BluetoothLEExplorer.Models
+{
+ ///
+ /// Wrapper around to make it easier to use
+ ///
+ public class ObservableBluetoothLEDevice : INotifyPropertyChanged, IEquatable
+ {
+ ///
+ /// 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"));
+ }
+ }
+ }
+
+ ///
+ /// 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"));
+ }
+ }
+ }
+
+ private bool isSecureConnection;
+
+ public bool IsSecureConnection
+ {
+ get
+ {
+ return isSecureConnection;
+ }
+
+ set
+ {
+ if (isSecureConnection != value)
+ {
+ isSecureConnection = value;
+ OnPropertyChanged(new PropertyChangedEventArgs("IsSecureConnection"));
+ }
+ }
+ }
+
+ ///
+ /// Source for
+ ///
+ private ObservableCollection services = new ObservableCollection();
+
+ ///
+ /// Gets the services this device supports
+ ///
+ public ObservableCollection 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"));
+ }
+ }
+ }
+
+ ///
+ /// Source for
+ ///
+ private int batteryLevel = -1;
+
+ ///
+ /// Gets or sets the Battery level of this device. -1 if unknown.
+ ///
+ public int BatteryLevel
+ {
+ get
+ {
+ return batteryLevel;
+ }
+
+ set
+ {
+ if (batteryLevel != value)
+ {
+ batteryLevel = value;
+ OnPropertyChanged(new PropertyChangedEventArgs("BatteryLevel"));
+ }
+ }
+ }
+
+ 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"));
+ }
+ }
+ }
+
+ ///
+ /// 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;
+
+ LoadGlyph();
+
+ this.PropertyChanged += ObservableBluetoothLEDevice_PropertyChanged;
+ }
+
+ private void ObservableBluetoothLEDevice_PropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if(e.PropertyName == "DeviceInfo")
+ {
+ 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);
+ }
+ 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;
+
+ IsPaired = DeviceInfo.Pairing.IsPaired;
+ IsConnected = BluetoothLEDevice.ConnectionStatus == BluetoothConnectionStatus.Connected;
+
+ Name = BluetoothLEDevice.Name;
+
+ // Get all the services for this device
+ CancellationTokenSource GetGattServicesAsyncTokenSource = new CancellationTokenSource(5000);
+ var GetGattServicesAsyncTask = Task.Run(() => BluetoothLEDevice.GetGattServicesAsync(BluetoothCacheMode.Uncached), GetGattServicesAsyncTokenSource.Token);
+
+ result = await GetGattServicesAsyncTask.Result;
+
+ if (result.Status == GattCommunicationStatus.Success)
+ {
+ // In case we connected before, clear the service list and recreate it
+ Services.Clear();
+
+ System.Diagnostics.Debug.WriteLine(debugMsg + "GetGattServiceAsync SUCCESS");
+ foreach (var serv in result.Services)
+ {
+ Services.Add(new ObservableGattDeviceService(serv));
+ }
+
+ 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();
+
+ 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,
+ () =>
+ {
+ Name = BluetoothLEDevice.Name;
+ });
+ }
+
+ ///
+ /// 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;
+ IsConnected = BluetoothLEDevice.ConnectionStatus == BluetoothConnectionStatus.Connected;
+ });
+ }
+
+ ///
+ /// 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 () =>
+ {
+ DeviceThumbnail deviceThumbnail = await DeviceInfo.GetGlyphThumbnailAsync();
+ BitmapImage glyphBitmapImage = new BitmapImage();
+ await glyphBitmapImage.SetSourceAsync(deviceThumbnail);
+ Glyph = glyphBitmapImage;
+ });
+ }
+
+ ///
+ /// 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"));
+ }
+ }
+}
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Models/ObservableGattCharacteristics.cs b/BluetoothLEExplorer/BluetoothLEExplorer/Models/ObservableGattCharacteristics.cs
new file mode 100644
index 0000000..144b102
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorer/Models/ObservableGattCharacteristics.cs
@@ -0,0 +1,742 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------------------------
+using System;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Text;
+using System.Threading.Tasks;
+using BluetoothLEExplorer.Services.GattUuidsService;
+using BluetoothLEExplorer.Services.Other;
+using Windows.Devices.Bluetooth;
+using Windows.Devices.Bluetooth.GenericAttributeProfile;
+using Windows.Security.Cryptography;
+using Windows.Storage.Streams;
+using GattHelper.Converters;
+
+namespace BluetoothLEExplorer.Models
+{
+ ///
+ /// Wrapper around to make it easier to use
+ ///
+ public class ObservableGattCharacteristics : INotifyPropertyChanged
+ {
+ ///
+ /// Enum used to determine how the should be displayed
+ ///
+ public enum DisplayTypes
+ {
+ NotSet,
+ Bool,
+ Decimal,
+ Hex,
+ UTF8,
+ UTF16,
+ Unsupported
+ }
+
+ ///
+ /// Raw buffer of this value of this characteristic
+ ///
+ private IBuffer rawData;
+
+ ///
+ /// byte array representation of the characteristic value
+ ///
+ private byte[] data;
+
+ ///
+ /// Source for
+ ///
+ private GattCharacteristic characteristic;
+
+ ///
+ /// Gets or sets the characteristic this class wraps
+ ///
+ public GattCharacteristic Characteristic
+ {
+ get
+ {
+ return characteristic;
+ }
+
+ set
+ {
+ if (characteristic != value)
+ {
+ characteristic = value;
+ OnPropertyChanged(new PropertyChangedEventArgs("Characteristic"));
+ }
+ }
+ }
+
+ ///
+ /// Source for
+ ///
+ private bool isIndicateSet = false;
+
+ ///
+ /// Gets or sets a value indicating whether indicate is set
+ ///
+ public bool IsIndicateSet
+ {
+ get
+ {
+ return isIndicateSet;
+ }
+
+ set
+ {
+ if (isIndicateSet != value)
+ {
+ isIndicateSet = value;
+ OnPropertyChanged(new PropertyChangedEventArgs("IsIndicateSet"));
+ }
+ }
+ }
+
+ ///
+ /// Source for
+ ///
+ private bool isNotifySet = false;
+
+ ///
+ /// Gets or sets a value indicating whether notify is set
+ ///
+ public bool IsNotifySet
+ {
+ get
+ {
+ return isNotifySet;
+ }
+
+ set
+ {
+ if (isNotifySet != value)
+ {
+ isNotifySet = value;
+ OnPropertyChanged(new PropertyChangedEventArgs("IsNotifySet"));
+ }
+ }
+ }
+
+ ///
+ /// Source for
+ ///
+ private ObservableGattDeviceService parent;
+
+ ///
+ /// Gets or sets the parent service of this characteristic
+ ///
+ public ObservableGattDeviceService Parent
+ {
+ get
+ {
+ return parent;
+ }
+
+ set
+ {
+ if (parent != value)
+ {
+ parent = value;
+ OnPropertyChanged(new PropertyChangedEventArgs("Parent"));
+ }
+ }
+ }
+
+ ///
+ /// Source for
+ ///
+ private string name;
+
+ ///
+ /// Gets or sets the name of this characteristic
+ ///
+ public string Name
+ {
+ get
+ {
+ return name;
+ }
+
+ set
+ {
+ if (name != value)
+ {
+ name = value;
+ OnPropertyChanged(new PropertyChangedEventArgs("Name"));
+ }
+ }
+ }
+
+ ///
+ /// Source for
+ ///
+ private string uuid;
+
+ ///
+ /// Gets or sets the UUID of this characteristic
+ ///
+ public string UUID
+ {
+ get
+ {
+ return uuid;
+ }
+
+ set
+ {
+ if (uuid != value)
+ {
+ uuid = value;
+ OnPropertyChanged(new PropertyChangedEventArgs("UUID"));
+ }
+ }
+ }
+
+ ///
+ /// Source for
+ ///
+ private string value;
+
+ ///
+ /// Gets the value of this characteristic
+ ///
+ public string Value
+ {
+ get
+ {
+ return value;
+ }
+
+ private set
+ {
+ if (this.value != value)
+ {
+ this.value = value;
+ OnPropertyChanged(new PropertyChangedEventArgs("Value"));
+ }
+ }
+ }
+
+ ///
+ /// Source for
+ ///
+ private DisplayTypes displayType = DisplayTypes.NotSet;
+
+ ///
+ /// Gets or sets how this characteristic's value should be displayed
+ ///
+ public DisplayTypes DisplayType
+ {
+ get
+ {
+ return displayType;
+ }
+
+ set
+ {
+ if (value == DisplayTypes.NotSet)
+ {
+ return;
+ }
+
+ if (displayType != value)
+ {
+ displayType = value;
+ OnPropertyChanged(new PropertyChangedEventArgs("DisplayType"));
+ }
+ }
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Characteristic this class wraps
+ /// The parent service that wraps this characteristic
+ public ObservableGattCharacteristics(GattCharacteristic characteristic, ObservableGattDeviceService parent)
+ {
+ Characteristic = characteristic;
+ Parent = parent;
+ Name = GattUuidsService.ConvertUuidToName(Characteristic.Uuid);
+ UUID = Characteristic.Uuid.ToString();
+
+ ReadValueAsync();
+
+ characteristic.ValueChanged += Characteristic_ValueChanged;
+
+ PropertyChanged += ObservableGattCharacteristics_PropertyChanged;
+
+ return;
+ }
+
+ ///
+ /// Destruct this object by unsetting notification/indication and unregistering from property changed callbacks
+ ///
+ ~ObservableGattCharacteristics()
+ {
+ characteristic.ValueChanged -= Characteristic_ValueChanged;
+ PropertyChanged -= ObservableGattCharacteristics_PropertyChanged;
+
+ Cleanup();
+ }
+
+ ///
+ /// Cleanup this object by unsetting notification/indication
+ ///
+ private async void Cleanup()
+ {
+ await StopIndicate();
+ await StopNotify();
+ }
+
+ ///
+ /// Executes when this characteristic changes
+ ///
+ ///
+ ///
+ private void ObservableGattCharacteristics_PropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == "DisplayType")
+ {
+ SetValue();
+ }
+ }
+
+ ///
+ /// Reads the value of the Characteristic
+ ///
+ public async void ReadValueAsync()
+ {
+ try
+ {
+ GattReadResult result = await Characteristic.ReadValueAsync(BluetoothCacheMode.Uncached);
+
+ if (result.Status == GattCommunicationStatus.Success)
+ {
+ SetValue(result.Value);
+ }
+ else if (result.Status == GattCommunicationStatus.ProtocolError)
+ {
+ Value = Services.Other.GattProtocolErrorParser.GetErrorString(result.ProtocolError);
+ }
+ else
+ {
+ Value = "Unreachable";
+ }
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine("Exception: " + ex.Message);
+ Value = "Exception!";
+ }
+ }
+
+ ///
+ /// Set's the indicate descriptor
+ ///
+ /// Set indicate task
+ public async Task SetIndicate()
+ {
+ if (IsIndicateSet == true)
+ {
+ // already set
+ return true;
+ }
+
+ try
+ {
+ // BT_Code: Must write the CCCD in order for server to send indications.
+ // We receive them in the ValueChanged event handler.
+ // Note that this sample configures either Indicate or Notify, but not both.
+ var result = await
+ Characteristic.WriteClientCharacteristicConfigurationDescriptorAsync(
+ GattClientCharacteristicConfigurationDescriptorValue.Indicate);
+ if (result == GattCommunicationStatus.Success)
+ {
+ Debug.WriteLine("Successfully registered for indications");
+ IsIndicateSet = true;
+ return true;
+ }
+ else if (result == GattCommunicationStatus.ProtocolError)
+ {
+ Debug.WriteLine("Error registering for indications: Protocol Error");
+ IsIndicateSet = false;
+ return false;
+ }
+ else if (result == GattCommunicationStatus.Unreachable)
+ {
+ Debug.WriteLine("Error registering for indications: Unreachable");
+ IsIndicateSet = false;
+ return false;
+ }
+ }
+ catch (UnauthorizedAccessException ex)
+ {
+ // This usually happens when a device reports that it support indicate, but it actually doesn't.
+ Debug.WriteLine("Unauthorized Exception: " + ex.Message);
+ IsIndicateSet = false;
+ return false;
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine("Generic Exception: " + ex.Message);
+ IsIndicateSet = false;
+ return false;
+ }
+
+ IsIndicateSet = false;
+ return false;
+ }
+
+ ///
+ /// Unsets the indicate descriptor
+ ///
+ /// Unset indicate task
+ public async Task StopIndicate()
+ {
+ if (IsIndicateSet == false)
+ {
+ // indicate is not set, can skip this
+ return true;
+ }
+
+ try
+ {
+ // BT_Code: Must write the CCCD in order for server to send indications.
+ // We receive them in the ValueChanged event handler.
+ // Note that this sample configures either Indicate or Notify, but not both.
+ var result = await
+ Characteristic.WriteClientCharacteristicConfigurationDescriptorAsync(
+ GattClientCharacteristicConfigurationDescriptorValue.None);
+ if (result == GattCommunicationStatus.Success)
+ {
+ Debug.WriteLine("Successfully un-registered for indications");
+ IsIndicateSet = false;
+ return true;
+ }
+ else if (result == GattCommunicationStatus.ProtocolError)
+ {
+ Debug.WriteLine("Error un-registering for indications: Protocol Error");
+ IsIndicateSet = true;
+ return false;
+ }
+ else if (result == GattCommunicationStatus.Unreachable)
+ {
+ Debug.WriteLine("Error un-registering for indications: Unreachable");
+ IsIndicateSet = true;
+ return false;
+ }
+ }
+ catch (UnauthorizedAccessException ex)
+ {
+ // This usually happens when a device reports that it support indicate, but it actually doesn't.
+ Debug.WriteLine("Exception: " + ex.Message);
+ IsIndicateSet = true;
+ return false;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Sets the notify characteristic
+ ///
+ /// Set notify task
+ public async Task SetNotify()
+ {
+ if (IsNotifySet == true)
+ {
+ // already set
+ return true;
+ }
+
+ try
+ {
+ // BT_Code: Must write the CCCD in order for server to send indications.
+ // We receive them in the ValueChanged event handler.
+ // Note that this sample configures either Indicate or Notify, but not both.
+ var result = await
+ Characteristic.WriteClientCharacteristicConfigurationDescriptorAsync(
+ GattClientCharacteristicConfigurationDescriptorValue.Notify);
+ if (result == GattCommunicationStatus.Success)
+ {
+ Debug.WriteLine("Successfully registered for notifications");
+ IsNotifySet = true;
+ return true;
+ }
+ else if (result == GattCommunicationStatus.ProtocolError)
+ {
+ Debug.WriteLine("Error registering for notifications: Protocol Error");
+ IsNotifySet = false;
+ return false;
+ }
+ else if (result == GattCommunicationStatus.Unreachable)
+ {
+ Debug.WriteLine("Error registering for notifications: Unreachable");
+ IsNotifySet = false;
+ return false;
+ }
+ }
+ catch (UnauthorizedAccessException ex)
+ {
+ // This usually happens when a device reports that it support indicate, but it actually doesn't.
+ Debug.WriteLine("Unauthorized Exception: " + ex.Message);
+ IsNotifySet = false;
+ return false;
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine("Generic Exception: " + ex.Message);
+ IsNotifySet = false;
+ return false;
+ }
+
+ IsNotifySet = false;
+ return false;
+ }
+
+ ///
+ /// Unsets the notify descriptor
+ ///
+ /// Unset notify task
+ public async Task StopNotify()
+ {
+ if (IsNotifySet == false)
+ {
+ // indicate is not set, can skip this
+ return true;
+ }
+
+ try
+ {
+ // BT_Code: Must write the CCCD in order for server to send indications.
+ // We receive them in the ValueChanged event handler.
+ // Note that this sample configures either Indicate or Notify, but not both.
+ var result = await
+ Characteristic.WriteClientCharacteristicConfigurationDescriptorAsync(
+ GattClientCharacteristicConfigurationDescriptorValue.None);
+ if (result == GattCommunicationStatus.Success)
+ {
+ Debug.WriteLine("Successfully un-registered for notifications");
+ IsNotifySet = false;
+ return true;
+ }
+ else if (result == GattCommunicationStatus.ProtocolError)
+ {
+ Debug.WriteLine("Error un-registering for notifications: Protocol Error");
+ IsNotifySet = true;
+ return false;
+ }
+ else if (result == GattCommunicationStatus.Unreachable)
+ {
+ Debug.WriteLine("Error un-registering for notifications: Unreachable");
+ IsNotifySet = true;
+ return false;
+ }
+ }
+ catch (UnauthorizedAccessException ex)
+ {
+ // This usually happens when a device reports that it support indicate, but it actually doesn't.
+ Debug.WriteLine("Exception: " + ex.Message);
+ IsNotifySet = true;
+ return false;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Executes when value changes
+ ///
+ ///
+ ///
+ private async void Characteristic_ValueChanged(GattCharacteristic sender, GattValueChangedEventArgs args)
+ {
+ await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(
+ Windows.UI.Core.CoreDispatcherPriority.Normal,
+ () =>
+ {
+ SetValue(args.CharacteristicValue);
+ });
+ }
+
+ ///
+ /// helper function that copies the raw data into byte array
+ ///
+ /// The raw input buffer
+ private void SetValue(IBuffer buffer)
+ {
+ rawData = buffer;
+ CryptographicBuffer.CopyToByteArray(rawData, out data);
+
+ SetValue();
+ }
+
+ ///
+ /// Sets the value of this characteristic based on the display type
+ ///
+ private void SetValue()
+ {
+ if (data == null)
+ {
+ Value = "NULL";
+ return;
+ }
+
+ GattPresentationFormat format = null;
+
+ if (Characteristic.PresentationFormats.Count > 0)
+ {
+ format = Characteristic.PresentationFormats[0];
+ }
+
+ // Determine what to set our DisplayType to
+ if (format == null && DisplayType == DisplayTypes.NotSet)
+ {
+ if (Name == "DeviceName")
+ {
+ // All devices have DeviceName so this is a special case.
+ DisplayType = DisplayTypes.UTF8;
+ }
+ else
+ {
+ string buffer = string.Empty;
+ bool isString = true;
+
+ try
+ {
+ buffer = GattConvert.ToUTF8String(rawData);
+ }
+ catch(Exception)
+ {
+ isString = false;
+ }
+
+ if (isString == true)
+ {
+
+ // if buffer is only 1 char or 2 char with 0 at end then let's assume it's hex
+ if (buffer.Length == 1)
+ {
+ isString = false;
+ }
+ else if (buffer.Length == 2 && buffer[1] == 0)
+ {
+ isString = false;
+ }
+ else
+ {
+ foreach (char b in buffer)
+ {
+ // if within the reasonable range of used characters and not null, let's assume it's a UTF8 string by default, else hex
+ if ((b < ' ' || b > '~') && b != 0)
+ {
+ isString = false;
+ break;
+ }
+ }
+ }
+ }
+
+ if (isString)
+ {
+ DisplayType = DisplayTypes.UTF8;
+ }
+ else
+ {
+ // By default, display as Hex
+ DisplayType = DisplayTypes.Hex;
+ }
+ }
+ }
+ else if (format != null && DisplayType == DisplayTypes.NotSet)
+ {
+ if (format.FormatType == GattPresentationFormatTypes.Boolean ||
+ format.FormatType == GattPresentationFormatTypes.Bit2 ||
+ format.FormatType == GattPresentationFormatTypes.Nibble ||
+ format.FormatType == GattPresentationFormatTypes.UInt8 ||
+ format.FormatType == GattPresentationFormatTypes.UInt12 ||
+ format.FormatType == GattPresentationFormatTypes.UInt16 ||
+ format.FormatType == GattPresentationFormatTypes.UInt24 ||
+ format.FormatType == GattPresentationFormatTypes.UInt32 ||
+ format.FormatType == GattPresentationFormatTypes.UInt48 ||
+ format.FormatType == GattPresentationFormatTypes.UInt64 ||
+ format.FormatType == GattPresentationFormatTypes.SInt8 ||
+ format.FormatType == GattPresentationFormatTypes.SInt12 ||
+ format.FormatType == GattPresentationFormatTypes.SInt16 ||
+ format.FormatType == GattPresentationFormatTypes.SInt24 ||
+ format.FormatType == GattPresentationFormatTypes.SInt32)
+ {
+ DisplayType = DisplayTypes.Decimal;
+ }
+ else if (format.FormatType == GattPresentationFormatTypes.Utf8)
+ {
+ DisplayType = DisplayTypes.UTF8;
+ }
+ else if (format.FormatType == GattPresentationFormatTypes.Utf16)
+ {
+ DisplayType = DisplayTypes.UTF16;
+ }
+ else if (format.FormatType == GattPresentationFormatTypes.UInt128 ||
+ format.FormatType == GattPresentationFormatTypes.SInt128 ||
+ format.FormatType == GattPresentationFormatTypes.DUInt16 ||
+ format.FormatType == GattPresentationFormatTypes.SInt64 ||
+ format.FormatType == GattPresentationFormatTypes.Struct ||
+ format.FormatType == GattPresentationFormatTypes.Float ||
+ format.FormatType == GattPresentationFormatTypes.Float32 ||
+ format.FormatType == GattPresentationFormatTypes.Float64)
+ {
+ DisplayType = DisplayTypes.Unsupported;
+ }
+ else
+ {
+ DisplayType = DisplayTypes.Unsupported;
+ }
+ }
+
+ // Decode the value into the right display type
+ if (DisplayType == DisplayTypes.Hex || DisplayType == DisplayTypes.Unsupported)
+ {
+ Value = GattConvert.ToHexString(rawData);
+ }
+ else if (DisplayType == DisplayTypes.Decimal)
+ {
+ //TODO: if data is larger then int32 this will overflow. Need to fix.
+ Value = GattConvert.ToInt32(rawData).ToString();
+ }
+ else if (DisplayType == DisplayTypes.UTF8)
+ {
+ Value = GattConvert.ToUTF8String(rawData);
+ }
+ else if (DisplayType == DisplayTypes.UTF16)
+ {
+ Value = GattConvert.ToUTF16String(rawData);
+ }
+ }
+
+ ///
+ /// Event to notify when this object has changed
+ ///
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ ///
+ /// Executes when this class changes
+ ///
+ ///
+ private void OnPropertyChanged(PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == "DisplayType")
+ {
+ Debug.WriteLine($"{this.Name} - DisplayType set: {this.DisplayType.ToString()}");
+ }
+
+ if (PropertyChanged != null)
+ {
+ PropertyChanged(this, e);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Models/ObservableGattDeviceService.cs b/BluetoothLEExplorer/BluetoothLEExplorer/Models/ObservableGattDeviceService.cs
new file mode 100644
index 0000000..cece05b
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorer/Models/ObservableGattDeviceService.cs
@@ -0,0 +1,279 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------------------------
+using System;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using BluetoothLEExplorer.Services.GattUuidsService;
+using Windows.Devices.Bluetooth.GenericAttributeProfile;
+
+namespace BluetoothLEExplorer.Models
+{
+ ///
+ /// Wrapper around to make it easier to use
+ ///
+ public class ObservableGattDeviceService : INotifyPropertyChanged
+ {
+ ///
+ /// Source for
+ ///
+ private GattDeviceService service;
+
+ ///
+ /// Gets or sets the service this class wraps
+ ///
+ public GattDeviceService Service
+ {
+ get
+ {
+ return service;
+ }
+
+ set
+ {
+ if (service != value)
+ {
+ service = value;
+ OnPropertyChanged(new PropertyChangedEventArgs("Service"));
+ }
+ }
+ }
+
+ ///
+ /// Source for
+ ///
+ private ObservableCollection characteristics = new ObservableCollection();
+
+ ///
+ /// Gets or sets all the characteristics of this service
+ ///
+ public ObservableCollection Characteristics
+ {
+ get
+ {
+ return characteristics;
+ }
+
+ set
+ {
+ if (characteristics != value)
+ {
+ characteristics = value;
+ OnPropertyChanged(new PropertyChangedEventArgs("Characteristics"));
+ }
+ }
+ }
+
+ ///
+ /// Source for
+ ///
+ private ObservableGattCharacteristics selectedCharacteristic;
+
+ ///
+ /// Gets or sets the currently selected characteristic
+ ///
+ public ObservableGattCharacteristics SelectedCharacteristic
+ {
+ get
+ {
+ return selectedCharacteristic;
+ }
+
+ set
+ {
+ if (selectedCharacteristic != value)
+ {
+ selectedCharacteristic = value;
+ OnPropertyChanged(new PropertyChangedEventArgs("SelectedCharacteristic"));
+
+ // The SelectedProperty doesn't exist when this object is first created. This takes
+ // care of adding the correct event handler after the first time it's changed.
+ SelectedCharacteristic_PropertyChanged();
+ }
+ }
+ }
+
+ ///
+ /// Source for
+ ///
+ private string name;
+
+ ///
+ /// Gets or sets the name of this service
+ ///
+ public string Name
+ {
+ get
+ {
+ return name;
+ }
+
+ set
+ {
+ if (name != value)
+ {
+ name = value;
+ OnPropertyChanged(new PropertyChangedEventArgs("Name"));
+ }
+ }
+ }
+
+ ///
+ /// Source for
+ ///
+ private string uuid;
+
+ ///
+ /// Gets or sets the UUID of this service
+ ///
+ public string UUID
+ {
+ get
+ {
+ return uuid;
+ }
+
+ set
+ {
+ if (uuid != value)
+ {
+ uuid = value;
+ OnPropertyChanged(new PropertyChangedEventArgs("UUID"));
+ }
+ }
+ }
+
+ ///
+ /// Determines if the SelectedCharacteristic_PropertyChanged has been added
+ ///
+ private bool hasSelectedCharacteristicPropertyChangedHandler = false;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The service this class wraps
+ public ObservableGattDeviceService(GattDeviceService service)
+ {
+ Service = service;
+ Name = GattUuidsService.ConvertUuidToName(service.Uuid);
+ UUID = Service.Uuid.ToString();
+ GetAllCharacteristics();
+ }
+
+ ///
+ /// Destruct by clearing characteristic list
+ ///
+ ~ObservableGattDeviceService()
+ {
+ Characteristics.Clear();
+ }
+
+ ///
+ /// Adds the SelectedCharacteristic_PropertyChanged event handler
+ ///
+ private void SelectedCharacteristic_PropertyChanged()
+ {
+ if (hasSelectedCharacteristicPropertyChangedHandler == false)
+ {
+ SelectedCharacteristic.PropertyChanged += SelectedCharacteristic_PropertyChanged;
+ hasSelectedCharacteristicPropertyChangedHandler = true;
+ }
+ }
+
+ ///
+ /// Updates the selected characteristic in the app context
+ ///
+ ///
+ ///
+ private void SelectedCharacteristic_PropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ GattSampleContext.Context.SelectedCharacteristic = SelectedCharacteristic;
+ }
+
+ ///
+ /// Gets all the characteristics of this service
+ ///
+ private async void GetAllCharacteristics()
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.Append("ObservableGattDeviceService::getAllCharacteristics: ");
+ sb.Append(Name);
+
+ try
+ {
+ CancellationTokenSource tokenSource = new CancellationTokenSource(5000);
+ var t = Task.Run(() => Service.GetCharacteristicsAsync(Windows.Devices.Bluetooth.BluetoothCacheMode.Uncached), tokenSource.Token);
+
+ GattCharacteristicsResult result = null;
+ result = await t.Result;
+
+ if (result.Status == GattCommunicationStatus.Success)
+ {
+ sb.Append(" - getAllCharacteristics found ");
+ sb.Append(result.Characteristics.Count());
+ sb.Append(" characteristics");
+ Debug.WriteLine(sb);
+ foreach (GattCharacteristic gattchar in result.Characteristics)
+ {
+ Characteristics.Add(new ObservableGattCharacteristics(gattchar, this));
+ }
+ }
+ else if (result.Status == GattCommunicationStatus.Unreachable)
+ {
+ sb.Append(" - getAllCharacteristics failed with Unreachable");
+ Debug.WriteLine(sb.ToString());
+ }
+ else if (result.Status == GattCommunicationStatus.ProtocolError)
+ {
+ sb.Append(" - getAllCharacteristics failed with Unreachable");
+ Debug.WriteLine(sb.ToString());
+ }
+ }
+ catch (AggregateException ae)
+ {
+ foreach (var ex in ae.InnerExceptions)
+ {
+ if (ex is TaskCanceledException)
+ {
+ Debug.WriteLine("Getting characteristics took too long.");
+ Name += " - Timed out getting some characteristics";
+ return;
+ }
+ }
+ }
+ catch (UnauthorizedAccessException)
+ {
+ // Bug 9145823:GetCharacteristicsAsync throw System.UnauthorizedAccessException when querying GenericAccess Service Characteristics
+ Name += " - Unauthorized Access";
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine("getAllCharacteristics: Exception - {0}" + ex.Message);
+ throw;
+ }
+ }
+
+ ///
+ /// Event to notify when this object has changed
+ ///
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ ///
+ /// Property changed
+ ///
+ ///
+ private void OnPropertyChanged(PropertyChangedEventArgs e)
+ {
+ if (PropertyChanged != null)
+ {
+ PropertyChanged(this, e);
+ }
+ }
+ }
+}
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Package.appxmanifest b/BluetoothLEExplorer/BluetoothLEExplorer/Package.appxmanifest
new file mode 100644
index 0000000..e98405a
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorer/Package.appxmanifest
@@ -0,0 +1,33 @@
+
+
+
+
+
+ Bluetooth LE Explorer
+ Microsoft Corporation
+ Assets\StoreLogo.png
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Properties/AssemblyInfo.cs b/BluetoothLEExplorer/BluetoothLEExplorer/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..f9047ee
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorer/Properties/AssemblyInfo.cs
@@ -0,0 +1,33 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------------------------
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("BluetoothLEExplorer")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("Microsoft")]
+[assembly: AssemblyProduct("BluetoothLEExplorer")]
+[assembly: AssemblyCopyright("Copyright © 2015")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
+[assembly: ComVisible(false)]
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Properties/Default.rd.xml b/BluetoothLEExplorer/BluetoothLEExplorer/Properties/Default.rd.xml
new file mode 100644
index 0000000..7d8a349
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorer/Properties/Default.rd.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Services/Converters/Converters.cs b/BluetoothLEExplorer/BluetoothLEExplorer/Services/Converters/Converters.cs
new file mode 100644
index 0000000..be4b480
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorer/Services/Converters/Converters.cs
@@ -0,0 +1,152 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------------------------
+using System;
+using System.Diagnostics;
+using BluetoothLEExplorer.Models;
+using BluetoothLEExplorer.ViewModels;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Data;
+
+[module: System.Diagnostics.CodeAnalysis.SuppressMessage(
+ "StyleCop.CSharp.DocumentationRules",
+ "SA1649:FileHeaderFileNameDocumentationMustMatchTypeName",
+ Justification = "This is a helper file for Converters used in the xaml")]
+
+[module: System.Diagnostics.CodeAnalysis.SuppressMessage(
+ "Microsoft.StyleCop.CSharp.MaintainabilityRules",
+ "SA1402:FileMayOnlyContainASingleClass",
+ Justification = "This is a helper file for Converters used in the xaml")]
+
+namespace BluetoothLEExplorer.Services.Converters
+{
+ ///
+ /// Converter to change to a boolean.
+ /// This is used in to determine if a radio button is checked or not
+ ///
+ public class DisplayTypeToBooleanConverter : 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()}, ");
+ ObservableGattCharacteristics.DisplayTypes type = ObservableGattCharacteristics.DisplayTypes.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;
+ }
+ }
+ }
+
+ ///
+ /// Converts to boolean.
+ /// Used in radio buttons.
+ ///
+ public class WriteTypeToBooleanConverter : IValueConverter
+ {
+ ///
+ /// Converts WriteType to boolean used in radio buttons
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// Boolean if radio button should be checked
+ public object Convert(object value, Type targetType, object parameter, string culture)
+ {
+ return value.ToString().Equals(parameter);
+ }
+
+ ///
+ /// Radio button state of write type radio buttons to boolean
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// Write value of radio button
+ public object ConvertBack(object value, Type targetType, object parameter, string culture)
+ {
+ CharacteristicPageViewModel.WriteTypes type = CharacteristicPageViewModel.WriteTypes.NotSet;
+ if (value.Equals(true))
+ {
+ return Enum.Parse(type.GetType(), parameter as string);
+ }
+ else
+ {
+ return DependencyProperty.UnsetValue;
+ }
+ }
+ }
+
+ ///
+ /// Uses a boolean to determine if text should be crossed out
+ ///
+ public class ShellFontConverter : IValueConverter
+ {
+ ///
+ /// Converts boolean value to strikethrough font
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// Either a normal font or a strike through font
+ public object Convert(object value, Type targetType, object parameter, string language)
+ {
+ if ((bool)value == true)
+ {
+ return Windows.UI.Text.TextDecorations.None;
+ }
+ else
+ {
+ return Windows.UI.Text.TextDecorations.Strikethrough;
+ }
+
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Not implemented
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// Not implemented return value
+ public object ConvertBack(object value, Type targetType, object parameter, string language)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Services/DispatcherService/DispatcherService.cs b/BluetoothLEExplorer/BluetoothLEExplorer/Services/DispatcherService/DispatcherService.cs
new file mode 100644
index 0000000..c0a6222
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorer/Services/DispatcherService/DispatcherService.cs
@@ -0,0 +1,69 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------------------------
+using System;
+using System.Threading.Tasks;
+using Windows.UI.Core;
+
+namespace BluetoothLEExplorer.Services.DispatcherService
+{
+ ///
+ /// Helper class used to run functions on the UI thread
+ ///
+ public static class DispatcherService
+ {
+ ///
+ /// Helper function to run task on UI thread
+ ///
+ /// return value of task
+ ///
+ ///
+ ///
+ /// UI thread task
+ public static async Task RunTaskAsync(
+ this CoreDispatcher dispatcher,
+ Func> func,
+ CoreDispatcherPriority priority = CoreDispatcherPriority.Normal)
+ {
+ var taskCompletionSource = new TaskCompletionSource();
+
+ await dispatcher.RunAsync(
+ priority,
+ async () =>
+ {
+ try
+ {
+ taskCompletionSource.SetResult(await func());
+ }
+ catch (Exception ex)
+ {
+ taskCompletionSource.SetException(ex);
+ }
+ });
+ return await taskCompletionSource.Task;
+ }
+
+ //// There is no TaskCompletionSource so we use a bool that we throw away.
+
+ ///
+ /// Helper function to run task on UI thread
+ ///
+ ///
+ ///
+ ///
+ /// UI thread task
+ public static async Task RunTaskAsync(
+ this CoreDispatcher dispatcher,
+ Func func,
+ CoreDispatcherPriority priority = CoreDispatcherPriority.Normal) =>
+ await RunTaskAsync(
+ dispatcher,
+ async () =>
+ {
+ await func();
+ return false;
+ },
+ priority);
+ }
+}
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Services/GattUuidsService/GattUuidsService.cs b/BluetoothLEExplorer/BluetoothLEExplorer/Services/GattUuidsService/GattUuidsService.cs
new file mode 100644
index 0000000..048ed58
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorer/Services/GattUuidsService/GattUuidsService.cs
@@ -0,0 +1,221 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------------------------
+using System;
+
+namespace BluetoothLEExplorer.Services.GattUuidsService
+{
+ ///
+ /// Helper class used when working with UUIDs
+ ///
+ public static class GattUuidsService
+ {
+ ///
+ /// Helper function to convert a UUID to a name
+ ///
+ ///
+ /// Name of the UUID
+ public static string ConvertUuidToName(Guid uuid)
+ {
+ GattNativeUuid name;
+ if (Enum.TryParse(ConvertUuidToShortId(uuid).ToString(), out name) == true)
+ {
+ return name.ToString();
+ }
+ else
+ {
+ return uuid.ToString();
+ }
+ }
+
+ ///
+ /// Converts from standard 128bit UUID to the assigned 32bit UUIDs. Makes it easy to compare services
+ /// that devices expose to the standard list.
+ ///
+ /// UUID to convert to 32 bit
+ /// 32bit version of the input UUID
+ public static ushort ConvertUuidToShortId(Guid uuid)
+ {
+ // Get the short Uuid
+ var bytes = uuid.ToByteArray();
+ var shortUuid = (ushort)(bytes[0] | (bytes[1] << 8));
+ return shortUuid;
+ }
+ }
+
+ ///
+ /// This enum assists in finding a string representation of a BT SIG assigned value for UUIDS
+ /// Reference: https://developer.bluetooth.org/gatt/services/Pages/ServicesHome.aspx
+ /// Reference: https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicsHome.aspx
+ /// Reference: https://developer.bluetooth.org/gatt/descriptors/Pages/DescriptorsHomePage.aspx
+ ///
+ public enum GattNativeUuid : ushort
+ {
+ ///
+ /// This enum assists in finding a string representation of a BT SIG assigned value for Service UUIDS
+ /// Reference: https://developer.bluetooth.org/gatt/services/Pages/ServicesHome.aspx
+ ///
+ None = 0,
+ AlertNotificationService = 0x1811,
+ AutomationIO = 0x1815,
+ BatteryService = 0x180F,
+ BloodPressure = 0x1810,
+ BodyComposition = 0x181B,
+ BondManagement = 0x181E,
+ ContinuousGlucoseMonitoring = 0x181F,
+ CurrentTimeService = 0x1805,
+ CyclingPower = 0x1818,
+ CyclingSpeedandCadence = 0x1816,
+ DeviceInformation = 0x180A,
+ EnvironmentalSensing = 0x181A,
+ GenericAccess = 0x1800,
+ GenericAttribute = 0x1801,
+ Glucose = 0x1808,
+ HealthThermometer = 0x1809,
+ HeartRate = 0x180D,
+ HTTPProxy = 0x1823,
+ HumanInterfaceDevice = 0x1812,
+ ImmediateAlert = 0x1802,
+ IndoorPositioning = 0x1821,
+ InternetProtocolSupport = 0x1820,
+ LinkLoss = 0x1803,
+ LocationandNavigation = 0x1819,
+ NextDSTChangeService = 0x1807,
+ ObjectTransfer = 0x1825,
+ PhoneAlertStatusService = 0x180E,
+ PulseOximeter = 0x1822,
+ ReferenceTimeUpdateService = 0x1806,
+ RunningSpeedandCadence = 0x1814,
+ ScanParameters = 0x1813,
+ TransportDiscovery = 0x1824,
+ TxPower = 0x1804,
+ UserData = 0x181C,
+ WeightScale = 0x181D,
+
+
+
+
+
+
+ ///
+ /// This enum is nice for finding a string representation of a BT SIG assigned value for Characteristic UUIDs
+ /// Reference: https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicsHome.aspx
+ ///
+ AlertCategoryID = 0x2A43,
+ AlertCategoryIDBitMask = 0x2A42,
+ AlertLevel = 0x2A06,
+ AlertNotificationControlPoint = 0x2A44,
+ AlertStatus = 0x2A3F,
+ Appearance = 0x2A01,
+ BatteryLevel = 0x2A19,
+ BloodPressureFeature = 0x2A49,
+ BloodPressureMeasurement = 0x2A35,
+ BodySensorLocation = 0x2A38,
+ BootKeyboardInputReport = 0x2A22,
+ BootKeyboardOutputReport = 0x2A32,
+ BootMouseInputReport = 0x2A33,
+ CSCFeature = 0x2A5C,
+ CSCMeasurement = 0x2A5B,
+ CurrentTime = 0x2A2B,
+ DateTime = 0x2A08,
+ DayDateTime = 0x2A0A,
+ DayofWeek = 0x2A09,
+ DeviceName = 0x2A00,
+ DSTOffset = 0x2A0D,
+ ExactTime256 = 0x2A0C,
+ FirmwareRevisionString = 0x2A26,
+ GlucoseFeature = 0x2A51,
+ GlucoseMeasurement = 0x2A18,
+ GlucoseMeasurementContext = 0x2A34,
+ HardwareRevisionString = 0x2A27,
+ HeartRateControlPoint = 0x2A39,
+ HeartRateMeasurement = 0x2A37,
+ HIDControlPoint = 0x2A4C,
+ HIDInformation = 0x2A4A,
+ IEEE11073_20601RegulatoryCertificationDataList = 0x2A2A,
+ IntermediateCuffPressure = 0x2A36,
+ IntermediateTemperature = 0x2A1E,
+ LocalTimeInformation = 0x2A0F,
+ ManufacturerNameString = 0x2A29,
+ MeasurementInterval = 0x2A21,
+ ModelNumberString = 0x2A24,
+ NewAlert = 0x2A46,
+ PeripheralPreferredConnectionParameters = 0x2A04,
+ PeripheralPrivacyFlag = 0x2A02,
+ PnPID = 0x2A50,
+ ProtocolMode = 0x2A4E,
+ ReconnectionAddress = 0x2A03,
+ RecordAccessControlPoint = 0x2A52,
+ ReferenceTimeInformation = 0x2A14,
+ Report = 0x2A4D,
+ ReportMap = 0x2A4B,
+ RingerControlPoint = 0x2A40,
+ RingerSetting = 0x2A41,
+ RSCFeature = 0x2A54,
+ RSCMeasurement = 0x2A53,
+ SCControlPoint = 0x2A55,
+ ScanIntervalWindow = 0x2A4F,
+ ScanRefresh = 0x2A31,
+ SensorLocation = 0x2A5D,
+ SerialNumberString = 0x2A25,
+ ServiceChanged = 0x2A05,
+ SoftwareRevisionString = 0x2A28,
+ SupportedNewAlertCategory = 0x2A47,
+ SupportedUnreadAlertCategory = 0x2A48,
+ SystemID = 0x2A23,
+ TemperatureMeasurement = 0x2A1C,
+ TemperatureType = 0x2A1D,
+ TimeAccuracy = 0x2A12,
+ TimeSource = 0x2A13,
+ TimeUpdateControlPoint = 0x2A16,
+ TimeUpdateState = 0x2A17,
+ TimewithDST = 0x2A11,
+ TimeZone = 0x2A0E,
+ TxPowerLevel = 0x2A07,
+ UnreadAlertStatus = 0x2A45,
+ AggregateInput = 0x2A5A,
+ AnalogInput = 0x2A58,
+ AnalogOutput = 0x2A59,
+ CyclingPowerControlPoint = 0x2A66,
+ CyclingPowerFeature = 0x2A65,
+ CyclingPowerMeasurement = 0x2A63,
+ CyclingPowerVector = 0x2A64,
+ DigitalInput = 0x2A56,
+ DigitalOutput = 0x2A57,
+ ExactTime100 = 0x2A0B,
+ LNControlPoint = 0x2A6B,
+ LNFeature = 0x2A6A,
+ LocationandSpeed = 0x2A67,
+ Navigation = 0x2A68,
+ NetworkAvailability = 0x2A3E,
+ PositionQuality = 0x2A69,
+ ScientificTemperatureinCelsius = 0x2A3C,
+ SecondaryTimeZone = 0x2A10,
+ String = 0x2A3D,
+ TemperatureinCelsius = 0x2A1F,
+ TemperatureinFahrenheit = 0x2A20,
+ TimeBroadcast = 0x2A15,
+ BatteryLevelState = 0x2A1B,
+ BatteryPowerState = 0x2A1A,
+ PulseOximetryContinuousMeasurement = 0x2A5F,
+ PulseOximetryControlPoint = 0x2A62,
+ PulseOximetryFeatures = 0x2A61,
+ PulseOximetryPulsatileEvent = 0x2A60,
+ SimpleKeyState = 0xFFE1,
+
+ ///
+ /// This enum assists in finding a string representation of a BT SIG assigned value for Descriptor UUIDs
+ /// Reference: https://developer.bluetooth.org/gatt/descriptors/Pages/DescriptorsHomePage.aspx
+ ///
+ CharacteristicExtendedProperties = 0x2900,
+ CharacteristicUserDescription = 0x2901,
+ ClientCharacteristicConfiguration = 0x2902,
+ ServerCharacteristicConfiguration = 0x2903,
+ CharacteristicPresentationFormat = 0x2904,
+ CharacteristicAggregateFormat = 0x2905,
+ ValidRange = 0x2906,
+ ExternalReportReference = 0x2907,
+ ReportReference = 0x2908
+ }
+}
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Services/Other/BytePadder.cs b/BluetoothLEExplorer/BluetoothLEExplorer/Services/Other/BytePadder.cs
new file mode 100644
index 0000000..494d9d7
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorer/Services/Other/BytePadder.cs
@@ -0,0 +1,36 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------------------------
+
+namespace BluetoothLEExplorer.Services.Other
+{
+ ///
+ /// Helper class used to pad bytes
+ ///
+ public static class BytePadder
+ {
+ ///
+ /// Takes an input array of bytes and returns an array with more zeros in the front
+ ///
+ ///
+ ///
+ /// A byte array with more zeros in front"/>
+ public static byte[] GetBytes(byte[] input, int length)
+ {
+ byte[] ret = new byte[length];
+
+ if (input.Length >= length)
+ {
+ return input;
+ }
+
+ for (int i = 0; i < input.Length; i++)
+ {
+ ret[i] = input[i];
+ }
+
+ return ret;
+ }
+ }
+}
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Services/Other/GattProtocolErrorParser.cs b/BluetoothLEExplorer/BluetoothLEExplorer/Services/Other/GattProtocolErrorParser.cs
new file mode 100644
index 0000000..a993953
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorer/Services/Other/GattProtocolErrorParser.cs
@@ -0,0 +1,100 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------------------------
+using Windows.Devices.Bluetooth.GenericAttributeProfile;
+
+namespace BluetoothLEExplorer.Services.Other
+{
+ ///
+ /// Helper function when working with
+ ///
+ public static class GattProtocolErrorParser
+ {
+ ///
+ /// Helper to convert an gatt error value into a string
+ ///
+ ///
+ /// String representation of the error
+ public static string GetErrorString(byte? errorValue)
+ {
+ string ret = "Protocol Error";
+
+ if (errorValue.HasValue == false)
+ {
+ return ret;
+ }
+
+ if (errorValue == GattProtocolError.AttributeNotFound)
+ {
+ return "Attribute Not Found";
+ }
+ else if (errorValue == GattProtocolError.AttributeNotLong)
+ {
+ return "Attribute Not Long";
+ }
+ else if (errorValue == GattProtocolError.InsufficientAuthentication)
+ {
+ return "Insufficient Authentication";
+ }
+ else if (errorValue == GattProtocolError.InsufficientAuthorization)
+ {
+ return "Insufficient Authorization";
+ }
+ else if (errorValue == GattProtocolError.InsufficientEncryption)
+ {
+ return "Insufficient Encryption";
+ }
+ else if (errorValue == GattProtocolError.InsufficientEncryptionKeySize)
+ {
+ return "Insufficient Encryption Key Size";
+ }
+ else if (errorValue == GattProtocolError.InsufficientResources)
+ {
+ return "Insufficient Resources";
+ }
+ else if (errorValue == GattProtocolError.InvalidAttributeValueLength)
+ {
+ return "Invalid Attribute Value Length";
+ }
+ else if (errorValue == GattProtocolError.InvalidHandle)
+ {
+ return "Invalid Handle";
+ }
+ else if (errorValue == GattProtocolError.InvalidOffset)
+ {
+ return "Invalid Offset";
+ }
+ else if (errorValue == GattProtocolError.InvalidPdu)
+ {
+ return "Invalid Pdu";
+ }
+ else if (errorValue == GattProtocolError.PrepareQueueFull)
+ {
+ return "Prepare Queue Full";
+ }
+ else if (errorValue == GattProtocolError.ReadNotPermitted)
+ {
+ return "Read Not Permitted";
+ }
+ else if (errorValue == GattProtocolError.RequestNotSupported)
+ {
+ return "Request Not Supported";
+ }
+ else if (errorValue == GattProtocolError.UnlikelyError)
+ {
+ return "UnlikelyError";
+ }
+ else if (errorValue == GattProtocolError.UnsupportedGroupType)
+ {
+ return "Unsupported Group Type";
+ }
+ else if (errorValue == GattProtocolError.WriteNotPermitted)
+ {
+ return "Write Not Permitted";
+ }
+
+ return ret;
+ }
+ }
+}
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Services/SettingsServices/SettingsService.cs b/BluetoothLEExplorer/BluetoothLEExplorer/Services/SettingsServices/SettingsService.cs
new file mode 100644
index 0000000..f20a6bb
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorer/Services/SettingsServices/SettingsService.cs
@@ -0,0 +1,96 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------------------------
+using System;
+using Template10.Common;
+using Template10.Utils;
+using Windows.UI.Xaml;
+
+namespace BluetoothLEExplorer.Services.SettingsServices
+{
+ ///
+ /// Settings helper service
+ ///
+ public class SettingsService
+ {
+ ///
+ /// Gets the settings service
+ ///
+ public static SettingsService Instance { get; } = new SettingsService();
+
+ ///
+ /// The settings helper
+ ///
+ private Template10.Services.SettingsService.ISettingsHelper helper;
+
+ ///
+ /// Prevents a default instance of the class from being created.
+ ///
+ private SettingsService()
+ {
+ helper = new Template10.Services.SettingsService.SettingsHelper();
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the shell screen should have a back button
+ ///
+ public bool UseShellBackButton
+ {
+ get
+ {
+ return helper.Read(nameof(UseShellBackButton), true);
+ }
+
+ set
+ {
+ helper.Write(nameof(UseShellBackButton), value);
+ BootStrapper.Current.NavigationService.Dispatcher.Dispatch(() =>
+ {
+ BootStrapper.Current.ShowShellBackButton = value;
+ BootStrapper.Current.UpdateShellBackButton();
+ BootStrapper.Current.NavigationService.Refresh();
+ });
+ }
+ }
+
+ ///
+ /// Gets or sets the application theme
+ ///
+ public ApplicationTheme AppTheme
+ {
+ get
+ {
+ var theme = ApplicationTheme.Light;
+ var value = helper.Read(nameof(AppTheme), theme.ToString());
+ return Enum.TryParse(value, out theme) ? theme : ApplicationTheme.Dark;
+ }
+
+ set
+ {
+ helper.Write(nameof(AppTheme), value.ToString());
+ (Window.Current.Content as FrameworkElement).RequestedTheme = value.ToElementTheme();
+ Views.Shell.HamburgerMenu.RefreshStyles(value);
+ }
+ }
+
+ ///
+ /// Gets or sets a value that I don't know what it does
+ ///
+ public TimeSpan CacheMaxDuration
+ {
+ get
+ {
+ return helper.Read(nameof(CacheMaxDuration), TimeSpan.FromDays(2));
+ }
+
+ set
+ {
+ helper.Write(nameof(CacheMaxDuration), value);
+ BootStrapper.Current.CacheMaxDuration = value;
+ }
+ }
+
+ public ObservableDictionary SettingsDictionary = new ObservableDictionary();
+ }
+}
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Services/ToastService/ToastService.cs b/BluetoothLEExplorer/BluetoothLEExplorer/Services/ToastService/ToastService.cs
new file mode 100644
index 0000000..b2f2d4f
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorer/Services/ToastService/ToastService.cs
@@ -0,0 +1,125 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------------------------
+using System.Text;
+using Windows.Data.Xml.Dom;
+using Windows.Foundation.Collections;
+using Windows.Foundation.Metadata;
+using Windows.UI.Notifications;
+
+namespace BluetoothLEExplorer.Services.ToastService
+{
+ ///
+ /// Service to help displaying toast notifications
+ ///
+ public static class ToastService
+ {
+ ///
+ /// Pop up a toast
+ ///
+ ///
+ ///
+ /// A toast notification
+ public static ToastNotification PopToast(string title, string content)
+ {
+ return PopToast(title, content, null, null);
+ }
+
+ ///
+ /// Pop up a toast
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// A toast notification
+ public static ToastNotification PopToast(string title, string content, string tag, string group)
+ {
+ string xml = $@"
+
+
+
+
+ ";
+
+ XmlDocument doc = new XmlDocument();
+
+ doc.LoadXml(xml);
+
+ var binding = doc.SelectSingleNode("//binding");
+
+ var el = doc.CreateElement("text");
+ el.InnerText = title;
+
+ binding.AppendChild(el);
+
+ el = doc.CreateElement("text");
+
+ try
+ {
+ el.InnerText = content;
+ }
+ catch (System.Runtime.InteropServices.COMException)
+ {
+ el.InnerText = "Undisplayable UTF8 character";
+ }
+
+ binding.AppendChild(el);
+
+ return PopCustomToast(doc, tag, group);
+ }
+
+ ///
+ /// Pop up a task
+ ///
+ ///
+ /// A toast notification
+ public static ToastNotification PopCustomToast(string xml)
+ {
+ return PopCustomToast(xml, null, null);
+ }
+
+ ///
+ /// Pop up a custom toast
+ ///
+ ///
+ ///
+ ///
+ /// A toast notification
+ public static ToastNotification PopCustomToast(string xml, string tag, string group)
+ {
+ XmlDocument doc = new XmlDocument();
+ doc.LoadXml(xml);
+
+ return PopCustomToast(doc, tag, group);
+ }
+
+ ///
+ /// pop up a custom toast
+ ///
+ ///
+ ///
+ ///
+ /// A toast notification
+ [DefaultOverloadAttribute]
+ public static ToastNotification PopCustomToast(XmlDocument doc, string tag, string group)
+ {
+ var toast = new ToastNotification(doc);
+
+ if (tag != null)
+ {
+ toast.Tag = tag;
+ }
+
+ if (group != null)
+ {
+ toast.Group = group;
+ }
+
+ ToastNotificationManager.CreateToastNotifier().Show(toast);
+
+ return toast;
+ }
+ }
+}
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Settings.StyleCop b/BluetoothLEExplorer/BluetoothLEExplorer/Settings.StyleCop
new file mode 100644
index 0000000..658d284
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorer/Settings.StyleCop
@@ -0,0 +1,19 @@
+
+
+
+ aspx
+
+
+
+
+
+
+
+ False
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Styles/Custom.xaml b/BluetoothLEExplorer/BluetoothLEExplorer/Styles/Custom.xaml
new file mode 100644
index 0000000..c24aaae
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorer/Styles/Custom.xaml
@@ -0,0 +1,123 @@
+
+
+ 0
+ 521
+ 1200
+
+ 820
+
+ #05A6F0
+ Black
+ #05A6F0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/BeaconViewModel.cs b/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/BeaconViewModel.cs
new file mode 100644
index 0000000..0a2c713
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/BeaconViewModel.cs
@@ -0,0 +1,89 @@
+//
+// 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
new file mode 100644
index 0000000..c94be65
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/CharacteristicPageViewModel.cs
@@ -0,0 +1,714 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------------------------
+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
+{
+ ///
+ /// View model for Characteristics View
+ ///
+ public class CharacteristicPageViewModel : ViewModelBase
+ {
+ ///
+ /// Enum to determine how to write the to the server
+ ///
+ public enum WriteTypes
+ {
+ NotSet,
+ Decimal,
+ Hex,
+ UTF8
+ }
+
+ ///
+ /// App context
+ ///
+ private GattSampleContext context = GattSampleContext.Context;
+
+ ///
+ /// Gets or sets strings to show user
+ ///
+ public ObservableCollection NotifyUser { get; set; } = new ObservableCollection();
+
+ ///
+ /// True if currently settings the error message
+ ///
+ private bool settingErrorMessage = false;
+
+ ///
+ /// Source for
+ ///
+ private ObservableGattCharacteristics characteristic = GattSampleContext.Context.SelectedCharacteristic;
+
+ ///
+ /// Gets or sets the characteristic that this view model wraps
+ ///
+ public ObservableGattCharacteristics Characteristic
+ {
+ get
+ {
+ return characteristic;
+ }
+
+ set
+ {
+ Set(ref characteristic, value, "Characteristic");
+ }
+ }
+
+ ///
+ /// Source for
+ ///
+ private string properties = "None";
+
+ ///
+ /// Gets the string showing what properties are supported
+ ///
+ public string Properties
+ {
+ get
+ {
+ return properties;
+ }
+
+ private set
+ {
+ Set(ref properties, value);
+ }
+ }
+
+ ///
+ /// Source for
+ ///
+ private bool notify = false;
+
+ ///
+ /// Gets or sets a value indicating whether notify is supported by this characteristic
+ ///
+ public bool Notify
+ {
+ get
+ {
+ return notify;
+ }
+
+ set
+ {
+ if (notify == value)
+ {
+ return;
+ }
+
+ // The heavy lifting for writting the CCCD is done in the PropertyChanged method
+ // in this class that gets called when this property is actually changed.
+
+ Set(ref notify, value);
+ }
+ }
+
+ ///
+ /// Source for
+ ///
+ private bool notifyProgress = false;
+
+ ///
+ /// Gets or sets a value indicating whether the progress ring should be displayed
+ /// while the notify descriptor is written
+ ///
+ public bool NotifyProgress
+ {
+ get
+ {
+ return notifyProgress;
+ }
+
+ set
+ {
+ if (notifyProgress == value)
+ {
+ return;
+ }
+
+ Set(ref notifyProgress, value);
+ }
+ }
+
+ ///
+ /// Source for
+ ///
+ private bool notifyError = false;
+
+ ///
+ /// Gets a value indicating whether there was an error setting the notify descriptor
+ ///
+ public bool NotifyError
+ {
+ get
+ {
+ return notifyError;
+ }
+
+ private set
+ {
+ if (notifyError == value)
+ {
+ return;
+ }
+
+ Set(ref notifyError, value);
+ }
+ }
+
+ ///
+ /// Source for
+ ///
+ private bool indicate = false;
+
+ ///
+ /// Gets or sets a value indicating whether indicate is supported by this characteristic
+ ///
+ public bool Indicate
+ {
+ get
+ {
+ return indicate;
+ }
+
+ set
+ {
+ if (indicate == value)
+ {
+ return;
+ }
+
+ // The heavy lifting for writting the CCCD is done in the PropertyChanged method
+ // in this class that gets called when this property is actually changed.
+
+ Set(ref indicate, value);
+ }
+ }
+
+ ///
+ /// Source for
+ ///
+ private bool indicateProgress = false;
+
+ ///
+ /// Gets or sets a value indicating whether the progress ring should be displayed
+ /// while the indicate descriptor is written
+ ///
+ public bool IndicateProgress
+ {
+ get
+ {
+ return indicateProgress;
+ }
+
+ set
+ {
+ if (indicateProgress == value)
+ {
+ return;
+ }
+
+ Set(ref indicateProgress, value);
+ }
+ }
+
+ ///
+ /// Source for
+ ///
+ private bool indicateError = false;
+
+ ///
+ /// Gets a value indicating whether there was an error setting the indicate descriptor
+ ///
+ public bool IndicateError
+ {
+ get
+ {
+ return indicateError;
+ }
+
+ private set
+ {
+ if (indicateError == value)
+ {
+ return;
+ }
+
+ Set(ref indicateError, value);
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether this characteristic can be read
+ ///
+ public bool CharacteristicCanBeRead
+ {
+ get
+ {
+ return Characteristic.Characteristic.CharacteristicProperties.HasFlag(GattCharacteristicProperties.Read);
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether this characteristic can be written to
+ ///
+ public bool CharacteristicCanWrite
+ {
+ get
+ {
+ return ( Characteristic.Characteristic.CharacteristicProperties.HasFlag(GattCharacteristicProperties.Write) ||
+ Characteristic.Characteristic.CharacteristicProperties.HasFlag(GattCharacteristicProperties.WriteWithoutResponse));
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether this characteristic can notify
+ ///
+ public bool CharacteristicCanNotify
+ {
+ get
+ {
+ return Characteristic.Characteristic.CharacteristicProperties.HasFlag(GattCharacteristicProperties.Notify);
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether this characteristic can indicate
+ ///
+ public bool CharacteristicCanIndicate
+ {
+ get
+ {
+ return Characteristic.Characteristic.CharacteristicProperties.HasFlag(GattCharacteristicProperties.Indicate);
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether this characteristic can notify or indicate
+ ///
+ public bool CharacteristicCanNotifyOrIndicate
+ {
+ get
+ {
+ return CharacteristicCanNotify | CharacteristicCanIndicate;
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether the value text box should be shown
+ ///
+ public bool CharacteristicValueVisible
+ {
+ get
+ {
+ return CharacteristicCanNotify | CharacteristicCanIndicate | CharacteristicCanBeRead;
+ }
+ }
+
+ ///
+ /// Source for
+ ///
+ private string valueToWrite = String.Empty;
+
+ ///
+ /// Gets or sets the value to write to server
+ ///
+ public string ValueToWrite
+ {
+ get
+ {
+ return valueToWrite;
+ }
+
+ set
+ {
+ Set(ref valueToWrite, value);
+ }
+ }
+
+ ///
+ /// Source for
+ ///
+ private WriteTypes writeType = WriteTypes.Hex;
+
+ ///
+ /// Gets or sets how the value should be written to server
+ ///
+ public WriteTypes WriteType
+ {
+ get
+ {
+ return writeType;
+ }
+
+ set
+ {
+ Set(ref writeType, value);
+ }
+ }
+
+ ///
+ /// Source for
+ ///
+ private bool useWindowsNotifications = false;
+
+ ///
+ /// Gets or sets a value indicating whether windows notifications should be used
+ ///
+ public bool UseWindowsNotifications
+ {
+ get
+ {
+ return useWindowsNotifications;
+ }
+
+ set
+ {
+ Set(ref useWindowsNotifications, value);
+ }
+ }
+
+ ///
+ /// Gets the name of the device
+ ///
+ public string DeviceName
+ {
+ get
+ {
+ return context.SelectedBluetoothLEDevice.Name;
+ }
+ }
+
+ ///
+ /// Source for
+ ///
+ private Windows.UI.Xaml.Visibility displayPresentError = Windows.UI.Xaml.Visibility.Visible;
+
+ ///
+ /// Gets value to show or hide error text depending on
+ ///
+ public Windows.UI.Xaml.Visibility DisplayPresentError
+ {
+ get
+ {
+ return displayPresentError;
+ }
+
+ private set
+ {
+ Set(ref displayPresentError, value);
+ }
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public CharacteristicPageViewModel()
+ {
+ if (Windows.ApplicationModel.DesignMode.DesignModeEnabled)
+ {
+ }
+
+ Notify = Characteristic.IsNotifySet;
+ Indicate = Characteristic.IsIndicateSet;
+
+ this.PropertyChanged += CharacteristicPageViewModel_PropertyChanged;
+ Characteristic.PropertyChanged += Characteristic_PropertyChanged;
+
+ if (Characteristic.DisplayType == ObservableGattCharacteristics.DisplayTypes.Unsupported)
+ {
+ DisplayPresentError = Windows.UI.Xaml.Visibility.Visible;
+ }
+ }
+
+ ///
+ /// Characteristic changed
+ ///
+ ///
+ ///
+ private void Characteristic_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == "Value")
+ {
+ if (UseWindowsNotifications == true && (Notify == true || Indicate == true))
+ {
+ BluetoothLEExplorer.Services.ToastService.ToastService.PopToast(Characteristic.Name, Characteristic.Value, "Notification", "Notification");
+ }
+ }
+
+ if (e.PropertyName == "DisplayType")
+ {
+ if (Characteristic.DisplayType == ObservableGattCharacteristics.DisplayTypes.Unsupported)
+ {
+ DisplayPresentError = Windows.UI.Xaml.Visibility.Visible;
+ }
+ }
+ }
+
+ ///
+ /// Characteristic view model changed
+ ///
+ ///
+ ///
+ private async void CharacteristicPageViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == "Notify")
+ {
+ if (Notify == true)
+ {
+ NotifyProgress = true;
+ bool success = await Characteristic.SetNotify();
+ NotifyProgress = false;
+ if (success == true)
+ {
+ if (settingErrorMessage == true)
+ {
+ settingErrorMessage = false;
+ }
+ else
+ {
+ NotifyError = false;
+ }
+ }
+ else
+ {
+ settingErrorMessage = true;
+ Notify = false;
+ NotifyError = true;
+ }
+ }
+ else
+ {
+ NotifyProgress = true;
+ bool success = await Characteristic.StopNotify();
+ NotifyProgress = false;
+ if (success == true)
+ {
+ if (settingErrorMessage == true)
+ {
+ settingErrorMessage = false;
+ }
+ else
+ {
+ NotifyError = false;
+ }
+ }
+ else
+ {
+ settingErrorMessage = true;
+ NotifyError = true;
+ Notify = true;
+ }
+ }
+ }
+
+ if (e.PropertyName == "Indicate")
+ {
+ if (Indicate == true)
+ {
+ IndicateProgress = true;
+ bool success = await Characteristic.SetIndicate();
+ IndicateProgress = false;
+ if (success == true)
+ {
+ IndicateError = false;
+ }
+ else
+ {
+ Indicate = false;
+ IndicateError = true;
+ }
+ }
+ else
+ {
+ IndicateProgress = true;
+ bool success = await Characteristic.StopIndicate();
+ IndicateProgress = false;
+
+ if (success == true)
+ {
+ IndicateError = false;
+ }
+ else
+ {
+ IndicateError = true;
+ Indicate = true;
+ }
+ }
+ }
+ }
+
+ ///
+ /// Write the value to the server
+ ///
+ public async void WriteValue()
+ {
+ 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);
+ }
+
+ try
+ {
+ // BT_Code: Writes the value from the buffer to the characteristic.
+ GattCommunicationStatus result = await Characteristic.Characteristic.WriteValueAsync(writeBuffer);
+
+ if (result == GattCommunicationStatus.Unreachable)
+ {
+ NotifyUser.Insert(0, "Unable to write data - Device unreachable");
+ }
+ else if (result == GattCommunicationStatus.ProtocolError)
+ {
+ NotifyUser.Insert(0, "Unable to write data - Protocol error");
+ }
+ }
+ catch (Exception ex) when ((uint)ex.HResult == 0x80650003 || (uint)ex.HResult == 0x80070005)
+ {
+ // E_BLUETOOTH_ATT_WRITE_NOT_PERMITTED or E_ACCESSDENIED
+ // This usually happens when a device reports that it support writing, but it actually doesn't.
+ NotifyUser.Insert(0, "Error writing to characteristic. This usually happens when a device reports that it support writing, but it actually doesn't.");
+ }
+ catch(Exception ex)
+ {
+ MessageDialog dialog = new MessageDialog(ex.Message, "Error");
+ await dialog.ShowAsync();
+ }
+ }
+ else
+ {
+ NotifyUser.Insert(0, "No data to write to device");
+ }
+ }
+
+ ///
+ /// Navigate to page
+ ///
+ ///
+ ///
+ ///
+ /// Navigate to task
+ public override async Task OnNavigatedToAsync(object parameter, NavigationMode mode, IDictionary suspensionState)
+ {
+ await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(
+ Windows.UI.Core.CoreDispatcherPriority.Normal,
+ () =>
+ {
+
+ if(Services.SettingsServices.SettingsService.Instance.SettingsDictionary.Keys.Contains(Characteristic.UUID + "-UseWindowsNotification"))
+ {
+ UseWindowsNotifications = (bool)Services.SettingsServices.SettingsService.Instance.SettingsDictionary[Characteristic.UUID + "-UseWindowsNotification"];
+ }
+ });
+
+ if (Characteristic.Characteristic.CharacteristicProperties != GattCharacteristicProperties.None)
+ {
+ StringBuilder sb = new StringBuilder();
+ bool first = true;
+
+ foreach (GattCharacteristicProperties p in Enum.GetValues(typeof(GattCharacteristicProperties)))
+ {
+ if (p == GattCharacteristicProperties.None)
+ {
+ continue;
+ }
+
+ if (Characteristic.Characteristic.CharacteristicProperties.HasFlag(p))
+ {
+ if (!first)
+ {
+ sb.Append(", ");
+ }
+ else
+ {
+ first = false;
+ }
+
+ sb.Append(Enum.GetName(typeof(GattCharacteristicProperties), p));
+ }
+ }
+
+ Properties = sb.ToString();
+ }
+ }
+
+ ///
+ /// Navigate from page
+ ///
+ ///
+ ///
+ /// Navigate task
+ public override async Task OnNavigatedFromAsync(IDictionary suspensionState, bool suspending)
+ {
+ if (suspending)
+ {
+ }
+
+ Services.SettingsServices.SettingsService.Instance.SettingsDictionary[Characteristic.UUID + "-UseWindowsNotification"] = useWindowsNotifications;
+
+ await Task.CompletedTask;
+ }
+
+ ///
+ /// Navigate from page
+ ///
+ ///
+ /// Navigate from task
+ public override async Task OnNavigatingFromAsync(NavigatingEventArgs args)
+ {
+ args.Cancel = false;
+ await Task.CompletedTask;
+ }
+ }
+}
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/DeviceServicesPageViewModel.cs b/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/DeviceServicesPageViewModel.cs
new file mode 100644
index 0000000..4b79792
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/DeviceServicesPageViewModel.cs
@@ -0,0 +1,129 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------------------------
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using BluetoothLEExplorer.Models;
+using BluetoothLEExplorer.Views;
+using Template10.Mvvm;
+using Template10.Services.NavigationService;
+using Windows.UI.Xaml.Navigation;
+
+namespace BluetoothLEExplorer.ViewModels
+{
+ ///
+ /// View Model for Device Services View
+ ///
+ public class DeviceServicesPageViewModel : ViewModelBase
+ {
+ ///
+ /// App context
+ ///
+ private GattSampleContext context = GattSampleContext.Context;
+
+ ///
+ /// Gets the currently selected bluetooth device
+ ///
+ public ObservableBluetoothLEDevice Device { get; private set; } = GattSampleContext.Context.SelectedBluetoothLEDevice;
+
+ ///
+ /// Source for
+ ///
+ private string errorText = String.Empty;
+
+ ///
+ /// Gets the error text to display
+ ///
+ public string ErrorText
+ {
+ get
+ {
+ return errorText;
+ }
+
+ private set
+ {
+ Set(ref errorText, value, "ErrorText");
+ }
+ }
+
+ ///
+ /// Source for
+ ///
+ private ObservableGattCharacteristics selectedCharacteristic;
+
+ ///
+ /// Gets or sets the currently selected characteristic
+ ///
+ public ObservableGattCharacteristics SelectedCharacteristic
+ {
+ get
+ {
+ return selectedCharacteristic;
+ }
+
+ set
+ {
+ Set(ref selectedCharacteristic, value, "SelectedCharacteristic");
+ context.SelectedCharacteristic = SelectedCharacteristic;
+ NavigationService.Navigate(typeof(CharacteristicPage));
+ }
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public DeviceServicesPageViewModel()
+ {
+ if (Windows.ApplicationModel.DesignMode.DesignModeEnabled)
+ {
+ }
+ }
+
+ ///
+ /// Navigate from page
+ ///
+ ///
+ ///
+ ///
+ /// Navigate from Task
+ public override async Task OnNavigatedToAsync(object parameter, NavigationMode mode, IDictionary suspensionState)
+ {
+ await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(
+ Windows.UI.Core.CoreDispatcherPriority.Normal,
+ () =>
+ {
+ });
+
+ 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;
+ }
+ }
+}
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/DiscoverViewModel.cs b/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/DiscoverViewModel.cs
new file mode 100644
index 0000000..d96c36b
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/DiscoverViewModel.cs
@@ -0,0 +1,449 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------------------------
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Diagnostics;
+using System.Linq;
+using System.Threading.Tasks;
+using BluetoothLEExplorer.Models;
+using Template10.Mvvm;
+using Template10.Services.NavigationService;
+using Windows.UI.Xaml.Navigation;
+using SortedObservableCollection;
+
+namespace BluetoothLEExplorer.ViewModels
+{
+ ///
+ /// View Model for the device discovery page
+ ///
+ public class DiscoverViewModel : ViewModelBase
+ {
+ ///
+ /// App context
+ ///
+ //private GattSampleContext context;
+
+ public GattSampleContext Context
+ {
+ get; private set;
+ }
+
+ private object deviceListLock = new object();
+
+ ///
+ /// Source for
+ ///
+ private SortedObservableCollection deviceList = new SortedObservableCollection(new ObservableBluetoothLEDevice.RSSIComparer(), "RSSI");
+
+ ///
+ /// Gets or sets the device list
+ ///
+ public SortedObservableCollection DeviceList
+ {
+ get
+ {
+ return deviceList;
+ }
+
+ set
+ {
+ Set(ref deviceList, value);
+ }
+ }
+
+ ///
+ /// Source for
+ ///
+ private ObservableBluetoothLEDevice selectedDevice;
+
+ ///
+ /// Gets or sets currently selected service
+ ///
+ public ObservableBluetoothLEDevice SelectedDevice
+ {
+ get
+ {
+ return selectedDevice;
+ }
+
+ set
+ {
+ bool same = selectedDevice == value;
+
+ Set(ref selectedDevice, value, "SelectedDevice");
+ Context.SelectedBluetoothLEDevice = selectedDevice;
+
+ if (same == false && value != null)
+ {
+ ConnectToSelectedDevice();
+ }
+ }
+ }
+
+ ///
+ /// Source for
+ ///
+ private bool isEnumerating = false;
+
+ ///
+ /// Gets a value indicating whether app is currently enumerating BT LE devices
+ ///
+ public bool IsEnumerating
+ {
+ get
+ {
+ return Context.IsEnumerating;
+ }
+
+ private set
+ {
+ Set(ref isEnumerating, value, "IsEnumerating");
+ }
+ }
+
+ ///
+ /// Source for
+ ///
+ private bool enumerationFinished = false;
+
+ ///
+ /// Gets a value indicating whether enumeration has finished
+ ///
+ public bool EnumerationFinished
+ {
+ get
+ {
+ return enumerationFinished;
+ }
+
+ private set
+ {
+ Set(ref enumerationFinished, value, "EnumerationFinished");
+ }
+ }
+
+ private bool continuousEnumeration = false;
+ public bool ContinuousEnumeration
+ {
+ get
+ {
+ return continuousEnumeration;
+ }
+
+ set
+ {
+ Set(ref continuousEnumeration, value, "ContinuousEnumeration");
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether peripheral role is supported
+ ///
+ public bool IsPeripheralRoleSupported
+ {
+ get
+ {
+ return GattSampleContext.Context.IsPeripheralRoleSupported;
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether central role is supported by this device
+ ///
+ public bool IsCentralRoleSupported
+ {
+ get
+ {
+ return Context.IsCentralRoleSupported;
+ }
+ }
+
+ private string gridFilter = string.Empty;
+ public string GridFilter
+ {
+ get
+ {
+ return gridFilter;
+ }
+
+ set
+ {
+ if(gridFilter != value)
+ {
+ Set(ref gridFilter, value, "GridFilter");
+ UpdateDeviceList();
+ }
+ }
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public DiscoverViewModel()
+ {
+ if (Windows.ApplicationModel.DesignMode.DesignModeEnabled)
+ {
+ }
+
+ Context = GattSampleContext.Context;
+ Context.PropertyChanged += Context_PropertyChanged;
+ }
+
+ ///
+ /// Context property changed
+ ///
+ ///
+ ///
+ private void Context_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == "IsEnumerating")
+ {
+ IsEnumerating = Context.IsEnumerating;
+ }
+
+ if (e.PropertyName == "EnumerationFinished")
+ {
+ EnumerationFinished = Context.EnumerationFinished;
+ if(ContinuousEnumeration == true)
+ {
+ Debug.WriteLine("Enumeration finished, but continue is set, continuing");
+ StartEnumeration();
+ }
+ }
+
+ if (e.PropertyName == "IsPeripheralRoleSupported")
+ {
+ this.RaisePropertyChanged("IsPeripheralRoleSupported");
+ }
+
+ if (e.PropertyName == "IsCentralRoleSupported")
+ {
+ RaisePropertyChanged("IsCentralRoleSupported");
+ }
+ }
+
+ ///
+ /// The devices list changed
+ ///
+ ///
+ ///
+ private void BluetoothLEDevices_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
+ {
+ string msg = string.Empty;
+ lock (deviceListLock)
+ {
+ if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
+ {
+ foreach (ObservableBluetoothLEDevice newDev in e.NewItems)
+ {
+ if (ShouldShow(newDev))
+ {
+ DeviceList.Add(newDev);
+ }
+ }
+ }
+ else if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove)
+ {
+ foreach (ObservableBluetoothLEDevice oldDev in e.OldItems)
+ {
+ DeviceList.Remove(oldDev);
+ }
+ }
+ else if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Reset)
+ {
+ DeviceList.Clear();
+ }
+ }
+
+ UpdateDeviceList();
+ }
+
+ private bool ShouldShow(ObservableBluetoothLEDevice dev)
+ {
+ string filter = gridFilter.ToUpper();
+ return (dev.Name.ToUpper().Contains(filter) || dev.BluetoothAddressAsString.ToUpper().Contains(filter));
+ }
+
+ private void UpdateDeviceList()
+ {
+ List toRemove = new List();
+
+ lock (deviceListLock)
+ {
+ foreach (ObservableBluetoothLEDevice dev in DeviceList)
+ {
+ if (ShouldShow(dev) == false)
+ {
+ toRemove.Add(dev);
+ }
+ }
+
+ foreach (ObservableBluetoothLEDevice dev in toRemove)
+ {
+ DeviceList.Remove(dev);
+ }
+
+ foreach (ObservableBluetoothLEDevice dev in Context.BluetoothLEDevices)
+ {
+ if (ShouldShow(dev) && DeviceList.Contains(dev) == false)
+ {
+ DeviceList.Add(dev);
+ }
+ }
+ }
+ }
+
+ ///
+ /// Connect to the currently selected service
+ ///
+ public async void ConnectToSelectedDevice()
+ {
+ Debug.WriteLine("ConnectToSelectedDevice: Entering");
+ StopEnumeration();
+ Views.Busy.SetBusy(true, "Connecting to " + SelectedDevice.Name);
+
+ Debug.WriteLine("ConnectToSelectedDevice: Trying to connect to " + SelectedDevice.Name);
+
+ if (await SelectedDevice.Connect() == false)
+ {
+ Debug.WriteLine("ConnectToSelectedDevice: Something went wrong getting the BluetoothLEDevice");
+ Views.Busy.SetBusy(false);
+ SelectedDevice = null;
+ NavigationService.Navigate(typeof(Views.Discover));
+ return;
+ }
+
+ Debug.WriteLine("ConnectToSelectedDevice: Going to Device Service Page");
+ Views.Busy.SetBusy(false);
+ GotoDeviceServicesPage();
+ Debug.WriteLine("ConnectToSelectedDevice: Exiting");
+ }
+
+ ///
+ /// Navigate to page
+ ///
+ ///
+ ///
+ ///
+ /// Navigate to task
+ public override async Task OnNavigatedToAsync(object parameter, NavigationMode mode, IDictionary suspensionState)
+ {
+ if (suspensionState.Any())
+ {
+ }
+
+ Context.BluetoothLEDevices.CollectionChanged += BluetoothLEDevices_CollectionChanged;
+ GridFilter = "";
+ UpdateDeviceList();
+
+ await Task.CompletedTask;
+ }
+
+ ///
+ /// Navigate from page
+ ///
+ ///
+ ///
+ /// Navigate from task
+ public override async Task OnNavigatedFromAsync(IDictionary suspensionState, bool suspending)
+ {
+ if (suspending)
+ {
+ }
+
+ Context.BluetoothLEDevices.CollectionChanged -= BluetoothLEDevices_CollectionChanged;
+ await Task.CompletedTask;
+ }
+
+ ///
+ /// Navigate from page
+ ///
+ ///
+ /// Navigate from task
+ public override async Task OnNavigatingFromAsync(NavigatingEventArgs args)
+ {
+ args.Cancel = false;
+ Context.StopEnumeration();
+ await Task.CompletedTask;
+ }
+
+ ///
+ /// Toggles the enumeration
+ ///
+ public async void ToggleEnumeration()
+ {
+ await Dispatcher.DispatchAsync(() =>
+ {
+ if (Context.IsEnumerating == false)
+ {
+ Context.StartEnumeration();
+ }
+ else
+ {
+ Context.StopEnumeration();
+ }
+ });
+ }
+
+ ///
+ /// Start enumeration
+ ///
+ public async void StartEnumeration()
+ {
+ if (Context.IsEnumerating == true)
+ {
+ return;
+ }
+
+ await Dispatcher.DispatchAsync(() =>
+ {
+ Context.StartEnumeration();
+ });
+ }
+
+ ///
+ /// Stop enumeration
+ ///
+ public async void StopEnumeration()
+ {
+ if (Context.IsEnumerating == false)
+ {
+ return;
+ }
+
+ await Dispatcher.DispatchAsync(() =>
+ {
+ Context.StopEnumeration();
+ });
+ }
+
+ ///
+ /// Go to Devices page
+ ///
+ public void GotoDeviceServicesPage()
+ {
+ NavigationService.Navigate(typeof(Views.DeviceServicesPage));
+ }
+
+ ///
+ /// 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/GenericGattCharacteristicViewModel.cs b/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/GenericGattCharacteristicViewModel.cs
new file mode 100644
index 0000000..afbeb79
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/GenericGattCharacteristicViewModel.cs
@@ -0,0 +1,239 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------------------------
+using System.Collections.ObjectModel;
+using GattServicesLibrary;
+using Template10.Mvvm;
+using Windows.Devices.Bluetooth.GenericAttributeProfile;
+using GattHelper.Converters;
+using System;
+
+namespace BluetoothLEExplorer.ViewModels
+{
+ ///
+ /// View model used to display a
+ ///
+ public class GenericGattCharacteristicViewModel : ViewModelBase
+ {
+ ///
+ /// Source for
+ ///
+ private GenericGattCharacteristic characteristic;
+
+ ///
+ /// Gets the characteristic that this class wraps
+ ///
+ public GenericGattCharacteristic Characteristic
+ {
+ get
+ {
+ return characteristic;
+ }
+
+ private set
+ {
+ Set(ref characteristic, value, "Characteristic");
+ }
+ }
+
+ ///
+ /// Source for
+ ///
+ private string userDescription;
+
+ ///
+ /// Gets or sets the user description
+ ///
+ public string UserDescription
+ {
+ get
+ {
+ return userDescription;
+ }
+
+ set
+ {
+ Set(ref userDescription, value, "UserDescription");
+ }
+ }
+
+ ///
+ /// Source for
+ ///
+ private ObservableCollection descriptors;
+
+ ///
+ /// Gets or sets the list of descriptors for this service
+ ///
+ private ObservableCollection Descriptors
+ {
+ get
+ {
+ return descriptors;
+ }
+
+ set
+ {
+ Set(ref descriptors, value, "Descriptors");
+ }
+ }
+
+ ///
+ /// Source for
+ ///
+ private bool hasReadDescriptor = true;
+
+ ///
+ /// Gets or sets a value indicating whether this characteristic can be read
+ ///
+ public bool HasReadDescriptor
+ {
+ get
+ {
+ return hasReadDescriptor;
+ }
+
+ set
+ {
+ Set(ref hasReadDescriptor, value, "HasReadDescriptor");
+ }
+ }
+
+ ///
+ /// Source for
+ ///
+ private bool hasWriteDescriptor = false;
+
+ ///
+ /// Gets or sets a value indicating whether this characteristic can write
+ ///
+ public bool HasWriteDescriptor
+ {
+ get
+ {
+ return hasWriteDescriptor;
+ }
+
+ set
+ {
+ Set(ref hasWriteDescriptor, value, "HasWriteDescriptor");
+ }
+ }
+
+ ///
+ /// Source for
+ ///
+ private bool hasWriteWithoutResponseDescriptor = false;
+
+ ///
+ /// Gets or sets a value indicating whether this characteristic can write without responds
+ ///
+ public bool HasWriteWithoutResponseDescriptor
+ {
+ get
+ {
+ return hasWriteWithoutResponseDescriptor;
+ }
+
+ set
+ {
+ Set(ref hasWriteWithoutResponseDescriptor, value, "JasWriteWithoutRespondsDescriptor");
+ }
+ }
+
+ ///
+ /// Source for
+ ///
+ private bool hasNotifyDescriptor = false;
+
+ ///
+ /// Gets or sets a value indicating whether this characteristic can notify
+ ///
+ public bool HasNotifyDescriptor
+ {
+ get
+ {
+ return hasNotifyDescriptor;
+ }
+
+ set
+ {
+ Set(ref hasNotifyDescriptor, value, "HasNotifyDescriptor");
+ }
+ }
+
+ ///
+ /// Source for
+ ///
+ private bool hasIndicateDescriptor = false;
+
+ ///
+ /// Gets or sets a value indicating whether this characteristic can indicate
+ ///
+ public bool HasIndicateDescriptor
+ {
+ get
+ {
+ return hasIndicateDescriptor;
+ }
+
+ set
+ {
+ Set(ref hasIndicateDescriptor, value, "HasIndicateDescriptor");
+ }
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ public GenericGattCharacteristicViewModel(GenericGattCharacteristic characteristic)
+ {
+ this.characteristic = characteristic;
+ userDescription = Characteristic.Characteristic.UserDescription;
+
+ HasReadDescriptor = Characteristic.Characteristic.CharacteristicProperties.HasFlag(GattCharacteristicProperties.Read);
+ HasWriteDescriptor = Characteristic.Characteristic.CharacteristicProperties.HasFlag(GattCharacteristicProperties.Write);
+ HasWriteWithoutResponseDescriptor = Characteristic.Characteristic.CharacteristicProperties.HasFlag(GattCharacteristicProperties.WriteWithoutResponse);
+ HasNotifyDescriptor = Characteristic.Characteristic.CharacteristicProperties.HasFlag(GattCharacteristicProperties.Notify);
+ HasIndicateDescriptor = Characteristic.Characteristic.CharacteristicProperties.HasFlag(GattCharacteristicProperties.Indicate);
+ }
+
+ ///
+ /// This sets the value based on the input string with regard to the characteristics first presentation format
+ ///
+ /// value to set
+ /// is the passed in string a hex byte array
+ public void SetValueFromString(string value, bool isHexString = false)
+ {
+ if(isHexString == true)
+ {
+ Characteristic.Value = GattConvert.ToIBufferFromHexString(value);
+ return;
+ }
+
+ if(Characteristic.Characteristic.PresentationFormats.Count > 0)
+ {
+ byte format = Characteristic.Characteristic.PresentationFormats[0].FormatType;
+
+ // Check our supported formats to convert a string to this Characteristics Value
+ if(!((format != GattPresentationFormatTypes.SInt32) ||
+ (format != GattPresentationFormatTypes.Utf8)))
+ {
+ throw new NotImplementedException("Only SInt32 and UTF8 are supported");
+ }
+
+ //TODO: Support more presentation types
+ if(format == GattPresentationFormatTypes.SInt32)
+ {
+ Characteristic.Value = GattConvert.ToIBuffer(Convert.ToInt32(value));
+ }
+ else if(format == GattPresentationFormatTypes.Utf8)
+ {
+ Characteristic.Value = GattConvert.ToIBuffer(value);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/GenericGattServiceViewModel.cs b/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/GenericGattServiceViewModel.cs
new file mode 100644
index 0000000..5183584
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/GenericGattServiceViewModel.cs
@@ -0,0 +1,125 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------------------------
+using System;
+using BluetoothLEExplorer.Models;
+using GattServicesLibrary;
+using Template10.Mvvm;
+using Windows.Devices.Bluetooth.GenericAttributeProfile;
+
+namespace BluetoothLEExplorer.ViewModels
+{
+ ///
+ /// View Model used to display a
+ ///
+ public class GenericGattServiceViewModel : ViewModelBase
+ {
+ ///
+ /// Source for property
+ ///
+ private GenericGattService service = null;
+
+ ///
+ /// Gets the service for this view model
+ ///
+ public GenericGattService Service
+ {
+ get
+ {
+ return service;
+ }
+
+ private set
+ {
+ Set(ref service, value, "Service");
+ }
+ }
+
+ ///
+ /// Source for property
+ ///
+ private bool isPublishing = false;
+
+ ///
+ /// Gets or sets a value indicating whether the Advertisement status is published or not
+ ///
+ public bool IsPublishing
+ {
+ get
+ {
+ return isPublishing;
+ }
+
+ set
+ {
+ if(value != isPublishing)
+ {
+ if(value == true)
+ {
+ Start();
+
+ }
+ if(value == false)
+ {
+ Stop();
+ }
+
+ isPublishing = value;
+ RaisePropertyChanged("IsPublishing");
+ }
+
+ }
+ }
+
+ ///
+ /// Starts the service
+ ///
+ private async void Start()
+ {
+ await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(
+ Windows.UI.Core.CoreDispatcherPriority.Normal,
+ () =>
+ {
+ Service.Start(true);
+ });
+ }
+
+ ///
+ /// Stops the service
+ ///
+ private async void Stop()
+ {
+ await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(
+ Windows.UI.Core.CoreDispatcherPriority.Normal,
+ () =>
+ {
+ Service.Stop();
+ });
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ public GenericGattServiceViewModel(GenericGattService service)
+ {
+ Service = service;
+ Service.PropertyChanged += Service_PropertyChanged;
+ }
+
+ private void Service_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
+ {
+ // Track the IsPublishing property
+ IsPublishing = Service.IsPublishing;
+ }
+
+ ///
+ /// Removes this service from context
+ ///
+ public void RemoveThisFromContext()
+ {
+ GattSampleContext.Context.CreatedServices.Remove(this);
+ }
+ }
+}
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/Services/BatteryServicePageViewModel.cs b/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/Services/BatteryServicePageViewModel.cs
new file mode 100644
index 0000000..840520e
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/Services/BatteryServicePageViewModel.cs
@@ -0,0 +1,79 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------------------------
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using BluetoothLEExplorer.Models;
+using GattServicesLibrary.Services;
+using Template10.Mvvm;
+using Template10.Services.NavigationService;
+using Windows.UI.Xaml.Navigation;
+
+namespace BluetoothLEExplorer.ViewModels
+{
+ ///
+ /// View Model for the view
+ ///
+ public class BatteryServicePageViewModel : ViewModelBase
+ {
+ ///
+ /// App context
+ ///
+ private GattSampleContext context = GattSampleContext.Context;
+
+ ///
+ /// Gets or sets the currently selected service view model
+ ///
+ public GenericGattServiceViewModel ServiceVM { get; set; } = GattSampleContext.Context.SelectedGattServerService;
+
+ ///
+ /// Gets or sets the currently selected service
+ ///
+ public BatteryService Service { get; set; } = GattSampleContext.Context.SelectedGattServerService.Service as BatteryService;
+
+ ///
+ /// Gets the battery level
+ ///
+ public GenericGattCharacteristicViewModel BatteryLevel { get; private set; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public BatteryServicePageViewModel()
+ {
+ BatteryLevel = new GenericGattCharacteristicViewModel(Service.BatteryLevel);
+ }
+
+ ///
+ /// Navigate from page
+ ///
+ ///
+ /// Navigate from task
+ public override async Task OnNavigatingFromAsync(NavigatingEventArgs args)
+ {
+ context.SelectedGattServerService = null;
+ 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/Services/BloodPressureServicePageViewModel.cs b/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/Services/BloodPressureServicePageViewModel.cs
new file mode 100644
index 0000000..6cac9e1
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/Services/BloodPressureServicePageViewModel.cs
@@ -0,0 +1,85 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------------------------
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using BluetoothLEExplorer.Models;
+using GattServicesLibrary.Services;
+using Template10.Mvvm;
+using Template10.Services.NavigationService;
+using Windows.UI.Xaml.Navigation;
+
+namespace BluetoothLEExplorer.ViewModels
+{
+ ///
+ /// View Model for the view
+ ///
+ public class BloodPressureServicePageViewModel : ViewModelBase
+ {
+ ///
+ /// App context
+ ///
+ private GattSampleContext context = GattSampleContext.Context;
+
+ ///
+ /// Gets or sets the currently selected service view model
+ ///
+ public GenericGattServiceViewModel ServiceVM { get; set; } = GattSampleContext.Context.SelectedGattServerService;
+
+ ///
+ /// Gets or sets the currently selected service
+ ///
+ public BloodPressureService Service { get; set; } = GattSampleContext.Context.SelectedGattServerService.Service as BloodPressureService;
+
+ ///
+ /// Gets the blood pressure measurement
+ ///
+ public GenericGattCharacteristicViewModel BloodPressureMeasurement { get; private set; }
+
+ ///
+ /// Gets the blood pressure feature
+ ///
+ public GenericGattCharacteristicViewModel BloodPressureFeature { get; private set; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public BloodPressureServicePageViewModel()
+ {
+ BloodPressureMeasurement = new GenericGattCharacteristicViewModel(Service.BloodPressureMeasurement);
+ BloodPressureFeature = new GenericGattCharacteristicViewModel(Service.BloodPressureFeature);
+ }
+
+ ///
+ /// Navigate from page
+ ///
+ ///
+ /// Navigate from task
+ public override async Task OnNavigatingFromAsync(NavigatingEventArgs args)
+ {
+ context.SelectedGattServerService = null;
+ 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/Services/HeartRateServicePageViewModel.cs b/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/Services/HeartRateServicePageViewModel.cs
new file mode 100644
index 0000000..6e8d4ea
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/Services/HeartRateServicePageViewModel.cs
@@ -0,0 +1,79 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------------------------
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using BluetoothLEExplorer.Models;
+using GattServicesLibrary.Services;
+using Template10.Mvvm;
+using Template10.Services.NavigationService;
+using Windows.UI.Xaml.Navigation;
+
+namespace BluetoothLEExplorer.ViewModels
+{
+ ///
+ /// View Model for the view
+ ///
+ public class HeartRateServicePageViewModel : ViewModelBase
+ {
+ ///
+ /// App context
+ ///
+ private GattSampleContext context = GattSampleContext.Context;
+
+ ///
+ /// Gets or sets the currently selected service view model
+ ///
+ public GenericGattServiceViewModel ServiceVM { get; set; } = GattSampleContext.Context.SelectedGattServerService;
+
+ ///
+ /// Gets or sets the currently selected service
+ ///
+ public HeartRateService Service { get; set; } = GattSampleContext.Context.SelectedGattServerService.Service as HeartRateService;
+
+ ///
+ /// Gets the heart rate measurement
+ ///
+ public GenericGattCharacteristicViewModel HeartRateMeasurement { get; private set; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public HeartRateServicePageViewModel()
+ {
+ HeartRateMeasurement = new GenericGattCharacteristicViewModel(Service.HeartRateMeasurement);
+ }
+
+ ///
+ /// Navigate from page
+ ///
+ ///
+ /// Navigate from task
+ public override async Task OnNavigatingFromAsync(NavigatingEventArgs args)
+ {
+ context.SelectedGattServerService = null;
+ 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/Services/MicrosoftServicePageViewModel.cs b/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/Services/MicrosoftServicePageViewModel.cs
new file mode 100644
index 0000000..3d6413f
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/Services/MicrosoftServicePageViewModel.cs
@@ -0,0 +1,103 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------------------------
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using BluetoothLEExplorer.Models;
+using GattServicesLibrary.Services;
+using Template10.Mvvm;
+using Template10.Services.NavigationService;
+using Windows.UI.Xaml.Navigation;
+
+namespace BluetoothLEExplorer.ViewModels
+{
+ ///
+ /// View Model for the view
+ ///
+ public class MicrosoftServicePageViewModel : ViewModelBase
+ {
+ ///
+ /// App context
+ ///
+ private GattSampleContext context = GattSampleContext.Context;
+
+ ///
+ /// Gets or sets the selected service view model
+ ///
+ public GenericGattServiceViewModel ServiceVM { get; set; } = GattSampleContext.Context.SelectedGattServerService;
+
+ ///
+ /// Gets or sets the selected service
+ ///
+ public MicrosoftService Service { get; set; } = GattSampleContext.Context.SelectedGattServerService.Service as MicrosoftService;
+
+ ///
+ /// Gets the read Characteristic
+ ///
+ public GenericGattCharacteristicViewModel ReadCharacteristic { get; private set; }
+
+ ///
+ /// Gets write Characteristic
+ ///
+ public GenericGattCharacteristicViewModel WriteCharacteristic { get; private set; }
+
+ ///
+ /// Gets notify Characteristic
+ ///
+ public GenericGattCharacteristicViewModel NotifyCharacteristic { get; private set; }
+
+ ///
+ /// Gets indicate Characteristic
+ ///
+ public GenericGattCharacteristicViewModel IndicateCharacteristic { get; private set; }
+
+ ///
+ /// Gets the read long characteristic
+ ///
+ public GenericGattCharacteristicViewModel ReadLongCharacteristic { get; private set; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public MicrosoftServicePageViewModel()
+ {
+ ReadCharacteristic = new GenericGattCharacteristicViewModel(Service.ReadCharacteristic);
+ WriteCharacteristic = new GenericGattCharacteristicViewModel(Service.WriteCharacteristic);
+ NotifyCharacteristic = new GenericGattCharacteristicViewModel(Service.NotifyCharacteristic);
+ IndicateCharacteristic = new GenericGattCharacteristicViewModel(Service.IndicateCharacteristic);
+ ReadLongCharacteristic = new GenericGattCharacteristicViewModel(Service.ReadLongCharacteristic);
+ }
+
+ ///
+ /// Navigate from page
+ ///
+ ///
+ /// Navigate from task
+ public override async Task OnNavigatingFromAsync(NavigatingEventArgs args)
+ {
+ context.SelectedGattServerService = null;
+ 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/SettingsPageViewModel.cs b/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/SettingsPageViewModel.cs
new file mode 100644
index 0000000..f036cb6
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/SettingsPageViewModel.cs
@@ -0,0 +1,164 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------------------------
+using System;
+using System.Threading.Tasks;
+using Template10.Mvvm;
+using Windows.UI.Xaml;
+
+[module: System.Diagnostics.CodeAnalysis.SuppressMessage(
+ "Microsoft.StyleCop.CSharp.MaintainabilityRules",
+ "SA1402:FileMayOnlyContainASingleClass",
+ Justification = "Template 10 comes this way and it improves readability")]
+
+namespace BluetoothLEExplorer.ViewModels
+{
+ ///
+ /// View Model for the settings page
+ ///
+ public class SettingsPageViewModel : ViewModelBase
+ {
+ ///
+ /// Gets the settings part of the settings page
+ ///
+ public SettingsPartViewModel SettingsPartViewModel { get; } = new SettingsPartViewModel();
+
+ ///
+ /// Gets the about part of the settings page
+ ///
+ public AboutPartViewModel AboutPartViewModel { get; } = new AboutPartViewModel();
+ }
+
+ ///
+ /// View Model for the settings part of the settings page
+ ///
+ public class SettingsPartViewModel : ViewModelBase
+ {
+ ///
+ /// Settings service
+ ///
+ private Services.SettingsServices.SettingsService settings;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public SettingsPartViewModel()
+ {
+ if (Windows.ApplicationModel.DesignMode.DesignModeEnabled)
+ {
+ // designtime
+ }
+ else
+ {
+ settings = Services.SettingsServices.SettingsService.Instance;
+ }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the shell should have the back button
+ ///
+ public bool UseShellBackButton
+ {
+ get
+ {
+ return settings.UseShellBackButton;
+ }
+
+ set
+ {
+ settings.UseShellBackButton = value;
+ RaisePropertyChanged();
+ }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether light theme should be used
+ ///
+ public bool UseLightThemeButton
+ {
+ get
+ {
+ return settings.AppTheme.Equals(ApplicationTheme.Light);
+ }
+
+ set
+ {
+ settings.AppTheme = value ? ApplicationTheme.Light : ApplicationTheme.Dark;
+ RaisePropertyChanged();
+ }
+ }
+
+ ///
+ /// Text to show on busy page
+ ///
+ private string busyText = "Please wait...";
+
+ ///
+ /// Gets or sets the busy text
+ ///
+ public string BusyText
+ {
+ get
+ {
+ return busyText;
+ }
+
+ set
+ {
+ Set(ref busyText, value);
+ showBusyCommand.RaiseCanExecuteChanged();
+ }
+ }
+
+ ///
+ /// Delegate command to show busy page
+ ///
+ private DelegateCommand showBusyCommand;
+
+ ///
+ /// Show busy command delegate
+ ///
+ public DelegateCommand ShowBusyCommand
+ => showBusyCommand ?? (showBusyCommand = new DelegateCommand(async () =>
+ {
+ Views.Busy.SetBusy(true, busyText);
+ await Task.Delay(5000);
+ Views.Busy.SetBusy(false);
+ },
+ () => !string.IsNullOrEmpty(BusyText)));
+ }
+
+ ///
+ /// View Model of the about part of the settings page
+ ///
+ public class AboutPartViewModel : ViewModelBase
+ {
+ ///
+ /// Gets the logo of this application
+ ///
+ public Uri Logo => Windows.ApplicationModel.Package.Current.Logo;
+
+ ///
+ /// Gets the display name of this application
+ ///
+ public string DisplayName => Windows.ApplicationModel.Package.Current.DisplayName;
+
+ ///
+ /// Gets the publisher of this application
+ ///
+ public string Publisher => Windows.ApplicationModel.Package.Current.PublisherDisplayName;
+
+ ///
+ /// Gets the version string
+ ///
+ public string Version
+ {
+ get
+ {
+ var v = Windows.ApplicationModel.Package.Current.Id.Version;
+ return $"{v.Major}.{v.Minor}.{v.Build}.{v.Revision}";
+ }
+ }
+ }
+}
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/ShellViewModel.cs b/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/ShellViewModel.cs
new file mode 100644
index 0000000..acaa5aa
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/ShellViewModel.cs
@@ -0,0 +1,72 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------------------------
+using BluetoothLEExplorer.Models;
+using Template10.Mvvm;
+
+namespace BluetoothLEExplorer.ViewModels
+{
+ ///
+ /// View Model for the shell
+ ///
+ public class ShellViewModel : ViewModelBase
+ {
+ ///
+ /// App context
+ ///
+ private GattSampleContext context = GattSampleContext.Context;
+
+ ///
+ /// Gets a value indicating whether this host supports peripheral role
+ ///
+ public bool IsPeripheralRoleSupported
+ {
+ get
+ {
+ return context.IsPeripheralRoleSupported;
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether this host supports central role
+ ///
+ public bool IsCentralRoleSupported
+ {
+ get
+ {
+ return context.IsCentralRoleSupported;
+ }
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ShellViewModel()
+ {
+ if (Windows.ApplicationModel.DesignMode.DesignModeEnabled)
+ {
+ }
+
+ context.PropertyChanged += Context_PropertyChanged;
+ }
+
+ ///
+ /// Property changed
+ ///
+ ///
+ ///
+ private void Context_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == "IsPeripheralRoleSupported")
+ {
+ this.RaisePropertyChanged("IsPeripheralRoleSupported");
+ }
+
+ if (e.PropertyName == "IsCentralRoleSupported")
+ {
+ RaisePropertyChanged("IsCentralRoleSupported");
+ }
+ }
+ }
+}
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/VirtualPeripheralPageViewModel.cs b/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/VirtualPeripheralPageViewModel.cs
new file mode 100644
index 0000000..7d5b599
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorer/ViewModels/VirtualPeripheralPageViewModel.cs
@@ -0,0 +1,241 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------------------------
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using BluetoothLEExplorer.Models;
+using Template10.Mvvm;
+using Template10.Services.NavigationService;
+using Windows.UI.Xaml.Navigation;
+
+namespace BluetoothLEExplorer.ViewModels
+{
+ ///
+ /// View Model for the Virtual Peripheral Page
+ ///
+ public class VirtualPeripheralPageViewModel : ViewModelBase
+ {
+ ///
+ /// Gets or sets the name of the available services
+ ///
+ public List AvailableServices { get; set; } = new List();
+
+ ///
+ /// Gets or sets the name of the newly selected service
+ ///
+ public string NewSelectedService { get; set; } = String.Empty;
+
+ ///
+ /// Source for
+ ///
+ private GenericGattServiceViewModel selectedService;
+
+ ///
+ /// Gets or sets the currently selected service
+ ///
+ public GenericGattServiceViewModel SelectedService
+ {
+ get
+ {
+ return selectedService;
+ }
+
+ set
+ {
+ if (selectedService != value && value != null)
+ {
+ Set(ref selectedService, value, "SelectedService");
+ Context.SelectedGattServerService = selectedService;
+ }
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether this host supports peripheral role
+ ///
+ public bool IsPeripheralRoleSupported
+ {
+ get
+ {
+ return GattSampleContext.Context.IsPeripheralRoleSupported;
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether this host supports central role
+ ///
+ public bool IsCentralRoleSupported
+ {
+ get
+ {
+ return Context.IsCentralRoleSupported;
+ }
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public VirtualPeripheralPageViewModel()
+ {
+ Context.PropertyChanged += Context_PropertyChanged;
+ AvailableServices.Add("Battery");
+ AvailableServices.Add("Microsoft Service");
+ AvailableServices.Add("Heart Rate Service");
+ AvailableServices.Add("Blood Pressure Service");
+ }
+
+ ///
+ /// Callback when context changes
+ ///
+ ///
+ ///
+ private void Context_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == "IsPeripheralRoleSupported")
+ {
+ this.RaisePropertyChanged("IsPeripheralRoleSupported");
+ }
+
+ if (e.PropertyName == "IsCentralRoleSupported")
+ {
+ RaisePropertyChanged("IsCentralRoleSupported");
+ }
+ }
+
+ ///
+ /// Gets the context
+ ///
+ public GattSampleContext Context { get; } = GattSampleContext.Context;
+
+ ///
+ /// Creates the service that was selected
+ ///
+ public async void CreateService()
+ {
+ GattServicesLibrary.GenericGattService service = null;
+
+ if (NewSelectedService == null)
+ {
+ return;
+ }
+
+ Views.Busy.SetBusy(true, "Creating Service");
+
+ switch(NewSelectedService)
+ {
+ case "Battery":
+ service = new GattServicesLibrary.Services.BatteryService();
+ break;
+
+ case "Microsoft Service":
+ service = new GattServicesLibrary.Services.MicrosoftService();
+ break;
+
+ case "Heart Rate Service":
+ service = new GattServicesLibrary.Services.HeartRateService();
+ break;
+
+ case "Blood Pressure Service":
+ service = new GattServicesLibrary.Services.BloodPressureService();
+ break;
+
+ default:
+ return;
+ }
+
+ await service.Init();
+ GenericGattServiceViewModel serviceVM = new GenericGattServiceViewModel(service);
+ Context.CreatedServices.Add(serviceVM);
+ Context.SelectedGattServerService = serviceVM;
+ NavigateToService();
+ return;
+ }
+
+ ///
+ /// Initializes
+ ///
+ public void NavigateToService()
+ {
+ if (Context.SelectedGattServerService == null)
+ {
+ Views.Busy.SetBusy(false);
+ return;
+ }
+
+ switch(Context.SelectedGattServerService.Service.Name)
+ {
+ case "Battery Service":
+ Views.Busy.SetBusy(false);
+ NavigationService.Navigate(typeof(Views.BatteryServicePage));
+ break;
+
+ case "Microsoft Service":
+ Views.Busy.SetBusy(false);
+ NavigationService.Navigate(typeof(Views.MicrosoftServicePage));
+ break;
+
+ case "Heart Rate Service":
+ Views.Busy.SetBusy(false);
+ NavigationService.Navigate(typeof(Views.HeartRateServicePage));
+ break;
+
+ case "Blood Pressure Service":
+ Views.Busy.SetBusy(false);
+ NavigationService.Navigate(typeof(Views.BloodPressureServicePage));
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ ///
+ /// Navigating to page
+ ///
+ ///
+ ///
+ ///
+ /// Navigate to task
+ public override async Task OnNavigatedToAsync(object parameter, NavigationMode mode, IDictionary suspensionState)
+ {
+ if (suspensionState.Any())
+ {
+ }
+
+ Context.SelectedGattServerService = null;
+ await Task.CompletedTask;
+ }
+
+ ///
+ /// Navigating 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 private
+ ///
+ 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/Views/Beacon.xaml b/BluetoothLEExplorer/BluetoothLEExplorer/Views/Beacon.xaml
new file mode 100644
index 0000000..da181fd
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorer/Views/Beacon.xaml
@@ -0,0 +1,124 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Views/Beacon.xaml.cs b/BluetoothLEExplorer/BluetoothLEExplorer/Views/Beacon.xaml.cs
new file mode 100644
index 0000000..5feb2f0
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorer/Views/Beacon.xaml.cs
@@ -0,0 +1,24 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------------------------
+using Windows.UI.Xaml.Controls;
+
+//// The Blank Page item template is documented at http://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 Beacon : Page
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public Beacon()
+ {
+ this.InitializeComponent();
+ }
+ }
+}
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Views/Busy.xaml b/BluetoothLEExplorer/BluetoothLEExplorer/Views/Busy.xaml
new file mode 100644
index 0000000..6832792
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorer/Views/Busy.xaml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Views/Busy.xaml.cs b/BluetoothLEExplorer/BluetoothLEExplorer/Views/Busy.xaml.cs
new file mode 100644
index 0000000..6039652
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorer/Views/Busy.xaml.cs
@@ -0,0 +1,75 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------------------------
+using Template10.Common;
+using Template10.Controls;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+
+namespace BluetoothLEExplorer.Views
+{
+ ///
+ /// Busy overlay
+ ///
+ public sealed partial class Busy : UserControl
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public Busy()
+ {
+ InitializeComponent();
+ }
+
+ ///
+ /// Gets or sets a value of the busy text
+ ///
+ public string BusyText
+ {
+ get { return (string)GetValue(BusyTextProperty); }
+ set { SetValue(BusyTextProperty, value); }
+ }
+
+ ///
+ /// Busy text property
+ ///
+ public static readonly DependencyProperty BusyTextProperty = DependencyProperty.Register(nameof(BusyText), typeof(string), typeof(Busy), new PropertyMetadata("Please wait..."));
+
+ ///
+ /// Gets or sets a value indicating whether the busy screen should be displayed
+ ///
+ public bool IsBusy
+ {
+ get { return (bool)GetValue(IsBusyProperty); }
+ set { SetValue(IsBusyProperty, value); }
+ }
+
+ ///
+ /// Dependency object for is busy property
+ ///
+ public static readonly DependencyProperty IsBusyProperty = DependencyProperty.Register(nameof(IsBusy), typeof(bool), typeof(Busy), new PropertyMetadata(false));
+
+ ///
+ /// hide and show busy dialog
+ ///
+ ///
+ ///
+ public static void SetBusy(bool busy, string text = null)
+ {
+ WindowWrapper.Current().Dispatcher.Dispatch(() =>
+ {
+ var modal = Window.Current.Content as ModalDialog;
+ var view = modal.ModalContent as Busy;
+
+ if (view == null)
+ {
+ modal.ModalContent = view = new Busy();
+ }
+
+ modal.IsModal = view.IsBusy = busy;
+ view.BusyText = text;
+ });
+ }
+ }
+}
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Views/CharacteristicPage.xaml b/BluetoothLEExplorer/BluetoothLEExplorer/Views/CharacteristicPage.xaml
new file mode 100644
index 0000000..6c8f442
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorer/Views/CharacteristicPage.xaml
@@ -0,0 +1,292 @@
+
+
+
+
+
+
+
+
+
+ Collapsed
+
+
+ Visible
+
+
+
+
+
+ True
+
+
+ Visible
+
+
+ Collapsed
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0,0,-45,0
+
+
+
+
+ 0,0,-45,0
+
+
+
+
+ 0,0,-45,0
+
+
+
+
+ 0,0,-45,0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0,0,-45,0
+
+
+
+
+ 0,0,-45,0
+
+
+
+
+ 0,0,-45,0
+
+
+
+
+ 0,0,-45,0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Views/CharacteristicPage.xaml.cs b/BluetoothLEExplorer/BluetoothLEExplorer/Views/CharacteristicPage.xaml.cs
new file mode 100644
index 0000000..fb6e95d
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorer/Views/CharacteristicPage.xaml.cs
@@ -0,0 +1,61 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------------------------
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Media;
+using Windows.UI.Xaml.Navigation;
+
+namespace BluetoothLEExplorer.Views
+{
+ ///
+ /// Characteristic Page
+ ///
+ public sealed partial class CharacteristicPage : Page
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public CharacteristicPage()
+ {
+ InitializeComponent();
+ NavigationCacheMode = NavigationCacheMode.Disabled;
+ }
+
+ private void WriteValue_TextChanging(TextBox sender, TextBoxTextChangingEventArgs args)
+ {
+ WriteBoxBackgroundCheck();
+ }
+
+ private void radioButton5_Checked(object sender, Windows.UI.Xaml.RoutedEventArgs e)
+ {
+ WriteBoxBackgroundCheck();
+ }
+
+ private void radioButton5_Unchecked(object sender, Windows.UI.Xaml.RoutedEventArgs e)
+ {
+ WriteBoxBackgroundCheck();
+ }
+
+ private void WriteBoxBackgroundCheck()
+ {
+ if (ViewModel.WriteType == ViewModels.CharacteristicPageViewModel.WriteTypes.Hex)
+ {
+ int buf;
+ if ((WriteValue.Text != string.Empty) &&
+ (int.TryParse(WriteValue.Text, System.Globalization.NumberStyles.HexNumber, null, out buf) == false))
+ {
+ WriteValue.Background = new SolidColorBrush(Windows.UI.Colors.Red);
+ }
+ else
+ {
+ WriteValue.Background = new SolidColorBrush(Windows.UI.Colors.White);
+ }
+ }
+ else
+ {
+ WriteValue.Background = new SolidColorBrush(Windows.UI.Colors.White);
+ }
+ }
+ }
+}
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Views/DeviceServicesPage.xaml b/BluetoothLEExplorer/BluetoothLEExplorer/Views/DeviceServicesPage.xaml
new file mode 100644
index 0000000..c1ddbd1
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorer/Views/DeviceServicesPage.xaml
@@ -0,0 +1,153 @@
+
+
+
+
+
+
+
+
+
+ Collapsed
+
+
+ Visible
+
+
+
+
+
+ True
+
+
+ Visible
+
+
+ Collapsed
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Views/DeviceServicesPage.xaml.cs b/BluetoothLEExplorer/BluetoothLEExplorer/Views/DeviceServicesPage.xaml.cs
new file mode 100644
index 0000000..1fde4f7
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorer/Views/DeviceServicesPage.xaml.cs
@@ -0,0 +1,35 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------------------------
+using BluetoothLEExplorer.Models;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Navigation;
+
+namespace BluetoothLEExplorer.Views
+{
+ ///
+ /// Device Service page
+ ///
+ public sealed partial class DeviceServicesPage : Page
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public DeviceServicesPage()
+ {
+ InitializeComponent();
+ NavigationCacheMode = NavigationCacheMode.Disabled;
+ }
+
+ ///
+ /// Updates the view model with the just selected characteristic
+ ///
+ ///
+ ///
+ private void CharacteristicsListView_ItemClick(object sender, ItemClickEventArgs e)
+ {
+ ViewModel.SelectedCharacteristic = (ObservableGattCharacteristics)e.ClickedItem;
+ }
+ }
+}
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Views/Discover.xaml b/BluetoothLEExplorer/BluetoothLEExplorer/Views/Discover.xaml
new file mode 100644
index 0000000..0b79cde
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorer/Views/Discover.xaml
@@ -0,0 +1,244 @@
+
+
+
+
+
+
+
+
+
+ True
+
+
+ Visible
+
+
+ Collapsed
+
+
+
+
+
+ False
+
+
+ Visible
+
+
+ Collapsed
+
+
+
+
+
+ True
+
+
+ Stop
+
+
+ Start
+
+
+
+
+
+ 0
+
+
+ Collapsed
+
+
+ Visible
+
+
+
+
+
+ -1
+
+
+ Collapsed
+
+
+ Visible
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ RSSI:
+
+
+ Battery:
+
+
+
+
+
+ Name:
+
+
+ BT Address:
+
+
+ Connected:
+
+
+ Paired:
+
+
+ Service Count:
+
+
+ RSSI:
+
+
+
+
+
+
+
+
+
+
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Views/Discover.xaml.cs b/BluetoothLEExplorer/BluetoothLEExplorer/Views/Discover.xaml.cs
new file mode 100644
index 0000000..1547a97
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorer/Views/Discover.xaml.cs
@@ -0,0 +1,36 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------------------------
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Controls.Primitives;
+
+//// The Blank Page item template is documented at http://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 Discover : Page
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public Discover()
+ {
+ this.InitializeComponent();
+ }
+
+ private void Grid_Holding(object sender, Windows.UI.Xaml.Input.HoldingRoutedEventArgs e)
+ {
+ FlyoutBase.ShowAttachedFlyout(sender as FrameworkElement);
+ }
+
+ private void Grid_RightTapped(object sender, Windows.UI.Xaml.Input.RightTappedRoutedEventArgs e)
+ {
+ FlyoutBase.ShowAttachedFlyout(sender as FrameworkElement);
+ }
+ }
+}
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Views/Services/BatteryServicePage.xaml b/BluetoothLEExplorer/BluetoothLEExplorer/Views/Services/BatteryServicePage.xaml
new file mode 100644
index 0000000..253b025
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorer/Views/Services/BatteryServicePage.xaml
@@ -0,0 +1,117 @@
+
+
+
+
+
+
+
+
+
+ True
+
+
+ Visible
+
+
+ Collapsed
+
+
+
+
+
+ True
+
+
+ False
+
+
+ True
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Views/Services/BatteryServicePage.xaml.cs b/BluetoothLEExplorer/BluetoothLEExplorer/Views/Services/BatteryServicePage.xaml.cs
new file mode 100644
index 0000000..0a1c2f9
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorer/Views/Services/BatteryServicePage.xaml.cs
@@ -0,0 +1,24 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------------------------
+using Windows.UI.Xaml.Controls;
+
+//// The Blank Page item template is documented at http://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 BatteryServicePage : Page
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public BatteryServicePage()
+ {
+ this.InitializeComponent();
+ }
+ }
+}
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Views/Services/BloodPressureServicePage.xaml b/BluetoothLEExplorer/BluetoothLEExplorer/Views/Services/BloodPressureServicePage.xaml
new file mode 100644
index 0000000..23e7cca
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorer/Views/Services/BloodPressureServicePage.xaml
@@ -0,0 +1,114 @@
+
+
+
+
+
+
+
+
+
+ True
+
+
+ Visible
+
+
+ Collapsed
+
+
+
+
+
+ True
+
+
+ False
+
+
+ True
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Views/Services/BloodPressureServicePage.xaml.cs b/BluetoothLEExplorer/BluetoothLEExplorer/Views/Services/BloodPressureServicePage.xaml.cs
new file mode 100644
index 0000000..df9a812
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorer/Views/Services/BloodPressureServicePage.xaml.cs
@@ -0,0 +1,24 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------------------------
+using Windows.UI.Xaml.Controls;
+
+//// The Blank Page item template is documented at http://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 BloodPressureServicePage : Page
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public BloodPressureServicePage()
+ {
+ this.InitializeComponent();
+ }
+ }
+}
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Views/Services/HeartRateServicePage.xaml b/BluetoothLEExplorer/BluetoothLEExplorer/Views/Services/HeartRateServicePage.xaml
new file mode 100644
index 0000000..1b754f0
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorer/Views/Services/HeartRateServicePage.xaml
@@ -0,0 +1,113 @@
+
+
+
+
+
+
+
+
+
+ True
+
+
+ Visible
+
+
+ Collapsed
+
+
+
+
+
+ True
+
+
+ False
+
+
+ True
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Views/Services/HeartRateServicePage.xaml.cs b/BluetoothLEExplorer/BluetoothLEExplorer/Views/Services/HeartRateServicePage.xaml.cs
new file mode 100644
index 0000000..5a0fca6
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorer/Views/Services/HeartRateServicePage.xaml.cs
@@ -0,0 +1,24 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------------------------
+using Windows.UI.Xaml.Controls;
+
+//// The Blank Page item template is documented at http://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 HeartRateServicePage : Page
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public HeartRateServicePage()
+ {
+ this.InitializeComponent();
+ }
+ }
+}
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Views/Services/MicrosoftServicePage.xaml b/BluetoothLEExplorer/BluetoothLEExplorer/Views/Services/MicrosoftServicePage.xaml
new file mode 100644
index 0000000..d37665c
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorer/Views/Services/MicrosoftServicePage.xaml
@@ -0,0 +1,157 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Views/Services/MicrosoftServicePage.xaml.cs b/BluetoothLEExplorer/BluetoothLEExplorer/Views/Services/MicrosoftServicePage.xaml.cs
new file mode 100644
index 0000000..910e260
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorer/Views/Services/MicrosoftServicePage.xaml.cs
@@ -0,0 +1,22 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------------------------
+using Windows.UI.Xaml.Controls;
+
+namespace BluetoothLEExplorer.Views
+{
+ ///
+ /// An empty page that can be used on its own or navigated to within a Frame.
+ ///
+ public sealed partial class MicrosoftServicePage : Page
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public MicrosoftServicePage()
+ {
+ this.InitializeComponent();
+ }
+ }
+}
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Views/SettingsPage.xaml b/BluetoothLEExplorer/BluetoothLEExplorer/Views/SettingsPage.xaml
new file mode 100644
index 0000000..afef735
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorer/Views/SettingsPage.xaml
@@ -0,0 +1,135 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Privacy Statement
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Version
+
+
+
+
+
+
+
+
+
+
+
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Views/SettingsPage.xaml.cs b/BluetoothLEExplorer/BluetoothLEExplorer/Views/SettingsPage.xaml.cs
new file mode 100644
index 0000000..619115f
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorer/Views/SettingsPage.xaml.cs
@@ -0,0 +1,39 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------------------------
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Navigation;
+
+namespace BluetoothLEExplorer.Views
+{
+ ///
+ /// Settings page
+ ///
+ 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;
+ }
+
+ ///
+ /// Executes when navigating to settings page
+ ///
+ ///
+ 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
new file mode 100644
index 0000000..4aa0659
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorer/Views/Shell.xaml
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Views/Shell.xaml.cs b/BluetoothLEExplorer/BluetoothLEExplorer/Views/Shell.xaml.cs
new file mode 100644
index 0000000..32a649d
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorer/Views/Shell.xaml.cs
@@ -0,0 +1,53 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------------------------
+using Template10.Controls;
+using Template10.Services.NavigationService;
+using Windows.UI.Xaml.Controls;
+
+namespace BluetoothLEExplorer.Views
+{
+ ///
+ /// Shell page
+ ///
+ public sealed partial class Shell : Page
+ {
+ ///
+ /// Gets or sets the shell instance
+ ///
+ public static Shell Instance { get; set; }
+
+ ///
+ /// Hamburger menu instance
+ ///
+ public static HamburgerMenu HamburgerMenu => Instance.MyHamburgerMenu;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public Shell()
+ {
+ Instance = this;
+ InitializeComponent();
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ public Shell(INavigationService navigationService) : this()
+ {
+ SetNavigationService(navigationService);
+ }
+
+ ///
+ /// Initializes the navigation service
+ ///
+ ///
+ public void SetNavigationService(INavigationService navigationService)
+ {
+ MyHamburgerMenu.NavigationService = navigationService;
+ }
+ }
+}
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Views/Splash.xaml b/BluetoothLEExplorer/BluetoothLEExplorer/Views/Splash.xaml
new file mode 100644
index 0000000..fe7d705
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorer/Views/Splash.xaml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Views/Splash.xaml.cs b/BluetoothLEExplorer/BluetoothLEExplorer/Views/Splash.xaml.cs
new file mode 100644
index 0000000..f35b7c0
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorer/Views/Splash.xaml.cs
@@ -0,0 +1,51 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------------------------
+using Windows.ApplicationModel.Activation;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+
+namespace BluetoothLEExplorer.Views
+{
+ ///
+ /// Splash screen
+ ///
+ public sealed partial class Splash : UserControl
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ public Splash(SplashScreen splashScreen)
+ {
+ InitializeComponent();
+ Window.Current.SizeChanged += (s, e) => Resize(splashScreen);
+ Resize(splashScreen);
+ }
+
+ ///
+ /// Resizes the splash screen
+ ///
+ ///
+ private void Resize(SplashScreen splashScreen)
+ {
+ if (splashScreen.ImageLocation.Top == 0)
+ {
+ splashImage.Visibility = Visibility.Collapsed;
+ return;
+ }
+ else
+ {
+ rootCanvas.Background = null;
+ splashImage.Visibility = Visibility.Visible;
+ }
+
+ splashImage.Height = splashScreen.ImageLocation.Height;
+ splashImage.Width = splashScreen.ImageLocation.Width;
+ splashImage.SetValue(Canvas.TopProperty, splashScreen.ImageLocation.Top);
+ splashImage.SetValue(Canvas.LeftProperty, splashScreen.ImageLocation.Left);
+ ProgressTransform.TranslateY = splashImage.Height / 2;
+ }
+ }
+}
\ No newline at end of file
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Views/VirtualPeripheralPage.xaml b/BluetoothLEExplorer/BluetoothLEExplorer/Views/VirtualPeripheralPage.xaml
new file mode 100644
index 0000000..41a5ae1
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorer/Views/VirtualPeripheralPage.xaml
@@ -0,0 +1,116 @@
+
+
+
+
+
+
+
+
+ True
+
+
+ Visible
+
+
+ Collapsed
+
+
+
+
+
+ False
+
+
+ Visible
+
+
+ Collapsed
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/BluetoothLEExplorer/BluetoothLEExplorer/Views/VirtualPeripheralPage.xaml.cs b/BluetoothLEExplorer/BluetoothLEExplorer/Views/VirtualPeripheralPage.xaml.cs
new file mode 100644
index 0000000..7f8476e
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorer/Views/VirtualPeripheralPage.xaml.cs
@@ -0,0 +1,24 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------------------------
+using Windows.UI.Xaml.Controls;
+
+//// The Blank Page item template is documented at http://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 VirtualPeripheralPage : Page
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public VirtualPeripheralPage()
+ {
+ this.InitializeComponent();
+ }
+ }
+}
diff --git a/BluetoothLEExplorer/BluetoothLEExplorerUnitTests/Assets/LockScreenLogo.scale-200.png b/BluetoothLEExplorer/BluetoothLEExplorerUnitTests/Assets/LockScreenLogo.scale-200.png
new file mode 100644
index 0000000..735f57a
Binary files /dev/null and b/BluetoothLEExplorer/BluetoothLEExplorerUnitTests/Assets/LockScreenLogo.scale-200.png differ
diff --git a/BluetoothLEExplorer/BluetoothLEExplorerUnitTests/Assets/SplashScreen.scale-200.png b/BluetoothLEExplorer/BluetoothLEExplorerUnitTests/Assets/SplashScreen.scale-200.png
new file mode 100644
index 0000000..023e7f1
Binary files /dev/null and b/BluetoothLEExplorer/BluetoothLEExplorerUnitTests/Assets/SplashScreen.scale-200.png differ
diff --git a/BluetoothLEExplorer/BluetoothLEExplorerUnitTests/Assets/Square150x150Logo.scale-200.png b/BluetoothLEExplorer/BluetoothLEExplorerUnitTests/Assets/Square150x150Logo.scale-200.png
new file mode 100644
index 0000000..af49fec
Binary files /dev/null and b/BluetoothLEExplorer/BluetoothLEExplorerUnitTests/Assets/Square150x150Logo.scale-200.png differ
diff --git a/BluetoothLEExplorer/BluetoothLEExplorerUnitTests/Assets/Square44x44Logo.scale-200.png b/BluetoothLEExplorer/BluetoothLEExplorerUnitTests/Assets/Square44x44Logo.scale-200.png
new file mode 100644
index 0000000..ce342a2
Binary files /dev/null and b/BluetoothLEExplorer/BluetoothLEExplorerUnitTests/Assets/Square44x44Logo.scale-200.png differ
diff --git a/BluetoothLEExplorer/BluetoothLEExplorerUnitTests/Assets/Square44x44Logo.targetsize-24_altform-unplated.png b/BluetoothLEExplorer/BluetoothLEExplorerUnitTests/Assets/Square44x44Logo.targetsize-24_altform-unplated.png
new file mode 100644
index 0000000..f6c02ce
Binary files /dev/null and b/BluetoothLEExplorer/BluetoothLEExplorerUnitTests/Assets/Square44x44Logo.targetsize-24_altform-unplated.png differ
diff --git a/BluetoothLEExplorer/BluetoothLEExplorerUnitTests/Assets/StoreLogo.png b/BluetoothLEExplorer/BluetoothLEExplorerUnitTests/Assets/StoreLogo.png
new file mode 100644
index 0000000..7385b56
Binary files /dev/null and b/BluetoothLEExplorer/BluetoothLEExplorerUnitTests/Assets/StoreLogo.png differ
diff --git a/BluetoothLEExplorer/BluetoothLEExplorerUnitTests/Assets/Wide310x150Logo.scale-200.png b/BluetoothLEExplorer/BluetoothLEExplorerUnitTests/Assets/Wide310x150Logo.scale-200.png
new file mode 100644
index 0000000..288995b
Binary files /dev/null and b/BluetoothLEExplorer/BluetoothLEExplorerUnitTests/Assets/Wide310x150Logo.scale-200.png differ
diff --git a/BluetoothLEExplorer/BluetoothLEExplorerUnitTests/BluetoothLEExplorerUnitTests.csproj b/BluetoothLEExplorer/BluetoothLEExplorerUnitTests/BluetoothLEExplorerUnitTests.csproj
new file mode 100644
index 0000000..34138b8
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorerUnitTests/BluetoothLEExplorerUnitTests.csproj
@@ -0,0 +1,149 @@
+
+
+
+
+ Debug
+ x86
+ {6F503DF9-71C9-4340-90BB-D9AA14ADB686}
+ AppContainerExe
+ Properties
+ BluetoothLEExplorerUnitTests
+ BluetoothLEExplorerUnitTests
+ en-US
+ UAP
+ 10.0.15063.0
+ 10.0.15063.0
+ 14
+ 512
+ {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ $(VisualStudioVersion)
+
+
+ true
+ UnitTest\bin\x86\Debug\
+ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
+ ;2008
+ full
+ x86
+ false
+ prompt
+ true
+
+
+ UnitTest\bin\x86\Release\
+ TRACE;NETFX_CORE;WINDOWS_UWP
+ true
+ ;2008
+ pdbonly
+ x86
+ false
+ prompt
+ true
+ true
+
+
+ true
+ UnitTest\bin\ARM\Debug\
+ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
+ ;2008
+ full
+ ARM
+ false
+ prompt
+ true
+
+
+ UnitTest\bin\ARM\Release\
+ TRACE;NETFX_CORE;WINDOWS_UWP
+ true
+ ;2008
+ pdbonly
+ ARM
+ false
+ prompt
+ true
+ true
+
+
+ true
+ UnitTest\bin\x64\Debug\
+ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
+ ;2008
+ full
+ x64
+ false
+ prompt
+ true
+
+
+ UnitTest\bin\x64\Release\
+ TRACE;NETFX_CORE;WINDOWS_UWP
+ true
+ ;2008
+ pdbonly
+ x64
+ false
+ prompt
+ true
+ true
+
+
+ PackageReference
+
+
+
+
+
+
+
+ UnitTestApp.xaml
+
+
+
+
+
+ MSBuild:Compile
+ Designer
+
+
+
+
+ Designer
+
+
+
+
+
+
+
+
+ 5.3.3
+
+
+ 1.1.17
+
+
+ 1.1.17
+
+
+
+
+
+
+
+ {37c63fdd-d995-4cc2-b014-4fb323194001}
+ GattHelper
+
+
+
+ 14.0
+
+
+
+
\ No newline at end of file
diff --git a/BluetoothLEExplorer/BluetoothLEExplorerUnitTests/Package.appxmanifest b/BluetoothLEExplorer/BluetoothLEExplorerUnitTests/Package.appxmanifest
new file mode 100644
index 0000000..5c4ed8c
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorerUnitTests/Package.appxmanifest
@@ -0,0 +1,28 @@
+
+
+
+
+
+ BluetoothLEExplorerUnitTests
+ stfro
+ Assets\StoreLogo.png
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/BluetoothLEExplorer/BluetoothLEExplorerUnitTests/Properties/AssemblyInfo.cs b/BluetoothLEExplorer/BluetoothLEExplorerUnitTests/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..b225cc9
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorerUnitTests/Properties/AssemblyInfo.cs
@@ -0,0 +1,18 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+[assembly: AssemblyTitle("BluetoothLEExplorerUnitTests")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("BluetoothLEExplorerUnitTests")]
+[assembly: AssemblyCopyright("Copyright © 2017")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+[assembly: AssemblyMetadata("TargetPlatform","UAP")]
+
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
+[assembly: ComVisible(false)]
\ No newline at end of file
diff --git a/BluetoothLEExplorer/BluetoothLEExplorerUnitTests/Properties/UnitTestApp.rd.xml b/BluetoothLEExplorer/BluetoothLEExplorerUnitTests/Properties/UnitTestApp.rd.xml
new file mode 100644
index 0000000..b3a104b
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorerUnitTests/Properties/UnitTestApp.rd.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/BluetoothLEExplorer/BluetoothLEExplorerUnitTests/UnitTest.cs b/BluetoothLEExplorer/BluetoothLEExplorerUnitTests/UnitTest.cs
new file mode 100644
index 0000000..6532dd7
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorerUnitTests/UnitTest.cs
@@ -0,0 +1,187 @@
+
+using System;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Windows.Storage.Streams;
+using System.Text;
+using Windows.Security.Cryptography;
+using System.Collections;
+using GattHelper.Converters;
+
+namespace BluetoothLEExplorer.UnitTest
+{
+ [TestClass]
+ public class UnitTest1
+ {
+ [TestMethod]
+ public void StringTest1()
+ {
+ string testString = "Hello World";
+
+ DataWriter writer = new DataWriter();
+ writer.WriteString(testString);
+ IBuffer testBuffer = writer.DetachBuffer();
+
+ Assert.AreEqual(testString, GattConvert.ToUTF8String(testBuffer));
+ Assert.AreNotEqual("Goodbye", GattConvert.ToUTF8String(testBuffer));
+ }
+
+ [TestMethod]
+ public void IntTest1()
+ {
+ int data = 42;
+
+ IBuffer buf = GattConvert.ToIBuffer(data);
+
+ Assert.AreEqual(data, GattConvert.ToInt32(buf));
+ Assert.AreNotEqual(43, GattConvert.ToInt32(buf));
+ }
+
+
+ [TestMethod]
+ public void IntTest2()
+ {
+ byte[] data = { 42, 0 };
+ DataWriter writer = new DataWriter();
+ writer.ByteOrder = ByteOrder.LittleEndian;
+ writer.WriteBytes(data);
+
+ IBuffer result = writer.DetachBuffer();
+
+ Assert.AreEqual(42, GattConvert.ToInt32(result));
+ Assert.AreNotEqual(43, GattConvert.ToInt32(result));
+ }
+
+ [TestMethod]
+ public void IntTest3()
+ {
+ Int16 data = 42;
+ DataWriter writer = new DataWriter();
+ writer.ByteOrder = ByteOrder.LittleEndian;
+ writer.WriteInt16(data);
+
+ IBuffer result = writer.DetachBuffer();
+
+ Assert.AreEqual(42, GattConvert.ToInt32(result));
+ Assert.AreNotEqual(43, GattConvert.ToInt32(result));
+ }
+
+ [TestMethod]
+ public void UInt16Test()
+ {
+ UInt16 data = 42;
+
+ Assert.AreEqual(data, GattConvert.ToUInt16(GattConvert.ToIBuffer(data)));
+ Assert.AreNotEqual(data - 1, GattConvert.ToUInt16(GattConvert.ToIBuffer(data)));
+ }
+
+ [TestMethod]
+ public void UInt32Test()
+ {
+ UInt32 data = 42;
+
+ Assert.AreEqual(data, GattConvert.ToUInt32(GattConvert.ToIBuffer(data)));
+ Assert.AreNotEqual(data - 1, GattConvert.ToUInt32(GattConvert.ToIBuffer(data)));
+ }
+
+ [TestMethod]
+ public void UInt64Test()
+ {
+ UInt64 data = 42;
+
+ Assert.AreEqual(data, GattConvert.ToUInt16(GattConvert.ToIBuffer(data)));
+ Assert.AreNotEqual(data - 1, GattConvert.ToUInt64(GattConvert.ToIBuffer(data)));
+ }
+
+ [TestMethod]
+ public void Int16Test()
+ {
+ Int16 data = 42;
+ Int64 wrong = Int64.MaxValue;
+
+ Assert.AreEqual(data, GattConvert.ToInt32(GattConvert.ToIBuffer(data)));
+ Assert.AreNotEqual(data - 1, GattConvert.ToInt16(GattConvert.ToIBuffer(data)));
+
+ Assert.AreNotEqual(wrong, GattConvert.ToInt16(GattConvert.ToIBuffer(wrong)));
+ }
+
+ [TestMethod]
+ public void Int16Test2()
+ {
+ byte[] input = { 0, 42, 0, 42, 0, 42, 0 };
+ byte[] expected = { 0, 42 };
+
+ Assert.AreEqual(BitConverter.ToInt16(expected, 0), GattConvert.ToInt16(GattConvert.ToIBuffer(input)));
+ }
+ [TestMethod]
+ public void Int32Test()
+ {
+ Int32 data = 42;
+
+ Assert.AreEqual(data, GattConvert.ToInt32(GattConvert.ToIBuffer(data)));
+ Assert.AreNotEqual(data - 1, GattConvert.ToInt32(GattConvert.ToIBuffer(data)));
+ }
+
+ [TestMethod]
+ public void Int64Test()
+ {
+ Int64 data = 42;
+
+ Assert.AreEqual(data, GattConvert.ToInt64(GattConvert.ToIBuffer(data)));
+ Assert.AreNotEqual(data - 1, GattConvert.ToInt64(GattConvert.ToIBuffer(data)));
+ }
+
+ [TestMethod]
+ public void Int64Test2()
+ {
+ Int16 data = 42;
+
+ Assert.IsTrue(data == GattConvert.ToInt64(GattConvert.ToIBuffer(data)));
+ Assert.IsFalse((data - 1) == GattConvert.ToInt64(GattConvert.ToIBuffer(data)));
+ }
+
+ [TestMethod]
+ public void HexTest1()
+ {
+ byte[] data = { 0, 1, 2, 42 };
+ DataWriter writer = new DataWriter();
+ writer.WriteBytes(data);
+
+ IBuffer result = writer.DetachBuffer();
+
+ Assert.AreEqual("00-01-02-2A", GattConvert.ToHexString(result));
+ Assert.AreNotEqual("00-00-00-00", GattConvert.ToHexString(result));
+ }
+
+ [TestMethod]
+ public void HexTest2()
+ {
+ byte[] data = { 0, 1, 2, 42 };
+ byte[] incorrect = { 0, 0, 0, 0 };
+ DataWriter writer = new DataWriter();
+ writer.WriteBytes(data);
+ IBuffer dataIBuffer = writer.DetachBuffer();
+
+ IBuffer resultIBuffer = GattConvert.ToIBufferFromHexString(GattConvert.ToHexString(dataIBuffer));
+ byte[] result;
+ CryptographicBuffer.CopyToByteArray(resultIBuffer, out result);
+
+ Assert.IsTrue(StructuralComparisons.StructuralEqualityComparer.Equals(data, result));
+ Assert.IsFalse(StructuralComparisons.StructuralEqualityComparer.Equals(incorrect, result));
+ }
+
+ [TestMethod]
+ public void IBufferTest1()
+ {
+ byte[] correct = { 0x48, 0x65, 0x6C, 0x6C, 0x6F };
+ byte[] incorrect = { 0x42, 0x42, 0x42, 0x42, 0x42 };
+
+ byte[] result;
+ CryptographicBuffer.CopyToByteArray(GattConvert.ToIBuffer("Hello"), out result);
+
+ Assert.IsTrue(StructuralComparisons.StructuralEqualityComparer.Equals(correct, result));
+ Assert.IsFalse(StructuralComparisons.StructuralEqualityComparer.Equals(incorrect, result));
+
+ }
+
+ }
+}
diff --git a/BluetoothLEExplorer/BluetoothLEExplorerUnitTests/UnitTestApp.xaml b/BluetoothLEExplorer/BluetoothLEExplorerUnitTests/UnitTestApp.xaml
new file mode 100644
index 0000000..13311b7
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorerUnitTests/UnitTestApp.xaml
@@ -0,0 +1,8 @@
+
+
+
diff --git a/BluetoothLEExplorer/BluetoothLEExplorerUnitTests/UnitTestApp.xaml.cs b/BluetoothLEExplorer/BluetoothLEExplorerUnitTests/UnitTestApp.xaml.cs
new file mode 100644
index 0000000..7e82c57
--- /dev/null
+++ b/BluetoothLEExplorer/BluetoothLEExplorerUnitTests/UnitTestApp.xaml.cs
@@ -0,0 +1,102 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices.WindowsRuntime;
+using Windows.ApplicationModel;
+using Windows.ApplicationModel.Activation;
+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;
+
+namespace BluetoothLEExplorerUnitTests
+{
+ ///
+ /// Provides application-specific behavior to supplement the default Application class.
+ ///
+ sealed partial class App : Application
+ {
+ ///
+ /// Initializes the singleton application object. This is the first line of authored code
+ /// executed, and as such is the logical equivalent of main() or WinMain().
+ ///
+ public App()
+ {
+ this.InitializeComponent();
+ this.Suspending += OnSuspending;
+ }
+
+ ///
+ /// Invoked when the application is launched normally by the end user. Other entry points
+ /// will be used such as when the application is launched to open a specific file.
+ ///
+ /// Details about the launch request and process.
+ protected override void OnLaunched(LaunchActivatedEventArgs e)
+ {
+
+#if DEBUG
+ if (System.Diagnostics.Debugger.IsAttached)
+ {
+ this.DebugSettings.EnableFrameRateCounter = true;
+ }
+#endif
+
+ Frame rootFrame = Window.Current.Content as Frame;
+
+ // Do not repeat app initialization when the Window already has content,
+ // just ensure that the window is active
+ if (rootFrame == null)
+ {
+ // Create a Frame to act as the navigation context and navigate to the first page
+ rootFrame = new Frame();
+
+ rootFrame.NavigationFailed += OnNavigationFailed;
+
+ if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
+ {
+ //TODO: Load state from previously suspended application
+ }
+
+ // Place the frame in the current Window
+ Window.Current.Content = rootFrame;
+ }
+
+ Microsoft.VisualStudio.TestPlatform.TestExecutor.UnitTestClient.CreateDefaultUI();
+
+ // Ensure the current window is active
+ Window.Current.Activate();
+
+ Microsoft.VisualStudio.TestPlatform.TestExecutor.UnitTestClient.Run(e.Arguments);
+ }
+
+ ///
+ /// Invoked when Navigation to a certain page fails
+ ///
+ /// The Frame which failed navigation
+ /// Details about the navigation failure
+ void OnNavigationFailed(object sender, NavigationFailedEventArgs e)
+ {
+ throw new Exception("Failed to load Page " + e.SourcePageType.FullName);
+ }
+
+ ///
+ /// Invoked when application execution is being suspended. Application state is saved
+ /// without knowing whether the application will be terminated or resumed with the contents
+ /// of memory still intact.
+ ///
+ /// The source of the suspend request.
+ /// Details about the suspend request.
+ private void OnSuspending(object sender, SuspendingEventArgs e)
+ {
+ var deferral = e.SuspendingOperation.GetDeferral();
+ //TODO: Save application state and stop any background activity
+ deferral.Complete();
+ }
+ }
+}
diff --git a/BluetoothLEExplorer/DesignModel/DesignModel.modelproj b/BluetoothLEExplorer/DesignModel/DesignModel.modelproj
new file mode 100644
index 0000000..f67b8cc
--- /dev/null
+++ b/BluetoothLEExplorer/DesignModel/DesignModel.modelproj
@@ -0,0 +1,30 @@
+
+
+
+
+ Debug
+ AnyCPU
+ 2.0
+ {11b48755-a5f4-4640-9c9d-1176ff71f0e8}
+ 2.0.0.0
+ DesignModel
+ DesignModel
+
+
+ bin\Debug\
+
+
+ bin\Release\
+
+
+ 10.0
+ $(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)
+
+
+
+
+
+ Content
+
+
+
\ No newline at end of file
diff --git a/BluetoothLEExplorer/DesignModel/ModelDefinition/DesignModel.uml b/BluetoothLEExplorer/DesignModel/ModelDefinition/DesignModel.uml
new file mode 100644
index 0000000..c270a75
--- /dev/null
+++ b/BluetoothLEExplorer/DesignModel/ModelDefinition/DesignModel.uml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/BluetoothLEExplorer/GattHelper/Converters/GattConvert.cs b/BluetoothLEExplorer/GattHelper/Converters/GattConvert.cs
new file mode 100644
index 0000000..7fb4c67
--- /dev/null
+++ b/BluetoothLEExplorer/GattHelper/Converters/GattConvert.cs
@@ -0,0 +1,256 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Windows.Security.Cryptography;
+using Windows.Storage.Streams;
+
+namespace GattHelper.Converters
+{
+ public static class GattConvert
+ {
+ public static IBuffer ToIBufferFromHexString(string data)
+ {
+ data = data.Replace("-", "");
+
+ 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);
+ }
+
+ DataWriter writer = new DataWriter();
+ writer.WriteBytes(bytes);
+ return writer.DetachBuffer();
+ }
+
+ public static IBuffer ToIBuffer(bool data)
+ {
+ DataWriter writer = new DataWriter();
+ writer.ByteOrder = ByteOrder.LittleEndian;
+ writer.WriteBoolean(data);
+ return writer.DetachBuffer();
+ }
+
+ public static IBuffer ToIBuffer(byte data)
+ {
+ DataWriter writer = new DataWriter();
+ writer.ByteOrder = ByteOrder.LittleEndian;
+ writer.WriteByte(data);
+ return writer.DetachBuffer();
+ }
+
+ public static IBuffer ToIBuffer(byte[] data)
+ {
+ DataWriter writer = new DataWriter();
+ writer.ByteOrder = ByteOrder.LittleEndian;
+ writer.WriteBytes(data);
+ return writer.DetachBuffer();
+ }
+
+ public static IBuffer ToIBuffer(double data)
+ {
+ DataWriter writer = new DataWriter();
+ writer.ByteOrder = ByteOrder.LittleEndian;
+ writer.WriteDouble(data);
+ return writer.DetachBuffer();
+ }
+
+ public static IBuffer ToIBuffer(Int16 data)
+ {
+ DataWriter writer = new DataWriter();
+ writer.ByteOrder = ByteOrder.LittleEndian;
+ writer.WriteInt16(data);
+ return writer.DetachBuffer();
+ }
+
+ public static IBuffer ToIBuffer(Int32 data)
+ {
+ DataWriter writer = new DataWriter();
+ writer.ByteOrder = ByteOrder.LittleEndian;
+ writer.WriteInt32(data);
+ return writer.DetachBuffer();
+ }
+
+ public static IBuffer ToIBuffer(Int64 data)
+ {
+ DataWriter writer = new DataWriter();
+ writer.ByteOrder = ByteOrder.LittleEndian;
+ writer.WriteInt64(data);
+ return writer.DetachBuffer();
+ }
+
+ public static IBuffer ToIBuffer(Single data)
+ {
+ DataWriter writer = new DataWriter();
+ writer.ByteOrder = ByteOrder.LittleEndian;
+ writer.WriteSingle(data);
+ return writer.DetachBuffer();
+ }
+
+ public static IBuffer ToIBuffer(UInt16 data)
+ {
+ DataWriter writer = new DataWriter();
+ writer.ByteOrder = ByteOrder.LittleEndian;
+ writer.WriteUInt16(data);
+ return writer.DetachBuffer();
+ }
+
+ public static IBuffer ToIBuffer(UInt32 data)
+ {
+ DataWriter writer = new DataWriter();
+ writer.ByteOrder = ByteOrder.LittleEndian;
+ writer.WriteUInt32(data);
+ return writer.DetachBuffer();
+ }
+
+ public static IBuffer ToIBuffer(UInt64 data)
+ {
+ DataWriter writer = new DataWriter();
+ writer.ByteOrder = ByteOrder.LittleEndian;
+ writer.WriteUInt64(data);
+ return writer.DetachBuffer();
+ }
+
+ public static IBuffer ToIBuffer(string data)
+ {
+ DataWriter writer = new DataWriter();
+ writer.ByteOrder = ByteOrder.LittleEndian;
+ writer.WriteString(data);
+ return writer.DetachBuffer();
+ }
+
+ public static string ToUTF8String(IBuffer buffer)
+ {
+ DataReader reader = DataReader.FromBuffer(buffer);
+ reader.UnicodeEncoding = Windows.Storage.Streams.UnicodeEncoding.Utf8;
+ reader.ByteOrder = Windows.Storage.Streams.ByteOrder.LittleEndian;
+ return reader.ReadString(buffer.Length);
+ }
+
+ public static string ToUTF16String(IBuffer buffer)
+ {
+ DataReader reader = DataReader.FromBuffer(buffer);
+ reader.UnicodeEncoding = Windows.Storage.Streams.UnicodeEncoding.Utf16LE;
+ reader.ByteOrder = Windows.Storage.Streams.ByteOrder.LittleEndian;
+ return reader.ReadString(buffer.Length);
+ }
+
+ public static Int16 ToInt16(IBuffer buffer)
+ {
+ byte[] data = new byte[buffer.Length];
+ DataReader reader = DataReader.FromBuffer(buffer);
+ reader.ByteOrder = ByteOrder.LittleEndian;
+ reader.ReadBytes(data);
+ data = PadBytes(data, 2);
+ return BitConverter.ToInt16(data, 0);
+ }
+
+ public static int ToInt32(IBuffer buffer)
+ {
+ byte[] data = new byte[buffer.Length];
+ DataReader reader = DataReader.FromBuffer(buffer);
+ reader.ByteOrder = ByteOrder.LittleEndian;
+ reader.ReadBytes(data);
+ data = PadBytes(data, 4);
+ return BitConverter.ToInt32(data, 0);
+ }
+
+ public static Int64 ToInt64(IBuffer buffer)
+ {
+ byte[] data = new byte[buffer.Length];
+ DataReader reader = DataReader.FromBuffer(buffer);
+ reader.ByteOrder = ByteOrder.LittleEndian;
+ reader.ReadBytes(data);
+ data = PadBytes(data, 8);
+ return BitConverter.ToInt32(data, 0);
+ }
+
+ public static Single ToSingle(IBuffer buffer)
+ {
+ byte[] data = new byte[buffer.Length];
+ DataReader reader = DataReader.FromBuffer(buffer);
+ reader.ByteOrder = ByteOrder.LittleEndian;
+ reader.ReadBytes(data);
+ data = PadBytes(data, 4);
+ return BitConverter.ToSingle(data, 0);
+ }
+
+ public static Double ToDouble(IBuffer buffer)
+ {
+ byte[] data = new byte[buffer.Length];
+ DataReader reader = DataReader.FromBuffer(buffer);
+ reader.ByteOrder = ByteOrder.LittleEndian;
+ reader.ReadBytes(data);
+ data = PadBytes(data, 8);
+ return BitConverter.ToDouble(data, 0);
+ }
+
+ public static UInt16 ToUInt16(IBuffer buffer)
+ {
+ byte[] data = new byte[buffer.Length];
+ DataReader reader = DataReader.FromBuffer(buffer);
+ reader.ByteOrder = ByteOrder.LittleEndian;
+ reader.ReadBytes(data);
+ data = PadBytes(data, 2);
+ return BitConverter.ToUInt16(data, 0);
+ }
+
+ public static UInt32 ToUInt32(IBuffer buffer)
+ {
+ byte[] data = new byte[buffer.Length];
+ DataReader reader = DataReader.FromBuffer(buffer);
+ reader.ByteOrder = ByteOrder.LittleEndian;
+ reader.ReadBytes(data);
+ data = PadBytes(data, 4);
+ return BitConverter.ToUInt32(data, 0);
+ }
+
+ public static UInt64 ToUInt64(IBuffer buffer)
+ {
+ byte[] data = new byte[buffer.Length];
+ DataReader reader = DataReader.FromBuffer(buffer);
+ reader.ByteOrder = ByteOrder.LittleEndian;
+ reader.ReadBytes(data);
+ data = PadBytes(data, 8);
+ return BitConverter.ToUInt64(data, 0);
+ }
+
+ public static byte[] ToByteArray(IBuffer buffer)
+ {
+ byte[] data = new byte[buffer.Length];
+ DataReader reader = DataReader.FromBuffer(buffer);
+ reader.ByteOrder = ByteOrder.LittleEndian;
+ reader.ReadBytes(data);
+ return data;
+ }
+
+ public static string ToHexString(IBuffer buffer)
+ {
+ byte[] data;
+ CryptographicBuffer.CopyToByteArray(buffer, out data);
+ return BitConverter.ToString(data);
+ }
+
+ ///
+ /// Takes an input array of bytes and returns an array with more zeros in the front
+ ///
+ ///
+ ///
+ /// A byte array with more zeros in back per little endianness"/>
+ private static byte[] PadBytes(byte[] input, int length)
+ {
+ if (input.Length >= length)
+ {
+ return input;
+ }
+
+ byte[] ret = new byte[length];
+ Array.Copy(input, ret, input.Length);
+ return ret;
+ }
+ }
+}
diff --git a/BluetoothLEExplorer/GattHelper/GattHelper.csproj b/BluetoothLEExplorer/GattHelper/GattHelper.csproj
new file mode 100644
index 0000000..48bdfc3
--- /dev/null
+++ b/BluetoothLEExplorer/GattHelper/GattHelper.csproj
@@ -0,0 +1,132 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {37C63FDD-D995-4CC2-B014-4FB323194001}
+ Library
+ Properties
+ GattHelper
+ GattHelper
+ en-US
+ UAP
+ 10.0.15063.0
+ 10.0.15063.0
+ 14
+ 512
+ {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
+ prompt
+ 4
+
+
+ AnyCPU
+ pdbonly
+ true
+ bin\Release\
+ TRACE;NETFX_CORE;WINDOWS_UWP
+ prompt
+ 4
+
+
+ x86
+ true
+ bin\x86\Debug\
+ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
+ ;2008
+ full
+ x86
+ false
+ prompt
+
+
+ x86
+ bin\x86\Release\
+ TRACE;NETFX_CORE;WINDOWS_UWP
+ true
+ ;2008
+ pdbonly
+ x86
+ false
+ prompt
+
+
+ ARM
+ true
+ bin\ARM\Debug\
+ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
+ ;2008
+ full
+ ARM
+ false
+ prompt
+
+
+ ARM
+ bin\ARM\Release\
+ TRACE;NETFX_CORE;WINDOWS_UWP
+ true
+ ;2008
+ pdbonly
+ ARM
+ false
+ prompt
+
+
+ x64
+ true
+ bin\x64\Debug\
+ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
+ ;2008
+ full
+ x64
+ false
+ prompt
+
+
+ x64
+ bin\x64\Release\
+ TRACE;NETFX_CORE;WINDOWS_UWP
+ true
+ ;2008
+ pdbonly
+ x64
+ false
+ prompt
+
+
+ PackageReference
+
+
+
+
+
+
+
+
+ 5.3.3
+
+
+
+
+
+
+ 14.0
+
+
+
+
\ No newline at end of file
diff --git a/BluetoothLEExplorer/GattHelper/Properties/AssemblyInfo.cs b/BluetoothLEExplorer/GattHelper/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..d1f277e
--- /dev/null
+++ b/BluetoothLEExplorer/GattHelper/Properties/AssemblyInfo.cs
@@ -0,0 +1,29 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("GattHelper")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("GattHelper")]
+[assembly: AssemblyCopyright("Copyright © 2017")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
+[assembly: ComVisible(false)]
\ No newline at end of file
diff --git a/BluetoothLEExplorer/GattHelper/Properties/GattHelper.rd.xml b/BluetoothLEExplorer/GattHelper/Properties/GattHelper.rd.xml
new file mode 100644
index 0000000..65a96b4
--- /dev/null
+++ b/BluetoothLEExplorer/GattHelper/Properties/GattHelper.rd.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
diff --git a/BluetoothLEExplorer/GattHelper/project.json b/BluetoothLEExplorer/GattHelper/project.json
new file mode 100644
index 0000000..ec51351
--- /dev/null
+++ b/BluetoothLEExplorer/GattHelper/project.json
@@ -0,0 +1,16 @@
+{
+ "dependencies": {
+ "Microsoft.NETCore.UniversalWindowsPlatform": "5.3.3"
+ },
+ "frameworks": {
+ "uap10.0": {}
+ },
+ "runtimes": {
+ "win10-arm": {},
+ "win10-arm-aot": {},
+ "win10-x86": {},
+ "win10-x86-aot": {},
+ "win10-x64": {},
+ "win10-x64-aot": {}
+ }
+}
\ No newline at end of file
diff --git a/GattServicesLibrary/GattServicesLibrary.sln b/GattServicesLibrary/GattServicesLibrary.sln
new file mode 100644
index 0000000..87aaf94
--- /dev/null
+++ b/GattServicesLibrary/GattServicesLibrary.sln
@@ -0,0 +1,40 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 14
+VisualStudioVersion = 14.0.25420.1
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GattServicesLibrary", "GattServicesLibrary\GattServicesLibrary.csproj", "{644F40AD-EABB-4570-B9B7-E3F8DDAA80F3}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|ARM = Debug|ARM
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|ARM = Release|ARM
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {644F40AD-EABB-4570-B9B7-E3F8DDAA80F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {644F40AD-EABB-4570-B9B7-E3F8DDAA80F3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {644F40AD-EABB-4570-B9B7-E3F8DDAA80F3}.Debug|ARM.ActiveCfg = Debug|ARM
+ {644F40AD-EABB-4570-B9B7-E3F8DDAA80F3}.Debug|ARM.Build.0 = Debug|ARM
+ {644F40AD-EABB-4570-B9B7-E3F8DDAA80F3}.Debug|x64.ActiveCfg = Debug|x64
+ {644F40AD-EABB-4570-B9B7-E3F8DDAA80F3}.Debug|x64.Build.0 = Debug|x64
+ {644F40AD-EABB-4570-B9B7-E3F8DDAA80F3}.Debug|x86.ActiveCfg = Debug|x86
+ {644F40AD-EABB-4570-B9B7-E3F8DDAA80F3}.Debug|x86.Build.0 = Debug|x86
+ {644F40AD-EABB-4570-B9B7-E3F8DDAA80F3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {644F40AD-EABB-4570-B9B7-E3F8DDAA80F3}.Release|Any CPU.Build.0 = Release|Any CPU
+ {644F40AD-EABB-4570-B9B7-E3F8DDAA80F3}.Release|ARM.ActiveCfg = Release|ARM
+ {644F40AD-EABB-4570-B9B7-E3F8DDAA80F3}.Release|ARM.Build.0 = Release|ARM
+ {644F40AD-EABB-4570-B9B7-E3F8DDAA80F3}.Release|x64.ActiveCfg = Release|x64
+ {644F40AD-EABB-4570-B9B7-E3F8DDAA80F3}.Release|x64.Build.0 = Release|x64
+ {644F40AD-EABB-4570-B9B7-E3F8DDAA80F3}.Release|x86.ActiveCfg = Release|x86
+ {644F40AD-EABB-4570-B9B7-E3F8DDAA80F3}.Release|x86.Build.0 = Release|x86
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/GattServicesLibrary/GattServicesLibrary/CharacteristicParameterValues/CharacteristicParameterValueEnums.cs b/GattServicesLibrary/GattServicesLibrary/CharacteristicParameterValues/CharacteristicParameterValueEnums.cs
new file mode 100644
index 0000000..350f73f
--- /dev/null
+++ b/GattServicesLibrary/GattServicesLibrary/CharacteristicParameterValues/CharacteristicParameterValueEnums.cs
@@ -0,0 +1,49 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------------------------
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+[module: System.Diagnostics.CodeAnalysis.SuppressMessage(
+ "StyleCop.CSharp.DocumentationRules",
+ "SA1649:FileHeaderFileNameDocumentationMustMatchTypeName",
+ Justification = "This is a enum only file")]
+
+namespace GattServicesLibrary.CharacteristicParameterValues
+{
+ ///
+ /// Bluetooth sig: https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.alert_category_id_bit_mask.xml
+ ///
+ [Flags]
+ public enum AlertCategoryID : byte
+ {
+ None = 0, // 0b00000000
+ SimpleAlert = 1, // 0b00000001
+ Email = 2, // 0b00000010
+ News = 4, // 0b00000100
+ Call = 8, // 0b00001000
+ MissedCall = 16, // 0b00010000
+ SMS_MMS = 32, // 0b00100000
+ VoiceMail = 64, // 0b01000000
+ Schedule = 128, // 0b10000000
+ }
+
+ ///
+ /// Bluetooth sig: https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.blood_pressure_feature.xml
+ ///
+ public enum BloodPressureFeatures : byte
+ {
+ None = 0, // 0b00000000
+ BodyMovementDetection = 1, // 0b00000001
+ CuffFitDetection = 2, // 0b00000010
+ IrregularPulseDetection = 4, // 0b00000100
+ PulseRateRangeDetection = 8, // 0b00001000
+ MeasurementPositionDetection = 16, // 0b00010000
+ MultipleBondSupport = 32 // 0b00010000
+ }
+}
diff --git a/GattServicesLibrary/GattServicesLibrary/Characteristics/BatteryLevelCharacteristic.cs b/GattServicesLibrary/GattServicesLibrary/Characteristics/BatteryLevelCharacteristic.cs
new file mode 100644
index 0000000..f789239
--- /dev/null
+++ b/GattServicesLibrary/GattServicesLibrary/Characteristics/BatteryLevelCharacteristic.cs
@@ -0,0 +1,63 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------------------------
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using GattServicesLibrary.Helpers;
+using Windows.Devices.Bluetooth.GenericAttributeProfile;
+using Windows.Devices.Power;
+using Windows.Storage.Streams;
+
+namespace GattServicesLibrary.Characteristics
+{
+ ///
+ /// Implementation of the battery profile
+ ///
+ public class BatteryLevelCharacteristic : GenericGattCharacteristic
+ {
+ ///
+ /// Access to the battery of this system
+ ///
+ private Battery aggBattery = Battery.AggregateBattery;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The characteristic that this wraps
+ public BatteryLevelCharacteristic(GattLocalCharacteristic characteristic, GenericGattService service) : base(characteristic, service)
+ {
+ aggBattery.ReportUpdated += AggBattery_ReportUpdated;
+ UpdateBatteryValue();
+ }
+
+ ///
+ /// Callback when the battery level changes
+ ///
+ ///
+ ///
+ private void AggBattery_ReportUpdated(Battery sender, object args)
+ {
+ UpdateBatteryValue();
+ NotifyValue();
+ }
+
+ ///
+ /// Method that updates with the current battery level
+ ///
+ private void UpdateBatteryValue()
+ {
+ // Get report
+ BatteryReport report = aggBattery.GetReport();
+ float fullCharge = Convert.ToSingle(report.FullChargeCapacityInMilliwattHours);
+ float currentCharge = Convert.ToSingle(report.RemainingCapacityInMilliwattHours);
+
+ float val = (fullCharge > 0) ? (currentCharge / fullCharge) * 100.0f : 0.0f;
+
+ Value = GattHelper.Converters.GattConvert.ToIBuffer((byte)Math.Round(val, 0));
+ }
+ }
+}
diff --git a/GattServicesLibrary/GattServicesLibrary/Characteristics/BloodPressureFeatureCharacteristic.cs b/GattServicesLibrary/GattServicesLibrary/Characteristics/BloodPressureFeatureCharacteristic.cs
new file mode 100644
index 0000000..7de967d
--- /dev/null
+++ b/GattServicesLibrary/GattServicesLibrary/Characteristics/BloodPressureFeatureCharacteristic.cs
@@ -0,0 +1,35 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using GattServicesLibrary.Helpers;
+using Windows.Devices.Bluetooth.GenericAttributeProfile;
+using Windows.Devices.Power;
+using Windows.Storage.Streams;
+using GattHelper.Converters;
+
+namespace GattServicesLibrary.Characteristics
+{
+ ///
+ /// Microsoft boilerplate characteristic that supports 'Indicate' provided for completeness. This service is almost identical to MicrosoftNotifyCharacteristic.
+ ///
+ public class BloodPressureFeatureCharacteristic : GenericGattCharacteristic
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Characteristic this wraps
+ public BloodPressureFeatureCharacteristic(GattLocalCharacteristic characteristic, GenericGattService service) : base(characteristic, service)
+ {
+ // Supports no extra features - required per spec
+ // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.blood_pressure_feature.xml
+ Value = GattConvert.ToIBuffer((Int16)0);
+ }
+ }
+}
diff --git a/GattServicesLibrary/GattServicesLibrary/Characteristics/BloodPressureMeasurementCharacteristic.cs b/GattServicesLibrary/GattServicesLibrary/Characteristics/BloodPressureMeasurementCharacteristic.cs
new file mode 100644
index 0000000..67a7bba
--- /dev/null
+++ b/GattServicesLibrary/GattServicesLibrary/Characteristics/BloodPressureMeasurementCharacteristic.cs
@@ -0,0 +1,66 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using GattServicesLibrary.Helpers;
+using Windows.Devices.Bluetooth.GenericAttributeProfile;
+using Windows.Devices.Power;
+using Windows.Storage.Streams;
+using System.Threading;
+using GattHelper.Converters;
+
+namespace GattServicesLibrary.Characteristics
+{
+ ///
+ /// Microsoft boilerplate characteristic that supports 'Indicate' provided for completeness. This service is almost identical to MicrosoftNotifyCharacteristic.
+ ///
+ public class BloodPressureMeasurementCharacteristic : GenericGattCharacteristic
+ {
+ private Timer bloodPressureTicker = null;
+ private Int16 Systolic = 120;
+ private Int16 Diastolic = 80;
+ private Random rand = new Random();
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Characteristic this wraps
+ public BloodPressureMeasurementCharacteristic(GattLocalCharacteristic characteristic, GenericGattService service) : base(characteristic, service)
+ {
+ bloodPressureTicker = new Timer(updateBloodPressure, "", TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1));
+ }
+
+ private void updateBloodPressure(Object state)
+ {
+ // Create random blood pressure between 100-160 over 60-100
+ Systolic = (Int16)rand.Next(100, 160);
+ Diastolic = (Int16)rand.Next(60, 100);
+
+ UInt16 MAP = (UInt16)(((2 * Diastolic) + Systolic) / 3);
+
+ // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.blood_pressure_measurement.xml
+ byte[] flags = { 0 };
+ byte[] value = flags.Concat(
+ BitConverter.GetBytes(Systolic)).Concat(
+ BitConverter.GetBytes(Diastolic)).Concat(
+ BitConverter.GetBytes(MAP)).ToArray();
+
+ Value = GattConvert.ToIBuffer(value);
+ NotifyValue();
+ }
+
+ ///
+ /// Override so we can update the value before notifying or indicating the client
+ ///
+ public override void NotifyValue()
+ {
+ base.NotifyValue();
+ }
+ }
+}
diff --git a/GattServicesLibrary/GattServicesLibrary/Characteristics/HeartRateMeasurementCharacteristic.cs b/GattServicesLibrary/GattServicesLibrary/Characteristics/HeartRateMeasurementCharacteristic.cs
new file mode 100644
index 0000000..011673a
--- /dev/null
+++ b/GattServicesLibrary/GattServicesLibrary/Characteristics/HeartRateMeasurementCharacteristic.cs
@@ -0,0 +1,70 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------------------------
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using GattServicesLibrary.Helpers;
+using Windows.Devices.Bluetooth.GenericAttributeProfile;
+using Windows.Devices.Power;
+using Windows.Storage.Streams;
+using System.Threading;
+using GattHelper.Converters;
+
+namespace GattServicesLibrary.Characteristics
+{
+ ///
+ /// Microsoft boilerplate characteristic that supports 'Notify' provided for completeness. This service is almost identical to MicrosoftIndicateCharacteristic.
+ ///
+ public class HeartRateMeasurementCharacteristic : GenericGattCharacteristic
+ {
+ private Timer heartRateTicker = null;
+ private Int16 currentHeartRate = 70;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Characteristic this wraps
+ public HeartRateMeasurementCharacteristic(GattLocalCharacteristic characteristic, GenericGattService service) : base(characteristic, service)
+ {
+ heartRateTicker = new Timer(UpdateHeartRate, "", TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1));
+ }
+
+ private void UpdateHeartRate(Object state)
+ {
+ if (currentHeartRate == 110)
+ {
+ currentHeartRate = 70;
+ }
+ else
+ {
+ currentHeartRate++;
+ }
+
+ SetHeartRate();
+ }
+
+ private void SetHeartRate()
+ {
+ // Heart rate service starts with flags, then the value. I combine them here then set the characterstic value
+ byte[] flags = { 0x07 };
+ byte[] heartRate = BitConverter.GetBytes(currentHeartRate);
+
+ byte[] value = flags.Concat(heartRate).ToArray();
+
+ Value = GattConvert.ToIBuffer(value);
+ NotifyValue();
+ }
+
+ ///
+ /// Override so we can update the value before notifying or indicating the client
+ ///
+ public override void NotifyValue()
+ {
+ base.NotifyValue();
+ }
+ }
+}
diff --git a/GattServicesLibrary/GattServicesLibrary/Characteristics/MicrosoftIndicateCharacteristic.cs b/GattServicesLibrary/GattServicesLibrary/Characteristics/MicrosoftIndicateCharacteristic.cs
new file mode 100644
index 0000000..b0428d0
--- /dev/null
+++ b/GattServicesLibrary/GattServicesLibrary/Characteristics/MicrosoftIndicateCharacteristic.cs
@@ -0,0 +1,67 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using GattServicesLibrary.Helpers;
+using Windows.Devices.Bluetooth.GenericAttributeProfile;
+using Windows.Devices.Power;
+using Windows.Storage.Streams;
+
+namespace GattServicesLibrary.Characteristics
+{
+ ///
+ /// Microsoft boilerplate characteristic that supports 'Indicate' provided for completeness. This service is almost identical to MicrosoftNotifyCharacteristic.
+ ///
+ public class MicrosoftIndicateCharacteristic : GenericGattCharacteristic
+ {
+ ///
+ /// Random number generator used when updating the value
+ ///
+ private Random rand = new Random();
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Characteristic this wraps
+ public MicrosoftIndicateCharacteristic(GattLocalCharacteristic characteristic, GenericGattService service) : base(characteristic, service)
+ {
+ UpdateValue();
+ }
+
+ ///
+ /// Read request callback to update the value
+ ///
+ ///
+ ///
+ protected override void Characteristic_ReadRequested(GattLocalCharacteristic sender, GattReadRequestedEventArgs args)
+ {
+ System.Diagnostics.Debug.WriteLine("Entering MSFTIndicateRequest.Characteristic_ReadRequested");
+ UpdateValue();
+ base.Characteristic_ReadRequested(sender, args);
+ }
+
+ ///
+ /// Override so we can update the value before notifying or indicating the client
+ ///
+ public override void NotifyValue()
+ {
+ UpdateValue();
+ base.NotifyValue();
+ }
+
+ ///
+ /// Updates our value with a random number
+ ///
+ private void UpdateValue()
+ {
+ int readValue = rand.Next(20, 40);
+ Value = GattServicesHelper.ConvertValueToBuffer(Convert.ToByte(readValue));
+ }
+ }
+}
diff --git a/GattServicesLibrary/GattServicesLibrary/Characteristics/MicrosoftNotifyCharacteristic.cs b/GattServicesLibrary/GattServicesLibrary/Characteristics/MicrosoftNotifyCharacteristic.cs
new file mode 100644
index 0000000..4a47e73
--- /dev/null
+++ b/GattServicesLibrary/GattServicesLibrary/Characteristics/MicrosoftNotifyCharacteristic.cs
@@ -0,0 +1,57 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------------------------
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using GattServicesLibrary.Helpers;
+using Windows.Devices.Bluetooth.GenericAttributeProfile;
+using Windows.Devices.Power;
+using Windows.Storage.Streams;
+
+namespace GattServicesLibrary.Characteristics
+{
+ ///
+ /// Microsoft boilerplate characteristic that supports 'Notify' provided for completeness. This service is almost identical to MicrosoftIndicateCharacteristic.
+ ///
+ public class MicrosoftNotifyCharacteristic : GenericGattCharacteristic
+ {
+ ///
+ /// Random number generator used when updating the value
+ ///
+ private Random rand = new Random();
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Characteristic this wraps
+ public MicrosoftNotifyCharacteristic(GattLocalCharacteristic characteristic, GenericGattService service) : base(characteristic, service)
+ {
+ DataWriter writer = new DataWriter();
+ writer.WriteString("Hello World!");
+ Value = writer.DetachBuffer();
+ }
+
+ ///
+ /// Read request callback to update the value
+ ///
+ ///
+ ///
+ protected override void Characteristic_ReadRequested(GattLocalCharacteristic sender, GattReadRequestedEventArgs args)
+ {
+ System.Diagnostics.Debug.WriteLine("Entering MSFTNotifyRequest.Characteristic_ReadRequested");
+ base.Characteristic_ReadRequested(sender, args);
+ }
+
+ ///
+ /// Override so we can update the value before notifying or indicating the client
+ ///
+ public override void NotifyValue()
+ {
+ base.NotifyValue();
+ }
+ }
+}
diff --git a/GattServicesLibrary/GattServicesLibrary/Characteristics/MicrosoftReadCharacteristic.cs b/GattServicesLibrary/GattServicesLibrary/Characteristics/MicrosoftReadCharacteristic.cs
new file mode 100644
index 0000000..8e1a6ae
--- /dev/null
+++ b/GattServicesLibrary/GattServicesLibrary/Characteristics/MicrosoftReadCharacteristic.cs
@@ -0,0 +1,58 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using GattServicesLibrary.Helpers;
+using Windows.Devices.Bluetooth.GenericAttributeProfile;
+using Windows.Devices.Power;
+using Windows.Storage.Streams;
+
+namespace GattServicesLibrary.Characteristics
+{
+ ///
+ /// Microsoft boilerplate characteristic that supports 'Read' provided for completeness.
+ ///
+ public class MicrosoftReadCharacteristic : GenericGattCharacteristic
+ {
+ ///
+ /// Random number generator used when updating the value
+ ///
+ private Random rand = new Random();
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Characteristic this wraps
+ public MicrosoftReadCharacteristic(GattLocalCharacteristic characteristic, GenericGattService service) : base(characteristic, service)
+ {
+ UpdateValue();
+ }
+
+ ///
+ /// Read request callback to update the value
+ ///
+ ///
+ ///
+ protected override void Characteristic_ReadRequested(GattLocalCharacteristic sender, GattReadRequestedEventArgs args)
+ {
+ System.Diagnostics.Debug.WriteLine("Entering MSFTReadRequest.Characteristic_ReadRequested");
+ UpdateValue();
+ base.Characteristic_ReadRequested(sender, args);
+ }
+
+ ///
+ /// Override so we can update the value when the value is read
+ ///
+ private void UpdateValue()
+ {
+ int readValue = rand.Next(1, 20);
+ Value = GattServicesHelper.ConvertValueToBuffer(Convert.ToByte(readValue));
+ }
+ }
+}
diff --git a/GattServicesLibrary/GattServicesLibrary/Characteristics/MicrosoftReadLongCharacteristic.cs b/GattServicesLibrary/GattServicesLibrary/Characteristics/MicrosoftReadLongCharacteristic.cs
new file mode 100644
index 0000000..f05e222
--- /dev/null
+++ b/GattServicesLibrary/GattServicesLibrary/Characteristics/MicrosoftReadLongCharacteristic.cs
@@ -0,0 +1,122 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------------------------
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using GattServicesLibrary.Helpers;
+using Windows.Devices.Bluetooth.GenericAttributeProfile;
+using Windows.Devices.Power;
+using Windows.Foundation;
+using Windows.Storage.Streams;
+
+namespace GattServicesLibrary.Characteristics
+{
+ ///
+ /// Microsoft boilerplate characteristic that has a long characteristic value provided for completeness.
+ ///
+ public class MicrosoftReadLongCharacteristic : GenericGattCharacteristic
+ {
+ #region Local variables
+ ///
+ /// Long characteristic payload
+ ///
+ private byte[] longCharacteristicData = new byte[800];
+
+ ///
+ /// Lock around longCharacteristicData
+ ///
+ private object dataLock = new object();
+
+ ///
+ /// Microsoft service long characteristics offset value
+ ///
+ private int longCharacteristicReadOffset = 0;
+ #endregion
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Characteristic this wraps
+ public MicrosoftReadLongCharacteristic(GattLocalCharacteristic characteristic, GenericGattService service) : base(characteristic, service)
+ {
+ for (int i = 0; i < longCharacteristicData.Length; i++)
+ {
+ longCharacteristicData[i] = (byte)(i % 10);
+ }
+ }
+
+ ///
+ /// Read request callback to update the value
+ ///
+ ///
+ ///
+ protected override async void Characteristic_ReadRequested(GattLocalCharacteristic sender, GattReadRequestedEventArgs args)
+ {
+ Deferral deferral = args.GetDeferral();
+
+ Debug.WriteLine($"MSFTLongReadCharacteristic: ReadRequested - MaxPduSize {args.Session.MaxPduSize}");
+ await UpdateValue(args);
+
+ base.Characteristic_ReadRequested(sender, args, deferral);
+ }
+
+ ///
+ /// Updates the Value which is what gets send back by the . It verifies that the
+ /// source data is bigger than a single read request so that a ReadBlobRequest has to be done
+ ///
+ ///
+ private async Task UpdateValue(GattReadRequestedEventArgs args)
+ {
+
+ DataWriter writer = new DataWriter();
+ int maxPayloadSize = args.Session.MaxPduSize - 1;
+
+ // start getting the read request
+ var requestTask = args.GetRequestAsync();
+
+ // make sure our source data is bigger than a single read request to make sure a ReadBlobRequest is done
+ if (longCharacteristicData.Length < maxPayloadSize)
+ {
+ // This should not be required as the server should only be processing one request at a time
+ // but it's better to be safe than sorry
+ lock (dataLock)
+ {
+ longCharacteristicData = new byte[(int)(maxPayloadSize * 2.5)];
+
+ for (int i = 0; i < longCharacteristicData.Length; i++)
+ {
+ longCharacteristicData[i] = (byte)(i % 10);
+ }
+ }
+ }
+
+ // finish getting the read request
+ GattReadRequest request = await requestTask;
+ int offset = (int)request.Offset;
+
+ // calculate the size of the data we send back
+ int chunk = Math.Min(maxPayloadSize, longCharacteristicData.Length - offset);
+ Debug.WriteLine($"UpdateValue: payloadSize: {maxPayloadSize}, chunk {chunk}");
+
+ // prep the data we send back
+ var readValue = String.Empty;
+ var buffer = new byte[chunk];
+ buffer.Initialize();
+
+ // copy from source to target
+ Array.Copy(longCharacteristicData, longCharacteristicReadOffset, buffer, 0, chunk);
+
+ // write to our internal Value which will be used to send back the data
+ writer.WriteBytes(buffer);
+ readValue = buffer.BytesToString();
+ Debug.WriteLine("MicrosoftReadLongCharacteristic: Read request value: {readValue}");
+ Value = writer.DetachBuffer();
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/GattServicesLibrary/GattServicesLibrary/Characteristics/MicrosoftWriteCharacteristic.cs b/GattServicesLibrary/GattServicesLibrary/Characteristics/MicrosoftWriteCharacteristic.cs
new file mode 100644
index 0000000..ccaccf6
--- /dev/null
+++ b/GattServicesLibrary/GattServicesLibrary/Characteristics/MicrosoftWriteCharacteristic.cs
@@ -0,0 +1,29 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------------------------
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Windows.Devices.Bluetooth.GenericAttributeProfile;
+using Windows.Devices.Power;
+using Windows.Storage.Streams;
+
+namespace GattServicesLibrary.Characteristics
+{
+ ///
+ /// Microsoft boilerplate characteristic that supports 'Write' provided for completeness.
+ ///
+ public class MicrosoftWriteCharacteristic : GenericGattCharacteristic
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Characteristic this wraps
+ public MicrosoftWriteCharacteristic(GattLocalCharacteristic characteristic, GenericGattService service) : base(characteristic, service)
+ {
+ }
+ }
+}
diff --git a/GattServicesLibrary/GattServicesLibrary/GattServicesLibrary.csproj b/GattServicesLibrary/GattServicesLibrary/GattServicesLibrary.csproj
new file mode 100644
index 0000000..181ade3
--- /dev/null
+++ b/GattServicesLibrary/GattServicesLibrary/GattServicesLibrary.csproj
@@ -0,0 +1,156 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {644F40AD-EABB-4570-B9B7-E3F8DDAA80F3}
+ Library
+ Properties
+ GattServicesLibrary
+ GattServicesLibrary
+ en-US
+ UAP
+ 10.0.15063.0
+ 10.0.15063.0
+ 14
+ 512
+ {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
+ prompt
+ 4
+
+
+ AnyCPU
+ pdbonly
+ true
+ bin\Release\
+ TRACE;NETFX_CORE;WINDOWS_UWP
+ prompt
+ 4
+
+
+ x86
+ true
+ bin\x86\Debug\
+ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
+ ;2008
+ full
+ x86
+ false
+ prompt
+
+
+ x86
+ bin\x86\Release\
+ TRACE;NETFX_CORE;WINDOWS_UWP
+ true
+ ;2008
+ pdbonly
+ x86
+ false
+ prompt
+
+
+ ARM
+ true
+ bin\ARM\Debug\
+ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
+ ;2008
+ full
+ ARM
+ false
+ prompt
+
+
+ ARM
+ bin\ARM\Release\
+ TRACE;NETFX_CORE;WINDOWS_UWP
+ true
+ ;2008
+ pdbonly
+ ARM
+ false
+ prompt
+
+
+ x64
+ true
+ bin\x64\Debug\
+ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
+ ;2008
+ full
+ x64
+ false
+ prompt
+
+
+ x64
+ bin\x64\Release\
+ TRACE;NETFX_CORE;WINDOWS_UWP
+ true
+ ;2008
+ pdbonly
+ x64
+ false
+ prompt
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {37c63fdd-d995-4cc2-b014-4fb323194001}
+ GattHelper
+
+
+
+ 14.0
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/GattServicesLibrary/GattServicesLibrary/GenericGattCharacteristic.cs b/GattServicesLibrary/GattServicesLibrary/GenericGattCharacteristic.cs
new file mode 100644
index 0000000..e6260ea
--- /dev/null
+++ b/GattServicesLibrary/GattServicesLibrary/GenericGattCharacteristic.cs
@@ -0,0 +1,275 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------------------------
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using GattServicesLibrary.Helpers;
+using Windows.ApplicationModel.Core;
+using Windows.Devices.Bluetooth.GenericAttributeProfile;
+using Windows.Security.Cryptography;
+using Windows.Storage.Streams;
+using Windows.UI.Core;
+using Windows.Foundation;
+
+namespace GattServicesLibrary
+{
+ ///
+ /// Base class for any characteristic. This handles basic responds for read/write and supplies method to
+ /// notify or indicate clients.
+ ///
+ public abstract class GenericGattCharacteristic : INotifyPropertyChanged
+ {
+ #region INotifyPropertyChanged requirements
+ ///
+ /// Property changed event
+ ///
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ ///
+ /// Property changed method
+ ///
+ /// Property that changed
+ protected void OnPropertyChanged(PropertyChangedEventArgs e)
+ {
+ if (PropertyChanged != null)
+ {
+ PropertyChanged(this, e);
+ }
+ }
+ #endregion
+
+ ///
+ /// Source of
+ ///
+ private GattLocalCharacteristic characteristic;
+
+ ///
+ /// Gets or sets that is wrapped by this class
+ ///
+ public GattLocalCharacteristic Characteristic
+ {
+ get
+ {
+ return characteristic;
+ }
+
+ set
+ {
+ if (characteristic != value)
+ {
+ characteristic = value;
+ OnPropertyChanged(new PropertyChangedEventArgs("Characteristic"));
+ }
+ }
+ }
+
+ ///
+ /// Gets parent service that this characteristic belongs to
+ ///
+ public GenericGattService ParentService
+ {
+ get; private set;
+ }
+
+ ///
+ /// Source of
+ ///
+ private IBuffer value;
+
+ ///
+ /// Gets or sets the Value of the characteristic
+ ///
+ public IBuffer Value
+ {
+ get
+ {
+ return value;
+ }
+
+ set
+ {
+ if (this.value != value)
+ {
+ this.value = value;
+ OnPropertyChanged(new PropertyChangedEventArgs("Value"));
+ }
+ }
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Characteristic this wraps
+ public GenericGattCharacteristic(GattLocalCharacteristic characteristic, GenericGattService service)
+ {
+ Characteristic = characteristic;
+ ParentService = service;
+
+ if (Characteristic.CharacteristicProperties.HasFlag(GattCharacteristicProperties.Read))
+ {
+ Characteristic.ReadRequested += Characteristic_ReadRequested;
+ }
+
+ if (Characteristic.CharacteristicProperties.HasFlag(GattCharacteristicProperties.Write) ||
+ Characteristic.CharacteristicProperties.HasFlag(GattCharacteristicProperties.WriteWithoutResponse))
+ {
+ Characteristic.WriteRequested += Characteristic_WriteRequested;
+ }
+
+ Characteristic.SubscribedClientsChanged += Characteristic_SubscribedClientsChanged;
+ }
+
+ ///
+ /// Base implementation when number of subscribers changes
+ ///
+ ///
+ ///
+ protected virtual void Characteristic_SubscribedClientsChanged(GattLocalCharacteristic sender, object args)
+ {
+ Debug.WriteLine("Subscribers: {0}", sender.SubscribedClients.Count());
+ }
+
+ ///
+ /// Base implementation to Notify or Indicate clients
+ ///
+ public virtual async void NotifyValue()
+ {
+ // If parent service is not publishing we shouldn't try to notify
+ if (ParentService.IsPublishing == false)
+ {
+ return;
+ }
+
+ bool notify = Characteristic.CharacteristicProperties.HasFlag(GattCharacteristicProperties.Notify);
+ bool indicate = Characteristic.CharacteristicProperties.HasFlag(GattCharacteristicProperties.Indicate);
+
+ if (notify || indicate)
+ {
+ Debug.WriteLine($"NotifyValue executing: Notify = {notify}, Indicate = {indicate}");
+ await Characteristic.NotifyValueAsync(Value);
+ }
+ else
+ {
+ Debug.WriteLine("NotifyValue was called but CharacteristicProperties don't include Notify or Indicate");
+ }
+ }
+
+ ///
+ /// Base implementation for the read callback
+ ///
+ ///
+ ///
+ protected virtual void Characteristic_ReadRequested(GattLocalCharacteristic sender, GattReadRequestedEventArgs args)
+ {
+ // Grab the event deferral before performing any async operations in the handler.
+ var deferral = args.GetDeferral();
+
+ Characteristic_ReadRequested(sender, args, deferral);
+ }
+
+ ///
+ /// Base implementation for the read callback
+ ///
+ ///
+ ///
+ /// The deferral in case a specific implementation had to do async tasks
+ protected virtual async void Characteristic_ReadRequested(GattLocalCharacteristic sender, GattReadRequestedEventArgs args, Deferral deferral = null)
+ {
+ // Grab the event deferral before performing any async operations in the handler.
+ if (deferral == null)
+ {
+ deferral = args.GetDeferral();
+ }
+
+ Debug.WriteLine($"({this.GetType()})Entering base.Characteristic_ReadRequested");
+
+ // In order to get the remote request, access to the device must be provided by the user.
+ // This can be accomplished by calling BluetoothLEDevice.RequestAccessAsync(), or by getting the request on the UX thread.
+ //
+ // Note that subsequent calls to RequestAccessAsync or GetRequestAsync for the same device do not need to be called on the UX thread.
+ await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(
+ CoreDispatcherPriority.Normal,
+ async () =>
+ {
+
+ var request = await args.GetRequestAsync();
+ request.RespondWithValue(Value);
+ Debug.WriteLine($"Characteristic_ReadRequested - Length {request.Length}, State: {request.State}, Offset: {request.Offset}");
+ });
+ }
+
+ ///
+ /// Base implementation for the write callback
+ ///
+ ///
+ ///
+ protected virtual void Characteristic_WriteRequested(GattLocalCharacteristic sender, GattWriteRequestedEventArgs args)
+ {
+ Characteristic_WriteRequested(sender, args, args.GetDeferral());
+ }
+
+ ///
+ /// Base implementation for the write callback
+ ///
+ ///
+ ///
+ /// The deferral in case a specific implementation had to do async tasks
+ protected virtual async void Characteristic_WriteRequested(GattLocalCharacteristic sender, GattWriteRequestedEventArgs args, Deferral deferral)
+ {
+ Debug.WriteLine("Characteristic_WriteRequested: Write Requested");
+ GattWriteRequest request = null;
+
+ // Grab the event deferral before performing any async operations in the handler.
+ if (deferral == null)
+ {
+ deferral = args.GetDeferral();
+ }
+
+ // In order to get the remote request, access to the device must be provided by the user.
+ // This can be accomplished by calling BluetoothLEDevice.RequestAccessAsync(), or by getting the request on the UX thread.
+ //
+ // Note that subsequent calls to RequestAccessAsync or GetRequestAsync for the same device do not need to be called on the UX thread.
+ await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(
+ CoreDispatcherPriority.Normal,
+ async () =>
+ {
+ // Grab the request
+ request = await args.GetRequestAsync();
+
+ // Set the characteristic Value
+ Value = request.Value;
+
+ // Respond with completed
+ if (request.Option == GattWriteOption.WriteWithResponse)
+ {
+ Debug.WriteLine("Characteristic_WriteRequested: Completing request with responds");
+ request.Respond();
+ }
+ else
+ {
+ Debug.WriteLine("Characteristic_WriteRequested: Completing request without responds");
+ }
+
+ // everything below this is debug. Should implement this on non-UI thread based on
+ // https://github.com/Microsoft/Windows-task-snippets/blob/master/tasks/UI-thread-task-await-from-background-thread.md
+ byte[] data;
+ CryptographicBuffer.CopyToByteArray(Value, out data);
+
+ if (data == null)
+ {
+ Debug.WriteLine("Characteristic_WriteRequested: Value after write complete was NULL");
+ }
+ else
+ {
+ Debug.WriteLine($"Characteristic_WriteRequested: New Value: {data.BytesToString()}");
+ }
+ });
+ }
+ }
+}
diff --git a/GattServicesLibrary/GattServicesLibrary/GenericGattService.cs b/GattServicesLibrary/GattServicesLibrary/GenericGattService.cs
new file mode 100644
index 0000000..31b7757
--- /dev/null
+++ b/GattServicesLibrary/GattServicesLibrary/GenericGattService.cs
@@ -0,0 +1,224 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------------------------
+using System;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Runtime.InteropServices.WindowsRuntime;
+using System.Threading.Tasks;
+using GattServicesLibrary.Helpers;
+using Windows.Devices.Bluetooth;
+using Windows.Devices.Bluetooth.GenericAttributeProfile;
+
+namespace GattServicesLibrary
+{
+ ///
+ /// An abstract class defines the Generic parameters, properties, APIs common to GATT Services.
+ ///
+ public abstract class GenericGattService : INotifyPropertyChanged
+ {
+ #region Generic helper parameters
+ ///
+ /// Gatt Local characteristics parameter for Reading Parameters
+ ///
+ protected static readonly GattLocalCharacteristicParameters PlainReadParameter = new GattLocalCharacteristicParameters
+ {
+ CharacteristicProperties = GattCharacteristicProperties.Read,
+ WriteProtectionLevel = GattProtectionLevel.Plain,
+ ReadProtectionLevel = GattProtectionLevel.Plain
+ };
+
+ ///
+ /// Gatt Local characteristics parameter for Writing Parameters
+ ///
+ protected static readonly GattLocalCharacteristicParameters PlainWriteOrWriteWithoutRespondsParameter = new GattLocalCharacteristicParameters
+ {
+ CharacteristicProperties = GattCharacteristicProperties.Write | GattCharacteristicProperties.WriteWithoutResponse,
+ WriteProtectionLevel = GattProtectionLevel.Plain
+ };
+
+ ///
+ /// Gatt Local characteristics parameter for Reading and Notifying Parameters
+ ///
+ protected static readonly GattLocalCharacteristicParameters PlainReadNotifyParameters = new GattLocalCharacteristicParameters
+ {
+ CharacteristicProperties = GattCharacteristicProperties.Read | GattCharacteristicProperties.Notify,
+ ReadProtectionLevel = GattProtectionLevel.Plain,
+ WriteProtectionLevel = GattProtectionLevel.Plain
+ };
+
+ ///
+ /// Gatt Local characteristics parameter for Notifying Parameters
+ ///
+ protected static readonly GattLocalCharacteristicParameters PlainNotifyParameters = new GattLocalCharacteristicParameters
+ {
+ CharacteristicProperties = GattCharacteristicProperties.Notify,
+ WriteProtectionLevel = GattProtectionLevel.Plain
+ };
+
+ ///
+ /// Gatt Local characteristics parameter for Indicating Parameters
+ ///
+ protected static readonly GattLocalCharacteristicParameters PlainIndicateParameters = new GattLocalCharacteristicParameters
+ {
+ CharacteristicProperties = GattCharacteristicProperties.Indicate,
+ WriteProtectionLevel = GattProtectionLevel.Plain
+ };
+ #endregion
+
+ #region INotifyPropertyChanged requirements
+ ///
+ /// Property changed event
+ ///
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ ///
+ /// Property changed method
+ ///
+ /// Property that changed
+ protected void OnPropertyChanged(PropertyChangedEventArgs e)
+ {
+ if (PropertyChanged != null)
+ {
+ PropertyChanged(this, e);
+ }
+ }
+ #endregion
+
+ ///
+ /// Gets the name of the characteristic
+ ///
+ public abstract string Name
+ {
+ get;
+ }
+
+ private bool isPublishing = false;
+ public bool IsPublishing
+ {
+ get
+ {
+ return isPublishing;
+ }
+
+ private set
+ {
+ if(value != isPublishing)
+ {
+ isPublishing = value;
+ OnPropertyChanged(new PropertyChangedEventArgs("IsPublishing"));
+ }
+ }
+ }
+
+ ///
+ /// Internal ServiceProvider
+ ///
+ private GattServiceProvider serviceProvider;
+
+ ///
+ /// Gets or sets the Gatt Service Provider
+ ///
+ public GattServiceProvider ServiceProvider
+ {
+ get
+ {
+ return serviceProvider;
+ }
+
+ protected set
+ {
+ if (serviceProvider != value)
+ {
+ serviceProvider = value;
+ OnPropertyChanged(new PropertyChangedEventArgs("ServiceProvider"));
+ }
+ }
+ }
+
+ ///
+ /// Gets the Constant Read parameters using the Input value
+ ///
+ /// specific characteristic value for given profile
+ protected GattLocalCharacteristicParameters GetPlainReadParameterWithValue(byte[] value)
+ {
+ return new GattLocalCharacteristicParameters
+ {
+ CharacteristicProperties = GattCharacteristicProperties.Read,
+ StaticValue = value.AsBuffer(),
+ ReadProtectionLevel = GattProtectionLevel.Plain,
+ };
+ }
+
+ ///
+ /// Abstract method used to initialize this class
+ ///
+ /// Tasks that initializes the class
+ public abstract Task Init();
+
+ ///
+ /// Starts the Gatt Service
+ ///
+ /// True, Starts the Service as connectable. False, starts the Service as only Discoverable
+ public virtual void Start(bool connectable)
+ {
+ // MakeDiscoverable ensures that remote devices can query support for the service from the local device. MakeConnectable is PeripheralRole and advertises connectably
+ // and best-effort populates the ADV packet with the service ID
+ GattServiceProviderAdvertisingParameters advParameters = new GattServiceProviderAdvertisingParameters
+ {
+ IsDiscoverable = true,
+ IsConnectable = connectable
+ };
+
+ try
+ {
+ ServiceProvider.StartAdvertising(advParameters);
+ IsPublishing = true;
+ OnPropertyChanged(new PropertyChangedEventArgs("IsPublishing"));
+ }
+ catch (Exception)
+ {
+ Debug.WriteLine($"Exception while start Advertising {ServiceProvider.Service.Uuid}");
+ IsPublishing = false;
+ throw;
+ }
+ }
+
+ ///
+ /// Stops the already running Service
+ ///
+ public virtual void Stop()
+ {
+ try
+ {
+ ServiceProvider.StopAdvertising();
+ IsPublishing = false;
+ OnPropertyChanged(new PropertyChangedEventArgs("IsPublishing"));
+ }
+ catch (Exception)
+ {
+ Debug.WriteLine($"Exception while Stop Advertising {ServiceProvider.Service.Uuid}");
+ IsPublishing = true;
+ throw;
+ }
+ }
+
+ ///
+ /// Creates the Gatt Service provider
+ ///
+ /// UUID of the Service to create
+ protected async Task CreateServiceProvider(Guid uuid)
+ {
+ // Create Service Provider - similar to RFCOMM APIs
+ GattServiceProviderResult result = await GattServiceProvider.CreateAsync(uuid);
+
+ if (result.Error != BluetoothError.Success)
+ {
+ throw new CreateServiceException(result);
+ }
+
+ ServiceProvider = result.ServiceProvider;
+ }
+ }
+}
\ No newline at end of file
diff --git a/GattServicesLibrary/GattServicesLibrary/Helpers/CreateServiceException.cs b/GattServicesLibrary/GattServicesLibrary/Helpers/CreateServiceException.cs
new file mode 100644
index 0000000..0dfc9b3
--- /dev/null
+++ b/GattServicesLibrary/GattServicesLibrary/Helpers/CreateServiceException.cs
@@ -0,0 +1,30 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------------------------
+using System;
+using Windows.Devices.Bluetooth.GenericAttributeProfile;
+
+namespace GattServicesLibrary.Helpers
+{
+ ///
+ /// Class for Create Service Exception
+ ///
+ public class CreateServiceException : Exception
+ {
+ ///
+ /// Gets the value indicating the create service exception details
+ ///
+ public GattServiceProviderResult CreateServiceExceptionResult { get; }
+
+ ///
+ /// Initializes a new instance of the class
+ ///
+ /// Gatt Service provider result
+ public CreateServiceException(GattServiceProviderResult createServiceExceptionResult) :
+ base(string.Format($"Error occured while creating the provider, Error Code:{createServiceExceptionResult.Error}"))
+ {
+ CreateServiceExceptionResult = createServiceExceptionResult;
+ }
+ }
+}
diff --git a/GattServicesLibrary/GattServicesLibrary/Helpers/GattServicesHelper.cs b/GattServicesLibrary/GattServicesLibrary/Helpers/GattServicesHelper.cs
new file mode 100644
index 0000000..3b21c73
--- /dev/null
+++ b/GattServicesLibrary/GattServicesLibrary/Helpers/GattServicesHelper.cs
@@ -0,0 +1,87 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------------------------
+using System;
+using System.Diagnostics;
+using Windows.Devices.Bluetooth;
+using Windows.Devices.Bluetooth.GenericAttributeProfile;
+using Windows.Storage.Streams;
+
+namespace GattServicesLibrary.Helpers
+{
+ ///
+ /// Gatt Service Helper Class containing Utility functions used across the services.
+ ///
+ public static class GattServicesHelper
+ {
+ ///
+ /// Get Characteristics from the Characteristics Result
+ ///
+ /// Gatt Characteristics Result
+ /// Gatt characteristics
+ public static void GetCharacteristicsFromResult(GattLocalCharacteristicResult result, ref GattLocalCharacteristic characteristics)
+ {
+ if (result.Error == BluetoothError.Success)
+ {
+ characteristics = result.Characteristic;
+ }
+ else
+ {
+ Debug.WriteLine(result.Error.ToString());
+ }
+ }
+
+ ///
+ /// Converts byte value into Buffer
+ ///
+ /// Byte value
+ /// Data writer buffer
+ public static IBuffer ConvertValueToBuffer(byte byteValue)
+ {
+ //TODO: User GattConvert here
+ DataWriter writer = new DataWriter();
+ writer.WriteByte(byteValue);
+ return writer.DetachBuffer();
+ }
+
+ ///
+ /// Converts two byte values into buffer
+ ///
+ /// Byte value 1
+ /// Byte value 2
+ /// Data writer buffer
+ public static IBuffer ConvertValueToBuffer(byte byteValue1, byte byteValue2)
+ {
+ DataWriter writer = new DataWriter();
+ writer.WriteByte(byteValue1);
+ writer.WriteByte(byteValue2);
+
+ return writer.DetachBuffer();
+ }
+
+ ///
+ /// Converts date time value to buffer
+ ///
+ /// DateTime value
+ /// Data Writer Buffer
+ public static IBuffer ConvertValueToBuffer(DateTime time)
+ {
+ DataWriter writer = new DataWriter();
+
+ // Date time according to: https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.date_time.xml
+ writer.WriteUInt16((ushort)time.Year);
+ writer.WriteByte((byte)time.Month);
+ writer.WriteByte((byte)time.Day);
+ writer.WriteByte((byte)time.Hour);
+ writer.WriteByte((byte)time.Minute);
+ writer.WriteByte((byte)time.Second);
+
+ // Day of week according to: https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.day_of_week.xml
+ // Going to leave this "not known" for now - would have to perform a rotate of DayOfWeek property
+ writer.WriteByte(0x0);
+
+ return writer.DetachBuffer();
+ }
+ }
+}
diff --git a/GattServicesLibrary/GattServicesLibrary/Helpers/HelperExtensions.cs b/GattServicesLibrary/GattServicesLibrary/Helpers/HelperExtensions.cs
new file mode 100644
index 0000000..ef40f56
--- /dev/null
+++ b/GattServicesLibrary/GattServicesLibrary/Helpers/HelperExtensions.cs
@@ -0,0 +1,35 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------------------------
+using System.Text;
+
+namespace GattServicesLibrary.Helpers
+{
+ ///
+ /// Extension class for byte
+ ///
+ public static class HelperExtensions
+ {
+ ///
+ /// Converts byte array to string
+ ///
+ /// Byte array to covert
+ /// string equivalent of the byte array
+ public static string BytesToString(this byte[] array)
+ {
+ var result = new StringBuilder();
+
+ for (int i = 0; i < array.Length; i++)
+ {
+ result.Append($"{array[i]:X2}");
+ if (i < array.Length - 1)
+ {
+ result.Append(" ");
+ }
+ }
+
+ return result.ToString();
+ }
+ }
+}
diff --git a/GattServicesLibrary/GattServicesLibrary/Helpers/PresentationFormats.cs b/GattServicesLibrary/GattServicesLibrary/Helpers/PresentationFormats.cs
new file mode 100644
index 0000000..8584572
--- /dev/null
+++ b/GattServicesLibrary/GattServicesLibrary/Helpers/PresentationFormats.cs
@@ -0,0 +1,194 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------------------------
+
+namespace GattServicesLibrary
+{
+ ///
+ /// Class containing the details of Gatt characteristics presentation formats
+ ///
+ public class PresentationFormats
+ {
+ ///
+ /// The format field determines how a single value contained in the Characteristic Value is formatted.
+ ///
+ /// Please refer https://www.bluetooth.com/specifications/assigned-numbers/format-types
+ public enum FormatTypes
+ {
+ ReservedForFutureUse = 0,
+ Boolean,
+ Unsigned2BitInteger,
+ Unsigned4BitInteger,
+ Unsigned8BitInteger,
+ Unsigned12BitInteger,
+ Unsigned16BitInteger,
+ Unsigned24BitInteger,
+ Unsigned32BitInteger,
+ Unsigned48BitInteger,
+ Unsigned64BitInteger,
+ Unsigned128BitInteger,
+ Signed8BitInteger,
+ Signed12BitInteger,
+ Signed16BitInteger,
+ Signed24BitInteger,
+ Signed32BitInteger,
+ Signed48BitInteger,
+ Signed64BitInteger,
+ Signed128BitInteger,
+ IEEE75432BitFloatingPoint,
+ IEEE75464BitGloatingPoint,
+ IEEE1107316BitSFloat,
+ IEEE1107332BitFloat,
+ IEEE20601Format,
+ UTF8String,
+ UTF16String,
+ OpaqueStructure,
+ Reserved = 255,
+ }
+
+ ///
+ /// Units are established international standards for the measurement of physical quantities.
+ ///
+ /// Please refer https://www.bluetooth.com/specifications/assigned-numbers/units
+ public enum Units
+ {
+ Unitless = 0x2700,
+ LengthMetre = 0x2701,
+ MassKilogram = 0x2702,
+ TimeSecond = 0x2703,
+ ElectricCurrentAmpere = 0x2704,
+ ThermodynamicTemperatureKelvin = 0x2705,
+ AmountOfSubstanceMole = 0x2706,
+ LuminousIntensityCandela = 0x2707,
+ AreaSquareMetres = 0x2710,
+ VolumeCubicMetres = 0x2711,
+ VelocityMetresPerSecond = 0x2712,
+ AccelerationMetresPerSecondSquared = 0x2713,
+ WaveNumberReciprocalMetre = 0x2714,
+ DensityKilogramperCubicMetre = 0x2715,
+ SurfaceDensityKilogramPerSquareMetre = 0x2716,
+ SpecificVolumeCubicMetrePerKilogram = 0x2717,
+ CurrentDensityAmperePerSquareMetre = 0x2718,
+ MagneticFieldStrengthAmperePerMetre = 0x2719,
+ AmountConcentrationMolePerCubicMetre = 0x271A,
+ MassConcentrationKilogramPerCubicMetre = 0x271B,
+ LuminanceCandelaPerSquareMetre = 0x271C,
+ RefractiveIndex = 0x271D,
+ RelativePermeability = 0x271E,
+ PlaneAngleRadian = 0x2720,
+ SolidAngleSteradian = 0x2721,
+ FrequencyHertz = 0x2722,
+ ForceNewton = 0x2723,
+ PressurePascal = 0x2724,
+ EnergyJoule = 0x2725,
+ PowerWatt = 0x2726,
+ ElectricChargeCoulomb = 0x2727,
+ ElectricPotentialDifferenceVolt = 0x2728,
+ CapacitanceFarad = 0x2729,
+ ElectricResistanceOhm = 0x272A,
+ ElectricConductanceSiemens = 0x272B,
+ MagneticFluxWeber = 0x272C,
+ MagneticFluxDensityTesla = 0x272D,
+ InductanceHenry = 0x272E,
+ CelsiusTemperatureDegreeCelsius = 0x272F,
+ LuminousFluxLumen = 0x2730,
+ IlluminanceLux = 0x2731,
+ ActivityReferredToARadioNuclideBecquerel = 0x2732,
+ AbsorbedDoseGray = 0x2733,
+ DoseEquivalentSievert = 0x2734,
+ CatalyticActivityKatal = 0x2735,
+ DynamicViscosityPascalSecond = 0x2740,
+ MomentOfForceNewtonMetre = 0x2741,
+ SurfaceTensionNewtonPerMetre = 0x2742,
+ AngularVelocityRadianPerSecond = 0x2743,
+ AngularAccelerationRadianPerSecondSquared = 0x2744,
+ HeatFluxDensityWattPerSquareMetre = 0x2745,
+ HeatCapacityJoulePerKelvin = 0x2746,
+ SpecificHeatCapacityJoulePerKilogramKelvin = 0x2747,
+ SpecificEnergyJoulePerKilogram = 0x2748,
+ ThermalConductivityWattPerMetreKelvin = 0x2749,
+ EnergyDensityJoulePerCubicMetre = 0x274A,
+ ElectricfieldstrengthVoltPerMetre = 0x274B,
+ ElectricchargeDensityCoulombPerCubicMetre = 0x274C,
+ SurfacechargeDensityCoulombPerSquareMetre = 0x274D,
+ ElectricFluxDensityCoulombPerSquareMetre = 0x274E,
+ PermittivityFaradPerMetre = 0x274F,
+ PermeabilityHenryPerMetre = 0x2750,
+ MolarEnergyJoulePermole = 0x2751,
+ MolarentropyJoulePermoleKelvin = 0x2752,
+ ExposureCoulombPerKilogram = 0x2753,
+ AbsorbeddoserateGrayPerSecond = 0x2754,
+ RadiantintensityWattPerSteradian = 0x2755,
+ RadianceWattPerSquareMetreSteradian = 0x2756,
+ CatalyticActivityConcentrationKatalPerCubicMetre = 0x2757,
+ TimeMinute = 0x2760,
+ TimeHour = 0x2761,
+ TimeDay = 0x2762,
+ PlaneAngleDegree = 0x2763,
+ PlaneAngleMinute = 0x2764,
+ PlaneAngleSecond = 0x2765,
+ AreaHectare = 0x2766,
+ VolumeLitre = 0x2767,
+ MassTonne = 0x2768,
+ PressureBar = 0x2780,
+ PressureMilliMetreofmercury = 0x2781,
+ LengthAngstrom = 0x2782,
+ LengthNauticalMile = 0x2783,
+ AreaBarn = 0x2784,
+ VelocityKnot = 0x2785,
+ LogarithmicRadioQuantityNeper = 0x2786,
+ LogarithmicRadioQuantityBel = 0x2787,
+ LengthYard = 0x27A0,
+ LengthParsec = 0x27A1,
+ LengthInch = 0x27A2,
+ LengthFoot = 0x27A3,
+ LengthMile = 0x27A4,
+ PressurePoundForcePerSquareinch = 0x27A5,
+ VelocityKiloMetrePerHour = 0x27A6,
+ VelocityMilePerHour = 0x27A7,
+ AngularVelocityRevolutionPerminute = 0x27A8,
+ EnergyGramcalorie = 0x27A9,
+ EnergyKilogramcalorie = 0x27AA,
+ EnergyKiloWattHour = 0x27AB,
+ ThermodynamicTemperatureDegreeFahrenheit = 0x27AC,
+ Percentage = 0x27AD,
+ PerMille = 0x27AE,
+ PeriodBeatsPerMinute = 0x27AF,
+ ElectricchargeAmpereHours = 0x27B0,
+ MassDensityMilligramPerdeciLitre = 0x27B1,
+ MassDensityMillimolePerLitre = 0x27B2,
+ TimeYear = 0x27B3,
+ TimeMonth = 0x27B4,
+ ConcentrationCountPerCubicMetre = 0x27B5,
+ IrradianceWattPerSquareMetre = 0x27B6,
+ MilliliterPerKilogramPerminute = 0x27B7,
+ MassPound = 0x27B8
+ }
+
+ ///
+ /// The Name Space field is used to identify the organization that is responsible for defining the enumerations for the description field.
+ ///
+ ///
+ /// Please refer https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.characteristic_presentation_format.xml
+ ///
+ public enum NamespaceId
+ {
+ BluetoothSigAssignedNumber = 1,
+ ReservedForFutureUse
+ }
+
+ ///
+ /// The Description is an enumerated value from the organization identified by the Name Space field.
+ ///
+ ///
+ /// Please refer https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.characteristic_presentation_format.xml
+ ///
+ public const ushort Description = 0x0006;
+
+ ///
+ /// Exponent value for the characteristics
+ ///
+ public const int Exponent = 0;
+ }
+}
diff --git a/GattServicesLibrary/GattServicesLibrary/Helpers/ValueConverter.cs b/GattServicesLibrary/GattServicesLibrary/Helpers/ValueConverter.cs
new file mode 100644
index 0000000..e0e97f1
--- /dev/null
+++ b/GattServicesLibrary/GattServicesLibrary/Helpers/ValueConverter.cs
@@ -0,0 +1,124 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------------------------
+using GattHelper.Converters;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Windows.Devices.Bluetooth.GenericAttributeProfile;
+using Windows.Security.Cryptography;
+using Windows.Storage.Streams;
+
+namespace GattServicesLibrary.Helpers
+{
+ ///
+ /// Helper class to change the values so they're easily consumable
+ ///
+ public class ValueConverter
+ {
+ ///
+ /// Converts GenericGattCharacteristic.Value to a string based on the presentation format
+ ///
+ ///
+ /// value as a string
+ public static string ConvertGattCharacteristicValueToString(GenericGattCharacteristic characteristic)
+ {
+ if (characteristic.Value == null)
+ {
+ return String.Empty;
+ }
+
+ GattPresentationFormat format = null;
+
+ if (characteristic.Characteristic.PresentationFormats.Count > 0)
+ {
+ format = characteristic.Characteristic.PresentationFormats[0];
+ }
+
+ return ConvertValueBufferToString(characteristic.Value, format);
+ }
+
+ ///
+ /// Converts GenericGattCharacteristic.Value to a string based on the presentation format
+ ///
+ /// value to convert
+ /// presentation format to use
+ /// value as string
+ public static string ConvertValueBufferToString(IBuffer value, GattPresentationFormat format = null)
+ {
+ // no format, return bytes
+ if (format == null)
+ {
+ return GattConvert.ToHexString(value);
+ }
+
+ // Bool
+ if (format.FormatType == GattPresentationFormatTypes.Boolean)
+ {
+ // Previous implementation was incorrect. Need to implement in GattHelper.
+ throw new NotImplementedException();
+ }
+ else if (format.FormatType == GattPresentationFormatTypes.Bit2 ||
+ format.FormatType == GattPresentationFormatTypes.Nibble)
+ {
+ // 2bit or nibble - no exponent
+ // Previous implementation was incorrect. Need to implement in GattHelper.
+ return GattConvert.ToHexString(value);
+ }
+ else if (format.FormatType == GattPresentationFormatTypes.UInt8 ||
+ format.FormatType == GattPresentationFormatTypes.UInt12 ||
+ format.FormatType == GattPresentationFormatTypes.UInt16)
+ {
+ // Previous implementation was incorrect. Need to implement in GattHelper.
+ return GattConvert.ToHexString(value);
+ }
+ else if (format.FormatType == GattPresentationFormatTypes.UInt24 ||
+ format.FormatType == GattPresentationFormatTypes.UInt32)
+ {
+ // Previous implementation was incorrect. Need to implement in GattHelper.
+ return GattConvert.ToHexString(value);
+ }
+ else if (format.FormatType == GattPresentationFormatTypes.UInt48 ||
+ format.FormatType == GattPresentationFormatTypes.UInt64)
+ {
+ // Previous implementation was incorrect. Need to implement in GattHelper.
+ return GattConvert.ToHexString(value);
+ }
+ else if (format.FormatType == GattPresentationFormatTypes.SInt8 ||
+ format.FormatType == GattPresentationFormatTypes.SInt12 ||
+ format.FormatType == GattPresentationFormatTypes.SInt16)
+ {
+ // Previous implementation was incorrect. Need to implement in GattHelper.
+ return GattConvert.ToHexString(value);
+ }
+ else if (format.FormatType == GattPresentationFormatTypes.SInt24 ||
+ format.FormatType == GattPresentationFormatTypes.SInt32)
+ {
+ return GattConvert.ToInt32(value).ToString();
+ }
+ else if (format.FormatType == GattPresentationFormatTypes.Utf8)
+ {
+ return GattConvert.ToUTF8String(value);
+ }
+ else if (format.FormatType == GattPresentationFormatTypes.Utf16)
+ {
+ return GattConvert.ToUTF16String(value);
+ }
+ else
+ {
+ // format.FormatType == GattPresentationFormatTypes.UInt128 ||
+ // format.FormatType == GattPresentationFormatTypes.SInt128 ||
+ // format.FormatType == GattPresentationFormatTypes.DUInt16 ||
+ // format.FormatType == GattPresentationFormatTypes.SInt64 ||
+ // format.FormatType == GattPresentationFormatTypes.Struct ||
+ // format.FormatType == GattPresentationFormatTypes.Float ||
+ // format.FormatType == GattPresentationFormatTypes.Float32 ||
+ // format.FormatType == GattPresentationFormatTypes.Float64
+ return GattConvert.ToHexString(value);
+ }
+ }
+ }
+}
diff --git a/GattServicesLibrary/GattServicesLibrary/Properties/AssemblyInfo.cs b/GattServicesLibrary/GattServicesLibrary/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..e6472c3
--- /dev/null
+++ b/GattServicesLibrary/GattServicesLibrary/Properties/AssemblyInfo.cs
@@ -0,0 +1,32 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------------------------
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("GattServicesLibrary")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("GattServicesLibrary")]
+[assembly: AssemblyCopyright("Copyright © 2016")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
+[assembly: ComVisible(false)]
\ No newline at end of file
diff --git a/GattServicesLibrary/GattServicesLibrary/Properties/GattServicesLibrary.rd.xml b/GattServicesLibrary/GattServicesLibrary/Properties/GattServicesLibrary.rd.xml
new file mode 100644
index 0000000..d12d94b
--- /dev/null
+++ b/GattServicesLibrary/GattServicesLibrary/Properties/GattServicesLibrary.rd.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
diff --git a/GattServicesLibrary/GattServicesLibrary/Services/AlertNotificationService.cs b/GattServicesLibrary/GattServicesLibrary/Services/AlertNotificationService.cs
new file mode 100644
index 0000000..38d61a5
--- /dev/null
+++ b/GattServicesLibrary/GattServicesLibrary/Services/AlertNotificationService.cs
@@ -0,0 +1,151 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------------------------
+using System;
+using System.Threading.Tasks;
+using GattServicesLibrary.CharacteristicParameterValues;
+using GattServicesLibrary.Helpers;
+using Windows.Devices.Bluetooth.GenericAttributeProfile;
+using Windows.Storage.Streams;
+
+namespace GattServicesLibrary.Services
+{
+ ///
+ /// Class for Alert Notification Services
+ ///
+ public class AlertNotificationService : GenericGattService
+ {
+ ///
+ /// Characteristic provides the count of new alerts (for a given category) in the server.
+ ///
+ private GattLocalCharacteristic newAlert;
+
+ ///
+ /// Characteristic exposes the count of unread alert events existing in the server.
+ /// The count of unread alert events is provided with the Category ID.
+ ///
+ private GattLocalCharacteristic unreadAlertStatus;
+
+ ///
+ /// Characteristic allows the client device to enable/disable the alert notification of new alert and
+ /// unread alert events more selectively than can be done by setting or clearing the notification bit
+ /// in the Client Characteristic configuration for each alert characteristic.
+ ///
+ private GattLocalCharacteristic alertNotificationCtrlPnt;
+
+ ///
+ /// Characteristic is a bit map showing which categories of unread alert events are supported and which are not.
+ ///
+ private GattLocalCharacteristic supportUnreadAlert;
+
+ ///
+ /// Characteristic is a bit map showing which categories of new alert are supported and which are not.
+ ///
+ private GattLocalCharacteristic supportNewAlert;
+
+ ///
+ /// Name of the service
+ ///
+ public override string Name
+ {
+ get
+ {
+ return "Alert Notification Service";
+ }
+ }
+
+ ///
+ /// Starts the Alert Notification Services
+ ///
+ /// True, starts the service as Connectable. False, starts the service as only Discoverable
+ public override void Start(bool connectable)
+ {
+ // Please refer Category ID bit mask https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.alert_category_id_bit_mask.xml
+ // The value 0x21 is interpreted as "Simple Alert and SMS bits set"
+ StartWithValue(connectable, new byte[] { (byte)(AlertCategoryID.SimpleAlert | AlertCategoryID.SMS_MMS) });
+ }
+
+ ///
+ /// Starts the Alert Notification Services for Specific categories
+ ///
+ /// True, starts the service as Connectable. False, starts the service as only Discoverable
+ /// Category ID
+ public async void StartWithValue(bool connectable, byte[] value)
+ {
+ await CreateServiceProvider(GattServiceUuids.AlertNotification);
+
+ GattLocalCharacteristicResult result = await ServiceProvider.Service.CreateCharacteristicAsync(GattCharacteristicUuids.SupportedNewAlertCategory,
+ GetPlainReadParameterWithValue(value));
+ GattServicesHelper.GetCharacteristicsFromResult(result, ref supportNewAlert);
+
+ result = await ServiceProvider.Service.CreateCharacteristicAsync(GattCharacteristicUuids.SupportUnreadAlertCategory,
+ GetPlainReadParameterWithValue(value));
+ GattServicesHelper.GetCharacteristicsFromResult(result, ref supportUnreadAlert);
+
+ result = await ServiceProvider.Service.CreateCharacteristicAsync(GattCharacteristicUuids.NewAlert, PlainNotifyParameters);
+ GattServicesHelper.GetCharacteristicsFromResult(result, ref newAlert);
+
+ result = await ServiceProvider.Service.CreateCharacteristicAsync(GattCharacteristicUuids.UnreadAlertStatus, PlainNotifyParameters);
+ GattServicesHelper.GetCharacteristicsFromResult(result, ref unreadAlertStatus);
+
+ result = await ServiceProvider.Service.CreateCharacteristicAsync(GattCharacteristicUuids.AlertNotificationControlPoint,
+ PlainWriteOrWriteWithoutRespondsParameter);
+ GattServicesHelper.GetCharacteristicsFromResult(result, ref alertNotificationCtrlPnt);
+ if (alertNotificationCtrlPnt != null)
+ {
+ alertNotificationCtrlPnt.WriteRequested += AlertNotificationCtrlPntOnWriteRequested;
+ }
+
+ // Once all characteristics have been added - publish to the system
+ base.Start(connectable);
+ }
+
+ ///
+ /// Stops the already running Alert Notification Services
+ ///
+ public override void Stop()
+ {
+ newAlert = null;
+ unreadAlertStatus = null;
+ if (alertNotificationCtrlPnt != null)
+ {
+ alertNotificationCtrlPnt.WriteRequested -= AlertNotificationCtrlPntOnWriteRequested;
+ alertNotificationCtrlPnt = null;
+ }
+
+ supportUnreadAlert = null;
+ supportNewAlert = null;
+ base.Stop();
+ }
+
+ ///
+ /// Notifies the client about the alert
+ ///
+ public async void NotifyValue()
+ {
+ IBuffer buffer = GattServicesHelper.ConvertValueToBuffer(1);
+ await newAlert.NotifyValueAsync(buffer);
+ await unreadAlertStatus.NotifyValueAsync(buffer);
+ }
+
+ ///
+ /// Event handler for Alert Notification Control point
+ ///
+ /// The source of the Write request
+ /// Details about the request
+ private void AlertNotificationCtrlPntOnWriteRequested(GattLocalCharacteristic sender, GattWriteRequestedEventArgs args)
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Asynchronous initialization
+ ///
+ /// Initialization task
+ public override Task Init()
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/GattServicesLibrary/GattServicesLibrary/Services/BatteryService.cs b/GattServicesLibrary/GattServicesLibrary/Services/BatteryService.cs
new file mode 100644
index 0000000..501ecf4
--- /dev/null
+++ b/GattServicesLibrary/GattServicesLibrary/Services/BatteryService.cs
@@ -0,0 +1,94 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------------------------
+using System;
+using System.ComponentModel;
+using System.Threading.Tasks;
+using GattServicesLibrary.Helpers;
+using Windows.Devices.Bluetooth.GenericAttributeProfile;
+using Windows.Devices.Power;
+
+namespace GattServicesLibrary.Services
+{
+ ///
+ /// Class for Battery Services
+ ///
+ public class BatteryService : GenericGattService
+ {
+ ///
+ /// Name of the service
+ ///
+ public override string Name
+ {
+ get
+ {
+ return "Battery Service";
+ }
+ }
+
+ ///
+ /// Battery level
+ ///
+ private GenericGattCharacteristic batteryLevel;
+
+ ///
+ /// Gets or sets the battery level
+ ///
+ public GenericGattCharacteristic BatteryLevel
+ {
+ get
+ {
+ return batteryLevel;
+ }
+
+ set
+ {
+ if (batteryLevel != value)
+ {
+ batteryLevel = value;
+ OnPropertyChanged(new PropertyChangedEventArgs("BatteryLevel"));
+ }
+ }
+ }
+
+ ///
+ /// Asynchronous initialization
+ ///
+ /// Initialization Task
+ public override async Task Init()
+ {
+ await CreateServiceProvider(GattServiceUuids.Battery);
+
+ // Preparing the Battery Level characteristics
+ GattLocalCharacteristicParameters batteryCharacteristicsParameters = PlainReadNotifyParameters;
+
+ // Set the user descriptions
+ batteryCharacteristicsParameters.UserDescription = "Battery Level percentage remaining";
+
+ // Add presentation format - 16-bit integer, with exponent 0, the unit is percentage, defined per Bluetooth SIG with Microsoft as descriptor
+ batteryCharacteristicsParameters.PresentationFormats.Add(
+ GattPresentationFormat.FromParts(
+ Convert.ToByte(PresentationFormats.FormatTypes.Unsigned8BitInteger),
+ PresentationFormats.Exponent,
+ Convert.ToUInt16(PresentationFormats.Units.Percentage),
+ Convert.ToByte(PresentationFormats.NamespaceId.BluetoothSigAssignedNumber),
+ PresentationFormats.Description));
+
+ // Create the characteristic for the service
+ GattLocalCharacteristicResult result =
+ await ServiceProvider.Service.CreateCharacteristicAsync(
+ GattCharacteristicUuids.BatteryLevel,
+ batteryCharacteristicsParameters);
+
+ // Grab the characterist object from the service set it to the BatteryLevel property which is of a specfic Characteristic type
+ GattLocalCharacteristic baseBatteryLevel = null;
+ GattServicesHelper.GetCharacteristicsFromResult(result, ref baseBatteryLevel);
+
+ if (baseBatteryLevel != null)
+ {
+ BatteryLevel = new Characteristics.BatteryLevelCharacteristic(baseBatteryLevel, this);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/GattServicesLibrary/GattServicesLibrary/Services/BloodPressureService.cs b/GattServicesLibrary/GattServicesLibrary/Services/BloodPressureService.cs
new file mode 100644
index 0000000..bb3209f
--- /dev/null
+++ b/GattServicesLibrary/GattServicesLibrary/Services/BloodPressureService.cs
@@ -0,0 +1,141 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------------------------
+using System;
+using System.Threading.Tasks;
+using GattServicesLibrary.Helpers;
+using Windows.Devices.Bluetooth.GenericAttributeProfile;
+using System.ComponentModel;
+
+namespace GattServicesLibrary.Services
+{
+ ///
+ /// Class for Blood pressure services
+ ///
+ public class BloodPressureService : GenericGattService
+ {
+ ///
+ /// Name of the service
+ ///
+ public override string Name
+ {
+ get
+ {
+ return "Blood Pressure Service";
+ }
+ }
+
+ ///
+ /// The Blood Pressure measurement characteristic is used to send a Blood Pressure measurement.
+ ///
+ private GenericGattCharacteristic bloodPressureMeasurement;
+
+ ///
+ /// Gets or Sets the blood pressure measurement characteristic
+ ///
+ public GenericGattCharacteristic BloodPressureMeasurement
+ {
+ get
+ {
+ return bloodPressureMeasurement;
+ }
+
+ set
+ {
+ if (bloodPressureMeasurement != value)
+ {
+ bloodPressureMeasurement = value;
+ OnPropertyChanged(new PropertyChangedEventArgs("BloodPressureMeasurement"));
+ }
+ }
+ }
+
+ ///
+ /// The Blood Pressure Feature characteristic is used to describe the supported features of the Blood Pressure Sensor.
+ ///
+ private GenericGattCharacteristic bloodPressureFeature;
+
+ ///
+ /// Gets or Sets the blood pressure feature characteristic
+ ///
+ public GenericGattCharacteristic BloodPressureFeature
+ {
+ get
+ {
+ return bloodPressureFeature;
+ }
+
+ set
+ {
+ if (bloodPressureFeature != value)
+ {
+ bloodPressureFeature = value;
+ OnPropertyChanged(new PropertyChangedEventArgs("BloodPressureFeature"));
+ }
+ }
+ }
+
+ ///
+ /// Asynchronous initialization
+ ///
+ /// Initialization Task
+ public override async Task Init()
+ {
+ await CreateServiceProvider(GattServiceUuids.BloodPressure);
+
+ // Preparing the Blood pressure characteristics
+ var bloodPressureCharacteristics = PlainIndicateParameters;
+ bloodPressureCharacteristics.UserDescription = "Blood Pressure in mm Hg";
+ bloodPressureCharacteristics.PresentationFormats.Add(
+ GattPresentationFormat.FromParts(
+ Convert.ToByte(PresentationFormats.FormatTypes.OpaqueStructure),
+ PresentationFormats.Exponent,
+ Convert.ToUInt16(PresentationFormats.Units.PressureMilliMetreofmercury),
+ Convert.ToByte(PresentationFormats.NamespaceId.BluetoothSigAssignedNumber),
+ PresentationFormats.Description));
+
+ // Create the blood pressure measurement characteristic for the service
+ GattLocalCharacteristicResult result =
+ await ServiceProvider.Service.CreateCharacteristicAsync(
+ GattCharacteristicUuids.BloodPressureMeasurement,
+ bloodPressureCharacteristics);
+
+ // Grab the characterist object from the service
+ GattLocalCharacteristic baseBloodPressureMeasurement = null;
+ GattServicesHelper.GetCharacteristicsFromResult(result, ref baseBloodPressureMeasurement);
+
+ if (baseBloodPressureMeasurement != null)
+ {
+ BloodPressureMeasurement = new Characteristics.BloodPressureMeasurementCharacteristic(baseBloodPressureMeasurement, this);
+ }
+
+ result = null;
+
+ // Preparing the Blood pressure feature characteristics
+ var bloodPressureFeatureCharacteristics = PlainReadParameter;
+ bloodPressureFeatureCharacteristics.UserDescription = "The Blood Pressure Feature characteristic is used to describe the supported features of the Blood Pressure Sensor.";
+ bloodPressureFeatureCharacteristics.PresentationFormats.Add(
+ GattPresentationFormat.FromParts(
+ Convert.ToByte(PresentationFormats.FormatTypes.Unsigned16BitInteger),
+ PresentationFormats.Exponent,
+ Convert.ToUInt16(PresentationFormats.Units.Unitless),
+ Convert.ToByte(PresentationFormats.NamespaceId.BluetoothSigAssignedNumber),
+ PresentationFormats.Description));
+
+ // Create the blood pressure measurement characteristic for the service
+ result = await ServiceProvider.Service.CreateCharacteristicAsync(
+ GattCharacteristicUuids.BloodPressureFeature,
+ bloodPressureFeatureCharacteristics);
+
+ // Grab the characterist object from the service
+ GattLocalCharacteristic baseBloodPressureFeature = null;
+ GattServicesHelper.GetCharacteristicsFromResult(result, ref baseBloodPressureFeature);
+
+ if (baseBloodPressureFeature != null)
+ {
+ BloodPressureFeature = new Characteristics.BloodPressureFeatureCharacteristic(baseBloodPressureFeature, this);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/GattServicesLibrary/GattServicesLibrary/Services/CurrentTimeService.cs b/GattServicesLibrary/GattServicesLibrary/Services/CurrentTimeService.cs
new file mode 100644
index 0000000..d9847ef
--- /dev/null
+++ b/GattServicesLibrary/GattServicesLibrary/Services/CurrentTimeService.cs
@@ -0,0 +1,93 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------------------------
+using System;
+using System.Threading.Tasks;
+using GattServicesLibrary.Helpers;
+using Windows.Devices.Bluetooth.GenericAttributeProfile;
+
+namespace GattServicesLibrary.Services
+{
+ ///
+ /// Class for Current time service
+ ///
+ public class CurrentTimeService : GenericGattService
+ {
+ ///
+ /// Name of the service
+ ///
+ public override string Name
+ {
+ get
+ {
+ return "Current Time Service";
+ }
+ }
+
+ ///
+ /// Current time characteristics
+ ///
+ private GattLocalCharacteristic currentTime;
+
+ ///
+ /// Starts the Current time service
+ ///
+ /// True, starts the service as Connectable. False, starts the service as only Discoverable
+ public override async void Start(bool connectable)
+ {
+ await CreateServiceProvider(GattServiceUuids.CurrentTime);
+ GattLocalCharacteristicResult result = await ServiceProvider.Service.CreateCharacteristicAsync(GattCharacteristicUuids.CurrentTime,
+ PlainReadNotifyParameters);
+ GattServicesHelper.GetCharacteristicsFromResult(result, ref currentTime);
+ if (currentTime != null)
+ {
+ currentTime.ReadRequested += ReadCharacteristicReadRequested;
+ }
+
+ base.Start(connectable);
+ }
+
+ ///
+ /// Event handler for reading Current time
+ ///
+ /// The source of the Write request
+ /// Details about the request
+ private async void ReadCharacteristicReadRequested(GattLocalCharacteristic sender, GattReadRequestedEventArgs args)
+ {
+ var request = await args.GetRequestAsync();
+ request.RespondWithValue(GattServicesHelper.ConvertValueToBuffer(DateTime.Now));
+ }
+
+ ///
+ /// Event handler for notifying the current time characteristics value
+ ///
+ public async void NotifyValue()
+ {
+ await currentTime.NotifyValueAsync(GattServicesHelper.ConvertValueToBuffer(DateTime.Now));
+ }
+
+ ///
+ /// Stops the already running Current time services
+ ///
+ public override void Stop()
+ {
+ if (currentTime != null)
+ {
+ currentTime.ReadRequested -= ReadCharacteristicReadRequested;
+ currentTime = null;
+ }
+
+ base.Stop();
+ }
+
+ ///
+ /// Asynchronous initialization
+ ///
+ /// Initialization Task
+ public override Task Init()
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
\ No newline at end of file
diff --git a/GattServicesLibrary/GattServicesLibrary/Services/HeartRateService.cs b/GattServicesLibrary/GattServicesLibrary/Services/HeartRateService.cs
new file mode 100644
index 0000000..086768f
--- /dev/null
+++ b/GattServicesLibrary/GattServicesLibrary/Services/HeartRateService.cs
@@ -0,0 +1,88 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------------------------
+using System;
+using System.Threading.Tasks;
+using GattServicesLibrary.Helpers;
+using Windows.Devices.Bluetooth.GenericAttributeProfile;
+using System.ComponentModel;
+
+namespace GattServicesLibrary.Services
+{
+ ///
+ /// Class for Heart rate service
+ ///
+ public class HeartRateService : GenericGattService
+ {
+ ///
+ /// Name of the service
+ ///
+ public override string Name
+ {
+ get
+ {
+ return "Heart Rate Service";
+ }
+ }
+
+ ///
+ /// This characteristic is used to send a heart rate measurement.
+ ///
+ private GenericGattCharacteristic heartRateMeasurement;
+
+ ///
+ /// Gets or Sets the heart rate characteristic
+ ///
+ public GenericGattCharacteristic HeartRateMeasurement
+ {
+ get
+ {
+ return heartRateMeasurement;
+ }
+
+ set
+ {
+ if (heartRateMeasurement != value)
+ {
+ heartRateMeasurement = value;
+ OnPropertyChanged(new PropertyChangedEventArgs("HeartRateMeasurement"));
+ }
+ }
+ }
+
+ ///
+ /// Starts the Heart rate service
+ ///
+ public override async Task Init()
+ {
+ await CreateServiceProvider(GattServiceUuids.HeartRate);
+
+ // Preparing the Blood pressure characteristics
+ var heartRateCharacteristics = PlainNotifyParameters;
+ heartRateCharacteristics.UserDescription = "Heart Rates in Beats per Minute";
+ heartRateCharacteristics.PresentationFormats.Add(
+ GattPresentationFormat.FromParts(
+ Convert.ToByte(PresentationFormats.FormatTypes.Unsigned16BitInteger),
+ PresentationFormats.Exponent,
+ Convert.ToUInt16(PresentationFormats.Units.PeriodBeatsPerMinute),
+ Convert.ToByte(PresentationFormats.NamespaceId.BluetoothSigAssignedNumber),
+ PresentationFormats.Description));
+
+ // Create the heart rate characteristic for the service
+ GattLocalCharacteristicResult result =
+ await ServiceProvider.Service.CreateCharacteristicAsync(
+ GattCharacteristicUuids.HeartRateMeasurement,
+ PlainNotifyParameters);
+
+ // Grab the characterist object from the service set it to the HeartRate property which is of a specfic Characteristic type
+ GattLocalCharacteristic baseHeartRateMeasurement = null;
+ GattServicesHelper.GetCharacteristicsFromResult(result, ref baseHeartRateMeasurement);
+
+ if (baseHeartRateMeasurement != null)
+ {
+ HeartRateMeasurement = new Characteristics.HeartRateMeasurementCharacteristic(baseHeartRateMeasurement, this);
+ }
+ }
+ }
+}
diff --git a/GattServicesLibrary/GattServicesLibrary/Services/MicrosoftService.cs b/GattServicesLibrary/GattServicesLibrary/Services/MicrosoftService.cs
new file mode 100644
index 0000000..1421938
--- /dev/null
+++ b/GattServicesLibrary/GattServicesLibrary/Services/MicrosoftService.cs
@@ -0,0 +1,331 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------------------------
+using System;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Threading.Tasks;
+using GattServicesLibrary;
+using GattServicesLibrary.Helpers;
+using Windows.Devices.Bluetooth.GenericAttributeProfile;
+using Windows.Storage.Streams;
+
+namespace GattServicesLibrary.Services
+{
+ ///
+ /// Class for Microsoft services
+ ///
+ public class MicrosoftService : GenericGattService
+ {
+ ///
+ /// Name of the service
+ ///
+ public override string Name
+ {
+ get
+ {
+ return "Microsoft Service";
+ }
+ }
+
+ #region GUIDS
+ ///
+ /// Microsoft Service UUID
+ ///
+ public static readonly Guid MSFTServiceUuid = Guid.Parse("34B1CF4D-1069-4AD6-89B6-E161D79BE4D0");
+
+ ///
+ /// UUID for Microsoft service read characteristics
+ ///
+ public static readonly Guid MSFTReadChar = Guid.Parse("34B1CF4D-1069-4AD6-89B6-E161D79BE4D1");
+
+ ///
+ /// UUID for Microsoft service write characteristics
+ ///
+ public static readonly Guid MSFTWriteChar = Guid.Parse("34B1CF4D-1069-4AD6-89B6-E161D79BE4D2");
+
+ ///
+ /// UUID for Microsoft service notify characteristics
+ ///
+ public static readonly Guid MSFTNotifyChar = Guid.Parse("34B1CF4D-1069-4AD6-89B6-E161D79BE4D3");
+
+ ///
+ /// UUID for Microsoft service indicate characteristics
+ ///
+ public static readonly Guid MSFTIndicateChar = Guid.Parse("34B1CF4D-1069-4AD6-89B6-E161D79BE4D4");
+
+ ///
+ /// UUID for Microsoft service reading long characteristics
+ ///
+ public static readonly Guid MSFTLongChar = Guid.Parse("34B1CF4D-1069-4AD6-89B6-E161D79BE4D5");
+
+ #endregion
+
+ #region Characteristics
+ ///
+ /// Microsoft service read characteristics value
+ ///
+ private GenericGattCharacteristic readCharacteristic;
+
+ ///
+ /// Gets or sets the readCharacteristic
+ ///
+ public GenericGattCharacteristic ReadCharacteristic
+ {
+ get
+ {
+ return readCharacteristic;
+ }
+
+ set
+ {
+ if (readCharacteristic != value)
+ {
+ readCharacteristic = value;
+ OnPropertyChanged(new PropertyChangedEventArgs("ReadCharacteristic"));
+ }
+ }
+ }
+
+ ///
+ /// Microsoft service write characteristics value
+ ///
+ private GenericGattCharacteristic writeCharacteristic;
+
+ ///
+ /// Gets or sets the write characteristic
+ ///
+ public GenericGattCharacteristic WriteCharacteristic
+ {
+ get
+ {
+ return writeCharacteristic;
+ }
+
+ set
+ {
+ if (writeCharacteristic != value)
+ {
+ writeCharacteristic = value;
+ OnPropertyChanged(new PropertyChangedEventArgs("WriteCharacteristic"));
+ }
+ }
+ }
+
+ ///
+ /// Microsoft service notify characteristics value
+ ///
+ private GenericGattCharacteristic notifyCharacteristic;
+
+ ///
+ /// Gets or sets the notify characteristic
+ ///
+ public GenericGattCharacteristic NotifyCharacteristic
+ {
+ get
+ {
+ return notifyCharacteristic;
+ }
+
+ set
+ {
+ if (notifyCharacteristic != value)
+ {
+ notifyCharacteristic = value;
+ OnPropertyChanged(new PropertyChangedEventArgs("NotifyCharacteristic"));
+ }
+ }
+ }
+
+ ///
+ /// Microsoft service notify characteristics value
+ ///
+ private GenericGattCharacteristic indicateCharacteristic;
+
+ ///
+ /// Gets or sets the indicate characteristic
+ ///
+ public GenericGattCharacteristic IndicateCharacteristic
+ {
+ get
+ {
+ return indicateCharacteristic;
+ }
+
+ set
+ {
+ if (indicateCharacteristic != value)
+ {
+ indicateCharacteristic = value;
+ OnPropertyChanged(new PropertyChangedEventArgs("IndicateCharacteristic"));
+ }
+ }
+ }
+
+ ///
+ /// Microsoft service long characteristics value
+ ///
+ private GenericGattCharacteristic readLongCharacteristic;
+
+ ///
+ /// Gets or sets the read long characteristic
+ ///
+ public GenericGattCharacteristic ReadLongCharacteristic
+ {
+ get
+ {
+ return readLongCharacteristic;
+ }
+
+ set
+ {
+ if (readLongCharacteristic != value)
+ {
+ readLongCharacteristic = value;
+ OnPropertyChanged(new PropertyChangedEventArgs("ReadLongCharacteristic"));
+ }
+ }
+ }
+
+ #endregion
+
+ ///
+ /// Asynchronous initialization
+ ///
+ /// Initialization Task
+ public override async Task Init()
+ {
+ await CreateServiceProvider(MSFTServiceUuid);
+
+ GattLocalCharacteristicResult result = null;
+
+ // Prepare the Read Characteristic
+ GattLocalCharacteristicParameters readParam = PlainReadParameter;
+ readParam.UserDescription = "Microsoft Read characteristic";
+
+ // Add presentation format - 16-bit integer, with exponent 0, the unit is percentage, defined per Bluetooth SIG with Microsoft as descriptor
+ readParam.PresentationFormats.Add(
+ GattPresentationFormat.FromParts(
+ Convert.ToByte(PresentationFormats.FormatTypes.Signed32BitInteger),
+ PresentationFormats.Exponent,
+ Convert.ToUInt16(PresentationFormats.Units.Unitless),
+ Convert.ToByte(PresentationFormats.NamespaceId.BluetoothSigAssignedNumber),
+ PresentationFormats.Description));
+
+ GattLocalCharacteristic baseReadChar = null;
+ result = await ServiceProvider.Service.CreateCharacteristicAsync(MSFTReadChar, readParam);
+ GattServicesHelper.GetCharacteristicsFromResult(result, ref baseReadChar);
+ if (baseReadChar != null)
+ {
+ ReadCharacteristic = new Characteristics.MicrosoftReadCharacteristic(baseReadChar, this);
+ }
+
+ result = null;
+
+ // Prepare the Write Characteristic
+ GattLocalCharacteristicParameters writeParam = PlainWriteOrWriteWithoutRespondsParameter;
+ writeParam.UserDescription = "Microsoft Write characteristic";
+
+ // Add presentation format - 16-bit integer, with exponent 0, the unit is percentage, defined per Bluetooth SIG with Microsoft as descriptor
+ writeParam.PresentationFormats.Add(
+ GattPresentationFormat.FromParts(
+ Convert.ToByte(PresentationFormats.FormatTypes.UTF8String),
+ PresentationFormats.Exponent,
+ Convert.ToUInt16(PresentationFormats.Units.Unitless),
+ Convert.ToByte(PresentationFormats.NamespaceId.BluetoothSigAssignedNumber),
+ PresentationFormats.Description));
+
+ GattLocalCharacteristic baseWriteChar = null;
+ result = await ServiceProvider.Service.CreateCharacteristicAsync(MSFTWriteChar, writeParam);
+ GattServicesHelper.GetCharacteristicsFromResult(result, ref baseWriteChar);
+ if (baseWriteChar != null)
+ {
+ WriteCharacteristic = new Characteristics.MicrosoftWriteCharacteristic(baseWriteChar, this);
+ }
+
+ result = null;
+
+ // Prepare the Notify Characteristic
+ GattLocalCharacteristicParameters notifyParam = PlainReadNotifyParameters;
+ notifyParam.UserDescription = "Microsoft Notify characteristic";
+
+ // Add presentation format - string, the unit is percentage, defined per Bluetooth SIG with Microsoft as descriptor
+ notifyParam.PresentationFormats.Add(
+ GattPresentationFormat.FromParts(
+ Convert.ToByte(PresentationFormats.FormatTypes.UTF8String),
+ PresentationFormats.Exponent,
+ Convert.ToUInt16(PresentationFormats.Units.Unitless),
+ Convert.ToByte(PresentationFormats.NamespaceId.BluetoothSigAssignedNumber),
+ PresentationFormats.Description));
+
+ GattLocalCharacteristic baseNotifyChar = null;
+ result = await ServiceProvider.Service.CreateCharacteristicAsync(MSFTNotifyChar, notifyParam);
+ GattServicesHelper.GetCharacteristicsFromResult(result, ref baseNotifyChar);
+ if (baseNotifyChar != null)
+ {
+ NotifyCharacteristic = new Characteristics.MicrosoftNotifyCharacteristic(baseNotifyChar, this);
+ }
+
+ result = null;
+
+ // Prepare the Indicate Characteristic
+ GattLocalCharacteristicParameters indicateParam = new GattLocalCharacteristicParameters
+ {
+ CharacteristicProperties = GattCharacteristicProperties.Read | GattCharacteristicProperties.Indicate,
+ WriteProtectionLevel = GattProtectionLevel.Plain,
+ ReadProtectionLevel = GattProtectionLevel.Plain
+ };
+
+ indicateParam.UserDescription = "Microsoft Indicate characteristic";
+
+ // Add presentation format - 16-bit integer, with exponent 0, the unit is percentage, defined per Bluetooth SIG with Microsoft as descriptor
+ indicateParam.PresentationFormats.Add(
+ GattPresentationFormat.FromParts(
+ Convert.ToByte(PresentationFormats.FormatTypes.UTF8String),
+ PresentationFormats.Exponent,
+ Convert.ToUInt16(PresentationFormats.Units.Unitless),
+ Convert.ToByte(PresentationFormats.NamespaceId.BluetoothSigAssignedNumber),
+ PresentationFormats.Description));
+
+ GattLocalCharacteristic baseIndicateChar = null;
+ result = await ServiceProvider.Service.CreateCharacteristicAsync(MSFTIndicateChar, indicateParam);
+ GattServicesHelper.GetCharacteristicsFromResult(result, ref baseIndicateChar);
+ if (baseIndicateChar != null)
+ {
+ IndicateCharacteristic = new Characteristics.MicrosoftNotifyCharacteristic(baseIndicateChar, this);
+ }
+
+ result = null;
+
+ // Prepare the Read Long Characteristic
+ GattLocalCharacteristicParameters longParam = new GattLocalCharacteristicParameters
+ {
+ CharacteristicProperties = GattCharacteristicProperties.Read,
+ WriteProtectionLevel = GattProtectionLevel.Plain,
+ ReadProtectionLevel = GattProtectionLevel.Plain
+ };
+
+ longParam.UserDescription = "Microsoft Read Long characteristic";
+
+ // Add presentation format - 16-bit integer, with exponent 0, the unit is percentage, defined per Bluetooth SIG with Microsoft as descriptor
+ longParam.PresentationFormats.Add(
+ GattPresentationFormat.FromParts(
+ Convert.ToByte(PresentationFormats.FormatTypes.OpaqueStructure),
+ PresentationFormats.Exponent,
+ Convert.ToUInt16(PresentationFormats.Units.Unitless),
+ Convert.ToByte(PresentationFormats.NamespaceId.BluetoothSigAssignedNumber),
+ PresentationFormats.Description));
+
+ GattLocalCharacteristic baseLongReadChar = null;
+ result = await ServiceProvider.Service.CreateCharacteristicAsync(MSFTLongChar, longParam);
+ GattServicesHelper.GetCharacteristicsFromResult(result, ref baseLongReadChar);
+ if (baseLongReadChar != null)
+ {
+ ReadLongCharacteristic = new Characteristics.MicrosoftReadLongCharacteristic(baseLongReadChar, this);
+ }
+
+ result = null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/GattServicesLibrary/GattServicesLibrary/project.json b/GattServicesLibrary/GattServicesLibrary/project.json
new file mode 100644
index 0000000..ec51351
--- /dev/null
+++ b/GattServicesLibrary/GattServicesLibrary/project.json
@@ -0,0 +1,16 @@
+{
+ "dependencies": {
+ "Microsoft.NETCore.UniversalWindowsPlatform": "5.3.3"
+ },
+ "frameworks": {
+ "uap10.0": {}
+ },
+ "runtimes": {
+ "win10-arm": {},
+ "win10-arm-aot": {},
+ "win10-x86": {},
+ "win10-x86-aot": {},
+ "win10-x64": {},
+ "win10-x64-aot": {}
+ }
+}
\ No newline at end of file
diff --git a/SortedObservableCollection/SortedObservableCollection.sln b/SortedObservableCollection/SortedObservableCollection.sln
new file mode 100644
index 0000000..7cdc299
--- /dev/null
+++ b/SortedObservableCollection/SortedObservableCollection.sln
@@ -0,0 +1,62 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.26430.13
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SortedObservableCollection", "SortedObservableCollection\SortedObservableCollection.csproj", "{BE79FC52-6041-4913-B0D4-66C100944904}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SortedObservableCollectionUnitTests", "SortedObservableCollectionUnitTests\SortedObservableCollectionUnitTests.csproj", "{BEB995DE-D866-41CD-B0A8-64CC637C4A9F}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|ARM = Debug|ARM
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|ARM = Release|ARM
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {BE79FC52-6041-4913-B0D4-66C100944904}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {BE79FC52-6041-4913-B0D4-66C100944904}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {BE79FC52-6041-4913-B0D4-66C100944904}.Debug|ARM.ActiveCfg = Debug|ARM
+ {BE79FC52-6041-4913-B0D4-66C100944904}.Debug|ARM.Build.0 = Debug|ARM
+ {BE79FC52-6041-4913-B0D4-66C100944904}.Debug|x64.ActiveCfg = Debug|x64
+ {BE79FC52-6041-4913-B0D4-66C100944904}.Debug|x64.Build.0 = Debug|x64
+ {BE79FC52-6041-4913-B0D4-66C100944904}.Debug|x86.ActiveCfg = Debug|x86
+ {BE79FC52-6041-4913-B0D4-66C100944904}.Debug|x86.Build.0 = Debug|x86
+ {BE79FC52-6041-4913-B0D4-66C100944904}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {BE79FC52-6041-4913-B0D4-66C100944904}.Release|Any CPU.Build.0 = Release|Any CPU
+ {BE79FC52-6041-4913-B0D4-66C100944904}.Release|ARM.ActiveCfg = Release|ARM
+ {BE79FC52-6041-4913-B0D4-66C100944904}.Release|ARM.Build.0 = Release|ARM
+ {BE79FC52-6041-4913-B0D4-66C100944904}.Release|x64.ActiveCfg = Release|x64
+ {BE79FC52-6041-4913-B0D4-66C100944904}.Release|x64.Build.0 = Release|x64
+ {BE79FC52-6041-4913-B0D4-66C100944904}.Release|x86.ActiveCfg = Release|x86
+ {BE79FC52-6041-4913-B0D4-66C100944904}.Release|x86.Build.0 = Release|x86
+ {BEB995DE-D866-41CD-B0A8-64CC637C4A9F}.Debug|Any CPU.ActiveCfg = Debug|x86
+ {BEB995DE-D866-41CD-B0A8-64CC637C4A9F}.Debug|ARM.ActiveCfg = Debug|ARM
+ {BEB995DE-D866-41CD-B0A8-64CC637C4A9F}.Debug|ARM.Build.0 = Debug|ARM
+ {BEB995DE-D866-41CD-B0A8-64CC637C4A9F}.Debug|ARM.Deploy.0 = Debug|ARM
+ {BEB995DE-D866-41CD-B0A8-64CC637C4A9F}.Debug|x64.ActiveCfg = Debug|x64
+ {BEB995DE-D866-41CD-B0A8-64CC637C4A9F}.Debug|x64.Build.0 = Debug|x64
+ {BEB995DE-D866-41CD-B0A8-64CC637C4A9F}.Debug|x64.Deploy.0 = Debug|x64
+ {BEB995DE-D866-41CD-B0A8-64CC637C4A9F}.Debug|x86.ActiveCfg = Debug|x86
+ {BEB995DE-D866-41CD-B0A8-64CC637C4A9F}.Debug|x86.Build.0 = Debug|x86
+ {BEB995DE-D866-41CD-B0A8-64CC637C4A9F}.Debug|x86.Deploy.0 = Debug|x86
+ {BEB995DE-D866-41CD-B0A8-64CC637C4A9F}.Release|Any CPU.ActiveCfg = Release|x86
+ {BEB995DE-D866-41CD-B0A8-64CC637C4A9F}.Release|ARM.ActiveCfg = Release|ARM
+ {BEB995DE-D866-41CD-B0A8-64CC637C4A9F}.Release|ARM.Build.0 = Release|ARM
+ {BEB995DE-D866-41CD-B0A8-64CC637C4A9F}.Release|ARM.Deploy.0 = Release|ARM
+ {BEB995DE-D866-41CD-B0A8-64CC637C4A9F}.Release|x64.ActiveCfg = Release|x64
+ {BEB995DE-D866-41CD-B0A8-64CC637C4A9F}.Release|x64.Build.0 = Release|x64
+ {BEB995DE-D866-41CD-B0A8-64CC637C4A9F}.Release|x64.Deploy.0 = Release|x64
+ {BEB995DE-D866-41CD-B0A8-64CC637C4A9F}.Release|x86.ActiveCfg = Release|x86
+ {BEB995DE-D866-41CD-B0A8-64CC637C4A9F}.Release|x86.Build.0 = Release|x86
+ {BEB995DE-D866-41CD-B0A8-64CC637C4A9F}.Release|x86.Deploy.0 = Release|x86
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/SortedObservableCollection/SortedObservableCollection/Properties/AssemblyInfo.cs b/SortedObservableCollection/SortedObservableCollection/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..38c9245
--- /dev/null
+++ b/SortedObservableCollection/SortedObservableCollection/Properties/AssemblyInfo.cs
@@ -0,0 +1,29 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("SortedObservableCollection")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("SortedObservableCollection")]
+[assembly: AssemblyCopyright("Copyright © 2017")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
+[assembly: ComVisible(false)]
\ No newline at end of file
diff --git a/SortedObservableCollection/SortedObservableCollection/Properties/SortedObservableCollection.rd.xml b/SortedObservableCollection/SortedObservableCollection/Properties/SortedObservableCollection.rd.xml
new file mode 100644
index 0000000..2ea7846
--- /dev/null
+++ b/SortedObservableCollection/SortedObservableCollection/Properties/SortedObservableCollection.rd.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
diff --git a/SortedObservableCollection/SortedObservableCollection/SortedObservableCollection.cs b/SortedObservableCollection/SortedObservableCollection/SortedObservableCollection.cs
new file mode 100644
index 0000000..32f09cc
--- /dev/null
+++ b/SortedObservableCollection/SortedObservableCollection/SortedObservableCollection.cs
@@ -0,0 +1,250 @@
+using System;
+using System.Collections;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Reflection;
+using System.Threading;
+
+namespace SortedObservableCollection
+{
+ ///
+ /// Sorted version of ObserverableCollection.
+ ///
+ /// This code has some semaphores that make it look thread-safe. However, it is not
+ /// and there are some known race conditions. To make a truely thread-safe sorted observerable collection
+ /// this entire class would have to be rewritten and not inherit for ObserverableCollection.
+ ///
+ public class SortedObservableCollection : ObservableCollection
+ {
+ private IComparer comparer = null;
+ private string comparePropertyName = string.Empty;
+ private SemaphoreSlim semaphore = new SemaphoreSlim(1, 1);
+
+ ///
+ /// Compares 2 items based on given compare functions
+ ///
+ ///
+ ///
+ ///
+ private int compare(T x, T y)
+ {
+ if (comparer == null)
+ {
+ return ((IComparable)x).CompareTo(y);
+ }
+ else
+ {
+ return comparer.Compare(x, y);
+ }
+ }
+
+ ///
+ /// Find the current index of an item
+ ///
+ ///
+ ///
+ private int FindCurrIndex(T item)
+ {
+ int ret = this.Count;
+
+ for (int i = 0; i < this.Count; i++)
+ {
+ if (this[i].Equals(item) == true)
+ {
+ ret = i;
+ return ret;
+ }
+ }
+
+ return ret;
+ }
+
+ ///
+ /// Find what index the item needs to move or inserted to
+ ///
+ /// Item to move or insert
+ /// Is this a new item or existing item
+ ///
+ private int FindNewIndex(T item, bool newItem = true)
+ {
+ int index = -1;
+
+ if (this.Count == 0)
+ { // if the collection is empty, just return 0
+ return 0;
+ }
+ if( this.Count == 1)
+ {
+ // if the collection only has 1 item, it's an easy compare
+ if(compare(item, this[0]) >= 0)
+ {
+ return 1;
+ }
+ else
+ {
+ return 0;
+ }
+ }
+
+ int firstLargerItem = -1;
+
+ // Linear search through collection finding the first larger item
+ for (int i = 0; i < this.Count; i++)
+ {
+ if (compare(item, this[i]) < 0)
+ {
+ // break out of loop
+ firstLargerItem = i;
+ break;
+ }
+ }
+
+ if(firstLargerItem == -1)
+ {
+ if(newItem)
+ {
+ index = this.Count;
+ }
+ else
+ {
+ index = this.Count - 1;
+ }
+ }
+ else
+ {
+ index = firstLargerItem;
+ }
+
+ return index;
+ }
+
+ public SortedObservableCollection()
+ {
+ if (typeof(IComparable).IsAssignableFrom(typeof(T)) == false)
+ {
+ throw new InvalidOperationException(String.Format("{0} does not impliment IComparable and no ICompare function is given", typeof(T).Name));
+ }
+ }
+
+ ///
+ /// Constructor for SortedObservableCollection
+ ///
+ /// Compare function used to sort. If none is given then item.compare is used.
+ /// (Optional) Name of the property that has to change for a resort to happen if one property is all that is used.
+ /// If a single property is all that is used to compare then this is checked when an item changes before a resort is calculacted which can
+ /// create large optimizations. If none is given, resort will always be calculated and applied if required.
+ public SortedObservableCollection(IComparer comparer, string comparePropertyName = "")
+ {
+ this.comparer = comparer;
+ this.comparePropertyName = comparePropertyName;
+ }
+
+ private async void NotifyableItem_PropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if(comparePropertyName != String.Empty && e.PropertyName != this.comparePropertyName)
+ if(String.IsNullOrEmpty(comparePropertyName) == false && String.Compare(e.PropertyName, comparePropertyName) != 0)
+ {
+ // The item changed, but not the property used to compare, no need to recalculate the sorting
+ return;
+ }
+
+ try
+ {
+ await semaphore.WaitAsync();
+
+ int curr = FindCurrIndex((T)sender);
+ int next = FindNewIndex((T)sender, false);
+
+ // If the updated item is shifting up, then there is room below it so we have to adjust
+ // the index. The FindNextIndex essentially finds the next largest without knowing where
+ // the current is
+ if (next > 0 && next > curr)
+ {
+ next--;
+ Debug.Assert(next >= 0);
+ }
+
+ if (curr != next)
+ {
+ this.MoveItem(curr, next);
+ }
+ }
+ finally
+ {
+ semaphore.Release();
+ }
+ }
+
+ protected async override void InsertItem(int index, T item)
+ {
+ INotifyPropertyChanged notifyableItem = item as INotifyPropertyChanged;
+
+ if (notifyableItem != null)
+ {
+ notifyableItem.PropertyChanged += NotifyableItem_PropertyChanged;
+ }
+
+ try
+ {
+ await semaphore.WaitAsync();
+ base.InsertItem(FindNewIndex(item), item);
+ }
+ finally
+ {
+ semaphore.Release();
+ }
+
+ }
+
+ protected async override void RemoveItem(int index)
+ {
+ INotifyPropertyChanged notifyableItem = this[index] as INotifyPropertyChanged;
+
+ if (notifyableItem != null)
+ {
+ notifyableItem.PropertyChanged -= NotifyableItem_PropertyChanged;
+ }
+
+ ///This is a known race condition. A new item could get inserted
+ ///after this function is called, before the semaphore is gotten which would
+ ///invalidate the index.
+ try
+ {
+ await semaphore.WaitAsync();
+ base.RemoveItem(index);
+ }
+ finally
+ {
+ semaphore.Release();
+ }
+ }
+
+ protected async override void ClearItems()
+ {
+ try
+ {
+ await semaphore.WaitAsync();
+ base.ClearItems();
+ }
+ finally
+ {
+ semaphore.Release();
+ }
+ }
+
+ protected async override void SetItem(int index, T item)
+ {
+ try
+ {
+ await semaphore.WaitAsync();
+ base.SetItem(index, item);
+ }
+ finally
+ {
+ semaphore.Release();
+ }
+
+ }
+ }
+}
diff --git a/SortedObservableCollection/SortedObservableCollection/SortedObservableCollection.csproj b/SortedObservableCollection/SortedObservableCollection/SortedObservableCollection.csproj
new file mode 100644
index 0000000..e23356b
--- /dev/null
+++ b/SortedObservableCollection/SortedObservableCollection/SortedObservableCollection.csproj
@@ -0,0 +1,125 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {BE79FC52-6041-4913-B0D4-66C100944904}
+ Library
+ Properties
+ SortedObservableCollection
+ SortedObservableCollection
+ en-US
+ UAP
+ 10.0.10586.0
+ 10.0.10586.0
+ 14
+ 512
+ {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
+ prompt
+ 4
+
+
+ AnyCPU
+ pdbonly
+ true
+ bin\Release\
+ TRACE;NETFX_CORE;WINDOWS_UWP
+ prompt
+ 4
+
+
+ x86
+ true
+ bin\x86\Debug\
+ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
+ ;2008
+ full
+ x86
+ false
+ prompt
+
+
+ x86
+ bin\x86\Release\
+ TRACE;NETFX_CORE;WINDOWS_UWP
+ true
+ ;2008
+ pdbonly
+ x86
+ false
+ prompt
+
+
+ ARM
+ true
+ bin\ARM\Debug\
+ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
+ ;2008
+ full
+ ARM
+ false
+ prompt
+
+
+ ARM
+ bin\ARM\Release\
+ TRACE;NETFX_CORE;WINDOWS_UWP
+ true
+ ;2008
+ pdbonly
+ ARM
+ false
+ prompt
+
+
+ x64
+ true
+ bin\x64\Debug\
+ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
+ ;2008
+ full
+ x64
+ false
+ prompt
+
+
+ x64
+ bin\x64\Release\
+ TRACE;NETFX_CORE;WINDOWS_UWP
+ true
+ ;2008
+ pdbonly
+ x64
+ false
+ prompt
+
+
+
+
+
+
+
+
+
+
+
+ 14.0
+
+
+
+
\ No newline at end of file
diff --git a/SortedObservableCollection/SortedObservableCollection/project.json b/SortedObservableCollection/SortedObservableCollection/project.json
new file mode 100644
index 0000000..3242d45
--- /dev/null
+++ b/SortedObservableCollection/SortedObservableCollection/project.json
@@ -0,0 +1,16 @@
+{
+ "dependencies": {
+ "Microsoft.NETCore.UniversalWindowsPlatform": "5.2.3"
+ },
+ "frameworks": {
+ "uap10.0": {}
+ },
+ "runtimes": {
+ "win10-arm": {},
+ "win10-arm-aot": {},
+ "win10-x86": {},
+ "win10-x86-aot": {},
+ "win10-x64": {},
+ "win10-x64-aot": {}
+ }
+}
\ No newline at end of file
diff --git a/SortedObservableCollection/SortedObservableCollectionUnitTests/Assets/LockScreenLogo.scale-200.png b/SortedObservableCollection/SortedObservableCollectionUnitTests/Assets/LockScreenLogo.scale-200.png
new file mode 100644
index 0000000..735f57a
Binary files /dev/null and b/SortedObservableCollection/SortedObservableCollectionUnitTests/Assets/LockScreenLogo.scale-200.png differ
diff --git a/SortedObservableCollection/SortedObservableCollectionUnitTests/Assets/SplashScreen.scale-200.png b/SortedObservableCollection/SortedObservableCollectionUnitTests/Assets/SplashScreen.scale-200.png
new file mode 100644
index 0000000..023e7f1
Binary files /dev/null and b/SortedObservableCollection/SortedObservableCollectionUnitTests/Assets/SplashScreen.scale-200.png differ
diff --git a/SortedObservableCollection/SortedObservableCollectionUnitTests/Assets/Square150x150Logo.scale-200.png b/SortedObservableCollection/SortedObservableCollectionUnitTests/Assets/Square150x150Logo.scale-200.png
new file mode 100644
index 0000000..af49fec
Binary files /dev/null and b/SortedObservableCollection/SortedObservableCollectionUnitTests/Assets/Square150x150Logo.scale-200.png differ
diff --git a/SortedObservableCollection/SortedObservableCollectionUnitTests/Assets/Square44x44Logo.scale-200.png b/SortedObservableCollection/SortedObservableCollectionUnitTests/Assets/Square44x44Logo.scale-200.png
new file mode 100644
index 0000000..ce342a2
Binary files /dev/null and b/SortedObservableCollection/SortedObservableCollectionUnitTests/Assets/Square44x44Logo.scale-200.png differ
diff --git a/SortedObservableCollection/SortedObservableCollectionUnitTests/Assets/Square44x44Logo.targetsize-24_altform-unplated.png b/SortedObservableCollection/SortedObservableCollectionUnitTests/Assets/Square44x44Logo.targetsize-24_altform-unplated.png
new file mode 100644
index 0000000..f6c02ce
Binary files /dev/null and b/SortedObservableCollection/SortedObservableCollectionUnitTests/Assets/Square44x44Logo.targetsize-24_altform-unplated.png differ
diff --git a/SortedObservableCollection/SortedObservableCollectionUnitTests/Assets/StoreLogo.png b/SortedObservableCollection/SortedObservableCollectionUnitTests/Assets/StoreLogo.png
new file mode 100644
index 0000000..7385b56
Binary files /dev/null and b/SortedObservableCollection/SortedObservableCollectionUnitTests/Assets/StoreLogo.png differ
diff --git a/SortedObservableCollection/SortedObservableCollectionUnitTests/Assets/Wide310x150Logo.scale-200.png b/SortedObservableCollection/SortedObservableCollectionUnitTests/Assets/Wide310x150Logo.scale-200.png
new file mode 100644
index 0000000..288995b
Binary files /dev/null and b/SortedObservableCollection/SortedObservableCollectionUnitTests/Assets/Wide310x150Logo.scale-200.png differ
diff --git a/SortedObservableCollection/SortedObservableCollectionUnitTests/Package.appxmanifest b/SortedObservableCollection/SortedObservableCollectionUnitTests/Package.appxmanifest
new file mode 100644
index 0000000..a6172bb
--- /dev/null
+++ b/SortedObservableCollection/SortedObservableCollectionUnitTests/Package.appxmanifest
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+ SortedObservableCollectionUnitTests
+ stfro
+ Assets\StoreLogo.png
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SortedObservableCollection/SortedObservableCollectionUnitTests/Properties/AssemblyInfo.cs b/SortedObservableCollection/SortedObservableCollectionUnitTests/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..9c56679
--- /dev/null
+++ b/SortedObservableCollection/SortedObservableCollectionUnitTests/Properties/AssemblyInfo.cs
@@ -0,0 +1,18 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+[assembly: AssemblyTitle("SortedObservableCollectionUnitTests")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("SortedObservableCollectionUnitTests")]
+[assembly: AssemblyCopyright("Copyright © 2017")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+[assembly: AssemblyMetadata("TargetPlatform","UAP")]
+
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
+[assembly: ComVisible(false)]
\ No newline at end of file
diff --git a/SortedObservableCollection/SortedObservableCollectionUnitTests/Properties/UnitTestApp.rd.xml b/SortedObservableCollection/SortedObservableCollectionUnitTests/Properties/UnitTestApp.rd.xml
new file mode 100644
index 0000000..b3a104b
--- /dev/null
+++ b/SortedObservableCollection/SortedObservableCollectionUnitTests/Properties/UnitTestApp.rd.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SortedObservableCollection/SortedObservableCollectionUnitTests/SortedObservableCollectionUnitTests.cs b/SortedObservableCollection/SortedObservableCollectionUnitTests/SortedObservableCollectionUnitTests.cs
new file mode 100644
index 0000000..8f1294c
--- /dev/null
+++ b/SortedObservableCollection/SortedObservableCollectionUnitTests/SortedObservableCollectionUnitTests.cs
@@ -0,0 +1,274 @@
+
+using System;
+using SortedObservableCollection;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using System.Collections;
+using System.ComponentModel;
+
+namespace SortedObservableCollection
+{
+ class TestObject : INotifyPropertyChanged
+ {
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ private void OnPropertyChanged(PropertyChangedEventArgs e)
+ {
+ if (PropertyChanged != null)
+ {
+ PropertyChanged(this, e);
+ }
+ }
+
+ public TestObject()
+ { }
+
+ public TestObject(int testPropertyValue)
+ {
+ TestProperty = testPropertyValue;
+ }
+
+ private int testProperty;
+
+ public int TestProperty
+ {
+ get
+ {
+ return testProperty;
+ }
+
+ set
+ {
+ if(value != testProperty)
+ {
+ testProperty = value;
+ OnPropertyChanged(new PropertyChangedEventArgs("TestProperty"));
+ }
+ }
+ }
+ }
+
+ class CompareableTestObject : TestObject, IComparable
+ {
+ public CompareableTestObject(int testPropertyValue) : base(testPropertyValue)
+ {
+ }
+
+ public int CompareTo(object obj)
+ {
+ return this.TestProperty.CompareTo(((TestObject)obj).TestProperty);
+ }
+ }
+
+ [TestClass]
+ public class SortedObservableCollectionUnitTests
+ {
+ [TestMethod]
+ public void InitTest()
+ {
+ Assert.ThrowsException(() => new SortedObservableCollection());
+
+ Assert.IsNotNull(new SortedObservableCollection());
+ }
+
+ [TestMethod]
+ public void TwoIntTest1()
+ {
+ SortedObservableCollection collection = new SortedObservableCollection();
+
+ collection.Add(1);
+ collection.Add(0);
+
+ Assert.AreEqual(2, collection.Count);
+
+ Assert.AreEqual(0, collection[0]);
+ Assert.AreEqual(1, collection[1]);
+ }
+
+ [TestMethod]
+ public void TwoTestObjectMoveTest1()
+ {
+ SortedObservableCollection collection = new SortedObservableCollection();
+
+ collection.Add(new CompareableTestObject(5));
+ collection.Add(new CompareableTestObject(3));
+
+ Assert.AreEqual(2, collection.Count);
+
+ Assert.AreEqual(3, collection[0].TestProperty);
+ Assert.AreEqual(5, collection[1].TestProperty);
+
+ collection[1].TestProperty = 2;
+
+ Assert.AreEqual(2, collection[0].TestProperty);
+ Assert.AreEqual(3, collection[1].TestProperty);
+ }
+
+ [TestMethod]
+ public void TwoTestObjectNoMoveTest()
+ {
+ SortedObservableCollection collection = new SortedObservableCollection();
+
+ collection.Add(new CompareableTestObject(5));
+ collection.Add(new CompareableTestObject(3));
+
+ Assert.AreEqual(2, collection.Count);
+
+ Assert.AreEqual(3, collection[0].TestProperty);
+ Assert.AreEqual(5, collection[1].TestProperty);
+
+ collection[1].TestProperty = 7;
+
+ Assert.AreEqual(3, collection[0].TestProperty);
+ Assert.AreEqual(7, collection[1].TestProperty);
+ }
+
+ [TestMethod]
+ public void ReverseIntTest()
+ {
+ SortedObservableCollection collection = new SortedObservableCollection();
+
+ collection.Add(5);
+ collection.Add(4);
+ collection.Add(3);
+ collection.Add(2);
+ collection.Add(1);
+
+
+ Assert.AreEqual(5, collection.Count);
+
+ Assert.AreEqual(1, collection[0]);
+ Assert.AreEqual(2, collection[1]);
+ Assert.AreEqual(3, collection[2]);
+ Assert.AreEqual(4, collection[3]);
+ Assert.AreEqual(5, collection[4]);
+ }
+
+ [TestMethod]
+ public void MixedIntTest()
+ {
+ SortedObservableCollection collection = new SortedObservableCollection();
+
+ collection.Add(1);
+ collection.Add(3);
+ collection.Add(4);
+ collection.Add(2);
+ collection.Add(5);
+
+ Assert.AreEqual(5, collection.Count);
+
+ Assert.AreEqual(1, collection[0]);
+ Assert.AreEqual(2, collection[1]);
+ Assert.AreEqual(3, collection[2]);
+ Assert.AreEqual(4, collection[3]);
+ Assert.AreEqual(5, collection[4]);
+ }
+
+ [TestMethod]
+ public void SameIntTest()
+ {
+ SortedObservableCollection collection = new SortedObservableCollection();
+
+ collection.Add(1);
+ collection.Add(1);
+ collection.Add(1);
+ collection.Add(1);
+ collection.Add(1);
+
+ Assert.AreEqual(5, collection.Count);
+
+ Assert.AreEqual(1, collection[0]);
+ Assert.AreEqual(1, collection[1]);
+ Assert.AreEqual(1, collection[2]);
+ Assert.AreEqual(1, collection[3]);
+ Assert.AreEqual(1, collection[4]);
+ }
+
+
+ [TestMethod]
+ public void ReverseIntWithStartEndRemoveTest()
+ {
+ SortedObservableCollection collection = new SortedObservableCollection();
+
+ collection.Add(5);
+ collection.Add(4);
+ collection.Add(3);
+ collection.Add(2);
+ collection.Add(1);
+
+ collection.Remove(5);
+ collection.Remove(1);
+
+ Assert.AreEqual(3, collection.Count);
+
+ Assert.AreEqual(2, collection[0]);
+ Assert.AreEqual(3, collection[1]);
+ Assert.AreEqual(4, collection[2]);
+ }
+
+ [TestMethod]
+ public void MixedTestObjectWithMoveRemoveStay()
+ {
+ SortedObservableCollection collection = new SortedObservableCollection();
+
+ collection.Add(new CompareableTestObject(5));
+ collection.Add(new CompareableTestObject(3));
+ collection.Add(new CompareableTestObject(1));
+ collection.Add(new CompareableTestObject(4));
+ collection.Add(new CompareableTestObject(2));
+
+ Assert.AreEqual(5, collection.Count);
+
+ collection.Remove(collection[2]); //1, 2, 4, 5
+ collection.RemoveAt(2); //1, 2, 5
+
+ Assert.AreEqual(1, collection[0].TestProperty);
+ Assert.AreEqual(2, collection[1].TestProperty);
+ Assert.AreEqual(5, collection[2].TestProperty);
+
+ collection[2].TestProperty = 5; //1, 2, 5
+
+ Assert.AreEqual(1, collection[0].TestProperty);
+ Assert.AreEqual(2, collection[1].TestProperty);
+ Assert.AreEqual(5, collection[2].TestProperty);
+
+ collection[2].TestProperty = 4; //1, 2, 4
+
+ Assert.AreEqual(1, collection[0].TestProperty);
+ Assert.AreEqual(2, collection[1].TestProperty);
+ Assert.AreEqual(4, collection[2].TestProperty);
+
+ collection[0].TestProperty = 0; //0, 2, 4
+
+ Assert.AreEqual(0, collection[0].TestProperty);
+ Assert.AreEqual(2, collection[1].TestProperty);
+ Assert.AreEqual(4, collection[2].TestProperty);
+
+ collection[2].TestProperty = 5; //0, 2, 5
+
+ Assert.AreEqual(0, collection[0].TestProperty);
+ Assert.AreEqual(2, collection[1].TestProperty);
+ Assert.AreEqual(5, collection[2].TestProperty);
+
+ collection.Add(new CompareableTestObject(1));
+ collection.Add(new CompareableTestObject(3));
+ collection.Add(new CompareableTestObject(5)); //0, 1, 2, 3, 5, 5
+
+ Assert.AreEqual(0, collection[0].TestProperty);
+ Assert.AreEqual(1, collection[1].TestProperty);
+ Assert.AreEqual(2, collection[2].TestProperty);
+ Assert.AreEqual(3, collection[3].TestProperty);
+ Assert.AreEqual(5, collection[4].TestProperty);
+ Assert.AreEqual(5, collection[5].TestProperty);
+
+ collection[5].TestProperty = 4; //0, 1, 2, 3, 4, 5
+
+ Assert.AreEqual(0, collection[0].TestProperty);
+ Assert.AreEqual(1, collection[1].TestProperty);
+ Assert.AreEqual(2, collection[2].TestProperty);
+ Assert.AreEqual(3, collection[3].TestProperty);
+ Assert.AreEqual(4, collection[4].TestProperty);
+ Assert.AreEqual(5, collection[5].TestProperty);
+ }
+
+ }
+}
diff --git a/SortedObservableCollection/SortedObservableCollectionUnitTests/SortedObservableCollectionUnitTests.csproj b/SortedObservableCollection/SortedObservableCollectionUnitTests/SortedObservableCollectionUnitTests.csproj
new file mode 100644
index 0000000..873b4bd
--- /dev/null
+++ b/SortedObservableCollection/SortedObservableCollectionUnitTests/SortedObservableCollectionUnitTests.csproj
@@ -0,0 +1,147 @@
+
+
+
+
+ Debug
+ x86
+ {BEB995DE-D866-41CD-B0A8-64CC637C4A9F}
+ AppContainerExe
+ Properties
+ SortedObservableCollectionUnitTests
+ SortedObservableCollectionUnitTests
+ en-US
+ UAP
+ 10.0.10586.0
+ 10.0.10586.0
+ 14
+ 512
+ {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ SortedObservableCollectionUnitTests_TemporaryKey.pfx
+ $(VisualStudioVersion)
+
+
+ true
+ bin\x86\Debug\
+ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
+ ;2008
+ full
+ x86
+ false
+ prompt
+ true
+
+
+ bin\x86\Release\
+ TRACE;NETFX_CORE;WINDOWS_UWP
+ true
+ ;2008
+ pdbonly
+ x86
+ false
+ prompt
+ true
+ true
+
+
+ true
+ bin\ARM\Debug\
+ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
+ ;2008
+ full
+ ARM
+ false
+ prompt
+ true
+
+
+ bin\ARM\Release\
+ TRACE;NETFX_CORE;WINDOWS_UWP
+ true
+ ;2008
+ pdbonly
+ ARM
+ false
+ prompt
+ true
+ true
+
+
+ true
+ bin\x64\Debug\
+ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
+ ;2008
+ full
+ x64
+ false
+ prompt
+ true
+
+
+ bin\x64\Release\
+ TRACE;NETFX_CORE;WINDOWS_UWP
+ true
+ ;2008
+ pdbonly
+ x64
+ false
+ prompt
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ UnitTestApp.xaml
+
+
+
+
+
+ MSBuild:Compile
+ Designer
+
+
+
+
+ Designer
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {be79fc52-6041-4913-b0d4-66c100944904}
+ SortedObservableCollection
+
+
+
+
+
+
+ 14.0
+
+
+
+
\ No newline at end of file
diff --git a/SortedObservableCollection/SortedObservableCollectionUnitTests/UnitTestApp.xaml b/SortedObservableCollection/SortedObservableCollectionUnitTests/UnitTestApp.xaml
new file mode 100644
index 0000000..cb04877
--- /dev/null
+++ b/SortedObservableCollection/SortedObservableCollectionUnitTests/UnitTestApp.xaml
@@ -0,0 +1,8 @@
+
+
+
diff --git a/SortedObservableCollection/SortedObservableCollectionUnitTests/UnitTestApp.xaml.cs b/SortedObservableCollection/SortedObservableCollectionUnitTests/UnitTestApp.xaml.cs
new file mode 100644
index 0000000..d073b1a
--- /dev/null
+++ b/SortedObservableCollection/SortedObservableCollectionUnitTests/UnitTestApp.xaml.cs
@@ -0,0 +1,100 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices.WindowsRuntime;
+using Windows.ApplicationModel;
+using Windows.ApplicationModel.Activation;
+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;
+
+namespace SortedObservableCollectionUnitTests
+{
+ ///
+ /// Provides application-specific behavior to supplement the default Application class.
+ ///
+ sealed partial class App : Application
+ {
+ ///
+ /// Initializes the singleton application object. This is the first line of authored code
+ /// executed, and as such is the logical equivalent of main() or WinMain().
+ ///
+ public App()
+ {
+ this.InitializeComponent();
+ this.Suspending += OnSuspending;
+ }
+
+ ///
+ /// Invoked when the application is launched normally by the end user. Other entry points
+ /// will be used such as when the application is launched to open a specific file.
+ ///
+ /// Details about the launch request and process.
+ protected override void OnLaunched(LaunchActivatedEventArgs e)
+ {
+
+#if DEBUG
+ if (System.Diagnostics.Debugger.IsAttached)
+ {
+ this.DebugSettings.EnableFrameRateCounter = true;
+ }
+#endif
+
+ Frame rootFrame = Window.Current.Content as Frame;
+
+ // Do not repeat app initialization when the Window already has content,
+ // just ensure that the window is active
+ if (rootFrame == null)
+ {
+ // Create a Frame to act as the navigation context and navigate to the first page
+ rootFrame = new Frame();
+
+ rootFrame.NavigationFailed += OnNavigationFailed;
+
+ if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
+ {
+ }
+
+ // Place the frame in the current Window
+ Window.Current.Content = rootFrame;
+ }
+
+ Microsoft.VisualStudio.TestPlatform.TestExecutor.UnitTestClient.CreateDefaultUI();
+
+ // Ensure the current window is active
+ Window.Current.Activate();
+
+ Microsoft.VisualStudio.TestPlatform.TestExecutor.UnitTestClient.Run(e.Arguments);
+ }
+
+ ///
+ /// Invoked when Navigation to a certain page fails
+ ///
+ /// The Frame which failed navigation
+ /// Details about the navigation failure
+ void OnNavigationFailed(object sender, NavigationFailedEventArgs e)
+ {
+ throw new Exception("Failed to load Page " + e.SourcePageType.FullName);
+ }
+
+ ///
+ /// Invoked when application execution is being suspended. Application state is saved
+ /// without knowing whether the application will be terminated or resumed with the contents
+ /// of memory still intact.
+ ///
+ /// The source of the suspend request.
+ /// Details about the suspend request.
+ private void OnSuspending(object sender, SuspendingEventArgs e)
+ {
+ var deferral = e.SuspendingOperation.GetDeferral();
+ deferral.Complete();
+ }
+ }
+}
diff --git a/SortedObservableCollection/SortedObservableCollectionUnitTests/project.json b/SortedObservableCollection/SortedObservableCollectionUnitTests/project.json
new file mode 100644
index 0000000..3c43e10
--- /dev/null
+++ b/SortedObservableCollection/SortedObservableCollectionUnitTests/project.json
@@ -0,0 +1,18 @@
+{
+ "dependencies": {
+ "Microsoft.NETCore.UniversalWindowsPlatform": "5.2.3",
+ "MSTest.TestAdapter": "1.1.11",
+ "MSTest.TestFramework": "1.1.11"
+ },
+ "frameworks": {
+ "uap10.0": {}
+ },
+ "runtimes": {
+ "win10-arm": {},
+ "win10-arm-aot": {},
+ "win10-x86": {},
+ "win10-x86-aot": {},
+ "win10-x64": {},
+ "win10-x64-aot": {}
+ }
+}
\ No newline at end of file