diff --git a/.github/workflows/CIBuild.yml b/.github/workflows/CIBuild.yml index 0aea018..913ffd6 100644 --- a/.github/workflows/CIBuild.yml +++ b/.github/workflows/CIBuild.yml @@ -18,8 +18,13 @@ jobs: - name: Clone NmeaParser uses: actions/checkout@v1 + - name: Setup Visual Studio Command Prompt + uses: warrenbuckley/Setup-MSBuild@v1 + - name: Build run: | - call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\Tools\VsDevCmd.bat" - msbuild /restore /t:Build src/NmeaParser.sln /p:Configuration=Release /p:JavaSdkDirectory="$(JAVA_HOME_8_X64)" + msbuild /restore /t:Build src/NmeaParser.sln /p:Configuration=Release + - name: Tests + run: | + dotnet test src/UnitTests/NmeaParser.Tests.NET/bin/Release/netcoreapp3.1/NmeaParser.Tests.Net.dll -v normal \ No newline at end of file diff --git a/src/NmeaParser.sln b/src/NmeaParser.sln index 2e70d94..7eff29c 100644 --- a/src/NmeaParser.sln +++ b/src/NmeaParser.sln @@ -1,6 +1,6 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 16 +# Visual Studio Version 16 VisualStudioVersion = 16.0.0.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "NmeaParser", "NmeaParser", "{1701F3BA-A09C-4706-A612-24FD9340FC18}" @@ -34,18 +34,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NmeaParser.Tests.UWP", "Uni EndProject Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "NmeaParser.Tests", "UnitTests\NmeaParser.Tests\NmeaParser.Tests.shproj", "{979AE182-EB59-4181-9D45-3FD6E4817F11}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NmeaParser.Tests.NET45", "UnitTests\NmeaParser.Tests.NET45\NmeaParser.Tests.NET45.csproj", "{170EE734-37F0-425F-822B-B865348ECEC6}" - ProjectSection(ProjectDependencies) = postProject - {1ADC3666-1DDB-48C4-9811-1E58B6D09A7C} = {1ADC3666-1DDB-48C4-9811-1E58B6D09A7C} - EndProjectSection -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Android", "Android", "{456E7573-3324-43CB-8BA0-8D9C300EEB50}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleApp.Droid", "SampleApp.Droid\SampleApp.Droid.csproj", "{48540D33-4349-42D2-9D49-144A7049565A}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NmeaParser.Tests.Net", "UnitTests\NmeaParser.Tests.NET\NmeaParser.Tests.Net.csproj", "{73EFB2EF-DE40-46C4-9685-745A9815C0D2}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution - UnitTests\NmeaParser.Tests\NmeaParser.Tests.projitems*{170ee734-37f0-425f-822b-b865348ecec6}*SharedItemsImports = 4 UnitTests\NmeaParser.Tests\NmeaParser.Tests.projitems*{92cad93b-6c3b-45a0-a723-be046de50fec}*SharedItemsImports = 4 UnitTests\NmeaParser.Tests\NmeaParser.Tests.projitems*{979ae182-eb59-4181-9d45-3fd6e4817f11}*SharedItemsImports = 13 EndGlobalSection @@ -138,22 +134,6 @@ Global {92CAD93B-6C3B-45A0-A723-BE046DE50FEC}.Release|x86.ActiveCfg = Release|x86 {92CAD93B-6C3B-45A0-A723-BE046DE50FEC}.Release|x86.Build.0 = Release|x86 {92CAD93B-6C3B-45A0-A723-BE046DE50FEC}.Release|x86.Deploy.0 = Release|x86 - {170EE734-37F0-425F-822B-B865348ECEC6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {170EE734-37F0-425F-822B-B865348ECEC6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {170EE734-37F0-425F-822B-B865348ECEC6}.Debug|ARM.ActiveCfg = Debug|Any CPU - {170EE734-37F0-425F-822B-B865348ECEC6}.Debug|ARM.Build.0 = Debug|Any CPU - {170EE734-37F0-425F-822B-B865348ECEC6}.Debug|x64.ActiveCfg = Debug|Any CPU - {170EE734-37F0-425F-822B-B865348ECEC6}.Debug|x64.Build.0 = Debug|Any CPU - {170EE734-37F0-425F-822B-B865348ECEC6}.Debug|x86.ActiveCfg = Debug|Any CPU - {170EE734-37F0-425F-822B-B865348ECEC6}.Debug|x86.Build.0 = Debug|Any CPU - {170EE734-37F0-425F-822B-B865348ECEC6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {170EE734-37F0-425F-822B-B865348ECEC6}.Release|Any CPU.Build.0 = Release|Any CPU - {170EE734-37F0-425F-822B-B865348ECEC6}.Release|ARM.ActiveCfg = Release|Any CPU - {170EE734-37F0-425F-822B-B865348ECEC6}.Release|ARM.Build.0 = Release|Any CPU - {170EE734-37F0-425F-822B-B865348ECEC6}.Release|x64.ActiveCfg = Release|Any CPU - {170EE734-37F0-425F-822B-B865348ECEC6}.Release|x64.Build.0 = Release|Any CPU - {170EE734-37F0-425F-822B-B865348ECEC6}.Release|x86.ActiveCfg = Release|Any CPU - {170EE734-37F0-425F-822B-B865348ECEC6}.Release|x86.Build.0 = Release|Any CPU {48540D33-4349-42D2-9D49-144A7049565A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {48540D33-4349-42D2-9D49-144A7049565A}.Debug|Any CPU.Build.0 = Debug|Any CPU {48540D33-4349-42D2-9D49-144A7049565A}.Debug|Any CPU.Deploy.0 = Debug|Any CPU @@ -178,6 +158,22 @@ Global {48540D33-4349-42D2-9D49-144A7049565A}.Release|x86.ActiveCfg = Release|Any CPU {48540D33-4349-42D2-9D49-144A7049565A}.Release|x86.Build.0 = Release|Any CPU {48540D33-4349-42D2-9D49-144A7049565A}.Release|x86.Deploy.0 = Release|Any CPU + {73EFB2EF-DE40-46C4-9685-745A9815C0D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {73EFB2EF-DE40-46C4-9685-745A9815C0D2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {73EFB2EF-DE40-46C4-9685-745A9815C0D2}.Debug|ARM.ActiveCfg = Debug|Any CPU + {73EFB2EF-DE40-46C4-9685-745A9815C0D2}.Debug|ARM.Build.0 = Debug|Any CPU + {73EFB2EF-DE40-46C4-9685-745A9815C0D2}.Debug|x64.ActiveCfg = Debug|Any CPU + {73EFB2EF-DE40-46C4-9685-745A9815C0D2}.Debug|x64.Build.0 = Debug|Any CPU + {73EFB2EF-DE40-46C4-9685-745A9815C0D2}.Debug|x86.ActiveCfg = Debug|Any CPU + {73EFB2EF-DE40-46C4-9685-745A9815C0D2}.Debug|x86.Build.0 = Debug|Any CPU + {73EFB2EF-DE40-46C4-9685-745A9815C0D2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {73EFB2EF-DE40-46C4-9685-745A9815C0D2}.Release|Any CPU.Build.0 = Release|Any CPU + {73EFB2EF-DE40-46C4-9685-745A9815C0D2}.Release|ARM.ActiveCfg = Release|Any CPU + {73EFB2EF-DE40-46C4-9685-745A9815C0D2}.Release|ARM.Build.0 = Release|Any CPU + {73EFB2EF-DE40-46C4-9685-745A9815C0D2}.Release|x64.ActiveCfg = Release|Any CPU + {73EFB2EF-DE40-46C4-9685-745A9815C0D2}.Release|x64.Build.0 = Release|Any CPU + {73EFB2EF-DE40-46C4-9685-745A9815C0D2}.Release|x86.ActiveCfg = Release|Any CPU + {73EFB2EF-DE40-46C4-9685-745A9815C0D2}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -191,9 +187,9 @@ Global {1ADC3666-1DDB-48C4-9811-1E58B6D09A7C} = {1701F3BA-A09C-4706-A612-24FD9340FC18} {92CAD93B-6C3B-45A0-A723-BE046DE50FEC} = {28B8E327-C504-4E08-B2CE-09D1CBB8B904} {979AE182-EB59-4181-9D45-3FD6E4817F11} = {28B8E327-C504-4E08-B2CE-09D1CBB8B904} - {170EE734-37F0-425F-822B-B865348ECEC6} = {28B8E327-C504-4E08-B2CE-09D1CBB8B904} {456E7573-3324-43CB-8BA0-8D9C300EEB50} = {A4B9D59A-C8C6-4199-A7F3-F3AF0C748281} {48540D33-4349-42D2-9D49-144A7049565A} = {456E7573-3324-43CB-8BA0-8D9C300EEB50} + {73EFB2EF-DE40-46C4-9685-745A9815C0D2} = {28B8E327-C504-4E08-B2CE-09D1CBB8B904} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {03788B53-C0BF-485B-AA19-A9EAB0E9AF7B} diff --git a/src/NmeaParser/BluetoothDevice.Android.cs b/src/NmeaParser/BluetoothDevice.Android.cs index 0f1d56a..12c893a 100644 --- a/src/NmeaParser/BluetoothDevice.Android.cs +++ b/src/NmeaParser/BluetoothDevice.Android.cs @@ -22,11 +22,11 @@ using Android.Bluetooth; namespace NmeaParser { - /// - /// A Bluetooth NMEA device - /// - public class BluetoothDevice : NmeaDevice - { + /// + /// A Bluetooth NMEA device + /// + public class BluetoothDevice : NmeaDevice + { private static Java.Util.UUID SERIAL_UUID = Java.Util.UUID.FromString("00001101-0000-1000-8000-00805F9B34FB"); private Android.Bluetooth.BluetoothDevice m_device; private BluetoothSocket? m_socket; @@ -45,14 +45,14 @@ namespace NmeaParser } } - /// - /// Initializes a new instance of the class. - /// - /// The Android Bluetooth Device. - public BluetoothDevice(Android.Bluetooth.BluetoothDevice device) - { + /// + /// Initializes a new instance of the class. + /// + /// The Android Bluetooth Device. + public BluetoothDevice(Android.Bluetooth.BluetoothDevice device) + { m_device = device ?? throw new ArgumentNullException(nameof(device)); - } + } /// /// Creates the stream the NmeaDevice is working on top off. @@ -66,24 +66,24 @@ namespace NmeaParser var d = adapter.GetRemoteDevice(m_device.Address); var socket = d.CreateRfcommSocketToServiceRecord(SERIAL_UUID); socket.Connect(); - m_socket = socket; + m_socket = socket; return Task.FromResult(socket.InputStream); - } + } - /// - /// Closes the stream the NmeaDevice is working on top off. - /// - /// The stream. - /// - protected override Task CloseStreamAsync(System.IO.Stream stream) - { - if (stream == null) - throw new ArgumentNullException("stream"); - stream.Dispose(); - m_socket?.Dispose(); - m_socket = null; - return Task.FromResult(true); - } + /// + /// Closes the stream the NmeaDevice is working on top off. + /// + /// The stream. + /// + protected override Task CloseStreamAsync(System.IO.Stream stream) + { + if (stream == null) + throw new ArgumentNullException("stream"); + stream.Dispose(); + m_socket?.Dispose(); + m_socket = null; + return Task.FromResult(true); + } /// public override bool CanWrite => true; @@ -95,6 +95,6 @@ namespace NmeaParser throw new InvalidOperationException("Device not open"); return m_socket.OutputStream.WriteAsync(buffer, offset, length); } - } + } } #endif \ No newline at end of file diff --git a/src/NmeaParser/BluetoothDevice.UWP.cs b/src/NmeaParser/BluetoothDevice.UWP.cs index f4d6bff..e752eea 100644 --- a/src/NmeaParser/BluetoothDevice.UWP.cs +++ b/src/NmeaParser/BluetoothDevice.UWP.cs @@ -28,12 +28,12 @@ using Windows.Networking.Proximity; namespace NmeaParser { - /// - /// A Bluetooth NMEA device - /// - public class BluetoothDevice : NmeaDevice - { - private Windows.Devices.Bluetooth.Rfcomm.RfcommDeviceService? m_deviceService; + /// + /// A Bluetooth NMEA device + /// + public class BluetoothDevice : NmeaDevice + { + private Windows.Devices.Bluetooth.Rfcomm.RfcommDeviceService? m_deviceService; private Windows.Networking.Proximity.PeerInformation? m_devicePeer; private StreamSocket? m_socket; private bool m_disposeService; @@ -59,8 +59,8 @@ namespace NmeaParser /// The RF Comm Device service. /// Whether this devicee should also dispose the RfcommDeviceService provided when this device disposes. public BluetoothDevice(RfcommDeviceService service, bool disposeService = false) - { - m_deviceService = service ?? throw new ArgumentNullException(nameof(service)); + { + m_deviceService = service ?? throw new ArgumentNullException(nameof(service)); m_disposeService = disposeService; } @@ -88,8 +88,8 @@ namespace NmeaParser /// /// protected override async Task OpenStreamAsync() - { - var socket = new Windows.Networking.Sockets.StreamSocket(); + { + var socket = new Windows.Networking.Sockets.StreamSocket(); socket.Control.KeepAlive = true; if (m_devicePeer != null) { @@ -101,10 +101,10 @@ namespace NmeaParser } else throw new InvalidOperationException(); - m_socket = socket; + m_socket = socket; return new DummyStream(); //We're going to use WinRT buffers instead and will handle read/write, so no reason to return a real stream. This is mainly done to avoid locking issues reading and writing at the same time - } + } private class DummyStream : Stream { @@ -126,13 +126,13 @@ namespace NmeaParser /// The stream. /// protected override Task CloseStreamAsync(System.IO.Stream stream) - { + { if(m_socket == null) throw new InvalidOperationException("No connection to close"); - m_socket.Dispose(); - m_socket = null; - return Task.FromResult(true); - } + m_socket.Dispose(); + m_socket = null; + return Task.FromResult(true); + } /// @@ -174,6 +174,6 @@ namespace NmeaParser m_semaphoreSlim.Release(); } } - } + } } #endif \ No newline at end of file diff --git a/src/NmeaParser/BufferedStreamDevice.cs b/src/NmeaParser/BufferedStreamDevice.cs index 3cd218a..09decc0 100644 --- a/src/NmeaParser/BufferedStreamDevice.cs +++ b/src/NmeaParser/BufferedStreamDevice.cs @@ -21,241 +21,241 @@ using System.Threading.Tasks; namespace NmeaParser { - /// - /// An abstract generic NMEA device that reads a stream at a decreased pace, - /// mostly used to emulate NMEA input from files and strings. - /// - public abstract class BufferedStreamDevice : NmeaDevice - { - private BufferedStream? m_stream; - private readonly int m_readSpeed; + /// + /// An abstract generic NMEA device that reads a stream at a decreased pace, + /// mostly used to emulate NMEA input from files and strings. + /// + public abstract class BufferedStreamDevice : NmeaDevice + { + private BufferedStream? m_stream; + private readonly int m_readSpeed; - /// - /// Initializes a new instance of the class. - /// - protected BufferedStreamDevice() : this(1000) - { - } - /// - /// Initializes a new instance of the class. - /// - /// The time to wait between each group of lines being read in milliseconds - protected BufferedStreamDevice(int readSpeed) - { - m_readSpeed = readSpeed; - } + /// + /// Initializes a new instance of the class. + /// + protected BufferedStreamDevice() : this(1000) + { + } + /// + /// Initializes a new instance of the class. + /// + /// The time to wait between each group of lines being read in milliseconds + protected BufferedStreamDevice(int readSpeed) + { + m_readSpeed = readSpeed; + } - /// - /// Gets the stream to perform buffer reads on. - /// - /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] - protected abstract Task GetStreamAsync(); + /// + /// Gets the stream to perform buffer reads on. + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] + protected abstract Task GetStreamAsync(); - /// - /// Opens the stream asynchronous. - /// - /// - protected sealed async override Task OpenStreamAsync() - { - var stream = await GetStreamAsync(); - StreamReader sr = new StreamReader(stream); - m_stream = new BufferedStream(sr, m_readSpeed); - return m_stream; - } + /// + /// Opens the stream asynchronous. + /// + /// + protected sealed async override Task OpenStreamAsync() + { + var stream = await GetStreamAsync(); + StreamReader sr = new StreamReader(stream); + m_stream = new BufferedStream(sr, m_readSpeed); + return m_stream; + } - /// - /// Closes the stream asynchronous. - /// - /// The stream. - /// - protected override Task CloseStreamAsync(System.IO.Stream stream) - { - m_stream?.Dispose(); - return Task.FromResult(true); - } + /// + /// Closes the stream asynchronous. + /// + /// The stream. + /// + protected override Task CloseStreamAsync(System.IO.Stream stream) + { + m_stream?.Dispose(); + return Task.FromResult(true); + } - // stream that slowly populates a buffer from a StreamReader to simulate nmea messages coming - // in lastLineRead by lastLineRead at a steady stream - private class BufferedStream : Stream - { - private readonly StreamReader m_sr; - private byte[] m_buffer = new byte[0]; - private readonly System.Threading.Timer m_timer; - private readonly object lockObj = new object(); - private string? groupToken = null; - private string? lastLineRead = null; - /// - /// Initializes a new instance of the class. - /// - /// The stream. - /// The read speed in milliseconds. - public BufferedStream(StreamReader stream, int readSpeed) - { - m_sr = stream; - m_timer = new System.Threading.Timer(OnRead, null, 0, readSpeed); //read a group of lines every 'readSpeed' milliseconds - } - private void OnRead(object state) - { - if (lastLineRead != null) - AppendToBuffer(lastLineRead); - //Get the group token if we don't have one - while (groupToken == null && (lastLineRead == null || !lastLineRead.StartsWith("$", StringComparison.Ordinal))) - { - lastLineRead = ReadLine(); //seek forward to first nmea token - AppendToBuffer(lastLineRead); - } - if(groupToken == null && lastLineRead != null) - { - var values = lastLineRead.Trim().Split(new char[] { ',' }); - if (values.Length > 0) - groupToken = values[0]; - } - lastLineRead = ReadLine(); - while (!lastLineRead.StartsWith(groupToken, StringComparison.Ordinal)) //keep reading until messages start repeating again - { - AppendToBuffer(lastLineRead); - lastLineRead = ReadLine(); - } - } - private void AppendToBuffer(string line) - { - var bytes = Encoding.UTF8.GetBytes(line); - lock (lockObj) - { - byte[] newBuffer = new byte[m_buffer.Length + bytes.Length]; - m_buffer.CopyTo(newBuffer, 0); - bytes.CopyTo(newBuffer, m_buffer.Length); - m_buffer = newBuffer; - } - } - private string ReadLine() - { - if (m_sr.EndOfStream) - m_sr.BaseStream.Seek(0, SeekOrigin.Begin); //start over - return m_sr.ReadLine() + '\n'; - } - /// - /// Gets a value indicating whether this instance can read. - /// - /// - /// true if this instance can read; otherwise, false. - /// - public override bool CanRead { get { return true; } } - /// - /// Gets a value indicating whether this instance can seek. - /// - /// - /// true if this instance can seek; otherwise, false. - /// - public override bool CanSeek { get { return false; } } - /// - /// Gets a value indicating whether this instance can write. - /// - /// - /// true if this instance can write; otherwise, false. - /// - public override bool CanWrite { get { return false; } } - /// - /// Flushes this instance. - /// - public override void Flush() { } - /// - /// Gets the length. - /// - /// - /// The length. - /// - public override long Length { get { return m_sr.BaseStream.Length; } } + // stream that slowly populates a buffer from a StreamReader to simulate nmea messages coming + // in lastLineRead by lastLineRead at a steady stream + private class BufferedStream : Stream + { + private readonly StreamReader m_sr; + private byte[] m_buffer = new byte[0]; + private readonly System.Threading.Timer m_timer; + private readonly object lockObj = new object(); + private string? groupToken = null; + private string? lastLineRead = null; + /// + /// Initializes a new instance of the class. + /// + /// The stream. + /// The read speed in milliseconds. + public BufferedStream(StreamReader stream, int readSpeed) + { + m_sr = stream; + m_timer = new System.Threading.Timer(OnRead, null, 0, readSpeed); //read a group of lines every 'readSpeed' milliseconds + } + private void OnRead(object state) + { + if (lastLineRead != null) + AppendToBuffer(lastLineRead); + //Get the group token if we don't have one + while (groupToken == null && (lastLineRead == null || !lastLineRead.StartsWith("$", StringComparison.Ordinal))) + { + lastLineRead = ReadLine(); //seek forward to first nmea token + AppendToBuffer(lastLineRead); + } + if(groupToken == null && lastLineRead != null) + { + var values = lastLineRead.Trim().Split(new char[] { ',' }); + if (values.Length > 0) + groupToken = values[0]; + } + lastLineRead = ReadLine(); + while (!lastLineRead.StartsWith(groupToken, StringComparison.Ordinal)) //keep reading until messages start repeating again + { + AppendToBuffer(lastLineRead); + lastLineRead = ReadLine(); + } + } + private void AppendToBuffer(string line) + { + var bytes = Encoding.UTF8.GetBytes(line); + lock (lockObj) + { + byte[] newBuffer = new byte[m_buffer.Length + bytes.Length]; + m_buffer.CopyTo(newBuffer, 0); + bytes.CopyTo(newBuffer, m_buffer.Length); + m_buffer = newBuffer; + } + } + private string ReadLine() + { + if (m_sr.EndOfStream) + m_sr.BaseStream.Seek(0, SeekOrigin.Begin); //start over + return m_sr.ReadLine() + '\n'; + } + /// + /// Gets a value indicating whether this instance can read. + /// + /// + /// true if this instance can read; otherwise, false. + /// + public override bool CanRead { get { return true; } } + /// + /// Gets a value indicating whether this instance can seek. + /// + /// + /// true if this instance can seek; otherwise, false. + /// + public override bool CanSeek { get { return false; } } + /// + /// Gets a value indicating whether this instance can write. + /// + /// + /// true if this instance can write; otherwise, false. + /// + public override bool CanWrite { get { return false; } } + /// + /// Flushes this instance. + /// + public override void Flush() { } + /// + /// Gets the length. + /// + /// + /// The length. + /// + public override long Length { get { return m_sr.BaseStream.Length; } } - /// - /// Gets or sets the position. - /// - /// - /// The position. - /// - /// - public override long Position - { - get { return m_sr.BaseStream.Position; } - set - { - throw new NotSupportedException(); - } - } - /// - /// Reads the specified buffer. - /// - /// The buffer. - /// The offset. - /// The count. - /// - public override int Read(byte[] buffer, int offset, int count) - { - lock (lockObj) - { - if (this.m_buffer.Length <= count) - { - int length = this.m_buffer.Length; - this.m_buffer.CopyTo(buffer, 0); - this.m_buffer = new byte[0]; - return length; - } - else - { - Array.Copy(this.m_buffer, buffer, count); - byte[] newBuffer = new byte[this.m_buffer.Length - count]; - Array.Copy(this.m_buffer, count, newBuffer, 0, newBuffer.Length); - this.m_buffer = newBuffer; - return count; - } - } - } + /// + /// Gets or sets the position. + /// + /// + /// The position. + /// + /// + public override long Position + { + get { return m_sr.BaseStream.Position; } + set + { + throw new NotSupportedException(); + } + } + /// + /// Reads the specified buffer. + /// + /// The buffer. + /// The offset. + /// The count. + /// + public override int Read(byte[] buffer, int offset, int count) + { + lock (lockObj) + { + if (this.m_buffer.Length <= count) + { + int length = this.m_buffer.Length; + this.m_buffer.CopyTo(buffer, 0); + this.m_buffer = new byte[0]; + return length; + } + else + { + Array.Copy(this.m_buffer, buffer, count); + byte[] newBuffer = new byte[this.m_buffer.Length - count]; + Array.Copy(this.m_buffer, count, newBuffer, 0, newBuffer.Length); + this.m_buffer = newBuffer; + return count; + } + } + } - /// - /// Seeks the specified offset. - /// - /// The offset. - /// The origin. - /// - /// - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotSupportedException(); - } + /// + /// Seeks the specified offset. + /// + /// The offset. + /// The origin. + /// + /// + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException(); + } - /// - /// Sets the length. - /// - /// The value. - /// - public override void SetLength(long value) - { - throw new NotSupportedException(); - } + /// + /// Sets the length. + /// + /// The value. + /// + public override void SetLength(long value) + { + throw new NotSupportedException(); + } - /// - /// Writes the specified buffer to the device. - /// - /// The buffer. - /// The offset. - /// The count. - /// - public override void Write(byte[] buffer, int offset, int count) - { - throw new NotSupportedException(); - } + /// + /// Writes the specified buffer to the device. + /// + /// The buffer. + /// The offset. + /// The count. + /// + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotSupportedException(); + } - /// - /// Releases unmanaged and - optionally - managed resources. - /// - /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - m_sr.Dispose(); - m_timer.Dispose(); - } - } - } + /// + /// Releases unmanaged and - optionally - managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + m_sr.Dispose(); + m_timer.Dispose(); + } + } + } } diff --git a/src/NmeaParser/IMultiPartMessage.cs b/src/NmeaParser/IMultiPartMessage.cs deleted file mode 100644 index 0f79943..0000000 --- a/src/NmeaParser/IMultiPartMessage.cs +++ /dev/null @@ -1,36 +0,0 @@ -// ******************************************************************************* -// * Licensed under the Apache License, Version 2.0 (the "License"); -// * you may not use this file except in compliance with the License. -// * You may obtain a copy of the License at -// * -// * http://www.apache.org/licenses/LICENSE-2.0 -// * -// * Unless required by applicable law or agreed to in writing, software -// * distributed under the License is distributed on an "AS IS" BASIS, -// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// * See the License for the specific language governing permissions and -// * limitations under the License. -// ****************************************************************************** - -using System; -using System.Collections.Generic; -using System.Text; - -namespace NmeaParser -{ - interface IMultiPartMessage : System.Collections.IEnumerable - { - /// - /// Total number of messages of this type in this cycle - /// - int TotalMessages { get; } - - /// - /// Message number - /// - int MessageNumber { get; } - } - interface IMultiPartMessage : IMultiPartMessage, IEnumerable - { - } -} diff --git a/src/NmeaParser/IMultiSentenceMessage.cs b/src/NmeaParser/IMultiSentenceMessage.cs new file mode 100644 index 0000000..4e31a19 --- /dev/null +++ b/src/NmeaParser/IMultiSentenceMessage.cs @@ -0,0 +1,42 @@ +// ******************************************************************************* +// * Licensed under the Apache License, Version 2.0 (the "License"); +// * you may not use this file except in compliance with the License. +// * You may obtain a copy of the License at +// * +// * http://www.apache.org/licenses/LICENSE-2.0 +// * +// * Unless required by applicable law or agreed to in writing, software +// * distributed under the License is distributed on an "AS IS" BASIS, +// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// * See the License for the specific language governing permissions and +// * limitations under the License. +// ****************************************************************************** + +using System; +using System.Collections.Generic; +using System.Text; + +namespace NmeaParser +{ + /// + /// Interface used for NMEA messages that span multiple sentences + /// + public interface IMultiSentenceMessage + { + /// + /// Attempts to append one message to an existing one + /// + /// + /// This method should return false if the message being appended isn't the next message in line, and various indicators show this is a different message than the previous one. It should also return false if you append to a message that didn't start with the first message. + /// + /// + /// + /// True is the message was successfully appended, False is the message couldn't be appended. + bool TryAppend(string messageType, string[] values); + + /// + /// Gets a value indicating whether this message is fully loaded from all its sentences + /// + bool IsComplete { get; } + } +} diff --git a/src/NmeaParser/Nmea/Garmin/PGRME.cs b/src/NmeaParser/Nmea/Garmin/PGRME.cs index 5be2e18..99cc1bd 100644 --- a/src/NmeaParser/Nmea/Garmin/PGRME.cs +++ b/src/NmeaParser/Nmea/Garmin/PGRME.cs @@ -20,9 +20,9 @@ namespace NmeaParser.Nmea.Garmin /// Recommended Minimum /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Pgrme")] - [NmeaMessageType("PGRME")] - public class Pgrme : NmeaMessage - { + [NmeaMessageType("PGRME")] + public class Pgrme : NmeaMessage + { /// /// Initializes a new instance of the class. /// @@ -31,47 +31,47 @@ namespace NmeaParser.Nmea.Garmin public Pgrme(string type, string[] message) : base(type, message) { if (message == null || message.Length < 6) - throw new ArgumentException("Invalid PGRME", "message"); - - HorizontalError = NmeaMessage.StringToDouble(message[0]); - HorizontalErrorUnits = message[1]; - VerticalError = NmeaMessage.StringToDouble(message[2]); - VerticalErrorUnits = message[3]; - SphericalError = NmeaMessage.StringToDouble(message[4]); - SphericalErrorUnits = message[5]; - } + throw new ArgumentException("Invalid PGRME", "message"); + + HorizontalError = NmeaMessage.StringToDouble(message[0]); + HorizontalErrorUnits = message[1]; + VerticalError = NmeaMessage.StringToDouble(message[2]); + VerticalErrorUnits = message[3]; + SphericalError = NmeaMessage.StringToDouble(message[4]); + SphericalErrorUnits = message[5]; + } - /// - /// Estimated horizontal position error in meters (HPE) - /// - /// Range: 0.0 to 999.9 meters - public double HorizontalError{ get; } + /// + /// Estimated horizontal position error in meters (HPE) + /// + /// Range: 0.0 to 999.9 meters + public double HorizontalError{ get; } - /// - /// Horizontal Error unit ('M' for Meters) - /// - public string HorizontalErrorUnits{ get; } + /// + /// Horizontal Error unit ('M' for Meters) + /// + public string HorizontalErrorUnits{ get; } - /// - /// Estimated vertical position error in meters (VPE) - /// - /// Range: 0.0 to 999.9 meters - public double VerticalError{ get; } + /// + /// Estimated vertical position error in meters (VPE) + /// + /// Range: 0.0 to 999.9 meters + public double VerticalError{ get; } - /// - /// Vertical Error unit ('M' for Meters) - /// - public string VerticalErrorUnits{ get; } + /// + /// Vertical Error unit ('M' for Meters) + /// + public string VerticalErrorUnits{ get; } - /// - /// Overall spherical equivalent position error (EPE) - /// - /// Range: 0.0 to 999.9 meters - public double SphericalError{ get; } + /// + /// Overall spherical equivalent position error (EPE) + /// + /// Range: 0.0 to 999.9 meters + public double SphericalError{ get; } - /// - /// Spherical Error unit ('M' for Meters) - /// - public string SphericalErrorUnits{ get; } - } + /// + /// Spherical Error unit ('M' for Meters) + /// + public string SphericalErrorUnits{ get; } + } } diff --git a/src/NmeaParser/Nmea/Gga.cs b/src/NmeaParser/Nmea/Gga.cs index bdda051..1a1e441 100644 --- a/src/NmeaParser/Nmea/Gga.cs +++ b/src/NmeaParser/Nmea/Gga.cs @@ -23,97 +23,97 @@ namespace NmeaParser.Nmea [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Gpgga")] [NmeaMessageType("--GGA")] public class Gga : NmeaMessage - { + { /// /// Initializes a new instance of the class. /// /// The message type /// The NMEA message values. public Gga(string type, string[] message) : base(type, message) - { - if (message == null || message.Length < 14) - throw new ArgumentException("Invalid GPGGA", "message"); - FixTime = StringToTimeSpan(message[0]); - Latitude = NmeaMessage.StringToLatitude(message[1], message[2]); - Longitude = NmeaMessage.StringToLongitude(message[3], message[4]); - Quality = (Gga.FixQuality)int.Parse(message[5], CultureInfo.InvariantCulture); - NumberOfSatellites = int.Parse(message[6], CultureInfo.InvariantCulture); - Hdop = NmeaMessage.StringToDouble(message[7]); - Altitude = NmeaMessage.StringToDouble(message[8]); - AltitudeUnits = message[9]; - HeightOfGeoid = NmeaMessage.StringToDouble(message[10]); - HeightOfGeoidUnits = message[11]; - var timeInSeconds = StringToDouble(message[12]); - if (!double.IsNaN(timeInSeconds)) - TimeSinceLastDgpsUpdate = TimeSpan.FromSeconds(timeInSeconds); - else - TimeSinceLastDgpsUpdate = TimeSpan.MaxValue; - if (message[13].Length > 0) - DgpsStationId = int.Parse(message[13], CultureInfo.InvariantCulture); - else - DgpsStationId = -1; - } + { + if (message == null || message.Length < 14) + throw new ArgumentException("Invalid GPGGA", "message"); + FixTime = StringToTimeSpan(message[0]); + Latitude = NmeaMessage.StringToLatitude(message[1], message[2]); + Longitude = NmeaMessage.StringToLongitude(message[3], message[4]); + Quality = (Gga.FixQuality)int.Parse(message[5], CultureInfo.InvariantCulture); + NumberOfSatellites = int.Parse(message[6], CultureInfo.InvariantCulture); + Hdop = NmeaMessage.StringToDouble(message[7]); + Altitude = NmeaMessage.StringToDouble(message[8]); + AltitudeUnits = message[9]; + HeightOfGeoid = NmeaMessage.StringToDouble(message[10]); + HeightOfGeoidUnits = message[11]; + var timeInSeconds = StringToDouble(message[12]); + if (!double.IsNaN(timeInSeconds)) + TimeSinceLastDgpsUpdate = TimeSpan.FromSeconds(timeInSeconds); + else + TimeSinceLastDgpsUpdate = TimeSpan.MaxValue; + if (message[13].Length > 0) + DgpsStationId = int.Parse(message[13], CultureInfo.InvariantCulture); + else + DgpsStationId = -1; + } - /// - /// Time of day fix was taken - /// - public TimeSpan FixTime { get; } - - /// - /// Latitude - /// - public double Latitude { get; } + /// + /// Time of day fix was taken + /// + public TimeSpan FixTime { get; } + + /// + /// Latitude + /// + public double Latitude { get; } - /// - /// Longitude - /// - public double Longitude { get; } + /// + /// Longitude + /// + public double Longitude { get; } - /// - /// Fix Quality - /// - public Gga.FixQuality Quality { get; } + /// + /// Fix Quality + /// + public Gga.FixQuality Quality { get; } - /// - /// Number of satellites being tracked - /// - public int NumberOfSatellites { get; } + /// + /// Number of satellites being tracked + /// + public int NumberOfSatellites { get; } - /// - /// Horizontal Dilution of Precision - /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Hdop")] - public double Hdop { get; } + /// + /// Horizontal Dilution of Precision + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Hdop")] + public double Hdop { get; } - /// - /// Altitude - /// - public double Altitude { get; } + /// + /// Altitude + /// + public double Altitude { get; } - /// - /// Altitude units ('M' for Meters) - /// - public string AltitudeUnits { get; } - - /// - /// Height of geoid (mean sea level) above WGS84 - /// - public double HeightOfGeoid { get; } + /// + /// Altitude units ('M' for Meters) + /// + public string AltitudeUnits { get; } + + /// + /// Height of geoid (mean sea level) above WGS84 + /// + public double HeightOfGeoid { get; } - /// - /// Altitude units ('M' for Meters) - /// - public string HeightOfGeoidUnits { get; } + /// + /// Altitude units ('M' for Meters) + /// + public string HeightOfGeoidUnits { get; } - /// - /// Time since last DGPS update - /// - public TimeSpan TimeSinceLastDgpsUpdate { get; } + /// + /// Time since last DGPS update + /// + public TimeSpan TimeSinceLastDgpsUpdate { get; } - /// - /// DGPS Station ID Number - /// - public int DgpsStationId { get; } + /// + /// DGPS Station ID Number + /// + public int DgpsStationId { get; } /// /// Fix quality diff --git a/src/NmeaParser/Nmea/Gll.cs b/src/NmeaParser/Nmea/Gll.cs index 61966a8..60086f1 100644 --- a/src/NmeaParser/Nmea/Gll.cs +++ b/src/NmeaParser/Nmea/Gll.cs @@ -22,7 +22,7 @@ namespace NmeaParser.Nmea [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Gll")] [NmeaMessageType("--GLL")] public class Gll : NmeaMessage - { + { /// /// Initializes a new instance of the class. /// @@ -31,14 +31,14 @@ namespace NmeaParser.Nmea public Gll(string type, string[] message) : base(type, message) { if (message == null || message.Length < 4) - throw new ArgumentException("Invalid GPGLL", "message"); - Latitude = NmeaMessage.StringToLatitude(message[0], message[1]); - Longitude = NmeaMessage.StringToLongitude(message[2], message[3]); - if (message.Length >= 5) //Some older GPS doesn't broadcast fix time - { - FixTime = StringToTimeSpan(message[4]); - } - DataActive = (message.Length < 6 || message[5] == "A"); + throw new ArgumentException("Invalid GPGLL", "message"); + Latitude = NmeaMessage.StringToLatitude(message[0], message[1]); + Longitude = NmeaMessage.StringToLongitude(message[2], message[3]); + if (message.Length >= 5) //Some older GPS doesn't broadcast fix time + { + FixTime = StringToTimeSpan(message[4]); + } + DataActive = (message.Length < 6 || message[5] == "A"); ModeIndicator = DataActive ? Mode.Autonomous : Mode.DataNotValid; if (message.Length > 6) { @@ -54,28 +54,28 @@ namespace NmeaParser.Nmea } } - /// - /// Latitude - /// - public double Latitude { get; } + /// + /// Latitude + /// + public double Latitude { get; } - /// - /// Longitude - /// - public double Longitude { get; } + /// + /// Longitude + /// + public double Longitude { get; } - /// - /// Time since last DGPS update - /// - public TimeSpan FixTime { get; } + /// + /// Time since last DGPS update + /// + public TimeSpan FixTime { get; } - /// - /// Gets a value indicating whether data is active. - /// - /// - /// true if data is active; otherwise, false. - /// - public bool DataActive { get; } + /// + /// Gets a value indicating whether data is active. + /// + /// + /// true if data is active; otherwise, false. + /// + public bool DataActive { get; } /// /// Positioning system Mode Indicator @@ -113,5 +113,5 @@ namespace NmeaParser.Nmea /// DataNotValid } - } + } } diff --git a/src/NmeaParser/Nmea/Gns.cs b/src/NmeaParser/Nmea/Gns.cs index db70972..3a5e31e 100644 --- a/src/NmeaParser/Nmea/Gns.cs +++ b/src/NmeaParser/Nmea/Gns.cs @@ -24,7 +24,7 @@ namespace NmeaParser.Nmea [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Gns")] [NmeaMessageType("--GNS")] public class Gns : NmeaMessage - { + { /* * Example of GNS messages: * $GNGNS,014035.00,4332.69262,S,17235.48549,E,RR,13,0.9,25.63,11.24,,*70 //GLONASS @@ -116,11 +116,11 @@ namespace NmeaParser.Nmea } /// - /// Initializes a new instance of the class. - /// + /// Initializes a new instance of the class. + /// /// The message type - /// The NMEA message values. - public Gns(string type, string[] message) : base(type, message) + /// The NMEA message values. + public Gns(string type, string[] message) : base(type, message) { if (message == null || message.Length < 12) throw new ArgumentException("Invalid GNS", "message"); @@ -162,20 +162,20 @@ namespace NmeaParser.Nmea } } - /// - /// Time of day fix was taken - /// - public TimeSpan FixTime { get; } - - /// - /// Latitude - /// - public double Latitude { get; } + /// + /// Time of day fix was taken + /// + public TimeSpan FixTime { get; } + + /// + /// Latitude + /// + public double Latitude { get; } - /// - /// Longitude - /// - public double Longitude { get; } + /// + /// Longitude + /// + public double Longitude { get; } /// /// Mode indicator for GPS @@ -207,7 +207,7 @@ namespace NmeaParser.Nmea /// Horizontal Dilution of Precision (HDOP), calculated using all the satellites (GPS, GLONASS, and any future satellites) used in computing the solution reported in each GNS sentence. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Hdop")] - public double Hdop { get; } + public double Hdop { get; } /// /// Orthometric height in meters (MSL reference) @@ -222,11 +222,11 @@ namespace NmeaParser.Nmea /// /// Age of differential data - if talker ID is GN, additional GNS messages follow with GP and/or GL Age of differential data - /// - public TimeSpan TimeSinceLastDgpsUpdate { get; } + /// + public TimeSpan TimeSinceLastDgpsUpdate { get; } - /// - /// eference station ID1, range 0000-4095 - Null if talker ID is GN, additional GNS messages follow with GP and/or GL Reference station ID + /// + /// eference station ID1, range 0000-4095 - Null if talker ID is GN, additional GNS messages follow with GP and/or GL Reference station ID /// public string? DgpsStationId { get; } @@ -234,5 +234,5 @@ namespace NmeaParser.Nmea /// Navigational status /// public NavigationalStatus Status { get; } - } + } } diff --git a/src/NmeaParser/Nmea/Gsv.cs b/src/NmeaParser/Nmea/Gsv.cs index f4a67b9..db1d5ec 100644 --- a/src/NmeaParser/Nmea/Gsv.cs +++ b/src/NmeaParser/Nmea/Gsv.cs @@ -24,8 +24,10 @@ namespace NmeaParser.Nmea [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Gsv")] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")] [NmeaMessageType("--GSV")] - public class Gsv : NmeaMessage, IMultiPartMessage + public class Gsv : NmeaMultiSentenceMessage, IEnumerable { + private readonly List svs = new List(); + /// /// Initializes a new instance of the class. /// @@ -35,41 +37,43 @@ namespace NmeaParser.Nmea { if (message == null || message.Length < 3) throw new ArgumentException("Invalid GSV", "message"); + } - TotalMessages = int.Parse(message[0], CultureInfo.InvariantCulture); - MessageNumber = int.Parse(message[1], CultureInfo.InvariantCulture); - SVsInView = int.Parse(message[2], CultureInfo.InvariantCulture); + /// + protected override int MessageCountIndex => 0; + + /// + protected override int MessageNumberIndex => 1; + + /// + protected override bool ParseSentences(Talker talkerType, string[] message) + { + var satellites = int.Parse(message[2], CultureInfo.InvariantCulture); + + if (SVsInView == -1) + SVsInView = satellites; + else if ( satellites != SVsInView) + return false; // Messages do not match - List svs = new List(); for (int i = 3; i < message.Length - 3; i += 4) { if (message[i].Length == 0) continue; else - svs.Add(new SatelliteVehicle(message, i)); + svs.Add(new SatelliteVehicle(talkerType, message, i)); } - this.SVs = svs.ToArray(); + return true; } - /// - /// Total number of messages of this type in this cycle - /// - public int TotalMessages { get; } - - /// - /// Message number - /// - public int MessageNumber { get; } - /// /// Total number of SVs in view /// - public int SVsInView { get; } + public int SVsInView { get; private set; } = -1; /// /// Satellite vehicles in this message part. /// - public IReadOnlyList SVs { get; } + public IReadOnlyList SVs => svs.AsReadOnly(); /// /// Returns an enumerator that iterates through the collection. @@ -95,7 +99,7 @@ namespace NmeaParser.Nmea /// public sealed class SatelliteVehicle { - internal SatelliteVehicle(string[] message, int startIndex) + internal SatelliteVehicle(Talker talker, string[] message, int startIndex) { PrnNumber = int.Parse(message[startIndex], CultureInfo.InvariantCulture); if (double.TryParse(message[startIndex + 1], NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out double e)) @@ -105,19 +109,29 @@ namespace NmeaParser.Nmea int snr = -1; if (int.TryParse(message[startIndex + 3], out snr)) SignalToNoiseRatio = snr; + TalkerId = talker; } + + /// + /// Gets the talker ID for this vehicle + /// + public Talker TalkerId { get; } + /// /// SV PRN number /// public int PrnNumber { get; } + /// /// Elevation in degrees, 90 maximum /// public double Elevation { get; } = double.NaN; + /// /// Azimuth, degrees from true north, 000 to 359 /// public double Azimuth { get; } = double.NaN; + /// /// Signal-to-Noise ratio, 0-99 dB (-1 when not tracking) /// diff --git a/src/NmeaParser/Nmea/LaserRange/LaserRangeMessage.cs b/src/NmeaParser/Nmea/LaserRange/LaserRangeMessage.cs index d5a8de2..c95ab1c 100644 --- a/src/NmeaParser/Nmea/LaserRange/LaserRangeMessage.cs +++ b/src/NmeaParser/Nmea/LaserRange/LaserRangeMessage.cs @@ -30,62 +30,62 @@ namespace NmeaParser.Nmea.LaserRange protected LaserRangeMessage(string type, string[] message) : base(type, message) { if (message == null || message.Length < 9) - throw new ArgumentException("Invalid Laser Range Message", "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]; - } + throw new ArgumentException("Invalid Laser Range Message", "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]; + } - /// - /// Gets the horizontal vector. - /// - public string HorizontalVector { get; } + /// + /// Gets the horizontal vector. + /// + public string HorizontalVector { get; } - /// - /// Gets the horizontal distance. - /// - public double HorizontalDistance { get; } + /// + /// Gets the horizontal distance. + /// + public double HorizontalDistance { get; } - /// - /// Gets the units of the value. - /// - public char HorizontalDistanceUnits { get; } + /// + /// Gets the units of the value. + /// + public char HorizontalDistanceUnits { get; } - /// - /// Gets the horizontal angle. - /// - public double HorizontalAngle { get; } + /// + /// Gets the horizontal angle. + /// + public double HorizontalAngle { get; } - /// - /// Gets the units of the value. - /// - public char HorizontalAngleUnits { get; } + /// + /// Gets the units of the value. + /// + public char HorizontalAngleUnits { get; } - /// - /// Gets the vertical angle. - /// - public double VerticalAngle { get; } + /// + /// Gets the vertical angle. + /// + public double VerticalAngle { get; } - /// - /// Gets the units of the value. - /// - public char VerticalAngleUnits { get; } + /// + /// Gets the units of the value. + /// + public char VerticalAngleUnits { get; } - /// - /// Gets the slope distance. - /// - public double SlopeDistance { get; } + /// + /// Gets the slope distance. + /// + public double SlopeDistance { get; } - /// - /// Gets the units of the value. - /// - public char SlopeDistanceUnits { get; } - } + /// + /// Gets the units of the value. + /// + public char SlopeDistanceUnits { get; } + } } diff --git a/src/NmeaParser/Nmea/LaserRange/LaserTech/PLTIT.cs b/src/NmeaParser/Nmea/LaserRange/LaserTech/PLTIT.cs index e479bfb..7390d86 100644 --- a/src/NmeaParser/Nmea/LaserRange/LaserTech/PLTIT.cs +++ b/src/NmeaParser/Nmea/LaserRange/LaserTech/PLTIT.cs @@ -18,8 +18,8 @@ namespace NmeaParser.Nmea.LaserRange.LaserTech /// Laser Range /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Pltit")] - [NmeaMessageType("PLTIT")] - public class Pltit : LaserRangeMessage + [NmeaMessageType("PLTIT")] + public class Pltit : LaserRangeMessage { /// /// Initializes a new instance of the class. diff --git a/src/NmeaParser/Nmea/LaserRange/Trimble/PTNLA.cs b/src/NmeaParser/Nmea/LaserRange/Trimble/PTNLA.cs index 453d8a8..17c31bb 100644 --- a/src/NmeaParser/Nmea/LaserRange/Trimble/PTNLA.cs +++ b/src/NmeaParser/Nmea/LaserRange/Trimble/PTNLA.cs @@ -18,8 +18,8 @@ namespace NmeaParser.Nmea.LaserRange.Trimble /// Burden finder /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Ptnla")] - [NmeaMessageType("PTNLA")] - public class Ptnla : LaserRangeMessage + [NmeaMessageType("PTNLA")] + public class Ptnla : LaserRangeMessage { /// /// Initializes a new instance of the class. diff --git a/src/NmeaParser/Nmea/NmeaMessage.cs b/src/NmeaParser/Nmea/NmeaMessage.cs index 14c4ce4..a773dec 100644 --- a/src/NmeaParser/Nmea/NmeaMessage.cs +++ b/src/NmeaParser/Nmea/NmeaMessage.cs @@ -24,21 +24,21 @@ namespace NmeaParser.Nmea /// Nmea message attribute type used on concrete implementations. /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] - public sealed class NmeaMessageTypeAttribute : Attribute - { - /// - /// Initializes a new instance of the class. - /// - /// The type. - public NmeaMessageTypeAttribute(string nmeaType) - { - NmeaType = nmeaType; - } - /// - /// Gets or sets the NMEA message type. - /// - public string NmeaType { get; private set; } - } + public sealed class NmeaMessageTypeAttribute : Attribute + { + /// + /// Initializes a new instance of the class. + /// + /// The type. + public NmeaMessageTypeAttribute(string nmeaType) + { + NmeaType = nmeaType; + } + /// + /// Gets or sets the NMEA message type. + /// + public string NmeaType { get; private set; } + } /// /// NMEA Message base class. @@ -87,15 +87,16 @@ namespace NmeaParser.Nmea /// Parses the specified NMEA message. /// /// The NMEA message string. + /// The previously received message (only used if parsing multi-sentence messages) /// /// /// Invalid nmea message: Missing starting character '$' /// or checksum failure /// - public static NmeaMessage Parse(string message) + public static NmeaMessage Parse(string message, IMultiSentenceMessage? previousSentence = null) { if (string.IsNullOrEmpty(message)) - throw new ArgumentNullException("message"); + throw new ArgumentNullException(nameof(message)); int checksum = -1; if (message[0] != '$') @@ -120,6 +121,13 @@ namespace NmeaParser.Nmea string[] parts = message.Split(new char[] { ',' }); string MessageType = parts[0].Substring(1); string[] MessageParts = parts.Skip(1).ToArray(); + if(previousSentence is NmeaMessage pmsg && pmsg.MessageType.Substring(2) == MessageType.Substring(2)) + { + if (previousSentence.TryAppend(MessageType, MessageParts)) + { + return pmsg; + } + } if (messageTypes.ContainsKey(MessageType)) { return (NmeaMessage)messageTypes[MessageType].Invoke(new object[] { MessageType, MessageParts }); @@ -147,7 +155,7 @@ namespace NmeaParser.Nmea /// /// Gets the talker ID for this message ( /// - public Talker TalkerId => TalkerHelper.GetTalker(MessageType); + public virtual Talker TalkerId => TalkerHelper.GetTalker(MessageType); /// /// Gets a value indicating whether this message type is proprietary @@ -161,69 +169,68 @@ namespace NmeaParser.Nmea /// A that represents this instance. /// public override string ToString() - { - return string.Format(CultureInfo.InvariantCulture, "${0},{1}*{2:X2}", MessageType, string.Join(",", MessageParts), Checksum); - } + { + return string.Format(CultureInfo.InvariantCulture, "${0},{1}*{2:X2}", MessageType, string.Join(",", MessageParts), Checksum); + } - /// - /// Gets the checksum value of the message. - /// - public byte Checksum - { - get - { - int checksumTest = 0; - for (int j = -1; j < MessageParts.Count; j++) - { - string message = j < 0 ? MessageType : MessageParts[j]; - if (j >= 0) - checksumTest ^= 0x2C; //Comma separator - for (int i = 0; i < message.Length; i++) - { - checksumTest ^= Convert.ToByte(message[i]); - } - } - return Convert.ToByte(checksumTest); - } - } + /// + /// Gets the checksum value of the message. + /// + public byte Checksum => GetChecksum(MessageType, MessageParts); - internal static double StringToLatitude(string value, string ns) - { - if (value == null || value.Length < 3) - return double.NaN; - double latitude = int.Parse(value.Substring(0, 2), CultureInfo.InvariantCulture) + double.Parse(value.Substring(2), CultureInfo.InvariantCulture) / 60; - if (ns == "S") - latitude *= -1; - return latitude; - } + internal static byte GetChecksum(string messageType, IReadOnlyList messageParts) + { + int checksumTest = 0; + for (int j = -1; j < messageParts.Count; j++) + { + string message = j < 0 ? messageType : messageParts[j]; + if (j >= 0) + checksumTest ^= 0x2C; //Comma separator + for (int i = 0; i < message.Length; i++) + { + checksumTest ^= Convert.ToByte(message[i]); + } + } + return Convert.ToByte(checksumTest); + } - internal static double StringToLongitude(string value, string ew) - { - if (value == null || value.Length < 4) - return double.NaN; - double longitude = int.Parse(value.Substring(0, 3), CultureInfo.InvariantCulture) + double.Parse(value.Substring(3), CultureInfo.InvariantCulture) / 60; - if (ew == "W") - longitude *= -1; - return longitude; - } + internal static double StringToLatitude(string value, string ns) + { + if (value == null || value.Length < 3) + return double.NaN; + double latitude = int.Parse(value.Substring(0, 2), CultureInfo.InvariantCulture) + double.Parse(value.Substring(2), CultureInfo.InvariantCulture) / 60; + if (ns == "S") + latitude *= -1; + return latitude; + } - internal static double StringToDouble(string value) - { - if(value != null && double.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out double result)) - { - return result; - } - return double.NaN; - } - internal static TimeSpan StringToTimeSpan(string value) - { - if (value != null && value.Length >= 6) - { - return new TimeSpan(int.Parse(value.Substring(0, 2), CultureInfo.InvariantCulture), - int.Parse(value.Substring(2, 2), CultureInfo.InvariantCulture), 0) - .Add(TimeSpan.FromSeconds(double.Parse(value.Substring(4), CultureInfo.InvariantCulture))); - } - return TimeSpan.Zero; - } - } + internal static double StringToLongitude(string value, string ew) + { + if (value == null || value.Length < 4) + return double.NaN; + double longitude = int.Parse(value.Substring(0, 3), CultureInfo.InvariantCulture) + double.Parse(value.Substring(3), CultureInfo.InvariantCulture) / 60; + if (ew == "W") + longitude *= -1; + return longitude; + } + + internal static double StringToDouble(string value) + { + if(value != null && double.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out double result)) + { + return result; + } + return double.NaN; + } + internal static TimeSpan StringToTimeSpan(string value) + { + if (value != null && value.Length >= 6) + { + return new TimeSpan(int.Parse(value.Substring(0, 2), CultureInfo.InvariantCulture), + int.Parse(value.Substring(2, 2), CultureInfo.InvariantCulture), 0) + .Add(TimeSpan.FromSeconds(double.Parse(value.Substring(4), CultureInfo.InvariantCulture))); + } + return TimeSpan.Zero; + } + } } diff --git a/src/NmeaParser/Nmea/NmeaMultiSentenceMessage.cs b/src/NmeaParser/Nmea/NmeaMultiSentenceMessage.cs new file mode 100644 index 0000000..a646a36 --- /dev/null +++ b/src/NmeaParser/Nmea/NmeaMultiSentenceMessage.cs @@ -0,0 +1,116 @@ +// ******************************************************************************* +// * Licensed under the Apache License, Version 2.0 (the "License"); +// * you may not use this file except in compliance with the License. +// * You may obtain a copy of the License at +// * +// * http://www.apache.org/licenses/LICENSE-2.0 +// * +// * Unless required by applicable law or agreed to in writing, software +// * distributed under the License is distributed on an "AS IS" BASIS, +// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// * See the License for the specific language governing permissions and +// * limitations under the License. +// ****************************************************************************** + +using System; +using System.Collections.Generic; +using System.Globalization; + +namespace NmeaParser.Nmea +{ + /// + /// Base class for easily creating message that are spread across multiple sentences + /// + public abstract class NmeaMultiSentenceMessage : NmeaMessage, IMultiSentenceMessage + { + private int lastMessageNumber = 0; + private int totalMessages; + private readonly int firstMessageNumber; + private readonly List messages = new List(); + private readonly bool initialized; + + /// + /// Initializes an instance of the class. + /// + /// Type + /// Message values + protected NmeaMultiSentenceMessage(string messageType, string[] messageParts) : base(messageType, messageParts) + { + totalMessages = int.Parse(messageParts[MessageCountIndex], CultureInfo.InvariantCulture); + firstMessageNumber = int.Parse(messageParts[MessageNumberIndex], CultureInfo.InvariantCulture); + talkerId = base.TalkerId; + if (!((IMultiSentenceMessage)this).TryAppend(messageType, messageParts)) + throw new ArgumentException("Failed to parse message"); + initialized = true; + } + + /// + /// Gets the index in the where the total count of messages is listed. + /// + protected virtual int MessageCountIndex { get; } = 0; + + /// + /// Gets the index in the where the message number is listed. + /// + protected virtual int MessageNumberIndex { get; } = 1; + + bool IMultiSentenceMessage.IsComplete => firstMessageNumber == 1 && lastMessageNumber == totalMessages; + + /// + public override string ToString() + { + System.Text.StringBuilder sb = new System.Text.StringBuilder(); + foreach (var msg in messages) + { + if (sb.Length > 0) + sb.Append("\r\n"); + sb.AppendFormat(CultureInfo.InvariantCulture, "${0},{1}*{2:X2}", MessageType, string.Join(",", msg), Checksum); + } + return sb.ToString(); + } + + bool IMultiSentenceMessage.TryAppend(string messageType, string[] message) + { + if (message == null || message.Length < Math.Max(MessageCountIndex, MessageNumberIndex)) + throw new ArgumentException("Invalid message", "message"); + + + int msgCount = int.Parse(message[0], CultureInfo.InvariantCulture); + int msgNumber = int.Parse(message[1], CultureInfo.InvariantCulture); + + if (initialized) + { + if (firstMessageNumber != 1) //We can only append to message who has message number 1 + return false; + if (msgCount != totalMessages || msgNumber != lastMessageNumber + 1) + return false; // Messages do not match + } + + var talker = TalkerHelper.GetTalker(messageType); + if (talkerId != talker) + talkerId = Talker.Multiple; + if (ParseSentences(talker, message)) + { + lastMessageNumber = msgNumber; + messages.Add(message); + return true; + } + return false; + } + + /// + /// Parses the messages or any message being appended. False should be returned if it's a message being appended doesn't appear to match what has already been loded. + /// + /// + /// + /// True if the message could succesfully be appended. + protected abstract bool ParseSentences(Talker talkerType, string[] message); + + private Talker talkerId; + + + /// + public override Talker TalkerId => talkerId; + + } +} diff --git a/src/NmeaParser/Nmea/Rmc.cs b/src/NmeaParser/Nmea/Rmc.cs index 89af646..1cfdd1e 100644 --- a/src/NmeaParser/Nmea/Rmc.cs +++ b/src/NmeaParser/Nmea/Rmc.cs @@ -23,7 +23,7 @@ namespace NmeaParser.Nmea [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Gprmc")] [NmeaMessageType("--RMC")] public class Rmc : NmeaMessage - { + { /// /// Initializes a new instance of the class. /// @@ -32,60 +32,60 @@ namespace NmeaParser.Nmea public Rmc(string type, string[] message) : base(type, message) { if (message == null || message.Length < 11) - throw new ArgumentException("Invalid GPRMC", "message"); - - if (message[8].Length == 6 && message[0].Length >= 6) - { - FixTime = new DateTime(int.Parse(message[8].Substring(4, 2), CultureInfo.InvariantCulture) + 2000, - int.Parse(message[8].Substring(2, 2), CultureInfo.InvariantCulture), - int.Parse(message[8].Substring(0, 2), CultureInfo.InvariantCulture), - int.Parse(message[0].Substring(0, 2), CultureInfo.InvariantCulture), - int.Parse(message[0].Substring(2, 2), CultureInfo.InvariantCulture), - 0, DateTimeKind.Utc).AddSeconds(double.Parse(message[0].Substring(4), CultureInfo.InvariantCulture)); - } - Active = (message[1] == "A"); - Latitude = NmeaMessage.StringToLatitude(message[2], message[3]); - Longitude = NmeaMessage.StringToLongitude(message[4], message[5]); - Speed = NmeaMessage.StringToDouble(message[6]); - Course = NmeaMessage.StringToDouble(message[7]); - MagneticVariation = NmeaMessage.StringToDouble(message[9]); - if (!double.IsNaN(MagneticVariation) && message[10] == "W") - MagneticVariation *= -1; - } + throw new ArgumentException("Invalid GPRMC", "message"); + + if (message[8].Length == 6 && message[0].Length >= 6) + { + FixTime = new DateTime(int.Parse(message[8].Substring(4, 2), CultureInfo.InvariantCulture) + 2000, + int.Parse(message[8].Substring(2, 2), CultureInfo.InvariantCulture), + int.Parse(message[8].Substring(0, 2), CultureInfo.InvariantCulture), + int.Parse(message[0].Substring(0, 2), CultureInfo.InvariantCulture), + int.Parse(message[0].Substring(2, 2), CultureInfo.InvariantCulture), + 0, DateTimeKind.Utc).AddSeconds(double.Parse(message[0].Substring(4), CultureInfo.InvariantCulture)); + } + Active = (message[1] == "A"); + Latitude = NmeaMessage.StringToLatitude(message[2], message[3]); + Longitude = NmeaMessage.StringToLongitude(message[4], message[5]); + Speed = NmeaMessage.StringToDouble(message[6]); + Course = NmeaMessage.StringToDouble(message[7]); + MagneticVariation = NmeaMessage.StringToDouble(message[9]); + if (!double.IsNaN(MagneticVariation) && message[10] == "W") + MagneticVariation *= -1; + } - /// - /// Fix Time - /// - public DateTime FixTime { get; } + /// + /// Fix Time + /// + public DateTime FixTime { get; } - /// - /// Gets a value whether the device is active - /// - public bool Active { get; } + /// + /// Gets a value whether the device is active + /// + public bool Active { get; } - /// - /// Latitude - /// - public double Latitude { get; } + /// + /// Latitude + /// + public double Latitude { get; } - /// - /// Longitude - /// - public double Longitude { get; } + /// + /// Longitude + /// + public double Longitude { get; } - /// - /// Speed over the ground in knots - /// - public double Speed { get; } + /// + /// Speed over the ground in knots + /// + public double Speed { get; } - /// - /// Track angle in degrees True - /// - public double Course { get; } + /// + /// Track angle in degrees True + /// + public double Course { get; } - /// - /// Magnetic Variation - /// - public double MagneticVariation { get; } - } + /// + /// Magnetic Variation + /// + public double MagneticVariation { get; } + } } diff --git a/src/NmeaParser/Nmea/Rte.cs b/src/NmeaParser/Nmea/Rte.cs index 0ae1af8..8dcbbc3 100644 --- a/src/NmeaParser/Nmea/Rte.cs +++ b/src/NmeaParser/Nmea/Rte.cs @@ -32,8 +32,10 @@ namespace NmeaParser.Nmea [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Gprte")] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")] [NmeaMessageType("--RTE")] - public sealed class Rte : NmeaMessage, IMultiPartMessage + public sealed class Rte : NmeaMultiSentenceMessage, IEnumerable { + private readonly List _waypoints = new List(); + /// /// Waypoint tpe /// @@ -58,23 +60,18 @@ namespace NmeaParser.Nmea { if (message == null || message.Length < 4) throw new ArgumentException("Invalid GPRTE", "message"); - - TotalMessages = int.Parse(message[0], CultureInfo.InvariantCulture); - MessageNumber = int.Parse(message[1], CultureInfo.InvariantCulture); ListType = message[2] == "c" ? WaypointListType.CompleteWaypointsList : WaypointListType.RemainingWaypointsList; RouteId = message[3]; - Waypoints = message.Skip(4).ToArray(); } - /// - /// Total number of messages of this type in this cycle - /// - public int TotalMessages { get; } - - /// - /// Message number - /// - public int MessageNumber { get; } + /// + protected override bool ParseSentences(Talker talker, string[] message) + { + if (MessageParts[2] != message[2] || MessageParts[3] != message[3]) + return false; + _waypoints.AddRange(message.Skip(4)); + return true; + } /// /// Gets the type of the list. @@ -89,7 +86,7 @@ namespace NmeaParser.Nmea /// /// Waypoints /// - public IReadOnlyList Waypoints { get; } + public IReadOnlyList Waypoints => _waypoints.AsReadOnly(); /// /// Returns an enumerator that iterates through the collection. diff --git a/src/NmeaParser/Nmea/Talker.cs b/src/NmeaParser/Nmea/Talker.cs index 134494f..48e99e7 100644 --- a/src/NmeaParser/Nmea/Talker.cs +++ b/src/NmeaParser/Nmea/Talker.cs @@ -114,10 +114,14 @@ namespace NmeaParser.Nmea /// public enum Talker { + /// + /// Multiple talker IDs sometimes seen in + /// + Multiple = -2, /// /// Unrecognized Talker ID /// - Unknown, + Unknown = -1, /// Independent AIS Base Station IndependentAISBaseStation, // = AB /// Dependent AIS Base Station diff --git a/src/NmeaParser/Nmea/UnknownMessage.cs b/src/NmeaParser/Nmea/UnknownMessage.cs index cba4f01..f5a0d86 100644 --- a/src/NmeaParser/Nmea/UnknownMessage.cs +++ b/src/NmeaParser/Nmea/UnknownMessage.cs @@ -20,12 +20,12 @@ namespace NmeaParser.Nmea /// Represents an unknown message type /// public class UnknownMessage : NmeaMessage - { + { internal UnknownMessage(string type, string[] messageParts) : base(type, messageParts) { } - /// - /// Gets the nmea value array. - /// - public IReadOnlyList Values { get { return base.MessageParts; } } - } + /// + /// Gets the nmea value array. + /// + public IReadOnlyList Values { get { return base.MessageParts; } } + } } diff --git a/src/NmeaParser/Nmea/Vtg.cs b/src/NmeaParser/Nmea/Vtg.cs index 5ffd659..3c84dbf 100644 --- a/src/NmeaParser/Nmea/Vtg.cs +++ b/src/NmeaParser/Nmea/Vtg.cs @@ -17,12 +17,12 @@ using System; namespace NmeaParser.Nmea { /// - /// Course over ground and ground speed - /// + /// Course over ground and ground speed + /// /// /// The actual course and speed relative to the ground. /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "GPVTG")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "GPVTG")] [NmeaMessageType("--VTG")] public class Vtg : NmeaMessage { diff --git a/src/NmeaParser/NmeaDevice.cs b/src/NmeaParser/NmeaDevice.cs index 7ad7b77..8f20a95 100644 --- a/src/NmeaParser/NmeaDevice.cs +++ b/src/NmeaParser/NmeaDevice.cs @@ -22,40 +22,40 @@ using System.Threading; namespace NmeaParser { - /// - /// A generic abstract NMEA device - /// - public abstract class NmeaDevice : IDisposable - { - private readonly object m_lockObject = new object(); - private string m_message = ""; - private Stream? m_stream; - private CancellationTokenSource? m_cts; - private bool m_isOpening; + /// + /// A generic abstract NMEA device + /// + public abstract class NmeaDevice : IDisposable + { + private readonly object m_lockObject = new object(); + private string m_message = ""; + private Stream? m_stream; + private CancellationTokenSource? m_cts; + private bool m_isOpening; private Task? m_ParserTask; /// /// Initializes a new instance of the class. /// protected NmeaDevice() - { - } + { + } /// - /// Opens the device connection. - /// - /// - public async Task OpenAsync() - { - lock (m_lockObject) - { - if (IsOpen || m_isOpening) return; + /// Opens the device connection. + /// + /// + public async Task OpenAsync() + { + lock (m_lockObject) + { + if (IsOpen || m_isOpening) return; m_isOpening = true; - } - m_cts = new CancellationTokenSource(); - m_stream = await OpenStreamAsync(); - StartParser(m_cts.Token); - MultiPartMessageCache.Clear(); + } + m_cts = new CancellationTokenSource(); + m_stream = await OpenStreamAsync(); + StartParser(m_cts.Token); + _lastMultiMessage = null; lock (m_lockObject) { IsOpen = true; @@ -64,30 +64,30 @@ namespace NmeaParser } [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1804:RemoveUnusedLocals", MessageId = "_")] - private void StartParser(CancellationToken token) - { - System.Diagnostics.Debug.WriteLine("Starting parser..."); + private void StartParser(CancellationToken token) + { + System.Diagnostics.Debug.WriteLine("Starting parser..."); m_ParserTask = Task.Run(async () => - { - byte[] buffer = new byte[1024]; - while (!token.IsCancellationRequested) - { - int readCount = 0; - try - { - readCount = await ReadAsync(buffer, 0, 1024, token).ConfigureAwait(false); - } - catch { } - if (token.IsCancellationRequested) - break; - if (readCount > 0) - { - OnData(buffer.Take(readCount).ToArray()); - } - await Task.Delay(50); - } - }); - } + { + byte[] buffer = new byte[1024]; + while (!token.IsCancellationRequested) + { + int readCount = 0; + try + { + readCount = await ReadAsync(buffer, 0, 1024, token).ConfigureAwait(false); + } + catch { } + if (token.IsCancellationRequested) + break; + if (readCount > 0) + { + OnData(buffer.Take(readCount).ToArray()); + } + await Task.Delay(50); + } + }); + } /// /// Performs a read operation of the stream @@ -116,151 +116,134 @@ namespace NmeaParser /// protected abstract Task OpenStreamAsync(); - /// - /// Closes the device. - /// - /// - public async Task CloseAsync() - { - if (m_cts != null) - { - if (m_cts != null) - m_cts.Cancel(); - m_cts = null; - } + /// + /// Closes the device. + /// + /// + public async Task CloseAsync() + { + if (m_cts != null) + { + if (m_cts != null) + m_cts.Cancel(); + m_cts = null; + } if (m_ParserTask != null) await m_ParserTask; if (m_stream != null) await CloseStreamAsync(m_stream); - MultiPartMessageCache.Clear(); - m_stream = null; + _lastMultiMessage = null; + m_stream = null; lock (m_lockObject) { m_isOpening = false; IsOpen = false; } - } - /// - /// Closes the stream the NmeaDevice is working on top off. - /// - /// The stream. - /// - protected abstract Task CloseStreamAsync(Stream stream); + } + /// + /// Closes the stream the NmeaDevice is working on top off. + /// + /// The stream. + /// + protected abstract Task CloseStreamAsync(Stream stream); - private void OnData(byte[] data) - { - var nmea = System.Text.Encoding.UTF8.GetString(data, 0, data.Length); - List lines = new List(); - lock (m_lockObject) - { - m_message += nmea; + private void OnData(byte[] data) + { + var nmea = System.Text.Encoding.UTF8.GetString(data, 0, data.Length); + List lines = new List(); + lock (m_lockObject) + { + m_message += nmea; - var lineEnd = m_message.IndexOf("\n", StringComparison.Ordinal); - while (lineEnd > -1) - { - string line = m_message.Substring(0, lineEnd).Trim(); - m_message = m_message.Substring(lineEnd + 1); - if (!string.IsNullOrEmpty(line)) - lines.Add(line); - lineEnd = m_message.IndexOf("\n", StringComparison.Ordinal); - } - } - foreach(var line in lines) - ProcessMessage(line); - } + var lineEnd = m_message.IndexOf("\n", StringComparison.Ordinal); + while (lineEnd > -1) + { + string line = m_message.Substring(0, lineEnd).Trim(); + m_message = m_message.Substring(lineEnd + 1); + if (!string.IsNullOrEmpty(line)) + lines.Add(line); + lineEnd = m_message.IndexOf("\n", StringComparison.Ordinal); + } + } + foreach(var line in lines) + ProcessMessage(line); + } + + + private IMultiSentenceMessage? _lastMultiMessage; - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification="Must silently handle invalid/corrupt input")] - private void ProcessMessage(string p) - { - try - { - var msg = NmeaParser.Nmea.NmeaMessage.Parse(p); - if (msg != null) - OnMessageReceived(msg); - } - catch { } - } + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification="Must silently handle invalid/corrupt input")] + private void ProcessMessage(string p) + { + try + { + var msg = NmeaParser.Nmea.NmeaMessage.Parse(p, _lastMultiMessage); + if(msg is IMultiSentenceMessage multi) + { + if (!multi.IsComplete) + { + _lastMultiMessage = multi; //Keep it around until next time + return; + } + } + _lastMultiMessage = null; + if (msg != null) + OnMessageReceived(msg); + } + catch { } + } private void OnMessageReceived(Nmea.NmeaMessage msg) { if (msg == null) return; - Nmea.NmeaMessage[]? messageParts = null; - if (msg is IMultiPartMessage multi) - { - string messageType = msg.MessageType.Substring(2); //We don't care about the two first characters. Ie GPGSV, GLGSV, GAGSV etc are all part of the same multi-part message - if (MultiPartMessageCache.ContainsKey(messageType)) - { - var dic = MultiPartMessageCache[messageType]; - if (dic.ContainsKey(multi.MessageNumber - 1) && !dic.ContainsKey(multi.MessageNumber)) - { - dic[multi.MessageNumber] = msg; - } - else //Something is out of order. Clear cache - MultiPartMessageCache.Remove(messageType); - } - else if (multi.MessageNumber == 1) - { - MultiPartMessageCache[messageType] = new Dictionary(multi.TotalMessages); - MultiPartMessageCache[messageType][1] = msg; - } - if (MultiPartMessageCache.ContainsKey(messageType)) - { - var dic = MultiPartMessageCache[messageType]; - if (dic.Count == multi.TotalMessages) //We have a full list - { - MultiPartMessageCache.Remove(messageType); - messageParts = dic.Values.ToArray(); - } - } - } - - MessageReceived?.Invoke(this, new NmeaMessageReceivedEventArgs(msg, messageParts)); + + MessageReceived?.Invoke(this, new NmeaMessageReceivedEventArgs(msg)); } - private readonly Dictionary> MultiPartMessageCache = new Dictionary>(); + //private readonly Dictionary> MultiPartMessageCache = new Dictionary>(); - /// - /// Occurs when an NMEA message is received. - /// - public event EventHandler? MessageReceived; + /// + /// Occurs when an NMEA message is received. + /// + public event EventHandler? MessageReceived; - /// - /// Releases unmanaged and - optionally - managed resources. - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } + /// + /// Releases unmanaged and - optionally - managed resources. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } - /// - /// Releases unmanaged and - optionally - managed resources. - /// - /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected virtual void Dispose(bool disposing) - { - if (m_stream != null) - { - if (m_cts != null) - { - m_cts.Cancel(); - m_cts = null; - } - CloseStreamAsync(m_stream); - if (disposing && m_stream != null) - m_stream.Dispose(); - m_stream = null; - } - } + /// + /// Releases unmanaged and - optionally - managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected virtual void Dispose(bool disposing) + { + if (m_stream != null) + { + if (m_cts != null) + { + m_cts.Cancel(); + m_cts = null; + } + CloseStreamAsync(m_stream); + if (disposing && m_stream != null) + m_stream.Dispose(); + m_stream = null; + } + } - /// - /// Gets a value indicating whether this device is open. - /// - /// - /// true if this instance is open; otherwise, false. - /// - public bool IsOpen { get; private set; } + /// + /// Gets a value indicating whether this device is open. + /// + /// + /// true if this instance is open; otherwise, false. + /// + public bool IsOpen { get; private set; } /// /// Gets a value indicating whether this device supports writing @@ -272,51 +255,34 @@ namespace NmeaParser /// Writes to the device stream. Useful for transmitting RTCM corrections to the device /// Check the property before calling this method. /// - /// The byte array that contains the data to write to the port. - /// The zero-based byte offset in the buffer parameter at which to begin copying - /// bytes to the port. - /// The number of bytes to write. + /// The byte array that contains the data to write to the port. + /// The zero-based byte offset in the buffer parameter at which to begin copying + /// bytes to the port. + /// The number of bytes to write. /// Task /// public virtual Task WriteAsync(byte[] buffer, int offset, int length) { throw new NotSupportedException(); } - } + } - /// - /// Event argument for the - /// - public sealed class NmeaMessageReceivedEventArgs : EventArgs - { - internal NmeaMessageReceivedEventArgs(Nmea.NmeaMessage message, IReadOnlyList? messageParts) + /// + /// Event argument for the + /// + public sealed class NmeaMessageReceivedEventArgs : EventArgs + { + internal NmeaMessageReceivedEventArgs(Nmea.NmeaMessage message) { - Message = message; - MessageParts = messageParts; - } + Message = message; + } - /// - /// Gets the nmea message. - /// - /// - /// The nmea message. - /// - public Nmea.NmeaMessage Message { get; } - /// - /// Gets a value indicating whether this instance is a multi part message. + /// Gets the nmea message. /// /// - /// true if this instance is multi part; otherwise, false. + /// The nmea message. /// - public bool IsMultipart => Message is IMultiPartMessage; - - /// - /// Gets the message parts if this is a multi-part message and all message parts has been received. - /// - /// - /// The message parts. - /// - public IReadOnlyList? MessageParts { get; } - } + public Nmea.NmeaMessage Message { get; } + } } diff --git a/src/NmeaParser/NmeaFileDevice.cs b/src/NmeaParser/NmeaFileDevice.cs index e7f7d13..48bdc88 100644 --- a/src/NmeaParser/NmeaFileDevice.cs +++ b/src/NmeaParser/NmeaFileDevice.cs @@ -21,13 +21,13 @@ using System.Threading.Tasks; namespace NmeaParser { - /// - /// A file-based NMEA device reading from a NMEA log file. - /// - public class NmeaFileDevice : BufferedStreamDevice - { + /// + /// A file-based NMEA device reading from a NMEA log file. + /// + public class NmeaFileDevice : BufferedStreamDevice + { #if NETFX_CORE - private Windows.Storage.IStorageFile? m_storageFile; + private Windows.Storage.IStorageFile? m_storageFile; #endif private string m_filename; @@ -36,7 +36,7 @@ namespace NmeaParser /// Initializes a new instance of the class. /// /// - public NmeaFileDevice(string fileName) : this(fileName, 1000) + public NmeaFileDevice(string fileName) : this(fileName, 1000) { } @@ -46,18 +46,18 @@ namespace NmeaParser /// /// public NmeaFileDevice(Windows.Storage.IStorageFile storageFile) : this(storageFile, 1000) - { - } + { + } #endif /// /// Initializes a new instance of the class. /// /// /// The time to wait between each group of lines being read in milliseconds - public NmeaFileDevice(string fileName, int readSpeed) : base(readSpeed) - { - m_filename = fileName; - } + public NmeaFileDevice(string fileName, int readSpeed) : base(readSpeed) + { + m_filename = fileName; + } #if NETFX_CORE /// @@ -73,24 +73,24 @@ namespace NmeaParser } #endif - /// - /// Gets the name of the nmea file this device is using. - /// - public string FileName - { - get + /// + /// Gets the name of the nmea file this device is using. + /// + public string FileName + { + get { return m_filename; } - } + } /// /// Gets the stream to perform buffer reads on. /// /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")] protected override Task GetStreamAsync() - { + { #if NETFX_CORE if (m_storageFile != null) @@ -102,5 +102,5 @@ namespace NmeaParser return Task.FromResult(System.IO.File.OpenRead(m_filename)); #endif } - } + } } diff --git a/src/NmeaParser/Properties/NmeaParser.rd.xml b/src/NmeaParser/Properties/NmeaParser.rd.xml index 9c46111..0b133a3 100644 --- a/src/NmeaParser/Properties/NmeaParser.rd.xml +++ b/src/NmeaParser/Properties/NmeaParser.rd.xml @@ -27,7 +27,7 @@ - + diff --git a/src/NmeaParser/SerialPortDevice.Desktop.cs b/src/NmeaParser/SerialPortDevice.Desktop.cs index 7c84311..bad59e3 100644 --- a/src/NmeaParser/SerialPortDevice.Desktop.cs +++ b/src/NmeaParser/SerialPortDevice.Desktop.cs @@ -22,22 +22,22 @@ using System.Threading.Tasks; namespace NmeaParser { - /// - /// A Serial Port NMEA device - /// - public class SerialPortDevice : NmeaDevice - { + /// + /// A Serial Port NMEA device + /// + public class SerialPortDevice : NmeaDevice + { /// /// Initializes a new instance of the class. /// /// The serial port. /// port public SerialPortDevice(System.IO.Ports.SerialPort port) - { - if (port == null) - throw new ArgumentNullException("port"); - Port = port; - } + { + if (port == null) + throw new ArgumentNullException("port"); + Port = port; + } /// /// Gets the active serial port. @@ -49,35 +49,35 @@ namespace NmeaParser /// /// protected override Task OpenStreamAsync() - { + { if (!Port.IsOpen) Port.Open(); - return Task.FromResult(Port.BaseStream); - } + return Task.FromResult(Port.BaseStream); + } - /// - /// Closes the stream the NmeaDevice is working on top off. - /// - /// The stream. - /// - protected override Task CloseStreamAsync(System.IO.Stream stream) - { + /// + /// Closes the stream the NmeaDevice is working on top off. + /// + /// The stream. + /// + protected override Task CloseStreamAsync(System.IO.Stream stream) + { if (Port.IsOpen) Port.Close(); - return Task.FromResult(null); - } + return Task.FromResult(null); + } - /// - /// Writes data to the serial port (useful for RTCM/dGPS scenarios) - /// - /// The byte array that contains the data to write to the port. - /// The zero-based byte offset in the buffer parameter at which to begin copying - /// bytes to the port. - /// The number of bytes to write. + /// + /// Writes data to the serial port (useful for RTCM/dGPS scenarios) + /// + /// The byte array that contains the data to write to the port. + /// The zero-based byte offset in the buffer parameter at which to begin copying + /// bytes to the port. + /// The number of bytes to write. [Obsolete("Use WriteAsync")] - public void Write(byte[] buffer, int offset, int count) - { - Port.Write(buffer, offset, count); + public void Write(byte[] buffer, int offset, int count) + { + Port.Write(buffer, offset, count); } /// diff --git a/src/NmeaParser/SerialPortDevice.UWP.cs b/src/NmeaParser/SerialPortDevice.UWP.cs index c30119e..9c572f5 100644 --- a/src/NmeaParser/SerialPortDevice.UWP.cs +++ b/src/NmeaParser/SerialPortDevice.UWP.cs @@ -24,67 +24,67 @@ using System.Runtime.InteropServices.WindowsRuntime; namespace NmeaParser { - /// - /// A Serial Port NMEA device - /// - public class SerialPortDevice : NmeaDevice - { - private SerialDevice m_port; + /// + /// A Serial Port NMEA device + /// + public class SerialPortDevice : NmeaDevice + { + private SerialDevice m_port; - /// - /// Initializes a new instance of the class. - /// - /// The serial port. - /// port - public SerialPortDevice(SerialDevice device) - { - if (device == null) - throw new ArgumentNullException("device"); - m_port = device; - } + /// + /// Initializes a new instance of the class. + /// + /// The serial port. + /// port + public SerialPortDevice(SerialDevice device) + { + if (device == null) + throw new ArgumentNullException("device"); + m_port = device; + } - /// - /// Gets the active serial port. - /// - public SerialDevice SerialDevice - { - get - { - return m_port; - } - } + /// + /// Gets the active serial port. + /// + public SerialDevice SerialDevice + { + get + { + return m_port; + } + } - /// - /// Creates the stream the NmeaDevice is working on top off. - /// - /// - protected override Task OpenStreamAsync() - { - return Task.FromResult(m_port.InputStream.AsStreamForRead(0)); - } + /// + /// Creates the stream the NmeaDevice is working on top off. + /// + /// + protected override Task OpenStreamAsync() + { + return Task.FromResult(m_port.InputStream.AsStreamForRead(0)); + } - /// - /// Closes the stream the NmeaDevice is working on top off. - /// - /// The stream. - /// - protected override Task CloseStreamAsync(System.IO.Stream stream) - { - return Task.CompletedTask; - } + /// + /// Closes the stream the NmeaDevice is working on top off. + /// + /// The stream. + /// + protected override Task CloseStreamAsync(System.IO.Stream stream) + { + return Task.CompletedTask; + } - /// - /// Writes data to the serial port (useful for RTCM/dGPS scenarios) - /// - /// The byte array that contains the data to write to the port. - /// The zero-based byte offset in the buffer parameter at which to begin copying - /// bytes to the port. - /// The number of bytes to write. + /// + /// Writes data to the serial port (useful for RTCM/dGPS scenarios) + /// + /// The byte array that contains the data to write to the port. + /// The zero-based byte offset in the buffer parameter at which to begin copying + /// bytes to the port. + /// The number of bytes to write. [Obsolete("Use WriteAsync")] - public void Write(byte[] buffer, int offset, int count) - { - m_port.OutputStream.AsStreamForWrite().Write(buffer, offset, count); - } + public void Write(byte[] buffer, int offset, int count) + { + m_port.OutputStream.AsStreamForWrite().Write(buffer, offset, count); + } /// public override bool CanWrite => true; diff --git a/src/NmeaParser/StreamDevice.cs b/src/NmeaParser/StreamDevice.cs index f92cbef..e8cbafc 100644 --- a/src/NmeaParser/StreamDevice.cs +++ b/src/NmeaParser/StreamDevice.cs @@ -20,51 +20,51 @@ using System.Threading.Tasks; namespace NmeaParser { - /// - /// Generic stream device - /// + /// + /// Generic stream device + /// public class StreamDevice : NmeaDevice { - private Stream m_stream; + private Stream m_stream; - /// - /// Initializes a new instance of the class. - /// - /// The stream. - public StreamDevice(Stream stream) : base() - { - m_stream = stream ?? throw new ArgumentNullException(nameof(stream)); - } + /// + /// Initializes a new instance of the class. + /// + /// The stream. + public StreamDevice(Stream stream) : base() + { + m_stream = stream ?? throw new ArgumentNullException(nameof(stream)); + } - /// - /// Opens the stream asynchronous. - /// - /// - protected override Task OpenStreamAsync() - { - return Task.FromResult(m_stream); - } + /// + /// Opens the stream asynchronous. + /// + /// + protected override Task OpenStreamAsync() + { + return Task.FromResult(m_stream); + } - /// - /// Closes the stream asynchronous. - /// - /// The stream. - /// - protected override Task CloseStreamAsync(System.IO.Stream stream) - { - return Task.FromResult(true); //do nothing - } + /// + /// Closes the stream asynchronous. + /// + /// The stream. + /// + protected override Task CloseStreamAsync(System.IO.Stream stream) + { + return Task.FromResult(true); //do nothing + } - /// - /// Releases unmanaged and - optionally - managed resources. - /// - /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - if (m_stream != null) - m_stream.Dispose(); - } + /// + /// Releases unmanaged and - optionally - managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + if (m_stream != null) + m_stream.Dispose(); + } /// public override bool CanWrite => m_stream?.CanWrite == true; diff --git a/src/SampleApp.Droid/MainActivity.cs b/src/SampleApp.Droid/MainActivity.cs index f584d7e..a1dc683 100644 --- a/src/SampleApp.Droid/MainActivity.cs +++ b/src/SampleApp.Droid/MainActivity.cs @@ -60,7 +60,7 @@ namespace SampleApp.Droid socket?.Close(); socket?.Dispose(); socket = null; - listener.CloseAsync(); + _ = listener.CloseAsync(); listener = null; startButton.Enabled = !(stopButton.Enabled = false); } diff --git a/src/SampleApp.Droid/Resources/Resource.Designer.cs b/src/SampleApp.Droid/Resources/Resource.Designer.cs index c146640..1084f06 100644 --- a/src/SampleApp.Droid/Resources/Resource.Designer.cs +++ b/src/SampleApp.Droid/Resources/Resource.Designer.cs @@ -31,31 +31,31 @@ namespace SampleApp.Droid public partial class Attribute { - // aapt resource value: 0x7f010007 - public const int font = 2130771975; + // aapt resource value: 0x7F010000 + public const int font = 2130771968; - // aapt resource value: 0x7f010000 - public const int fontProviderAuthority = 2130771968; + // aapt resource value: 0x7F010001 + public const int fontProviderAuthority = 2130771969; - // aapt resource value: 0x7f010003 - public const int fontProviderCerts = 2130771971; + // aapt resource value: 0x7F010002 + public const int fontProviderCerts = 2130771970; - // aapt resource value: 0x7f010004 - public const int fontProviderFetchStrategy = 2130771972; + // aapt resource value: 0x7F010003 + public const int fontProviderFetchStrategy = 2130771971; - // aapt resource value: 0x7f010005 - public const int fontProviderFetchTimeout = 2130771973; + // aapt resource value: 0x7F010004 + public const int fontProviderFetchTimeout = 2130771972; - // aapt resource value: 0x7f010001 - public const int fontProviderPackage = 2130771969; + // aapt resource value: 0x7F010005 + public const int fontProviderPackage = 2130771973; - // aapt resource value: 0x7f010002 - public const int fontProviderQuery = 2130771970; + // aapt resource value: 0x7F010006 + public const int fontProviderQuery = 2130771974; - // aapt resource value: 0x7f010006 - public const int fontStyle = 2130771974; + // aapt resource value: 0x7F010007 + public const int fontStyle = 2130771975; - // aapt resource value: 0x7f010008 + // aapt resource value: 0x7F010008 public const int fontWeight = 2130771976; static Attribute() @@ -71,8 +71,8 @@ namespace SampleApp.Droid public partial class Boolean { - // aapt resource value: 0x7f080000 - public const int abc_action_bar_embed_tabs = 2131230720; + // aapt resource value: 0x7F020000 + public const int abc_action_bar_embed_tabs = 2130837504; static Boolean() { @@ -87,26 +87,26 @@ namespace SampleApp.Droid public partial class Color { - // aapt resource value: 0x7f050003 - public const int notification_action_color_filter = 2131034115; + // aapt resource value: 0x7F030000 + public const int notification_action_color_filter = 2130903040; - // aapt resource value: 0x7f050004 - public const int notification_icon_bg_color = 2131034116; + // aapt resource value: 0x7F030001 + public const int notification_icon_bg_color = 2130903041; - // aapt resource value: 0x7f050000 - public const int notification_material_background_media_default_color = 2131034112; + // aapt resource value: 0x7F030002 + public const int notification_material_background_media_default_color = 2130903042; - // aapt resource value: 0x7f050001 - public const int primary_text_default_material_dark = 2131034113; + // aapt resource value: 0x7F030003 + public const int primary_text_default_material_dark = 2130903043; - // aapt resource value: 0x7f050005 - public const int ripple_material_light = 2131034117; + // aapt resource value: 0x7F030004 + public const int ripple_material_light = 2130903044; - // aapt resource value: 0x7f050002 - public const int secondary_text_default_material_dark = 2131034114; + // aapt resource value: 0x7F030005 + public const int secondary_text_default_material_dark = 2130903045; - // aapt resource value: 0x7f050006 - public const int secondary_text_default_material_light = 2131034118; + // aapt resource value: 0x7F030006 + public const int secondary_text_default_material_light = 2130903046; static Color() { @@ -121,65 +121,65 @@ namespace SampleApp.Droid public partial class Dimension { - // aapt resource value: 0x7f090004 - public const int compat_button_inset_horizontal_material = 2131296260; + // aapt resource value: 0x7F040000 + public const int compat_button_inset_horizontal_material = 2130968576; - // aapt resource value: 0x7f090005 - public const int compat_button_inset_vertical_material = 2131296261; + // aapt resource value: 0x7F040001 + public const int compat_button_inset_vertical_material = 2130968577; - // aapt resource value: 0x7f090006 - public const int compat_button_padding_horizontal_material = 2131296262; + // aapt resource value: 0x7F040002 + public const int compat_button_padding_horizontal_material = 2130968578; - // aapt resource value: 0x7f090007 - public const int compat_button_padding_vertical_material = 2131296263; + // aapt resource value: 0x7F040003 + public const int compat_button_padding_vertical_material = 2130968579; - // aapt resource value: 0x7f090008 - public const int compat_control_corner_material = 2131296264; + // aapt resource value: 0x7F040004 + public const int compat_control_corner_material = 2130968580; - // aapt resource value: 0x7f090009 - public const int notification_action_icon_size = 2131296265; + // aapt resource value: 0x7F040005 + public const int notification_action_icon_size = 2130968581; - // aapt resource value: 0x7f09000a - public const int notification_action_text_size = 2131296266; + // aapt resource value: 0x7F040006 + public const int notification_action_text_size = 2130968582; - // aapt resource value: 0x7f09000b - public const int notification_big_circle_margin = 2131296267; + // aapt resource value: 0x7F040007 + public const int notification_big_circle_margin = 2130968583; - // aapt resource value: 0x7f090001 - public const int notification_content_margin_start = 2131296257; + // aapt resource value: 0x7F040008 + public const int notification_content_margin_start = 2130968584; - // aapt resource value: 0x7f09000c - public const int notification_large_icon_height = 2131296268; + // aapt resource value: 0x7F040009 + public const int notification_large_icon_height = 2130968585; - // aapt resource value: 0x7f09000d - public const int notification_large_icon_width = 2131296269; + // aapt resource value: 0x7F04000A + public const int notification_large_icon_width = 2130968586; - // aapt resource value: 0x7f090002 - public const int notification_main_column_padding_top = 2131296258; + // aapt resource value: 0x7F04000B + public const int notification_main_column_padding_top = 2130968587; - // aapt resource value: 0x7f090003 - public const int notification_media_narrow_margin = 2131296259; + // aapt resource value: 0x7F04000C + public const int notification_media_narrow_margin = 2130968588; - // aapt resource value: 0x7f09000e - public const int notification_right_icon_size = 2131296270; + // aapt resource value: 0x7F04000D + public const int notification_right_icon_size = 2130968589; - // aapt resource value: 0x7f090000 - public const int notification_right_side_padding_top = 2131296256; + // aapt resource value: 0x7F04000E + public const int notification_right_side_padding_top = 2130968590; - // aapt resource value: 0x7f09000f - public const int notification_small_icon_background_padding = 2131296271; + // aapt resource value: 0x7F04000F + public const int notification_small_icon_background_padding = 2130968591; - // aapt resource value: 0x7f090010 - public const int notification_small_icon_size_as_large = 2131296272; + // aapt resource value: 0x7F040010 + public const int notification_small_icon_size_as_large = 2130968592; - // aapt resource value: 0x7f090011 - public const int notification_subtext_size = 2131296273; + // aapt resource value: 0x7F040011 + public const int notification_subtext_size = 2130968593; - // aapt resource value: 0x7f090012 - public const int notification_top_pad = 2131296274; + // aapt resource value: 0x7F040012 + public const int notification_top_pad = 2130968594; - // aapt resource value: 0x7f090013 - public const int notification_top_pad_large_text = 2131296275; + // aapt resource value: 0x7F040013 + public const int notification_top_pad_large_text = 2130968595; static Dimension() { @@ -194,41 +194,41 @@ namespace SampleApp.Droid public partial class Drawable { - // aapt resource value: 0x7f020000 - public const int notification_action_background = 2130837504; + // aapt resource value: 0x7F050000 + public const int notification_action_background = 2131034112; - // aapt resource value: 0x7f020001 - public const int notification_bg = 2130837505; + // aapt resource value: 0x7F050001 + public const int notification_bg = 2131034113; - // aapt resource value: 0x7f020002 - public const int notification_bg_low = 2130837506; + // aapt resource value: 0x7F050002 + public const int notification_bg_low = 2131034114; - // aapt resource value: 0x7f020003 - public const int notification_bg_low_normal = 2130837507; + // aapt resource value: 0x7F050003 + public const int notification_bg_low_normal = 2131034115; - // aapt resource value: 0x7f020004 - public const int notification_bg_low_pressed = 2130837508; + // aapt resource value: 0x7F050004 + public const int notification_bg_low_pressed = 2131034116; - // aapt resource value: 0x7f020005 - public const int notification_bg_normal = 2130837509; + // aapt resource value: 0x7F050005 + public const int notification_bg_normal = 2131034117; - // aapt resource value: 0x7f020006 - public const int notification_bg_normal_pressed = 2130837510; + // aapt resource value: 0x7F050006 + public const int notification_bg_normal_pressed = 2131034118; - // aapt resource value: 0x7f020007 - public const int notification_icon_background = 2130837511; + // aapt resource value: 0x7F050007 + public const int notification_icon_background = 2131034119; - // aapt resource value: 0x7f02000a - public const int notification_template_icon_bg = 2130837514; + // aapt resource value: 0x7F050008 + public const int notification_template_icon_bg = 2131034120; - // aapt resource value: 0x7f02000b - public const int notification_template_icon_low_bg = 2130837515; + // aapt resource value: 0x7F050009 + public const int notification_template_icon_low_bg = 2131034121; - // aapt resource value: 0x7f020008 - public const int notification_tile_bg = 2130837512; + // aapt resource value: 0x7F05000A + public const int notification_tile_bg = 2131034122; - // aapt resource value: 0x7f020009 - public const int notify_panel_notification_icon_bg = 2130837513; + // aapt resource value: 0x7F05000B + public const int notify_panel_notification_icon_bg = 2131034123; static Drawable() { @@ -243,119 +243,119 @@ namespace SampleApp.Droid public partial class Id { - // aapt resource value: 0x7f0a0015 - public const int action0 = 2131361813; + // aapt resource value: 0x7F060000 + public const int action0 = 2131099648; - // aapt resource value: 0x7f0a0012 - public const int action_container = 2131361810; + // aapt resource value: 0x7F060005 + public const int actions = 2131099653; - // aapt resource value: 0x7f0a0019 - public const int action_divider = 2131361817; + // aapt resource value: 0x7F060001 + public const int action_container = 2131099649; - // aapt resource value: 0x7f0a0013 - public const int action_image = 2131361811; + // aapt resource value: 0x7F060002 + public const int action_divider = 2131099650; - // aapt resource value: 0x7f0a0014 - public const int action_text = 2131361812; + // aapt resource value: 0x7F060003 + public const int action_image = 2131099651; - // aapt resource value: 0x7f0a0023 - public const int actions = 2131361827; + // aapt resource value: 0x7F060004 + public const int action_text = 2131099652; - // aapt resource value: 0x7f0a000d - public const int altitude = 2131361805; + // aapt resource value: 0x7F060006 + public const int altitude = 2131099654; - // aapt resource value: 0x7f0a0006 - public const int async = 2131361798; + // aapt resource value: 0x7F060007 + public const int async = 2131099655; - // aapt resource value: 0x7f0a0007 - public const int blocking = 2131361799; + // aapt resource value: 0x7F060008 + public const int blocking = 2131099656; - // aapt resource value: 0x7f0a0016 - public const int cancel_action = 2131361814; + // aapt resource value: 0x7F060009 + public const int cancel_action = 2131099657; - // aapt resource value: 0x7f0a001e - public const int chronometer = 2131361822; + // aapt resource value: 0x7F06000A + public const int chronometer = 2131099658; - // aapt resource value: 0x7f0a000e - public const int device_picker = 2131361806; + // aapt resource value: 0x7F06000B + public const int device_picker = 2131099659; - // aapt resource value: 0x7f0a0025 - public const int end_padder = 2131361829; + // aapt resource value: 0x7F06000C + public const int end_padder = 2131099660; - // aapt resource value: 0x7f0a0008 - public const int forever = 2131361800; + // aapt resource value: 0x7F06000D + public const int forever = 2131099661; - // aapt resource value: 0x7f0a0020 - public const int icon = 2131361824; + // aapt resource value: 0x7F06000E + public const int icon = 2131099662; - // aapt resource value: 0x7f0a0024 - public const int icon_group = 2131361828; + // aapt resource value: 0x7F06000F + public const int icon_group = 2131099663; - // aapt resource value: 0x7f0a001f - public const int info = 2131361823; + // aapt resource value: 0x7F060010 + public const int info = 2131099664; - // aapt resource value: 0x7f0a0009 - public const int italic = 2131361801; + // aapt resource value: 0x7F060011 + public const int italic = 2131099665; - // aapt resource value: 0x7f0a000c - public const int latitude = 2131361804; + // aapt resource value: 0x7F060012 + public const int latitude = 2131099666; - // aapt resource value: 0x7f0a0000 - public const int line1 = 2131361792; + // aapt resource value: 0x7F060013 + public const int line1 = 2131099667; - // aapt resource value: 0x7f0a0001 - public const int line3 = 2131361793; + // aapt resource value: 0x7F060014 + public const int line3 = 2131099668; - // aapt resource value: 0x7f0a000b - public const int longitude = 2131361803; + // aapt resource value: 0x7F060015 + public const int longitude = 2131099669; - // aapt resource value: 0x7f0a0018 - public const int media_actions = 2131361816; + // aapt resource value: 0x7F060016 + public const int media_actions = 2131099670; - // aapt resource value: 0x7f0a000a - public const int normal = 2131361802; + // aapt resource value: 0x7F060017 + public const int normal = 2131099671; - // aapt resource value: 0x7f0a0022 - public const int notification_background = 2131361826; + // aapt resource value: 0x7F060018 + public const int notification_background = 2131099672; - // aapt resource value: 0x7f0a001b - public const int notification_main_column = 2131361819; + // aapt resource value: 0x7F060019 + public const int notification_main_column = 2131099673; - // aapt resource value: 0x7f0a001a - public const int notification_main_column_container = 2131361818; + // aapt resource value: 0x7F06001A + public const int notification_main_column_container = 2131099674; - // aapt resource value: 0x7f0a0011 - public const int output = 2131361809; + // aapt resource value: 0x7F06001B + public const int output = 2131099675; - // aapt resource value: 0x7f0a0021 - public const int right_icon = 2131361825; + // aapt resource value: 0x7F06001C + public const int right_icon = 2131099676; - // aapt resource value: 0x7f0a001c - public const int right_side = 2131361820; + // aapt resource value: 0x7F06001D + public const int right_side = 2131099677; - // aapt resource value: 0x7f0a000f - public const int startButton = 2131361807; + // aapt resource value: 0x7F06001E + public const int startButton = 2131099678; - // aapt resource value: 0x7f0a0017 - public const int status_bar_latest_event_content = 2131361815; + // aapt resource value: 0x7F06001F + public const int status_bar_latest_event_content = 2131099679; - // aapt resource value: 0x7f0a0010 - public const int stopButton = 2131361808; + // aapt resource value: 0x7F060020 + public const int stopButton = 2131099680; - // aapt resource value: 0x7f0a0002 - public const int tag_transition_group = 2131361794; + // aapt resource value: 0x7F060021 + public const int tag_transition_group = 2131099681; - // aapt resource value: 0x7f0a0003 - public const int text = 2131361795; + // aapt resource value: 0x7F060022 + public const int text = 2131099682; - // aapt resource value: 0x7f0a0004 - public const int text2 = 2131361796; + // aapt resource value: 0x7F060023 + public const int text2 = 2131099683; - // aapt resource value: 0x7f0a001d - public const int time = 2131361821; + // aapt resource value: 0x7F060024 + public const int time = 2131099684; - // aapt resource value: 0x7f0a0005 - public const int title = 2131361797; + // aapt resource value: 0x7F060025 + public const int title = 2131099685; static Id() { @@ -370,11 +370,11 @@ namespace SampleApp.Droid public partial class Integer { - // aapt resource value: 0x7f060000 - public const int cancel_button_image_alpha = 2131099648; + // aapt resource value: 0x7F070000 + public const int cancel_button_image_alpha = 2131165184; - // aapt resource value: 0x7f060001 - public const int status_bar_notification_info_maxnum = 2131099649; + // aapt resource value: 0x7F070001 + public const int status_bar_notification_info_maxnum = 2131165185; static Integer() { @@ -389,53 +389,53 @@ namespace SampleApp.Droid public partial class Layout { - // aapt resource value: 0x7f030000 - public const int Main = 2130903040; + // aapt resource value: 0x7F080000 + public const int Main = 2131230720; - // aapt resource value: 0x7f030001 - public const int notification_action = 2130903041; + // aapt resource value: 0x7F080001 + public const int notification_action = 2131230721; - // aapt resource value: 0x7f030002 - public const int notification_action_tombstone = 2130903042; + // aapt resource value: 0x7F080002 + public const int notification_action_tombstone = 2131230722; - // aapt resource value: 0x7f030003 - public const int notification_media_action = 2130903043; + // aapt resource value: 0x7F080003 + public const int notification_media_action = 2131230723; - // aapt resource value: 0x7f030004 - public const int notification_media_cancel_action = 2130903044; + // aapt resource value: 0x7F080004 + public const int notification_media_cancel_action = 2131230724; - // aapt resource value: 0x7f030005 - public const int notification_template_big_media = 2130903045; + // aapt resource value: 0x7F080005 + public const int notification_template_big_media = 2131230725; - // aapt resource value: 0x7f030006 - public const int notification_template_big_media_custom = 2130903046; + // aapt resource value: 0x7F080006 + public const int notification_template_big_media_custom = 2131230726; - // aapt resource value: 0x7f030007 - public const int notification_template_big_media_narrow = 2130903047; + // aapt resource value: 0x7F080007 + public const int notification_template_big_media_narrow = 2131230727; - // aapt resource value: 0x7f030008 - public const int notification_template_big_media_narrow_custom = 2130903048; + // aapt resource value: 0x7F080008 + public const int notification_template_big_media_narrow_custom = 2131230728; - // aapt resource value: 0x7f030009 - public const int notification_template_custom_big = 2130903049; + // aapt resource value: 0x7F080009 + public const int notification_template_custom_big = 2131230729; - // aapt resource value: 0x7f03000a - public const int notification_template_icon_group = 2130903050; + // aapt resource value: 0x7F08000A + public const int notification_template_icon_group = 2131230730; - // aapt resource value: 0x7f03000b - public const int notification_template_lines_media = 2130903051; + // aapt resource value: 0x7F08000B + public const int notification_template_lines_media = 2131230731; - // aapt resource value: 0x7f03000c - public const int notification_template_media = 2130903052; + // aapt resource value: 0x7F08000C + public const int notification_template_media = 2131230732; - // aapt resource value: 0x7f03000d - public const int notification_template_media_custom = 2130903053; + // aapt resource value: 0x7F08000D + public const int notification_template_media_custom = 2131230733; - // aapt resource value: 0x7f03000e - public const int notification_template_part_chronometer = 2130903054; + // aapt resource value: 0x7F08000E + public const int notification_template_part_chronometer = 2131230734; - // aapt resource value: 0x7f03000f - public const int notification_template_part_time = 2130903055; + // aapt resource value: 0x7F08000F + public const int notification_template_part_time = 2131230735; static Layout() { @@ -450,11 +450,11 @@ namespace SampleApp.Droid public partial class String { - // aapt resource value: 0x7f070001 - public const int app_name = 2131165185; + // aapt resource value: 0x7F090000 + public const int app_name = 2131296256; - // aapt resource value: 0x7f070000 - public const int status_bar_notification_info_overflow = 2131165184; + // aapt resource value: 0x7F090001 + public const int status_bar_notification_info_overflow = 2131296257; static String() { @@ -469,41 +469,41 @@ namespace SampleApp.Droid public partial class Style { - // aapt resource value: 0x7f040005 - public const int TextAppearance_Compat_Notification = 2130968581; + // aapt resource value: 0x7F0A0000 + public const int TextAppearance_Compat_Notification = 2131361792; - // aapt resource value: 0x7f040006 - public const int TextAppearance_Compat_Notification_Info = 2130968582; + // aapt resource value: 0x7F0A0001 + public const int TextAppearance_Compat_Notification_Info = 2131361793; - // aapt resource value: 0x7f040000 - public const int TextAppearance_Compat_Notification_Info_Media = 2130968576; + // aapt resource value: 0x7F0A0002 + public const int TextAppearance_Compat_Notification_Info_Media = 2131361794; - // aapt resource value: 0x7f04000b - public const int TextAppearance_Compat_Notification_Line2 = 2130968587; + // aapt resource value: 0x7F0A0003 + public const int TextAppearance_Compat_Notification_Line2 = 2131361795; - // aapt resource value: 0x7f040004 - public const int TextAppearance_Compat_Notification_Line2_Media = 2130968580; + // aapt resource value: 0x7F0A0004 + public const int TextAppearance_Compat_Notification_Line2_Media = 2131361796; - // aapt resource value: 0x7f040001 - public const int TextAppearance_Compat_Notification_Media = 2130968577; + // aapt resource value: 0x7F0A0005 + public const int TextAppearance_Compat_Notification_Media = 2131361797; - // aapt resource value: 0x7f040007 - public const int TextAppearance_Compat_Notification_Time = 2130968583; + // aapt resource value: 0x7F0A0006 + public const int TextAppearance_Compat_Notification_Time = 2131361798; - // aapt resource value: 0x7f040002 - public const int TextAppearance_Compat_Notification_Time_Media = 2130968578; + // aapt resource value: 0x7F0A0007 + public const int TextAppearance_Compat_Notification_Time_Media = 2131361799; - // aapt resource value: 0x7f040008 - public const int TextAppearance_Compat_Notification_Title = 2130968584; + // aapt resource value: 0x7F0A0008 + public const int TextAppearance_Compat_Notification_Title = 2131361800; - // aapt resource value: 0x7f040003 - public const int TextAppearance_Compat_Notification_Title_Media = 2130968579; + // aapt resource value: 0x7F0A0009 + public const int TextAppearance_Compat_Notification_Title_Media = 2131361801; - // aapt resource value: 0x7f040009 - public const int Widget_Compat_NotificationActionContainer = 2130968585; + // aapt resource value: 0x7F0A000A + public const int Widget_Compat_NotificationActionContainer = 2131361802; - // aapt resource value: 0x7f04000a - public const int Widget_Compat_NotificationActionText = 2130968586; + // aapt resource value: 0x7F0A000B + public const int Widget_Compat_NotificationActionText = 2131361803; static Style() { @@ -518,37 +518,21 @@ namespace SampleApp.Droid public partial class Styleable { + // aapt resource value: { 0x7F010001,0x7F010002,0x7F010003,0x7F010004,0x7F010005,0x7F010006 } public static int[] FontFamily = new int[] { - 2130771968, 2130771969, 2130771970, 2130771971, 2130771972, - 2130771973}; - - // aapt resource value: 0 - public const int FontFamily_fontProviderAuthority = 0; - - // aapt resource value: 3 - public const int FontFamily_fontProviderCerts = 3; - - // aapt resource value: 4 - public const int FontFamily_fontProviderFetchStrategy = 4; - - // aapt resource value: 5 - public const int FontFamily_fontProviderFetchTimeout = 5; - - // aapt resource value: 1 - public const int FontFamily_fontProviderPackage = 1; - - // aapt resource value: 2 - public const int FontFamily_fontProviderQuery = 2; + 2130771973, + 2130771974}; + // aapt resource value: { 0x1010532,0x1010533,0x101053F,0x7F010000,0x7F010007,0x7F010008 } public static int[] FontFamilyFont = new int[] { 16844082, 16844083, 16844095, - 2130771974, + 2130771968, 2130771975, 2130771976}; @@ -561,15 +545,33 @@ namespace SampleApp.Droid // aapt resource value: 1 public const int FontFamilyFont_android_fontWeight = 1; - // aapt resource value: 4 - public const int FontFamilyFont_font = 4; - // aapt resource value: 3 - public const int FontFamilyFont_fontStyle = 3; + public const int FontFamilyFont_font = 3; + + // aapt resource value: 4 + public const int FontFamilyFont_fontStyle = 4; // aapt resource value: 5 public const int FontFamilyFont_fontWeight = 5; + // aapt resource value: 0 + public const int FontFamily_fontProviderAuthority = 0; + + // aapt resource value: 1 + public const int FontFamily_fontProviderCerts = 1; + + // aapt resource value: 2 + public const int FontFamily_fontProviderFetchStrategy = 2; + + // aapt resource value: 3 + public const int FontFamily_fontProviderFetchTimeout = 3; + + // aapt resource value: 4 + public const int FontFamily_fontProviderPackage = 4; + + // aapt resource value: 5 + public const int FontFamily_fontProviderQuery = 5; + static Styleable() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); diff --git a/src/SampleApp.WinDesktop/MainWindow.xaml.cs b/src/SampleApp.WinDesktop/MainWindow.xaml.cs index 71e24b9..126ffdb 100644 --- a/src/SampleApp.WinDesktop/MainWindow.xaml.cs +++ b/src/SampleApp.WinDesktop/MainWindow.xaml.cs @@ -83,24 +83,21 @@ namespace SampleApp.WinDesktop ((NmeaParser.SerialPortDevice)device).Port.BaudRate); } } - Dictionary> gsvMessages = new Dictionary>(); + Dictionary gsvMessages = new Dictionary(); private void device_MessageReceived(object sender, NmeaParser.NmeaMessageReceivedEventArgs args) { Dispatcher.BeginInvoke((Action) delegate() { - messages.Enqueue(args.Message.MessageType + ": " + args.Message.ToString()); + messages.Enqueue(args.Message.ToString()); if (messages.Count > 100) messages.Dequeue(); //Keep message queue at 100 output.Text = string.Join("\n", messages.ToArray()); output.Select(output.Text.Length - 1, 0); //scroll to bottom - if(args.Message is NmeaParser.Nmea.Gsv gpgsv) + if (args.Message is NmeaParser.Nmea.Gsv gpgsv) { - if (args.IsMultipart && args.MessageParts != null) - { - gsvMessages[args.Message.MessageType] = args.MessageParts.OfType().ToList(); - satView.GsvMessages = gsvMessages.SelectMany(m=>m.Value); - } + gsvMessages[gpgsv.TalkerId] = gpgsv; + satView.GsvMessages = gsvMessages.Values; } else if (args.Message is NmeaParser.Nmea.Rmc) gprmcView.Message = args.Message as NmeaParser.Nmea.Rmc; @@ -112,19 +109,19 @@ namespace SampleApp.WinDesktop gpgllView.Message = args.Message as NmeaParser.Nmea.Gll; else if (args.Message is NmeaParser.Nmea.Garmin.Pgrme) pgrmeView.Message = args.Message as NmeaParser.Nmea.Garmin.Pgrme; - else - { - var ctrl = MessagePanel.Children.OfType().Where(c => c.Message.MessageType == args.Message.MessageType).FirstOrDefault(); - if(ctrl == null) - { - ctrl = new UnknownMessageControl() - { - Style = this.Resources["card"] as Style - }; - MessagePanel.Children.Add(ctrl); - } - ctrl.Message = args.Message; - } + else + { + var ctrl = MessagePanel.Children.OfType().Where(c => c.Message.MessageType == args.Message.MessageType).FirstOrDefault(); + if (ctrl == null) + { + ctrl = new UnknownMessageControl() + { + Style = this.Resources["card"] as Style + }; + MessagePanel.Children.Add(ctrl); + } + ctrl.Message = args.Message; + } }); } diff --git a/src/UnitTests/NmeaParser.Tests.NET/NmeaParser.Tests.Net.csproj b/src/UnitTests/NmeaParser.Tests.NET/NmeaParser.Tests.Net.csproj new file mode 100644 index 0000000..7c7832d --- /dev/null +++ b/src/UnitTests/NmeaParser.Tests.NET/NmeaParser.Tests.Net.csproj @@ -0,0 +1,30 @@ + + + + net451;netcoreapp3.1 + false + + + + + + + + + + + + + + NmeaSampleData.txt + PreserveNewest + + + TrimbleR2SampleData.txt + PreserveNewest + + + + + + \ No newline at end of file diff --git a/src/UnitTests/NmeaParser.Tests.NET45/NmeaParser.Tests.NET45.csproj b/src/UnitTests/NmeaParser.Tests.NET45/NmeaParser.Tests.NET45.csproj deleted file mode 100644 index c3d4368..0000000 --- a/src/UnitTests/NmeaParser.Tests.NET45/NmeaParser.Tests.NET45.csproj +++ /dev/null @@ -1,77 +0,0 @@ - - - - - Debug - AnyCPU - {170EE734-37F0-425F-822B-B865348ECEC6} - Library - Properties - NmeaParser.Tests.NET45 - NmeaParser.Tests.NET45 - v4.6.1 - 512 - {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 15.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages - False - UnitTest - - - 8.0 - enable - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - PackageReference - - - - 1.4.0 - - - 1.4.0 - - - - - - - - - - {1adc3666-1ddb-48c4-9811-1e58b6d09a7c} - NmeaParser - - - - - NmeaSampleData.txt - PreserveNewest - - - TrimbleR2SampleData.txt - PreserveNewest - - - - - - \ No newline at end of file diff --git a/src/UnitTests/NmeaParser.Tests.NET45/Properties/AssemblyInfo.cs b/src/UnitTests/NmeaParser.Tests.NET45/Properties/AssemblyInfo.cs deleted file mode 100644 index 67a3afd..0000000 --- a/src/UnitTests/NmeaParser.Tests.NET45/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -[assembly: AssemblyTitle("NmeaParser.Tests.NET45")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("NmeaParser.Tests.NET45")] -[assembly: AssemblyCopyright("Copyright © 2017")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -[assembly: ComVisible(false)] - -[assembly: Guid("170ee734-37f0-425f-822b-b865348ecec6")] - -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/UnitTests/NmeaParser.Tests/DeviceTests.cs b/src/UnitTests/NmeaParser.Tests/DeviceTests.cs index ae045ea..d20ea1b 100644 --- a/src/UnitTests/NmeaParser.Tests/DeviceTests.cs +++ b/src/UnitTests/NmeaParser.Tests/DeviceTests.cs @@ -8,46 +8,42 @@ using System.Threading.Tasks; namespace NmeaParser.Tests { - [TestClass] - public class DeviceTests - { - [TestMethod] - [TestCategory("Device")] + [TestClass] + public class DeviceTests + { + [TestMethod] + [TestCategory("Device")] [Timeout(2000)] - public async Task TestGpgsvGroupMessage() - { - var message = "$GPGSV,3,1,9,00,30,055,48,00,19,281,00,27,19,275,00,12,16,319,00*4C\n$GPGSV,3,2,9,00,30,055,48,00,19,281,00,27,19,275,00,12,16,319,00*4F\n$GPGSV,3,3,9,32,10,037,00,,,,,,,,,,,,*74"; - NmeaDevice dev = new BufferedStringDevice(message); - TaskCompletionSource tcs = new TaskCompletionSource(); - int count = 0; - dev.MessageReceived += (s, e) => - { - count++; - try - { - Assert.IsTrue(e.IsMultipart, "IsMultiPart"); - Assert.IsInstanceOfType(e.Message, typeof(NmeaParser.Nmea.Gsv)); - var msg = (NmeaParser.Nmea.Gsv)e.Message; - if (msg.TotalMessages == msg.MessageNumber) - { - Assert.IsNotNull(e.MessageParts); - Assert.AreEqual(e.MessageParts.Count, 3, "MessageParts.Length"); - tcs.SetResult(true); - } - else - Assert.IsNull(e.MessageParts); - if (count > 3) - Assert.Fail(); - } - catch(System.Exception ex) - { - tcs.SetException(ex); - } - }; - await dev.OpenAsync(); - await tcs.Task; - var _ = dev.CloseAsync(); - } + public async Task TestGpgsvGroupMessage() + { + var message = "$GPGSV,3,1,9,00,30,055,48,00,19,281,00,27,19,275,00,12,16,319,00*4C\n$GPGSV,3,2,9,00,30,055,48,00,19,281,00,27,19,275,00,12,16,319,00*4F\n$GPGSV,3,3,9,32,10,037,00,,,,,,,,,,,,*74"; + NmeaDevice dev = new BufferedStringDevice(message); + TaskCompletionSource tcs = new TaskCompletionSource(); + int count = 0; + dev.MessageReceived += (s, e) => + { + count++; + try + { + Assert.IsInstanceOfType(e.Message, typeof(Gsv)); + var msg = (NmeaParser.Nmea.Gsv)e.Message; + Assert.IsTrue(((IMultiSentenceMessage)e.Message).IsComplete); + Assert.AreEqual(9, msg.SVsInView); + Assert.AreEqual(9, msg.SVs.Count); + + if (count > 1) + Assert.Fail(); + tcs.SetResult(true); + } + catch(System.Exception ex) + { + tcs.SetException(ex); + } + }; + await dev.OpenAsync(); + await tcs.Task; + var _ = dev.CloseAsync(); + } [TestMethod] [TestCategory("Device")] @@ -61,34 +57,18 @@ $GLGSV,4,3,14,73,52,022,47,74,62,248,47,72,44,331,42,71,78,111,49*6A $GAGSV,4,4,14,19,82,349,40,1,44,220,40,4,24,314,38*5F"; NmeaDevice dev = new BufferedStringDevice(message); TaskCompletionSource tcs = new TaskCompletionSource(); - int count = 0; dev.MessageReceived += (s, e) => { - count++; try { - Assert.IsTrue(e.IsMultipart, "IsMultiPart"); Assert.IsInstanceOfType(e.Message, typeof(NmeaParser.Nmea.Gsv)); + Assert.AreEqual(Talker.Multiple, e.Message.TalkerId); var msg = (NmeaParser.Nmea.Gsv)e.Message; - if (msg.TotalMessages == msg.MessageNumber) - { - Assert.IsNotNull(e.MessageParts); - Assert.AreEqual(e.MessageParts.Count, 4, "MessageParts.Length"); - Assert.IsInstanceOfType(e.MessageParts[0], typeof(NmeaParser.Nmea.Gsv)); - Assert.IsInstanceOfType(e.MessageParts[1], typeof(NmeaParser.Nmea.Gsv)); - Assert.IsInstanceOfType(e.MessageParts[2], typeof(NmeaParser.Nmea.Gsv)); - Assert.IsInstanceOfType(e.MessageParts[3], typeof(NmeaParser.Nmea.Gsv)); - Assert.AreEqual(Talker.GlobalPositioningSystem, e.MessageParts[0].TalkerId); - Assert.AreEqual(Talker.GlobalPositioningSystem, e.MessageParts[1].TalkerId); - Assert.AreEqual(Talker.GlonassReceiver, e.MessageParts[2].TalkerId); - Assert.AreEqual(Talker.GalileoPositioningSystem, e.MessageParts[3].TalkerId); - - tcs.SetResult(true); - } - else - Assert.IsNull(e.MessageParts); - if (count > 3) - Assert.Fail(); + Assert.AreEqual(Talker.GlobalPositioningSystem, msg.SVs[0].TalkerId); + Assert.AreEqual(Talker.GlobalPositioningSystem, msg.SVs[4].TalkerId); + Assert.AreEqual(Talker.GlonassReceiver, msg.SVs[8].TalkerId); + Assert.AreEqual(Talker.GalileoPositioningSystem, msg.SVs[12].TalkerId); + tcs.SetResult(true); } catch (System.Exception ex) { @@ -101,34 +81,23 @@ $GAGSV,4,4,14,19,82,349,40,1,44,220,40,4,24,314,38*5F"; } [TestMethod] - [TestCategory("Device")] + [TestCategory("Device")] [Timeout(2000)] public async Task TestInvalidGpgsvGroupMessage() - { - var message = "$GPGSV,3,2,9,00,30,055,48,00,19,281,00,27,19,275,00,12,16,319,00*4D\n$GPGSV,3,2,9,00,30,055,48,00,19,281,00,27,19,275,00,12,16,319,00*4F\n$GPGSV,3,3,9,32,10,037,00,,,,,,,,,,,,*74"; - NmeaDevice dev = new BufferedStringDevice(message); - TaskCompletionSource tcs = new TaskCompletionSource(); - int count = 0; - dev.MessageReceived += (s, e) => - { - count++; - try - { - Assert.IsTrue(e.IsMultipart, "IsMultiPart"); - Assert.IsInstanceOfType(e.Message, typeof(NmeaParser.Nmea.Gsv)); - var msg = e.Message as NmeaParser.Nmea.Gsv; - Assert.IsNull(e.MessageParts); - if (count > 6) - tcs.SetResult(true); - } - catch (System.Exception ex) - { - tcs.SetException(ex); - } - }; - await dev.OpenAsync(); - await tcs.Task; - var _ = dev.CloseAsync(); - } - } + { + var message = "$GPGSV,3,2,9,00,30,055,48,00,19,281,00,27,19,275,00,12,16,319,00*4D\n$GPGSV,3,2,9,00,30,055,48,00,19,281,00,27,19,275,00,12,16,319,00*4F\n$GPGSV,3,3,9,32,10,037,00,,,,,,,,,,,,*74"; + NmeaDevice dev = new BufferedStringDevice(message); + TaskCompletionSource tcs = new TaskCompletionSource(); + bool messageRecieved = false; + dev.MessageReceived += (s, e) => + { + messageRecieved = true; + }; + await dev.OpenAsync(); + await Task.Delay(100); + var _ = dev.CloseAsync(); + if (messageRecieved) + Assert.Fail("Event shouldn't be raised for incomplete messages"); + } + } } diff --git a/src/UnitTests/NmeaParser.Tests/NmeaMessages.cs b/src/UnitTests/NmeaParser.Tests/NmeaMessages.cs index 5a46943..505e761 100644 --- a/src/UnitTests/NmeaParser.Tests/NmeaMessages.cs +++ b/src/UnitTests/NmeaParser.Tests/NmeaMessages.cs @@ -30,7 +30,13 @@ namespace NmeaParser.Tests public class NmeaMessages { [TestMethod] - public async Task ParseNmeaFile() + public +#if NETFX_CORE + async Task +#else + void +#endif + ParseNmeaFile() { #if NETFX_CORE var file = await Windows.Storage.StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///NmeaSampleData.txt")); @@ -38,12 +44,13 @@ namespace NmeaParser.Tests #else System.IO.StreamReader reader = new System.IO.StreamReader("NmeaSampleData.txt"); #endif + NmeaMessage previousMessage = null; while (!reader.EndOfStream) { var line = reader.ReadLine(); if (line.StartsWith("$")) { - var msg = NmeaMessage.Parse(line); + var msg = NmeaMessage.Parse(line, previousMessage as IMultiSentenceMessage); Assert.IsNotNull(msg); var idx = line.IndexOf('*'); if (idx >= 0) @@ -56,7 +63,13 @@ namespace NmeaParser.Tests } } [TestMethod] - public async Task ParseTrimbleR2NmeaFile() + public +#if NETFX_CORE + async Task +#else + void +#endif + ParseTrimbleR2NmeaFile() { #if NETFX_CORE var file = await Windows.Storage.StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///TrimbleR2SampleData.txt")); @@ -64,12 +77,13 @@ namespace NmeaParser.Tests #else System.IO.StreamReader reader = new System.IO.StreamReader("TrimbleR2SampleData.txt"); #endif + NmeaMessage previousMessage = null; while (!reader.EndOfStream) { var line = reader.ReadLine(); if (line.StartsWith("$")) { - var msg = NmeaMessage.Parse(line); + var msg = NmeaMessage.Parse(line, previousMessage as IMultiSentenceMessage); Assert.IsNotNull(msg); var idx = line.IndexOf('*'); if (idx >= 0) @@ -302,8 +316,8 @@ namespace NmeaParser.Tests var msg = NmeaMessage.Parse(input); Assert.IsInstanceOfType(msg, typeof(Gsv)); Gsv gsv = (Gsv)msg; - Assert.AreEqual(3, gsv.TotalMessages); - Assert.AreEqual(3, gsv.MessageNumber); + Assert.IsInstanceOfType(msg, typeof(IMultiSentenceMessage)); + Assert.IsFalse(((IMultiSentenceMessage)msg).IsComplete); Assert.AreEqual(11, gsv.SVsInView); Assert.IsNotNull(gsv.SVs); Assert.AreEqual(3, gsv.SVs.Count); @@ -336,13 +350,75 @@ namespace NmeaParser.Tests var msg = NmeaMessage.Parse(input); Assert.IsInstanceOfType(msg, typeof(Gsv)); Gsv gsv = (Gsv)msg; - Assert.AreEqual(1, gsv.TotalMessages); - Assert.AreEqual(1, gsv.MessageNumber); + Assert.IsTrue(((IMultiSentenceMessage)gsv).IsComplete); Assert.AreEqual(0, gsv.SVsInView); Assert.IsNotNull(gsv.SVs); Assert.AreEqual(0, gsv.SVs.Count); } + [TestMethod] + public void TestGpgsv_Multi() + { + var input1 = "$GPGSV,3,1,9,00,30,055,48,00,19,281,00,27,19,275,00,12,16,319,00*4C"; + var input2 = "$GPGSV,3,2,9,00,30,055,48,00,19,281,00,27,19,275,00,12,16,319,00*4F"; + var input3 = "$GPGSV,3,3,9,32,10,037,00,,,,,,,,,,,,*74"; + var msg1 = NmeaMessage.Parse(input1); + Assert.IsFalse(((IMultiSentenceMessage)msg1).IsComplete); + var msg2 = NmeaMessage.Parse(input2, msg1 as IMultiSentenceMessage); + Assert.IsFalse(((IMultiSentenceMessage)msg2).IsComplete); + var msg3 = NmeaMessage.Parse(input3, msg2 as IMultiSentenceMessage); + Assert.IsTrue(((IMultiSentenceMessage)msg3).IsComplete); + Assert.IsInstanceOfType(msg1, typeof(Gsv)); + Assert.AreSame(msg1, msg2); + Assert.AreSame(msg1, msg3); + Gsv gsv = (Gsv)msg1; + Assert.AreEqual(9, gsv.SVsInView); + Assert.IsNotNull(gsv.SVs); + Assert.AreEqual(9, gsv.SVs.Count); + } + + [TestMethod] + public void TestGpgsv_MultiMissing() + { + var input1 = "$GPGSV,2,1,9,00,30,055,48,00,19,281,00,27,19,275,00,12,16,319,00*4D"; + var input2 = "$GPGSV,2,2,8,00,30,055,48,00,19,281,00,27,19,275,00,12,16,319,00*4F"; //Satellite count doesn't match, so append will fail + var msg1 = NmeaMessage.Parse(input1); + Assert.IsFalse(((IMultiSentenceMessage)msg1).IsComplete); + var msg2 = NmeaMessage.Parse(input2, msg1 as IMultiSentenceMessage); + Assert.IsFalse(((IMultiSentenceMessage)msg2).IsComplete); + Assert.IsInstanceOfType(msg2, typeof(Gsv)); + Assert.AreNotSame(msg1, msg2); + Gsv gsv1 = (Gsv)msg1; + Assert.AreEqual(9, gsv1.SVsInView); + Assert.IsNotNull(gsv1.SVs); + Assert.AreEqual(4, gsv1.SVs.Count); + Gsv gsv2 = (Gsv)msg2; + Assert.AreEqual(8, gsv2.SVsInView); + Assert.IsNotNull(gsv2.SVs); + Assert.AreEqual(4, gsv2.SVs.Count); + } + + + [TestMethod] + public void TestGpgsv_MultiNotMatching() + { + var input2 = "$GPGSV,3,2,9,00,30,055,48,00,19,281,00,27,19,275,00,12,16,319,00*4F"; + var input3 = "$GPGSV,3,3,9,32,10,037,00,,,,,,,,,,,,*74"; + var msg2 = NmeaMessage.Parse(input2); + Assert.IsFalse(((IMultiSentenceMessage)msg2).IsComplete); + var msg3 = NmeaMessage.Parse(input3, msg2 as IMultiSentenceMessage); + Assert.IsFalse(((IMultiSentenceMessage)msg3).IsComplete); + Assert.IsInstanceOfType(msg2, typeof(Gsv)); + Assert.AreNotSame(msg2, msg3); + Gsv gsv2 = (Gsv)msg2; + Assert.AreEqual(9, gsv2.SVsInView); + Assert.IsNotNull(gsv2.SVs); + Assert.AreEqual(4, gsv2.SVs.Count); + Gsv gsv3 = (Gsv)msg3; + Assert.AreEqual(9, gsv3.SVsInView); + Assert.IsNotNull(gsv3.SVs); + Assert.AreEqual(1, gsv3.SVs.Count); + } [TestMethod] [WorkItem(53)] @@ -352,8 +428,7 @@ namespace NmeaParser.Tests var msg = NmeaMessage.Parse(msgstr); Assert.IsInstanceOfType(msg, typeof(Gsv)); Gsv gsv = (Gsv)msg; - Assert.AreEqual(3, gsv.TotalMessages); - Assert.AreEqual(1, gsv.MessageNumber); + Assert.IsFalse(((IMultiSentenceMessage)gsv).IsComplete); Assert.AreEqual(12, gsv.SVsInView); Assert.IsNotNull(gsv.SVs); Assert.AreEqual(4, gsv.SVs.Count); @@ -497,118 +572,117 @@ namespace NmeaParser.Tests } [TestMethod] - public void TestGpgll_NoFixTime_OrActiveIndicator() - { - string input = "$GPGLL,3751.65,S,14507.36,E*77"; - var msg = NmeaMessage.Parse(input); - Assert.IsInstanceOfType(msg, typeof(Gll)); - Gll gll = (Gll)msg; - Assert.IsTrue(gll.DataActive); - Assert.AreEqual(-37.860833333333333333, gll.Latitude); - Assert.AreEqual(145.1226666666666666667, gll.Longitude); - Assert.AreEqual(TimeSpan.Zero, gll.FixTime); - } + public void TestGpgll_NoFixTime_OrActiveIndicator() + { + string input = "$GPGLL,3751.65,S,14507.36,E*77"; + var msg = NmeaMessage.Parse(input); + Assert.IsInstanceOfType(msg, typeof(Gll)); + Gll gll = (Gll)msg; + Assert.IsTrue(gll.DataActive); + Assert.AreEqual(-37.860833333333333333, gll.Latitude); + Assert.AreEqual(145.1226666666666666667, gll.Longitude); + Assert.AreEqual(TimeSpan.Zero, gll.FixTime); + } - [TestMethod] - public void TestGpbod_Empty() - { - string input = "$GPBOD,,T,,M,,*47"; - var msg = NmeaMessage.Parse(input); - Assert.IsInstanceOfType(msg, typeof(Bod)); - Bod bod = (Bod)msg; - Assert.AreEqual(double.NaN, bod.TrueBearing, "TrueBearing"); - Assert.AreEqual(double.NaN, bod.MagneticBearing, "MagneticBearing"); - Assert.IsNull(bod.OriginId, "OriginID"); - Assert.IsNull(bod.DestinationId, "DestinationID"); - } + [TestMethod] + public void TestGpbod_Empty() + { + string input = "$GPBOD,,T,,M,,*47"; + var msg = NmeaMessage.Parse(input); + Assert.IsInstanceOfType(msg, typeof(Bod)); + Bod bod = (Bod)msg; + Assert.AreEqual(double.NaN, bod.TrueBearing, "TrueBearing"); + Assert.AreEqual(double.NaN, bod.MagneticBearing, "MagneticBearing"); + Assert.IsNull(bod.OriginId, "OriginID"); + Assert.IsNull(bod.DestinationId, "DestinationID"); + } - [TestMethod] - public void TestGpbod_GoToMode() - { - string input = "$GPBOD,099.3,T,105.6,M,POINTB,*48"; - var msg = NmeaMessage.Parse(input); - Assert.IsInstanceOfType(msg, typeof(Bod)); - Bod bod = (Bod)msg; - Assert.AreEqual(99.3, bod.TrueBearing, "TrueBearing"); - Assert.AreEqual(105.6, bod.MagneticBearing, "MagneticBearing"); - Assert.AreEqual("POINTB", bod.DestinationId, "DestinationID"); - Assert.IsNull(bod.OriginId, "OriginID"); - } + [TestMethod] + public void TestGpbod_GoToMode() + { + string input = "$GPBOD,099.3,T,105.6,M,POINTB,*48"; + var msg = NmeaMessage.Parse(input); + Assert.IsInstanceOfType(msg, typeof(Bod)); + Bod bod = (Bod)msg; + Assert.AreEqual(99.3, bod.TrueBearing, "TrueBearing"); + Assert.AreEqual(105.6, bod.MagneticBearing, "MagneticBearing"); + Assert.AreEqual("POINTB", bod.DestinationId, "DestinationID"); + Assert.IsNull(bod.OriginId, "OriginID"); + } - [TestMethod] - public void TestGpbod() - { - string input = "$GPBOD,097.0,T,103.2,M,POINTB,POINTA*4A"; - var msg = NmeaMessage.Parse(input); - Assert.IsInstanceOfType(msg, typeof(Bod)); - Bod bod = (Bod)msg; - Assert.AreEqual(97d, bod.TrueBearing, "TrueBearing"); - Assert.AreEqual(103.2, bod.MagneticBearing, "MagneticBearing"); - Assert.AreEqual("POINTB", bod.DestinationId, "DestinationID"); - Assert.AreEqual("POINTA", bod.OriginId, "OriginID"); - } + [TestMethod] + public void TestGpbod() + { + string input = "$GPBOD,097.0,T,103.2,M,POINTB,POINTA*4A"; + var msg = NmeaMessage.Parse(input); + Assert.IsInstanceOfType(msg, typeof(Bod)); + Bod bod = (Bod)msg; + Assert.AreEqual(97d, bod.TrueBearing, "TrueBearing"); + Assert.AreEqual(103.2, bod.MagneticBearing, "MagneticBearing"); + Assert.AreEqual("POINTB", bod.DestinationId, "DestinationID"); + Assert.AreEqual("POINTA", bod.OriginId, "OriginID"); + } - [TestMethod] - public void TestPgrmz_Empty() - { - string input = "$PGRMZ,,,*7E"; - var msg = NmeaMessage.Parse(input); - Assert.IsInstanceOfType(msg, typeof(NmeaParser.Nmea.Gps.Garmin.Pgrmz)); - var rmz = (NmeaParser.Nmea.Gps.Garmin.Pgrmz)msg; - Assert.AreEqual(double.NaN, rmz.Altitude, "Altitude"); - Assert.AreEqual(NmeaParser.Nmea.Gps.Garmin.Pgrmz.AltitudeUnit.Unknown, rmz.Unit, "Unit"); - Assert.AreEqual(NmeaParser.Nmea.Gps.Garmin.Pgrmz.PositionFixType.Unknown, rmz.FixType, "FixDimension"); - } + [TestMethod] + public void TestPgrmz_Empty() + { + string input = "$PGRMZ,,,*7E"; + var msg = NmeaMessage.Parse(input); + Assert.IsInstanceOfType(msg, typeof(NmeaParser.Nmea.Gps.Garmin.Pgrmz)); + var rmz = (NmeaParser.Nmea.Gps.Garmin.Pgrmz)msg; + Assert.AreEqual(double.NaN, rmz.Altitude, "Altitude"); + Assert.AreEqual(NmeaParser.Nmea.Gps.Garmin.Pgrmz.AltitudeUnit.Unknown, rmz.Unit, "Unit"); + Assert.AreEqual(NmeaParser.Nmea.Gps.Garmin.Pgrmz.PositionFixType.Unknown, rmz.FixType, "FixDimension"); + } - [TestMethod] - public void TestPgrmz() - { - string input = "$PGRMZ,93,f,3*21"; - var msg = NmeaMessage.Parse(input); - Assert.IsInstanceOfType(msg, typeof(NmeaParser.Nmea.Gps.Garmin.Pgrmz)); - var rmz = (NmeaParser.Nmea.Gps.Garmin.Pgrmz)msg; - Assert.AreEqual(93d, rmz.Altitude, "Altitude"); - Assert.AreEqual(NmeaParser.Nmea.Gps.Garmin.Pgrmz.AltitudeUnit.Feet, rmz.Unit, "Unit"); - Assert.AreEqual(NmeaParser.Nmea.Gps.Garmin.Pgrmz.PositionFixType.Fix3D, rmz.FixType, "FixDimension"); - } + [TestMethod] + public void TestPgrmz() + { + string input = "$PGRMZ,93,f,3*21"; + var msg = NmeaMessage.Parse(input); + Assert.IsInstanceOfType(msg, typeof(NmeaParser.Nmea.Gps.Garmin.Pgrmz)); + var rmz = (NmeaParser.Nmea.Gps.Garmin.Pgrmz)msg; + Assert.AreEqual(93d, rmz.Altitude, "Altitude"); + Assert.AreEqual(NmeaParser.Nmea.Gps.Garmin.Pgrmz.AltitudeUnit.Feet, rmz.Unit, "Unit"); + Assert.AreEqual(NmeaParser.Nmea.Gps.Garmin.Pgrmz.PositionFixType.Fix3D, rmz.FixType, "FixDimension"); + } - [TestMethod] - public void TestGprte() - { - string input = "$GPRTE,2,1,c,0,W3IWI,DRIVWY,32CEDR,32-29,32BKLD,32-I95,32-US1,BW-32,BW-198*69"; - var msg = NmeaMessage.Parse(input); - Assert.IsInstanceOfType(msg, typeof(Rte)); - Rte gsv = (Rte)msg; - Assert.AreEqual(2, gsv.TotalMessages); - Assert.AreEqual(1, gsv.MessageNumber); - Assert.AreEqual(Rte.WaypointListType.CompleteWaypointsList, gsv.ListType); - Assert.AreEqual("0", gsv.RouteId); - Assert.AreEqual("0", gsv.RouteId); - Assert.AreEqual(9, gsv.Waypoints.Count); - Assert.AreEqual("W3IWI", gsv.Waypoints[0]); - Assert.AreEqual("32BKLD", gsv.Waypoints[4]); - Assert.AreEqual("BW-198", gsv.Waypoints[8]); - } + [TestMethod] + public void TestGprte() + { + string input = "$GPRTE,2,1,c,0,W3IWI,DRIVWY,32CEDR,32-29,32BKLD,32-I95,32-US1,BW-32,BW-198*69"; + var msg = NmeaMessage.Parse(input); + Assert.IsInstanceOfType(msg, typeof(Rte)); + Rte rte = (Rte)msg; + Assert.IsFalse(((IMultiSentenceMessage)rte).IsComplete); + Assert.AreEqual(Rte.WaypointListType.CompleteWaypointsList, rte.ListType); + Assert.AreEqual("0", rte.RouteId); + Assert.AreEqual("0", rte.RouteId); + Assert.AreEqual(9, rte.Waypoints.Count); + Assert.AreEqual("W3IWI", rte.Waypoints[0]); + Assert.AreEqual("32BKLD", rte.Waypoints[4]); + Assert.AreEqual("BW-198", rte.Waypoints[8]); + } - [TestMethod] - public void TestGpgst() - { - string input = "$GPGST,172814.0,0.006,0.023,0.020,273.6,0.023,0.020,0.031*6A"; - var msg = NmeaMessage.Parse(input); - Assert.IsInstanceOfType(msg, typeof(Gst)); - Gst gst = (Gst)msg; - Assert.AreEqual(new TimeSpan(17, 28, 14), gst.FixTime); - Assert.AreEqual(0.006, gst.Rms); - Assert.AreEqual(0.023, gst.SemiMajorError); - Assert.AreEqual(0.02, gst.SemiMinorError); - Assert.AreEqual(273.6, gst.ErrorOrientation); - Assert.AreEqual(0.023, gst.SigmaLatitudeError); - Assert.AreEqual(0.020, gst.SigmaLongitudeError); - Assert.AreEqual(0.031, gst.SigmaHeightError); - } + [TestMethod] + public void TestGpgst() + { + string input = "$GPGST,172814.0,0.006,0.023,0.020,273.6,0.023,0.020,0.031*6A"; + var msg = NmeaMessage.Parse(input); + Assert.IsInstanceOfType(msg, typeof(Gst)); + Gst gst = (Gst)msg; + Assert.AreEqual(new TimeSpan(17, 28, 14), gst.FixTime); + Assert.AreEqual(0.006, gst.Rms); + Assert.AreEqual(0.023, gst.SemiMajorError); + Assert.AreEqual(0.02, gst.SemiMinorError); + Assert.AreEqual(273.6, gst.ErrorOrientation); + Assert.AreEqual(0.023, gst.SigmaLatitudeError); + Assert.AreEqual(0.020, gst.SigmaLongitudeError); + Assert.AreEqual(0.031, gst.SigmaHeightError); + } [TestMethod] public void TestGngst()