diff --git a/BaseGpsClient.cs b/BaseGpsClient.cs new file mode 100644 index 0000000..7d251ce --- /dev/null +++ b/BaseGpsClient.cs @@ -0,0 +1,66 @@ +using System; +using GpsClient2.Model; + +namespace GpsClient2 { + public abstract class BaseGpsClient { + #region Properties + + public GpsType GpsType { get; } + public bool IsRunning { get; set; } + + protected BaseGpsInfo GpsInfo { get; set; } + + #endregion + + #region Event handlers + + public event EventHandler GpsCallbackEvent; + public event EventHandler RawGpsCallbackEvent; + public event EventHandler GpsStatusEvent; + + #endregion + + #region Constructors + + protected BaseGpsClient(GpsType gpsType, BaseGpsInfo gpsInfo) { + GpsType = gpsType; + GpsInfo = gpsInfo; + } + + #endregion + + #region Connect and Disconnect + + public abstract bool Connect(); + + public abstract bool Disconnect(); + + #endregion + + #region Events Triggers + + protected virtual void OnGpsDataReceived(GpsDataEventArgs e) { + if (GpsInfo.CoordinateSystem == GpsCoordinateSystem.Lambert72) { + var x = 0.0d; + var y = 0.0d; + var h = 0.0d; + CoordinateConverterUtilities.GeoETRS89ToLambert72(e.Latitude, e.Longitude, 0, ref x, ref y, ref h); + e.CoordinateSystem = GpsCoordinateSystem.Lambert72; + e.Latitude = x; + e.Longitude = y; + } + + GpsCallbackEvent?.Invoke(this, e); + } + + protected virtual void OnRawGpsDataReceived(string e) { + RawGpsCallbackEvent?.Invoke(this, e); + } + + protected virtual void OnGpsStatusChanged(GpsStatus e) { + GpsStatusEvent?.Invoke(this, e); + } + + #endregion + } +} diff --git a/ComPortGpsClient.cs b/ComPortGpsClient.cs new file mode 100644 index 0000000..c868731 --- /dev/null +++ b/ComPortGpsClient.cs @@ -0,0 +1,89 @@ +using GpsClient2.Model; +using GpsClient2.NmeaMessages; +using GpsClient2.Exceptions; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO.Ports; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace GpsClient2 { + public class ComPortGpsClient : BaseGpsClient { + #region Private Properties + + private readonly NmeaParser _parser = new NmeaParser(); + private SerialPort _serialPort; + + private DateTime? _previousReadTime; + + #endregion + + #region Constructors + + public ComPortGpsClient(ComPortInfo connectionData) : base(GpsType.ComPort, connectionData) { + } + + public ComPortGpsClient(BaseGpsInfo connectionData) : base(GpsType.ComPort, connectionData) { + } + + #endregion + + #region Connect and Disconnect + + public override bool Connect() { + var data = (ComPortInfo)GpsInfo; + + IsRunning = true; + OnGpsStatusChanged(GpsStatus.Connecting); + _serialPort = new SerialPort(data.ComPort, 9600, Parity.None, 8, StopBits.One); + + // Attach a method to be called when there + // is data waiting in the port's buffer + _serialPort.DataReceived += port_DataReceived; + try { + // Begin communications + _serialPort.Open(); + + OnGpsStatusChanged(GpsStatus.Connected); + // Enter an application loop to keep this thread alive + while (_serialPort.IsOpen) { + Thread.Sleep(data.ReadFrequenty); + } + } catch { + Disconnect(); + throw; + } + + return true; + } + + public override bool Disconnect() { + _serialPort.Close(); + IsRunning = false; + OnGpsStatusChanged(GpsStatus.Disabled); + return true; + } + + #endregion + + #region Location Callbacks + + private void port_DataReceived(object sender, SerialDataReceivedEventArgs e) { + try { + var readString = _serialPort.ReadExisting(); + OnRawGpsDataReceived(readString); + var result = _parser.Parse(readString); + if (typeof(GprmcMessage) != result.GetType()) return; + if (_previousReadTime != null && GpsInfo.ReadFrequenty != 0 && ((GprmcMessage)result).UpdateDate.Subtract(new TimeSpan(0, 0, 0, 0, GpsInfo.ReadFrequenty)) <= _previousReadTime) return; + OnGpsDataReceived(new GpsDataEventArgs((GprmcMessage)result)); + } catch (UnknownTypeException ex) { + Console.WriteLine(ex.Message); + } + } + + #endregion + } +} \ No newline at end of file diff --git a/CoordinateConverterUtilities.cs b/CoordinateConverterUtilities.cs new file mode 100644 index 0000000..0a934c4 --- /dev/null +++ b/CoordinateConverterUtilities.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace GpsClient2 { + internal class CoordinateConverterUtilities { +#if WIN64 + private const string DllImport = @"plugins/ETRS89_LAMBERT_UTM_64bits.dll"; +#else + private const string DllImport = @"plugins/ETRS89_LAMBERT_UTM_32bits.dll"; +#endif + + #region Coordinate conversion functions using NGI DLL + + //Import the dll with the functions to calculate lambert coordinates + [DllImport(DllImport, SetLastError = true, CharSet = CharSet.Auto)] + public static extern int GeoETRS89ToLambert72(double Xi, double Yi, double Zi, ref double xo, ref double yo, ref double Ho); + + [DllImport(DllImport, SetLastError = true, CharSet = CharSet.Auto)] + public static extern int Lambert72ToLambert08(double Xi, double Yi, double Zi, ref double xo, ref double yo, ref double Ho); + + [DllImport(DllImport, SetLastError = true, CharSet = CharSet.Auto)] + public static extern int Lambert72ToGeoETRS89(double Xi, double Yi, double Zi, ref double xo, ref double yo, ref double Ho); + + #endregion + } +} diff --git a/Enums.cs b/Enums.cs new file mode 100644 index 0000000..e01d9b9 --- /dev/null +++ b/Enums.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GpsClient2 { + public enum CoordinateType { + Latitude, + Longitude + } + + public enum GpsFixQuality { + Invalid = 0, + /// + /// GPS fix (SPS) + /// + GpsFix = 1, + /// + /// DGPS fix + /// + DgpsFix = 2, + /// + /// PPS fix + /// + PpsFix = 3, + /// + /// Real Time Kinematic + /// + Rtk = 4, + /// + /// Float RTK + /// + FloatRtk = 5, + /// + /// estimated (dead reckoning) (2.3 feature) + /// + Estimated = 6, + /// + /// Manual input mode + /// + ManualInput = 7, + /// + /// Simulation mode + /// + Simulation = 8 + } + + public enum SatelliteFixType { + NoFix, + TwoDFix, + ThreeDFix + } + + public enum FileType { + Nmea, + Gpsd, + LatitudeLongitude, + } + + public enum GpsCoordinateSystem { + Lambert72, + GeoEtrs89, + } + + public enum GpsStatus { + Disabled, + Connecting, + Connected + } + + public enum GpsType { + File, + ComPort, + Gpsd, + WindowsLocationApi + } +} diff --git a/Exceptions/UnknownTypeException.cs b/Exceptions/UnknownTypeException.cs new file mode 100644 index 0000000..9d03135 --- /dev/null +++ b/Exceptions/UnknownTypeException.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GpsClient2.Exceptions { + public class UnknownTypeException : Exception { + public UnknownTypeException(Type type) : base($"Unknown Class Type: {type}") { + + } + + public UnknownTypeException(string type) : base($"Unknown Class Type: {type}") { + + } + + public UnknownTypeException() : base("Unknown Class Type") { + + } + } +} diff --git a/GpsClient2.csproj b/GpsClient2.csproj new file mode 100644 index 0000000..314bae5 --- /dev/null +++ b/GpsClient2.csproj @@ -0,0 +1,76 @@ + + + + + Debug + AnyCPU + {C62E7A96-F279-4390-A1AC-8FACDD9E8C14} + Library + Properties + GpsClient2 + GpsClient2 + v4.7.2 + 512 + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Always + + + Always + + + + \ No newline at end of file diff --git a/GpsClient2.sln b/GpsClient2.sln new file mode 100644 index 0000000..0174876 --- /dev/null +++ b/GpsClient2.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.6.33801.468 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GpsClient2", "GpsClient2.csproj", "{C62E7A96-F279-4390-A1AC-8FACDD9E8C14}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C62E7A96-F279-4390-A1AC-8FACDD9E8C14}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C62E7A96-F279-4390-A1AC-8FACDD9E8C14}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C62E7A96-F279-4390-A1AC-8FACDD9E8C14}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C62E7A96-F279-4390-A1AC-8FACDD9E8C14}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {85C8CFC9-7CA5-4D17-B598-653F56226D85} + EndGlobalSection +EndGlobal diff --git a/GpsDataEventArgs.cs b/GpsDataEventArgs.cs new file mode 100644 index 0000000..17c55da --- /dev/null +++ b/GpsDataEventArgs.cs @@ -0,0 +1,47 @@ +using System; +using GpsClient2.Model; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Device.Location; +using GpsClient2.NmeaMessages; + +namespace GpsClient2 { + public class GpsDataEventArgs : EventArgs { + public GpsCoordinateSystem CoordinateSystem { get; set; } = GpsCoordinateSystem.GeoEtrs89; + + public double Latitude { get; set; } + public double Longitude { get; set; } + + public double Speed { get; set; } + + public GpsDataEventArgs(GpsLocation gpsLocation) { + Latitude = gpsLocation.Latitude; + Longitude = gpsLocation.Longitude; + Speed = gpsLocation.Speed; + } + + public GpsDataEventArgs(GprmcMessage gpsLocation) { + Latitude = gpsLocation.Latitude; + Longitude = gpsLocation.Longitude; + Speed = gpsLocation.Speed; + } + + public GpsDataEventArgs(GeoCoordinate gpsLocation) { + Latitude = gpsLocation.Latitude; + Longitude = gpsLocation.Longitude; + Speed = gpsLocation.Speed; + } + + public GpsDataEventArgs(double latitude, double longitude, double speed = 0.0d) { + Latitude = latitude; + Longitude = longitude; + Speed = speed; + } + + public override string ToString() { + return $"Latitude: {Latitude} - Longitude: {Longitude} - Speed: {Speed}"; + } + } +} diff --git a/Model/BaseGpsInfo.cs b/Model/BaseGpsInfo.cs new file mode 100644 index 0000000..bd964d1 --- /dev/null +++ b/Model/BaseGpsInfo.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GpsClient2.Model { + public abstract class BaseGpsInfo { + public GpsCoordinateSystem CoordinateSystem { get; set; } = GpsCoordinateSystem.GeoEtrs89; + + public int ReadFrequenty { get; set; } + } +} diff --git a/Model/ComPortInfo.cs b/Model/ComPortInfo.cs new file mode 100644 index 0000000..26737c6 --- /dev/null +++ b/Model/ComPortInfo.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GpsClient2.Model { + public class ComPortInfo : BaseGpsInfo { + public string ComPort { get; set; } = "ComPort1"; + + public ComPortInfo() { + ReadFrequenty = 1000; + } + + public ComPortInfo(string comPort, int readFrequenty = 1000) { + ComPort = comPort; + ReadFrequenty = readFrequenty; + } + } +} diff --git a/Model/GpsLocation.cs b/Model/GpsLocation.cs new file mode 100644 index 0000000..8d2f90f --- /dev/null +++ b/Model/GpsLocation.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; +using System.Xml.Linq; + +namespace GpsClient2.Model { + [DataContract] + public class GpsLocation { + [DataMember(Name = "tag")] + public string Tag { get; set; } + + [DataMember(Name = "device")] + public string Device { get; set; } + + [DataMember(Name = "mode")] + public int Mode { get; set; } + + [DataMember(Name = "time")] + public DateTime Time { get; set; } + + [DataMember(Name = "ept")] + public float Ept { get; set; } + + [DataMember(Name = "lat")] + public double Latitude { get; set; } + + [DataMember(Name = "lon")] + public double Longitude { get; set; } + + [DataMember(Name = "track")] + public float Track { get; set; } + + [DataMember(Name = "speed")] + public float SpeedKnots { get; set; } + + public double Speed => SpeedKnots * 1.852; + + public override string ToString() { + return $"Tag: {Tag} - Device: {Device} - Mode: {Mode} - Time: {Time} - Latitude: {Latitude} - Longitude: {Longitude} - Track: {Track} - Speed: {Speed}"; + } + } +} diff --git a/Model/WindowsLocationApiInfo.cs b/Model/WindowsLocationApiInfo.cs new file mode 100644 index 0000000..e7bc541 --- /dev/null +++ b/Model/WindowsLocationApiInfo.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GpsClient2.Model { + public class WindowsLocationApiInfo : BaseGpsInfo { + public int Timeout { get; set; } = 1000; + + public WindowsLocationApiInfo() { + ReadFrequenty = 0; + } + } +} diff --git a/NmeaConstants.cs b/NmeaConstants.cs new file mode 100644 index 0000000..b75b695 --- /dev/null +++ b/NmeaConstants.cs @@ -0,0 +1,36 @@ +using GpsClient2.Exceptions; +using GpsClient2.NmeaMessages; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GpsClient2 { + public static class NmeaConstants { + private static readonly Dictionary TypeDictionary = new Dictionary + { + {"GPGGA", typeof(GpggaMessage)}, + {"GPRMC", typeof(GprmcMessage)}, + {"GPVTG", typeof(GpvtgMessage)}, + {"GPGSA", typeof(GpgsaMessage)} + }; + + /// + /// Returns the correct class type of the message. + /// + /// The type name given. + /// The class type. + /// Given if the type is unkown. + public static Type GetClassType(string typeName) { + Type result; + TypeDictionary.TryGetValue(typeName, out result); + + if (result == null) { + throw new UnknownTypeException(); + } + + return result; + } + } +} diff --git a/NmeaMessages/GpggaMessage.cs b/NmeaMessages/GpggaMessage.cs new file mode 100644 index 0000000..33662bc --- /dev/null +++ b/NmeaMessages/GpggaMessage.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GpsClient2.NmeaMessages { + public class GpggaMessage : NmeaMessage { + #region Description + + // $GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47 + + // Where: + // GGA Global Positioning System Fix Data + // 123519 Fix taken at 12:35:19 UTC + // 4807.038,N Latitude 48 deg 07.038' N + // 01131.000,E Longitude 11 deg 31.000' E + // 1 Fix quality: 0 = invalid + // 1 = GPS fix(SPS) + // 2 = DGPS fix + // 3 = PPS fix + // 4 = Real Time Kinematic + // 5 = Float RTK + // 6 = estimated(dead reckoning) (2.3 feature) + // 7 = Manual input mode + // 8 = Simulation mode + // 08 Number of satellites being tracked + // 0.9 Horizontal dilution of position + // 545.4, M Altitude, Meters, above mean sea level + // 46.9, M Height of geoid(mean sea level) above WGS84 + // ellipsoid + // (empty field) time in seconds since last DGPS update + // (empty field) DGPS station ID number + // *47 the checksum data, always begins with* + + #endregion + + #region Properties + + /// + /// Fix taken + /// + public TimeSpan FixTime { get; set; } + + public double Latitude { get; set; } + + public double Longitude { get; set; } + + public GpsFixQuality FixQuality { get; set; } + + public int NumberOfSatellites { get; set; } + + /// + /// Horizontal dilution of position + /// + public float Hdop { get; set; } + + /// + /// Altitude, Meters, above mean sea level + /// + public float Altitude { get; set; } + + /// + /// Altitude units ('M' for Meters) + /// + public string AltitudeUnits { get; set; } + + /// + /// Height of geoid (mean sea level) above WGS84 + /// + public float HeightOfGeoId { get; set; } + + public string HeightOfGeoIdUnits { get; set; } + + /// + /// Time in seconds since last DGPS update + /// + public int TimeSpanSinceDgpsUpdate { get; set; } + + /// + /// DGPS station ID number + /// + public int? DgpsStationId { get; set; } + + #endregion + + #region Message parsing + + public override void Parse(string[] messageParts) { + if (messageParts == null || messageParts.Length < 14) { + throw new ArgumentException("Invalid GPGGA message"); + } + FixTime = messageParts[1].ToTimeSpan(); + Latitude = messageParts[2].ToCoordinates(messageParts[3], CoordinateType.Latitude); + Longitude = messageParts[4].ToCoordinates(messageParts[5], CoordinateType.Longitude); + FixQuality = (GpsFixQuality)Enum.Parse(typeof(GpsFixQuality), messageParts[6]); + NumberOfSatellites = messageParts[7].ToInteger(); + Hdop = messageParts[8].ToFloat(); + Altitude = messageParts[9].ToFloat(); + AltitudeUnits = messageParts[10]; + HeightOfGeoId = messageParts[11].ToFloat(); + HeightOfGeoIdUnits = messageParts[12]; + TimeSpanSinceDgpsUpdate = messageParts[13].ToInteger(); + DgpsStationId = messageParts[14].ToInteger(); + } + + #endregion + + public override string ToString() { + return $"Latitude {Latitude} - Longitude {Longitude} - Hoogte {Altitude}"; + } + } +} diff --git a/NmeaMessages/GpgsaMessage.cs b/NmeaMessages/GpgsaMessage.cs new file mode 100644 index 0000000..a1f2a5c --- /dev/null +++ b/NmeaMessages/GpgsaMessage.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GpsClient2.NmeaMessages { + public class GpgsaMessage : NmeaMessage { + #region Description + + // $GPGSA,A,3,04,05,,09,12,,,24,,,,,2.5,1.3,2.1*39 + + // Where: + // GSA Satellite status + // A Auto selection of 2D or 3D fix(M = manual) + // 3 3D fix - values include: 1 = no fix + // 2 = 2D fix + // 3 = 3D fix + // 04,05... PRNs of satellites used for fix(space for 12) + // 2.5 PDOP(dilution of precision) + // 1.3 Horizontal dilution of precision(HDOP) + // 2.1 Vertical dilution of precision(VDOP) + // *39 the checksum data, always begins with* + + #endregion + + #region Properties + + /// + /// Auto selection of 2D or 3D fix(M = manual) + /// + public bool GpsStatusAuto { get; set; } + + /// + /// 3D fix - values include: 1 = no fix + // 2 = 2D fix + // 3 = 3D fix + /// + public SatelliteFixType SatelliteFix { get; set; } + + /// + /// PRNs of satellites used for fix(space for 12) + /// + public string Pnrs { get; set; } + + /// + /// PDOP(dilution of precision) + /// + public float Pdop { get; set; } + + + /// + /// Horizontal dilution of precision(HDOP) + /// + public float Hdop { get; set; } + + /// + /// Vertical dilution of precision(VDOP) + /// + public float Vdop { get; set; } + + #endregion + + #region Message parsing + + public override void Parse(string[] messageParts) { + if (messageParts == null || messageParts.Length < 9) { + throw new ArgumentException("Invalid GPGSA message"); + } + GpsStatusAuto = messageParts[1].ToBoolean("A"); + SatelliteFix = (SatelliteFixType)Enum.Parse(typeof(SatelliteFixType), messageParts[2]); + for (var i = 0 + 3; i < 12 + 3; i++) { + Pnrs += $"{messageParts[i]},"; + } + + Pdop = messageParts[15].ToFloat(); + Hdop = messageParts[16].ToFloat(); + Vdop = messageParts[17].ToFloat(); + } + + #endregion + + public override string ToString() { + return $"Status {GpsStatusAuto} - Satellite Fix Type {SatelliteFix}"; + } + } +} diff --git a/NmeaMessages/GprmcMessage.cs b/NmeaMessages/GprmcMessage.cs new file mode 100644 index 0000000..e5ac660 --- /dev/null +++ b/NmeaMessages/GprmcMessage.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GpsClient2.NmeaMessages { + public class GprmcMessage : NmeaMessage { + #region Description + + // $GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A + // + // Where: + // RMC Recommended Minimum sentence C + // 123519 Fix taken at 12:35:19 UTC + // A Status A = active or V = Void. + // 4807.038, N Latitude 48 deg 07.038' N + // 01131.000,E Longitude 11 deg 31.000' E + // 022.4 Speed over the ground in knots + // 084.4 Track angle in degrees True + // 230394 Date - 23rd of March 1994 + // 003.1,W Magnetic Variation + // *6A The checksum data, always begins with * + + #endregion + + #region Properties + + public TimeSpan FixTime { get; set; } + + /// + /// Status A = active or V = Void. + /// + public bool IsActive { get; set; } + + public double Latitude { get; set; } + + public double Longitude { get; set; } + + /// + /// Speed over the ground in knots + /// + public float Speed { get; set; } + + /// + /// Track angle in degrees True + /// + public float Course { get; set; } + + /// + /// Date - 23rd of March 1994 + /// + public DateTime UpdateDate { get; set; } + + /// + /// Magnetic Variation + /// + public float MagneticVariation { get; set; } + + /// + /// Magnetic Variation Unit + /// + public string MagneticVariationUnit { get; set; } + + #endregion + + #region Message parsing + + public override void Parse(string[] messageParts) { + if (messageParts == null || messageParts.Length < 11) { + throw new ArgumentException("Invalid GPGGA message"); + } + FixTime = messageParts[1].ToTimeSpan(); + IsActive = messageParts[2].ToBoolean("A"); + Latitude = messageParts[3].ToCoordinates(messageParts[4], CoordinateType.Latitude); + Longitude = messageParts[5].ToCoordinates(messageParts[6], CoordinateType.Longitude); + Speed = messageParts[7].ToFloat(); + Course = messageParts[8].ToFloat(); + UpdateDate = DateTime.ParseExact(messageParts[9], "ddMMyy", CultureInfo.InvariantCulture); + MagneticVariation = messageParts[10].ToFloat(); + MagneticVariationUnit = messageParts[11]; + } + + #endregion + + public override string ToString() { + return $"Latitude {Latitude} - Longitude {Longitude} - Speed {Speed}"; + } + } +} diff --git a/NmeaMessages/GpvtgMessage.cs b/NmeaMessages/GpvtgMessage.cs new file mode 100644 index 0000000..a7bccd5 --- /dev/null +++ b/NmeaMessages/GpvtgMessage.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GpsClient2.NmeaMessages { + public class GpvtgMessage : NmeaMessage { + #region Description + + // $GPVTG,054.7,T,034.4,M,005.5,N,010.2,K*48 + + // where: + // VTG Track made good and ground speed + // 054.7,T True track made good(degrees) + // 034.4,M Magnetic track made good + // 005.5,N Ground speed, knots + // 010.2,K Ground speed, Kilometers per hour + // *48 Checksum + + #endregion + + #region Properties + + /// + /// True track made good(degrees) + /// + public float TrackDegrees { get; set; } + + /// + /// Magnetic track made good + /// + public float MagneticTrack { get; set; } + + /// + /// Ground speed, knots + /// + public float GroundSpeetKnots { get; set; } + + /// + /// Ground speed, Kilometers per hour + /// + public float GroundSpeed { get; set; } + + #endregion + + #region Message parsing + + public override void Parse(string[] messageParts) { + //$GPVTG,054.7,T,034.4,M,005.5,N,010.2,K * 48 + if (messageParts == null || messageParts.Length < 9) { + throw new ArgumentException("Invalid GPVTG message"); + } + TrackDegrees = messageParts[1].ToFloat(); + MagneticTrack = messageParts[3].ToFloat(); + GroundSpeetKnots = messageParts[5].ToFloat(); + GroundSpeed = messageParts[7].ToFloat(); + } + + #endregion + + public override string ToString() { + return $"Speed {GroundSpeed} - Track level {TrackDegrees}"; + } + } +} diff --git a/NmeaMessages/NmeaMessage.cs b/NmeaMessages/NmeaMessage.cs new file mode 100644 index 0000000..9c05623 --- /dev/null +++ b/NmeaMessages/NmeaMessage.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GpsClient2.NmeaMessages { + public abstract class NmeaMessage { + public abstract void Parse(string[] messageParts); + } +} diff --git a/NmeaParser.cs b/NmeaParser.cs new file mode 100644 index 0000000..1ed57d7 --- /dev/null +++ b/NmeaParser.cs @@ -0,0 +1,27 @@ +using GpsClient2.NmeaMessages; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GpsClient2 { + public class NmeaParser { + /// + /// Parses a string to the NmeaMessage class. + /// + /// The nmea string that need to be parsed. + /// Returns an NmeaMessage class. If it cannot parse it will return null. + public NmeaMessage Parse(string message) { + if (!message.StartsWith("$")) { + return null; + } + + var messageParts = message.RemoveAfter("*").Split(','); + var classType = NmeaConstants.GetClassType(messageParts[0].TrimStart('$')); + var newInstance = (NmeaMessage)Activator.CreateInstance(classType); + newInstance.Parse(messageParts); + return newInstance; + } + } +} diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..8822502 --- /dev/null +++ b/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("GpsClient2")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("GpsClient2")] +[assembly: AssemblyCopyright("Copyright © 2023")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("c62e7a96-f279-4390-a1ac-8facdd9e8c14")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/StringExtensions.cs b/StringExtensions.cs new file mode 100644 index 0000000..51b4dfe --- /dev/null +++ b/StringExtensions.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace GpsClient2 { + public static class StringExtensions { + private const string Pattern = @"(\d{2})(\d{2})(\d{2})[.\d{2}]?"; + + public static TimeSpan ToTimeSpan(this string inputString) { + var regexMatch = Regex.Match(inputString, Pattern); + return regexMatch.Groups.Count == 0 ? TimeSpan.Zero : new TimeSpan(0, int.Parse(regexMatch.Groups[1].Value), int.Parse(regexMatch.Groups[2].Value), int.Parse(regexMatch.Groups[3].Value)); + } + + public static double ToCoordinates(this string inputString, string cardinalDirection, CoordinateType coordinateType) { + if (string.IsNullOrEmpty(inputString)) return 0.0d; + + var degreeCharacters = coordinateType == CoordinateType.Latitude ? 2 : 3; + + var degree = inputString.Substring(0, degreeCharacters).ToDouble(); + degree += inputString.Substring(degreeCharacters).ToDouble() / 60; + + if (!double.IsNaN(degree) && (cardinalDirection == "S" || cardinalDirection == "W")) { + degree *= -1; + } + return degree; + } + + public static int ToInteger(this string inputString) { + return !string.IsNullOrEmpty(inputString) ? int.Parse(inputString, CultureInfo.InvariantCulture) : 0; + } + + public static double ToDouble(this string inputString) { + return !string.IsNullOrEmpty(inputString) ? double.Parse(inputString, CultureInfo.InvariantCulture) : double.NaN; + } + + public static float ToFloat(this string inputString) { + return !string.IsNullOrEmpty(inputString) ? float.Parse(inputString, CultureInfo.InvariantCulture) : float.NaN; + } + + public static bool ToBoolean(this string inputString, string validValue) { + return inputString == validValue; + } + + public static string RemoveAfter(this string inputString, string charValue) { + var index = inputString.IndexOf(charValue, StringComparison.Ordinal); + return index > 0 ? inputString.Substring(0, index) : inputString; + } + } +} diff --git a/WindowsLocationApiGpsClient.cs b/WindowsLocationApiGpsClient.cs new file mode 100644 index 0000000..2052659 --- /dev/null +++ b/WindowsLocationApiGpsClient.cs @@ -0,0 +1,85 @@ +using GpsClient2.Model; +using System; +using System.Collections.Generic; +using System.Device.Location; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GpsClient2 { + public class WindowsLocationApiGpsClient : BaseGpsClient { + #region Private Properties + + private GeoCoordinateWatcher _watcher; + + private DateTimeOffset? _previousReadTime; + + #endregion + + #region Constructors + + public WindowsLocationApiGpsClient(WindowsLocationApiInfo connectionData) : base(GpsType.WindowsLocationApi, connectionData) { + } + + public WindowsLocationApiGpsClient(BaseGpsInfo connectionData) : base(GpsType.WindowsLocationApi, connectionData) { + } + + #endregion + + #region Connect and Disconnect + + public override bool Connect() { + var data = (WindowsLocationApiInfo)GpsInfo; + + IsRunning = true; + OnGpsStatusChanged(GpsStatus.Connecting); + + _watcher = new GeoCoordinateWatcher(); + + _watcher.PositionChanged += WatcherOnPositionChanged; + _watcher.StatusChanged += WatcherOnStatusChanged; + + bool result; + try { + result = _watcher.TryStart(false, TimeSpan.FromMilliseconds(data.Timeout)); + } catch { + Disconnect(); + throw; + } + return result; + } + + private void WatcherOnStatusChanged(object sender, GeoPositionStatusChangedEventArgs e) { + switch (e.Status) { + case GeoPositionStatus.Ready: + OnGpsStatusChanged(GpsStatus.Connected); + break; + case GeoPositionStatus.Initializing: + OnGpsStatusChanged(GpsStatus.Connecting); + break; + case GeoPositionStatus.NoData: + OnGpsStatusChanged(GpsStatus.Connecting); + break; + case GeoPositionStatus.Disabled: + OnGpsStatusChanged(GpsStatus.Disabled); + break; + } + } + + private void WatcherOnPositionChanged(object sender, GeoPositionChangedEventArgs e) { + if (_previousReadTime != null && GpsInfo.ReadFrequenty != 0 && e.Position.Timestamp.Subtract(new TimeSpan(0, 0, 0, 0, GpsInfo.ReadFrequenty)) <= _previousReadTime) return; + OnGpsDataReceived(new GpsDataEventArgs(e.Position.Location)); + _previousReadTime = e.Position.Timestamp; + } + + public override bool Disconnect() { + IsRunning = false; + _watcher.Stop(); + _watcher.Dispose(); + OnGpsStatusChanged(GpsStatus.Disabled); + return true; + } + + #endregion + } +} diff --git a/plugins/ETRS89_LAMBERT_UTM_32bits.dll b/plugins/ETRS89_LAMBERT_UTM_32bits.dll new file mode 100644 index 0000000..8b160af Binary files /dev/null and b/plugins/ETRS89_LAMBERT_UTM_32bits.dll differ diff --git a/plugins/ETRS89_LAMBERT_UTM_64bits.dll b/plugins/ETRS89_LAMBERT_UTM_64bits.dll new file mode 100644 index 0000000..e64d9a9 Binary files /dev/null and b/plugins/ETRS89_LAMBERT_UTM_64bits.dll differ