Browse Source

Add NMEA device support

master
The6P4C 7 years ago
parent
commit
2e227a65d8
4 changed files with 233 additions and 0 deletions
  1. +12
    -0
      GPSDOTimeSync/FormMain.cs
  2. +3
    -0
      GPSDOTimeSync/GPSDOTimeSync.csproj
  3. +168
    -0
      GPSDOTimeSync/TimeProviders/NMEA/NMEASerialPort.cs
  4. +50
    -0
      GPSDOTimeSync/TimeProviders/NMEA/NMEATimeProvider.cs

+ 12
- 0
GPSDOTimeSync/FormMain.cs View File

@@ -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;
}) })
} }


+ 3
- 0
GPSDOTimeSync/GPSDOTimeSync.csproj View File

@@ -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.


+ 168
- 0
GPSDOTimeSync/TimeProviders/NMEA/NMEASerialPort.cs View File

@@ -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);
}
}
}
}
}
}

+ 50
- 0
GPSDOTimeSync/TimeProviders/NMEA/NMEATimeProvider.cs View File

@@ -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);
}
}
}
}

Loading…
Cancel
Save