mirror of
https://github.com/dotMorten/NmeaParser.git
synced 2026-04-04 22:17:50 +00: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
12 changed files with 662 additions and 109 deletions
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
|
namespace NmeaParser.Gnss.Ntrip
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Enumeration for the carrier used by the <see cref="NtripStream"/>
|
||||||
|
/// </summary>
|
||||||
public enum Carrier : int
|
public enum Carrier : int
|
||||||
{
|
{
|
||||||
No = 0,
|
/// <summary>
|
||||||
|
/// None / unknown
|
||||||
|
/// </summary>
|
||||||
|
None = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// L1 wave
|
||||||
|
/// </summary>
|
||||||
L1 = 1,
|
L1 = 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// L1 and L2 waves
|
||||||
|
/// </summary>
|
||||||
L1L2 = 2
|
L1L2 = 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -17,6 +17,9 @@ using System.Net;
|
||||||
|
|
||||||
namespace NmeaParser.Gnss.Ntrip
|
namespace NmeaParser.Gnss.Ntrip
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets metadata about the NTRIP Caster
|
||||||
|
/// </summary>
|
||||||
public class Caster : NtripSource
|
public class Caster : NtripSource
|
||||||
{
|
{
|
||||||
internal Caster (string[] d)
|
internal Caster (string[] d)
|
||||||
|
|
@ -33,6 +36,18 @@ namespace NmeaParser.Gnss.Ntrip
|
||||||
FallbackAddress = IPAddress.Parse(d[9]);
|
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)
|
public Caster(IPAddress address, int port, string identifier, string _operator, bool supportsNmea, string countryCode, double latitude, double longitude, IPAddress fallbackkAddress)
|
||||||
{
|
{
|
||||||
Address = address;
|
Address = address;
|
||||||
|
|
@ -46,14 +61,49 @@ namespace NmeaParser.Gnss.Ntrip
|
||||||
FallbackAddress = fallbackkAddress;
|
FallbackAddress = fallbackkAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the caster IP Address
|
||||||
|
/// </summary>
|
||||||
public IPAddress Address { get; }
|
public IPAddress Address { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the caster port
|
||||||
|
/// </summary>
|
||||||
public int Port { get; }
|
public int Port { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the caster identifier
|
||||||
|
/// </summary>
|
||||||
public string Identifier { get; }
|
public string Identifier { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the caster operator
|
||||||
|
/// </summary>
|
||||||
public string Operator { get; }
|
public string Operator { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether it supports NMEA
|
||||||
|
/// </summary>
|
||||||
public bool SupportsNmea { get; }
|
public bool SupportsNmea { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the country code for the caster origin
|
||||||
|
/// </summary>
|
||||||
public string CountryCode { get; }
|
public string CountryCode { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the latitude for the caster
|
||||||
|
/// </summary>
|
||||||
public double Latitude { get; }
|
public double Latitude { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the longitude for the caster
|
||||||
|
/// </summary>
|
||||||
public double Longitude { get; }
|
public double Longitude { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the fallback address for the caster
|
||||||
|
/// </summary>
|
||||||
public IPAddress FallbackAddress { get; }
|
public IPAddress FallbackAddress { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -11,7 +11,6 @@
|
||||||
// * See the License for the specific language governing permissions and
|
// * See the License for the specific language governing permissions and
|
||||||
// * limitations under the License.
|
// * limitations under the License.
|
||||||
// ******************************************************************************
|
// ******************************************************************************
|
||||||
#nullable enable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
@ -21,6 +20,9 @@ using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace NmeaParser.Gnss.Ntrip
|
namespace NmeaParser.Gnss.Ntrip
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// NTRIP Client for querying an NTRIP server and opening an NTRIP stream
|
||||||
|
/// </summary>
|
||||||
public class Client : IDisposable
|
public class Client : IDisposable
|
||||||
{
|
{
|
||||||
private readonly string _host;
|
private readonly string _host;
|
||||||
|
|
@ -30,17 +32,33 @@ namespace NmeaParser.Gnss.Ntrip
|
||||||
private bool connected;
|
private bool connected;
|
||||||
private Task? runningTask;
|
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)
|
public Client(string host, int port)
|
||||||
{
|
{
|
||||||
_host = host;
|
_host = host;
|
||||||
_port = port;
|
_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)
|
public Client(string host, int port, string username, string password) : this(host, port)
|
||||||
{
|
{
|
||||||
_auth = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(username + ":" + password));
|
_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()
|
public IEnumerable<NtripSource> GetSourceTable()
|
||||||
{
|
{
|
||||||
string data = "";
|
string data = "";
|
||||||
|
|
@ -77,8 +95,8 @@ namespace NmeaParser.Gnss.Ntrip
|
||||||
{
|
{
|
||||||
var sckt = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
|
var sckt = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
|
||||||
sckt.Blocking = true;
|
sckt.Blocking = true;
|
||||||
|
sckt.ReceiveTimeout = 5000;
|
||||||
sckt.Connect(_host, _port);
|
sckt.Connect(_host, _port);
|
||||||
|
|
||||||
string msg = $"GET /{path} HTTP/1.1\r\n";
|
string msg = $"GET /{path} HTTP/1.1\r\n";
|
||||||
msg += "User-Agent: NTRIP ntripclient\r\n";
|
msg += "User-Agent: NTRIP ntripclient\r\n";
|
||||||
if (_auth != null)
|
if (_auth != null)
|
||||||
|
|
@ -92,11 +110,29 @@ namespace NmeaParser.Gnss.Ntrip
|
||||||
sckt.Send(data);
|
sckt.Send(data);
|
||||||
return sckt;
|
return sckt;
|
||||||
}
|
}
|
||||||
|
/// <summary>
|
||||||
public void Connect(string strName)
|
/// 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");
|
if (sckt != null) throw new Exception("Connection already open");
|
||||||
sckt = Request(strName);
|
sckt = Request(mountPoint);
|
||||||
connected = true;
|
connected = true;
|
||||||
runningTask = Task.Run(ReceiveThread);
|
runningTask = Task.Run(ReceiveThread);
|
||||||
}
|
}
|
||||||
|
|
@ -104,10 +140,10 @@ namespace NmeaParser.Gnss.Ntrip
|
||||||
private async Task ReceiveThread()
|
private async Task ReceiveThread()
|
||||||
{
|
{
|
||||||
byte[] buffer = new byte[65536];
|
byte[] buffer = new byte[65536];
|
||||||
sckt.ReceiveTimeout = 1000;
|
|
||||||
while (connected && sckt != null)
|
while (connected && sckt != null)
|
||||||
{
|
{
|
||||||
int count = sckt.Receive(buffer, SocketFlags.None, out SocketError errorCode);
|
int count = sckt.Receive(buffer);
|
||||||
if (count > 0)
|
if (count > 0)
|
||||||
{
|
{
|
||||||
DataReceived?.Invoke(this, buffer.Take(count).ToArray());
|
DataReceived?.Invoke(this, buffer.Take(count).ToArray());
|
||||||
|
|
@ -128,6 +164,10 @@ namespace NmeaParser.Gnss.Ntrip
|
||||||
sckt = null;
|
sckt = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Shuts down the stream
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
public Task CloseAsync()
|
public Task CloseAsync()
|
||||||
{
|
{
|
||||||
if (runningTask != null)
|
if (runningTask != null)
|
||||||
|
|
@ -137,15 +177,30 @@ namespace NmeaParser.Gnss.Ntrip
|
||||||
runningTask = null;
|
runningTask = null;
|
||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
#if NETSTANDARD || NETFX
|
||||||
|
return Task.FromResult<object?>(null);
|
||||||
|
#else
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_ = CloseAsync();
|
_ = CloseAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fired when bytes has been received from the stream
|
||||||
|
/// </summary>
|
||||||
public event EventHandler<byte[]>? DataReceived;
|
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
|
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()
|
protected NtripSource()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
@ -17,6 +17,9 @@ using System.Globalization;
|
||||||
|
|
||||||
namespace NmeaParser.Gnss.Ntrip
|
namespace NmeaParser.Gnss.Ntrip
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Metadata on an NTRIP Data Stream
|
||||||
|
/// </summary>
|
||||||
public class NtripStream : NtripSource
|
public class NtripStream : NtripSource
|
||||||
{
|
{
|
||||||
internal NtripStream(string[] d)
|
internal NtripStream(string[] d)
|
||||||
|
|
@ -38,15 +41,54 @@ namespace NmeaParser.Gnss.Ntrip
|
||||||
SupportsNmea = d[11] == "1";
|
SupportsNmea = d[11] == "1";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The mountpoint used with <see cref="Client.Connect(string)"/>
|
||||||
|
/// </summary>
|
||||||
public string Mountpoint { get; }
|
public string Mountpoint { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the unique identifier for the stream
|
||||||
|
/// </summary>
|
||||||
public string Identifier { get; }
|
public string Identifier { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the stream format
|
||||||
|
/// </summary>
|
||||||
public string Format { get; }
|
public string Format { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the details about the format
|
||||||
|
/// </summary>
|
||||||
public string FormatDetails { get; }
|
public string FormatDetails { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the wave carrier for the stream
|
||||||
|
/// </summary>
|
||||||
public Carrier Carrier { get; }
|
public Carrier Carrier { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the network for the stream
|
||||||
|
/// </summary>
|
||||||
public string Network { get; }
|
public string Network { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the country code for where the stream originates
|
||||||
|
/// </summary>
|
||||||
public string CountryCode { get; }
|
public string CountryCode { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the latitude location of the base station
|
||||||
|
/// </summary>
|
||||||
public double Latitude { get; }
|
public double Latitude { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the longitude location of the base station
|
||||||
|
/// </summary>
|
||||||
public double Longitude { get; }
|
public double Longitude { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether the stream supports NMEA
|
||||||
|
/// </summary>
|
||||||
public bool SupportsNmea { get; }
|
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)
|
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>
|
</Window.Resources>
|
||||||
<Grid>
|
<Grid>
|
||||||
<TabControl>
|
<TabControl>
|
||||||
<TabItem Header="GPS Info">
|
<TabItem Header="Messages">
|
||||||
<ScrollViewer HorizontalScrollBarVisibility="Disabled" Background="#FFEEEEEE">
|
<ScrollViewer HorizontalScrollBarVisibility="Disabled" Background="#FFEEEEEE">
|
||||||
<WrapPanel x:Name="MessagePanel">
|
<WrapPanel x:Name="MessagePanel">
|
||||||
<local:RmcControl x:Name="gprmcView" Style="{StaticResource card}" Visibility="{Binding Message, ElementName=gprmcView, Converter={StaticResource nullConv}}" />
|
<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" />
|
<local:SatelliteSnr Grid.Row="1" x:Name="satSnr" />
|
||||||
</Grid>
|
</Grid>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
|
<TabItem Header="GNSS Monitor">
|
||||||
|
<local:GnssMonitorView x:Name="gnssMonitorView" />
|
||||||
|
</TabItem>
|
||||||
<TabItem Header="Map">
|
<TabItem Header="Map">
|
||||||
<local:View2D x:Name="view2d" />
|
<local:View2D x:Name="view2d" />
|
||||||
</TabItem>
|
</TabItem>
|
||||||
<TabItem Header="3D">
|
<TabItem Header="3D">
|
||||||
<local:View3D x:Name="view3d" />
|
<local:View3D x:Name="view3d" />
|
||||||
</TabItem>
|
</TabItem>
|
||||||
<TabItem Header="Messages">
|
<TabItem Header="NMEA Log">
|
||||||
<TextBox x:Name="output"
|
<TextBox x:Name="output"
|
||||||
AcceptsReturn="True"
|
AcceptsReturn="True"
|
||||||
IsReadOnly="True"
|
IsReadOnly="True"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using NmeaParser.Gnss;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
@ -15,6 +16,7 @@ namespace SampleApp.WinDesktop
|
||||||
{
|
{
|
||||||
private Queue<string> messages = new Queue<string>(101);
|
private Queue<string> messages = new Queue<string>(101);
|
||||||
public static NmeaParser.NmeaDevice currentDevice;
|
public static NmeaParser.NmeaDevice currentDevice;
|
||||||
|
|
||||||
//Dialog for browsing to nmea log files
|
//Dialog for browsing to nmea log files
|
||||||
private Microsoft.Win32.OpenFileDialog nmeaOpenFileDialog = new Microsoft.Win32.OpenFileDialog()
|
private Microsoft.Win32.OpenFileDialog nmeaOpenFileDialog = new Microsoft.Win32.OpenFileDialog()
|
||||||
{
|
{
|
||||||
|
|
@ -53,6 +55,7 @@ namespace SampleApp.WinDesktop
|
||||||
if (currentDevice.IsOpen)
|
if (currentDevice.IsOpen)
|
||||||
await currentDevice.CloseAsync();
|
await currentDevice.CloseAsync();
|
||||||
currentDevice.Dispose();
|
currentDevice.Dispose();
|
||||||
|
gnssMonitorView.Monitor = null;
|
||||||
}
|
}
|
||||||
output.Text = "";
|
output.Text = "";
|
||||||
messages.Clear();
|
messages.Clear();
|
||||||
|
|
@ -78,6 +81,7 @@ namespace SampleApp.WinDesktop
|
||||||
((NmeaParser.SerialPortDevice)device).Port.BaudRate);
|
((NmeaParser.SerialPortDevice)device).Port.BaudRate);
|
||||||
}
|
}
|
||||||
await device.OpenAsync();
|
await device.OpenAsync();
|
||||||
|
gnssMonitorView.Monitor = new GnssMonitor(device);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void device_MessageReceived(object sender, NmeaParser.NmeaMessageReceivedEventArgs args)
|
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;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Esri.ArcGISRuntime.Geometry;
|
||||||
|
using NmeaParser.Gnss;
|
||||||
|
|
||||||
namespace SampleApp.WinDesktop
|
namespace SampleApp.WinDesktop
|
||||||
{
|
{
|
||||||
public class NmeaLocationDataSource : Esri.ArcGISRuntime.Location.LocationDataSource
|
public class NmeaLocationDataSource : Esri.ArcGISRuntime.Location.LocationDataSource
|
||||||
{
|
{
|
||||||
private static SpatialReference wgs84_ellipsoidHeight = SpatialReference.Create(4326, 115700);
|
private static SpatialReference wgs84_ellipsoidHeight = SpatialReference.Create(4326, 115700);
|
||||||
private readonly NmeaParser.NmeaDevice m_device;
|
private readonly GnssMonitor m_gnssMonitor;
|
||||||
private double m_Accuracy = 0;
|
private readonly bool m_startStopDevice;
|
||||||
private double m_altitude = double.NaN;
|
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
|
||||||
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
|
|
||||||
|
|
||||||
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;
|
m_startStopDevice = startStopDevice;
|
||||||
}
|
}
|
||||||
|
|
||||||
void device_MessageReceived(object sender, NmeaParser.NmeaMessageReceivedEventArgs e)
|
protected async override Task OnStartAsync()
|
||||||
{
|
{
|
||||||
var message = e.Message;
|
m_gnssMonitor.LocationChanged += OnLocationChanged;
|
||||||
ParseMessage(message);
|
m_gnssMonitor.LocationLost += OnLocationChanged;
|
||||||
}
|
if (m_startStopDevice && !this.m_gnssMonitor.Device.IsOpen)
|
||||||
public void ParseMessage(NmeaParser.Messages.NmeaMessage message)
|
await this.m_gnssMonitor.Device.OpenAsync();
|
||||||
{
|
|
||||||
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
|
|
||||||
|
|
||||||
if (message is NmeaParser.Messages.Garmin.Pgrme rme)
|
if (m_gnssMonitor.IsFixValid)
|
||||||
{
|
OnLocationChanged(this, EventArgs.Empty);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Task OnStopAsync()
|
protected override Task OnStopAsync()
|
||||||
{
|
{
|
||||||
m_device.MessageReceived -= device_MessageReceived;
|
m_gnssMonitor.LocationChanged -= OnLocationChanged;
|
||||||
m_Accuracy = double.NaN;
|
m_gnssMonitor.LocationLost -= OnLocationChanged;
|
||||||
if(m_startStopDevice)
|
if(m_startStopDevice)
|
||||||
return this.m_device.CloseAsync();
|
return m_gnssMonitor.Device.CloseAsync();
|
||||||
else
|
else
|
||||||
return System.Threading.Tasks.Task<bool>.FromResult(true);
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public NmeaParser.Messages.Gsa Gsa { get; private set; }
|
private void OnLocationChanged(object sender, EventArgs e)
|
||||||
public NmeaParser.Messages.Gga Gga { get; private set; }
|
{
|
||||||
public NmeaParser.Messages.Rmc Rmc { get; private set; }
|
if (double.IsNaN(m_gnssMonitor.Longitude) || double.IsNaN(m_gnssMonitor.Latitude)) return;
|
||||||
public NmeaParser.Messages.Gst Gst { get; private set; }
|
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…
Add table
Add a link
Reference in a new issue