diff --git a/src/NmeaParser/Gnss/GnssMonitor.cs b/src/NmeaParser/Gnss/GnssMonitor.cs
new file mode 100644
index 0000000..9695c1b
--- /dev/null
+++ b/src/NmeaParser/Gnss/GnssMonitor.cs
@@ -0,0 +1,305 @@
+// *******************************************************************************
+// * 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.Linq;
+using NmeaParser.Messages;
+
+namespace NmeaParser.Gnss
+{
+ ///
+ /// Helper class for monitoring GNSS messages and combine them into a single useful location info
+ ///
+ public class GnssMonitor
+ {
+ private bool m_supportGNMessages; // If device detect GN* messages, ignore all other Talker ID
+ private bool m_supportGGaMessages; //If device support GGA, ignore RMC for location
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The NMEA device to monitor for GNSS messages
+ public GnssMonitor(NmeaDevice device)
+ {
+ if (device == null)
+ throw new ArgumentNullException(nameof(device));
+ Device = device;
+ Device.MessageReceived += NmeaMessageReceived;
+ }
+
+ ///
+ /// Gets the NMEA device that is being monitored
+ ///
+ public NmeaDevice Device { get; }
+
+ private void NmeaMessageReceived(object sender, NmeaParser.NmeaMessageReceivedEventArgs e)
+ {
+ OnMessageReceived(e.Message);
+ }
+
+ ///
+ /// Called when a message is received.
+ ///
+ /// The NMEA message that was received
+ protected virtual void OnMessageReceived(NmeaMessage message)
+ {
+ bool isNewFix = false;
+ bool lostFix = false;
+ double lat = 0;
+ double lon = 0;
+ AllMessages[message.MessageType] = message;
+
+ if (message.TalkerId == NmeaParser.Talker.GlobalNavigationSatelliteSystem)
+ m_supportGNMessages = true; // Support for GN* messages detected
+ else if (m_supportGNMessages && message.TalkerId != NmeaParser.Talker.GlobalNavigationSatelliteSystem)
+ return; // If device supports combined GN* messages, ignore non-GN messages
+ if (message is ITimestampedMessage ts)
+ FixTime = ts.Timestamp;
+
+ if (message is NmeaParser.Messages.Garmin.Pgrme rme)
+ {
+ HorizontalError = rme.HorizontalError;
+ VerticalError = rme.VerticalError;
+ }
+ else if (message is Gst gst)
+ {
+ Gst = gst;
+ VerticalError = gst.SigmaHeightError;
+ HorizontalError = Math.Round(Math.Sqrt(Gst.SigmaLatitudeError * Gst.SigmaLatitudeError + Gst.SigmaLongitudeError * Gst.SigmaLongitudeError), 3);
+ }
+ else if (message is Rmc rmc)
+ {
+ Rmc = rmc;
+ if (!m_supportGGaMessages)
+ {
+ if (Rmc.Active)
+ {
+ lat = Rmc.Latitude;
+ lon = Rmc.Longitude;
+ isNewFix = true;
+ }
+ else
+ {
+ lostFix = true;
+ }
+ }
+ }
+ else if (message is Dtm dtm)
+ {
+ if (Dtm?.Checksum != dtm.Checksum)
+ {
+ // Datum change
+ Dtm = dtm;
+ Latitude = double.NaN;
+ Longitude = double.NaN;
+ IsFixValid = false;
+ }
+ }
+ else if (message is Gga gga)
+ {
+ Gga = gga;
+ m_supportGGaMessages = true;
+ if (gga.Quality != Gga.FixQuality.Invalid)
+ {
+ lat = gga.Latitude;
+ lon = gga.Longitude;
+ GeoidHeight = gga.GeoidalSeparation;
+ Altitude = gga.Altitude + gga.GeoidalSeparation; //Convert to ellipsoidal height
+ }
+ if (gga.Quality == Gga.FixQuality.Invalid || gga.Quality == Gga.FixQuality.Estimated)
+ {
+ lostFix = true;
+ }
+ isNewFix = true;
+ }
+ else if (message is Gsa gsa)
+ {
+ Gsa = gsa;
+ }
+ else if (message is Vtg vtg)
+ {
+ Vtg = vtg;
+ }
+ if (lostFix)
+ {
+ if (!IsFixValid)
+ {
+ IsFixValid = false;
+ LocationLost?.Invoke(this, EventArgs.Empty);
+ }
+ }
+ if (isNewFix)
+ {
+ Latitude = lat;
+ Longitude = lon;
+ IsFixValid = true;
+ LocationChanged?.Invoke(this, EventArgs.Empty);
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether the current fix is valid.
+ ///
+ ///
+ /// If false the provided values like and are no longer current and reflect the last known location.
+ ///
+ ///
+ public bool IsFixValid { get; private set; }
+
+ ///
+ /// Gets the latitude for the current or last known location.
+ ///
+ ///
+ ///
+ public double Latitude { get; private set; } = double.NaN;
+
+ ///
+ /// Gets the longitude for the current or last known location.
+ ///
+ ///
+ ///
+ public double Longitude { get; private set; } = double.NaN;
+
+ ///
+ /// Gets the geight above the ellipsoid
+ ///
+ public double Altitude { get; private set; } = double.NaN;
+ ///
+ /// Gets the Geoid Height. Add this value to to get the Geoid heights which is roughly MSL heights.
+ ///
+ public double GeoidHeight { get; private set; } = double.NaN;
+
+ ///
+ /// Gets the speed in knots
+ ///
+ public double Speed => Rmc?.Speed ?? Vtg?.SpeedKnots ?? double.NaN;
+
+ ///
+ /// Gets the current cource
+ ///
+ public double Course => Rmc?.Course ?? double.NaN;
+
+ ///
+ /// Gets an estimate of the horizontal error in meters
+ ///
+ public double HorizontalError { get; private set; } = double.NaN;
+
+ ///
+ /// Gets an estimate of the vertical error in meters
+ ///
+ public double VerticalError { get; private set; } = double.NaN;
+
+ ///
+ /// Gets the horizontal dilution of precision
+ ///
+ public double Hdop => Gsa?.Hdop ?? Gga?.Hdop ?? double.NaN;
+
+ ///
+ /// Gets the 3D point dilution of precision
+ ///
+ public double Pdop => Gsa?.Pdop ?? double.NaN;
+
+ ///
+ /// Gets the vertical dilution of precision
+ ///
+ public double Vdop => Gsa?.Vdop ?? double.NaN;
+
+ ///
+ /// Gets the latest known GSA message.
+ ///
+ public Gsa? Gsa { get; private set; }
+
+ ///
+ /// Gets the latest known GGA message.
+ ///
+ public Gga? Gga { get; private set; }
+
+ ///
+ /// Gets the latest known RMC message.
+ ///
+ public Rmc? Rmc { get; private set; }
+
+ ///
+ /// Gets the latest known GST message.
+ ///
+ public Gst? Gst { get; private set; }
+
+ ///
+ /// Gets the latest known DTM message.
+ ///
+ public Dtm? Dtm { get; private set; }
+
+ ///
+ /// Gets the latest known VTG message.
+ ///
+ public Vtg? Vtg { get; private set; }
+
+ ///
+ /// Gets the current fix time
+ ///
+ public TimeSpan FixTime { get; private set; }
+
+ ///
+ /// Gets a list of satellite vehicles in the sky
+ ///
+ public IEnumerable Satellites => AllMessages.Values.OfType().SelectMany(s => s.SVs);
+
+ ///
+ /// Gets the number of satellites in the sky
+ ///
+ public int SatellitesInView => AllMessages.Values.OfType().Sum(s => s.SatellitesInView);
+
+ ///
+ /// Gets the quality of the current fix
+ ///
+ public Gga.FixQuality FixQuality => !IsFixValid ? Gga.FixQuality.Invalid : (Gga?.Quality ?? Gga.FixQuality.GpsFix);
+
+ ///
+ /// Gets a list of all NMEA messages currently part of this location
+ ///
+ public Dictionary AllMessages { get; } = new Dictionary();
+
+ ///
+ /// Gets a value indicating the current Datum being used.
+ ///
+ public string Datum
+ {
+ get
+ {
+ if (Dtm == null)
+ return "WGS84";
+ switch (Dtm.ReferenceDatumCode)
+ {
+ case "W84": return "WGS84";
+ case "W72": return "WGS72";
+ case "S85": return "SGS85";
+ case "P90": return "PE90";
+ default: return Dtm.ReferenceDatumCode;
+ }
+ }
+ }
+
+ ///
+ /// Raised when a new location has been updated
+ ///
+ public event EventHandler? LocationChanged;
+
+ ///
+ /// Raised if location tracking was lost
+ ///
+ ///
+ public event EventHandler? LocationLost;
+ }
+}
diff --git a/src/SampleApp.WinDesktop/Ntrip/Carrier.cs b/src/NmeaParser/Gnss/Ntrip/Carrier.cs
similarity index 72%
rename from src/SampleApp.WinDesktop/Ntrip/Carrier.cs
rename to src/NmeaParser/Gnss/Ntrip/Carrier.cs
index aed0979..4e4990e 100644
--- a/src/SampleApp.WinDesktop/Ntrip/Carrier.cs
+++ b/src/NmeaParser/Gnss/Ntrip/Carrier.cs
@@ -14,10 +14,24 @@
namespace NmeaParser.Gnss.Ntrip
{
+ ///
+ /// Enumeration for the carrier used by the
+ ///
public enum Carrier : int
{
- No = 0,
+ ///
+ /// None / unknown
+ ///
+ None = 0,
+
+ ///
+ /// L1 wave
+ ///
L1 = 1,
+
+ ///
+ /// L1 and L2 waves
+ ///
L1L2 = 2
}
}
diff --git a/src/SampleApp.WinDesktop/Ntrip/Caster.cs b/src/NmeaParser/Gnss/Ntrip/Caster.cs
similarity index 61%
rename from src/SampleApp.WinDesktop/Ntrip/Caster.cs
rename to src/NmeaParser/Gnss/Ntrip/Caster.cs
index 639f1f5..dd213ad 100644
--- a/src/SampleApp.WinDesktop/Ntrip/Caster.cs
+++ b/src/NmeaParser/Gnss/Ntrip/Caster.cs
@@ -17,6 +17,9 @@ using System.Net;
namespace NmeaParser.Gnss.Ntrip
{
+ ///
+ /// Gets metadata about the NTRIP Caster
+ ///
public class Caster : NtripSource
{
internal Caster (string[] d)
@@ -33,6 +36,18 @@ namespace NmeaParser.Gnss.Ntrip
FallbackAddress = IPAddress.Parse(d[9]);
}
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
public Caster(IPAddress address, int port, string identifier, string _operator, bool supportsNmea, string countryCode, double latitude, double longitude, IPAddress fallbackkAddress)
{
Address = address;
@@ -46,14 +61,49 @@ namespace NmeaParser.Gnss.Ntrip
FallbackAddress = fallbackkAddress;
}
+ ///
+ /// Gets the caster IP Address
+ ///
public IPAddress Address { get; }
+
+ ///
+ /// Gets the caster port
+ ///
public int Port { get; }
+
+ ///
+ /// Gets the caster identifier
+ ///
public string Identifier { get; }
+
+ ///
+ /// Gets the caster operator
+ ///
public string Operator { get; }
+
+ ///
+ /// Gets a value indicating whether it supports NMEA
+ ///
public bool SupportsNmea { get; }
+
+ ///
+ /// Gets the country code for the caster origin
+ ///
public string CountryCode { get; }
+
+ ///
+ /// Gets the latitude for the caster
+ ///
public double Latitude { get; }
+
+ ///
+ /// Gets the longitude for the caster
+ ///
public double Longitude { get; }
+
+ ///
+ /// Gets the fallback address for the caster
+ ///
public IPAddress FallbackAddress { get; }
}
}
diff --git a/src/SampleApp.WinDesktop/Ntrip/Client.cs b/src/NmeaParser/Gnss/Ntrip/Client.cs
similarity index 64%
rename from src/SampleApp.WinDesktop/Ntrip/Client.cs
rename to src/NmeaParser/Gnss/Ntrip/Client.cs
index 830c207..a519ab5 100644
--- a/src/SampleApp.WinDesktop/Ntrip/Client.cs
+++ b/src/NmeaParser/Gnss/Ntrip/Client.cs
@@ -11,7 +11,6 @@
// * See the License for the specific language governing permissions and
// * limitations under the License.
// ******************************************************************************
-#nullable enable
using System;
using System.Collections.Generic;
@@ -21,6 +20,9 @@ using System.Threading.Tasks;
namespace NmeaParser.Gnss.Ntrip
{
+ ///
+ /// NTRIP Client for querying an NTRIP server and opening an NTRIP stream
+ ///
public class Client : IDisposable
{
private readonly string _host;
@@ -30,17 +32,33 @@ namespace NmeaParser.Gnss.Ntrip
private bool connected;
private Task? runningTask;
+ ///
+ /// Initializes a new instance of the class
+ ///
+ /// Host name
+ /// Port, usually 2101
public Client(string host, int port)
{
_host = host;
_port = port;
}
+ ///
+ /// Initializes a new instance of the class
+ ///
+ /// Host name
+ /// Port, usually 2101
+ /// Username
+ /// Password
public Client(string host, int port, string username, string password) : this(host, port)
{
_auth = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(username + ":" + password));
}
+ ///
+ /// Gets a list of sources from the NTRIP endpoint
+ ///
+ ///
public IEnumerable GetSourceTable()
{
string data = "";
@@ -77,8 +95,8 @@ namespace NmeaParser.Gnss.Ntrip
{
var sckt = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
sckt.Blocking = true;
+ sckt.ReceiveTimeout = 5000;
sckt.Connect(_host, _port);
-
string msg = $"GET /{path} HTTP/1.1\r\n";
msg += "User-Agent: NTRIP ntripclient\r\n";
if (_auth != null)
@@ -92,11 +110,29 @@ namespace NmeaParser.Gnss.Ntrip
sckt.Send(data);
return sckt;
}
-
- public void Connect(string strName)
+ ///
+ /// Connects to the endpoint for the specified
+ ///
+ ///
+ public void Connect(NtripStream stream)
{
+ if (stream == null)
+ throw new ArgumentNullException(nameof(stream));
+ Connect(stream.Mountpoint);
+ }
+
+ ///
+ /// Connects to the endpoint for the specified
+ ///
+ ///
+ public void Connect(string mountPoint)
+ {
+ if (mountPoint == null)
+ throw new ArgumentNullException(nameof(mountPoint));
+ if (string.IsNullOrWhiteSpace(mountPoint))
+ throw new ArgumentException(nameof(mountPoint));
if (sckt != null) throw new Exception("Connection already open");
- sckt = Request(strName);
+ sckt = Request(mountPoint);
connected = true;
runningTask = Task.Run(ReceiveThread);
}
@@ -104,10 +140,10 @@ namespace NmeaParser.Gnss.Ntrip
private async Task ReceiveThread()
{
byte[] buffer = new byte[65536];
- sckt.ReceiveTimeout = 1000;
+
while (connected && sckt != null)
{
- int count = sckt.Receive(buffer, SocketFlags.None, out SocketError errorCode);
+ int count = sckt.Receive(buffer);
if (count > 0)
{
DataReceived?.Invoke(this, buffer.Take(count).ToArray());
@@ -128,6 +164,10 @@ namespace NmeaParser.Gnss.Ntrip
sckt = null;
}
+ ///
+ /// Shuts down the stream
+ ///
+ ///
public Task CloseAsync()
{
if (runningTask != null)
@@ -137,15 +177,30 @@ namespace NmeaParser.Gnss.Ntrip
runningTask = null;
return t;
}
+#if NETSTANDARD || NETFX
+ return Task.FromResult