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