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