mirror of
https://github.com/dotMorten/NmeaParser.git
synced 2026-02-03 22:34:20 +01:00
Moved NTRIP classes to NmeaParser lib and introduced new GnssMonitor class for simplified monitoring of GNSS messages
This commit is contained in:
parent
f3f80534f9
commit
73dbdf508f
305
src/NmeaParser/Gnss/GnssMonitor.cs
Normal file
305
src/NmeaParser/Gnss/GnssMonitor.cs
Normal file
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper class for monitoring GNSS messages and combine them into a single useful location info
|
||||
/// </summary>
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GnssMonitor"/> class.
|
||||
/// </summary>
|
||||
/// <param name="device">The NMEA device to monitor for GNSS messages</param>
|
||||
public GnssMonitor(NmeaDevice device)
|
||||
{
|
||||
if (device == null)
|
||||
throw new ArgumentNullException(nameof(device));
|
||||
Device = device;
|
||||
Device.MessageReceived += NmeaMessageReceived;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the NMEA device that is being monitored
|
||||
/// </summary>
|
||||
public NmeaDevice Device { get; }
|
||||
|
||||
private void NmeaMessageReceived(object sender, NmeaParser.NmeaMessageReceivedEventArgs e)
|
||||
{
|
||||
OnMessageReceived(e.Message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when a message is received.
|
||||
/// </summary>
|
||||
/// <param name="message">The NMEA message that was received</param>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the current fix is valid.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If <c>false</c> the provided values like <see cref="Latitude"/> and <see cref="Longitude"/> are no longer current and reflect the last known location.
|
||||
/// </remarks>
|
||||
/// <seealso cref="LocationLost"/>
|
||||
public bool IsFixValid { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the latitude for the current or last known location.
|
||||
/// </summary>
|
||||
/// <seealso cref="IsFixValid"/>
|
||||
/// <seealso cref="Longitude"/>
|
||||
public double Latitude { get; private set; } = double.NaN;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the longitude for the current or last known location.
|
||||
/// </summary>
|
||||
/// <seealso cref="IsFixValid"/>
|
||||
/// <seealso cref="Latitude"/>
|
||||
public double Longitude { get; private set; } = double.NaN;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the geight above the ellipsoid
|
||||
/// </summary>
|
||||
public double Altitude { get; private set; } = double.NaN;
|
||||
/// <summary>
|
||||
/// Gets the Geoid Height. Add this value to <see cref="Altitude"/> to get the Geoid heights which is roughly MSL heights.
|
||||
/// </summary>
|
||||
public double GeoidHeight { get; private set; } = double.NaN;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the speed in knots
|
||||
/// </summary>
|
||||
public double Speed => Rmc?.Speed ?? Vtg?.SpeedKnots ?? double.NaN;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current cource
|
||||
/// </summary>
|
||||
public double Course => Rmc?.Course ?? double.NaN;
|
||||
|
||||
/// <summary>
|
||||
/// Gets an estimate of the horizontal error in meters
|
||||
/// </summary>
|
||||
public double HorizontalError { get; private set; } = double.NaN;
|
||||
|
||||
/// <summary>
|
||||
/// Gets an estimate of the vertical error in meters
|
||||
/// </summary>
|
||||
public double VerticalError { get; private set; } = double.NaN;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the horizontal dilution of precision
|
||||
/// </summary>
|
||||
public double Hdop => Gsa?.Hdop ?? Gga?.Hdop ?? double.NaN;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the 3D point dilution of precision
|
||||
/// </summary>
|
||||
public double Pdop => Gsa?.Pdop ?? double.NaN;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the vertical dilution of precision
|
||||
/// </summary>
|
||||
public double Vdop => Gsa?.Vdop ?? double.NaN;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the latest known GSA message.
|
||||
/// </summary>
|
||||
public Gsa? Gsa { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the latest known GGA message.
|
||||
/// </summary>
|
||||
public Gga? Gga { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the latest known RMC message.
|
||||
/// </summary>
|
||||
public Rmc? Rmc { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the latest known GST message.
|
||||
/// </summary>
|
||||
public Gst? Gst { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the latest known DTM message.
|
||||
/// </summary>
|
||||
public Dtm? Dtm { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the latest known VTG message.
|
||||
/// </summary>
|
||||
public Vtg? Vtg { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current fix time
|
||||
/// </summary>
|
||||
public TimeSpan FixTime { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of satellite vehicles in the sky
|
||||
/// </summary>
|
||||
public IEnumerable<SatelliteVehicle> Satellites => AllMessages.Values.OfType<Gsv>().SelectMany(s => s.SVs);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of satellites in the sky
|
||||
/// </summary>
|
||||
public int SatellitesInView => AllMessages.Values.OfType<Gsv>().Sum(s => s.SatellitesInView);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the quality of the current fix
|
||||
/// </summary>
|
||||
public Gga.FixQuality FixQuality => !IsFixValid ? Gga.FixQuality.Invalid : (Gga?.Quality ?? Gga.FixQuality.GpsFix);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of all NMEA messages currently part of this location
|
||||
/// </summary>
|
||||
public Dictionary<string, NmeaMessage> AllMessages { get; } = new Dictionary<string, NmeaMessage>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating the current Datum being used.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised when a new location has been updated
|
||||
/// </summary>
|
||||
public event EventHandler? LocationChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Raised if location tracking was lost
|
||||
/// </summary>
|
||||
/// <seealso cref="IsFixValid"/>
|
||||
public event EventHandler? LocationLost;
|
||||
}
|
||||
}
|
||||
|
|
@ -14,10 +14,24 @@
|
|||
|
||||
namespace NmeaParser.Gnss.Ntrip
|
||||
{
|
||||
/// <summary>
|
||||
/// Enumeration for the carrier used by the <see cref="NtripStream"/>
|
||||
/// </summary>
|
||||
public enum Carrier : int
|
||||
{
|
||||
No = 0,
|
||||
/// <summary>
|
||||
/// None / unknown
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// L1 wave
|
||||
/// </summary>
|
||||
L1 = 1,
|
||||
|
||||
/// <summary>
|
||||
/// L1 and L2 waves
|
||||
/// </summary>
|
||||
L1L2 = 2
|
||||
}
|
||||
}
|
||||
|
|
@ -17,6 +17,9 @@ using System.Net;
|
|||
|
||||
namespace NmeaParser.Gnss.Ntrip
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets metadata about the NTRIP Caster
|
||||
/// </summary>
|
||||
public class Caster : NtripSource
|
||||
{
|
||||
internal Caster (string[] d)
|
||||
|
|
@ -33,6 +36,18 @@ namespace NmeaParser.Gnss.Ntrip
|
|||
FallbackAddress = IPAddress.Parse(d[9]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Caster"/> class.
|
||||
/// </summary>
|
||||
/// <param name="address"></param>
|
||||
/// <param name="port"></param>
|
||||
/// <param name="identifier"></param>
|
||||
/// <param name="_operator"></param>
|
||||
/// <param name="supportsNmea"></param>
|
||||
/// <param name="countryCode"></param>
|
||||
/// <param name="latitude"></param>
|
||||
/// <param name="longitude"></param>
|
||||
/// <param name="fallbackkAddress"></param>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the caster IP Address
|
||||
/// </summary>
|
||||
public IPAddress Address { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the caster port
|
||||
/// </summary>
|
||||
public int Port { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the caster identifier
|
||||
/// </summary>
|
||||
public string Identifier { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the caster operator
|
||||
/// </summary>
|
||||
public string Operator { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether it supports NMEA
|
||||
/// </summary>
|
||||
public bool SupportsNmea { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the country code for the caster origin
|
||||
/// </summary>
|
||||
public string CountryCode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the latitude for the caster
|
||||
/// </summary>
|
||||
public double Latitude { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the longitude for the caster
|
||||
/// </summary>
|
||||
public double Longitude { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the fallback address for the caster
|
||||
/// </summary>
|
||||
public IPAddress FallbackAddress { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// NTRIP Client for querying an NTRIP server and opening an NTRIP stream
|
||||
/// </summary>
|
||||
public class Client : IDisposable
|
||||
{
|
||||
private readonly string _host;
|
||||
|
|
@ -30,17 +32,33 @@ namespace NmeaParser.Gnss.Ntrip
|
|||
private bool connected;
|
||||
private Task? runningTask;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Client"/> class
|
||||
/// </summary>
|
||||
/// <param name="host">Host name</param>
|
||||
/// <param name="port">Port, usually 2101</param>
|
||||
public Client(string host, int port)
|
||||
{
|
||||
_host = host;
|
||||
_port = port;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Client"/> class
|
||||
/// </summary>
|
||||
/// <param name="host">Host name</param>
|
||||
/// <param name="port">Port, usually 2101</param>
|
||||
/// <param name="username">Username</param>
|
||||
/// <param name="password">Password</param>
|
||||
public Client(string host, int port, string username, string password) : this(host, port)
|
||||
{
|
||||
_auth = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(username + ":" + password));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of sources from the NTRIP endpoint
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<NtripSource> 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)
|
||||
/// <summary>
|
||||
/// Connects to the endpoint for the specified <see cref="NtripStream.Mountpoint"/>
|
||||
/// </summary>
|
||||
/// <param name="stream"></param>
|
||||
public void Connect(NtripStream stream)
|
||||
{
|
||||
if (stream == null)
|
||||
throw new ArgumentNullException(nameof(stream));
|
||||
Connect(stream.Mountpoint);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Connects to the endpoint for the specified <see cref="NtripStream.Mountpoint"/>
|
||||
/// </summary>
|
||||
/// <param name="mountPoint"></param>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shuts down the stream
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Task CloseAsync()
|
||||
{
|
||||
if (runningTask != null)
|
||||
|
|
@ -137,15 +177,30 @@ namespace NmeaParser.Gnss.Ntrip
|
|||
runningTask = null;
|
||||
return t;
|
||||
}
|
||||
#if NETSTANDARD || NETFX
|
||||
return Task.FromResult<object?>(null);
|
||||
#else
|
||||
return Task.CompletedTask;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
_ = CloseAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fired when bytes has been received from the stream
|
||||
/// </summary>
|
||||
public event EventHandler<byte[]>? DataReceived;
|
||||
public event EventHandler Disconnected;
|
||||
|
||||
/// <summary>
|
||||
/// Fired if the socket connection was dropped, and the connection was closed.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This event is useful for handling network glitches, and trying to retry connection by calling <see cref="Connect(string)"/> again a few times.
|
||||
/// </remarks>
|
||||
public event EventHandler? Disconnected;
|
||||
}
|
||||
}
|
||||
|
|
@ -14,8 +14,14 @@
|
|||
|
||||
namespace NmeaParser.Gnss.Ntrip
|
||||
{
|
||||
public class NtripSource
|
||||
/// <summary>
|
||||
/// Baseclass for the sources returned from an NTRIP Service
|
||||
/// </summary>
|
||||
public abstract class NtripSource
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NtripSource"/> class.
|
||||
/// </summary>
|
||||
protected NtripSource()
|
||||
{
|
||||
}
|
||||
|
|
@ -17,6 +17,9 @@ using System.Globalization;
|
|||
|
||||
namespace NmeaParser.Gnss.Ntrip
|
||||
{
|
||||
/// <summary>
|
||||
/// Metadata on an NTRIP Data Stream
|
||||
/// </summary>
|
||||
public class NtripStream : NtripSource
|
||||
{
|
||||
internal NtripStream(string[] d)
|
||||
|
|
@ -38,15 +41,54 @@ namespace NmeaParser.Gnss.Ntrip
|
|||
SupportsNmea = d[11] == "1";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The mountpoint used with <see cref="Client.Connect(string)"/>
|
||||
/// </summary>
|
||||
public string Mountpoint { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the unique identifier for the stream
|
||||
/// </summary>
|
||||
public string Identifier { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the stream format
|
||||
/// </summary>
|
||||
public string Format { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the details about the format
|
||||
/// </summary>
|
||||
public string FormatDetails { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the wave carrier for the stream
|
||||
/// </summary>
|
||||
public Carrier Carrier { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the network for the stream
|
||||
/// </summary>
|
||||
public string Network { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the country code for where the stream originates
|
||||
/// </summary>
|
||||
public string CountryCode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the latitude location of the base station
|
||||
/// </summary>
|
||||
public double Latitude { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the longitude location of the base station
|
||||
/// </summary>
|
||||
public double Longitude { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the stream supports NMEA
|
||||
/// </summary>
|
||||
public bool SupportsNmea { get; }
|
||||
}
|
||||
}
|
||||
28
src/SampleApp.WinDesktop/GnssMonitorView.xaml
Normal file
28
src/SampleApp.WinDesktop/GnssMonitorView.xaml
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
<UserControl x:Class="SampleApp.WinDesktop.GnssMonitorView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:SampleApp.WinDesktop"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="300" d:DesignWidth="300">
|
||||
<StackPanel DataContext="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=Message}">
|
||||
<Border Background="LightGray" Padding="10" Margin="-10,-10,-10,5" x:Name="HeaderPanel">
|
||||
<TextBlock Text="{Binding MessageType}" x:Name="typeName" FontSize="20" FontWeight="Bold" Foreground="White" />
|
||||
</Border>
|
||||
<ItemsControl x:Name="Values">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid >
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition MinWidth="100" Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="{Binding Key}" Margin="0,0,5,0" VerticalAlignment="Top" />
|
||||
<TextBlock Text="{Binding Value}" TextWrapping="Wrap" Grid.Column="1" VerticalAlignment="Top" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
76
src/SampleApp.WinDesktop/GnssMonitorView.xaml.cs
Normal file
76
src/SampleApp.WinDesktop/GnssMonitorView.xaml.cs
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using NmeaParser.Gnss;
|
||||
using NmeaParser.Messages;
|
||||
|
||||
namespace SampleApp.WinDesktop
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for GnssMonitorView.xaml
|
||||
/// </summary>
|
||||
public partial class GnssMonitorView : UserControl
|
||||
{
|
||||
public GnssMonitorView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public GnssMonitor Monitor
|
||||
{
|
||||
get { return (GnssMonitor)GetValue(MonitorProperty); }
|
||||
set { SetValue(MonitorProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty MonitorProperty =
|
||||
DependencyProperty.Register(nameof(Monitor), typeof(GnssMonitor), typeof(GnssMonitorView), new PropertyMetadata(null, (d,e) => ((GnssMonitorView)d).OnMonitorPropertyChanged(e)));
|
||||
|
||||
private void OnMonitorPropertyChanged(DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.OldValue is GnssMonitor oldMonitor)
|
||||
{
|
||||
oldMonitor.LocationChanged -= LocationChanged;
|
||||
oldMonitor.LocationLost -= LocationChanged;
|
||||
}
|
||||
if (e.NewValue is GnssMonitor newMonitor)
|
||||
{
|
||||
newMonitor.LocationChanged += LocationChanged;
|
||||
newMonitor.LocationLost += LocationChanged;
|
||||
}
|
||||
UpdateValues();
|
||||
}
|
||||
|
||||
private void LocationChanged(object sender, System.EventArgs e)
|
||||
{
|
||||
Dispatcher.Invoke(UpdateValues);
|
||||
}
|
||||
private void UpdateValues()
|
||||
{
|
||||
if (Monitor == null)
|
||||
Values.ItemsSource = null;
|
||||
else
|
||||
{
|
||||
var props = Monitor.GetType().GetProperties(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public);
|
||||
List<KeyValuePair<string, object>> values = new List<KeyValuePair<string, object>>();
|
||||
foreach (var prop in props.OrderBy(t => t.Name))
|
||||
{
|
||||
if (prop.Name == nameof(GnssMonitor.AllMessages)) continue;
|
||||
if (prop.PropertyType.IsSubclassOf(typeof(NmeaMessage)))
|
||||
continue;
|
||||
var value = prop.GetValue(Monitor);
|
||||
if (!(value is string) && value is System.Collections.IEnumerable arr)
|
||||
{
|
||||
var str = "[" + string.Join(",", arr.OfType<object>().ToArray()) + "]";
|
||||
if (str.Length == 2)
|
||||
str = "[ ]";
|
||||
value = str;
|
||||
}
|
||||
values.Add(new KeyValuePair<string, object>(prop.Name, value));
|
||||
}
|
||||
Values.ItemsSource = values;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -37,7 +37,10 @@ namespace SampleApp.WinDesktop
|
|||
|
||||
private void OnGsaPropertyChanged(DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
vehicles.Value = string.Join(",", Message?.SatelliteIDs);
|
||||
if (Message == null)
|
||||
vehicles.Value = null;
|
||||
else
|
||||
vehicles.Value = string.Join(",", Message?.SatelliteIDs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
</Window.Resources>
|
||||
<Grid>
|
||||
<TabControl>
|
||||
<TabItem Header="GPS Info">
|
||||
<TabItem Header="Messages">
|
||||
<ScrollViewer HorizontalScrollBarVisibility="Disabled" Background="#FFEEEEEE">
|
||||
<WrapPanel x:Name="MessagePanel">
|
||||
<local:RmcControl x:Name="gprmcView" Style="{StaticResource card}" Visibility="{Binding Message, ElementName=gprmcView, Converter={StaticResource nullConv}}" />
|
||||
|
|
@ -42,13 +42,16 @@
|
|||
<local:SatelliteSnr Grid.Row="1" x:Name="satSnr" />
|
||||
</Grid>
|
||||
</TabItem>
|
||||
<TabItem Header="GNSS Monitor">
|
||||
<local:GnssMonitorView x:Name="gnssMonitorView" />
|
||||
</TabItem>
|
||||
<TabItem Header="Map">
|
||||
<local:View2D x:Name="view2d" />
|
||||
</TabItem>
|
||||
<TabItem Header="3D">
|
||||
<local:View3D x:Name="view3d" />
|
||||
</TabItem>
|
||||
<TabItem Header="Messages">
|
||||
<TabItem Header="NMEA Log">
|
||||
<TextBox x:Name="output"
|
||||
AcceptsReturn="True"
|
||||
IsReadOnly="True"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using NmeaParser.Gnss;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
|
@ -15,6 +16,7 @@ namespace SampleApp.WinDesktop
|
|||
{
|
||||
private Queue<string> messages = new Queue<string>(101);
|
||||
public static NmeaParser.NmeaDevice currentDevice;
|
||||
|
||||
//Dialog for browsing to nmea log files
|
||||
private Microsoft.Win32.OpenFileDialog nmeaOpenFileDialog = new Microsoft.Win32.OpenFileDialog()
|
||||
{
|
||||
|
|
@ -53,6 +55,7 @@ namespace SampleApp.WinDesktop
|
|||
if (currentDevice.IsOpen)
|
||||
await currentDevice.CloseAsync();
|
||||
currentDevice.Dispose();
|
||||
gnssMonitorView.Monitor = null;
|
||||
}
|
||||
output.Text = "";
|
||||
messages.Clear();
|
||||
|
|
@ -78,6 +81,7 @@ namespace SampleApp.WinDesktop
|
|||
((NmeaParser.SerialPortDevice)device).Port.BaudRate);
|
||||
}
|
||||
await device.OpenAsync();
|
||||
gnssMonitorView.Monitor = new GnssMonitor(device);
|
||||
}
|
||||
|
||||
private void device_MessageReceived(object sender, NmeaParser.NmeaMessageReceivedEventArgs args)
|
||||
|
|
|
|||
|
|
@ -1,120 +1,87 @@
|
|||
using Esri.ArcGISRuntime.Geometry;
|
||||
// *******************************************************************************
|
||||
// * 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.Threading.Tasks;
|
||||
using Esri.ArcGISRuntime.Geometry;
|
||||
using NmeaParser.Gnss;
|
||||
|
||||
namespace SampleApp.WinDesktop
|
||||
{
|
||||
public class NmeaLocationDataSource : Esri.ArcGISRuntime.Location.LocationDataSource
|
||||
{
|
||||
private static SpatialReference wgs84_ellipsoidHeight = SpatialReference.Create(4326, 115700);
|
||||
private readonly NmeaParser.NmeaDevice m_device;
|
||||
private double m_Accuracy = 0;
|
||||
private double m_altitude = double.NaN;
|
||||
private double m_speed = 0;
|
||||
private double m_course = 0;
|
||||
private bool m_startStopDevice;
|
||||
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
|
||||
private readonly GnssMonitor m_gnssMonitor;
|
||||
private readonly bool m_startStopDevice;
|
||||
private double lastCourse = 0; // Course can fallback to NaN, but ArcGIS Datasource don't allow NaN course, so we cache last known as a fallback
|
||||
|
||||
public NmeaLocationDataSource(NmeaParser.NmeaDevice device, bool startStopDevice = true)
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NmeaLocationDataSource"/> class.
|
||||
/// </summary>
|
||||
/// <param name="device">The NMEA device to monitor</param>
|
||||
/// <param name="startStopDevice">Whether starting this datasource also controls the underlying NMEA device</param>
|
||||
public NmeaLocationDataSource(NmeaParser.NmeaDevice device, bool startStopDevice = true) : this(new GnssMonitor(device), startStopDevice)
|
||||
{
|
||||
if (device == null)
|
||||
throw new ArgumentNullException(nameof(device));
|
||||
this.m_device = device;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NmeaLocationDataSource"/> class.
|
||||
/// </summary>
|
||||
/// <param name="monitor">The NMEA device to monitor</param>
|
||||
/// <param name="startStopDevice">Whether starting this datasource also controls the underlying NMEA device</param>
|
||||
public NmeaLocationDataSource(NmeaParser.Gnss.GnssMonitor monitor, bool startStopDevice = true)
|
||||
{
|
||||
if (monitor == null)
|
||||
throw new ArgumentNullException(nameof(monitor));
|
||||
this.m_gnssMonitor = monitor;
|
||||
m_startStopDevice = startStopDevice;
|
||||
}
|
||||
|
||||
void device_MessageReceived(object sender, NmeaParser.NmeaMessageReceivedEventArgs e)
|
||||
protected async override Task OnStartAsync()
|
||||
{
|
||||
var message = e.Message;
|
||||
ParseMessage(message);
|
||||
}
|
||||
public void ParseMessage(NmeaParser.Messages.NmeaMessage message)
|
||||
{
|
||||
bool isNewFix = false;
|
||||
bool lostFix = false;
|
||||
double lat = 0;
|
||||
double lon = 0;
|
||||
if (message.TalkerId == NmeaParser.Talker.GlobalNavigationSatelliteSystem)
|
||||
m_supportGNMessages = true;
|
||||
else if(m_supportGNMessages && message.TalkerId != NmeaParser.Talker.GlobalNavigationSatelliteSystem)
|
||||
return; // If device supports combined GN* messages, ignore non-GN messages
|
||||
m_gnssMonitor.LocationChanged += OnLocationChanged;
|
||||
m_gnssMonitor.LocationLost += OnLocationChanged;
|
||||
if (m_startStopDevice && !this.m_gnssMonitor.Device.IsOpen)
|
||||
await this.m_gnssMonitor.Device.OpenAsync();
|
||||
|
||||
if (message is NmeaParser.Messages.Garmin.Pgrme rme)
|
||||
{
|
||||
m_Accuracy = rme.HorizontalError;
|
||||
}
|
||||
else if(message is NmeaParser.Messages.Gst gst)
|
||||
{
|
||||
Gst = gst;
|
||||
m_Accuracy = Math.Round(Math.Sqrt(Gst.SigmaLatitudeError * Gst.SigmaLatitudeError + Gst.SigmaLongitudeError * Gst.SigmaLongitudeError), 3);
|
||||
}
|
||||
else if (message is NmeaParser.Messages.Rmc rmc)
|
||||
{
|
||||
Rmc = rmc;
|
||||
if (Rmc.Active)
|
||||
{
|
||||
m_speed = double.IsNaN(Rmc.Speed) ? 0 : Rmc.Speed;
|
||||
if (!double.IsNaN(Rmc.Course))
|
||||
m_course = Rmc.Course;
|
||||
lat = Rmc.Latitude;
|
||||
lon = Rmc.Longitude;
|
||||
}
|
||||
else
|
||||
{
|
||||
lostFix = true;
|
||||
}
|
||||
isNewFix = !m_supportGGaMessages;
|
||||
}
|
||||
else if (message is NmeaParser.Messages.Gga gga)
|
||||
{
|
||||
m_supportGGaMessages = true;
|
||||
if (gga.Quality != NmeaParser.Messages.Gga.FixQuality.Invalid)
|
||||
{
|
||||
lat = gga.Latitude;
|
||||
lon = gga.Longitude;
|
||||
m_altitude = gga.Altitude + gga.GeoidalSeparation; //Convert to ellipsoidal height
|
||||
}
|
||||
if (gga.Quality == NmeaParser.Messages.Gga.FixQuality.Invalid || gga.Quality == NmeaParser.Messages.Gga.FixQuality.Estimated)
|
||||
{
|
||||
lostFix = true;
|
||||
}
|
||||
isNewFix = true;
|
||||
}
|
||||
else if (message is NmeaParser.Messages.Gsa gsa)
|
||||
{
|
||||
Gsa = gsa;
|
||||
}
|
||||
if (isNewFix)
|
||||
{
|
||||
base.UpdateLocation(new Esri.ArcGISRuntime.Location.Location(
|
||||
!double.IsNaN(m_altitude) ? new MapPoint(lon, lat, m_altitude, wgs84_ellipsoidHeight) : new MapPoint(lon, lat, SpatialReferences.Wgs84),
|
||||
m_Accuracy, m_speed, m_course, lostFix));
|
||||
}
|
||||
}
|
||||
|
||||
protected override Task OnStartAsync()
|
||||
{
|
||||
m_device.MessageReceived += device_MessageReceived;
|
||||
if (m_startStopDevice)
|
||||
return this.m_device.OpenAsync();
|
||||
else
|
||||
return System.Threading.Tasks.Task<bool>.FromResult(true);
|
||||
if (m_gnssMonitor.IsFixValid)
|
||||
OnLocationChanged(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
protected override Task OnStopAsync()
|
||||
{
|
||||
m_device.MessageReceived -= device_MessageReceived;
|
||||
m_Accuracy = double.NaN;
|
||||
m_gnssMonitor.LocationChanged -= OnLocationChanged;
|
||||
m_gnssMonitor.LocationLost -= OnLocationChanged;
|
||||
if(m_startStopDevice)
|
||||
return this.m_device.CloseAsync();
|
||||
return m_gnssMonitor.Device.CloseAsync();
|
||||
else
|
||||
return System.Threading.Tasks.Task<bool>.FromResult(true);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public NmeaParser.Messages.Gsa Gsa { get; private set; }
|
||||
public NmeaParser.Messages.Gga Gga { get; private set; }
|
||||
public NmeaParser.Messages.Rmc Rmc { get; private set; }
|
||||
public NmeaParser.Messages.Gst Gst { get; private set; }
|
||||
private void OnLocationChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (double.IsNaN(m_gnssMonitor.Longitude) || double.IsNaN(m_gnssMonitor.Latitude)) return;
|
||||
if (!double.IsNaN(m_gnssMonitor.Course))
|
||||
lastCourse = m_gnssMonitor.Course;
|
||||
UpdateLocation(new Esri.ArcGISRuntime.Location.Location(
|
||||
timestamp: null,
|
||||
position: !double.IsNaN(m_gnssMonitor.Altitude) ? new MapPoint(m_gnssMonitor.Longitude, m_gnssMonitor.Latitude, m_gnssMonitor.Altitude, wgs84_ellipsoidHeight) : new MapPoint(m_gnssMonitor.Longitude, m_gnssMonitor.Latitude, SpatialReferences.Wgs84),
|
||||
horizontalAccuracy: m_gnssMonitor.HorizontalError,
|
||||
verticalAccuracy: m_gnssMonitor.VerticalError,
|
||||
velocity: double.IsNaN(m_gnssMonitor.Speed) ? 0 : m_gnssMonitor.Speed * 0.51444444,
|
||||
course: lastCourse,
|
||||
!m_gnssMonitor.IsFixValid));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue