repo time

Dependencies:   mbed MAX14720 MAX30205 USBDevice

Revision:
20:6d2af70c92ab
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/HspGuiSourceV301/GuiDLLs/SerialWrap/serialportio.cs	Tue Apr 06 06:41:40 2021 +0000
@@ -0,0 +1,372 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Threading;
+using System.Reflection;
+using System.IO;
+
+using System.IO.Ports;
+using SerialPortTester;
+
+using Common.Logging;
+
+namespace SerialWrap
+{
+    public interface ISerialPortIo : IDisposable
+    {
+        string PortName { get; }
+        string ReadLine();
+        void WriteLine(string text);
+    }
+
+    public class SerialPortConfig
+    {
+        public string Name { get; private set; }
+        public int BaudRate { get; private set; }
+        public int DataBits { get; private set; }
+        public StopBits StopBits { get; private set; }
+        public Parity Parity { get; private set; }
+        public bool DtrEnable { get; private set; }
+        public bool RtsEnable { get; private set; }
+
+        public SerialPortConfig(
+            string name,
+            int baudRate,
+            int dataBits,
+            StopBits stopBits,
+            Parity parity,
+            bool dtrEnable,
+            bool rtsEnable)
+        {
+            if (String.IsNullOrWhiteSpace(name)) throw new ArgumentNullException("name");
+
+            this.RtsEnable = rtsEnable;
+            this.BaudRate = baudRate;
+            this.DataBits = dataBits;
+            this.StopBits = stopBits;
+            this.Parity = parity;
+            this.DtrEnable = dtrEnable;
+            this.Name = name;
+        }
+
+        public override string ToString()
+        {
+            return String.Format(
+                "{0} (Baud: {1}/DataBits: {2}/Parity: {3}/StopBits: {4}/{5})",
+                this.Name,
+                this.BaudRate,
+                this.DataBits,
+                this.Parity,
+                this.StopBits,
+                this.RtsEnable ? "RTS" : "No RTS");
+        }
+    }
+
+    // Wrapper around SerialPort
+    public class SerialPortIo : ISerialPortIo
+    {
+        protected ILog Log { get; private set; }
+        static readonly ILog s_Log = LogManager.GetLogger(typeof(SerialPortIo));
+
+        readonly SerialPort _port;
+        readonly Stream _internalSerialStream;
+
+        private int readTimeout;
+        private int writeTimeout;
+
+        public int ReadTimeout
+        {
+            get { return readTimeout; }
+            set 
+            {
+                readTimeout = value;
+                _port.ReadTimeout = readTimeout;
+            }
+        }
+
+        public int WriteTimeout
+        {
+            get { return writeTimeout; }
+            set
+            {
+                writeTimeout = value;
+                _port.WriteTimeout = writeTimeout;
+            }
+        }
+
+        public void DiscardInBuffer()
+        {
+            _port.DiscardInBuffer();
+        }
+
+        public bool IsOpen
+        {
+            get { return _port.IsOpen; }
+        }
+
+        public SerialPortIo(SerialPortConfig portConfig)
+        {
+            if (portConfig == null) throw new ArgumentNullException("portConfig");
+
+            this.Log = LogManager.GetLogger(this.GetType());
+
+            // http://zachsaw.blogspot.com/2010/07/net-serialport-woes.html
+            SerialPortFixer.Execute(portConfig.Name);
+
+            var port = new SerialPort(
+                portConfig.Name,
+                portConfig.BaudRate,
+                portConfig.Parity,
+                portConfig.DataBits,
+                portConfig.StopBits)
+            {
+                RtsEnable = portConfig.RtsEnable,
+                DtrEnable = portConfig.DtrEnable,
+                ReadTimeout = 5000,
+                WriteTimeout = 5000
+            };
+            port.Open();
+
+            try
+            {
+                this._internalSerialStream = port.BaseStream;
+                this._port = port;
+                this._port.DiscardInBuffer();
+                this._port.DiscardOutBuffer();
+            }
+            catch (Exception ex)
+            {
+                Stream internalStream = this._internalSerialStream;
+
+                if (internalStream == null)
+                {
+                    FieldInfo field = typeof(SerialPort).GetField(
+                        "internalSerialStream",
+                        BindingFlags.Instance | BindingFlags.NonPublic);
+
+                    // This will happen if the SerialPort class is changed
+                    // in future versions of the .NET Framework
+                    if (field == null)
+                    {
+                        this.Log.WarnFormat(
+                            "An exception occured while creating the serial port adaptor, "
+                            + "the internal stream reference was not acquired and we were unable "
+                            + "to get it using reflection. The serial port may not be accessible "
+                            + "any further until the serial port object finalizer has been run: {0}",
+                            ex);
+
+                        throw;
+                    }
+
+                    internalStream = (Stream)field.GetValue(port);
+                }
+
+                this.Log.DebugFormat(
+                    "An error occurred while constructing the serial port adaptor: {0}", ex);
+
+                SafeDisconnect(port, internalStream);
+                throw;
+            }
+        }
+
+        public string PortName
+        {
+            get { return this._port.PortName; }
+        }
+
+        public int Read(char[] buffer, int offset, int count)
+        {
+            return this._port.Read(buffer, offset, count);
+        }
+
+        public int Read(byte[] buffer, int offset, int count)
+        {
+            return this._port.Read(buffer, offset, count);
+        }
+
+        public string ReadLine()
+        {
+            //return this._port.ReadTo(Environment.NewLine);
+            return this._port.ReadLine();
+        }
+
+        public string ReadExisting()
+        {
+            return this._port.ReadExisting();
+        }
+
+        public void Write(string text)
+        {
+            this._port.Write(text);
+        }
+
+        public void Write(char[] buffer, int offset, int count)
+        {
+            this._port.Write(buffer, offset, count);
+        }
+    
+
+        public void WriteLine(string text)
+        {
+            //this._port.Write(text);
+            //this._port.Write("\r");
+            this._port.WriteLine(text);
+        }
+
+        public void Dispose()
+        {
+            this.Dispose(true);
+        }
+
+        protected void Dispose(bool disposing)
+        {
+            SafeDisconnect(this._port, this._internalSerialStream);
+
+            if (disposing)
+            {
+                GC.SuppressFinalize(this);
+            }
+        }
+
+        /// <summary>
+        /// Safely closes a serial port and its internal stream even if
+        /// a USB serial interface was physically removed from the system
+        /// in a reliable manner.
+        /// </summary>
+        /// <param name="port"></param>
+        /// <param name="internalSerialStream"></param>
+        /// <remarks>
+        /// The <see cref="SerialPort"/> class has 3 different problems in disposal
+        /// in case of a USB serial device that is physically removed:
+        /// 
+        /// 1. The eventLoopRunner is asked to stop and <see cref="SerialPort.IsOpen"/> 
+        /// returns false. Upon disposal this property is checked and closing 
+        /// the internal serial stream is skipped, thus keeping the original 
+        /// handle open indefinitely (until the finalizer runs which leads to the next problem)
+        /// 
+        /// The solution for this one is to manually close the internal serial stream.
+        /// We can get its reference by <see cref="SerialPort.BaseStream" />
+        /// before the exception has happened or by reflection and getting the 
+        /// "internalSerialStream" field.
+        /// 
+        /// 2. Closing the internal serial stream throws an exception and closes 
+        /// the internal handle without waiting for its eventLoopRunner thread to finish, 
+        /// causing an uncatchable ObjectDisposedException from it later on when the finalizer 
+        /// runs (which oddly avoids throwing the exception but still fails to wait for 
+        /// the eventLoopRunner).
+        /// 
+        /// The solution is to manually ask the event loop runner thread to shutdown
+        /// (via reflection) and waiting for it before closing the internal serial stream.
+        /// 
+        /// 3. Since Dispose throws exceptions, the finalizer is not suppressed.
+        /// 
+        /// The solution is to suppress their finalizers at the beginning.
+        /// </remarks>
+        static void SafeDisconnect(SerialPort port, Stream internalSerialStream)
+        {
+            GC.SuppressFinalize(port);
+            GC.SuppressFinalize(internalSerialStream);
+
+            ShutdownEventLoopHandler(internalSerialStream);
+
+            try
+            {
+                s_Log.DebugFormat("Disposing internal serial stream");
+                internalSerialStream.Close();
+            }
+            catch (Exception ex)
+            {
+                s_Log.DebugFormat(
+                    "Exception in serial stream shutdown of port {0}: {1}", port.PortName, ex);
+            }
+
+            try
+            {
+                s_Log.DebugFormat("Disposing serial port");
+                port.Close();
+            }
+            catch (Exception ex)
+            {
+                s_Log.DebugFormat("Exception in port {0} shutdown: {1}", port.PortName, ex);
+            }
+        }
+
+        static void ShutdownEventLoopHandler(Stream internalSerialStream)
+        {
+            try
+            {
+                s_Log.DebugFormat("Working around .NET SerialPort class Dispose bug");
+
+                FieldInfo eventRunnerField = internalSerialStream.GetType()
+                    .GetField("eventRunner", BindingFlags.NonPublic | BindingFlags.Instance);
+
+                if (eventRunnerField == null)
+                {
+                    s_Log.WarnFormat(
+                        "Unable to find EventLoopRunner field. "
+                        + "SerialPort workaround failure. Application may crash after "
+                        + "disposing SerialPort unless .NET 1.1 unhandled exception "
+                        + "policy is enabled from the application's config file.");
+                }
+                else
+                {
+                    object eventRunner = eventRunnerField.GetValue(internalSerialStream);
+                    Type eventRunnerType = eventRunner.GetType();
+
+                    FieldInfo endEventLoopFieldInfo = eventRunnerType.GetField(
+                        "endEventLoop", BindingFlags.Instance | BindingFlags.NonPublic);
+
+                    FieldInfo eventLoopEndedSignalFieldInfo = eventRunnerType.GetField(
+                        "eventLoopEndedSignal", BindingFlags.Instance | BindingFlags.NonPublic);
+
+                    FieldInfo waitCommEventWaitHandleFieldInfo = eventRunnerType.GetField(
+                        "waitCommEventWaitHandle", BindingFlags.Instance | BindingFlags.NonPublic);
+
+                    if (endEventLoopFieldInfo == null
+                        || eventLoopEndedSignalFieldInfo == null
+                        || waitCommEventWaitHandleFieldInfo == null)
+                    {
+                        s_Log.WarnFormat(
+                            "Unable to find the EventLoopRunner internal wait handle or loop signal fields. "
+                            + "SerialPort workaround failure. Application may crash after "
+                            + "disposing SerialPort unless .NET 1.1 unhandled exception "
+                            + "policy is enabled from the application's config file.");
+                    }
+                    else
+                    {
+                        s_Log.DebugFormat(
+                            "Waiting for the SerialPort internal EventLoopRunner thread to finish...");
+
+                        var eventLoopEndedWaitHandle =
+                            (WaitHandle)eventLoopEndedSignalFieldInfo.GetValue(eventRunner);
+                        var waitCommEventWaitHandle =
+                            (ManualResetEvent)waitCommEventWaitHandleFieldInfo.GetValue(eventRunner);
+
+                        endEventLoopFieldInfo.SetValue(eventRunner, true);
+
+                        // Sometimes the event loop handler resets the wait handle
+                        // before exiting the loop and hangs (in case of USB disconnect)
+                        // In case it takes too long, brute-force it out of its wait by 
+                        // setting the handle again.
+                        do
+                        {
+                            waitCommEventWaitHandle.Set();
+                        } while (!eventLoopEndedWaitHandle.WaitOne(2000));
+
+                        s_Log.DebugFormat("Wait completed. Now it is safe to continue disposal.");
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                s_Log.ErrorFormat(
+                    "SerialPort workaround failure. Application may crash after "
+                    + "disposing SerialPort unless .NET 1.1 unhandled exception "
+                    + "policy is enabled from the application's config file: {0}",
+                    ex);
+            }
+        }
+    }
+}
\ No newline at end of file