diff --git a/src/BTDevices.Tests/BTDevices.Tests.csproj b/src/BTDevices.Tests/BTDevices.Tests.csproj
new file mode 100644
index 0000000..5548749
--- /dev/null
+++ b/src/BTDevices.Tests/BTDevices.Tests.csproj
@@ -0,0 +1,155 @@
+
+
+
+
+ Debug
+ AnyCPU
+ 8.0.30703
+ 2.0
+ {5B5BAF9D-3FB9-47F9-AE07-B8CC43EC887C}
+ Library
+ Properties
+ BTDevices.Tests
+ BTDevices.Tests
+ en-US
+ 8.1
+ 12
+ 512
+ {BC8A1FFA-BEE3-4634-8014-F334798102B3};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ BTDevices.Tests_TemporaryKey.pfx
+ Never
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE;NETFX_CORE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE;NETFX_CORE
+ prompt
+ 4
+
+
+ true
+ bin\ARM\Debug\
+ DEBUG;TRACE;NETFX_CORE
+ ;2008
+ full
+ ARM
+ false
+ prompt
+ true
+
+
+ bin\ARM\Release\
+ TRACE;NETFX_CORE
+ true
+ ;2008
+ pdbonly
+ ARM
+ false
+ prompt
+ true
+
+
+ true
+ bin\x64\Debug\
+ DEBUG;TRACE;NETFX_CORE
+ ;2008
+ full
+ x64
+ false
+ prompt
+ true
+
+
+ bin\x64\Release\
+ TRACE;NETFX_CORE
+ true
+ ;2008
+ pdbonly
+ x64
+ false
+ prompt
+ true
+
+
+ true
+ bin\x86\Debug\
+ DEBUG;TRACE;NETFX_CORE
+ ;2008
+ full
+ x86
+ false
+ prompt
+ true
+
+
+ bin\x86\Release\
+ TRACE;NETFX_CORE
+ true
+ ;2008
+ pdbonly
+ x86
+ false
+ prompt
+ true
+
+
+ True
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+ Designer
+
+
+
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+
+
+ {62a55887-10f5-40d2-9352-96246d1b11d3}
+ BTDevices.WinStore
+
+
+
+ 12.0
+
+
+
+
\ No newline at end of file
diff --git a/src/BTDevices.Tests/BTDevices.Tests_TemporaryKey.pfx b/src/BTDevices.Tests/BTDevices.Tests_TemporaryKey.pfx
new file mode 100644
index 0000000..527192e
Binary files /dev/null and b/src/BTDevices.Tests/BTDevices.Tests_TemporaryKey.pfx differ
diff --git a/src/BTDevices.Tests/Images/UnitTestLogo.scale-100.png b/src/BTDevices.Tests/Images/UnitTestLogo.scale-100.png
new file mode 100644
index 0000000..ebd735a
Binary files /dev/null and b/src/BTDevices.Tests/Images/UnitTestLogo.scale-100.png differ
diff --git a/src/BTDevices.Tests/Images/UnitTestSmallLogo.scale-100.png b/src/BTDevices.Tests/Images/UnitTestSmallLogo.scale-100.png
new file mode 100644
index 0000000..92dd105
Binary files /dev/null and b/src/BTDevices.Tests/Images/UnitTestSmallLogo.scale-100.png differ
diff --git a/src/BTDevices.Tests/Images/UnitTestSplashScreen.scale-100.png b/src/BTDevices.Tests/Images/UnitTestSplashScreen.scale-100.png
new file mode 100644
index 0000000..193187f
Binary files /dev/null and b/src/BTDevices.Tests/Images/UnitTestSplashScreen.scale-100.png differ
diff --git a/src/BTDevices.Tests/Images/UnitTestStoreLogo.scale-100.png b/src/BTDevices.Tests/Images/UnitTestStoreLogo.scale-100.png
new file mode 100644
index 0000000..3765186
Binary files /dev/null and b/src/BTDevices.Tests/Images/UnitTestStoreLogo.scale-100.png differ
diff --git a/src/BTDevices.Tests/NmeaMessages.cs b/src/BTDevices.Tests/NmeaMessages.cs
new file mode 100644
index 0000000..6204bcc
--- /dev/null
+++ b/src/BTDevices.Tests/NmeaMessages.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
+using BTDevices.Nmea;
+using BTDevices.Nmea.Gps;
+
+namespace BTDevices.Tests
+{
+ [TestClass]
+ public class NmeaMessages
+ {
+ [TestMethod]
+ public void TestGprmc()
+ {
+ string input = "$GPRMC,123519,A,4807.038,S,01131.000,W,022.4,084.4,230313,003.1,W*6E";
+ var msg = NmeaMessage.Parse(input);
+ Assert.IsInstanceOfType(msg, typeof(Gprmc));
+ Gprmc rmc = (Gprmc)msg;
+ Assert.AreEqual(new DateTime(23, 03, 2013, 12, 35, 19, DateTimeKind.Utc), rmc.FixTime);
+ }
+
+ [TestMethod]
+ public void TestPtlna()
+ {
+ string input = "$PTNLA,HV,002.94,M,288.1,D,008.6,D,002.98,M*74";
+ var msg = NmeaMessage.Parse(input);
+ Assert.IsInstanceOfType(msg, typeof(Nmea.Trimble.LaserRange.Ptnla));
+ Nmea.Trimble.LaserRange.Ptnla ptlna = (Nmea.Trimble.LaserRange.Ptnla)msg;
+ Assert.AreEqual(2.94, ptlna.HorizontalDistance);
+ Assert.AreEqual('M', ptlna.HorizontalDistanceUnits);
+ Assert.AreEqual(288.1, ptlna.HorizontalAngle);
+ Assert.AreEqual('D', ptlna.HorizontalAngleUnits);
+ Assert.AreEqual(8.6, ptlna.VerticalAngle);
+ Assert.AreEqual('D', ptlna.VerticalAngleUnits);
+ Assert.AreEqual(2.98, ptlna.SlopeDistance);
+ Assert.AreEqual('M', ptlna.SlopeDistanceUnits);
+ }
+ }
+}
diff --git a/src/BTDevices.Tests/Package.appxmanifest b/src/BTDevices.Tests/Package.appxmanifest
new file mode 100644
index 0000000..ba88fb9
--- /dev/null
+++ b/src/BTDevices.Tests/Package.appxmanifest
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+ BTDevices.Tests
+ mort5161
+ Images\UnitTestStoreLogo.png
+ BTDevices.Tests
+
+
+
+ 6.3.0
+ 6.3.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/BTDevices.Tests/Properties/AssemblyInfo.cs b/src/BTDevices.Tests/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..0255b2a
--- /dev/null
+++ b/src/BTDevices.Tests/Properties/AssemblyInfo.cs
@@ -0,0 +1,28 @@
+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("BTDevices.Tests")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("BTDevices.Tests")]
+[assembly: AssemblyCopyright("Copyright © 2014")]
+[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")]
diff --git a/src/BTDevices.sln b/src/BTDevices.sln
new file mode 100644
index 0000000..c0d1ed1
--- /dev/null
+++ b/src/BTDevices.sln
@@ -0,0 +1,91 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 2013
+VisualStudioVersion = 12.0.30110.0
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WinPhone", "WinPhone", "{26A0F6A9-4B11-46F4-BB01-50D37D1C3CB4}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WinStore", "WinStore", "{07131E3E-1C4E-41CB-BD14-7950AA858A23}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BTDevices.WinStore", "BTDevices\BTDevices.WinStore.csproj", "{62A55887-10F5-40D2-9352-96246D1B11D3}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BTDevices.Tests", "BTDevices.Tests\BTDevices.Tests.csproj", "{5B5BAF9D-3FB9-47F9-AE07-B8CC43EC887C}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BTDevices.WinPhone", "BTDevices\BTDevices.WinPhone.csproj", "{EA42A713-BC6E-4914-B54B-47C0891B7421}"
+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
+ {62A55887-10F5-40D2-9352-96246D1B11D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {62A55887-10F5-40D2-9352-96246D1B11D3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {62A55887-10F5-40D2-9352-96246D1B11D3}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {62A55887-10F5-40D2-9352-96246D1B11D3}.Debug|ARM.Build.0 = Debug|Any CPU
+ {62A55887-10F5-40D2-9352-96246D1B11D3}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {62A55887-10F5-40D2-9352-96246D1B11D3}.Debug|x64.Build.0 = Debug|Any CPU
+ {62A55887-10F5-40D2-9352-96246D1B11D3}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {62A55887-10F5-40D2-9352-96246D1B11D3}.Debug|x86.Build.0 = Debug|Any CPU
+ {62A55887-10F5-40D2-9352-96246D1B11D3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {62A55887-10F5-40D2-9352-96246D1B11D3}.Release|Any CPU.Build.0 = Release|Any CPU
+ {62A55887-10F5-40D2-9352-96246D1B11D3}.Release|ARM.ActiveCfg = Release|Any CPU
+ {62A55887-10F5-40D2-9352-96246D1B11D3}.Release|ARM.Build.0 = Release|Any CPU
+ {62A55887-10F5-40D2-9352-96246D1B11D3}.Release|x64.ActiveCfg = Release|Any CPU
+ {62A55887-10F5-40D2-9352-96246D1B11D3}.Release|x64.Build.0 = Release|Any CPU
+ {62A55887-10F5-40D2-9352-96246D1B11D3}.Release|x86.ActiveCfg = Release|Any CPU
+ {62A55887-10F5-40D2-9352-96246D1B11D3}.Release|x86.Build.0 = Release|Any CPU
+ {5B5BAF9D-3FB9-47F9-AE07-B8CC43EC887C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5B5BAF9D-3FB9-47F9-AE07-B8CC43EC887C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5B5BAF9D-3FB9-47F9-AE07-B8CC43EC887C}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
+ {5B5BAF9D-3FB9-47F9-AE07-B8CC43EC887C}.Debug|ARM.ActiveCfg = Debug|ARM
+ {5B5BAF9D-3FB9-47F9-AE07-B8CC43EC887C}.Debug|ARM.Build.0 = Debug|ARM
+ {5B5BAF9D-3FB9-47F9-AE07-B8CC43EC887C}.Debug|ARM.Deploy.0 = Debug|ARM
+ {5B5BAF9D-3FB9-47F9-AE07-B8CC43EC887C}.Debug|x64.ActiveCfg = Debug|x64
+ {5B5BAF9D-3FB9-47F9-AE07-B8CC43EC887C}.Debug|x64.Build.0 = Debug|x64
+ {5B5BAF9D-3FB9-47F9-AE07-B8CC43EC887C}.Debug|x64.Deploy.0 = Debug|x64
+ {5B5BAF9D-3FB9-47F9-AE07-B8CC43EC887C}.Debug|x86.ActiveCfg = Debug|x86
+ {5B5BAF9D-3FB9-47F9-AE07-B8CC43EC887C}.Debug|x86.Build.0 = Debug|x86
+ {5B5BAF9D-3FB9-47F9-AE07-B8CC43EC887C}.Debug|x86.Deploy.0 = Debug|x86
+ {5B5BAF9D-3FB9-47F9-AE07-B8CC43EC887C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5B5BAF9D-3FB9-47F9-AE07-B8CC43EC887C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5B5BAF9D-3FB9-47F9-AE07-B8CC43EC887C}.Release|Any CPU.Deploy.0 = Release|Any CPU
+ {5B5BAF9D-3FB9-47F9-AE07-B8CC43EC887C}.Release|ARM.ActiveCfg = Release|ARM
+ {5B5BAF9D-3FB9-47F9-AE07-B8CC43EC887C}.Release|ARM.Build.0 = Release|ARM
+ {5B5BAF9D-3FB9-47F9-AE07-B8CC43EC887C}.Release|ARM.Deploy.0 = Release|ARM
+ {5B5BAF9D-3FB9-47F9-AE07-B8CC43EC887C}.Release|x64.ActiveCfg = Release|x64
+ {5B5BAF9D-3FB9-47F9-AE07-B8CC43EC887C}.Release|x64.Build.0 = Release|x64
+ {5B5BAF9D-3FB9-47F9-AE07-B8CC43EC887C}.Release|x64.Deploy.0 = Release|x64
+ {5B5BAF9D-3FB9-47F9-AE07-B8CC43EC887C}.Release|x86.ActiveCfg = Release|x86
+ {5B5BAF9D-3FB9-47F9-AE07-B8CC43EC887C}.Release|x86.Build.0 = Release|x86
+ {5B5BAF9D-3FB9-47F9-AE07-B8CC43EC887C}.Release|x86.Deploy.0 = Release|x86
+ {EA42A713-BC6E-4914-B54B-47C0891B7421}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {EA42A713-BC6E-4914-B54B-47C0891B7421}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EA42A713-BC6E-4914-B54B-47C0891B7421}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {EA42A713-BC6E-4914-B54B-47C0891B7421}.Debug|ARM.Build.0 = Debug|Any CPU
+ {EA42A713-BC6E-4914-B54B-47C0891B7421}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {EA42A713-BC6E-4914-B54B-47C0891B7421}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {EA42A713-BC6E-4914-B54B-47C0891B7421}.Debug|x86.Build.0 = Debug|Any CPU
+ {EA42A713-BC6E-4914-B54B-47C0891B7421}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {EA42A713-BC6E-4914-B54B-47C0891B7421}.Release|Any CPU.Build.0 = Release|Any CPU
+ {EA42A713-BC6E-4914-B54B-47C0891B7421}.Release|ARM.ActiveCfg = Release|Any CPU
+ {EA42A713-BC6E-4914-B54B-47C0891B7421}.Release|ARM.Build.0 = Release|Any CPU
+ {EA42A713-BC6E-4914-B54B-47C0891B7421}.Release|x64.ActiveCfg = Release|Any CPU
+ {EA42A713-BC6E-4914-B54B-47C0891B7421}.Release|x86.ActiveCfg = Release|Any CPU
+ {EA42A713-BC6E-4914-B54B-47C0891B7421}.Release|x86.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {EA42A713-BC6E-4914-B54B-47C0891B7421} = {26A0F6A9-4B11-46F4-BB01-50D37D1C3CB4}
+ {62A55887-10F5-40D2-9352-96246D1B11D3} = {07131E3E-1C4E-41CB-BD14-7950AA858A23}
+ {5B5BAF9D-3FB9-47F9-AE07-B8CC43EC887C} = {07131E3E-1C4E-41CB-BD14-7950AA858A23}
+ EndGlobalSection
+EndGlobal
diff --git a/src/BTDevices/BTDevices.WinPhone.csproj b/src/BTDevices/BTDevices.WinPhone.csproj
new file mode 100644
index 0000000..ce88bf3
--- /dev/null
+++ b/src/BTDevices/BTDevices.WinPhone.csproj
@@ -0,0 +1,73 @@
+
+
+
+ Debug
+ AnyCPU
+ 10.0.20506
+ 2.0
+ {EA42A713-BC6E-4914-B54B-47C0891B7421}
+ {C089C8C0-30E0-4E22-80C0-CE093F111A43};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}
+ Library
+ Properties
+ BTDevices
+ BTDevices.WinPhone
+ WindowsPhone
+ v8.0
+ $(TargetFrameworkVersion)
+ false
+ true
+ 11.0
+ true
+
+
+ true
+ full
+ false
+ BinWP\Debug\
+ DEBUG;TRACE;SILVERLIGHT;WINDOWS_PHONE
+ true
+ true
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ BinWP\Release\
+ TRACE;SILVERLIGHT;WINDOWS_PHONE
+ true
+ true
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/BTDevices/BTDevices.WinStore.csproj b/src/BTDevices/BTDevices.WinStore.csproj
new file mode 100644
index 0000000..d7e93e0
--- /dev/null
+++ b/src/BTDevices/BTDevices.WinStore.csproj
@@ -0,0 +1,66 @@
+
+
+
+
+ Debug
+ AnyCPU
+ 8.0.30703
+ 2.0
+ {62A55887-10F5-40D2-9352-96246D1B11D3}
+ Library
+ Properties
+ BTDevices
+ BTDevices.WinStore
+ en-US
+ 8.1
+ 12
+ 512
+ {BC8A1FFA-BEE3-4634-8014-F334798102B3};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+
+
+ true
+ full
+ false
+ binWS\Debug\
+ DEBUG;TRACE;NETFX_CORE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ binWS\Release\
+ TRACE;NETFX_CORE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 12.0
+
+
+
+
\ No newline at end of file
diff --git a/src/BTDevices/Device.cs b/src/BTDevices/Device.cs
new file mode 100644
index 0000000..ba5bf10
--- /dev/null
+++ b/src/BTDevices/Device.cs
@@ -0,0 +1,116 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.IO;
+using System.Text;
+using System.Threading.Tasks;
+using Windows.Networking.Sockets;
+#if NETFX_CORE
+using BTDevice = Windows.Devices.Bluetooth.Rfcomm.RfcommDeviceService;
+using Windows.Devices.Bluetooth.Rfcomm;
+using Windows.Networking.Sockets;
+using System.Runtime.InteropServices.WindowsRuntime;
+using Windows.Foundation;
+#else
+using BTDevice = Windows.Networking.Proximity.PeerInformation;
+#endif
+
+namespace BTDevices
+{
+ public abstract class Device : IDisposable
+ {
+ private BTDevice m_device;
+ private StreamSocket socket;
+ System.Threading.CancellationTokenSource tcs;
+ public Device(BTDevice device)
+ {
+ if (device == null)
+ throw new ArgumentNullException("device");
+#if NETFX_CORE
+ if (device.ServiceId.Uuid != RfcommServiceId.SerialPort.Uuid)
+ throw new NotSupportedException("Only SerialPort devices supported");
+#endif
+ m_device = device;
+ }
+ TaskCompletionSource closeTask;
+
+ public async Task StartAsync()
+ {
+ if (tcs != null)
+ return;
+ if (m_device == null)
+ throw new ObjectDisposedException("Device");
+ tcs = new System.Threading.CancellationTokenSource();
+ socket = new StreamSocket();
+ await socket.ConnectAsync(
+#if NETFX_CORE
+ m_device.ConnectionHostName,
+ m_device.ConnectionServiceName);
+#else
+ m_device.HostName, "1");
+ //socket = await Windows.Networking.Proximity.PeerFinder.ConnectAsync(m_device.HostName;
+#endif
+ if (tcs.IsCancellationRequested) //Stop was called while opening device
+ {
+ socket.Dispose();
+ socket = null;
+ throw new TaskCanceledException();
+ } var token = tcs.Token;
+ var _ = Task.Run(async () =>
+ {
+ var stream = socket.InputStream.AsStreamForRead();
+ byte[] buffer = new byte[1024];
+ while (!token.IsCancellationRequested)
+ {
+ int readCount = 0;
+ try
+ {
+ readCount = await stream.ReadAsync(buffer, 0, 1024, token).ConfigureAwait(false);
+ }
+ catch { }
+ if (token.IsCancellationRequested)
+ break;
+ if (readCount > 0)
+ {
+ OnData(buffer.Take(readCount).ToArray());
+ }
+ await Task.Delay(10, token);
+ }
+ if(socket != null)
+ socket.Dispose();
+ if (closeTask != null)
+ closeTask.SetResult(true);
+ });
+ }
+
+ public Task StopAsync()
+ {
+ if (tcs != null)
+ {
+ socket.Dispose();
+ socket = null;
+ closeTask = new TaskCompletionSource();
+ if(tcs != null)
+ tcs.Cancel();
+ tcs = null;
+ return closeTask.Task;
+ }
+ return Task.FromResult(false);
+ }
+
+ protected abstract void OnData(byte[] data);
+
+ public void Dispose()
+ {
+ if (tcs != null)
+ {
+ tcs.Cancel();
+ tcs = null;
+ }
+ m_device = null;
+ if(socket != null)
+ socket.Dispose();
+ socket = null;
+ }
+ }
+}
diff --git a/src/BTDevices/Nmea/Gps/GPGGA.cs b/src/BTDevices/Nmea/Gps/GPGGA.cs
new file mode 100644
index 0000000..2530e70
--- /dev/null
+++ b/src/BTDevices/Nmea/Gps/GPGGA.cs
@@ -0,0 +1,106 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BTDevices.Nmea.Gps
+{
+ ///
+ /// Recommended Minimum
+ ///
+ [NmeaMessageType(Type = "GPGGA")]
+ public class Gpgga : NmeaMessage
+ {
+ public enum FixQuality : int
+ {
+ Invalid = 0,
+ GpsFix = 1,
+ DgpsFix = 2,
+ PpsFix = 3,
+ Rtk = 4,
+ FloatRtk = 5,
+ Estimated = 6,
+ ManualInput = 7,
+ Simulation = 8
+ }
+
+ protected override void LoadMessage(string[] message)
+ {
+ var time = message[0];
+ Latitude = int.Parse(message[1].Substring(0, 2), CultureInfo.InvariantCulture) + double.Parse(message[1].Substring(2), CultureInfo.InvariantCulture) / 60;
+ if (message[2] == "S")
+ Latitude *= -1;
+ Longitude = int.Parse(message[3].Substring(0, 3), CultureInfo.InvariantCulture) + double.Parse(message[3].Substring(3), CultureInfo.InvariantCulture) / 60;
+ if (message[4] == "W")
+ Latitude *= -1;
+ Quality = (FixQuality)int.Parse(message[5], CultureInfo.InvariantCulture);
+ NumberOfSatellites = int.Parse(message[6], CultureInfo.InvariantCulture);
+ Hdop = double.Parse(message[7], CultureInfo.InvariantCulture);
+ Altitude = double.Parse(message[8], CultureInfo.InvariantCulture);
+ AltitudeUnits = message[9];
+ HeightOfGeoid = double.Parse(message[10], CultureInfo.InvariantCulture);
+ HeightOfGeoidUnits = message[11];
+ if (message[12].Length > 0)
+ TimeSinceLastDgpsUpdate = TimeSpan.FromSeconds(int.Parse(message[12], CultureInfo.InvariantCulture));
+ if (message[13].Length > 0)
+ DgpsStationID = int.Parse(message[13], CultureInfo.InvariantCulture);
+ }
+
+ ///
+ /// Latitude
+ ///
+ public double Latitude { get; private set; }
+
+ ///
+ /// Longitude
+ ///
+ public double Longitude { get; private set; }
+
+ ///
+ /// Fix Quality
+ ///
+ public FixQuality Quality { get; private set; }
+
+ ///
+ /// Number of satellites being tracked
+ ///
+ public int NumberOfSatellites { get; private set; }
+
+ ///
+ /// Horizontal Dilution of Precision
+ ///
+ public double Hdop { get; private set; }
+
+ ///
+ /// Altitude
+ ///
+ public double Altitude { get; private set; }
+
+ ///
+ /// Altitude units ('M' for Meters)
+ ///
+ public string AltitudeUnits { get; private set; }
+
+ ///
+ /// Height of geoid (mean sea level) above WGS84
+ ///
+ public double HeightOfGeoid { get; private set; }
+
+ ///
+ /// Altitude units ('M' for Meters)
+ ///
+ public string HeightOfGeoidUnits { get; private set; }
+
+ ///
+ /// Time since last DGPS update
+ ///
+ public TimeSpan TimeSinceLastDgpsUpdate { get; set; }
+
+ ///
+ /// DGPS Station ID Number
+ ///
+ public int DgpsStationID { get; set; }
+ }
+}
diff --git a/src/BTDevices/Nmea/Gps/GPRMC.cs b/src/BTDevices/Nmea/Gps/GPRMC.cs
new file mode 100644
index 0000000..1353947
--- /dev/null
+++ b/src/BTDevices/Nmea/Gps/GPRMC.cs
@@ -0,0 +1,73 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BTDevices.Nmea.Gps
+{
+ ///
+ /// Recommended Minimum
+ ///
+ [NmeaMessageType(Type = "GPRMC")]
+ public class Gprmc : NmeaMessage
+ {
+ protected override void LoadMessage(string[] message)
+ {
+ FixTime = new DateTime(int.Parse(message[8].Substring(4, 2)) + 2000,
+ int.Parse(message[8].Substring(2, 2)),
+ int.Parse(message[8].Substring(0, 2)),
+ int.Parse(message[0].Substring(0, 2)),
+ int.Parse(message[0].Substring(2, 2)),
+ int.Parse(message[0].Substring(4, 2)), DateTimeKind.Utc);
+ Active = (message[1] == "A");
+ Latitude = int.Parse(message[2].Substring(0, 2), CultureInfo.InvariantCulture) + double.Parse(message[2].Substring(2), CultureInfo.InvariantCulture) / 60;
+ if (message[3] == "S")
+ Latitude *= -1;
+ Longitude = int.Parse(message[4].Substring(0, 3), CultureInfo.InvariantCulture) + double.Parse(message[4].Substring(3), CultureInfo.InvariantCulture) / 60;
+ if (message[5] == "W")
+ Latitude *= -1;
+ Speed = double.Parse(message[6], CultureInfo.InvariantCulture);
+ Course = double.Parse(message[7], CultureInfo.InvariantCulture);
+ MagneticVariation = double.Parse(message[9], CultureInfo.InvariantCulture);
+ if (message[10] == "W")
+ MagneticVariation *= -1;
+ }
+
+ ///
+ /// Fix Time
+ ///
+ public DateTime FixTime { get; private set; }
+
+ ///
+ /// Gets a value whether the device is active
+ ///
+ public bool Active { get; private set; }
+
+ ///
+ /// Latitude
+ ///
+ public double Latitude { get; private set; }
+
+ ///
+ /// Longitude
+ ///
+ public double Longitude { get; private set; }
+
+ ///
+ /// Speed over the ground in knots
+ ///
+ public double Speed { get; private set; }
+
+ ///
+ /// Track angle in degrees True
+ ///
+ public double Course { get; private set; }
+
+ ///
+ /// Magnetic Variation
+ ///
+ public double MagneticVariation { get; private set; }
+ }
+}
diff --git a/src/BTDevices/Nmea/Gps/Garmin/PGRME.cs b/src/BTDevices/Nmea/Gps/Garmin/PGRME.cs
new file mode 100644
index 0000000..0baae9e
--- /dev/null
+++ b/src/BTDevices/Nmea/Gps/Garmin/PGRME.cs
@@ -0,0 +1,56 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BTDevices.Nmea.Gps
+{
+ ///
+ /// Recommended Minimum
+ ///
+ [NmeaMessageType(Type = "PGRME")]
+ public class Pgrme : NmeaMessage
+ {
+ protected override void LoadMessage(string[] message)
+ {
+ HorizontalError = double.Parse(message[0], CultureInfo.InvariantCulture);
+ HorizontalErrorUnits = message[1];
+ VerticalError = double.Parse(message[2], CultureInfo.InvariantCulture);
+ VerticalErrorUnits = message[3];
+ SphericalError = double.Parse(message[4], CultureInfo.InvariantCulture);
+ SphericalErrorUnits = message[5];
+ }
+
+ ///
+ /// Estimated horizontal position error in meters (HPE)
+ ///
+ public double HorizontalError { get; private set; }
+
+ ///
+ /// Horizontal Error unit ('M' for Meters)
+ ///
+ public string HorizontalErrorUnits { get; private set; }
+
+ ///
+ /// Estimated vertical position error in meters (VPE)
+ ///
+ public double VerticalError { get; private set; }
+
+ ///
+ /// Vertical Error unit ('M' for Meters)
+ ///
+ public string VerticalErrorUnits { get; private set; }
+
+ ///
+ /// Overall spherical equivalent position error
+ ///
+ public double SphericalError { get; private set; }
+
+ ///
+ /// Spherical Error unit ('M' for Meters)
+ ///
+ public string SphericalErrorUnits { get; private set; }
+ }
+}
diff --git a/src/BTDevices/Nmea/LaserRangeMessage.cs b/src/BTDevices/Nmea/LaserRangeMessage.cs
new file mode 100644
index 0000000..9102b21
--- /dev/null
+++ b/src/BTDevices/Nmea/LaserRangeMessage.cs
@@ -0,0 +1,46 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BTDevices.Nmea
+{
+ ///
+ /// Laser Range Measurement
+ ///
+ public abstract class LaserRangeMessage : NmeaMessage
+ {
+ protected override void LoadMessage(string[] message)
+ {
+ HorizontalVector = message[0];
+ HorizontalDistance = double.Parse(message[1], CultureInfo.InvariantCulture);
+ HorizontalDistanceUnits = message[2][0];
+ HorizontalAngle = double.Parse(message[3], CultureInfo.InvariantCulture);
+ HorizontalAngleUnits = message[4][0];
+ VerticalAngle = double.Parse(message[5], CultureInfo.InvariantCulture);
+ VerticalAngleUnits = message[6][0];
+ SlopeDistance = double.Parse(message[7], CultureInfo.InvariantCulture);
+ SlopeDistanceUnits = message[8][0];
+ }
+
+ public string HorizontalVector { get; private set; }
+
+ public double HorizontalDistance { get; private set; }
+
+ public char HorizontalDistanceUnits { get; private set; }
+
+ public double HorizontalAngle { get; private set; }
+
+ public char HorizontalAngleUnits { get; private set; }
+
+ public double VerticalAngle { get; private set; }
+
+ public char VerticalAngleUnits { get; private set; }
+
+ public double SlopeDistance { get; private set; }
+
+ public char SlopeDistanceUnits { get; private set; }
+ }
+}
diff --git a/src/BTDevices/Nmea/LaserTech/LaserRange/PLTIT.cs b/src/BTDevices/Nmea/LaserTech/LaserRange/PLTIT.cs
new file mode 100644
index 0000000..9e8a3f2
--- /dev/null
+++ b/src/BTDevices/Nmea/LaserTech/LaserRange/PLTIT.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BTDevices.Nmea.LaserTech.LaserRange
+{
+ ///
+ /// Laser Range
+ ///
+ [NmeaMessageType(Type = "PLTIT")]
+ public class Pltit : LaserRangeMessage
+ {
+ }
+}
diff --git a/src/BTDevices/Nmea/NmeaMessage.cs b/src/BTDevices/Nmea/NmeaMessage.cs
new file mode 100644
index 0000000..69bfab7
--- /dev/null
+++ b/src/BTDevices/Nmea/NmeaMessage.cs
@@ -0,0 +1,97 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BTDevices.Nmea
+{
+ public class NmeaMessageType : Attribute { public string Type { get; set; } }
+
+ public abstract class NmeaMessage
+ {
+ public static NmeaMessage Parse(string message)
+ {
+ int checksum = -1;
+ if (message[0] != '$')
+ throw new ArgumentException("Invalid nmea message: Missing starting character '$'");
+ var idx = message.IndexOf('*');
+ if (idx >= 0)
+ {
+ checksum = Convert.ToInt32(message.Substring(idx + 1), 16);
+ message = message.Substring(0, message.IndexOf('*'));
+ }
+ if (checksum > -1)
+ {
+ int checksumTest = 0;
+ for (int i = 1; i < message.Length; i++)
+ {
+ if (i == 0) continue;
+ checksumTest ^= Convert.ToByte(message[i]);
+ }
+ if (checksum != checksumTest)
+ throw new ArgumentException("Invalid nmea message: Checksum failure");
+ }
+
+ string[] parts = message.Split(new char[] { ',' });
+ string MessageType = parts[0].Substring(1);
+ string[] MessageParts = parts.Skip(1).ToArray();
+ if(messageTypes == null)
+ {
+ LoadResponseTypes();
+ }
+ NmeaMessage msg = null;
+ if (messageTypes.ContainsKey(MessageType))
+ {
+ msg = (NmeaMessage)messageTypes[MessageType].Invoke(new object[] { });
+ }
+ else
+ {
+ msg = new UnknownMessage();
+ }
+ msg.MessageType = MessageType;
+ msg.MessageParts = MessageParts;
+ msg.LoadMessage(MessageParts);
+ return msg;
+ }
+
+ private static void LoadResponseTypes()
+ {
+ messageTypes = new Dictionary();
+ var typeinfo = typeof(NmeaMessage).GetTypeInfo();
+ foreach (var subclass in typeinfo.Assembly.DefinedTypes.Where(t => t.IsSubclassOf(typeof(NmeaMessage))))
+ {
+ var attr = subclass.GetCustomAttribute(false);
+ if (attr != null)
+ {
+ if (!subclass.IsAbstract)
+ {
+ foreach (var c in subclass.DeclaredConstructors)
+ {
+ var pinfo = c.GetParameters();
+ if (pinfo.Length == 0)
+ {
+ messageTypes.Add(attr.Type, c);
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private static Dictionary messageTypes;
+
+ protected string[] MessageParts { get; private set; }
+
+ public string MessageType { get; private set; }
+
+ protected virtual void LoadMessage(string[] message) { MessageParts = message; }
+
+ public override string ToString()
+ {
+ return string.Format("${0},{1}", MessageType, string.Join(",", MessageParts));
+ }
+ }
+}
diff --git a/src/BTDevices/Nmea/Trimble/LaserRange/PTNLA.cs b/src/BTDevices/Nmea/Trimble/LaserRange/PTNLA.cs
new file mode 100644
index 0000000..ce2f80e
--- /dev/null
+++ b/src/BTDevices/Nmea/Trimble/LaserRange/PTNLA.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BTDevices.Nmea.Trimble.LaserRange
+{
+ ///
+ /// Burden finder
+ ///
+ [NmeaMessageType(Type = "PTNLA")]
+ public class Ptnla : LaserRangeMessage
+ {
+ }
+}
diff --git a/src/BTDevices/Nmea/Trimble/LaserRange/PTNLB.cs b/src/BTDevices/Nmea/Trimble/LaserRange/PTNLB.cs
new file mode 100644
index 0000000..31635c7
--- /dev/null
+++ b/src/BTDevices/Nmea/Trimble/LaserRange/PTNLB.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BTDevices.Nmea.Trimble.LaserRange
+{
+ ///
+ /// Tree Measurement
+ ///
+ [NmeaMessageType(Type = "PTNLB")]
+ public class Ptnlb : NmeaMessage
+ {
+ protected override void LoadMessage(string[] message)
+ {
+ TreeHeight = message[0];
+ MeasuredTreeHeight = double.Parse(message[1], CultureInfo.InvariantCulture);
+ MeasuredTreeHeightUnits = message[2][0];
+ TreeDiameter = message[3];
+ MeasuredTreeDiameter = double.Parse(message[4], CultureInfo.InvariantCulture);
+ MeasuredTreeDiameterUnits = message[5][0];
+ }
+
+ public string TreeHeight { get; private set; }
+
+ public double MeasuredTreeHeight { get; private set; }
+
+ public char MeasuredTreeHeightUnits { get; private set; }
+
+ public string TreeDiameter { get; private set; }
+
+ public double MeasuredTreeDiameter { get; private set; }
+
+ public char MeasuredTreeDiameterUnits { get; private set; }
+
+ //more to do...
+
+ }
+}
diff --git a/src/BTDevices/Nmea/UnknownMessage.cs b/src/BTDevices/Nmea/UnknownMessage.cs
new file mode 100644
index 0000000..cd4ada5
--- /dev/null
+++ b/src/BTDevices/Nmea/UnknownMessage.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BTDevices.Nmea
+{
+ public class UnknownMessage : NmeaMessage
+ {
+ public string[] Values { get { return base.MessageParts; } }
+ protected override void LoadMessage(string[] message)
+ {
+ }
+ }
+}
diff --git a/src/BTDevices/NmeaDevice.cs b/src/BTDevices/NmeaDevice.cs
new file mode 100644
index 0000000..a0b8057
--- /dev/null
+++ b/src/BTDevices/NmeaDevice.cs
@@ -0,0 +1,55 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+#if NETFX_CORE
+using BTDevice = Windows.Devices.Bluetooth.Rfcomm.RfcommDeviceService;
+using Windows.Devices.Bluetooth.Rfcomm;
+#else
+using BTDevice = Windows.Networking.Proximity.PeerInformation;
+#endif
+using Windows.Foundation;
+
+namespace BTDevices
+{
+ public class NmeaDevice : Device
+ {
+ private string message = "";
+ public NmeaDevice(BTDevice device)
+ : base(device)
+ {
+ }
+ protected override void OnData(byte[] data)
+ {
+ var nmea = System.Text.Encoding.UTF8.GetString(data, 0, data.Length);
+ message += nmea;
+ var lineEnd = message.IndexOf("\n");
+ if (lineEnd > -1)
+ {
+ string line = message.Substring(0, lineEnd);
+ message = message.Substring(lineEnd).Trim();
+ ProcessMessage(line.Trim());
+ }
+ }
+
+ private void ProcessMessage(string p)
+ {
+ try
+ {
+ var msg = BTDevices.Nmea.NmeaMessage.Parse(p);
+ if (msg != null)
+ OnMessageReceived(msg);
+ }
+ catch { }
+ }
+
+ private void OnMessageReceived(Nmea.NmeaMessage msg)
+ {
+ if (MessageReceived != null)
+ MessageReceived(this, msg);
+ }
+
+ public event TypedEventHandler MessageReceived;
+ }
+}
diff --git a/src/BTDevices/Properties/AssemblyInfo.cs b/src/BTDevices/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..4256cd8
--- /dev/null
+++ b/src/BTDevices/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("BTDevices")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("BTDevices")]
+[assembly: AssemblyCopyright("Copyright © 2014")]
+[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