1 // Licensed to the .NET Foundation under one or more agreements. 2 // The .NET Foundation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 // Notes on test capabilities: 6 // 7 // A single port just means there's at least one port, which can be opened by the test application 8 // A loopback port is a port where the DATA is looped back, but the HANDSHAKE is not. Several tests 9 // rely on enabling RTS/CTS handshake to block transmissions from the port, but if RTS/CTS is looped-back 10 // the port will never block and the tests will either hang or fail (we could perhaps detect this when probing for loopback ports) 11 // A null-modem connection is available when you have at least two openable ports, where some pair of ports is connected with 12 // a null modem connection - i.e. TX/RX and CTS/RTS are crossed between the ports. 13 14 using System; 15 using System.Diagnostics; 16 using System.IO; 17 using System.IO.Ports; 18 using System.Threading; 19 using System.Threading.Tasks; 20 using Xunit.Sdk; 21 22 namespace Legacy.Support 23 { 24 public class SerialPortConnection 25 { VerifyConnection(string portName1, string portName2)26 public static bool VerifyConnection(string portName1, string portName2) 27 { 28 using (SerialPort com1 = new SerialPort(portName1, 115200)) 29 using (SerialPort com2 = new SerialPort(portName2, 115200)) 30 { 31 bool connectionVerified; 32 try 33 { 34 com1.Open(); 35 com2.Open(); 36 connectionVerified = VerifyReadWrite(com1, com2); 37 } 38 catch (Exception) 39 { 40 // One of the com ports does not exist on the machine that this is being run on 41 // thus their can not be a connection between com1 and com2 42 connectionVerified = false; 43 } 44 return connectionVerified; 45 } 46 } 47 VerifyLoopback(string portName)48 public static bool VerifyLoopback(string portName) 49 { 50 using (SerialPort com = new SerialPort(portName, 115200)) 51 { 52 bool loopbackVerified; 53 try 54 { 55 com.Open(); 56 loopbackVerified = VerifyReadWrite(com, com); 57 } 58 catch (Exception) 59 { 60 // The com ports does not exist on the machine that this is being run on 61 // thus their can not be a loopback between the ports 62 loopbackVerified = false; 63 } 64 return loopbackVerified; 65 } 66 } 67 VerifyReadWrite(SerialPort com1, SerialPort com2)68 private static bool VerifyReadWrite(SerialPort com1, SerialPort com2) 69 { 70 try 71 { 72 com1.ReadTimeout = 1000; 73 com2.ReadTimeout = 1000; 74 com1.WriteTimeout = 1000; 75 com2.WriteTimeout = 1000; 76 77 com1.WriteLine("Ping"); 78 79 if ("Ping" != com2.ReadLine()) 80 { 81 return false; 82 } 83 84 com2.WriteLine("Response"); 85 86 if ("Response" != com1.ReadLine()) 87 { 88 return false; 89 } 90 91 return true; 92 } 93 catch (TimeoutException) 94 { 95 return false; 96 } 97 } 98 99 /// <summary> 100 /// Not all types of serial ports block transmission completely when they're trying to transmit 101 /// with flow-control set against them. Some ports transmit a certain amount of data onward to 102 /// the hardware before stopping, so if you want them to block, you need to send more than 103 /// this minimum of data 104 /// 105 /// This routine performs a series of probes of the behaviour of a port, to establish how much 106 /// data is needed - the length of the probe packet is doubled on each probe up to a maximum of 8192 bytes 107 /// 108 /// A traditional 16550 UART port will block on the first byte. Some FTDI USB-Serial devices need more than 4096 bytes before 109 /// they block 110 /// </summary> 111 /// <returns>The smallest probe </returns> MeasureFlowControlCapabilities(string portName)112 public static FlowControlCapabilities MeasureFlowControlCapabilities(string portName) 113 { 114 for (int probeBase = 1; probeBase <= 65536; probeBase *= 2) 115 { 116 // We always probe one over the powers of two to make sure we just exceed common buffer sizes 117 int probeLength; 118 probeLength = probeBase + 1; 119 int bufferSize = MeasureTransmitBufferSize(portName, probeLength); 120 if (bufferSize < probeLength) 121 { 122 Debug.WriteLine("{0}: Found blocking packet of length {1}, hardware buffer {2}", portName, probeLength, bufferSize); 123 return new FlowControlCapabilities(probeLength, bufferSize, true); 124 } 125 } 126 127 Debug.WriteLine("Failed to achieve write blocking on serial port - no hardware flow-control available"); 128 return new FlowControlCapabilities(0, -1, false); 129 } 130 131 /// <summary> 132 /// Measure the amount of data which can be written to a blocked serial port before it 133 /// starts to queue-up 134 /// </summary> MeasureTransmitBufferSize(string portName, int probeLength)135 private static int MeasureTransmitBufferSize(string portName, int probeLength) 136 { 137 int measuredHardwareCapacity = 0; 138 using (var com = new SerialPort(portName, 115200)) 139 { 140 com.Handshake = Handshake.RequestToSend; 141 com.WriteTimeout = 2000; 142 com.Open(); 143 com.DiscardOutBuffer(); 144 145 var testBlock = new byte[probeLength]; 146 147 Task t = Task.Run(() => 148 { 149 int lastHardwareCapacity = -1; 150 for (int i = 0; i < 10; i++) 151 { 152 int queuedLength = com.BytesToWrite; 153 int hardwareCapacity = testBlock.Length - queuedLength; 154 155 if (hardwareCapacity == lastHardwareCapacity) 156 { 157 // We've had two readings the same 158 measuredHardwareCapacity = hardwareCapacity; 159 break; 160 } 161 // We're still pushing stuff out - wait for two readings the same 162 lastHardwareCapacity = hardwareCapacity; 163 Thread.Sleep(10); 164 } 165 com.Handshake = Handshake.None; 166 com.DiscardOutBuffer(); 167 }); 168 169 try 170 { 171 com.Write(testBlock, 0, testBlock.Length); 172 } 173 catch (IOException) 174 { 175 // We may see hardware exceptions when the task calls Discard 176 } 177 178 t.Wait(2000); 179 } 180 return measuredHardwareCapacity; 181 } 182 } 183 } 184