Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Dependencies: mbed MAX14720 MAX30205 USBDevice
serialportio.cs
00001 using System; 00002 using System.Collections.Generic; 00003 using System.Linq; 00004 using System.Text; 00005 using System.Threading.Tasks; 00006 using System.Threading; 00007 using System.Reflection; 00008 using System.IO; 00009 00010 using System.IO.Ports; 00011 using SerialPortTester; 00012 00013 using Common.Logging; 00014 00015 namespace SerialWrap 00016 { 00017 public interface ISerialPortIo : IDisposable 00018 { 00019 string PortName { get; } 00020 string ReadLine(); 00021 void WriteLine(string text); 00022 } 00023 00024 public class SerialPortConfig 00025 { 00026 public string Name { get; private set; } 00027 public int BaudRate { get; private set; } 00028 public int DataBits { get; private set; } 00029 public StopBits StopBits { get; private set; } 00030 public Parity Parity { get; private set; } 00031 public bool DtrEnable { get; private set; } 00032 public bool RtsEnable { get; private set; } 00033 00034 public SerialPortConfig( 00035 string name, 00036 int baudRate, 00037 int dataBits, 00038 StopBits stopBits, 00039 Parity parity, 00040 bool dtrEnable, 00041 bool rtsEnable) 00042 { 00043 if (String.IsNullOrWhiteSpace(name)) throw new ArgumentNullException("name"); 00044 00045 this.RtsEnable = rtsEnable; 00046 this.BaudRate = baudRate; 00047 this.DataBits = dataBits; 00048 this.StopBits = stopBits; 00049 this.Parity = parity; 00050 this.DtrEnable = dtrEnable; 00051 this.Name = name; 00052 } 00053 00054 public override string ToString() 00055 { 00056 return String.Format( 00057 "{0} (Baud: {1}/DataBits: {2}/Parity: {3}/StopBits: {4}/{5})", 00058 this.Name, 00059 this.BaudRate, 00060 this.DataBits, 00061 this.Parity, 00062 this.StopBits, 00063 this.RtsEnable ? "RTS" : "No RTS"); 00064 } 00065 } 00066 00067 // Wrapper around SerialPort 00068 public class SerialPortIo : ISerialPortIo 00069 { 00070 protected ILog Log { get; private set; } 00071 static readonly ILog s_Log = LogManager.GetLogger(typeof(SerialPortIo)); 00072 00073 readonly SerialPort _port; 00074 readonly Stream _internalSerialStream; 00075 00076 private int readTimeout; 00077 private int writeTimeout; 00078 00079 public int ReadTimeout 00080 { 00081 get { return readTimeout; } 00082 set 00083 { 00084 readTimeout = value; 00085 _port.ReadTimeout = readTimeout; 00086 } 00087 } 00088 00089 public int WriteTimeout 00090 { 00091 get { return writeTimeout; } 00092 set 00093 { 00094 writeTimeout = value; 00095 _port.WriteTimeout = writeTimeout; 00096 } 00097 } 00098 00099 public void DiscardInBuffer() 00100 { 00101 _port.DiscardInBuffer(); 00102 } 00103 00104 public bool IsOpen 00105 { 00106 get { return _port.IsOpen; } 00107 } 00108 00109 public SerialPortIo(SerialPortConfig portConfig) 00110 { 00111 if (portConfig == null) throw new ArgumentNullException("portConfig"); 00112 00113 this.Log = LogManager.GetLogger(this.GetType()); 00114 00115 // http://zachsaw.blogspot.com/2010/07/net-serialport-woes.html 00116 SerialPortFixer.Execute(portConfig.Name); 00117 00118 var port = new SerialPort( 00119 portConfig.Name, 00120 portConfig.BaudRate, 00121 portConfig.Parity, 00122 portConfig.DataBits, 00123 portConfig.StopBits) 00124 { 00125 RtsEnable = portConfig.RtsEnable, 00126 DtrEnable = portConfig.DtrEnable, 00127 ReadTimeout = 5000, 00128 WriteTimeout = 5000 00129 }; 00130 port.Open(); 00131 00132 try 00133 { 00134 this._internalSerialStream = port.BaseStream; 00135 this._port = port; 00136 this._port.DiscardInBuffer(); 00137 this._port.DiscardOutBuffer(); 00138 } 00139 catch (Exception ex) 00140 { 00141 Stream internalStream = this._internalSerialStream; 00142 00143 if (internalStream == null) 00144 { 00145 FieldInfo field = typeof(SerialPort).GetField( 00146 "internalSerialStream", 00147 BindingFlags.Instance | BindingFlags.NonPublic); 00148 00149 // This will happen if the SerialPort class is changed 00150 // in future versions of the .NET Framework 00151 if (field == null) 00152 { 00153 this.Log.WarnFormat( 00154 "An exception occured while creating the serial port adaptor, " 00155 + "the internal stream reference was not acquired and we were unable " 00156 + "to get it using reflection. The serial port may not be accessible " 00157 + "any further until the serial port object finalizer has been run: {0}", 00158 ex); 00159 00160 throw; 00161 } 00162 00163 internalStream = (Stream)field.GetValue(port); 00164 } 00165 00166 this.Log.DebugFormat( 00167 "An error occurred while constructing the serial port adaptor: {0}", ex); 00168 00169 SafeDisconnect(port, internalStream); 00170 throw; 00171 } 00172 } 00173 00174 public string PortName 00175 { 00176 get { return this._port.PortName; } 00177 } 00178 00179 public int Read(char[] buffer, int offset, int count) 00180 { 00181 return this._port.Read(buffer, offset, count); 00182 } 00183 00184 public int Read(byte[] buffer, int offset, int count) 00185 { 00186 return this._port.Read(buffer, offset, count); 00187 } 00188 00189 public string ReadLine() 00190 { 00191 //return this._port.ReadTo(Environment.NewLine); 00192 return this._port.ReadLine(); 00193 } 00194 00195 public string ReadExisting() 00196 { 00197 return this._port.ReadExisting(); 00198 } 00199 00200 public void Write(string text) 00201 { 00202 this._port.Write(text); 00203 } 00204 00205 public void Write(char[] buffer, int offset, int count) 00206 { 00207 this._port.Write(buffer, offset, count); 00208 } 00209 00210 00211 public void WriteLine(string text) 00212 { 00213 //this._port.Write(text); 00214 //this._port.Write("\r"); 00215 this._port.WriteLine(text); 00216 } 00217 00218 public void Dispose() 00219 { 00220 this.Dispose(true); 00221 } 00222 00223 protected void Dispose(bool disposing) 00224 { 00225 SafeDisconnect(this._port, this._internalSerialStream); 00226 00227 if (disposing) 00228 { 00229 GC.SuppressFinalize(this); 00230 } 00231 } 00232 00233 /// <summary> 00234 /// Safely closes a serial port and its internal stream even if 00235 /// a USB serial interface was physically removed from the system 00236 /// in a reliable manner. 00237 /// </summary> 00238 /// <param name="port"></param> 00239 /// <param name="internalSerialStream"></param> 00240 /// <remarks> 00241 /// The <see cref="SerialPort"/> class has 3 different problems in disposal 00242 /// in case of a USB serial device that is physically removed: 00243 /// 00244 /// 1. The eventLoopRunner is asked to stop and <see cref="SerialPort.IsOpen"/> 00245 /// returns false. Upon disposal this property is checked and closing 00246 /// the internal serial stream is skipped, thus keeping the original 00247 /// handle open indefinitely (until the finalizer runs which leads to the next problem) 00248 /// 00249 /// The solution for this one is to manually close the internal serial stream. 00250 /// We can get its reference by <see cref="SerialPort.BaseStream" /> 00251 /// before the exception has happened or by reflection and getting the 00252 /// "internalSerialStream" field. 00253 /// 00254 /// 2. Closing the internal serial stream throws an exception and closes 00255 /// the internal handle without waiting for its eventLoopRunner thread to finish, 00256 /// causing an uncatchable ObjectDisposedException from it later on when the finalizer 00257 /// runs (which oddly avoids throwing the exception but still fails to wait for 00258 /// the eventLoopRunner). 00259 /// 00260 /// The solution is to manually ask the event loop runner thread to shutdown 00261 /// (via reflection) and waiting for it before closing the internal serial stream. 00262 /// 00263 /// 3. Since Dispose throws exceptions, the finalizer is not suppressed. 00264 /// 00265 /// The solution is to suppress their finalizers at the beginning. 00266 /// </remarks> 00267 static void SafeDisconnect(SerialPort port, Stream internalSerialStream) 00268 { 00269 GC.SuppressFinalize(port); 00270 GC.SuppressFinalize(internalSerialStream); 00271 00272 ShutdownEventLoopHandler(internalSerialStream); 00273 00274 try 00275 { 00276 s_Log.DebugFormat("Disposing internal serial stream"); 00277 internalSerialStream.Close(); 00278 } 00279 catch (Exception ex) 00280 { 00281 s_Log.DebugFormat( 00282 "Exception in serial stream shutdown of port {0}: {1}", port.PortName, ex); 00283 } 00284 00285 try 00286 { 00287 s_Log.DebugFormat("Disposing serial port"); 00288 port.Close(); 00289 } 00290 catch (Exception ex) 00291 { 00292 s_Log.DebugFormat("Exception in port {0} shutdown: {1}", port.PortName, ex); 00293 } 00294 } 00295 00296 static void ShutdownEventLoopHandler(Stream internalSerialStream) 00297 { 00298 try 00299 { 00300 s_Log.DebugFormat("Working around .NET SerialPort class Dispose bug"); 00301 00302 FieldInfo eventRunnerField = internalSerialStream.GetType() 00303 .GetField("eventRunner", BindingFlags.NonPublic | BindingFlags.Instance); 00304 00305 if (eventRunnerField == null) 00306 { 00307 s_Log.WarnFormat( 00308 "Unable to find EventLoopRunner field. " 00309 + "SerialPort workaround failure. Application may crash after " 00310 + "disposing SerialPort unless .NET 1.1 unhandled exception " 00311 + "policy is enabled from the application's config file."); 00312 } 00313 else 00314 { 00315 object eventRunner = eventRunnerField.GetValue(internalSerialStream); 00316 Type eventRunnerType = eventRunner.GetType(); 00317 00318 FieldInfo endEventLoopFieldInfo = eventRunnerType.GetField( 00319 "endEventLoop", BindingFlags.Instance | BindingFlags.NonPublic); 00320 00321 FieldInfo eventLoopEndedSignalFieldInfo = eventRunnerType.GetField( 00322 "eventLoopEndedSignal", BindingFlags.Instance | BindingFlags.NonPublic); 00323 00324 FieldInfo waitCommEventWaitHandleFieldInfo = eventRunnerType.GetField( 00325 "waitCommEventWaitHandle", BindingFlags.Instance | BindingFlags.NonPublic); 00326 00327 if (endEventLoopFieldInfo == null 00328 || eventLoopEndedSignalFieldInfo == null 00329 || waitCommEventWaitHandleFieldInfo == null) 00330 { 00331 s_Log.WarnFormat( 00332 "Unable to find the EventLoopRunner internal wait handle or loop signal fields. " 00333 + "SerialPort workaround failure. Application may crash after " 00334 + "disposing SerialPort unless .NET 1.1 unhandled exception " 00335 + "policy is enabled from the application's config file."); 00336 } 00337 else 00338 { 00339 s_Log.DebugFormat( 00340 "Waiting for the SerialPort internal EventLoopRunner thread to finish..."); 00341 00342 var eventLoopEndedWaitHandle = 00343 (WaitHandle)eventLoopEndedSignalFieldInfo.GetValue(eventRunner); 00344 var waitCommEventWaitHandle = 00345 (ManualResetEvent)waitCommEventWaitHandleFieldInfo.GetValue(eventRunner); 00346 00347 endEventLoopFieldInfo.SetValue(eventRunner, true); 00348 00349 // Sometimes the event loop handler resets the wait handle 00350 // before exiting the loop and hangs (in case of USB disconnect) 00351 // In case it takes too long, brute-force it out of its wait by 00352 // setting the handle again. 00353 do 00354 { 00355 waitCommEventWaitHandle.Set(); 00356 } while (!eventLoopEndedWaitHandle.WaitOne(2000)); 00357 00358 s_Log.DebugFormat("Wait completed. Now it is safe to continue disposal."); 00359 } 00360 } 00361 } 00362 catch (Exception ex) 00363 { 00364 s_Log.ErrorFormat( 00365 "SerialPort workaround failure. Application may crash after " 00366 + "disposing SerialPort unless .NET 1.1 unhandled exception " 00367 + "policy is enabled from the application's config file: {0}", 00368 ex); 00369 } 00370 } 00371 } 00372 }
Generated on Thu Jul 28 2022 18:07:15 by
1.7.2