Darien Figueroa / Mbed 2 deprecated repo3

Dependencies:   mbed MAX14720 MAX30205 USBDevice

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers serialportio.cs Source File

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 }