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