From 54ed60d9f89698483aecfcdb8f52aaa93b69e74f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20H=C3=B6glinger-Stelzer?= Date: Mon, 26 Sep 2022 23:51:23 +0200 Subject: [PATCH] Clean-up --- .../ModemStatus.cs | 12 +- .../QueueStatus.cs | 268 ++--- Nefarius.Peripherals.SerialPort/SerialPort.cs | 993 ++++++++---------- .../Win32PInvoke/COMSTAT.cs | 19 - 4 files changed, 608 insertions(+), 684 deletions(-) delete mode 100644 Nefarius.Peripherals.SerialPort/Win32PInvoke/COMSTAT.cs diff --git a/Nefarius.Peripherals.SerialPort/ModemStatus.cs b/Nefarius.Peripherals.SerialPort/ModemStatus.cs index a7ad443..64529c4 100644 --- a/Nefarius.Peripherals.SerialPort/ModemStatus.cs +++ b/Nefarius.Peripherals.SerialPort/ModemStatus.cs @@ -7,9 +7,9 @@ namespace Nefarius.Peripherals.SerialPort; /// public readonly struct ModemStatus { - private readonly uint _status; + private readonly MODEM_STATUS_FLAGS _status; - internal ModemStatus(uint val) + internal ModemStatus(MODEM_STATUS_FLAGS val) { _status = val; } @@ -17,20 +17,20 @@ public readonly struct ModemStatus /// /// Condition of the Clear To Send signal. /// - public bool Cts => (_status & (uint)MODEM_STATUS_FLAGS.MS_CTS_ON) != 0; + public bool Cts => (_status & MODEM_STATUS_FLAGS.MS_CTS_ON) != 0; /// /// Condition of the Data Set Ready signal. /// - public bool Dsr => (_status & (uint)MODEM_STATUS_FLAGS.MS_DSR_ON) != 0; + public bool Dsr => (_status & MODEM_STATUS_FLAGS.MS_DSR_ON) != 0; /// /// Condition of the Receive Line Status Detection signal. /// - public bool Rlsd => (_status & (uint)MODEM_STATUS_FLAGS.MS_RLSD_ON) != 0; + public bool Rlsd => (_status & MODEM_STATUS_FLAGS.MS_RLSD_ON) != 0; /// /// Condition of the Ring Detection signal. /// - public bool Ring => (_status & (uint)MODEM_STATUS_FLAGS.MS_RING_ON) != 0; + public bool Ring => (_status & MODEM_STATUS_FLAGS.MS_RING_ON) != 0; } \ No newline at end of file diff --git a/Nefarius.Peripherals.SerialPort/QueueStatus.cs b/Nefarius.Peripherals.SerialPort/QueueStatus.cs index 69bf8de..9d36b5f 100644 --- a/Nefarius.Peripherals.SerialPort/QueueStatus.cs +++ b/Nefarius.Peripherals.SerialPort/QueueStatus.cs @@ -1,143 +1,149 @@ using System.Text; -using Nefarius.Peripherals.SerialPort.Win32PInvoke; -namespace Nefarius.Peripherals.SerialPort +namespace Nefarius.Peripherals.SerialPort; + +/// +/// Represents the current condition of the port queues. +/// +public readonly struct QueueStatus { - /// - /// Represents the current condition of the port queues. - /// - public struct QueueStatus + private const uint fCtsHold = 0x1; + private const uint fDsrHold = 0x2; + private const uint fRlsdHold = 0x4; + private const uint fXoffHold = 0x8; + private const uint fXoffSent = 0x10; + internal const uint fEof = 0x20; + private const uint fTxim = 0x40; + + private readonly uint _status; + private readonly uint _inQueue; + private readonly uint _outQueue; + private readonly uint _inQueueSize; + private readonly uint _outQueueSize; + + internal QueueStatus(uint stat, uint inQ, uint outQ, uint inQs, uint outQs) { - private readonly uint _status; - private readonly uint _inQueue; - private readonly uint _outQueue; - private readonly uint _inQueueSize; - private readonly uint _outQueueSize; + _status = stat; + _inQueue = inQ; + _outQueue = outQ; + _inQueueSize = inQs; + _outQueueSize = outQs; + } - internal QueueStatus(uint stat, uint inQ, uint outQ, uint inQs, uint outQs) + /// + /// Output is blocked by CTS handshaking. + /// + public bool CtsHold => (_status & fCtsHold) != 0; + + /// + /// Output is blocked by DRS handshaking. + /// + public bool DsrHold => (_status & fDsrHold) != 0; + + /// + /// Output is blocked by RLSD handshaking. + /// + public bool RlsdHold => (_status & fRlsdHold) != 0; + + /// + /// Output is blocked because software handshaking is enabled and XOFF was received. + /// + public bool XoffHold => (_status & fXoffHold) != 0; + + /// + /// Output was blocked because XOFF was sent and this station is not yet ready to receive. + /// + public bool XoffSent => (_status & fXoffSent) != 0; + + /// + /// There is a character waiting for transmission in the immediate buffer. + /// + public bool ImmediateWaiting => (_status & fTxim) != 0; + + /// + /// Number of bytes waiting in the input queue. + /// + public long InQueue => _inQueue; + + /// + /// Number of bytes waiting for transmission. + /// + public long OutQueue => _outQueue; + + /// + /// Total size of input queue (0 means information unavailable) + /// + public long InQueueSize => _inQueueSize; + + /// + /// Total size of output queue (0 means information unavailable) + /// + public long OutQueueSize => _outQueueSize; + + public override string ToString() + { + var m = new StringBuilder("The reception queue is ", 60); + if (_inQueueSize == 0) + m.Append("of unknown size and "); + else + m.Append(_inQueueSize + " bytes long and "); + if (_inQueue == 0) { - _status = stat; - _inQueue = inQ; - _outQueue = outQ; - _inQueueSize = inQs; - _outQueueSize = outQs; + m.Append("is empty."); + } + else if (_inQueue == 1) + { + m.Append("contains 1 byte."); + } + else + { + m.Append("contains "); + m.Append(_inQueue.ToString()); + m.Append(" bytes."); } - /// - /// Output is blocked by CTS handshaking. - /// - public bool CtsHold => (_status & COMSTAT.fCtsHold) != 0; - - /// - /// Output is blocked by DRS handshaking. - /// - public bool DsrHold => (_status & COMSTAT.fDsrHold) != 0; - - /// - /// Output is blocked by RLSD handshaking. - /// - public bool RlsdHold => (_status & COMSTAT.fRlsdHold) != 0; - - /// - /// Output is blocked because software handshaking is enabled and XOFF was received. - /// - public bool XoffHold => (_status & COMSTAT.fXoffHold) != 0; - - /// - /// Output was blocked because XOFF was sent and this station is not yet ready to receive. - /// - public bool XoffSent => (_status & COMSTAT.fXoffSent) != 0; - - /// - /// There is a character waiting for transmission in the immediate buffer. - /// - public bool ImmediateWaiting => (_status & COMSTAT.fTxim) != 0; - - /// - /// Number of bytes waiting in the input queue. - /// - public long InQueue => _inQueue; - - /// - /// Number of bytes waiting for transmission. - /// - public long OutQueue => _outQueue; - - /// - /// Total size of input queue (0 means information unavailable) - /// - public long InQueueSize => _inQueueSize; - - /// - /// Total size of output queue (0 means information unavailable) - /// - public long OutQueueSize => _outQueueSize; - - public override string ToString() + m.Append(" The transmission queue is "); + if (_outQueueSize == 0) + m.Append("of unknown size and "); + else + m.Append(_outQueueSize + " bytes long and "); + if (_outQueue == 0) { - var m = new StringBuilder("The reception queue is ", 60); - if (_inQueueSize == 0) - m.Append("of unknown size and "); - else - m.Append(_inQueueSize + " bytes long and "); - if (_inQueue == 0) - { - m.Append("is empty."); - } - else if (_inQueue == 1) - { - m.Append("contains 1 byte."); - } - else - { - m.Append("contains "); - m.Append(_inQueue.ToString()); - m.Append(" bytes."); - } - - m.Append(" The transmission queue is "); - if (_outQueueSize == 0) - m.Append("of unknown size and "); - else - m.Append(_outQueueSize + " bytes long and "); - if (_outQueue == 0) - { - m.Append("is empty"); - } - else if (_outQueue == 1) - { - m.Append("contains 1 byte. It is "); - } - else - { - m.Append("contains "); - m.Append(_outQueue.ToString()); - m.Append(" bytes. It is "); - } - - if (_outQueue > 0) - { - if (CtsHold || DsrHold || RlsdHold || XoffHold || XoffSent) - { - m.Append("holding on"); - if (CtsHold) m.Append(" CTS"); - if (DsrHold) m.Append(" DSR"); - if (RlsdHold) m.Append(" RLSD"); - if (XoffHold) m.Append(" Rx XOff"); - if (XoffSent) m.Append(" Tx XOff"); - } - else - { - m.Append("pumping data"); - } - } - - m.Append(". The immediate buffer is "); - if (ImmediateWaiting) - m.Append("full."); - else - m.Append("empty."); - return m.ToString(); + m.Append("is empty"); } + else if (_outQueue == 1) + { + m.Append("contains 1 byte. It is "); + } + else + { + m.Append("contains "); + m.Append(_outQueue.ToString()); + m.Append(" bytes. It is "); + } + + if (_outQueue > 0) + { + if (CtsHold || DsrHold || RlsdHold || XoffHold || XoffSent) + { + m.Append("holding on"); + if (CtsHold) m.Append(" CTS"); + if (DsrHold) m.Append(" DSR"); + if (RlsdHold) m.Append(" RLSD"); + if (XoffHold) m.Append(" Rx XOff"); + if (XoffSent) m.Append(" Tx XOff"); + } + else + { + m.Append("pumping data"); + } + } + + m.Append(". The immediate buffer is "); + if (ImmediateWaiting) + m.Append("full."); + else + m.Append("empty."); + return m.ToString(); } } \ No newline at end of file diff --git a/Nefarius.Peripherals.SerialPort/SerialPort.cs b/Nefarius.Peripherals.SerialPort/SerialPort.cs index 2ca73fc..62999ba 100644 --- a/Nefarius.Peripherals.SerialPort/SerialPort.cs +++ b/Nefarius.Peripherals.SerialPort/SerialPort.cs @@ -14,524 +14,8 @@ namespace Nefarius.Peripherals.SerialPort; /// PInvokeSerialPort main class. /// Borrowed from http://msdn.microsoft.com/en-us/magazine/cc301786.aspx ;) /// -public class SerialPort : IDisposable +public sealed class SerialPort : IDisposable { - /// - /// - /// For IDisposable - /// - public void Dispose() - { - Close(); - } - - /// - /// Opens the com port and configures it with the required settings - /// - /// false if the port could not be opened - [UsedImplicitly] - public bool Open() - { - var portDcb = new DCB(); - var commTimeouts = new COMMTIMEOUTS(); - - if (_online) return false; - - _hPort = PInvoke.CreateFile( - PortName, - FILE_ACCESS_FLAGS.FILE_GENERIC_READ | FILE_ACCESS_FLAGS.FILE_GENERIC_WRITE, - 0, - null, - FILE_CREATION_DISPOSITION.OPEN_EXISTING, FILE_FLAGS_AND_ATTRIBUTES.FILE_FLAG_OVERLAPPED, - null - ); - - if (_hPort.IsInvalid) - { - if (Marshal.GetLastWin32Error() == (int)WIN32_ERROR.ERROR_ACCESS_DENIED) return false; - throw new CommPortException("Port Open Failure"); - } - - _online = true; - - commTimeouts.WriteTotalTimeoutConstant = (uint)SendTimeoutConstant; - commTimeouts.WriteTotalTimeoutMultiplier = (uint)SendTimeoutMultiplier; - - portDcb.Init( - Parity is Parity.Odd or Parity.Even, - TxFlowCts, - TxFlowDsr, - (int)UseDtr, - RxGateDsr, - !TxWhenRxXoff, - TxFlowX, - RxFlowX, - (int)UseRts - ); - portDcb.BaudRate = (uint)BaudRate; - portDcb.ByteSize = (byte)DataBits; - portDcb.Parity = (DCB_PARITY)Parity; - portDcb.StopBits = (DCB_STOP_BITS)StopBits; - portDcb.XoffChar = new CHAR((byte)XoffChar); - portDcb.XonChar = new CHAR((byte)XonChar); - portDcb.XoffLim = (ushort)RxHighWater; - portDcb.XonLim = (ushort)RxLowWater; - - if (RxQueue != 0 || TxQueue != 0) - if (!PInvoke.SetupComm(_hPort, (uint)RxQueue, (uint)TxQueue)) - ThrowException("Bad queue settings"); - - if (!PInvoke.SetCommState(_hPort, portDcb)) ThrowException("Bad com settings"); - if (!PInvoke.SetCommTimeouts(_hPort, commTimeouts)) ThrowException("Bad timeout settings"); - - _stateBrk = 0; - switch (UseDtr) - { - case HsOutput.None: - _stateDtr = 0; - break; - case HsOutput.Online: - _stateDtr = 1; - break; - } - - switch (UseRts) - { - case HsOutput.None: - _stateRts = 0; - break; - case HsOutput.Online: - _stateRts = 1; - break; - } - - _checkSends = CheckAllSends; - - _writeOverlapped = new NativeOverlapped - { - EventHandle = _checkSends ? _writeEvent.SafeWaitHandle.DangerousGetHandle() : IntPtr.Zero - }; - - _writeCount = 0; - - _rxException = null; - _rxExceptionReported = false; - - _rxThread = new Thread(ReceiveThread) - { - Name = "CommBaseRx", - Priority = ThreadPriority.AboveNormal, - IsBackground = true - }; - - _rxThread.Start(); - Thread.Sleep(1); //Give rx thread time to start. By documentation, 0 should work, but it does not! - - _auto = false; - if (AfterOpen()) - { - _auto = AutoReopen; - return true; - } - - Close(); - - return false; - } - - /// - /// Closes the com port. - /// - [UsedImplicitly] - public void Close() - { - if (_online) - { - _auto = false; - BeforeClose(false); - InternalClose(); - _rxException = null; - } - } - - private void InternalClose() - { - PInvoke.CancelIo(_hPort); - if (_rxThread != null) - { - _rxThread.Abort(); - _rxThread = null; - } - - _hPort.Close(); - - _stateRts = 2; - _stateDtr = 2; - _stateBrk = 2; - _online = false; - } - - /// - /// Destructor (just in case) - /// - ~SerialPort() - { - Close(); - } - - /// - /// Block until all bytes in the queue have been transmitted. - /// - [UsedImplicitly] - public void Flush() - { - CheckOnline(); - CheckResult(); - } - - /// - /// Use this to throw exceptions in derived classes. Correctly handles threading issues - /// and closes the port if necessary. - /// - /// Description of fault - protected void ThrowException(string reason) - { - if (Thread.CurrentThread == _rxThread) throw new CommPortException(reason); - if (_online) - { - BeforeClose(true); - InternalClose(); - } - - if (_rxException == null) throw new CommPortException(reason); - throw new CommPortException(_rxException); - } - - /// - /// Queues bytes for transmission. - /// - /// Array of bytes to be sent - [UsedImplicitly] - public unsafe void Write(byte[] toSend) - { - CheckOnline(); - CheckResult(); - _writeCount = toSend.GetLength(0); - - fixed (byte* ptr = toSend) - fixed (NativeOverlapped* overlapped = &_writeOverlapped) - { - uint sent; - if (PInvoke.WriteFile(_hPort, ptr, (uint)_writeCount, &sent, overlapped)) - { - _writeCount -= (int)sent; - } - else - { - if (Marshal.GetLastWin32Error() != (int)WIN32_ERROR.ERROR_IO_PENDING) - ThrowException("Unexpected failure"); - } - } - } - - /// - /// Queues string for transmission. - /// - /// Array of bytes to be sent - [UsedImplicitly] - public void Write(string toSend) - { - Write(new ASCIIEncoding().GetBytes(toSend)); - } - - /// - /// Queues a single byte for transmission. - /// - /// Byte to be sent - [UsedImplicitly] - public void Write(byte toSend) - { - var b = new byte[1]; - b[0] = toSend; - Write(b); - } - - /// - /// Queues a single char for transmission. - /// - /// Byte to be sent - [UsedImplicitly] - public void Write(char toSend) - { - Write(toSend.ToString()); - } - - /// - /// Queues string with a new line ("\r\n") for transmission. - /// - /// Array of bytes to be sent - [UsedImplicitly] - public void WriteLine(string toSend) - { - Write(new ASCIIEncoding().GetBytes(toSend + Environment.NewLine)); - } - - private void CheckResult() - { - if (_writeCount <= 0) return; - - if (PInvoke.GetOverlappedResult(_hPort, _writeOverlapped, out var sent, _checkSends)) - { - _writeCount -= (int)sent; - if (_writeCount != 0) ThrowException("Send Timeout"); - } - else - { - if (Marshal.GetLastWin32Error() != (int)WIN32_ERROR.ERROR_IO_PENDING) ThrowException("Unexpected failure"); - } - } - - /// - /// Sends a protocol byte immediately ahead of any queued bytes. - /// - /// Byte to send - /// False if an immediate byte is already scheduled and not yet sent - public void SendImmediate(byte toSend) - { - CheckOnline(); - if (!PInvoke.TransmitCommChar(_hPort, new CHAR(toSend))) ThrowException("Transmission failure"); - } - - /// - /// Gets the status of the modem control input signals. - /// - /// Modem status object - /*protected ModemStatus GetModemStatus() - { - CheckOnline(); - if (!PInvoke.GetCommModemStatus(_hPort, out var f)) ThrowException("Unexpected failure"); - return new ModemStatus(f); - } - */ - - /// - /// Get the status of the queues - /// - /// Queue status object - /*protected QueueStatus GetQueueStatus() - { - COMSTAT cs; - COMMPROP cp; - uint er; - - CheckOnline(); - if (!PInvoke.ClearCommError(_hPort, out er, out cs)) ThrowException("Unexpected failure"); - if (!PInvoke.GetCommProperties(_hPort, out cp)) ThrowException("Unexpected failure"); - return new QueueStatus(cs.Flags, cs.cbInQue, cs.cbOutQue, cp.dwCurrentRxQueue, cp.dwCurrentTxQueue); - }*/ - - /// - /// Override this to provide processing after the port is opened (i.e. to configure remote - /// device or just check presence). - /// - /// false to close the port again - protected virtual bool AfterOpen() - { - return true; - } - - /// - /// Override this to provide processing prior to port closure. - /// - /// True if closing due to an error - protected virtual void BeforeClose(bool error) - { - } - - public event Action DataReceived; - - /// - /// Override this to process received bytes. - /// - /// The byte that was received - protected void OnRxChar(byte ch) - { - DataReceived?.Invoke(ch); - } - - /// - /// Override this to take action when transmission is complete (i.e. all bytes have actually - /// been sent, not just queued). - /// - protected virtual void OnTxDone() - { - } - - /// - /// Override this to take action when a break condition is detected on the input line. - /// - protected virtual void OnBreak() - { - } - - /// - /// Override this to take action when a ring condition is signaled by an attached modem. - /// - protected virtual void OnRing() - { - } - - /// - /// Override this to take action when one or more modem status inputs change state - /// - /// The status inputs that have changed state - /// The state of the status inputs - protected virtual void OnStatusChange(ModemStatus mask, ModemStatus state) - { - } - - /// - /// Override this to take action when the reception thread closes due to an exception being thrown. - /// - /// The exception which was thrown - protected virtual void OnRxException(Exception e) - { - } - - private unsafe void ReceiveThread() - { - var buf = new byte[1]; - - var sg = new AutoResetEvent(false); - var ov = new NativeOverlapped - { - EventHandle = sg.SafeWaitHandle.DangerousGetHandle() - }; - - COMM_EVENT_MASK eventMask = 0; - - try - { - while (true) - { - if (!PInvoke.SetCommMask( - _hPort, - COMM_EVENT_MASK.EV_RXCHAR | - COMM_EVENT_MASK.EV_TXEMPTY | - COMM_EVENT_MASK.EV_CTS | - COMM_EVENT_MASK.EV_DSR | - COMM_EVENT_MASK.EV_BREAK | - COMM_EVENT_MASK.EV_RLSD | - COMM_EVENT_MASK.EV_RING | - COMM_EVENT_MASK.EV_ERR) - ) - throw new CommPortException("IO Error [001]"); - - if (!PInvoke.WaitCommEvent(_hPort, ref eventMask, &ov)) - { - if (Marshal.GetLastWin32Error() == (int)WIN32_ERROR.ERROR_IO_PENDING) - sg.WaitOne(); - else - throw new CommPortException("IO Error [002]"); - } - - if ((eventMask & COMM_EVENT_MASK.EV_ERR) != 0) - { - CLEAR_COMM_ERROR_FLAGS errs; - - if (PInvoke.ClearCommError(_hPort, &errs, null)) - { - var s = new StringBuilder("UART Error: ", 40); - if ((errs & CLEAR_COMM_ERROR_FLAGS.CE_FRAME) != 0) s = s.Append("Framing,"); - if ((errs & CLEAR_COMM_ERROR_FLAGS.CE_BREAK) != 0) s = s.Append("Break,"); - if ((errs & CLEAR_COMM_ERROR_FLAGS.CE_OVERRUN) != 0) s = s.Append("Overrun,"); - if ((errs & CLEAR_COMM_ERROR_FLAGS.CE_RXOVER) != 0) s = s.Append("Receive Overflow,"); - if ((errs & CLEAR_COMM_ERROR_FLAGS.CE_RXPARITY) != 0) s = s.Append("Parity,"); - - s.Length = s.Length - 1; - - throw new CommPortException(s.ToString()); - } - - throw new CommPortException("IO Error [003]"); - } - - if ((eventMask & COMM_EVENT_MASK.EV_RXCHAR) != 0) - { - uint gotBytes; - do - { - fixed (byte* ptrBuffer = buf) - { - if (!PInvoke.ReadFile(_hPort, ptrBuffer, 1, &gotBytes, &ov)) - { - if (Marshal.GetLastWin32Error() == (int)WIN32_ERROR.ERROR_IO_PENDING) - { - PInvoke.CancelIo(_hPort); - gotBytes = 0; - } - else - { - throw new CommPortException("IO Error [004]"); - } - } - } - - if (gotBytes == 1) OnRxChar(buf[0]); - } while (gotBytes > 0); - } - - if ((eventMask & COMM_EVENT_MASK.EV_TXEMPTY) != 0) OnTxDone(); - if ((eventMask & COMM_EVENT_MASK.EV_BREAK) != 0) OnBreak(); - - uint i = 0; - - if ((eventMask & COMM_EVENT_MASK.EV_CTS) != 0) i |= (uint)MODEM_STATUS_FLAGS.MS_CTS_ON; - if ((eventMask & COMM_EVENT_MASK.EV_DSR) != 0) i |= (uint)MODEM_STATUS_FLAGS.MS_DSR_ON; - if ((eventMask & COMM_EVENT_MASK.EV_RLSD) != 0) i |= (uint)MODEM_STATUS_FLAGS.MS_RLSD_ON; - if ((eventMask & COMM_EVENT_MASK.EV_RING) != 0) i |= (uint)MODEM_STATUS_FLAGS.MS_RING_ON; - - if (i != 0) - if (!PInvoke.GetCommModemStatus(_hPort, out var f)) - throw new CommPortException("IO Error [005]"); - // TODO: fix me! OnStatusChange(new ModemStatus(i), new ModemStatus(f)); - } - } - catch (Exception e) - { - if (e is not ThreadAbortException) - { - _rxException = e; - OnRxException(e); - } - } - } - - private bool CheckOnline() - { - if (_rxException != null && !_rxExceptionReported) - { - _rxExceptionReported = true; - ThrowException("rx"); - } - - if (_online) - { - uint f; - if (PInvoke.GetHandleInformation(_hPort, out f)) return true; - ThrowException("Offline"); - return false; - } - - if (_auto) - if (Open()) - return true; - ThrowException("Offline"); - return false; - } - - #region Private fields - private readonly ManualResetEvent _writeEvent = new(false); private bool _auto; private bool _checkSends = true; @@ -539,7 +23,6 @@ public class SerialPort : IDisposable private Handshake _handShake; private SafeHandle _hPort; private bool _online; - private NativeOverlapped _writeOverlapped; private Exception _rxException; private bool _rxExceptionReported; private Thread _rxThread; @@ -547,10 +30,8 @@ public class SerialPort : IDisposable private int _stateDtr = 2; private int _stateRts = 2; private int _writeCount; + private NativeOverlapped _writeOverlapped; - #endregion - - #region Public properties /// /// Class constructor @@ -569,6 +50,39 @@ public class SerialPort : IDisposable BaudRate = baudRate; } + /// + /// Gets the status of the modem control input signals. + /// + /// Modem status object + public ModemStatus ModemStatus + { + get + { + CheckOnline(); + if (!PInvoke.GetCommModemStatus(_hPort, out var f)) ThrowException("Unexpected failure"); + return new ModemStatus(f); + } + } + + /// + /// Get the status of the queues + /// + /// Queue status object + public unsafe QueueStatus QueueStatus + { + get + { + COMSTAT cs; + var cp = new COMMPROP(); + CLEAR_COMM_ERROR_FLAGS er; + + CheckOnline(); + if (!PInvoke.ClearCommError(_hPort, &er, &cs)) ThrowException("Unexpected failure"); + if (!PInvoke.GetCommProperties(_hPort, ref cp)) ThrowException("Unexpected failure"); + return new QueueStatus(cs._bitfield, cs.cbInQue, cs.cbOutQue, cp.dwCurrentRxQueue, cp.dwCurrentTxQueue); + } + } + /// /// If true, the port will automatically re-open on next send if it was previously closed due /// to an error (default: false) @@ -717,12 +231,12 @@ public class SerialPort : IDisposable /// True if the RTS pin is controllable via the RTS property /// [UsedImplicitly] - protected bool RtSavailable => _stateRts < 2; + public bool RtSAvailable => _stateRts < 2; /// /// Set the state of the RTS modem control output /// - protected bool Rts + public bool Rts { set { @@ -749,12 +263,12 @@ public class SerialPort : IDisposable /// /// True if the DTR pin is controllable via the DTR property /// - protected bool DtrAvailable => _stateDtr < 2; + public bool DtrAvailable => _stateDtr < 2; /// /// The state of the DTR modem control output /// - protected bool Dtr + public bool Dtr { set { @@ -781,7 +295,7 @@ public class SerialPort : IDisposable /// /// Assert or remove a break condition from the transmission line /// - protected bool Break + public bool IsBreakEnabled { set { @@ -805,7 +319,6 @@ public class SerialPort : IDisposable get => _stateBrk == 1; } - /// /// Port Name /// @@ -866,5 +379,429 @@ public class SerialPort : IDisposable } } - #endregion + /// + /// + /// For IDisposable + /// + public void Dispose() + { + Close(); + } + + /// + /// Opens the com port and configures it with the required settings + /// + /// false if the port could not be opened + [UsedImplicitly] + public bool Open() + { + var portDcb = new DCB(); + var commTimeouts = new COMMTIMEOUTS(); + + if (_online) return false; + + _hPort = PInvoke.CreateFile( + PortName, + FILE_ACCESS_FLAGS.FILE_GENERIC_READ | FILE_ACCESS_FLAGS.FILE_GENERIC_WRITE, + 0, + null, + FILE_CREATION_DISPOSITION.OPEN_EXISTING, FILE_FLAGS_AND_ATTRIBUTES.FILE_FLAG_OVERLAPPED, + null + ); + + if (_hPort.IsInvalid) + { + if (Marshal.GetLastWin32Error() == (int)WIN32_ERROR.ERROR_ACCESS_DENIED) return false; + throw new CommPortException("Port Open Failure"); + } + + _online = true; + + commTimeouts.WriteTotalTimeoutConstant = (uint)SendTimeoutConstant; + commTimeouts.WriteTotalTimeoutMultiplier = (uint)SendTimeoutMultiplier; + + portDcb.Init( + Parity is Parity.Odd or Parity.Even, + TxFlowCts, + TxFlowDsr, + (int)UseDtr, + RxGateDsr, + !TxWhenRxXoff, + TxFlowX, + RxFlowX, + (int)UseRts + ); + portDcb.BaudRate = (uint)BaudRate; + portDcb.ByteSize = (byte)DataBits; + portDcb.Parity = (DCB_PARITY)Parity; + portDcb.StopBits = (DCB_STOP_BITS)StopBits; + portDcb.XoffChar = new CHAR((byte)XoffChar); + portDcb.XonChar = new CHAR((byte)XonChar); + portDcb.XoffLim = (ushort)RxHighWater; + portDcb.XonLim = (ushort)RxLowWater; + + if (RxQueue != 0 || TxQueue != 0) + if (!PInvoke.SetupComm(_hPort, (uint)RxQueue, (uint)TxQueue)) + ThrowException("Bad queue settings"); + + if (!PInvoke.SetCommState(_hPort, portDcb)) ThrowException("Bad com settings"); + if (!PInvoke.SetCommTimeouts(_hPort, commTimeouts)) ThrowException("Bad timeout settings"); + + _stateBrk = 0; + switch (UseDtr) + { + case HsOutput.None: + _stateDtr = 0; + break; + case HsOutput.Online: + _stateDtr = 1; + break; + } + + switch (UseRts) + { + case HsOutput.None: + _stateRts = 0; + break; + case HsOutput.Online: + _stateRts = 1; + break; + } + + _checkSends = CheckAllSends; + + _writeOverlapped = new NativeOverlapped + { + EventHandle = _checkSends ? _writeEvent.SafeWaitHandle.DangerousGetHandle() : IntPtr.Zero + }; + + _writeCount = 0; + + _rxException = null; + _rxExceptionReported = false; + + _rxThread = new Thread(ReceiveThread) + { + Name = "CommBaseRx", + Priority = ThreadPriority.AboveNormal, + IsBackground = true + }; + + _rxThread.Start(); + + _auto = false; + + Close(); + + return false; + } + + /// + /// Closes the com port. + /// + [UsedImplicitly] + public void Close() + { + if (_online) + { + _auto = false; + InternalClose(); + _rxException = null; + } + } + + private void InternalClose() + { + PInvoke.CancelIo(_hPort); + if (_rxThread != null) + { + _rxThread.Abort(); + _rxThread = null; + } + + _hPort.Close(); + + _stateRts = 2; + _stateDtr = 2; + _stateBrk = 2; + _online = false; + } + + /// + /// Destructor (just in case) + /// + ~SerialPort() + { + Close(); + } + + /// + /// Block until all bytes in the queue have been transmitted. + /// + [UsedImplicitly] + public void Flush() + { + CheckOnline(); + CheckResult(); + } + + /// + /// Use this to throw exceptions in derived classes. Correctly handles threading issues + /// and closes the port if necessary. + /// + /// Description of fault + private void ThrowException(string reason) + { + if (Thread.CurrentThread == _rxThread) throw new CommPortException(reason); + if (_online) InternalClose(); + + if (_rxException == null) throw new CommPortException(reason); + throw new CommPortException(_rxException); + } + + /// + /// Queues bytes for transmission. + /// + /// Array of bytes to be sent + [UsedImplicitly] + public unsafe void Write(byte[] toSend) + { + CheckOnline(); + CheckResult(); + _writeCount = toSend.GetLength(0); + + fixed (byte* ptr = toSend) + fixed (NativeOverlapped* overlapped = &_writeOverlapped) + { + uint sent; + if (PInvoke.WriteFile(_hPort, ptr, (uint)_writeCount, &sent, overlapped)) + { + _writeCount -= (int)sent; + } + else + { + if (Marshal.GetLastWin32Error() != (int)WIN32_ERROR.ERROR_IO_PENDING) + ThrowException("Unexpected failure"); + } + } + } + + /// + /// Queues string for transmission. + /// + /// Array of bytes to be sent + [UsedImplicitly] + public void Write(string toSend) + { + Write(new ASCIIEncoding().GetBytes(toSend)); + } + + /// + /// Queues a single byte for transmission. + /// + /// Byte to be sent + [UsedImplicitly] + public void Write(byte toSend) + { + var b = new byte[1]; + b[0] = toSend; + Write(b); + } + + /// + /// Queues a single char for transmission. + /// + /// Byte to be sent + [UsedImplicitly] + public void Write(char toSend) + { + Write(toSend.ToString()); + } + + /// + /// Queues string with a new line ("\r\n") for transmission. + /// + /// Array of bytes to be sent + [UsedImplicitly] + public void WriteLine(string toSend) + { + Write(new ASCIIEncoding().GetBytes(toSend + Environment.NewLine)); + } + + private void CheckResult() + { + if (_writeCount <= 0) return; + + if (PInvoke.GetOverlappedResult(_hPort, _writeOverlapped, out var sent, _checkSends)) + { + _writeCount -= (int)sent; + if (_writeCount != 0) ThrowException("Send Timeout"); + } + else + { + if (Marshal.GetLastWin32Error() != (int)WIN32_ERROR.ERROR_IO_PENDING) ThrowException("Unexpected failure"); + } + } + + /// + /// Sends a protocol byte immediately ahead of any queued bytes. + /// + /// Byte to send + /// False if an immediate byte is already scheduled and not yet sent + public void SendImmediate(byte toSend) + { + CheckOnline(); + if (!PInvoke.TransmitCommChar(_hPort, new CHAR(toSend))) ThrowException("Transmission failure"); + } + + /// + /// Override this to process received bytes. + /// + /// The byte that was received + private void OnRxChar(byte ch) + { + DataReceived?.Invoke(ch); + } + + private unsafe void ReceiveThread() + { + var buf = new byte[1]; + + var sg = new AutoResetEvent(false); + var ov = new NativeOverlapped + { + EventHandle = sg.SafeWaitHandle.DangerousGetHandle() + }; + + COMM_EVENT_MASK eventMask = 0; + + try + { + while (true) + { + if (!PInvoke.SetCommMask( + _hPort, + COMM_EVENT_MASK.EV_RXCHAR | + COMM_EVENT_MASK.EV_TXEMPTY | + COMM_EVENT_MASK.EV_CTS | + COMM_EVENT_MASK.EV_DSR | + COMM_EVENT_MASK.EV_BREAK | + COMM_EVENT_MASK.EV_RLSD | + COMM_EVENT_MASK.EV_RING | + COMM_EVENT_MASK.EV_ERR) + ) + throw new CommPortException("IO Error [001]"); + + if (!PInvoke.WaitCommEvent(_hPort, ref eventMask, &ov)) + { + if (Marshal.GetLastWin32Error() == (int)WIN32_ERROR.ERROR_IO_PENDING) + sg.WaitOne(); + else + throw new CommPortException("IO Error [002]"); + } + + if ((eventMask & COMM_EVENT_MASK.EV_ERR) != 0) + { + CLEAR_COMM_ERROR_FLAGS errs; + + if (PInvoke.ClearCommError(_hPort, &errs, null)) + { + var s = new StringBuilder("UART Error: ", 40); + if ((errs & CLEAR_COMM_ERROR_FLAGS.CE_FRAME) != 0) s = s.Append("Framing,"); + if ((errs & CLEAR_COMM_ERROR_FLAGS.CE_BREAK) != 0) s = s.Append("Break,"); + if ((errs & CLEAR_COMM_ERROR_FLAGS.CE_OVERRUN) != 0) s = s.Append("Overrun,"); + if ((errs & CLEAR_COMM_ERROR_FLAGS.CE_RXOVER) != 0) s = s.Append("Receive Overflow,"); + if ((errs & CLEAR_COMM_ERROR_FLAGS.CE_RXPARITY) != 0) s = s.Append("Parity,"); + + s.Length = s.Length - 1; + + throw new CommPortException(s.ToString()); + } + + throw new CommPortException("IO Error [003]"); + } + + if ((eventMask & COMM_EVENT_MASK.EV_RXCHAR) != 0) + { + uint gotBytes; + do + { + fixed (byte* ptrBuffer = buf) + { + if (!PInvoke.ReadFile(_hPort, ptrBuffer, 1, &gotBytes, &ov)) + { + if (Marshal.GetLastWin32Error() == (int)WIN32_ERROR.ERROR_IO_PENDING) + { + PInvoke.CancelIo(_hPort); + gotBytes = 0; + } + else + { + throw new CommPortException("IO Error [004]"); + } + } + } + + if (gotBytes == 1) OnRxChar(buf[0]); + } while (gotBytes > 0); + } + + if ((eventMask & COMM_EVENT_MASK.EV_TXEMPTY) != 0) TxDone?.Invoke(); + if ((eventMask & COMM_EVENT_MASK.EV_BREAK) != 0) Break?.Invoke(); + + MODEM_STATUS_FLAGS i = 0; + + if ((eventMask & COMM_EVENT_MASK.EV_CTS) != 0) i |= MODEM_STATUS_FLAGS.MS_CTS_ON; + if ((eventMask & COMM_EVENT_MASK.EV_DSR) != 0) i |= MODEM_STATUS_FLAGS.MS_DSR_ON; + if ((eventMask & COMM_EVENT_MASK.EV_RLSD) != 0) i |= MODEM_STATUS_FLAGS.MS_RLSD_ON; + if ((eventMask & COMM_EVENT_MASK.EV_RING) != 0) i |= MODEM_STATUS_FLAGS.MS_RING_ON; + + if (i != 0 || !PInvoke.GetCommModemStatus(_hPort, out var f)) + throw new CommPortException("IO Error [005]"); + + StatusChanged?.Invoke(new ModemStatus(i), new ModemStatus(f)); + } + } + catch (Exception e) + { + if (e is not ThreadAbortException) + { + _rxException = e; + ThreadException?.Invoke(e); + } + } + } + + private bool CheckOnline() + { + if (_rxException != null && !_rxExceptionReported) + { + _rxExceptionReported = true; + ThrowException("rx"); + } + + if (_online) + { + uint f; + if (PInvoke.GetHandleInformation(_hPort, out f)) return true; + ThrowException("Offline"); + return false; + } + + if (_auto) + if (Open()) + return true; + ThrowException("Offline"); + return false; + } + + public event Action StatusChanged; + + public event Action DataReceived; + + public event Action TxDone; + + public event Action Break; + + public event Action ThreadException; } \ No newline at end of file diff --git a/Nefarius.Peripherals.SerialPort/Win32PInvoke/COMSTAT.cs b/Nefarius.Peripherals.SerialPort/Win32PInvoke/COMSTAT.cs deleted file mode 100644 index 693bae2..0000000 --- a/Nefarius.Peripherals.SerialPort/Win32PInvoke/COMSTAT.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace Nefarius.Peripherals.SerialPort.Win32PInvoke; - -[StructLayout(LayoutKind.Sequential)] -internal struct COMSTAT -{ - internal const uint fCtsHold = 0x1; - internal const uint fDsrHold = 0x2; - internal const uint fRlsdHold = 0x4; - internal const uint fXoffHold = 0x8; - internal const uint fXoffSent = 0x10; - internal const uint fEof = 0x20; - internal const uint fTxim = 0x40; - internal UInt32 Flags; - internal UInt32 cbInQue; - internal UInt32 cbOutQue; -} \ No newline at end of file