diff --git a/GPSDOTimeSync/GPSDOTimeSync.csproj b/GPSDOTimeSync/GPSDOTimeSync.csproj index 00e428f..ff4f4cb 100644 --- a/GPSDOTimeSync/GPSDOTimeSync.csproj +++ b/GPSDOTimeSync/GPSDOTimeSync.csproj @@ -52,6 +52,7 @@ FormMain.cs + diff --git a/GPSDOTimeSync/SerialPortProcessor.cs b/GPSDOTimeSync/SerialPortProcessor.cs new file mode 100644 index 0000000..189c90f --- /dev/null +++ b/GPSDOTimeSync/SerialPortProcessor.cs @@ -0,0 +1,58 @@ +using System; +using System.IO.Ports; +using System.Threading; + +namespace GPSDOTimeSync { + abstract class SerialPortProcessor { + private SerialPort serialPort; + + private bool running; + private Thread readThread; + + /// + /// Initializes the class with a serial port to communicate with. + /// The serial port passed into this function must not be opened. + /// + /// The serial port to communicate with. + public SerialPortProcessor(SerialPort serialPort) { + this.serialPort = serialPort; + + readThread = new Thread(ReadSerialPort); + readThread.Name = "Unnamed SerialPortProcessor Read"; + } + + /// + /// Begins processing serial data and firing SentenceReceived events. + /// + public void Open() { + running = true; + + serialPort.Open(); + readThread.Start(); + } + + /// + /// Stops processing serial data and firing SentenceReceived events. + /// + public void Close() { + running = false; + + readThread.Join(); + serialPort.Close(); + } + + protected abstract void ProcessByte(byte b); + + private void ReadSerialPort() { + while (running) { + if (serialPort.BytesToRead > 0) { + int possibleCurrentByte = serialPort.ReadByte(); + + if (possibleCurrentByte != -1) { + ProcessByte((byte) possibleCurrentByte); + } + } + } + } + } +} diff --git a/GPSDOTimeSync/TimeProviders/NMEA/NMEASerialPort.cs b/GPSDOTimeSync/TimeProviders/NMEA/NMEASerialPort.cs index ae63935..b32c964 100644 --- a/GPSDOTimeSync/TimeProviders/NMEA/NMEASerialPort.cs +++ b/GPSDOTimeSync/TimeProviders/NMEA/NMEASerialPort.cs @@ -51,15 +51,10 @@ namespace GPSDOTimeSync.TimeProviders.NMEA { } } - class NMEASerialPort { + class NMEASerialPort : SerialPortProcessor { private StringBuilder sentenceBuffer; private bool inSentence; - private SerialPort serialPort; - - private bool running; - private Thread readThread; - /// /// A delegate which is used for the SentenceReceived event. /// @@ -71,39 +66,9 @@ namespace GPSDOTimeSync.TimeProviders.NMEA { /// public event SentenceReceivedEventHandler SentenceReceived; - /// - /// 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. - /// - /// The serial port with which to communicate with the NMEA device. - public NMEASerialPort(SerialPort serialPort) { + public NMEASerialPort(SerialPort serialPort) : base(serialPort) { sentenceBuffer = new StringBuilder(); inSentence = false; - - this.serialPort = serialPort; - - readThread = new Thread(ReadSerialPort); - readThread.Name = "NMEASerialPort Read"; - } - - /// - /// Begins processing serial data and firing SentenceReceived events. - /// - public void Open() { - running = true; - - serialPort.Open(); - readThread.Start(); - } - - /// - /// Stops processing serial data and firing SentenceReceived events. - /// - public void Close() { - running = false; - - readThread.Join(); - serialPort.Close(); } private void ProcessSentence() { @@ -134,7 +99,7 @@ namespace GPSDOTimeSync.TimeProviders.NMEA { SentenceReceived?.Invoke(new NMEASentence(true, talker, messageType, data, checksum, sentence)); } - private void ProcessByte(byte b) { + protected override void ProcessByte(byte b) { char c = (char) b; if (inSentence) { @@ -152,17 +117,5 @@ namespace GPSDOTimeSync.TimeProviders.NMEA { } } } - - private void ReadSerialPort() { - while (running) { - if (serialPort.BytesToRead > 0) { - int possibleCurrentByte = serialPort.ReadByte(); - - if (possibleCurrentByte != -1) { - ProcessByte((byte) possibleCurrentByte); - } - } - } - } } } diff --git a/GPSDOTimeSync/TimeProviders/Thunderbolt/ThunderboltSerialPort.cs b/GPSDOTimeSync/TimeProviders/Thunderbolt/ThunderboltSerialPort.cs index a767754..e96b6bf 100644 --- a/GPSDOTimeSync/TimeProviders/Thunderbolt/ThunderboltSerialPort.cs +++ b/GPSDOTimeSync/TimeProviders/Thunderbolt/ThunderboltSerialPort.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.IO.Ports; using System.Threading; @@ -36,18 +37,186 @@ namespace GPSDOTimeSync.Devices.Thunderbolt { } } - public class ThunderboltSerialPort { + //public class ThunderboltSerialPort { + // private static readonly byte CHAR_DLE = 0x10; + // private static readonly byte CHAR_ETX = 0x03; + + // private List packetBuffer; + // private bool inPacket; + + // private SerialPort serialPort; + + // private bool running; + // private Thread readThread; + + // /// + // /// A delegate which is called when a full packet is received over the serial port. + // /// + // /// The packet which was received. + // public delegate void PacketReceivedEventHandler(ThunderboltPacket packet); + + // /// + // /// An event which is called when a full packet is received over the serial port. + // /// + // public event PacketReceivedEventHandler PacketReceived; + + // /// + // /// Creates an instance of the ThunderboltSerialPort class, which processes serial data from a Thunderbolt and + // /// The serial port passed into the function must not be opened. + // /// + // /// The serial port on which to communicate with the Thunderbolt. + // public ThunderboltSerialPort(SerialPort serialPort) { + // packetBuffer = new List(); + // inPacket = false; + + // this.serialPort = serialPort; + + // readThread = new Thread(ReadSerialPort); + // readThread.Name = "ThunderboltSerialPort Read"; + // } + + // /// + // /// Begins processing serial data and firing PacketReceived events. + // /// + // public void Open() { + // running = true; + + // serialPort.Open(); + // readThread.Start(); + // } + + // /// + // /// Stops processing serial data and firing PacketReceived events. + // /// + // public void Close() { + // running = false; + + // readThread.Join(); + // serialPort.Close(); + // } + + // /// + // /// Converts a stuffed byte list (one where all 0x10 bytes are replaced with 0x10 0x10) into an unstuffed byte list. + // /// + // /// A reference to the list containing the data to be unstuffed. + // /// True if the unstuffing was successful, false otherwise. + // private bool Unstuff(ref List data) { + // List newData = new List(); + + // bool inStuffedDLE = false; + // foreach (byte b in data) { + // if (b == CHAR_DLE) { + // if (!inStuffedDLE) { + // newData.Add(b); + // inStuffedDLE = true; + // } else { + // // If we see a DLE after we've already seen one (inStuffedDLE == true), we don't need to add the byte to the list because we already did when the first byte was encountered + // // Just set the flag to false so that if this stuffed DLE is immediately followed by another, it will be correctly parsed + // inStuffedDLE = false; + // } + // } else { + // if (inStuffedDLE) { + // return false; + // } + + // newData.Add(b); + // } + // } + + // data = newData; + + // return true; + // } + + // private void ProcessPacket() { + // byte id = packetBuffer[1]; + + // // Grab only the data - not the first [DLE] or the last [DLE][ETX] + // List data = packetBuffer.GetRange(2, packetBuffer.Count - 4); + // bool isPacketValid = Unstuff(ref data); + + // ThunderboltPacket packet; + + // if (isPacketValid) { + // packet = new ThunderboltPacket(isPacketValid, id, data, packetBuffer); + // } else { + // packet = new ThunderboltPacket(isPacketValid, 0, new List(), new List()); + // } + + // PacketReceived?.Invoke(packet); + // } + + // // TODO: Improve this? Currently, if a malformed packet comes in it can take many more packets (totalling up to almost 5 to 10 seconds of time) + // // to bring the decoder back into sync with the true packets. It's probably only an issue when the Thunderbolt is initially plugged in, + // // as that's probably the only time we'd see malformed packets on the serial port, since we could connect in the middle of a packet. + // // Consider if this is really an issue, and think of a better way of decoding the packets to avoid this. + // private void ProcessByte(byte b) { + // // There aren't any packets this long, but during a corrupted or malformed packet the buffer can get quite large before the error + // // is fixed somehow. To prevent the almost 20 second delay between time packets that can be caused by some malformed packets, + // // just reset everything if the buffer's getting too long. + // // We should make sure the user knows, so send a invalid packet to them. + // if (packetBuffer.Count >= 128) { + // packetBuffer.Clear(); + // inPacket = false; + + // PacketReceived?.Invoke(new ThunderboltPacket(false, 0, new List(), new List())); + // } + + // if (inPacket) { + // packetBuffer.Add(b); + + // // Check buffer length to ensure we've reached a plausible end of packet. + // // 5 bytes is [DLE]<1 byte of data>[DLE][ETX] + // // Must check if previous character is a [DLE], otherwise an ETX with a malformed and unstuffed [DLE] will cause issues + // if (b == CHAR_ETX && packetBuffer.Count >= 5 && packetBuffer[packetBuffer.Count - 2] == CHAR_DLE) { + // int numberOfPrecedingDLEs = 0; + + // // Count number of DLEs, excluding the first two bytes (initial DLE and id) + // for (int i = 2; i < packetBuffer.Count; ++i) { + // if (packetBuffer[i] == CHAR_DLE) { + // ++numberOfPrecedingDLEs; + // } + // } + + // // Odd number (greater than zero) of DLEs means the ETX does in fact signify the end of the packet + // if (numberOfPrecedingDLEs % 2 == 1 && numberOfPrecedingDLEs > 0) { + // ProcessPacket(); + + // packetBuffer.Clear(); + // inPacket = false; + // } + // } + // } else { + // // A DLE received when not currently in a packet signifies the beginning of a packet + // if (b == CHAR_DLE) { + // packetBuffer.Add(b); + + // inPacket = true; + // } + // } + // } + + // private void ReadSerialPort() { + // while (running) { + // if (serialPort.BytesToRead > 0) { + // int possibleCurrentByte = serialPort.ReadByte(); + + // if (possibleCurrentByte != -1) { + // // Once we're sure the byte that was read wasn't -1 (which signifies the end of the read), we're safe to cast to a byte + // ProcessByte((byte) possibleCurrentByte); + // } + // } + // } + // } + //} + + class ThunderboltSerialPort : SerialPortProcessor { private static readonly byte CHAR_DLE = 0x10; private static readonly byte CHAR_ETX = 0x03; private List packetBuffer; private bool inPacket; - private SerialPort serialPort; - - private bool running; - private Thread readThread; - /// /// A delegate which is called when a full packet is received over the serial port. /// @@ -59,39 +228,9 @@ namespace GPSDOTimeSync.Devices.Thunderbolt { /// public event PacketReceivedEventHandler PacketReceived; - /// - /// Creates an instance of the ThunderboltSerialPort class, which processes serial data from a Thunderbolt and - /// The serial port passed into the function must not be opened. - /// - /// The serial port on which to communicate with the Thunderbolt. - public ThunderboltSerialPort(SerialPort serialPort) { + public ThunderboltSerialPort(SerialPort serialPort) : base(serialPort) { packetBuffer = new List(); inPacket = false; - - this.serialPort = serialPort; - - readThread = new Thread(ReadSerialPort); - readThread.Name = "ThunderboltSerialPort Read"; - } - - /// - /// Begins processing serial data and firing PacketReceived events. - /// - public void Open() { - running = true; - - serialPort.Open(); - readThread.Start(); - } - - /// - /// Stops processing serial data and firing PacketReceived events. - /// - public void Close() { - running = false; - - readThread.Join(); - serialPort.Close(); } /// @@ -145,11 +284,7 @@ namespace GPSDOTimeSync.Devices.Thunderbolt { PacketReceived?.Invoke(packet); } - // TODO: Improve this? Currently, if a malformed packet comes in it can take many more packets (totalling up to almost 5 to 10 seconds of time) - // to bring the decoder back into sync with the true packets. It's probably only an issue when the Thunderbolt is initially plugged in, - // as that's probably the only time we'd see malformed packets on the serial port, since we could connect in the middle of a packet. - // Consider if this is really an issue, and think of a better way of decoding the packets to avoid this. - private void ProcessByte(byte b) { + protected override void ProcessByte(byte b) { // There aren't any packets this long, but during a corrupted or malformed packet the buffer can get quite large before the error // is fixed somehow. To prevent the almost 20 second delay between time packets that can be caused by some malformed packets, // just reset everything if the buffer's getting too long. @@ -194,18 +329,5 @@ namespace GPSDOTimeSync.Devices.Thunderbolt { } } } - - private void ReadSerialPort() { - while (running) { - if (serialPort.BytesToRead > 0) { - int possibleCurrentByte = serialPort.ReadByte(); - - if (possibleCurrentByte != -1) { - // Once we're sure the byte that was read wasn't -1 (which signifies the end of the read), we're safe to cast to a byte - ProcessByte((byte) possibleCurrentByte); - } - } - } - } } } \ No newline at end of file