Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

173 строки
5.3 KiB

  1. using System.Collections.Generic;
  2. using System.Diagnostics;
  3. using System.IO.Ports;
  4. namespace ThunderboltTimeSync {
  5. public class ThunderboltPacket {
  6. /// <summary>
  7. /// The validity of the packet.
  8. /// If <code>false</code>, the values of <code>ID</code>, <code>Data</code> and <code>RawData</code> must not be used.
  9. /// If <code>true</code>, the packet may still contain invalid values or be of incorrect length.
  10. /// </summary>
  11. public bool IsPacketValid { get; }
  12. /// <summary>
  13. /// The ID of the packet.
  14. /// </summary>
  15. public byte ID { get; }
  16. /// <summary>
  17. /// The data contained within the packet.
  18. /// </summary>
  19. public List<byte> Data { get; }
  20. /// <summary>
  21. /// The raw data of the packet.
  22. /// Contains the initial [DLE] byte, and the terminating [DLE][ETX] bytes. [DLE] values within the data are still stuffed.
  23. /// </summary>
  24. public List<byte> RawData { get; }
  25. public ThunderboltPacket(bool isPacketValid, byte id, List<byte> data, List<byte> rawData) {
  26. IsPacketValid = isPacketValid;
  27. ID = id;
  28. Data = data;
  29. RawData = rawData;
  30. }
  31. }
  32. public class ThunderboltSerialPort {
  33. private static readonly byte CHAR_DLE = 0x10;
  34. private static readonly byte CHAR_ETX = 0x03;
  35. private List<byte> packetBuffer;
  36. private bool inPacket;
  37. private SerialPort serialPort;
  38. /// <summary>
  39. /// A delegate which is called when a full packet is received over the serial port.
  40. /// </summary>
  41. /// <param name="packet">The packet which was received.</param>
  42. public delegate void PacketReceivedEventHandler(ThunderboltPacket packet);
  43. /// <summary>
  44. /// An event which is called when a full packet is received over the serial port.
  45. /// </summary>
  46. public event PacketReceivedEventHandler PacketReceived;
  47. /// <summary>
  48. /// Creates an instance of the ThunderboltSerialPort class, which processes serial data from a Thunderbolt and
  49. /// The serial port passed into the function must not be opened, or have a delegate already attached to the DataReceived event.
  50. /// </summary>
  51. /// <param name="serialPort">The serial port on which to communicate with the Thunderbolt.</param>
  52. public ThunderboltSerialPort(SerialPort serialPort) {
  53. packetBuffer = new List<byte>();
  54. inPacket = false;
  55. this.serialPort = serialPort;
  56. serialPort.DataReceived += DataReceived;
  57. }
  58. /// <summary>
  59. /// Begins processing serial data.
  60. /// </summary>
  61. public void Open() {
  62. serialPort.Open();
  63. }
  64. /// <summary>
  65. /// Converts a stuffed byte list (one where all 0x10 bytes are replaced with 0x10 0x10) into an unstuffed byte list.
  66. /// </summary>
  67. /// <param name="data">A reference to the list containing the data to be unstuffed.</param>
  68. /// <returns>True if the unstuffing was successful, false otherwise.</returns>
  69. private bool Unstuff(ref List<byte> data) {
  70. List<byte> newData = new List<byte>();
  71. bool inStuffedDLE = false;
  72. foreach (byte b in data) {
  73. Debug.WriteLine(string.Format("{0:X2}", b));
  74. if (b == CHAR_DLE) {
  75. if (!inStuffedDLE) {
  76. newData.Add(b);
  77. inStuffedDLE = true;
  78. } else {
  79. // 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
  80. // Just set the flag to false so that if this stuffed DLE is immediately followed by another, it will be correctly parsed
  81. inStuffedDLE = false;
  82. }
  83. } else {
  84. if (inStuffedDLE) {
  85. return false;
  86. }
  87. newData.Add(b);
  88. }
  89. }
  90. data = newData;
  91. return true;
  92. }
  93. private void ProcessPacket() {
  94. byte id = packetBuffer[1];
  95. // Grab only the data - not the first [DLE]<id> or the last [DLE][ETX]
  96. List<byte> data = packetBuffer.GetRange(2, packetBuffer.Count - 4);
  97. bool isPacketValid = Unstuff(ref data);
  98. ThunderboltPacket packet;
  99. if (isPacketValid) {
  100. packet = new ThunderboltPacket(isPacketValid, id, data, packetBuffer);
  101. } else {
  102. packet = new ThunderboltPacket(isPacketValid, 0, new List<byte>(), new List<byte>());
  103. }
  104. PacketReceived?.Invoke(packet);
  105. }
  106. private void DataReceived(object sender, SerialDataReceivedEventArgs e) {
  107. int possibleCurrentByte;
  108. while ((possibleCurrentByte = serialPort.ReadByte()) != -1) {
  109. // 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
  110. byte currentByte = (byte) possibleCurrentByte;
  111. if (inPacket) {
  112. packetBuffer.Add(currentByte);
  113. // Check buffer length to ensure we've reached a plausible end of packet.
  114. // 5 bytes is [DLE]<id><1 byte of data>[DLE][ETX]
  115. if (currentByte == CHAR_ETX && packetBuffer.Count >= 5) {
  116. int numberOfPrecedingDLEs = 0;
  117. // Count number of DLEs, excluding the first two bytes (initial DLE and id)
  118. for (int i = 2; i < packetBuffer.Count; ++i) {
  119. if (packetBuffer[i] == CHAR_DLE) {
  120. ++numberOfPrecedingDLEs;
  121. }
  122. }
  123. // Odd number (greater than zero) of DLEs means the ETX does in fact signify the end of the packet
  124. if (numberOfPrecedingDLEs % 2 == 1 && numberOfPrecedingDLEs > 0) {
  125. ProcessPacket();
  126. packetBuffer.Clear();
  127. inPacket = false;
  128. }
  129. }
  130. } else {
  131. // A DLE received when not currently in a packet signifies the beginning of a packet
  132. if (currentByte == CHAR_DLE) {
  133. packetBuffer.Add(currentByte);
  134. inPacket = true;
  135. }
  136. }
  137. }
  138. }
  139. }
  140. }