@@ -6,6 +6,9 @@ using System.Windows.Forms; | |||||
using GPSDOTimeSync.Devices.Thunderbolt; | using GPSDOTimeSync.Devices.Thunderbolt; | ||||
using GPSDOTimeSync.TimeProviders; | using GPSDOTimeSync.TimeProviders; | ||||
using GPSDOTimeSync.TimeProviders.Thunderbolt; | using GPSDOTimeSync.TimeProviders.Thunderbolt; | ||||
using GPSDOTimeSync.TimeProviders.NMEA; | |||||
using System.Diagnostics; | |||||
using System.Linq; | |||||
namespace GPSDOTimeSync { | namespace GPSDOTimeSync { | ||||
public partial class FormMain : Form { | public partial class FormMain : Form { | ||||
@@ -22,6 +25,15 @@ namespace GPSDOTimeSync { | |||||
ThunderboltSerialPort thunderboltSerialPort = new ThunderboltSerialPort(serialPort); | ThunderboltSerialPort thunderboltSerialPort = new ThunderboltSerialPort(serialPort); | ||||
ITimeProvider timeProvider = new ThunderboltTimeProvider(thunderboltSerialPort); | ITimeProvider timeProvider = new ThunderboltTimeProvider(thunderboltSerialPort); | ||||
return timeProvider; | |||||
}) | |||||
}, | |||||
{ | |||||
"NMEA Device (e.g. BG7TBL GPSDO)", | |||||
new Func<SerialPort, ITimeProvider>((serialPort) => { | |||||
NMEASerialPort nmeaSerialPort = new NMEASerialPort(serialPort); | |||||
ITimeProvider timeProvider = new NMEATimeProvider(nmeaSerialPort); | |||||
return timeProvider; | return timeProvider; | ||||
}) | }) | ||||
} | } | ||||
@@ -56,6 +56,8 @@ | |||||
<Compile Include="Properties\AssemblyInfo.cs" /> | <Compile Include="Properties\AssemblyInfo.cs" /> | ||||
<Compile Include="SystemTimeUtils.cs" /> | <Compile Include="SystemTimeUtils.cs" /> | ||||
<Compile Include="TimeProviders\ITimeProvider.cs" /> | <Compile Include="TimeProviders\ITimeProvider.cs" /> | ||||
<Compile Include="TimeProviders\NMEA\NMEASerialPort.cs" /> | |||||
<Compile Include="TimeProviders\NMEA\NMEATimeProvider.cs" /> | |||||
<Compile Include="TimeProviders\Thunderbolt\ThunderboltSerialPort.cs" /> | <Compile Include="TimeProviders\Thunderbolt\ThunderboltSerialPort.cs" /> | ||||
<Compile Include="TimeProviders\Thunderbolt\ThunderboltTimeProvider.cs" /> | <Compile Include="TimeProviders\Thunderbolt\ThunderboltTimeProvider.cs" /> | ||||
<EmbeddedResource Include="FormMain.resx"> | <EmbeddedResource Include="FormMain.resx"> | ||||
@@ -83,6 +85,7 @@ | |||||
<ItemGroup> | <ItemGroup> | ||||
<None Include="App.config" /> | <None Include="App.config" /> | ||||
</ItemGroup> | </ItemGroup> | ||||
<ItemGroup /> | |||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> | <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> | ||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. | <!-- To modify your build process, add your task inside one of the targets below and uncomment it. | ||||
Other similar extension points exist, see Microsoft.Common.targets. | Other similar extension points exist, see Microsoft.Common.targets. | ||||
@@ -0,0 +1,168 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.IO.Ports; | |||||
using System.Text; | |||||
using System.Threading; | |||||
namespace GPSDOTimeSync.TimeProviders.NMEA { | |||||
public class NMEASentence { | |||||
/// <summary> | |||||
/// The validity of the NMEA sentence. | |||||
/// If <code>false</code>, the values of <code>Talker</code>, <code>MessageType</code>, <code>Data</code>, <code>Checksum</code> and <code>RawSentence</code> must not be used. | |||||
/// If <code>true</code>, the sentence may still contain invalid values. | |||||
/// </summary> | |||||
public bool IsSentenceValid { get; } | |||||
/// <summary> | |||||
/// The talker of the NMEA sentence. | |||||
/// </summary> | |||||
public string Talker { get; } | |||||
/// <summary> | |||||
/// The NMEA message type. | |||||
/// </summary> | |||||
public string MessageType { get; } | |||||
/// <summary> | |||||
/// The data fields of the NMEA message. | |||||
/// </summary> | |||||
public List<string> Data { get; } | |||||
/// <summary> | |||||
/// The checksum of the message, if present. | |||||
/// If the message did not contain a checksum, <code>Checksum</code> equals -1. | |||||
/// </summary> | |||||
public int Checksum { get; } | |||||
/// <summary> | |||||
/// The raw, unparsed NMEA sentence. | |||||
/// </summary> | |||||
public string RawSentence { get; } | |||||
public NMEASentence(bool isSentenceValid, string talker, string messageType, List<string> data, int checksum, string rawSentence) { | |||||
IsSentenceValid = isSentenceValid; | |||||
Talker = talker; | |||||
MessageType = messageType; | |||||
Data = data; | |||||
Checksum = checksum; | |||||
RawSentence = rawSentence; | |||||
} | |||||
} | |||||
class NMEASerialPort { | |||||
private StringBuilder sentenceBuffer; | |||||
private bool inSentence; | |||||
private SerialPort serialPort; | |||||
private bool running; | |||||
private Thread readThread; | |||||
/// <summary> | |||||
/// A delegate which is used for the <code>SentenceReceived</code> event. | |||||
/// </summary> | |||||
/// <param name="sentence">The parsed NMEA sentence which was received.</param> | |||||
public delegate void SentenceReceivedEventHandler(NMEASentence sentence); | |||||
/// <summary> | |||||
/// An event which is called when a sentence is received over the serial port. | |||||
/// </summary> | |||||
public event SentenceReceivedEventHandler SentenceReceived; | |||||
/// <summary> | |||||
/// Creates an instance of the NMEASerialPort class, which processes serial data from an NMEA device. | |||||
/// The serial port passed into the function must not be opened. | |||||
/// </summary> | |||||
/// <param name="serialPort">The serial port with which to communicate with the NMEA device.</param> | |||||
public NMEASerialPort(SerialPort serialPort) { | |||||
sentenceBuffer = new StringBuilder(); | |||||
inSentence = false; | |||||
this.serialPort = serialPort; | |||||
readThread = new Thread(ReadSerialPort); | |||||
readThread.Name = "NMEASerialPort Read"; | |||||
} | |||||
/// <summary> | |||||
/// Begins processing serial data and firing SentenceReceived events. | |||||
/// </summary> | |||||
public void Open() { | |||||
running = true; | |||||
serialPort.Open(); | |||||
readThread.Start(); | |||||
} | |||||
/// <summary> | |||||
/// Stops processing serial data and firing SentenceReceived events. | |||||
/// </summary> | |||||
public void Close() { | |||||
running = false; | |||||
readThread.Join(); | |||||
serialPort.Close(); | |||||
} | |||||
private void ProcessSentence() { | |||||
string sentence = sentenceBuffer.ToString(); | |||||
List<string> components = new List<string>(sentence.Split(',')); | |||||
string talkerAndMessageType = components[0]; | |||||
string talker = talkerAndMessageType.Substring(0, 2); | |||||
string messageType = talkerAndMessageType.Substring(2); | |||||
List<string> data = components.GetRange(1, components.Count - 1); | |||||
string lastEntry = data[data.Count - 1]; | |||||
int checksum = -1; | |||||
// Check if last entry is long enough to have a checksum at the end, and if so, | |||||
// if there's a star character there | |||||
if (lastEntry.Length >= 3 && lastEntry[lastEntry.Length - 3] == '*') { | |||||
// Fix last entry by removing checksum | |||||
data[data.Count - 1] = lastEntry.Substring(0, lastEntry.Length - 3); | |||||
string checksumString = lastEntry.Substring(lastEntry.Length - 2); | |||||
checksum = Convert.ToInt32(checksumString, 16); | |||||
} | |||||
SentenceReceived?.Invoke(new NMEASentence(true, talker, messageType, data, checksum, sentence)); | |||||
} | |||||
private void ProcessByte(byte b) { | |||||
char c = (char) b; | |||||
if (inSentence) { | |||||
if (c != '\r') { | |||||
sentenceBuffer.Append(c); | |||||
} else { | |||||
ProcessSentence(); | |||||
sentenceBuffer = new StringBuilder(); | |||||
inSentence = false; | |||||
} | |||||
} else { | |||||
if (c == '$') { | |||||
inSentence = true; | |||||
} | |||||
} | |||||
} | |||||
private void ReadSerialPort() { | |||||
while (running) { | |||||
if (serialPort.BytesToRead > 0) { | |||||
int possibleCurrentByte = serialPort.ReadByte(); | |||||
if (possibleCurrentByte != -1) { | |||||
ProcessByte((byte) possibleCurrentByte); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,50 @@ | |||||
using System; | |||||
using System.Diagnostics; | |||||
namespace GPSDOTimeSync.TimeProviders.NMEA { | |||||
class NMEATimeProvider : ITimeProvider { | |||||
private NMEASerialPort nmeaSerialPort; | |||||
public event TimeAvailableEventHandler TimeAvailable; | |||||
public event LogEventHandler Log; | |||||
/// <summary> | |||||
/// Creates an instance of the NMEATimeProvider class, which provides time information through the <code>ITimeProvider</code> interface. | |||||
/// The <code>NMEASerialPort</code> instance passed into this function must not be open. | |||||
/// </summary> | |||||
/// <param name="nmeaSerialPort">The <code>NMEASerialPort</code> instance to use when communicating with the NMEA device.</param> | |||||
public NMEATimeProvider(NMEASerialPort nmeaSerialPort) { | |||||
this.nmeaSerialPort = nmeaSerialPort; | |||||
nmeaSerialPort.SentenceReceived += SentenceReceived; | |||||
} | |||||
public void Start() { | |||||
nmeaSerialPort.Open(); | |||||
} | |||||
public void Stop() { | |||||
nmeaSerialPort.Close(); | |||||
} | |||||
private void SentenceReceived(NMEASentence sentence) { | |||||
if (sentence.IsSentenceValid) { | |||||
if (sentence.Talker == "GP" && sentence.MessageType == "RMC") { | |||||
string timeString = sentence.Data[0]; | |||||
int hour = int.Parse(timeString.Substring(0, 2)); | |||||
int minute = int.Parse(timeString.Substring(2, 2)); | |||||
int second = int.Parse(timeString.Substring(4, 2)); | |||||
string dateString = sentence.Data[8]; | |||||
int day = int.Parse(dateString.Substring(0, 2)); | |||||
int month = int.Parse(dateString.Substring(2, 2)); | |||||
int year = 2000 + int.Parse(dateString.Substring(4, 2)); | |||||
TimeAvailable?.Invoke(new DateTime(year, month, day, hour, minute, second)); | |||||
} | |||||
} else { | |||||
Log?.Invoke("An invalid packet was received.", LogLevel.Warning); | |||||
} | |||||
} | |||||
} | |||||
} |