using System.Collections.Generic;
using System.IO.Ports;
namespace ThunderboltTimeSync {
public class ThunderboltPacket {
///
/// The validity of the packet.
/// If false
, the values of ID
, Data
and RawData
must not be used.
/// If true
, the packet may still contain invalid values or be of incorrect length.
///
public bool IsPacketValid { get; }
///
/// The ID of the packet.
///
public byte ID { get; }
///
/// The data contained within the packet.
///
public List Data { get; }
///
/// The raw data of the packet.
/// Contains the initial [DLE] byte, and the terminating [DLE][ETX] bytes. [DLE] values within the data are still stuffed.
///
public List RawData { get; }
public ThunderboltPacket(bool isPacketValid, byte id, List data, List rawData) {
IsPacketValid = isPacketValid;
ID = id;
Data = data;
RawData = rawData;
}
}
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;
///
/// 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, or have a delegate already attached to the DataReceived event.
///
/// The serial port on which to communicate with the Thunderbolt.
public ThunderboltSerialPort(SerialPort serialPort) {
packetBuffer = new List();
inPacket = false;
this.serialPort = serialPort;
serialPort.DataReceived += DataReceived;
}
///
/// Begins processing serial data.
///
public void Open() {
serialPort.Open();
}
///
/// 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);
}
private void DataReceived(object sender, SerialDataReceivedEventArgs e) {
int possibleCurrentByte;
while ((possibleCurrentByte = serialPort.ReadByte()) != -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
byte currentByte = (byte) possibleCurrentByte;
if (inPacket) {
packetBuffer.Add(currentByte);
// Check buffer length to ensure we've reached a plausible end of packet.
// 5 bytes is [DLE]<1 byte of data>[DLE][ETX]
if (currentByte == CHAR_ETX && packetBuffer.Count >= 5) {
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 of DLEs means the ETX does in fact signify the end of the packet
if (numberOfPrecedingDLEs % 2 == 1) {
ProcessPacket();
packetBuffer.Clear();
inPacket = false;
}
}
} else {
// A DLE received when not currently in a packet signifies the beginning of a packet
if (currentByte == CHAR_DLE) {
packetBuffer.Add(currentByte);
inPacket = true;
}
}
}
}
}
}