diff --git a/Nefarius.Peripherals.SerialPort/SerialPort.cs b/Nefarius.Peripherals.SerialPort/SerialPort.cs
index d4ea3d5..8d58f3f 100644
--- a/Nefarius.Peripherals.SerialPort/SerialPort.cs
+++ b/Nefarius.Peripherals.SerialPort/SerialPort.cs
@@ -10,812 +10,810 @@ using Microsoft.Win32.SafeHandles;
using Nefarius.Peripherals.SerialPort.Win32PInvoke;
using COMMPROP = Nefarius.Peripherals.SerialPort.Win32PInvoke.COMMPROP;
using COMSTAT = Nefarius.Peripherals.SerialPort.Win32PInvoke.COMSTAT;
-using DCB = Windows.Win32.Devices.Communication.DCB;
-namespace Nefarius.Peripherals.SerialPort
+namespace Nefarius.Peripherals.SerialPort;
+
+///
+/// PInvokeSerialPort main class.
+/// Borrowed from http://msdn.microsoft.com/en-us/magazine/cc301786.aspx ;)
+///
+public class SerialPort : IDisposable
{
+ ///
///
- /// PInvokeSerialPort main class.
- /// Borrowed from http://msdn.microsoft.com/en-us/magazine/cc301786.aspx ;)
+ /// For IDisposable
///
- public class SerialPort : IDisposable
+ public void Dispose()
{
- #region Private fields
+ Close();
+ }
- private readonly ManualResetEvent _writeEvent = new ManualResetEvent(false);
- private bool _auto;
- private bool _checkSends = true;
+ ///
+ /// Opens the com port and configures it with the required settings
+ ///
+ /// false if the port could not be opened
+ public bool Open()
+ {
+ var portDcb = new DCB();
+ var commTimeouts = new COMMTIMEOUTS();
- private Handshake _handShake;
- private SafeFileHandle _hPort;
- private bool _online;
- private IntPtr _ptrUwo = IntPtr.Zero;
- private Exception _rxException;
- private bool _rxExceptionReported;
- private Thread _rxThread;
- private int _stateBrk = 2;
- private int _stateDtr = 2;
- private int _stateRts = 2;
- private int _writeCount;
+ if (_online) return false;
- #endregion
+ _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);
- #region Public properties
-
- ///
- /// Class constructor
- ///
- public SerialPort(string portName)
+ if (_hPort.IsInvalid)
{
- PortName = portName;
+ if (Marshal.GetLastWin32Error() == Win32Com.ERROR_ACCESS_DENIED) return false;
+ throw new CommPortException("Port Open Failure");
}
- ///
- ///
- /// Class constructor
- ///
- public SerialPort(string portName, int baudRate) : this(portName)
+ _online = true;
+
+ commTimeouts.ReadIntervalTimeout = 0;
+ commTimeouts.ReadTotalTimeoutConstant = 0;
+ commTimeouts.ReadTotalTimeoutMultiplier = 0;
+ 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 = (CHAR)(byte)XoffChar;
+ portDcb.XonChar = (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)
{
- BaudRate = baudRate;
+ case HsOutput.None:
+ _stateDtr = 0;
+ break;
+ case HsOutput.Online:
+ _stateDtr = 1;
+ break;
}
- ///
- /// If true, the port will automatically re-open on next send if it was previously closed due
- /// to an error (default: false)
- ///
- public bool AutoReopen { get; set; }
-
- ///
- /// Baud Rate (default: 115200)
- ///
- /// Unsupported rates will throw "Bad settings".
- public int BaudRate { get; set; } = 115200;
-
- ///
- /// If true, subsequent Send commands wait for completion of earlier ones enabling the results
- /// to be checked. If false, errors, including timeouts, may not be detected, but performance
- /// may be better.
- ///
- public bool CheckAllSends { get; set; } = true;
-
- ///
- /// Number of databits 1..8 (default: 8) unsupported values will throw "Bad settings"
- ///
- public int DataBits { get; set; } = 8;
-
- ///
- /// The parity checking scheme (default: none)
- ///
- public Parity Parity { get; set; } = Parity.None;
-
- ///
- /// If true, Xon and Xoff characters are sent to control the data flow from the remote station (default: false)
- ///
- public bool RxFlowX { get; set; }
-
- ///
- /// If true, received characters are ignored unless DSR is asserted by the remote station (default: false)
- ///
- public bool RxGateDsr { get; set; }
-
- ///
- /// The number of free bytes in the reception queue at which flow is disabled (default: 2048)
- ///
- public int RxHighWater { get; set; } = 2048;
-
- ///
- /// The number of bytes in the reception queue at which flow is re-enabled (default: 512)
- ///
- public int RxLowWater { get; set; } = 512;
-
- ///
- /// Requested size for receive queue (default: 0 = use operating system default)
- ///
- public int RxQueue { get; set; }
-
- ///
- /// Constant. Max time for Send in ms = (Multiplier * Characters) + Constant (default: 0)
- ///
- public int SendTimeoutConstant { get; set; }
-
- ///
- /// Multiplier. Max time for Send in ms = (Multiplier * Characters) + Constant
- /// (default: 0 = No timeout)
- ///
- public int SendTimeoutMultiplier { get; set; }
-
- ///
- /// Number of stop bits (default: one)
- ///
- public StopBits StopBits { get; set; } = StopBits.One;
-
- ///
- /// If true, transmission is halted unless CTS is asserted by the remote station (default: false)
- ///
- public bool TxFlowCts { get; set; }
-
- ///
- /// If true, transmission is halted unless DSR is asserted by the remote station (default: false)
- ///
- public bool TxFlowDsr { get; set; }
-
- ///
- /// If true, transmission is halted when Xoff is received and restarted when Xon is received (default: false)
- ///
- public bool TxFlowX { get; set; }
-
- ///
- /// Requested size for transmit queue (default: 0 = use operating system default)
- ///
- public int TxQueue { get; set; }
-
- ///
- /// If false, transmission is suspended when this station has sent Xoff to the remote station (default: true)
- /// Set false if the remote station treats any character as an Xon.
- ///
- public bool TxWhenRxXoff { get; set; } = true;
-
- ///
- /// Specidies the use to which the DTR output is put (default: none)
- ///
- public HsOutput UseDtr { get; set; } = HsOutput.None;
-
- ///
- /// Specifies the use to which the RTS output is put (default: none)
- ///
- public HsOutput UseRts { get; set; } = HsOutput.None;
-
- ///
- /// The character used to signal Xoff for X flow control (default: DC3)
- ///
- public ASCII XoffChar { get; set; } = ASCII.DC3;
-
- ///
- /// The character used to signal Xon for X flow control (default: DC1)
- ///
- public ASCII XonChar { get; set; } = ASCII.DC1;
-
- ///
- /// True if online.
- ///
- public bool Online => _online && CheckOnline();
-
- ///
- /// True if the RTS pin is controllable via the RTS property
- ///
- protected bool RtSavailable => _stateRts < 2;
-
- ///
- /// Set the state of the RTS modem control output
- ///
- protected bool Rts
+ switch (UseRts)
{
- set
- {
- if (_stateRts > 1) return;
- CheckOnline();
- if (value)
- {
- if (Win32Com.EscapeCommFunction(_hPort.DangerousGetHandle(), Win32Com.SETRTS))
- _stateRts = 1;
- else
- ThrowException("Unexpected Failure");
- }
- else
- {
- if (Win32Com.EscapeCommFunction(_hPort.DangerousGetHandle(), Win32Com.CLRRTS))
- _stateRts = 1;
- else
- ThrowException("Unexpected Failure");
- }
- }
- get => _stateRts == 1;
+ case HsOutput.None:
+ _stateRts = 0;
+ break;
+ case HsOutput.Online:
+ _stateRts = 1;
+ break;
}
- ///
- /// True if the DTR pin is controllable via the DTR property
- ///
- protected bool DtrAvailable => _stateDtr < 2;
+ _checkSends = CheckAllSends;
+ _ptrUwo.EventHandle = _checkSends ? _writeEvent.SafeWaitHandle.DangerousGetHandle() : IntPtr.Zero;
+ _writeCount = 0;
- ///
- /// The state of the DTR modem control output
- ///
- protected bool Dtr
+ _rxException = null;
+ _rxExceptionReported = false;
+
+ // TODO: utilize Task Parallel Library here
+ _rxThread = new Thread(ReceiveThread)
{
- set
- {
- if (_stateDtr > 1) return;
- CheckOnline();
- if (value)
- {
- if (Win32Com.EscapeCommFunction(_hPort.DangerousGetHandle(), Win32Com.SETDTR))
- _stateDtr = 1;
- else
- ThrowException("Unexpected Failure");
- }
- else
- {
- if (Win32Com.EscapeCommFunction(_hPort.DangerousGetHandle(), Win32Com.CLRDTR))
- _stateDtr = 0;
- else
- ThrowException("Unexpected Failure");
- }
- }
- get => _stateDtr == 1;
- }
+ Name = "CommBaseRx",
+ Priority = ThreadPriority.AboveNormal,
+ IsBackground = true
+ };
- ///
- /// Assert or remove a break condition from the transmission line
- ///
- protected bool Break
- {
- set
- {
- if (_stateBrk > 1) return;
- CheckOnline();
- if (value)
- {
- if (Win32Com.EscapeCommFunction(_hPort.DangerousGetHandle(), Win32Com.SETBREAK))
- _stateBrk = 0;
- else
- ThrowException("Unexpected Failure");
- }
- else
- {
- if (Win32Com.EscapeCommFunction(_hPort.DangerousGetHandle(), Win32Com.CLRBREAK))
- _stateBrk = 0;
- else
- ThrowException("Unexpected Failure");
- }
- }
- get => _stateBrk == 1;
- }
-
-
- ///
- /// Port Name
- ///
- public string PortName { get; set; }
-
- public Handshake Handshake
- {
- get => _handShake;
- set
- {
- _handShake = value;
- switch (_handShake)
- {
- case Handshake.None:
- TxFlowCts = false;
- TxFlowDsr = false;
- TxFlowX = false;
- RxFlowX = false;
- UseRts = HsOutput.Online;
- UseDtr = HsOutput.Online;
- TxWhenRxXoff = true;
- RxGateDsr = false;
- break;
- case Handshake.XonXoff:
- TxFlowCts = false;
- TxFlowDsr = false;
- TxFlowX = true;
- RxFlowX = true;
- UseRts = HsOutput.Online;
- UseDtr = HsOutput.Online;
- TxWhenRxXoff = true;
- RxGateDsr = false;
- XonChar = ASCII.DC1;
- XoffChar = ASCII.DC3;
- break;
- case Handshake.CtsRts:
- TxFlowCts = true;
- TxFlowDsr = false;
- TxFlowX = false;
- RxFlowX = false;
- UseRts = HsOutput.Handshake;
- UseDtr = HsOutput.Online;
- TxWhenRxXoff = true;
- RxGateDsr = false;
- break;
- case Handshake.DsrDtr:
- TxFlowCts = false;
- TxFlowDsr = true;
- TxFlowX = false;
- RxFlowX = false;
- UseRts = HsOutput.Online;
- UseDtr = HsOutput.Handshake;
- TxWhenRxXoff = true;
- RxGateDsr = false;
- break;
- }
- }
- }
-
- #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
- public bool Open()
- {
- var portDcb = new DCB();
- var commTimeouts = new COMMTIMEOUTS();
- var wo = new OVERLAPPED();
-
- 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() == Win32Com.ERROR_ACCESS_DENIED) return false;
- throw new CommPortException("Port Open Failure");
- }
-
- _online = true;
-
- commTimeouts.ReadIntervalTimeout = 0;
- commTimeouts.ReadTotalTimeoutConstant = 0;
- commTimeouts.ReadTotalTimeoutMultiplier = 0;
- 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 = (CHAR)(byte)XoffChar;
- portDcb.XonChar = (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;
- wo.Offset = 0;
- wo.OffsetHigh = 0;
- wo.hEvent = _checkSends ? _writeEvent.SafeWaitHandle.DangerousGetHandle() : IntPtr.Zero;
- _ptrUwo = Marshal.AllocHGlobal(Marshal.SizeOf(wo));
- Marshal.StructureToPtr(wo, _ptrUwo, true);
- _writeCount = 0;
-
- _rxException = null;
- _rxExceptionReported = false;
-
- // TODO: utilize Task Parallel Library here
- _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.
- ///
- public void Close()
- {
- if (_online)
- {
- _auto = false;
- BeforeClose(false);
- InternalClose();
- _rxException = null;
- }
- }
-
- private void InternalClose()
- {
- Win32Com.CancelIo(_hPort.DangerousGetHandle());
- if (_rxThread != null)
- {
- _rxThread.Abort();
- _rxThread = null;
- }
-
- _hPort.Dispose();
- if (_ptrUwo != IntPtr.Zero) Marshal.FreeHGlobal(_ptrUwo);
- _stateRts = 2;
- _stateDtr = 2;
- _stateBrk = 2;
- _online = false;
- }
-
- ///
- /// Destructor (just in case)
- ///
- ~SerialPort()
- {
- Close();
- }
-
- ///
- /// Block until all bytes in the queue have been transmitted.
- ///
- 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
- public void Write(byte[] toSend)
- {
- uint sent;
- CheckOnline();
- CheckResult();
- _writeCount = toSend.GetLength(0);
- if (Win32Com.WriteFile(_hPort.DangerousGetHandle(), toSend, (uint)_writeCount, out sent, _ptrUwo))
- {
- _writeCount -= (int)sent;
- }
- else
- {
- if (Marshal.GetLastWin32Error() != Win32Com.ERROR_IO_PENDING) ThrowException("Unexpected failure");
- }
- }
-
- ///
- /// Queues string for transmission.
- ///
- /// Array of bytes to be sent
- public void Write(string toSend)
- {
- Write(new ASCIIEncoding().GetBytes(toSend));
- }
-
- ///
- /// Queues a single byte for transmission.
- ///
- /// Byte to be sent
- 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
- public void Write(char toSend)
- {
- Write(toSend.ToString());
- }
-
- ///
- /// Queues string with a new line ("\r\n") for transmission.
- ///
- /// Array of bytes to be sent
- public void WriteLine(string toSend)
- {
- Write(new ASCIIEncoding().GetBytes(toSend + Environment.NewLine));
- }
-
- private void CheckResult()
- {
- if (_writeCount <= 0) return;
- uint sent;
- if (Win32Com.GetOverlappedResult(_hPort.DangerousGetHandle(), _ptrUwo, out sent, _checkSends))
- {
- _writeCount -= (int)sent;
- if (_writeCount != 0) ThrowException("Send Timeout");
- }
- else
- {
- if (Marshal.GetLastWin32Error() != Win32Com.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 (!Win32Com.TransmitCommChar(_hPort.DangerousGetHandle(), tosend)) ThrowException("Transmission failure");
- }
-
- ///
- /// Gets the status of the modem control input signals.
- ///
- /// Modem status object
- protected ModemStatus GetModemStatus()
- {
- uint f;
-
- CheckOnline();
- if (!Win32Com.GetCommModemStatus(_hPort.DangerousGetHandle(), out 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 (!Win32Com.ClearCommError(_hPort.DangerousGetHandle(), out er, out cs)) ThrowException("Unexpected failure");
- if (!Win32Com.GetCommProperties(_hPort.DangerousGetHandle(), 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()
+ _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;
}
- ///
- /// Override this to provide processing prior to port closure.
- ///
- /// True if closing due to an error
- protected virtual void BeforeClose(bool error)
+ Close();
+ return false;
+ }
+
+ ///
+ /// Closes the com port.
+ ///
+ public void Close()
+ {
+ if (_online)
{
+ _auto = false;
+ BeforeClose(false);
+ InternalClose();
+ _rxException = null;
+ }
+ }
+
+ private void InternalClose()
+ {
+ Win32Com.CancelIo(_hPort.DangerousGetHandle());
+ if (_rxThread != null)
+ {
+ _rxThread.Abort();
+ _rxThread = null;
}
- public event Action DataReceived;
+ _hPort.Dispose();
+ _stateRts = 2;
+ _stateDtr = 2;
+ _stateBrk = 2;
+ _online = false;
+ }
- ///
- /// Override this to process received bytes.
- ///
- /// The byte that was received
- protected void OnRxChar(byte ch)
+ ///
+ /// Destructor (just in case)
+ ///
+ ~SerialPort()
+ {
+ Close();
+ }
+
+ ///
+ /// Block until all bytes in the queue have been transmitted.
+ ///
+ 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)
{
- DataReceived?.Invoke(ch);
+ BeforeClose(true);
+ InternalClose();
}
- ///
- /// Override this to take action when transmission is complete (i.e. all bytes have actually
- /// been sent, not just queued).
- ///
- protected virtual void OnTxDone()
+ if (_rxException == null) throw new CommPortException(reason);
+ throw new CommPortException(_rxException);
+ }
+
+ ///
+ /// Queues bytes for transmission.
+ ///
+ /// Array of bytes to be sent
+ public unsafe void Write(byte[] toSend)
+ {
+ uint sent;
+ CheckOnline();
+ CheckResult();
+ _writeCount = toSend.GetLength(0);
+
+ fixed (byte* ptr = toSend)
+ fixed (NativeOverlapped* ptrOl = &_ptrUwo)
{
- }
-
- ///
- /// 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 void ReceiveThread()
- {
- var buf = new byte[1];
-
- var sg = new AutoResetEvent(false);
- var ov = new OVERLAPPED();
- var unmanagedOv = Marshal.AllocHGlobal(Marshal.SizeOf(ov));
- ov.Offset = 0;
- ov.OffsetHigh = 0;
- ov.hEvent = sg.SafeWaitHandle.DangerousGetHandle();
- Marshal.StructureToPtr(ov, unmanagedOv, true);
-
- uint eventMask = 0;
- var uMask = Marshal.AllocHGlobal(Marshal.SizeOf(eventMask));
-
- try
+ if (PInvoke.WriteFile(_hPort, ptr, (uint)_writeCount, &sent, ptrOl))
{
- while (true)
- {
- if (!Win32Com.SetCommMask(_hPort.DangerousGetHandle(),
+ _writeCount -= (int)sent;
+ }
+ else
+ {
+ if (Marshal.GetLastWin32Error() != Win32Com.ERROR_IO_PENDING) ThrowException("Unexpected failure");
+ }
+ }
+ }
+
+ ///
+ /// Queues string for transmission.
+ ///
+ /// Array of bytes to be sent
+ public void Write(string toSend)
+ {
+ Write(new ASCIIEncoding().GetBytes(toSend));
+ }
+
+ ///
+ /// Queues a single byte for transmission.
+ ///
+ /// Byte to be sent
+ 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
+ public void Write(char toSend)
+ {
+ Write(toSend.ToString());
+ }
+
+ ///
+ /// Queues string with a new line ("\r\n") for transmission.
+ ///
+ /// Array of bytes to be sent
+ public void WriteLine(string toSend)
+ {
+ Write(new ASCIIEncoding().GetBytes(toSend + Environment.NewLine));
+ }
+
+ private void CheckResult()
+ {
+ if (_writeCount <= 0) return;
+ if (PInvoke.GetOverlappedResult(_hPort, _ptrUwo, out var sent, _checkSends))
+ {
+ _writeCount -= (int)sent;
+ if (_writeCount != 0) ThrowException("Send Timeout");
+ }
+ else
+ {
+ if (Marshal.GetLastWin32Error() != Win32Com.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 (!Win32Com.TransmitCommChar(_hPort.DangerousGetHandle(), tosend)) ThrowException("Transmission failure");
+ }
+
+ ///
+ /// Gets the status of the modem control input signals.
+ ///
+ /// Modem status object
+ protected ModemStatus GetModemStatus()
+ {
+ uint f;
+
+ CheckOnline();
+ if (!Win32Com.GetCommModemStatus(_hPort.DangerousGetHandle(), out 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 (!Win32Com.ClearCommError(_hPort.DangerousGetHandle(), out er, out cs)) ThrowException("Unexpected failure");
+ if (!Win32Com.GetCommProperties(_hPort.DangerousGetHandle(), 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 void ReceiveThread()
+ {
+ var buf = new byte[1];
+
+ var sg = new AutoResetEvent(false);
+ var ov = new OVERLAPPED();
+ var unmanagedOv = Marshal.AllocHGlobal(Marshal.SizeOf(ov));
+ ov.Offset = 0;
+ ov.OffsetHigh = 0;
+ ov.hEvent = sg.SafeWaitHandle.DangerousGetHandle();
+ Marshal.StructureToPtr(ov, unmanagedOv, true);
+
+ uint eventMask = 0;
+ var uMask = Marshal.AllocHGlobal(Marshal.SizeOf(eventMask));
+
+ try
+ {
+ while (true)
+ {
+ if (!Win32Com.SetCommMask(_hPort.DangerousGetHandle(),
Win32Com.EV_RXCHAR | Win32Com.EV_TXEMPTY | Win32Com.EV_CTS | Win32Com.EV_DSR
| Win32Com.EV_BREAK | Win32Com.EV_RLSD | Win32Com.EV_RING | Win32Com.EV_ERR))
- throw new CommPortException("IO Error [001]");
- Marshal.WriteInt32(uMask, 0);
- if (!Win32Com.WaitCommEvent(_hPort.DangerousGetHandle(), uMask, unmanagedOv))
+ throw new CommPortException("IO Error [001]");
+ Marshal.WriteInt32(uMask, 0);
+ if (!Win32Com.WaitCommEvent(_hPort.DangerousGetHandle(), uMask, unmanagedOv))
+ {
+ if (Marshal.GetLastWin32Error() == Win32Com.ERROR_IO_PENDING)
+ sg.WaitOne();
+ else
+ throw new CommPortException("IO Error [002]");
+ }
+
+ eventMask = (uint)Marshal.ReadInt32(uMask);
+ if ((eventMask & Win32Com.EV_ERR) != 0)
+ {
+ uint errs;
+ if (Win32Com.ClearCommError(_hPort.DangerousGetHandle(), out errs, IntPtr.Zero))
{
- if (Marshal.GetLastWin32Error() == Win32Com.ERROR_IO_PENDING)
- sg.WaitOne();
- else
- throw new CommPortException("IO Error [002]");
+ var s = new StringBuilder("UART Error: ", 40);
+ if ((errs & Win32Com.CE_FRAME) != 0) s = s.Append("Framing,");
+ if ((errs & Win32Com.CE_IOE) != 0) s = s.Append("IO,");
+ if ((errs & Win32Com.CE_OVERRUN) != 0) s = s.Append("Overrun,");
+ if ((errs & Win32Com.CE_RXOVER) != 0) s = s.Append("Receive Overflow,");
+ if ((errs & Win32Com.CE_RXPARITY) != 0) s = s.Append("Parity,");
+ if ((errs & Win32Com.CE_TXFULL) != 0) s = s.Append("Transmit Overflow,");
+ s.Length = s.Length - 1;
+ throw new CommPortException(s.ToString());
}
- eventMask = (uint)Marshal.ReadInt32(uMask);
- if ((eventMask & Win32Com.EV_ERR) != 0)
+ throw new CommPortException("IO Error [003]");
+ }
+
+ if ((eventMask & Win32Com.EV_RXCHAR) != 0)
+ {
+ uint gotbytes;
+ do
{
- uint errs;
- if (Win32Com.ClearCommError(_hPort.DangerousGetHandle(), out errs, IntPtr.Zero))
+ if (!Win32Com.ReadFile(_hPort.DangerousGetHandle(), buf, 1, out gotbytes, unmanagedOv))
{
- var s = new StringBuilder("UART Error: ", 40);
- if ((errs & Win32Com.CE_FRAME) != 0) s = s.Append("Framing,");
- if ((errs & Win32Com.CE_IOE) != 0) s = s.Append("IO,");
- if ((errs & Win32Com.CE_OVERRUN) != 0) s = s.Append("Overrun,");
- if ((errs & Win32Com.CE_RXOVER) != 0) s = s.Append("Receive Overflow,");
- if ((errs & Win32Com.CE_RXPARITY) != 0) s = s.Append("Parity,");
- if ((errs & Win32Com.CE_TXFULL) != 0) s = s.Append("Transmit Overflow,");
- s.Length = s.Length - 1;
- throw new CommPortException(s.ToString());
+ if (Marshal.GetLastWin32Error() == Win32Com.ERROR_IO_PENDING)
+ {
+ Win32Com.CancelIo(_hPort.DangerousGetHandle());
+ gotbytes = 0;
+ }
+ else
+ {
+ throw new CommPortException("IO Error [004]");
+ }
}
- throw new CommPortException("IO Error [003]");
- }
-
- if ((eventMask & Win32Com.EV_RXCHAR) != 0)
- {
- uint gotbytes;
- do
- {
- if (!Win32Com.ReadFile(_hPort.DangerousGetHandle(), buf, 1, out gotbytes, unmanagedOv))
- {
- if (Marshal.GetLastWin32Error() == Win32Com.ERROR_IO_PENDING)
- {
- Win32Com.CancelIo(_hPort.DangerousGetHandle());
- gotbytes = 0;
- }
- else
- {
- throw new CommPortException("IO Error [004]");
- }
- }
-
- if (gotbytes == 1) OnRxChar(buf[0]);
- } while (gotbytes > 0);
- }
-
- if ((eventMask & Win32Com.EV_TXEMPTY) != 0) OnTxDone();
- if ((eventMask & Win32Com.EV_BREAK) != 0) OnBreak();
-
- uint i = 0;
- if ((eventMask & Win32Com.EV_CTS) != 0) i |= Win32Com.MS_CTS_ON;
- if ((eventMask & Win32Com.EV_DSR) != 0) i |= Win32Com.MS_DSR_ON;
- if ((eventMask & Win32Com.EV_RLSD) != 0) i |= Win32Com.MS_RLSD_ON;
- if ((eventMask & Win32Com.EV_RING) != 0) i |= Win32Com.MS_RING_ON;
- if (i != 0)
- {
- uint f;
- if (!Win32Com.GetCommModemStatus(_hPort.DangerousGetHandle(), out f)) throw new CommPortException("IO Error [005]");
- OnStatusChange(new ModemStatus(i), new ModemStatus(f));
- }
+ if (gotbytes == 1) OnRxChar(buf[0]);
+ } while (gotbytes > 0);
}
- }
- catch (Exception e)
- {
- if (uMask != IntPtr.Zero) Marshal.FreeHGlobal(uMask);
- if (unmanagedOv != IntPtr.Zero) Marshal.FreeHGlobal(unmanagedOv);
- if (!(e is ThreadAbortException))
+
+ if ((eventMask & Win32Com.EV_TXEMPTY) != 0) OnTxDone();
+ if ((eventMask & Win32Com.EV_BREAK) != 0) OnBreak();
+
+ uint i = 0;
+ if ((eventMask & Win32Com.EV_CTS) != 0) i |= Win32Com.MS_CTS_ON;
+ if ((eventMask & Win32Com.EV_DSR) != 0) i |= Win32Com.MS_DSR_ON;
+ if ((eventMask & Win32Com.EV_RLSD) != 0) i |= Win32Com.MS_RLSD_ON;
+ if ((eventMask & Win32Com.EV_RING) != 0) i |= Win32Com.MS_RING_ON;
+ if (i != 0)
{
- _rxException = e;
- OnRxException(e);
+ uint f;
+ if (!Win32Com.GetCommModemStatus(_hPort.DangerousGetHandle(), out f))
+ throw new CommPortException("IO Error [005]");
+ OnStatusChange(new ModemStatus(i), new ModemStatus(f));
}
}
}
-
- private bool CheckOnline()
+ catch (Exception e)
{
- if (_rxException != null && !_rxExceptionReported)
+ if (uMask != IntPtr.Zero) Marshal.FreeHGlobal(uMask);
+ if (unmanagedOv != IntPtr.Zero) Marshal.FreeHGlobal(unmanagedOv);
+ if (!(e is ThreadAbortException))
{
- _rxExceptionReported = true;
- ThrowException("rx");
+ _rxException = e;
+ OnRxException(e);
}
+ }
+ }
- if (_online)
- {
- uint f;
- if (Win32Com.GetHandleInformation(_hPort.DangerousGetHandle(), out f)) return true;
- ThrowException("Offline");
- return false;
- }
+ private bool CheckOnline()
+ {
+ if (_rxException != null && !_rxExceptionReported)
+ {
+ _rxExceptionReported = true;
+ ThrowException("rx");
+ }
- if (_auto)
- if (Open())
- return true;
+ if (_online)
+ {
+ uint f;
+ if (Win32Com.GetHandleInformation(_hPort.DangerousGetHandle(), 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;
+
+ private Handshake _handShake;
+ private SafeFileHandle _hPort;
+ private bool _online;
+ private NativeOverlapped _ptrUwo;
+ private Exception _rxException;
+ private bool _rxExceptionReported;
+ private Thread _rxThread;
+ private int _stateBrk = 2;
+ private int _stateDtr = 2;
+ private int _stateRts = 2;
+ private int _writeCount;
+
+ #endregion
+
+ #region Public properties
+
+ ///
+ /// Class constructor
+ ///
+ public SerialPort(string portName)
+ {
+ PortName = portName;
+ }
+
+ ///
+ ///
+ /// Class constructor
+ ///
+ public SerialPort(string portName, int baudRate) : this(portName)
+ {
+ BaudRate = baudRate;
+ }
+
+ ///
+ /// If true, the port will automatically re-open on next send if it was previously closed due
+ /// to an error (default: false)
+ ///
+ public bool AutoReopen { get; set; }
+
+ ///
+ /// Baud Rate (default: 115200)
+ ///
+ /// Unsupported rates will throw "Bad settings".
+ public int BaudRate { get; set; } = 115200;
+
+ ///
+ /// If true, subsequent Send commands wait for completion of earlier ones enabling the results
+ /// to be checked. If false, errors, including timeouts, may not be detected, but performance
+ /// may be better.
+ ///
+ public bool CheckAllSends { get; set; } = true;
+
+ ///
+ /// Number of databits 1..8 (default: 8) unsupported values will throw "Bad settings"
+ ///
+ public int DataBits { get; set; } = 8;
+
+ ///
+ /// The parity checking scheme (default: none)
+ ///
+ public Parity Parity { get; set; } = Parity.None;
+
+ ///
+ /// If true, Xon and Xoff characters are sent to control the data flow from the remote station (default: false)
+ ///
+ public bool RxFlowX { get; set; }
+
+ ///
+ /// If true, received characters are ignored unless DSR is asserted by the remote station (default: false)
+ ///
+ public bool RxGateDsr { get; set; }
+
+ ///
+ /// The number of free bytes in the reception queue at which flow is disabled (default: 2048)
+ ///
+ public int RxHighWater { get; set; } = 2048;
+
+ ///
+ /// The number of bytes in the reception queue at which flow is re-enabled (default: 512)
+ ///
+ public int RxLowWater { get; set; } = 512;
+
+ ///
+ /// Requested size for receive queue (default: 0 = use operating system default)
+ ///
+ public int RxQueue { get; set; }
+
+ ///
+ /// Constant. Max time for Send in ms = (Multiplier * Characters) + Constant (default: 0)
+ ///
+ public int SendTimeoutConstant { get; set; }
+
+ ///
+ /// Multiplier. Max time for Send in ms = (Multiplier * Characters) + Constant
+ /// (default: 0 = No timeout)
+ ///
+ public int SendTimeoutMultiplier { get; set; }
+
+ ///
+ /// Number of stop bits (default: one)
+ ///
+ public StopBits StopBits { get; set; } = StopBits.One;
+
+ ///
+ /// If true, transmission is halted unless CTS is asserted by the remote station (default: false)
+ ///
+ public bool TxFlowCts { get; set; }
+
+ ///
+ /// If true, transmission is halted unless DSR is asserted by the remote station (default: false)
+ ///
+ public bool TxFlowDsr { get; set; }
+
+ ///
+ /// If true, transmission is halted when Xoff is received and restarted when Xon is received (default: false)
+ ///
+ public bool TxFlowX { get; set; }
+
+ ///
+ /// Requested size for transmit queue (default: 0 = use operating system default)
+ ///
+ public int TxQueue { get; set; }
+
+ ///
+ /// If false, transmission is suspended when this station has sent Xoff to the remote station (default: true)
+ /// Set false if the remote station treats any character as an Xon.
+ ///
+ public bool TxWhenRxXoff { get; set; } = true;
+
+ ///
+ /// Specidies the use to which the DTR output is put (default: none)
+ ///
+ public HsOutput UseDtr { get; set; } = HsOutput.None;
+
+ ///
+ /// Specifies the use to which the RTS output is put (default: none)
+ ///
+ public HsOutput UseRts { get; set; } = HsOutput.None;
+
+ ///
+ /// The character used to signal Xoff for X flow control (default: DC3)
+ ///
+ public ASCII XoffChar { get; set; } = ASCII.DC3;
+
+ ///
+ /// The character used to signal Xon for X flow control (default: DC1)
+ ///
+ public ASCII XonChar { get; set; } = ASCII.DC1;
+
+ ///
+ /// True if online.
+ ///
+ public bool Online => _online && CheckOnline();
+
+ ///
+ /// True if the RTS pin is controllable via the RTS property
+ ///
+ protected bool RtSavailable => _stateRts < 2;
+
+ ///
+ /// Set the state of the RTS modem control output
+ ///
+ protected bool Rts
+ {
+ set
+ {
+ if (_stateRts > 1) return;
+ CheckOnline();
+ if (value)
+ {
+ if (Win32Com.EscapeCommFunction(_hPort.DangerousGetHandle(), Win32Com.SETRTS))
+ _stateRts = 1;
+ else
+ ThrowException("Unexpected Failure");
+ }
+ else
+ {
+ if (Win32Com.EscapeCommFunction(_hPort.DangerousGetHandle(), Win32Com.CLRRTS))
+ _stateRts = 1;
+ else
+ ThrowException("Unexpected Failure");
+ }
+ }
+ get => _stateRts == 1;
+ }
+
+ ///
+ /// True if the DTR pin is controllable via the DTR property
+ ///
+ protected bool DtrAvailable => _stateDtr < 2;
+
+ ///
+ /// The state of the DTR modem control output
+ ///
+ protected bool Dtr
+ {
+ set
+ {
+ if (_stateDtr > 1) return;
+ CheckOnline();
+ if (value)
+ {
+ if (Win32Com.EscapeCommFunction(_hPort.DangerousGetHandle(), Win32Com.SETDTR))
+ _stateDtr = 1;
+ else
+ ThrowException("Unexpected Failure");
+ }
+ else
+ {
+ if (Win32Com.EscapeCommFunction(_hPort.DangerousGetHandle(), Win32Com.CLRDTR))
+ _stateDtr = 0;
+ else
+ ThrowException("Unexpected Failure");
+ }
+ }
+ get => _stateDtr == 1;
+ }
+
+ ///
+ /// Assert or remove a break condition from the transmission line
+ ///
+ protected bool Break
+ {
+ set
+ {
+ if (_stateBrk > 1) return;
+ CheckOnline();
+ if (value)
+ {
+ if (Win32Com.EscapeCommFunction(_hPort.DangerousGetHandle(), Win32Com.SETBREAK))
+ _stateBrk = 0;
+ else
+ ThrowException("Unexpected Failure");
+ }
+ else
+ {
+ if (Win32Com.EscapeCommFunction(_hPort.DangerousGetHandle(), Win32Com.CLRBREAK))
+ _stateBrk = 0;
+ else
+ ThrowException("Unexpected Failure");
+ }
+ }
+ get => _stateBrk == 1;
+ }
+
+
+ ///
+ /// Port Name
+ ///
+ public string PortName { get; set; }
+
+ public Handshake Handshake
+ {
+ get => _handShake;
+ set
+ {
+ _handShake = value;
+ switch (_handShake)
+ {
+ case Handshake.None:
+ TxFlowCts = false;
+ TxFlowDsr = false;
+ TxFlowX = false;
+ RxFlowX = false;
+ UseRts = HsOutput.Online;
+ UseDtr = HsOutput.Online;
+ TxWhenRxXoff = true;
+ RxGateDsr = false;
+ break;
+ case Handshake.XonXoff:
+ TxFlowCts = false;
+ TxFlowDsr = false;
+ TxFlowX = true;
+ RxFlowX = true;
+ UseRts = HsOutput.Online;
+ UseDtr = HsOutput.Online;
+ TxWhenRxXoff = true;
+ RxGateDsr = false;
+ XonChar = ASCII.DC1;
+ XoffChar = ASCII.DC3;
+ break;
+ case Handshake.CtsRts:
+ TxFlowCts = true;
+ TxFlowDsr = false;
+ TxFlowX = false;
+ RxFlowX = false;
+ UseRts = HsOutput.Handshake;
+ UseDtr = HsOutput.Online;
+ TxWhenRxXoff = true;
+ RxGateDsr = false;
+ break;
+ case Handshake.DsrDtr:
+ TxFlowCts = false;
+ TxFlowDsr = true;
+ TxFlowX = false;
+ RxFlowX = false;
+ UseRts = HsOutput.Online;
+ UseDtr = HsOutput.Handshake;
+ TxWhenRxXoff = true;
+ RxGateDsr = false;
+ break;
+ }
+ }
+ }
+
+ #endregion
}
\ No newline at end of file