### Install NmeaParser via NuGet Source: https://github.com/dotmorten/nmeaparser/blob/main/docs/concepts/index.md Use the Package Manager Console to install the library. ```powershell PM> Install-Package SharpGIS.NmeaParser ``` -------------------------------- ### Connect to NTRIP Server and Stream Data Source: https://github.com/dotmorten/nmeaparser/blob/main/docs/concepts/ntrip.md Example of initializing an NTRIP client, retrieving the source table, and forwarding received data to an NMEA device. ```csharp string hostname = "esricaster.esri.com"; // Replace with a server near you int port = 2101; //Port for the ntrip server var client = new Ntrip.Client(hostname, port); // Get the source table from the server: var table = client.GetSourceTable(); // Just pick the first Ntrip datastream: var str = table.OfType().First(); // Listen for data, and simply forward it to the NMEA device: client.DataReceived += (sender, ntripData) => { nmeaDevice.WriteAsync(ntripData, 0, ntripData.Length); }; // Connect to the stream client.Connect(str.Mountpoint); ``` -------------------------------- ### Create NMEA Location Data Source Source: https://github.com/dotmorten/nmeaparser/blob/main/docs/concepts/ArcGISRuntime.md Initializes the NMEA location data source with a GnssMonitor or an NMEA device. Controls whether starting the data source also controls the underlying NMEA device. ```csharp using System; using System.Threading.Tasks; using Esri.ArcGISRuntime.Geometry; using NmeaParser.Gnss; namespace NmeaParser.ArcGIS { public class NmeaLocationDataSource : Esri.ArcGISRuntime.Location.LocationDataSource { private static SpatialReference wgs84_ellipsoidHeight = SpatialReference.Create(4326, 115700); 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 /// /// Initializes a new instance of the class. /// /// The NMEA device to monitor /// Whether starting this datasource also controls the underlying NMEA device public NmeaLocationDataSource(NmeaParser.NmeaDevice device, bool startStopDevice = true) : this(new GnssMonitor(device), startStopDevice) { } /// /// Initializes a new instance of the class. /// /// The NMEA device to monitor /// Whether starting this datasource also controls the underlying NMEA device public NmeaLocationDataSource(NmeaParser.Gnss.GnssMonitor monitor, bool startStopDevice = true) { if (monitor == null) throw new ArgumentNullException(nameof(monitor)); this.m_gnssMonitor = monitor; m_startStopDevice = startStopDevice; } protected async override Task OnStartAsync() { m_gnssMonitor.LocationChanged += OnLocationChanged; m_gnssMonitor.LocationLost += OnLocationChanged; if (m_startStopDevice && !this.m_gnssMonitor.Device.IsOpen) await this.m_gnssMonitor.Device.OpenAsync(); if (m_gnssMonitor.IsFixValid) OnLocationChanged(this, EventArgs.Empty); } protected override Task OnStopAsync() { m_gnssMonitor.LocationChanged -= OnLocationChanged; m_gnssMonitor.LocationLost -= OnLocationChanged; if(m_startStopDevice) return m_gnssMonitor.Device.CloseAsync(); else return Task.CompletedTask; } private Esri.ArcGISRuntime.Location.Location currentLocation; 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; DateTimeOffset? timestamp = null; if(m_gnssMonitor.FixTime.HasValue) timestamp = new DateTimeOffset(DateTime.UtcNow.Date.Add(m_gnssMonitor.FixTime.Value)); var location = new Esri.ArcGISRuntime.Location.Location( timestamp: timestamp, 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); // Avoid raising additional location events if nothing changed if (currentLocation == null || currentLocation.Position.X != location.Position.X || currentLocation.Position.Y != location.Position.Y || currentLocation.Position.Z != location.Position.Z || currentLocation.Course != location.Course || currentLocation.Velocity != location.Velocity || currentLocation.HorizontalAccuracy != location.HorizontalAccuracy || currentLocation.VerticalAccuracy != location.VerticalAccuracy || currentLocation.IsLastKnown != location.IsLastKnown || timestamp != location.Timestamp) { currentLocation = location; UpdateLocation(currentLocation); } ``` -------------------------------- ### Parse and Check Talker IDs in C# Source: https://context7.com/dotmorten/nmeaparser/llms.txt Parse NMEA messages from different satellite systems and check their talker IDs. This example demonstrates how to identify GPS, GLONASS, Galileo, BeiDou, and proprietary messages. ```csharp using NmeaParser; using NmeaParser.Messages; // Parse messages from different satellite systems var gpsMessage = NmeaMessage.Parse("$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A"); var glonassMessage = NmeaMessage.Parse("$GLRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*55"); var combinedMessage = NmeaMessage.Parse("$GNRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*4A"); var galileoMessage = NmeaMessage.Parse("$GARMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*44"); var beidouMessage = NmeaMessage.Parse("$GBRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*45"); // Check talker ID Console.WriteLine($"GPS Talker: {gpsMessage.TalkerId}"); // GlobalPositioningSystem Console.WriteLine($"GLONASS Talker: {glonassMessage.TalkerId}"); // GlonassReceiver Console.WriteLine($"Combined Talker: {combinedMessage.TalkerId}"); // GlobalNavigationSatelliteSystem Console.WriteLine($"Galileo Talker: {galileoMessage.TalkerId}"); // GalileoPositioningSystem Console.WriteLine($"BeiDou Talker: {beidouMessage.TalkerId}"); // BeiDouNavigationSatelliteSystem // Proprietary messages var garminMessage = NmeaMessage.Parse("$PGRME,3.0,M,4.5,M,5.2,M*28"); Console.WriteLine($"Is Proprietary: {garminMessage.IsProprietary}"); // true Console.WriteLine($"Talker: {garminMessage.TalkerId}"); // ProprietaryCode // Use talker ID for filtering void ProcessMessage(NmeaMessage msg) { switch (msg.TalkerId) { case Talker.GlobalPositioningSystem: Console.WriteLine("GPS message"); break; case Talker.GlonassReceiver: Console.WriteLine("GLONASS message"); break; case Talker.GlobalNavigationSatelliteSystem: Console.WriteLine("Combined GNSS message (preferred)"); break; case Talker.GalileoPositioningSystem: Console.WriteLine("Galileo message"); break; case Talker.BeiDouNavigationSatelliteSystem: Console.WriteLine("BeiDou message"); break; case Talker.ProprietaryCode: Console.WriteLine("Proprietary message"); break; } } ``` -------------------------------- ### Initialize NmeaFileDevice and Handle Messages Source: https://github.com/dotmorten/nmeaparser/blob/main/docs/concepts/index.md Create a file-based device and subscribe to the MessageReceived event to parse RMC messages. ```csharp // Create one of the NMEA devices var device = new NmeaFileDevice("PathToNmeaLogFile.txt", 1000); // Listen to messages from the device: device.MessageReceived += device_NmeaMessageReceived; // Open the device and start receiving: device.OpenAsync(); // Create event handler for receiving messages: private void device_NmeaMessageReceived(object sender, NmeaMessageReceivedEventArgs args) { // called when a message is received if(args.Message is NmeaParser.Messages.Rmc rmc) { Console.WriteLine($"Your current location is: {rmc.Latitude} , {rmc.Longitude}"); } } ``` -------------------------------- ### Create and Open Serial Port Device Source: https://github.com/dotmorten/nmeaparser/blob/main/docs/concepts/SerialPortNetCore.md Instantiate a SerialPort object with the correct port name and baud rate. Then, create an NmeaParser.SerialPortDevice, subscribe to the MessageReceived event, and open the port asynchronously. Ensure the port name and baud rate match your device's specifications. ```csharp string portname = "COM3"; // Change to match the name of the port your device is connected to int baudrate = 9600; // Change to the baud rate your device communicates at (usually specified in the manual) var port = new System.IO.Ports.SerialPort(portname, baudrate); var device = new NmeaParser.SerialPortDevice(port); device.MessageReceived += OnNmeaMessageReceived; device.OpenAsync(); ... private void OnNmeaMessageReceived(NmeaParser.NmeaDevice sender, NmeaParser.NmeaMessageReceivedEventArgs args) { // called when a message is received } ``` -------------------------------- ### Connect and Read from NMEA Devices Source: https://context7.com/dotmorten/nmeaparser/llms.txt Demonstrates using the NmeaDevice base class to open a stream, subscribe to message events, and handle device lifecycle. ```csharp using NmeaParser; using NmeaParser.Messages; // Example using a generic stream device var stream = File.OpenRead("gps_log.txt"); var device = new StreamDevice(stream); // Subscribe to message events device.MessageReceived += (sender, args) => { Console.WriteLine($"Received: {args.Message.MessageType}"); // Handle specific message types if (args.Message is Rmc rmc && rmc.Active) { Console.WriteLine($"Position: {rmc.Latitude:F6}, {rmc.Longitude:F6}"); Console.WriteLine($"Speed: {rmc.Speed} knots, Course: {rmc.Course}°"); Console.WriteLine($"Fix Time: {rmc.FixTime}"); } }; // Handle device disconnection device.DeviceDisconnected += (sender, ex) => { Console.WriteLine($"Device disconnected: {ex.Message}"); }; // Open the device and start receiving messages await device.OpenAsync(); // Check if device is open Console.WriteLine($"Device is open: {device.IsOpen}"); // Write data to device (e.g., RTCM corrections for DGPS) if (device.CanWrite) { byte[] rtcmData = GetRtcmCorrections(); await device.WriteAsync(rtcmData, 0, rtcmData.Length); } // Close when done await device.CloseAsync(); device.Dispose(); ``` -------------------------------- ### Configure MauiAsset Build Action Source: https://github.com/dotmorten/nmeaparser/blob/main/src/SampleApp.Maui/Resources/Raw/AboutAssets.txt Add this entry to your .csproj file to ensure raw assets are included in the application package. ```xml ``` -------------------------------- ### Access Raw Assets Source: https://github.com/dotmorten/nmeaparser/blob/main/src/SampleApp.Maui/Resources/Raw/AboutAssets.txt Use FileSystem.OpenAppPackageFileAsync to open a stream for a file located in the Resources/Raw directory. ```csharp async Task LoadMauiAsset() { using var stream = await FileSystem.OpenAppPackageFileAsync("AboutAssets.txt"); using var reader = new StreamReader(stream); var contents = reader.ReadToEnd(); } ``` -------------------------------- ### NTRIP Client Implementation Source: https://github.com/dotmorten/nmeaparser/blob/main/docs/concepts/ntrip.md Implements an NTRIP client to connect to a specified host and port, retrieve source tables, and handle authentication. Requires .NET framework. ```cs public class Client : IDisposable { private readonly string _host; private readonly int _port; private string _auth; private Socket sckt; public Client(string host, int port) { _host = host; _port = port; } public Client(string host, int port, string username, string password) : this(host, port) { _auth = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(username + ":" + password)); } public IEnumerable GetSourceTable() { string data = ""; byte[] buffer = new byte[1024]; using (var sck = Request("")) { int count; while ((count = sck.Receive(buffer)) > 0) { data += System.Text.Encoding.UTF8.GetString(buffer, 0, count); } } var lines = data.Split('\n'); List sources = new List(); foreach (var item in lines) { var d = item.Split(';'); if (d.Length == 0) continue; if (d[0] == "ENDSOURCETABLE") break; if (d[0] == "CAS") { var c = new Caster(); var a = d[1].Split(':'); c.Address = IPAddress.Parse(a[0]); c.Port = int.Parse(a[1]); c.Identifier = d[3]; c.Operator = d[4]; c.SupportsNmea = d[5] == "1"; c.CountryCode = d[6]; c.Latitude = double.Parse(d[7], CultureInfo.InvariantCulture); c.Longitude = double.Parse(d[8], CultureInfo.InvariantCulture); c.FallbackAddress = IPAddress.Parse(d[9]); sources.Add(c); } else if (d[0] == "STR") { var str = new NtripStream(); str.Mountpoint = d[1]; str.Identifier = d[2]; str.Format = d[3]; str.FormatDetails = d[4]; str.Carrier = (Carrier)int.Parse(d[5]); str.Network = d[7]; str.CountryCode = d[8]; str.Latitude = double.Parse(d[9], CultureInfo.InvariantCulture); str.Longitude = double.Parse(d[10], CultureInfo.InvariantCulture); str.SupportsNmea = d[11] == "1"; sources.Add(str); } } return sources; } private Socket Request(string path) { var sckt = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); sckt.Blocking = true; sckt.Connect(_host, _port); string msg = "GET /" + path + " HTTP/1.1\r\n"; msg += "User-Agent: NTRIP ntripclient\r\n"; if (_auth != null) { msg += "Authorization: Basic " + _auth + "\r\n"; } msg += "Accept: */*\r\nConnection: close\r\n"; msg += "\r\n"; byte[] data = System.Text.Encoding.ASCII.GetBytes(msg); ``` -------------------------------- ### Initialize NmeaLocationProvider Source: https://github.com/dotmorten/nmeaparser/blob/main/docs/concepts/ArcGISRuntime.md Configures the MapView's LocationDisplay to use an NMEA device as the data source. ```csharp NmeaParser.NmeaDevice device = new NmeaParser.NmeaFileDevice("NmeaSampleData.txt"); mapView.LocationDisplay.DataSource = new NmeaLocationProvider(device); mapView.LocationDisplay.InitialZoomScale = 20000; mapView.LocationDisplay.IsEnabled = true; ``` -------------------------------- ### Connect to SerialPortDevice in C# Source: https://context7.com/dotmorten/nmeaparser/llms.txt Configures a serial port and initializes a SerialPortDevice to process incoming NMEA messages. ```csharp using NmeaParser; using NmeaParser.Messages; using System.IO.Ports; // Create and configure the serial port string portName = "COM3"; // Or "/dev/ttyUSB0" on Linux int baudRate = 9600; // Common GPS baud rates: 4800, 9600, 38400, 115200 var serialPort = new SerialPort(portName, baudRate); serialPort.DataBits = 8; serialPort.Parity = Parity.None; serialPort.StopBits = StopBits.One; // Create the NMEA device var device = new SerialPortDevice(serialPort); device.MessageReceived += (sender, args) => { switch (args.Message) { case Gga gga when gga.Quality != Gga.FixQuality.Invalid: Console.WriteLine($"GGA Fix: {gga.Latitude:F6}, {gga.Longitude:F6}"); Console.WriteLine($"Altitude: {gga.Altitude}m, Satellites: {gga.NumberOfSatellites}"); break; case Rmc rmc when rmc.Active: Console.WriteLine($"RMC: Speed={rmc.Speed}kts, Course={rmc.Course}°"); break; case Gsv gsv: Console.WriteLine($"Satellites in view: {gsv.SatellitesInView}"); foreach (var sv in gsv.SVs) { Console.WriteLine($" PRN {sv.Id}: El={sv.Elevation}°, Az={sv.Azimuth}°, SNR={sv.SignalToNoiseRatio}dB"); } break; } }; // Open device await device.OpenAsync(); Console.WriteLine($"Listening on {portName}..."); // Keep running until user stops Console.ReadLine(); // Clean up await device.CloseAsync(); device.Dispose(); ``` -------------------------------- ### Connect to and Configure Serial Port Device Source: https://github.com/dotmorten/nmeaparser/blob/main/docs/concepts/SerialPortUWP.md C# code to find a serial port (e.g., COM3), create a SerialDevice object, configure its properties, and initialize the NMEA Parser's SerialPortDevice. Ensure the BaudRate and other settings match your specific hardware. ```csharp var selector = SerialDevice.GetDeviceSelector("COM3"); //Get the serial port on port '3' var devices = await DeviceInformation.FindAllAsync(selector); if(devices.Any()) //if the device is found { var deviceInfo = devices.First(); var serialDevice = await SerialDevice.FromIdAsync(deviceInfo.Id); //Set up serial device according to device specifications: //This might differ from device to device serialDevice.BaudRate = 4800; serialDevice.DataBits = 8; serialDevice.Parity = SerialParity.None; var device = new NmeaParser.SerialPortDevice(serialDevice); device.MessageReceived += device_NmeaMessageReceived; } ``` ```csharp private void device_NmeaMessageReceived(NmeaParser.NmeaDevice sender, NmeaParser.NmeaMessageReceivedEventArgs args) { // called when a message is received } ``` -------------------------------- ### Connect to Bluetooth Device and Parse NMEA Data Source: https://github.com/dotmorten/nmeaparser/blob/main/docs/concepts/BluetoothUWP.md This C# code finds a Bluetooth serial port device by its selector, retrieves its service, and initializes the NMEA parser. Ensure the Bluetooth device is paired before running. ```csharp //Get list of devices string serialDeviceType = RfcommDeviceService.GetDeviceSelector(RfcommServiceId.SerialPort); var devices = await DeviceInformation.FindAllAsync(serialDeviceType); //Select device by name (in this case TruePulse 360B Laser Range Finder) var TruePulse360B = devices.Where(t => t.Name.StartsWith("TP360B-")).FirstOrDefault(); //Get service RfcommDeviceService rfcommService = await RfcommDeviceService.FromIdAsync(TruePulse360B.Id); if (rfcommService != null) { var rangeFinder = new NmeaParser.BluetoothDevice(rfcommService); rangeFinder.MessageReceived += device_NmeaMessageReceived; await rangeFinder.OpenAsync(); } ... private void device_NmeaMessageReceived(object sender, NmeaParser.NmeaMessageReceivedEventArgs args) { // called when a message is received } ``` -------------------------------- ### Connect to NTRIP and Forward RTCM Data Source: https://context7.com/dotmorten/nmeaparser/llms.txt Establishes a connection to an NTRIP caster, lists available streams, selects a mountpoint, and forwards the RTCM data stream to a serial GPS device. ```csharp using NmeaParser; using NmeaParser.Gnss.Ntrip; // Create NTRIP client string ntripHost = "rtk2go.com"; // Public NTRIP caster int ntripPort = 2101; string? username = null; // Optional authentication string? password = null; var ntripClient = new Client(ntripHost, ntripPort, username, password); // Get available data streams var sourceTable = ntripClient.GetSourceTable(); Console.WriteLine("Available NTRIP sources:"); foreach (var source in sourceTable) { if (source is NtripStream stream) { Console.WriteLine($"Stream: {stream.Mountpoint}"); Console.WriteLine($" Identifier: {stream.Identifier}"); Console.WriteLine($" Format: {stream.Format} ({stream.FormatDetails})"); Console.WriteLine($" Location: {stream.Latitude:F4}, {stream.Longitude:F4}"); Console.WriteLine($" Country: {stream.CountryCode}"); Console.WriteLine($" Supports NMEA: {stream.SupportsNmea}"); } else if (source is Caster caster) { Console.WriteLine($"Caster: {caster.Identifier}"); Console.WriteLine($" Address: {caster.Address}:{caster.Port}"); Console.WriteLine($" Operator: {caster.Operator}"); } } // Select a nearby mountpoint var selectedStream = sourceTable.OfType() .Where(s => s.Format.Contains("RTCM")) .OrderBy(s => CalculateDistance(s.Latitude, s.Longitude, myLat, myLon)) .FirstOrDefault(); if (selectedStream != null) { Console.WriteLine($"Connecting to: {selectedStream.Mountpoint}"); // Open RTCM stream using var rtcmStream = ntripClient.OpenStream(selectedStream); // Create GPS device that supports writing var gpsDevice = new SerialPortDevice(new SerialPort("COM3", 9600)); await gpsDevice.OpenAsync(); // Forward RTCM data to GPS device var buffer = new byte[4096]; while (true) { int bytesRead = await rtcmStream.ReadAsync(buffer, 0, buffer.Length); if (bytesRead > 0 && gpsDevice.CanWrite) { await gpsDevice.WriteAsync(buffer, 0, bytesRead); Console.WriteLine($"Sent {bytesRead} bytes of RTCM data to GPS"); } } } // Helper to calculate distance double CalculateDistance(double lat1, double lon1, double lat2, double lon2) { var R = 6371; // Earth's radius in km var dLat = (lat2 - lat1) * Math.PI / 180; var dLon = (lon2 - lon1) * Math.PI / 180; var a = Math.Sin(dLat/2) * Math.Sin(dLat/2) + Math.Cos(lat1 * Math.PI / 180) * Math.Cos(lat2 * Math.PI / 180) * Math.Sin(dLon/2) * Math.Sin(dLon/2); return R * 2 * Math.Atan2(Math.Sqrt(a), Math.Sqrt(1-a)); } ``` -------------------------------- ### Enable Bluetooth RFCOMM Capability Source: https://github.com/dotmorten/nmeaparser/blob/main/docs/concepts/BluetoothUWP.md Add this XML to your package.appxmanifest file to enable Bluetooth RFCOMM serial port communication. ```xml ``` -------------------------------- ### Playback NMEA log files with NmeaFileDevice Source: https://context7.com/dotmorten/nmeaparser/llms.txt Reads recorded NMEA data from a file for simulation purposes, supporting custom playback speeds. ```csharp using NmeaParser; using NmeaParser.Messages; // Create file device with default 1000ms read interval var device = new NmeaFileDevice("gps_recording.nmea"); // Or specify custom read speed (time between line groups in milliseconds) var fastDevice = new NmeaFileDevice("gps_recording.nmea", readSpeed: 100); // 10x faster playback Console.WriteLine($"Playing back: {device.FileName}"); int messageCount = 0; device.MessageReceived += (sender, args) => { messageCount++; if (args.Message is IGeographicLocation location) { Console.WriteLine($"[{messageCount}] Location: {location.Latitude:F6}, {location.Longitude:F6}"); } if (args.Message is ITimestampedMessage timestamped) { Console.WriteLine($" Timestamp: {timestamped.Timestamp}"); } }; device.DeviceDisconnected += (sender, ex) => { Console.WriteLine($"Playback complete. Total messages: {messageCount}"); }; await device.OpenAsync(); // Wait for playback to complete await Task.Delay(TimeSpan.FromMinutes(5)); await device.CloseAsync(); ``` -------------------------------- ### Enable Serial Communication Capability in App Manifest Source: https://github.com/dotmorten/nmeaparser/blob/main/docs/concepts/SerialPortUWP.md Add this XML to your package.appxmanifest file to enable serial device communication. ```xml ``` -------------------------------- ### Implement NmeaLocationProvider class Source: https://github.com/dotmorten/nmeaparser/blob/main/docs/concepts/ArcGISRuntime.md Custom LocationDataSource that processes NMEA RMC messages to update the map location. ```csharp using System.Threading.Tasks; using Esri.ArcGISRuntime.Geometry; using Esri.ArcGISRuntime.Location; using NmeaParser; namespace NmeaParser.ArcGIS { public class NmeaLocationProvider : LocationDataSource { private readonly NmeaParser.NmeaDevice device; public NmeaLocationProvider(NmeaParser.NmeaDevice device) { this.device = device; device.MessageReceived += NmeaMessageReceived; } private void NmeaMessageReceived(object sender, NmeaMessageReceivedEventArgs e) { var message = e.Message; if (message is NmeaParser.Messages.Rmc rmc && rmc.Active) { base.UpdateLocation(new Location( new MapPoint(rmc.Longitude, rmc.Latitude, SpatialReferences.Wgs84), horizontalAccuracy: double.NaN, velocity: double.IsNaN(rmc.Speed) ? 0 : rmc.Speed, course: double.IsNaN(rmc.Course) ? 0 : rmc.Course, // Current ArcGIS Runtime limitation that course can't be NaN isLastKnown: false)); } } protected override Task OnStartAsync() => device.OpenAsync(); protected override Task OnStopAsync() => device.CloseAsync(); } } ``` -------------------------------- ### Socket Connection and Data Reception Management Source: https://github.com/dotmorten/nmeaparser/blob/main/docs/concepts/ntrip.md Internal methods for managing socket lifecycle, including connection, background data reception, and cleanup. ```csharp sckt.Send(data); return sckt; } public void Connect(string strName) { if (sckt != null) throw new Exception("Connection already open"); sckt = Request(strName); connected = true; runningTask = Task.Run(ReceiveThread); } private bool connected; private Task runningTask; private async Task ReceiveThread() { byte[] buffer = new byte[65536]; while (connected) { int count = sckt.Receive(buffer); if(count > 0) { DataReceived?.Invoke(this, buffer.Take(count).ToArray()); } await Task.Delay(10); } sckt.Shutdown(SocketShutdown.Both); sckt.Close(); sckt = null; } public Task CloseAsync() { if (runningTask != null) { connected = false; var t = runningTask; runningTask = null; return t; } return Task.CompletedTask; } public void Dispose() { _ = CloseAsync(); } public event EventHandler DataReceived; } } ``` -------------------------------- ### Define NTRIP Source Types Source: https://github.com/dotmorten/nmeaparser/blob/main/docs/concepts/ntrip.md Defines abstract and concrete classes for representing NTRIP casters and streams, including properties for network details, location, and data format support. ```cs using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Net; using System.Net.Sockets; using System.Threading.Tasks; namespace Ntrip { public abstract class NtripSource { } public class Caster : NtripSource { public IPAddress Address { get; set; } public int Port { get; set; } public string Identifier { get; set; } public string Operator { get; set; } public bool SupportsNmea { get; set; } public string CountryCode { get; set; } public double Latitude { get; set; } public double Longitude { get; set; } public IPAddress FallbackAddress { get; set; } } public class NtripStream : NtripSource { public string Mountpoint { get; set; } public string Identifier { get; set; } public string Format { get; set; } public string FormatDetails { get; set; } public Carrier Carrier { get; set; } public string Network { get; set; } public string CountryCode { get; set; } public double Latitude { get; set; } public double Longitude { get; set; } public bool SupportsNmea { get; set; } } public enum Carrier : int { No = 0, L1 = 1, L1L2 = 2 } } ``` -------------------------------- ### Define a Custom NMEA Message Source: https://github.com/dotmorten/nmeaparser/blob/main/docs/concepts/CustomMessages.md Create a class inheriting from NmeaMessage and decorate it with the NmeaMessageType attribute. The constructor must accept the type string and a string array of parameters. ```cs [NmeaMessageType("PTEST")] public class CustomMessage : NmeaMessage { public CustomMessage(string type, string[] parameters) : base(type, parameters) { Value = parameters[0]; } public string Value { get; } } ``` -------------------------------- ### Connect to Bluetooth GPS on Android Source: https://context7.com/dotmorten/nmeaparser/llms.txt This C# code snippet demonstrates how to discover paired Bluetooth serial devices, connect to a GPS device, and process received NMEA messages. Ensure the necessary Bluetooth and location permissions are declared in your AndroidManifest.xml. ```csharp // Android-specific Bluetooth device usage using NmeaParser; using NmeaParser.Messages; using Android.Bluetooth; using Android.Content; public class GpsService { private BluetoothDevice? _nmeaDevice; public async Task ConnectToBluetoothGps(Context context) { // Get list of paired Bluetooth devices that support serial communication var serialDevices = NmeaParser.BluetoothDevice.GetBluetoothSerialDevices(context).ToList(); Console.WriteLine($"Found {serialDevices.Count} serial Bluetooth devices:"); foreach (var device in serialDevices) { Console.WriteLine($" - {device.Name} ({device.Address})"); } // Select GPS device (usually named "GPS", "BT-GPS", etc.) var gpsDevice = serialDevices.FirstOrDefault(d => d.Name?.Contains("GPS", StringComparison.OrdinalIgnoreCase) == true); if (gpsDevice == null) { Console.WriteLine("No GPS device found"); return; } // Create NMEA device _nmeaDevice = new NmeaParser.BluetoothDevice(gpsDevice, context); _nmeaDevice.MessageReceived += (sender, args) => { if (args.Message is Gga gga && gga.Quality != Gga.FixQuality.Invalid) { Console.WriteLine($"Bluetooth GPS: {gga.Latitude:F6}, {gga.Longitude:F6}"); Console.WriteLine($"Fix Quality: {gga.Quality}, Satellites: {gga.NumberOfSatellites}"); } }; _nmeaDevice.DeviceDisconnected += (sender, ex) => { Console.WriteLine($"Bluetooth connection lost: {ex.Message}"); }; // Connect to device await _nmeaDevice.OpenAsync(); Console.WriteLine($"Connected to {gpsDevice.Name}"); } public async Task Disconnect() { if (_nmeaDevice != null) { await _nmeaDevice.CloseAsync(); _nmeaDevice.Dispose(); _nmeaDevice = null; } } } // Required AndroidManifest.xml permissions: // // // ``` -------------------------------- ### Implement Multi-Sentence NMEA Messages Source: https://github.com/dotmorten/nmeaparser/blob/main/docs/concepts/CustomMessages.md Subclass NmeaMultiSentenceMessage and implement IMultiSentenceMessage to handle messages split across multiple sentences. Parsing logic should be placed in the ParseSentences method. ```cs [NmeaMessageType("PTST2")] private class CustomMultiMessage : NmeaMultiSentenceMessage, IMultiSentenceMessage { public CustomMultiMessage(string type, string[] parameters) : base(type, parameters) { } public string Id { get; private set; } public List Values { get; } = new List(); // Set index in the message where the total count is: protected override int MessageCountIndex => 0; // Set index in the message where the message number is: protected override int MessageNumberIndex => 1; protected override bool ParseSentences(Talker talkerType, string[] message) { // Ensure this message matches the previous message. // Use any indicator to detect message difference, so you can error out and avoid // appending the wrong message if (Id == null) Id = message[2]; //First time it's not set else if (Id != message[2]) return false; Values.AddRange(message.Skip(3)); return true; } } ``` -------------------------------- ### Parse NMEA Sentences Statically Source: https://context7.com/dotmorten/nmeaparser/llms.txt Uses NmeaMessage.Parse to convert raw NMEA strings into strongly-typed objects with automatic checksum validation. ```csharp using NmeaParser.Messages; // Parse a GGA (GPS Fix Data) message string ggaInput = "$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,47.0,M,,*47"; var ggaMessage = NmeaMessage.Parse(ggaInput); if (ggaMessage is Gga gga) { Console.WriteLine($"Time: {gga.FixTime}"); Console.WriteLine($"Latitude: {gga.Latitude}"); Console.WriteLine($"Longitude: {gga.Longitude}"); Console.WriteLine($"Quality: {gga.Quality}"); // FixQuality enum (Invalid, GpsFix, DgpsFix, Rtk, etc.) Console.WriteLine($"Satellites: {gga.NumberOfSatellites}"); Console.WriteLine($"HDOP: {gga.Hdop}"); Console.WriteLine($"Altitude: {gga.Altitude} {gga.AltitudeUnits}"); Console.WriteLine($"Geoidal Separation: {gga.GeoidalSeparation}"); } // Parse an RMC (Recommended Minimum) message string rmcInput = "$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A"; var rmcMessage = NmeaMessage.Parse(rmcInput); if (rmcMessage is Rmc rmc) { Console.WriteLine($"Active: {rmc.Active}"); Console.WriteLine($"Fix Time: {rmc.FixTime}"); Console.WriteLine($"Speed: {rmc.Speed} knots"); Console.WriteLine($"Course: {rmc.Course}°"); Console.WriteLine($"Magnetic Variation: {rmc.MagneticVariation}"); } // Parse with checksum validation disabled var msgIgnoreChecksum = NmeaMessage.Parse("$GPGGA,123519,...*00", ignoreChecksum: true); // Get message metadata Console.WriteLine($"Message Type: {ggaMessage.MessageType}"); // "GPGGA" Console.WriteLine($"Talker ID: {ggaMessage.TalkerId}"); // GlobalPositioningSystem Console.WriteLine($"Is Proprietary: {ggaMessage.IsProprietary}"); // false Console.WriteLine($"Checksum: {ggaMessage.Checksum:X2}"); ``` -------------------------------- ### Define and Register Custom NMEA Message Source: https://context7.com/dotmorten/nmeaparser/llms.txt Define a custom NMEA message class by inheriting from NmeaMessage and applying the NmeaMessageType attribute. Register custom messages by assembly or individual types. Use 'replace: true' to override existing types. ```csharp using NmeaParser.Messages; using System.Reflection; // Define a custom message class [NmeaMessageType("PTEST")] public class CustomTestMessage : NmeaMessage { public CustomTestMessage(string type, string[] parameters) : base(type, parameters) { if (parameters.Length >= 3) { DeviceId = parameters[0]; Temperature = double.TryParse(parameters[1], out var temp) ? temp : double.NaN; Status = parameters[2]; } } public string DeviceId { get; } = string.Empty; public double Temperature { get; } = double.NaN; public string Status { get; } = string.Empty; } // Define a custom multi-sentence message [NmeaMessageType("PDATA")] public class CustomDataMessage : NmeaMultiSentenceMessage { private readonly List _values = new(); public CustomDataMessage(string type, string[] parameters) : base(type, parameters) { } protected override int MessageCountIndex => 0; // Total message count at index 0 protected override int MessageNumberIndex => 1; // Current message number at index 1 protected override bool ParseSentences(Talker talkerType, string[] message) { // Parse data values from index 2 onwards for (int i = 2; i < message.Length; i++) { if (double.TryParse(message[i], out var value)) _values.Add(value); } return true; } public IReadOnlyList Values => _values; } // Register custom messages NmeaMessage.RegisterAssembly(typeof(CustomTestMessage).Assembly); // Or register individual types: NmeaMessage.RegisterNmeaMessage(typeof(CustomTestMessage).GetTypeInfo()); NmeaMessage.RegisterNmeaMessage(typeof(CustomDataMessage).GetTypeInfo()); // Replace existing message type NmeaMessage.RegisterNmeaMessage(typeof(CustomTestMessage).GetTypeInfo(), replace: true); ``` -------------------------------- ### Parse Multi-Sentence Messages Source: https://github.com/dotmorten/nmeaparser/blob/main/docs/concepts/CustomMessages.md Register the multi-sentence message type and pass the previous message instance to the Parse method to append subsequent sentences. ```cs NmeaMessage.RegisterNmeaMessage(typeof(CustomMultiMessage).GetTypeInfo()); var input1 = "$PTST2,2,1,123,A,B,C,D*2A"; var input2 = "$PTST2,2,2,123,E,F,G,H*21"; var msg1 = NmeaMessage.Parse(input1); var msg2 = NmeaMessage.Parse(input2, msg1 as IMultiSentenceMessage); ``` -------------------------------- ### Parse multi-sentence GSV messages Source: https://context7.com/dotmorten/nmeaparser/llms.txt Parse and merge multiple GSV sentences to retrieve complete satellite information. Ensure the previous message is passed to NmeaMessage.Parse to correctly assemble multi-sentence data. ```csharp using NmeaParser.Messages; // Parse multi-sentence GSV message string gsv1 = "$GPGSV,3,1,11,03,03,111,00,04,15,270,00,06,01,010,00,13,06,292,00*74"; string gsv2 = "$GPGSV,3,2,11,14,25,170,00,16,57,208,39,18,67,296,40,19,40,246,00*74"; string gsv3 = "$GPGSV,3,3,11,22,42,067,42,24,14,311,43,27,05,244,00*4D"; // Parse first sentence var msg1 = NmeaMessage.Parse(gsv1); // Parse subsequent sentences, passing previous message for merging var msg2 = NmeaMessage.Parse(gsv2, msg1 as IMultiSentenceMessage); var msg3 = NmeaMessage.Parse(gsv3, msg2 as IMultiSentenceMessage); if (msg3 is Gsv gsv && gsv.IsComplete) { Console.WriteLine($"Total satellites in view: {gsv.SatellitesInView}"); Console.WriteLine($"GNSS Signal ID: {gsv.GnssSignalId}"); foreach (var satellite in gsv.SVs) { Console.WriteLine($"Satellite PRN {satellite.Id}:"); Console.WriteLine($" System: {satellite.System}"); // Gps, Glonass, Galileo, Waas Console.WriteLine($" Talker: {satellite.TalkerId}"); // GlobalPositioningSystem, GlonassReceiver, etc. Console.WriteLine($" Elevation: {satellite.Elevation}°"); Console.WriteLine($" Azimuth: {satellite.Azimuth}°"); Console.WriteLine($" SNR: {satellite.SignalToNoiseRatio} dB"); Console.WriteLine($" Signal ID: {satellite.GnssSignalId}"); } // Iterate directly over satellites foreach (var sv in gsv) { if (sv.SignalToNoiseRatio > 30) { Console.WriteLine($"Strong signal from {sv}"); } } } ``` -------------------------------- ### Parse Custom NMEA Messages Source: https://github.com/dotmorten/nmeaparser/blob/main/docs/concepts/CustomMessages.md Use the NmeaMessage.Parse method to process raw NMEA strings into the custom message object. ```cs var input = "$PTEST,TEST*7C"; var msg = NmeaMessage.Parse(input); ``` -------------------------------- ### Monitor GNSS location and state Source: https://context7.com/dotmorten/nmeaparser/llms.txt Use GnssMonitor to aggregate NMEA messages into a unified interface. Requires an initialized NmeaDevice and optional SynchronizationContext for UI thread updates. ```csharp using NmeaParser; using NmeaParser.Gnss; using NmeaParser.Messages; using System.ComponentModel; // Create device and monitor var device = new NmeaFileDevice("gps_log.nmea"); var monitor = new GnssMonitor(device); // Set synchronization context for UI thread notifications (optional) monitor.SynchronizationContext = SynchronizationContext.Current; // Subscribe to location changes monitor.LocationChanged += (sender, e) => { Console.WriteLine($"New location: {monitor.Latitude:F6}, {monitor.Longitude:F6}"); Console.WriteLine($" Altitude: {monitor.Altitude:F1}m (Geoid: {monitor.GeoidHeight:F1}m)"); Console.WriteLine($" Speed: {monitor.Speed:F1} knots, Course: {monitor.Course:F1}°"); Console.WriteLine($" HDOP: {monitor.Hdop:F2}, PDOP: {monitor.Pdop:F2}, VDOP: {monitor.Vdop:F2}"); Console.WriteLine($" Horizontal Error: {monitor.HorizontalError:F2}m"); Console.WriteLine($" Vertical Error: {monitor.VerticalError:F2}m"); Console.WriteLine($" Fix Quality: {monitor.FixQuality}"); Console.WriteLine($" Datum: {monitor.Datum}"); }; // Subscribe to fix lost events monitor.LocationLost += (sender, e) => { Console.WriteLine("GPS fix lost!"); Console.WriteLine($"Last known position: {monitor.Latitude:F6}, {monitor.Longitude:F6}"); }; // Subscribe to property changes for fine-grained updates monitor.PropertyChanged += (sender, e) => { switch (e.PropertyName) { case nameof(GnssMonitor.IsFixValid): Console.WriteLine($"Fix valid: {monitor.IsFixValid}"); break; case nameof(GnssMonitor.SatellitesInView): Console.WriteLine($"Satellites in view: {monitor.SatellitesInView}"); break; case nameof(GnssMonitor.Satellites): Console.WriteLine("Satellite list updated:"); foreach (var sat in monitor.Satellites) { Console.WriteLine($" {sat}: El={sat.Elevation}°, Az={sat.Azimuth}°, SNR={sat.SignalToNoiseRatio}"); } break; } }; // Open device to start monitoring await device.OpenAsync(); // Access current state at any time if (monitor.IsFixValid) { Console.WriteLine($"Current position: {monitor.Latitude}, {monitor.Longitude}"); } // Access raw NMEA messages foreach (var msg in monitor.AllMessages) { Console.WriteLine($"Message {msg.Key}: {msg.Value}"); } // Access specific message types if (monitor.Gga != null) Console.WriteLine($"GGA: {monitor.Gga.NumberOfSatellites} satellites"); if (monitor.Rmc != null) Console.WriteLine($"RMC: {monitor.Rmc.FixTime}"); if (monitor.Gsa != null) Console.WriteLine($"GSA: Fix mode {monitor.Gsa}"); ```