diff --git a/GpsClient.sln b/GpsClient.sln new file mode 100644 index 0000000..5d5159e --- /dev/null +++ b/GpsClient.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31605.320 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GpsClient", "GpsClient\GpsClient.csproj", "{1D1F37BA-9E34-4A02-A27C-717598839024}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GpsTestApp", "GpsTestApp\GpsTestApp.csproj", "{04FF8D42-1A4B-43EB-84DE-A9FE475BBD9B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1D1F37BA-9E34-4A02-A27C-717598839024}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1D1F37BA-9E34-4A02-A27C-717598839024}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1D1F37BA-9E34-4A02-A27C-717598839024}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1D1F37BA-9E34-4A02-A27C-717598839024}.Release|Any CPU.Build.0 = Release|Any CPU + {04FF8D42-1A4B-43EB-84DE-A9FE475BBD9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {04FF8D42-1A4B-43EB-84DE-A9FE475BBD9B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {04FF8D42-1A4B-43EB-84DE-A9FE475BBD9B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {04FF8D42-1A4B-43EB-84DE-A9FE475BBD9B}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {D479033C-89BF-4BD7-8614-609B1F7321E7} + EndGlobalSection +EndGlobal diff --git a/GpsClient/ActiveSatellites.cs b/GpsClient/ActiveSatellites.cs new file mode 100644 index 0000000..4ef2898 --- /dev/null +++ b/GpsClient/ActiveSatellites.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using KollNet.Lib.Models; + +namespace KollNet.Lib { + public static class ActiveSatellites { + public static Dictionary satellites { get; private set; } = new Dictionary(); + + public static void AddOrUpdate(SatelliteView sv) { + if (satellites.ContainsKey(sv.PRN)) { + satellites[sv.PRN] = sv; + } else { + satellites.Add(sv.PRN, sv); + } + } + + public static void Remove(SatelliteView sv) { + if (!satellites.ContainsKey(sv.PRN)) { return; } + satellites.Remove(sv.PRN); + } + + public static void Remove(int id) { + if (!satellites.ContainsKey(id)) { return; } + satellites.Remove(id); + } + + public static void Clear() { + satellites.Clear(); + } + } +} diff --git a/GpsClient/EventArguments/GpsCoordinatesChangedEventArgs.cs b/GpsClient/EventArguments/GpsCoordinatesChangedEventArgs.cs new file mode 100644 index 0000000..c95e68b --- /dev/null +++ b/GpsClient/EventArguments/GpsCoordinatesChangedEventArgs.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace KollNet.Lib.EventArguments { + public class GpsCoordinatesChangedEventArgs : EventArgs { + public GpsCoordinates Coordinates { get; set; } + + public GpsCoordinatesChangedEventArgs() { } + public GpsCoordinatesChangedEventArgs(GpsCoordinates coords) { Coordinates = coords; } + } +} diff --git a/GpsClient/EventArguments/NmeaReceivedEventArgs.cs b/GpsClient/EventArguments/NmeaReceivedEventArgs.cs new file mode 100644 index 0000000..a061a1e --- /dev/null +++ b/GpsClient/EventArguments/NmeaReceivedEventArgs.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace KollNet.Lib.EventArguments { + public class NmeaReceivedEventArgs : EventArgs { + public string NmeaSentence { get; set; } + + public NmeaReceivedEventArgs() { } + public NmeaReceivedEventArgs(string ns) { NmeaSentence = ns; } + } +} diff --git a/GpsClient/EventArguments/SatellitesUpdatedEventArgs.cs b/GpsClient/EventArguments/SatellitesUpdatedEventArgs.cs new file mode 100644 index 0000000..ad4e451 --- /dev/null +++ b/GpsClient/EventArguments/SatellitesUpdatedEventArgs.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using KollNet.Lib.Models; + +namespace KollNet.Lib.EventArguments { + public class SatellitesUpdatedEventArgs : EventArgs { + public List ActiveSatellites { get; set; } + + public SatellitesUpdatedEventArgs() { } + public SatellitesUpdatedEventArgs(List satellites) { ActiveSatellites = satellites; } + } +} diff --git a/GpsClient/GpsClient.cs b/GpsClient/GpsClient.cs new file mode 100644 index 0000000..890b47e --- /dev/null +++ b/GpsClient/GpsClient.cs @@ -0,0 +1,293 @@ +using System; +using System.Collections.Generic; +using System.IO.Ports; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using KollNet.Lib.EventArguments; +using KollNet.Lib.Models; + +namespace KollNet.Lib { + public static class GpsClient { + private static SerialPort gpsPort; + private static Timer timer1; + private static string carryOver; + private static bool movingCapable = false; + private static int roundInt = 6; + private static bool gpsOffsetSet = false; + + #region "Public Events" + internal static void OnSatellitesUpdated(SatellitesUpdatedEventArgs e) { SatellitesUpdated?.Invoke(null, e); } + public static event EventHandler SatellitesUpdated; + internal static void OnPositionChanged(GpsCoordinatesChangedEventArgs e) { PositionChanged?.Invoke(null, e); } + public static event EventHandler PositionChanged; + internal static void OnConnected() { Connected?.Invoke(null, EventArgs.Empty); } + public static event EventHandler Connected; + internal static void OnDisconnected() { Disconnected?.Invoke(null, EventArgs.Empty); } + public static event EventHandler Disconnected; + internal static void OnNmeaReceived(NmeaReceivedEventArgs e) { + Console.WriteLine("NMEA: " + e.NmeaSentence); + LastPositionTime = DateTime.Now; + NmeaReceived?.Invoke(null, e); + } + public static event EventHandler NmeaReceived; + #endregion + + #region "Public Properties" + public static bool SatelliteStats { get; set; } = true; + public static bool IsConnected { get; private set; } + public static DateTime LastPositionTime { get; private set; } + public static TimeSpan GpsTimeOffset { get; private set; } + public static string ComPort { + get { + return gpsPort?.PortName ?? ""; + } set { + if (gpsPort?.IsOpen ?? false) { + throw new Exception("GPS is currently connected and port assignment cannot be changed"); + } else { + InitGps(value); + } + } + } + public static int BaudRate { + get { return gpsPort?.BaudRate ?? 9600; } + set { if (gpsPort != null) gpsPort.BaudRate = value; } + } + #endregion + + + #region "GPS Connect / Disconnect Methods" + public static string FindGps(bool autoConfig=true) { + int[] bauds = { 9600, 4800 }; + + foreach(string comPort in SerialPort.GetPortNames()) { + foreach(int baud in bauds) { + if (IsGpsPort(comPort, baud)) { + if (autoConfig) { ComPort = ComPort; BaudRate = baud; } + return comPort + ":" + baud; + } + } + } + + return null; + } + public static bool Connect() { + if (gpsPort == null) { throw new Exception("GPS Port was not set prior to calling Connect() with no arguments"); } + + InitGps(ComPort, BaudRate); + gpsPort.ReadTimeout = 10000; + gpsPort.Open(); + if (gpsPort.IsOpen) { + IsConnected = true; + OnConnected(); + timer1 = new Timer(timer_Elapsed, null, 1000, 1000); + return true; + } + return false; + } + public static bool Connect(string portName, int baudRate=9600) { + ComPort = portName; + BaudRate = baudRate; + return Connect(); + } + + public static void Close() { + if (gpsPort == null) { return; } + if (gpsPort.IsOpen) { gpsPort.Close(); } + timer1?.Dispose(); + timer1 = null; + carryOver = null; + IsConnected = false; + movingCapable = false; + gpsPort.Dispose(); + gpsPort = null; + + OnDisconnected(); + } + + public static void Disconnect() { + Close(); + } + #endregion + + #region "Helper Methods" + private static double DmsToDd2(string val, string nwse) { + nwse = nwse.ToUpper(); + int chars = val.Substring(0, 1) == "0" ? 3 : 2; + //int chars = 2; + uint deg = Convert.ToUInt32(val.Substring(0, chars)); + double min = Convert.ToDouble(val.Substring(chars)); + + return Math.Round(Convert.ToDouble((nwse == "S" || nwse == "W" ? -1 : 1) * (deg + (min * 60) / 3600)), roundInt); + } + + private static DateTime? GetTimestamp(string date, string time) { + try { + if (date==null) { date = DateTime.UtcNow.ToString("ddMMyy"); } + DateTime dt = new DateTime(Convert.ToInt32("20" + date.Substring(4, 2)), + Convert.ToInt32(date.Substring(2, 2)), + Convert.ToInt32(date.Substring(0, 2)), + Convert.ToInt32(time.Substring(0, 2)), + Convert.ToInt32(time.Substring(2, 2)), + Convert.ToInt32(time.Substring(4, 2)), + DateTimeKind.Utc); + return dt; + } catch (Exception ex) { return null; } + } + + private static void SetTimeOffset(string date, string time) { + if (gpsOffsetSet) { return; } + DateTime dt = new DateTime(Convert.ToInt32("20" + date.Substring(4, 2)), + Convert.ToInt32(date.Substring(2, 2)), + Convert.ToInt32(date.Substring(0, 2)), + Convert.ToInt32(time.Substring(0, 2)), + Convert.ToInt32(time.Substring(2, 2)), + Convert.ToInt32(time.Substring(4, 2)), + DateTimeKind.Utc); + GpsTimeOffset = DateTime.UtcNow.Subtract(dt); + gpsOffsetSet = true; + } + + private static SatelliteView SvFromWords(string[] words) { + try { + SatelliteView sv = new SatelliteView() { + PRN = GetIntOrZero(words[0]), + Elevation = GetIntOrZero(words[1]), + Azimuth = GetIntOrZero(words[2]), + SNR = GetIntOrZero(words[3]) + }; + + return sv; + }catch (Exception ex) { + Console.WriteLine("Unable to create SatelliteView: " + ex.Message); + return null; + } + } + + private static int GetIntOrZero(string val) { + return string.IsNullOrEmpty(val) ? -1 : Convert.ToInt32(val); + } + + private static bool IsGpsPort(string comPort, int baudRate) { + Console.Write("Attempting to locate GPS on " + comPort + ":" + baudRate + "..."); + InitGps(comPort, baudRate); + gpsPort.ReadTimeout = 3000; + + try { + gpsPort.Open(); + Console.Write(" Connected. Listening for NMEA Sentences..."); + } catch (Exception ex) { + Console.WriteLine(" Error: " + ex.Message); + return false; + } + + string sData = ""; + while (true) { + string s; + try { + s = gpsPort.ReadLine(); + } catch (Exception ex) { + gpsPort.Close(); + Console.WriteLine(" Error: " + ex.Message); + return false; + } + + if (string.IsNullOrEmpty(s)) { return false; } + sData += s + Environment.NewLine; + + if (sData.Length > 1500) { + + if (sData.Contains("$GPRMC") || sData.Contains("$GPGGA") || sData.Contains("$GPGSA")) { + Console.WriteLine(" This is a GPS device!"); + gpsPort.Close(); + return true; + } + } + } + } + #endregion + + public static void InitGps(string portName, int baud=9600) { + gpsPort = new SerialPort(portName, baud); + } + + private static void timer_Elapsed(object state) { + try { + if (gpsPort != null && gpsPort.IsOpen) { + string data = gpsPort.ReadExisting(); + string[] sentences = (carryOver + data).Split(new string[] { Environment.NewLine }, StringSplitOptions.None); + int lastIndex = data.LastIndexOf(Environment.NewLine); + if (lastIndex < data.Length) { carryOver = data.Substring(lastIndex); } + + if (sentences.Count() > 1) { + foreach (string sentence in sentences) { + if (sentence.Trim() == "") { continue; } + + //Console.WriteLine("::" + sentence); + if (sentence.Substring(0,1) != "$") { continue; } + try { + string[] words = sentence.Split(','); + string cmd = words[0].ToUpper(); + + if (cmd == "$GPGGA" && words.Count() >= 14) { + OnNmeaReceived(new NmeaReceivedEventArgs(sentence)); + if (movingCapable) { continue; } + // Only capture this data if we haven't received a $GPRMC sentence + GpsCoordinates gc = new GpsCoordinates() { + Latitude = DmsToDd2(words[2], words[3]), + Longitude = DmsToDd2(words[4], words[5]), + Altitude = Convert.ToDouble(words[9]), + Timestamp = GetTimestamp(null, words[1]) + }; + OnPositionChanged(new GpsCoordinatesChangedEventArgs(gc)); + } else if (cmd == "$GPRMC" && words.Count() > 12) { + OnNmeaReceived(new NmeaReceivedEventArgs(sentence)); + if (words[2].ToUpper() != "A") { continue; } + SetTimeOffset(words[9], words[1]); + GpsCoordinates gc = new GpsCoordinates() { + Latitude = DmsToDd2(words[3], words[4]), + Longitude = DmsToDd2(words[5], words[6]), + Speed = (Convert.ToDouble(words[7]) * 1.15078), + Course = Convert.ToDouble(words[8]), + Timestamp = GetTimestamp(words[9], words[1]) + }; + movingCapable = true; + OnPositionChanged(new GpsCoordinatesChangedEventArgs(gc)); + } else if (cmd == "$GPGSA" && words.Count() > 17) { + OnNmeaReceived(new NmeaReceivedEventArgs(sentence)); + + } else if (cmd == "$GPGSV" && words.Count() > 7) { + OnNmeaReceived(new NmeaReceivedEventArgs(sentence)); + // Sometimes, we may not want to bother processing all of + // this data, so user can turn this off if not needed + if (!SatelliteStats) { return; } + + // Hacky way to detect a new set of Satellite information. + // We're assuming we always receive the list of GPGSV's in + // order, so we clear the list of Active Satellites when + // we receive the first Message + int gsvId = Convert.ToInt32(words[2]); + if (gsvId == 1) { ActiveSatellites.Clear(); } + + for (int i=4; i + + + + Debug + AnyCPU + {1D1F37BA-9E34-4A02-A27C-717598839024} + Library + Properties + KollNet.Lib + GpsClient + v4.7.2 + 512 + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/GpsClient/GpsCoordinates.cs b/GpsClient/GpsCoordinates.cs new file mode 100644 index 0000000..f596ab7 --- /dev/null +++ b/GpsClient/GpsCoordinates.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace KollNet.Lib { + public class GpsCoordinates { + public double Latitude { get; set; } + public double Longitude { get; set; } + public double Altitude { get; set; } + public double Course { get; set; } + public double Speed { get; set; } + public DateTime? Timestamp { get; set; } + } +} diff --git a/GpsClient/Models/SatelliteView.cs b/GpsClient/Models/SatelliteView.cs new file mode 100644 index 0000000..909a3ee --- /dev/null +++ b/GpsClient/Models/SatelliteView.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace KollNet.Lib.Models { + public class SatelliteView { + public int PRN { get; set; } + public int Elevation { get; set; } + public int Azimuth { get; set; } + public int SNR { get; set; } + } +} diff --git a/GpsClient/Properties/AssemblyInfo.cs b/GpsClient/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..cf9236a --- /dev/null +++ b/GpsClient/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("GpsClient")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("GpsClient")] +[assembly: AssemblyCopyright("Copyright © 2022")] +[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("1d1f37ba-9e34-4a02-a27c-717598839024")] + +// 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/GpsTestApp/App.config b/GpsTestApp/App.config new file mode 100644 index 0000000..56efbc7 --- /dev/null +++ b/GpsTestApp/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/GpsTestApp/Extensions.cs b/GpsTestApp/Extensions.cs new file mode 100644 index 0000000..39c870f --- /dev/null +++ b/GpsTestApp/Extensions.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace GpsTestApp { + public static class Extensions { + public static void InvokeIfRequired(this Control c, Action a) { + if (c.InvokeRequired) { + c.Invoke(new MethodInvoker(() => { InvokeIfRequired(c, a); })); + return; + } + try { + if (!c.IsHandleCreated) { IntPtr i = c.Handle; } // See links above - handle is not created, will cause cross-thread exceptions + a.Invoke(); + } catch (Exception ex) { } + } + } +} diff --git a/GpsTestApp/Form1.Designer.cs b/GpsTestApp/Form1.Designer.cs new file mode 100644 index 0000000..246e018 --- /dev/null +++ b/GpsTestApp/Form1.Designer.cs @@ -0,0 +1,333 @@ + +namespace GpsTestApp { + partial class Form1 { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) { + if (disposing && (components != null)) { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() { + this.layoutControl1 = new DevExpress.XtraLayout.LayoutControl(); + this.Root = new DevExpress.XtraLayout.LayoutControlGroup(); + this.ComPort = new DevExpress.XtraEditors.ComboBoxEdit(); + this.layoutControlItem1 = new DevExpress.XtraLayout.LayoutControlItem(); + this.emptySpaceItem1 = new DevExpress.XtraLayout.EmptySpaceItem(); + this.BaudRate = new DevExpress.XtraEditors.TextEdit(); + this.layoutControlItem2 = new DevExpress.XtraLayout.LayoutControlItem(); + this.simpleButton1 = new DevExpress.XtraEditors.SimpleButton(); + this.layoutControlItem3 = new DevExpress.XtraLayout.LayoutControlItem(); + this.memoEdit1 = new DevExpress.XtraEditors.MemoEdit(); + this.line1 = new DevComponents.DotNetBar.Controls.Line(); + this.layoutControlItem4 = new DevExpress.XtraLayout.LayoutControlItem(); + this.UtcTime = new DevExpress.XtraEditors.TextEdit(); + this.layoutControlItem5 = new DevExpress.XtraLayout.LayoutControlItem(); + this.LocalTime = new DevExpress.XtraEditors.TextEdit(); + this.layoutControlItem6 = new DevExpress.XtraLayout.LayoutControlItem(); + this.emptySpaceItem2 = new DevExpress.XtraLayout.EmptySpaceItem(); + this.emptySpaceItem3 = new DevExpress.XtraLayout.EmptySpaceItem(); + this.TimeOffset = new DevExpress.XtraEditors.TextEdit(); + this.layoutControlItem7 = new DevExpress.XtraLayout.LayoutControlItem(); + this.emptySpaceItem4 = new DevExpress.XtraLayout.EmptySpaceItem(); + ((System.ComponentModel.ISupportInitialize)(this.layoutControl1)).BeginInit(); + this.layoutControl1.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.Root)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.ComPort.Properties)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.layoutControlItem1)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.emptySpaceItem1)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.BaudRate.Properties)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.layoutControlItem2)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.layoutControlItem3)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.memoEdit1.Properties)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.layoutControlItem4)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.UtcTime.Properties)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.layoutControlItem5)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.LocalTime.Properties)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.layoutControlItem6)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.emptySpaceItem2)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.emptySpaceItem3)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.TimeOffset.Properties)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.layoutControlItem7)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.emptySpaceItem4)).BeginInit(); + this.SuspendLayout(); + // + // layoutControl1 + // + this.layoutControl1.Controls.Add(this.TimeOffset); + this.layoutControl1.Controls.Add(this.LocalTime); + this.layoutControl1.Controls.Add(this.UtcTime); + this.layoutControl1.Controls.Add(this.line1); + this.layoutControl1.Controls.Add(this.simpleButton1); + this.layoutControl1.Controls.Add(this.BaudRate); + this.layoutControl1.Controls.Add(this.ComPort); + this.layoutControl1.Dock = System.Windows.Forms.DockStyle.Top; + this.layoutControl1.Location = new System.Drawing.Point(0, 0); + this.layoutControl1.Name = "layoutControl1"; + this.layoutControl1.Root = this.Root; + this.layoutControl1.Size = new System.Drawing.Size(464, 186); + this.layoutControl1.TabIndex = 0; + this.layoutControl1.Text = "layoutControl1"; + // + // Root + // + this.Root.EnableIndentsWithoutBorders = DevExpress.Utils.DefaultBoolean.True; + this.Root.GroupBordersVisible = false; + this.Root.Items.AddRange(new DevExpress.XtraLayout.BaseLayoutItem[] { + this.layoutControlItem1, + this.emptySpaceItem1, + this.layoutControlItem2, + this.layoutControlItem3, + this.layoutControlItem4, + this.layoutControlItem5, + this.layoutControlItem6, + this.emptySpaceItem2, + this.emptySpaceItem3, + this.layoutControlItem7, + this.emptySpaceItem4}); + this.Root.Name = "Root"; + this.Root.Size = new System.Drawing.Size(464, 186); + this.Root.TextVisible = false; + // + // ComPort + // + this.ComPort.EditValue = "COM3"; + this.ComPort.Location = new System.Drawing.Point(84, 12); + this.ComPort.Name = "ComPort"; + this.ComPort.Properties.Buttons.AddRange(new DevExpress.XtraEditors.Controls.EditorButton[] { + new DevExpress.XtraEditors.Controls.EditorButton(DevExpress.XtraEditors.Controls.ButtonPredefines.Combo)}); + this.ComPort.Size = new System.Drawing.Size(145, 20); + this.ComPort.StyleController = this.layoutControl1; + this.ComPort.TabIndex = 4; + // + // layoutControlItem1 + // + this.layoutControlItem1.Control = this.ComPort; + this.layoutControlItem1.Location = new System.Drawing.Point(0, 0); + this.layoutControlItem1.Name = "layoutControlItem1"; + this.layoutControlItem1.Size = new System.Drawing.Size(221, 24); + this.layoutControlItem1.Text = "Port:"; + this.layoutControlItem1.TextSize = new System.Drawing.Size(60, 13); + // + // emptySpaceItem1 + // + this.emptySpaceItem1.AllowHotTrack = false; + this.emptySpaceItem1.Location = new System.Drawing.Point(0, 146); + this.emptySpaceItem1.Name = "emptySpaceItem1"; + this.emptySpaceItem1.Size = new System.Drawing.Size(444, 20); + this.emptySpaceItem1.TextSize = new System.Drawing.Size(0, 0); + // + // BaudRate + // + this.BaudRate.EditValue = "4800"; + this.BaudRate.Location = new System.Drawing.Point(305, 12); + this.BaudRate.Name = "BaudRate"; + this.BaudRate.Properties.MaskSettings.Set("MaskManagerType", typeof(DevExpress.Data.Mask.NumericMaskManager)); + this.BaudRate.Size = new System.Drawing.Size(147, 20); + this.BaudRate.StyleController = this.layoutControl1; + this.BaudRate.TabIndex = 5; + // + // layoutControlItem2 + // + this.layoutControlItem2.Control = this.BaudRate; + this.layoutControlItem2.Location = new System.Drawing.Point(221, 0); + this.layoutControlItem2.Name = "layoutControlItem2"; + this.layoutControlItem2.Size = new System.Drawing.Size(223, 24); + this.layoutControlItem2.Text = "Baud Rate:"; + this.layoutControlItem2.TextSize = new System.Drawing.Size(60, 13); + // + // simpleButton1 + // + this.simpleButton1.Location = new System.Drawing.Point(12, 36); + this.simpleButton1.Name = "simpleButton1"; + this.simpleButton1.Size = new System.Drawing.Size(440, 22); + this.simpleButton1.StyleController = this.layoutControl1; + this.simpleButton1.TabIndex = 6; + this.simpleButton1.Text = "Connect to GPS..."; + this.simpleButton1.Click += new System.EventHandler(this.ConnectButtonClicked); + // + // layoutControlItem3 + // + this.layoutControlItem3.Control = this.simpleButton1; + this.layoutControlItem3.Location = new System.Drawing.Point(0, 24); + this.layoutControlItem3.Name = "layoutControlItem3"; + this.layoutControlItem3.Size = new System.Drawing.Size(444, 26); + this.layoutControlItem3.TextSize = new System.Drawing.Size(0, 0); + this.layoutControlItem3.TextVisible = false; + // + // memoEdit1 + // + this.memoEdit1.Location = new System.Drawing.Point(12, 192); + this.memoEdit1.Name = "memoEdit1"; + this.memoEdit1.Size = new System.Drawing.Size(440, 246); + this.memoEdit1.TabIndex = 1; + // + // line1 + // + this.line1.Location = new System.Drawing.Point(12, 62); + this.line1.Name = "line1"; + this.line1.Size = new System.Drawing.Size(440, 20); + this.line1.TabIndex = 7; + this.line1.Text = "line1"; + // + // layoutControlItem4 + // + this.layoutControlItem4.Control = this.line1; + this.layoutControlItem4.Location = new System.Drawing.Point(0, 50); + this.layoutControlItem4.Name = "layoutControlItem4"; + this.layoutControlItem4.Size = new System.Drawing.Size(444, 24); + this.layoutControlItem4.TextSize = new System.Drawing.Size(0, 0); + this.layoutControlItem4.TextVisible = false; + // + // UtcTime + // + this.UtcTime.Location = new System.Drawing.Point(84, 86); + this.UtcTime.Name = "UtcTime"; + this.UtcTime.Properties.ReadOnly = true; + this.UtcTime.Size = new System.Drawing.Size(145, 20); + this.UtcTime.StyleController = this.layoutControl1; + this.UtcTime.TabIndex = 8; + // + // layoutControlItem5 + // + this.layoutControlItem5.Control = this.UtcTime; + this.layoutControlItem5.Location = new System.Drawing.Point(0, 74); + this.layoutControlItem5.Name = "layoutControlItem5"; + this.layoutControlItem5.Size = new System.Drawing.Size(221, 24); + this.layoutControlItem5.Text = "UTC Time:"; + this.layoutControlItem5.TextSize = new System.Drawing.Size(60, 13); + // + // LocalTime + // + this.LocalTime.Location = new System.Drawing.Point(84, 110); + this.LocalTime.Name = "LocalTime"; + this.LocalTime.Properties.ReadOnly = true; + this.LocalTime.Size = new System.Drawing.Size(145, 20); + this.LocalTime.StyleController = this.layoutControl1; + this.LocalTime.TabIndex = 9; + // + // layoutControlItem6 + // + this.layoutControlItem6.Control = this.LocalTime; + this.layoutControlItem6.Location = new System.Drawing.Point(0, 98); + this.layoutControlItem6.Name = "layoutControlItem6"; + this.layoutControlItem6.Size = new System.Drawing.Size(221, 24); + this.layoutControlItem6.Text = "Local Time:"; + this.layoutControlItem6.TextSize = new System.Drawing.Size(60, 13); + // + // emptySpaceItem2 + // + this.emptySpaceItem2.AllowHotTrack = false; + this.emptySpaceItem2.Location = new System.Drawing.Point(221, 74); + this.emptySpaceItem2.Name = "emptySpaceItem2"; + this.emptySpaceItem2.Size = new System.Drawing.Size(223, 24); + this.emptySpaceItem2.TextSize = new System.Drawing.Size(0, 0); + // + // emptySpaceItem3 + // + this.emptySpaceItem3.AllowHotTrack = false; + this.emptySpaceItem3.Location = new System.Drawing.Point(221, 98); + this.emptySpaceItem3.Name = "emptySpaceItem3"; + this.emptySpaceItem3.Size = new System.Drawing.Size(223, 24); + this.emptySpaceItem3.TextSize = new System.Drawing.Size(0, 0); + // + // TimeOffset + // + this.TimeOffset.Location = new System.Drawing.Point(84, 134); + this.TimeOffset.Name = "TimeOffset"; + this.TimeOffset.Properties.ReadOnly = true; + this.TimeOffset.Size = new System.Drawing.Size(145, 20); + this.TimeOffset.StyleController = this.layoutControl1; + this.TimeOffset.TabIndex = 10; + // + // layoutControlItem7 + // + this.layoutControlItem7.Control = this.TimeOffset; + this.layoutControlItem7.Location = new System.Drawing.Point(0, 122); + this.layoutControlItem7.Name = "layoutControlItem7"; + this.layoutControlItem7.Size = new System.Drawing.Size(221, 24); + this.layoutControlItem7.Text = "Time Offset:"; + this.layoutControlItem7.TextSize = new System.Drawing.Size(60, 13); + // + // emptySpaceItem4 + // + this.emptySpaceItem4.AllowHotTrack = false; + this.emptySpaceItem4.Location = new System.Drawing.Point(221, 122); + this.emptySpaceItem4.Name = "emptySpaceItem4"; + this.emptySpaceItem4.Size = new System.Drawing.Size(223, 24); + this.emptySpaceItem4.TextSize = new System.Drawing.Size(0, 0); + // + // Form1 + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(464, 450); + this.Controls.Add(this.memoEdit1); + this.Controls.Add(this.layoutControl1); + this.Name = "Form1"; + this.Text = "Form1"; + ((System.ComponentModel.ISupportInitialize)(this.layoutControl1)).EndInit(); + this.layoutControl1.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.Root)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.ComPort.Properties)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.layoutControlItem1)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.emptySpaceItem1)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.BaudRate.Properties)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.layoutControlItem2)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.layoutControlItem3)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.memoEdit1.Properties)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.layoutControlItem4)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.UtcTime.Properties)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.layoutControlItem5)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.LocalTime.Properties)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.layoutControlItem6)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.emptySpaceItem2)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.emptySpaceItem3)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.TimeOffset.Properties)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.layoutControlItem7)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.emptySpaceItem4)).EndInit(); + this.ResumeLayout(false); + + } + + #endregion + + private DevExpress.XtraLayout.LayoutControl layoutControl1; + private DevExpress.XtraLayout.LayoutControlGroup Root; + private DevExpress.XtraEditors.SimpleButton simpleButton1; + private DevExpress.XtraEditors.TextEdit BaudRate; + private DevExpress.XtraEditors.ComboBoxEdit ComPort; + private DevExpress.XtraLayout.LayoutControlItem layoutControlItem1; + private DevExpress.XtraLayout.EmptySpaceItem emptySpaceItem1; + private DevExpress.XtraLayout.LayoutControlItem layoutControlItem2; + private DevExpress.XtraLayout.LayoutControlItem layoutControlItem3; + private DevExpress.XtraEditors.MemoEdit memoEdit1; + private DevComponents.DotNetBar.Controls.Line line1; + private DevExpress.XtraLayout.LayoutControlItem layoutControlItem4; + private DevExpress.XtraEditors.TextEdit LocalTime; + private DevExpress.XtraEditors.TextEdit UtcTime; + private DevExpress.XtraLayout.LayoutControlItem layoutControlItem5; + private DevExpress.XtraLayout.LayoutControlItem layoutControlItem6; + private DevExpress.XtraLayout.EmptySpaceItem emptySpaceItem2; + private DevExpress.XtraLayout.EmptySpaceItem emptySpaceItem3; + private DevExpress.XtraEditors.TextEdit TimeOffset; + private DevExpress.XtraLayout.LayoutControlItem layoutControlItem7; + private DevExpress.XtraLayout.EmptySpaceItem emptySpaceItem4; + } +} + diff --git a/GpsTestApp/Form1.cs b/GpsTestApp/Form1.cs new file mode 100644 index 0000000..ae64290 --- /dev/null +++ b/GpsTestApp/Form1.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using DevExpress.XtraBars; +using DevExpress.XtraEditors; +using KollNet.Lib; +using KollNet.Lib.EventArguments; +using KollNet.Lib.Models; + +namespace GpsTestApp { + public partial class Form1 : Form { + + public Form1() { + InitializeComponent(); + } + + private void ConnectButtonClicked(object sender, EventArgs e) { + if (string.IsNullOrEmpty(ComPort.Text)) { + string comPort = GpsClient.FindGps(); + if (!string.IsNullOrEmpty(comPort)) { + string[] parts = comPort.Split(':'); + ComPort.Text = parts[0]; + BaudRate.Text = parts[1]; + } else { + XtraMessageBox.Show("No COM Port was specified and an active GPS COM Port was not found"); + return; + } + } + + if (!GpsClient.Connect(ComPort.Text, Convert.ToInt32(BaudRate.Text))) { + XtraMessageBox.Show("Unable to connect to " + ComPort.Text + " with Baud " + BaudRate.Text); + return; + } + + GpsClient.PositionChanged += GpsClient_PositionChanged; + GpsClient.NmeaReceived += GpsClient_NmeaReceived; + GpsClient.SatellitesUpdated += GpsClient_SatellitesUpdated; + } + + private void GpsClient_SatellitesUpdated(object sender, SatellitesUpdatedEventArgs e) { + this.InvokeIfRequired(() => { + memoEdit1.AppendText("Received Satellite Data for " + e.ActiveSatellites.Count + " satellites" + Environment.NewLine); + }); + } + + private void GpsClient_NmeaReceived(object sender, NmeaReceivedEventArgs e) { + this.InvokeIfRequired(() => { + if (e.NmeaSentence.StartsWith("$GPGSV")) { + Clipboard.SetText(e.NmeaSentence); + } + }); + } + + private void GpsClient_PositionChanged(object sender, GpsCoordinatesChangedEventArgs e) { + string data = "LL[" + e.Coordinates.Latitude.ToString("0.000000") + "," + e.Coordinates.Longitude.ToString("0.000000") + + "] SC[" + e.Coordinates.Speed + "," + e.Coordinates.Course + "]"; + + Console.WriteLine(data); + this.InvokeIfRequired(() => { + memoEdit1.AppendText(data + Environment.NewLine); + + TimeOffset.Text = GpsClient.GpsTimeOffset.ToString(); + UtcTime.Text = DateTime.UtcNow.Add(GpsClient.GpsTimeOffset).ToString(); + LocalTime.Text = DateTime.UtcNow.Add(GpsClient.GpsTimeOffset).ToLocalTime().ToString(); + }); + } + } +} diff --git a/GpsTestApp/Form1.resx b/GpsTestApp/Form1.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/GpsTestApp/Form1.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/GpsTestApp/GpsTestApp.csproj b/GpsTestApp/GpsTestApp.csproj new file mode 100644 index 0000000..3971a51 --- /dev/null +++ b/GpsTestApp/GpsTestApp.csproj @@ -0,0 +1,99 @@ + + + + + Debug + AnyCPU + {04FF8D42-1A4B-43EB-84DE-A9FE475BBD9B} + WinExe + GpsTestApp + GpsTestApp + v4.7.2 + 512 + true + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + + + + Form + + + Form1.cs + + + + + Form1.cs + + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + + + + + {1d1f37ba-9e34-4a02-a27c-717598839024} + GpsClient + + + + \ No newline at end of file diff --git a/GpsTestApp/Program.cs b/GpsTestApp/Program.cs new file mode 100644 index 0000000..1a6b8e7 --- /dev/null +++ b/GpsTestApp/Program.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace GpsTestApp { + static class Program { + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new Form1()); + } + } +} diff --git a/GpsTestApp/Properties/AssemblyInfo.cs b/GpsTestApp/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..7d45753 --- /dev/null +++ b/GpsTestApp/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("GpsTestApp")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("GpsTestApp")] +[assembly: AssemblyCopyright("Copyright © 2022")] +[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("04ff8d42-1a4b-43eb-84de-a9fe475bbd9b")] + +// 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/GpsTestApp/Properties/Resources.Designer.cs b/GpsTestApp/Properties/Resources.Designer.cs new file mode 100644 index 0000000..65119ad --- /dev/null +++ b/GpsTestApp/Properties/Resources.Designer.cs @@ -0,0 +1,61 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +namespace GpsTestApp.Properties { + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if ((resourceMan == null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("GpsTestApp.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/GpsTestApp/Properties/Resources.resx b/GpsTestApp/Properties/Resources.resx new file mode 100644 index 0000000..af7dbeb --- /dev/null +++ b/GpsTestApp/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/GpsTestApp/Properties/Settings.Designer.cs b/GpsTestApp/Properties/Settings.Designer.cs new file mode 100644 index 0000000..3ed5eaa --- /dev/null +++ b/GpsTestApp/Properties/Settings.Designer.cs @@ -0,0 +1,25 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +namespace GpsTestApp.Properties { + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + } +} diff --git a/GpsTestApp/Properties/Settings.settings b/GpsTestApp/Properties/Settings.settings new file mode 100644 index 0000000..3964565 --- /dev/null +++ b/GpsTestApp/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + diff --git a/GpsTestApp/Properties/licenses.licx b/GpsTestApp/Properties/licenses.licx new file mode 100644 index 0000000..0734337 --- /dev/null +++ b/GpsTestApp/Properties/licenses.licx @@ -0,0 +1,4 @@ +DevExpress.XtraEditors.TextEdit, DevExpress.XtraEditors.v21.2, Version=21.2.4.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a +DevExpress.XtraLayout.LayoutControl, DevExpress.XtraLayout.v21.2, Version=21.2.4.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a +DevExpress.XtraEditors.ButtonEdit, DevExpress.XtraEditors.v21.2, Version=21.2.4.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a +DevExpress.XtraEditors.ComboBoxEdit, DevExpress.XtraEditors.v21.2, Version=21.2.4.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a