您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

210 行
7.0 KiB

  1. using System.Collections.Generic;
  2. using System.Diagnostics;
  3. using System.IO.Ports;
  4. using System.Linq;
  5. using System.Threading;
  6. namespace ThunderboltTimeSync.Devices.Thunderbolt {
  7. public class ThunderboltPacket {
  8. /// <summary>
  9. /// The validity of the packet.
  10. /// If <code>false</code>, the values of <code>ID</code>, <code>Data</code> and <code>RawData</code> must not be used.
  11. /// If <code>true</code>, the packet may still contain invalid values or be of incorrect length.
  12. /// </summary>
  13. public bool IsPacketValid { get; }
  14. /// <summary>
  15. /// The ID of the packet.
  16. /// </summary>
  17. public byte ID { get; }
  18. /// <summary>
  19. /// The data contained within the packet.
  20. /// </summary>
  21. public List<byte> Data { get; }
  22. /// <summary>
  23. /// The raw data of the packet.
  24. /// Contains the initial [DLE] byte, and the terminating [DLE][ETX] bytes. [DLE] values within the data are still stuffed.
  25. /// </summary>
  26. public List<byte> RawData { get; }
  27. public ThunderboltPacket(bool isPacketValid, byte id, List<byte> data, List<byte> rawData) {
  28. IsPacketValid = isPacketValid;
  29. ID = id;
  30. Data = data;
  31. RawData = rawData;
  32. }
  33. }
  34. public class ThunderboltSerialPort {
  35. private static readonly byte CHAR_DLE = 0x10;
  36. private static readonly byte CHAR_ETX = 0x03;
  37. private List<byte> packetBuffer;
  38. private bool inPacket;
  39. private SerialPort serialPort;
  40. private bool running;
  41. private Thread readThread;
  42. /// <summary>
  43. /// A delegate which is called when a full packet is received over the serial port.
  44. /// </summary>
  45. /// <param name="packet">The packet which was received.</param>
  46. public delegate void PacketReceivedEventHandler(ThunderboltPacket packet);
  47. /// <summary>
  48. /// An event which is called when a full packet is received over the serial port.
  49. /// </summary>
  50. public event PacketReceivedEventHandler PacketReceived;
  51. /// <summary>
  52. /// Creates an instance of the ThunderboltSerialPort class, which processes serial data from a Thunderbolt and
  53. /// The serial port passed into the function must not be opened.
  54. /// </summary>
  55. /// <param name="serialPort">The serial port on which to communicate with the Thunderbolt.</param>
  56. public ThunderboltSerialPort(SerialPort serialPort) {
  57. packetBuffer = new List<byte>();
  58. inPacket = false;
  59. this.serialPort = serialPort;
  60. readThread = new Thread(ReadSerialPort);
  61. }
  62. /// <summary>
  63. /// Begins processing serial data and firing PacketReceived events.
  64. /// </summary>
  65. public void Open() {
  66. running = true;
  67. serialPort.Open();
  68. readThread.Start();
  69. }
  70. /// <summary>
  71. /// Stops processing serial data and firing PacketReceived events.
  72. /// </summary>
  73. public void Close() {
  74. running = false;
  75. readThread.Join();
  76. serialPort.Close();
  77. }
  78. /// <summary>
  79. /// Converts a stuffed byte list (one where all 0x10 bytes are replaced with 0x10 0x10) into an unstuffed byte list.
  80. /// </summary>
  81. /// <param name="data">A reference to the list containing the data to be unstuffed.</param>
  82. /// <returns>True if the unstuffing was successful, false otherwise.</returns>
  83. private bool Unstuff(ref List<byte> data) {
  84. List<byte> newData = new List<byte>();
  85. bool inStuffedDLE = false;
  86. foreach (byte b in data) {
  87. if (b == CHAR_DLE) {
  88. if (!inStuffedDLE) {
  89. newData.Add(b);
  90. inStuffedDLE = true;
  91. } else {
  92. // 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
  93. // Just set the flag to false so that if this stuffed DLE is immediately followed by another, it will be correctly parsed
  94. inStuffedDLE = false;
  95. }
  96. } else {
  97. if (inStuffedDLE) {
  98. return false;
  99. }
  100. newData.Add(b);
  101. }
  102. }
  103. data = newData;
  104. return true;
  105. }
  106. private void ProcessPacket() {
  107. List<string> byteStrings = packetBuffer.Select(x => string.Format("{0:X2}", x)).ToList();
  108. Debug.WriteLine(string.Join(" ", byteStrings));
  109. byte id = packetBuffer[1];
  110. // Grab only the data - not the first [DLE]<id> or the last [DLE][ETX]
  111. List<byte> data = packetBuffer.GetRange(2, packetBuffer.Count - 4);
  112. bool isPacketValid = Unstuff(ref data);
  113. ThunderboltPacket packet;
  114. if (isPacketValid) {
  115. packet = new ThunderboltPacket(isPacketValid, id, data, packetBuffer);
  116. } else {
  117. packet = new ThunderboltPacket(isPacketValid, 0, new List<byte>(), new List<byte>());
  118. }
  119. PacketReceived?.Invoke(packet);
  120. }
  121. // 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)
  122. // to bring the decoder back into sync with the true packets. It's probably only an issue when the Thunderbolt is initially plugged in,
  123. // 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.
  124. // Consider if this is really an issue, and think of a better way of decoding the packets to avoid this.
  125. private void ReadSerialPort() {
  126. while (running) {
  127. int possibleCurrentByte = serialPort.ReadByte();
  128. if (possibleCurrentByte != -1) {
  129. // There aren't any packets this long, but during a corrupted or malformed packet the buffer can get quite large before the error
  130. // is fixed somehow. To prevent the almost 20 second delay between time packets that can be caused by some malformed packets,
  131. // just reset everything if the buffer's getting too long.
  132. // We should make sure the user knows, so send a invalid packet to them.
  133. if (packetBuffer.Count >= 128) {
  134. packetBuffer.Clear();
  135. inPacket = false;
  136. PacketReceived?.Invoke(new ThunderboltPacket(false, 0, new List<byte>(), new List<byte>()));
  137. }
  138. // 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
  139. byte currentByte = (byte) possibleCurrentByte;
  140. if (inPacket) {
  141. packetBuffer.Add(currentByte);
  142. // Check buffer length to ensure we've reached a plausible end of packet.
  143. // 5 bytes is [DLE]<id><1 byte of data>[DLE][ETX]
  144. // Must check if previous character is a [DLE], otherwise an ETX with a malformed and unstuffed [DLE] will cause issues
  145. if (currentByte == CHAR_ETX && packetBuffer.Count >= 5 && packetBuffer[packetBuffer.Count - 2] == CHAR_DLE) {
  146. int numberOfPrecedingDLEs = 0;
  147. // Count number of DLEs, excluding the first two bytes (initial DLE and id)
  148. for (int i = 2; i < packetBuffer.Count; ++i) {
  149. if (packetBuffer[i] == CHAR_DLE) {
  150. ++numberOfPrecedingDLEs;
  151. }
  152. }
  153. // Odd number (greater than zero) of DLEs means the ETX does in fact signify the end of the packet
  154. if (numberOfPrecedingDLEs % 2 == 1 && numberOfPrecedingDLEs > 0) {
  155. ProcessPacket();
  156. packetBuffer.Clear();
  157. inPacket = false;
  158. }
  159. }
  160. } else {
  161. // A DLE received when not currently in a packet signifies the beginning of a packet
  162. if (currentByte == CHAR_DLE) {
  163. packetBuffer.Add(currentByte);
  164. inPacket = true;
  165. }
  166. }
  167. }
  168. }
  169. }
  170. }
  171. }