From 0686ecf2054e28b07d51dc3d7c99f879b2e16f6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20H=C3=B6glinger?= Date: Sun, 29 Dec 2019 16:49:00 +0100 Subject: [PATCH 01/13] Corrected copyright notice --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 5353eb2..21ca638 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2018 Benjamin Höglinger-Stelzer +Copyright (c) 2012-2017 Ebrahim Byagowi, 2018 Benjamin Höglinger-Stelzer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 8e53bb38148b1ddbc8bc5a03f8d6e261ffdbe9a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20H=C3=B6glinger?= Date: Sun, 29 Dec 2019 16:56:12 +0100 Subject: [PATCH 02/13] Update README.md --- README.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7e404bb..5433b46 100755 --- a/README.md +++ b/README.md @@ -1,7 +1,15 @@ -P/Invoke wrapper for Win32API serial port +# P/Invoke wrapper for Win32API serial port -Originally copied from http://msdn.microsoft.com/en-us/magazine/cc301786.aspx that I guess licensed under Ms-PL so this project is also under Ms-PL. (Update: well, after the years now I think this was not a true claim, but well I don't think MS will sue anyone because a sample intended for public use) +## About + +Originally copied from [John Hind - "Use P/Invoke to Develop a .NET Base Class Library for Serial Device Communications"](http://msdn.microsoft.com/en-us/magazine/cc301786.aspx) that I guess licensed under Ms-PL so this project is also under Ms-PL. (Update: well, after the years now I think this was not a true claim, but well I don't think MS will sue anyone because a sample intended for public use) It is useful in the cases System.IO.Ports.SerialPort is not working well (for connecting to \\\\.\\... devices) -Download it from https://nuget.org/packages/PInvokeSerialPort :) +## Motivation behind this fork + +`System.IO.Ports.SerialPort` is terrible and [this is exactly what I've experienced in a project](https://www.sparxeng.com/blog/software/must-use-net-system-io-ports-serialport) so this library came to the rescue. + +## Download + +Consume the NuGet via `Install-Package Nefarius.PInvokeSerialPort` From 49e29929331301ca72f8d075b4ae03f3a790c7ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20H=C3=B6glinger-Stelzer?= Date: Mon, 26 Sep 2022 21:44:50 +0200 Subject: [PATCH 03/13] Migrated most native APIs to CsWin32 --- .../ModemStatus.cs | 55 +- .../NativeMethods.txt | 3 +- .../Nefarius.Peripherals.SerialPort.csproj | 1 + Nefarius.Peripherals.SerialPort/SerialPort.cs | 1535 +++++++++-------- Nefarius.Peripherals.SerialPort/Util.cs | 23 + .../Win32PInvoke/COMMPROP.cs | 28 - .../Win32PInvoke/COMMTIMEOUTS.cs | 15 - .../Win32PInvoke/COMSTAT.cs | 29 +- .../Win32PInvoke/DCB.cs | 41 - .../Win32PInvoke/OVERLAPPED.cs | 15 - .../Win32PInvoke/Win32Com.cs | 155 -- 11 files changed, 871 insertions(+), 1029 deletions(-) create mode 100644 Nefarius.Peripherals.SerialPort/Util.cs delete mode 100644 Nefarius.Peripherals.SerialPort/Win32PInvoke/COMMPROP.cs delete mode 100644 Nefarius.Peripherals.SerialPort/Win32PInvoke/COMMTIMEOUTS.cs delete mode 100644 Nefarius.Peripherals.SerialPort/Win32PInvoke/DCB.cs delete mode 100644 Nefarius.Peripherals.SerialPort/Win32PInvoke/OVERLAPPED.cs delete mode 100644 Nefarius.Peripherals.SerialPort/Win32PInvoke/Win32Com.cs diff --git a/Nefarius.Peripherals.SerialPort/ModemStatus.cs b/Nefarius.Peripherals.SerialPort/ModemStatus.cs index dc993c9..a7ad443 100644 --- a/Nefarius.Peripherals.SerialPort/ModemStatus.cs +++ b/Nefarius.Peripherals.SerialPort/ModemStatus.cs @@ -1,29 +1,36 @@ -using Nefarius.Peripherals.SerialPort.Win32PInvoke; +using Windows.Win32.Devices.Communication; -namespace Nefarius.Peripherals.SerialPort +namespace Nefarius.Peripherals.SerialPort; + +/// +/// Represents the status of the modem control input signals. +/// +public readonly struct ModemStatus { - /// - /// Represents the status of the modem control input signals. - /// - public struct ModemStatus + private readonly uint _status; + + internal ModemStatus(uint val) { - private readonly uint _status; - internal ModemStatus(uint val) { _status = val; } - /// - /// Condition of the Clear To Send signal. - /// - public bool Cts { get { return ((_status & Win32Com.MS_CTS_ON) != 0); } } - /// - /// Condition of the Data Set Ready signal. - /// - public bool Dsr { get { return ((_status & Win32Com.MS_DSR_ON) != 0); } } - /// - /// Condition of the Receive Line Status Detection signal. - /// - public bool Rlsd { get { return ((_status & Win32Com.MS_RLSD_ON) != 0); } } - /// - /// Condition of the Ring Detection signal. - /// - public bool Ring { get { return ((_status & Win32Com.MS_RING_ON) != 0); } } + _status = val; } + + /// + /// Condition of the Clear To Send signal. + /// + public bool Cts => (_status & (uint)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; + + /// + /// Condition of the Receive Line Status Detection signal. + /// + public bool Rlsd => (_status & (uint)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; } \ No newline at end of file diff --git a/Nefarius.Peripherals.SerialPort/NativeMethods.txt b/Nefarius.Peripherals.SerialPort/NativeMethods.txt index c2b548f..15077ca 100644 --- a/Nefarius.Peripherals.SerialPort/NativeMethods.txt +++ b/Nefarius.Peripherals.SerialPort/NativeMethods.txt @@ -13,4 +13,5 @@ EscapeCommFunction GetCommModemStatus GetOverlappedResult ClearCommError -GetCommProperties \ No newline at end of file +GetCommProperties +WIN32_ERROR \ No newline at end of file diff --git a/Nefarius.Peripherals.SerialPort/Nefarius.Peripherals.SerialPort.csproj b/Nefarius.Peripherals.SerialPort/Nefarius.Peripherals.SerialPort.csproj index ce80c6b..a071ba9 100644 --- a/Nefarius.Peripherals.SerialPort/Nefarius.Peripherals.SerialPort.csproj +++ b/Nefarius.Peripherals.SerialPort/Nefarius.Peripherals.SerialPort.csproj @@ -30,6 +30,7 @@ + all diff --git a/Nefarius.Peripherals.SerialPort/SerialPort.cs b/Nefarius.Peripherals.SerialPort/SerialPort.cs index 673e2a5..2ca73fc 100644 --- a/Nefarius.Peripherals.SerialPort/SerialPort.cs +++ b/Nefarius.Peripherals.SerialPort/SerialPort.cs @@ -2,804 +2,869 @@ using System.Runtime.InteropServices; using System.Text; using System.Threading; -using Nefarius.Peripherals.SerialPort.Win32PInvoke; +using Windows.Win32; +using Windows.Win32.Devices.Communication; +using Windows.Win32.Foundation; +using Windows.Win32.Storage.FileSystem; +using JetBrains.Annotations; -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 + [UsedImplicitly] + public bool Open() + { + var portDcb = new DCB(); + var commTimeouts = new COMMTIMEOUTS(); - private Handshake _handShake; - private IntPtr _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() == (int)WIN32_ERROR.ERROR_ACCESS_DENIED) return false; + throw new CommPortException("Port Open Failure"); } - /// - /// - /// Class constructor - /// - public SerialPort(string portName, int baudRate) : this(portName) + _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) { - 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, Win32Com.SETRTS)) - _stateRts = 1; - else - ThrowException("Unexpected Failure"); - } - else - { - if (Win32Com.EscapeCommFunction(_hPort, 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; - /// - /// The state of the DTR modem control output - /// - protected bool Dtr + _writeOverlapped = new NativeOverlapped { - set - { - if (_stateDtr > 1) return; - CheckOnline(); - if (value) - { - if (Win32Com.EscapeCommFunction(_hPort, Win32Com.SETDTR)) - _stateDtr = 1; - else - ThrowException("Unexpected Failure"); - } - else - { - if (Win32Com.EscapeCommFunction(_hPort, Win32Com.CLRDTR)) - _stateDtr = 0; - else - ThrowException("Unexpected Failure"); - } - } - get => _stateDtr == 1; - } + EventHandle = _checkSends ? _writeEvent.SafeWaitHandle.DangerousGetHandle() : IntPtr.Zero + }; - /// - /// Assert or remove a break condition from the transmission line - /// - protected bool Break + _writeCount = 0; + + _rxException = null; + _rxExceptionReported = false; + + _rxThread = new Thread(ReceiveThread) { - set - { - if (_stateBrk > 1) return; - CheckOnline(); - if (value) - { - if (Win32Com.EscapeCommFunction(_hPort, Win32Com.SETBREAK)) - _stateBrk = 0; - else - ThrowException("Unexpected Failure"); - } - else - { - if (Win32Com.EscapeCommFunction(_hPort, Win32Com.CLRBREAK)) - _stateBrk = 0; - else - ThrowException("Unexpected Failure"); - } - } - get => _stateBrk == 1; - } + 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! - /// - /// 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 = Win32Com.CreateFile(PortName, Win32Com.GENERIC_READ | Win32Com.GENERIC_WRITE, 0, IntPtr.Zero, - Win32Com.OPEN_EXISTING, Win32Com.FILE_FLAG_OVERLAPPED, IntPtr.Zero); - if (_hPort == (IntPtr) Win32Com.INVALID_HANDLE_VALUE) - { - 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 = SendTimeoutConstant; - commTimeouts.WriteTotalTimeoutMultiplier = SendTimeoutMultiplier; - portDcb.Init(Parity == Parity.Odd || Parity == Parity.Even, TxFlowCts, TxFlowDsr, - (int) UseDtr, RxGateDsr, !TxWhenRxXoff, TxFlowX, RxFlowX, (int) UseRts); - portDcb.BaudRate = BaudRate; - portDcb.ByteSize = (byte) DataBits; - portDcb.Parity = (byte) Parity; - portDcb.StopBits = (byte) StopBits; - portDcb.XoffChar = (byte) XoffChar; - portDcb.XonChar = (byte) XonChar; - portDcb.XoffLim = (short) RxHighWater; - portDcb.XonLim = (short) RxLowWater; - if (RxQueue != 0 || TxQueue != 0) - if (!Win32Com.SetupComm(_hPort, (uint) RxQueue, (uint) TxQueue)) - ThrowException("Bad queue settings"); - if (!Win32Com.SetCommState(_hPort, ref portDcb)) ThrowException("Bad com settings"); - if (!Win32Com.SetCommTimeouts(_hPort, ref 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); - if (_rxThread != null) - { - _rxThread.Abort(); - _rxThread = null; - } - - Win32Com.CloseHandle(_hPort); - 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, 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, _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, 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, 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, out er, out cs)) ThrowException("Unexpected failure"); - if (!Win32Com.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() + _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. + /// + [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; } - public event Action DataReceived; + _hPort.Close(); - /// - /// Override this to process received bytes. - /// - /// The byte that was received - protected void OnRxChar(byte ch) + _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) { - 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 + [UsedImplicitly] + public unsafe void Write(byte[] toSend) + { + CheckOnline(); + CheckResult(); + _writeCount = toSend.GetLength(0); + + fixed (byte* ptr = toSend) + fixed (NativeOverlapped* overlapped = &_writeOverlapped) { - } - - /// - /// 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 + uint sent; + if (PInvoke.WriteFile(_hPort, ptr, (uint)_writeCount, &sent, overlapped)) { - while (true) + _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 (!Win32Com.SetCommMask(_hPort, - 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, uMask, unmanagedOv)) + 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)) { - 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 & 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()); } - eventMask = (uint) Marshal.ReadInt32(uMask); - if ((eventMask & Win32Com.EV_ERR) != 0) - { - uint errs; - if (Win32Com.ClearCommError(_hPort, out errs, IntPtr.Zero)) - { - 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()); - } + throw new CommPortException("IO Error [003]"); + } - throw new CommPortException("IO Error [003]"); - } - - if ((eventMask & Win32Com.EV_RXCHAR) != 0) + if ((eventMask & COMM_EVENT_MASK.EV_RXCHAR) != 0) + { + uint gotBytes; + do { - uint gotbytes; - do + fixed (byte* ptrBuffer = buf) { - if (!Win32Com.ReadFile(_hPort, buf, 1, out gotbytes, unmanagedOv)) + if (!PInvoke.ReadFile(_hPort, ptrBuffer, 1, &gotBytes, &ov)) { - if (Marshal.GetLastWin32Error() == Win32Com.ERROR_IO_PENDING) + if (Marshal.GetLastWin32Error() == (int)WIN32_ERROR.ERROR_IO_PENDING) { - Win32Com.CancelIo(_hPort); - gotbytes = 0; + PInvoke.CancelIo(_hPort); + 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, out f)) throw new CommPortException("IO Error [005]"); - OnStatusChange(new ModemStatus(i), new ModemStatus(f)); - } - } - } - catch (Exception e) - { - if (uMask != IntPtr.Zero) Marshal.FreeHGlobal(uMask); - if (unmanagedOv != IntPtr.Zero) Marshal.FreeHGlobal(unmanagedOv); - if (!(e is ThreadAbortException)) - { - _rxException = e; - OnRxException(e); + 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)); } } - - private bool CheckOnline() + catch (Exception e) { - if (_rxException != null && !_rxExceptionReported) + if (e is not ThreadAbortException) { - _rxExceptionReported = true; - ThrowException("rx"); + _rxException = e; + OnRxException(e); } + } + } - if (_online) - { - uint f; - if (Win32Com.GetHandleInformation(_hPort, 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 (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; + + private Handshake _handShake; + private SafeHandle _hPort; + private bool _online; + private NativeOverlapped _writeOverlapped; + 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) + /// + [UsedImplicitly] + public bool AutoReopen { get; set; } + + /// + /// Baud Rate (default: 115200) + /// + /// Unsupported rates will throw "Bad settings". + [UsedImplicitly] + 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. + /// + [UsedImplicitly] + public bool CheckAllSends { get; set; } = true; + + /// + /// Number of databits 1..8 (default: 8) unsupported values will throw "Bad settings" + /// + [UsedImplicitly] + public int DataBits { get; set; } = 8; + + /// + /// The parity checking scheme (default: none) + /// + [UsedImplicitly] + 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) + /// + [UsedImplicitly] + public bool RxFlowX { get; set; } + + /// + /// If true, received characters are ignored unless DSR is asserted by the remote station (default: false) + /// + [UsedImplicitly] + public bool RxGateDsr { get; set; } + + /// + /// The number of free bytes in the reception queue at which flow is disabled (default: 2048) + /// + [UsedImplicitly] + public int RxHighWater { get; set; } = 2048; + + /// + /// The number of bytes in the reception queue at which flow is re-enabled (default: 512) + /// + [UsedImplicitly] + public int RxLowWater { get; set; } = 512; + + /// + /// Requested size for receive queue (default: 0 = use operating system default) + /// + [UsedImplicitly] + public int RxQueue { get; set; } + + /// + /// Constant. Max time for Send in ms = (Multiplier * Characters) + Constant (default: 0) + /// + [UsedImplicitly] + public int SendTimeoutConstant { get; set; } + + /// + /// Multiplier. Max time for Send in ms = (Multiplier * Characters) + Constant + /// (default: 0 = No timeout) + /// + [UsedImplicitly] + public int SendTimeoutMultiplier { get; set; } + + /// + /// Number of stop bits (default: one) + /// + [UsedImplicitly] + public StopBits StopBits { get; set; } = StopBits.One; + + /// + /// If true, transmission is halted unless CTS is asserted by the remote station (default: false) + /// + [UsedImplicitly] + public bool TxFlowCts { get; set; } + + /// + /// If true, transmission is halted unless DSR is asserted by the remote station (default: false) + /// + [UsedImplicitly] + public bool TxFlowDsr { get; set; } + + /// + /// If true, transmission is halted when Xoff is received and restarted when Xon is received (default: false) + /// + [UsedImplicitly] + public bool TxFlowX { get; set; } + + /// + /// Requested size for transmit queue (default: 0 = use operating system default) + /// + [UsedImplicitly] + 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. + /// + [UsedImplicitly] + public bool TxWhenRxXoff { get; set; } = true; + + /// + /// Specidies the use to which the DTR output is put (default: none) + /// + [UsedImplicitly] + public HsOutput UseDtr { get; set; } = HsOutput.None; + + /// + /// Specifies the use to which the RTS output is put (default: none) + /// + [UsedImplicitly] + public HsOutput UseRts { get; set; } = HsOutput.None; + + /// + /// The character used to signal Xoff for X flow control (default: DC3) + /// + [UsedImplicitly] + public ASCII XoffChar { get; set; } = ASCII.DC3; + + /// + /// The character used to signal Xon for X flow control (default: DC1) + /// + [UsedImplicitly] + public ASCII XonChar { get; set; } = ASCII.DC1; + + /// + /// True if online. + /// + [UsedImplicitly] + public bool Online => _online && CheckOnline(); + + /// + /// True if the RTS pin is controllable via the RTS property + /// + [UsedImplicitly] + 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 (PInvoke.EscapeCommFunction(_hPort, ESCAPE_COMM_FUNCTION.SETRTS)) + _stateRts = 1; + else + ThrowException("Unexpected Failure"); + } + else + { + if (PInvoke.EscapeCommFunction(_hPort, ESCAPE_COMM_FUNCTION.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 (PInvoke.EscapeCommFunction(_hPort, ESCAPE_COMM_FUNCTION.SETDTR)) + _stateDtr = 1; + else + ThrowException("Unexpected Failure"); + } + else + { + if (PInvoke.EscapeCommFunction(_hPort, ESCAPE_COMM_FUNCTION.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 (PInvoke.EscapeCommFunction(_hPort, ESCAPE_COMM_FUNCTION.SETBREAK)) + _stateBrk = 0; + else + ThrowException("Unexpected Failure"); + } + else + { + if (PInvoke.EscapeCommFunction(_hPort, ESCAPE_COMM_FUNCTION.CLRBREAK)) + _stateBrk = 0; + else + ThrowException("Unexpected Failure"); + } + } + get => _stateBrk == 1; + } + + + /// + /// Port Name + /// + [UsedImplicitly] + 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 diff --git a/Nefarius.Peripherals.SerialPort/Util.cs b/Nefarius.Peripherals.SerialPort/Util.cs new file mode 100644 index 0000000..c0f22b0 --- /dev/null +++ b/Nefarius.Peripherals.SerialPort/Util.cs @@ -0,0 +1,23 @@ +using Windows.Win32.Devices.Communication; + +namespace Nefarius.Peripherals.SerialPort; + +internal static class DCBExtensions +{ + public static void Init(this DCB dcb, bool parity, bool outCts, bool outDsr, int dtr, bool inDsr, bool txc, + bool xOut, + bool xIn, int rts) + { + dcb.DCBlength = 28; + dcb._bitfield = 0x8001; + if (parity) dcb._bitfield |= 0x0002; + if (outCts) dcb._bitfield |= 0x0004; + if (outDsr) dcb._bitfield |= 0x0008; + dcb._bitfield |= (uint)((dtr & 0x0003) << 4); + if (inDsr) dcb._bitfield |= 0x0040; + if (txc) dcb._bitfield |= 0x0080; + if (xOut) dcb._bitfield |= 0x0100; + if (xIn) dcb._bitfield |= 0x0200; + dcb._bitfield |= (uint)((rts & 0x0003) << 12); + } +} \ No newline at end of file diff --git a/Nefarius.Peripherals.SerialPort/Win32PInvoke/COMMPROP.cs b/Nefarius.Peripherals.SerialPort/Win32PInvoke/COMMPROP.cs deleted file mode 100644 index bbd37be..0000000 --- a/Nefarius.Peripherals.SerialPort/Win32PInvoke/COMMPROP.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace Nefarius.Peripherals.SerialPort.Win32PInvoke -{ - [StructLayout(LayoutKind.Sequential)] - internal struct COMMPROP - { - internal UInt16 wPacketLength; - internal UInt16 wPacketVersion; - internal UInt32 dwServiceMask; - internal UInt32 dwReserved1; - internal UInt32 dwMaxTxQueue; - internal UInt32 dwMaxRxQueue; - internal UInt32 dwMaxBaud; - internal UInt32 dwProvSubType; - internal UInt32 dwProvCapabilities; - internal UInt32 dwSettableParams; - internal UInt32 dwSettableBaud; - internal UInt16 wSettableData; - internal UInt16 wSettableStopParity; - internal UInt32 dwCurrentTxQueue; - internal UInt32 dwCurrentRxQueue; - internal UInt32 dwProvSpec1; - internal UInt32 dwProvSpec2; - internal Byte wcProvChar; - } -} \ No newline at end of file diff --git a/Nefarius.Peripherals.SerialPort/Win32PInvoke/COMMTIMEOUTS.cs b/Nefarius.Peripherals.SerialPort/Win32PInvoke/COMMTIMEOUTS.cs deleted file mode 100644 index 6f74e2b..0000000 --- a/Nefarius.Peripherals.SerialPort/Win32PInvoke/COMMTIMEOUTS.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace Nefarius.Peripherals.SerialPort.Win32PInvoke -{ - [StructLayout(LayoutKind.Sequential)] - internal struct COMMTIMEOUTS - { - internal Int32 ReadIntervalTimeout; - internal Int32 ReadTotalTimeoutMultiplier; - internal Int32 ReadTotalTimeoutConstant; - internal Int32 WriteTotalTimeoutMultiplier; - internal Int32 WriteTotalTimeoutConstant; - } -} \ No newline at end of file diff --git a/Nefarius.Peripherals.SerialPort/Win32PInvoke/COMSTAT.cs b/Nefarius.Peripherals.SerialPort/Win32PInvoke/COMSTAT.cs index 523182c..693bae2 100644 --- a/Nefarius.Peripherals.SerialPort/Win32PInvoke/COMSTAT.cs +++ b/Nefarius.Peripherals.SerialPort/Win32PInvoke/COMSTAT.cs @@ -1,20 +1,19 @@ using System; using System.Runtime.InteropServices; -namespace Nefarius.Peripherals.SerialPort.Win32PInvoke +namespace Nefarius.Peripherals.SerialPort.Win32PInvoke; + +[StructLayout(LayoutKind.Sequential)] +internal struct COMSTAT { - [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; - } + 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 diff --git a/Nefarius.Peripherals.SerialPort/Win32PInvoke/DCB.cs b/Nefarius.Peripherals.SerialPort/Win32PInvoke/DCB.cs deleted file mode 100644 index ff3bee2..0000000 --- a/Nefarius.Peripherals.SerialPort/Win32PInvoke/DCB.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace Nefarius.Peripherals.SerialPort.Win32PInvoke -{ - [StructLayout(LayoutKind.Sequential)] - internal struct DCB - { - internal Int32 DCBlength; - internal Int32 BaudRate; - internal Int32 PackedValues; - internal Int16 wReserved; - internal Int16 XonLim; - internal Int16 XoffLim; - internal Byte ByteSize; - internal Byte Parity; - internal Byte StopBits; - internal Byte XonChar; - internal Byte XoffChar; - internal Byte ErrorChar; - internal Byte EofChar; - internal Byte EvtChar; - internal Int16 wReserved1; - - internal void Init(bool parity, bool outCts, bool outDsr, int dtr, bool inDsr, bool txc, bool xOut, - bool xIn, int rts) - { - DCBlength = 28; PackedValues = 0x8001; - if (parity) PackedValues |= 0x0002; - if (outCts) PackedValues |= 0x0004; - if (outDsr) PackedValues |= 0x0008; - PackedValues |= ((dtr & 0x0003) << 4); - if (inDsr) PackedValues |= 0x0040; - if (txc) PackedValues |= 0x0080; - if (xOut) PackedValues |= 0x0100; - if (xIn) PackedValues |= 0x0200; - PackedValues |= ((rts & 0x0003) << 12); - - } - } -} \ No newline at end of file diff --git a/Nefarius.Peripherals.SerialPort/Win32PInvoke/OVERLAPPED.cs b/Nefarius.Peripherals.SerialPort/Win32PInvoke/OVERLAPPED.cs deleted file mode 100644 index 8bd992a..0000000 --- a/Nefarius.Peripherals.SerialPort/Win32PInvoke/OVERLAPPED.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace Nefarius.Peripherals.SerialPort.Win32PInvoke -{ - [StructLayout(LayoutKind.Sequential)] - internal struct OVERLAPPED - { - internal UIntPtr Internal; - internal UIntPtr InternalHigh; - internal UInt32 Offset; - internal UInt32 OffsetHigh; - internal IntPtr hEvent; - } -} \ No newline at end of file diff --git a/Nefarius.Peripherals.SerialPort/Win32PInvoke/Win32Com.cs b/Nefarius.Peripherals.SerialPort/Win32PInvoke/Win32Com.cs deleted file mode 100644 index cd2cbb7..0000000 --- a/Nefarius.Peripherals.SerialPort/Win32PInvoke/Win32Com.cs +++ /dev/null @@ -1,155 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace Nefarius.Peripherals.SerialPort.Win32PInvoke -{ - internal class Win32Com - { - /// - /// Opening Testing and Closing the Port Handle. - /// - [DllImport("kernel32.dll", SetLastError = true)] - internal static extern IntPtr CreateFile(String lpFileName, UInt32 dwDesiredAccess, UInt32 dwShareMode, - IntPtr lpSecurityAttributes, UInt32 dwCreationDisposition, UInt32 dwFlagsAndAttributes, - IntPtr hTemplateFile); - - //Constants for errors: - internal const UInt32 ERROR_FILE_NOT_FOUND = 2; - internal const UInt32 ERROR_INVALID_NAME = 123; - internal const UInt32 ERROR_ACCESS_DENIED = 5; - internal const UInt32 ERROR_IO_PENDING = 997; - - //Constants for return value: - internal const Int32 INVALID_HANDLE_VALUE = -1; - - //Constants for dwFlagsAndAttributes: - internal const UInt32 FILE_FLAG_OVERLAPPED = 0x40000000; - - //Constants for dwCreationDisposition: - internal const UInt32 OPEN_EXISTING = 3; - - //Constants for dwDesiredAccess: - internal const UInt32 GENERIC_READ = 0x80000000; - internal const UInt32 GENERIC_WRITE = 0x40000000; - - [DllImport("kernel32.dll")] - internal static extern Boolean CloseHandle(IntPtr hObject); - - [DllImport("kernel32.dll")] - internal static extern Boolean GetHandleInformation(IntPtr hObject, out UInt32 lpdwFlags); - - - /// - /// Manipulating the communications settings. - /// - - [DllImport("kernel32.dll")] - internal static extern Boolean GetCommState(IntPtr hFile, ref DCB lpDCB); - - [DllImport("kernel32.dll")] - internal static extern Boolean GetCommTimeouts(IntPtr hFile, out COMMTIMEOUTS lpCommTimeouts); - - [DllImport("kernel32.dll")] - internal static extern Boolean BuildCommDCBAndTimeouts(String lpDef, ref DCB lpDCB, ref COMMTIMEOUTS lpCommTimeouts); - - [DllImport("kernel32.dll")] - internal static extern Boolean SetCommState(IntPtr hFile, [In] ref DCB lpDCB); - - [DllImport("kernel32.dll")] - internal static extern Boolean SetCommTimeouts(IntPtr hFile, [In] ref COMMTIMEOUTS lpCommTimeouts); - - [DllImport("kernel32.dll")] - internal static extern Boolean SetupComm(IntPtr hFile, UInt32 dwInQueue, UInt32 dwOutQueue); - - /// - /// Reading and writing. - /// - [DllImport("kernel32.dll", SetLastError = true)] - internal static extern Boolean WriteFile(IntPtr fFile, Byte[] lpBuffer, UInt32 nNumberOfBytesToWrite, - out UInt32 lpNumberOfBytesWritten, IntPtr lpOverlapped); - - [DllImport("kernel32.dll")] - internal static extern Boolean SetCommMask(IntPtr hFile, UInt32 dwEvtMask); - - // Constants for dwEvtMask: - internal const UInt32 EV_RXCHAR = 0x0001; - internal const UInt32 EV_RXFLAG = 0x0002; - internal const UInt32 EV_TXEMPTY = 0x0004; - internal const UInt32 EV_CTS = 0x0008; - internal const UInt32 EV_DSR = 0x0010; - internal const UInt32 EV_RLSD = 0x0020; - internal const UInt32 EV_BREAK = 0x0040; - internal const UInt32 EV_ERR = 0x0080; - internal const UInt32 EV_RING = 0x0100; - internal const UInt32 EV_PERR = 0x0200; - internal const UInt32 EV_RX80FULL = 0x0400; - internal const UInt32 EV_EVENT1 = 0x0800; - internal const UInt32 EV_EVENT2 = 0x1000; - - [DllImport("kernel32.dll", SetLastError = true)] - internal static extern Boolean WaitCommEvent(IntPtr hFile, IntPtr lpEvtMask, IntPtr lpOverlapped); - - [DllImport("kernel32.dll")] - internal static extern Boolean CancelIo(IntPtr hFile); - - [DllImport("kernel32.dll", SetLastError = true)] - internal static extern Boolean ReadFile(IntPtr hFile, [Out] Byte[] lpBuffer, UInt32 nNumberOfBytesToRead, - out UInt32 nNumberOfBytesRead, IntPtr lpOverlapped); - - [DllImport("kernel32.dll")] - internal static extern Boolean TransmitCommChar(IntPtr hFile, Byte cChar); - - /// - /// Control port functions. - /// - [DllImport("kernel32.dll")] - internal static extern Boolean EscapeCommFunction(IntPtr hFile, UInt32 dwFunc); - - // Constants for dwFunc: - internal const UInt32 SETXOFF = 1; - internal const UInt32 SETXON = 2; - internal const UInt32 SETRTS = 3; - internal const UInt32 CLRRTS = 4; - internal const UInt32 SETDTR = 5; - internal const UInt32 CLRDTR = 6; - internal const UInt32 RESETDEV = 7; - internal const UInt32 SETBREAK = 8; - internal const UInt32 CLRBREAK = 9; - - [DllImport("kernel32.dll")] - internal static extern Boolean GetCommModemStatus(IntPtr hFile, out UInt32 lpModemStat); - - // Constants for lpModemStat: - internal const UInt32 MS_CTS_ON = 0x0010; - internal const UInt32 MS_DSR_ON = 0x0020; - internal const UInt32 MS_RING_ON = 0x0040; - internal const UInt32 MS_RLSD_ON = 0x0080; - - /// - /// Status Functions. - /// - [DllImport("kernel32.dll", SetLastError = true)] - internal static extern Boolean GetOverlappedResult(IntPtr hFile, IntPtr lpOverlapped, - out UInt32 nNumberOfBytesTransferred, Boolean bWait); - - [DllImport("kernel32.dll")] - internal static extern Boolean ClearCommError(IntPtr hFile, out UInt32 lpErrors, IntPtr lpStat); - [DllImport("kernel32.dll")] - internal static extern Boolean ClearCommError(IntPtr hFile, out UInt32 lpErrors, out COMSTAT cs); - - //Constants for lpErrors: - internal const UInt32 CE_RXOVER = 0x0001; - internal const UInt32 CE_OVERRUN = 0x0002; - internal const UInt32 CE_RXPARITY = 0x0004; - internal const UInt32 CE_FRAME = 0x0008; - internal const UInt32 CE_BREAK = 0x0010; - internal const UInt32 CE_TXFULL = 0x0100; - internal const UInt32 CE_PTO = 0x0200; - internal const UInt32 CE_IOE = 0x0400; - internal const UInt32 CE_DNS = 0x0800; - internal const UInt32 CE_OOP = 0x1000; - internal const UInt32 CE_MODE = 0x8000; - [DllImport("kernel32.dll")] - internal static extern Boolean GetCommProperties(IntPtr hFile, out COMMPROP cp); - } -} \ No newline at end of file From db03e8103360e4dd3e3a732640366784cf2b68ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20H=C3=B6glinger-Stelzer?= Date: Mon, 26 Sep 2022 21:49:14 +0200 Subject: [PATCH 04/13] Disabled building tests for now --- Nefarius.Peripherals.SerialPort.sln | 8 +++----- PInvokeSerialPort.Test/PInvokeSerialPortTest.cs | 1 + 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Nefarius.Peripherals.SerialPort.sln b/Nefarius.Peripherals.SerialPort.sln index 3b5a375..5d37659 100644 --- a/Nefarius.Peripherals.SerialPort.sln +++ b/Nefarius.Peripherals.SerialPort.sln @@ -1,9 +1,9 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29613.14 +# Visual Studio Version 17 +VisualStudioVersion = 17.3.32825.248 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PInvokeSerialPort", "Nefarius.Peripherals.SerialPort\Nefarius.Peripherals.SerialPort.csproj", "{AEC711A5-AA9B-4127-A82C-C4D8FDA9741A}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Nefarius.Peripherals.SerialPort", "Nefarius.Peripherals.SerialPort\Nefarius.Peripherals.SerialPort.csproj", "{AEC711A5-AA9B-4127-A82C-C4D8FDA9741A}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{99052083-B245-462F-8778-4C94662ABABE}" ProjectSection(SolutionItems) = preProject @@ -47,7 +47,6 @@ Global {928609B4-70AB-4D93-A43E-4BE75C279066}.Release|Any CPU.ActiveCfg = Release|Any CPU {928609B4-70AB-4D93-A43E-4BE75C279066}.Release|Any CPU.Build.0 = Release|Any CPU {928609B4-70AB-4D93-A43E-4BE75C279066}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {928609B4-70AB-4D93-A43E-4BE75C279066}.Release|Mixed Platforms.Build.0 = Release|Any CPU {928609B4-70AB-4D93-A43E-4BE75C279066}.Release|x86.ActiveCfg = Release|Any CPU {76FAB402-7515-4A9B-8605-4FEC0736C78A}.Debug|Any CPU.ActiveCfg = Debug|x86 {76FAB402-7515-4A9B-8605-4FEC0736C78A}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 @@ -56,7 +55,6 @@ Global {76FAB402-7515-4A9B-8605-4FEC0736C78A}.Debug|x86.Build.0 = Debug|x86 {76FAB402-7515-4A9B-8605-4FEC0736C78A}.Release|Any CPU.ActiveCfg = Release|x86 {76FAB402-7515-4A9B-8605-4FEC0736C78A}.Release|Mixed Platforms.ActiveCfg = Release|x86 - {76FAB402-7515-4A9B-8605-4FEC0736C78A}.Release|Mixed Platforms.Build.0 = Release|x86 {76FAB402-7515-4A9B-8605-4FEC0736C78A}.Release|x86.ActiveCfg = Release|x86 {76FAB402-7515-4A9B-8605-4FEC0736C78A}.Release|x86.Build.0 = Release|x86 EndGlobalSection diff --git a/PInvokeSerialPort.Test/PInvokeSerialPortTest.cs b/PInvokeSerialPort.Test/PInvokeSerialPortTest.cs index e1553f5..c0c607f 100755 --- a/PInvokeSerialPort.Test/PInvokeSerialPortTest.cs +++ b/PInvokeSerialPort.Test/PInvokeSerialPortTest.cs @@ -1,4 +1,5 @@ using System; +using System.IO.Ports; using System.Text; using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Threading; From cd003539cb216606a60cd6b4eb09b7923c8d06d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20H=C3=B6glinger-Stelzer?= Date: Mon, 26 Sep 2022 21:50:41 +0200 Subject: [PATCH 05/13] Updated to VS 2022 --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 960a63b..9cd4246 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,5 +1,5 @@ version: 1.0.{build} -image: Visual Studio 2019 +image: Visual Studio 2022 configuration: - Release install: 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 06/13] 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 From c8b8859dbeb1ea2186513ea7f02ca5f8d5643e88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20H=C3=B6glinger-Stelzer?= Date: Mon, 26 Sep 2022 23:53:14 +0200 Subject: [PATCH 07/13] Updated README --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index bdc731c..a4d47a5 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ - + # Nefarius.Peripherals.SerialPort +`System.IO.Ports.SerialPort` but actually works 😏 + ## About Originally copied from [John Hind - "Use P/Invoke to Develop a .NET Base Class Library for Serial Device Communications"](http://msdn.microsoft.com/en-us/magazine/cc301786.aspx) that I guess licensed under Ms-PL so this project is also under Ms-PL. (Update: well, after the years now I think this was not a true claim, but well I don't think MS will sue anyone because a sample intended for public use) @@ -14,4 +16,4 @@ It is useful in the cases System.IO.Ports.SerialPort is not working well (for co ## Download -Consume the NuGet via `Install-Package Nefarius.PInvokeSerialPort` +Consume the NuGet via `Install-Package Nefarius.Peripherals.SerialPort` From bdf7351d52b1d798015a5a025bd1a7992b0177b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20H=C3=B6glinger-Stelzer?= Date: Mon, 26 Sep 2022 23:53:35 +0200 Subject: [PATCH 08/13] Updated README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a4d47a5..a3603e2 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ # Nefarius.Peripherals.SerialPort -`System.IO.Ports.SerialPort` but actually works 😏 +Like `System.IO.Ports.SerialPort` but actually works 😏 ## About From fa236732f346abd5d23d9622bb37741b3022c101 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20H=C3=B6glinger-Stelzer?= Date: Fri, 30 Sep 2022 16:33:06 +0200 Subject: [PATCH 09/13] Migrated sample project to SDK format --- .../PInvokeSerialPort.Sample.csproj | 62 +++---------------- PInvokeSerialPort.Sample/Program.cs | 13 ++-- .../Properties/AssemblyInfo.cs | 36 ----------- 3 files changed, 12 insertions(+), 99 deletions(-) delete mode 100755 PInvokeSerialPort.Sample/Properties/AssemblyInfo.cs diff --git a/PInvokeSerialPort.Sample/PInvokeSerialPort.Sample.csproj b/PInvokeSerialPort.Sample/PInvokeSerialPort.Sample.csproj index 3582d0a..9ccc942 100755 --- a/PInvokeSerialPort.Sample/PInvokeSerialPort.Sample.csproj +++ b/PInvokeSerialPort.Sample/PInvokeSerialPort.Sample.csproj @@ -1,69 +1,21 @@ - - + - Debug - x86 8.0.30703 - 2.0 {76FAB402-7515-4A9B-8605-4FEC0736C78A} Exe - Properties - PInvokeSerialPort.Sample - PInvokeSerialPort.Sample - v4.6.1 - - - 512 + net6 + PInvokeSerialPort.Sample + PInvokeSerialPort.Sample + Copyright © 2012 + bin\$(Configuration)\ - x86 - true full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - false - x86 pdbonly - true - bin\Release\ - TRACE - prompt - 4 - false - - - - - - - + - - - - - - - {AEC711A5-AA9B-4127-A82C-C4D8FDA9741A} - PInvokeSerialPort - - - - - - - \ No newline at end of file diff --git a/PInvokeSerialPort.Sample/Program.cs b/PInvokeSerialPort.Sample/Program.cs index 926e95c..7d6a008 100755 --- a/PInvokeSerialPort.Sample/Program.cs +++ b/PInvokeSerialPort.Sample/Program.cs @@ -1,19 +1,16 @@ using System; -using PInvokeSerialPort; +using Nefarius.Peripherals.SerialPort; namespace PInvokeSerialPort.Sample { - class Program + internal class Program { - static void Main(string[] args) + private static void Main(string[] args) { var serialPort = new SerialPort("com1", 14400); serialPort.DataReceived += x => Console.Write((char)x); serialPort.Open(); - while (true) - { - serialPort.Write(Console.ReadKey().KeyChar); - } + while (true) serialPort.Write(Console.ReadKey().KeyChar); } } -} +} \ No newline at end of file diff --git a/PInvokeSerialPort.Sample/Properties/AssemblyInfo.cs b/PInvokeSerialPort.Sample/Properties/AssemblyInfo.cs deleted file mode 100755 index 6d4480c..0000000 --- a/PInvokeSerialPort.Sample/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("PInvokeSerialPort.Sample")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("PInvokeSerialPort.Sample")] -[assembly: AssemblyCopyright("Copyright © 2012")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("9628528d-5e4d-4071-aad1-b1f85f3d45d5")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] From f7a200c8b233f8c9f2165fcae0a1f3de7b827a25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20H=C3=B6glinger-Stelzer?= Date: Fri, 30 Sep 2022 16:36:09 +0200 Subject: [PATCH 10/13] Typo fix --- Nefarius.Peripherals.SerialPort/SerialPort.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Nefarius.Peripherals.SerialPort/SerialPort.cs b/Nefarius.Peripherals.SerialPort/SerialPort.cs index 62999ba..08beacf 100644 --- a/Nefarius.Peripherals.SerialPort/SerialPort.cs +++ b/Nefarius.Peripherals.SerialPort/SerialPort.cs @@ -198,7 +198,7 @@ public sealed class SerialPort : IDisposable public bool TxWhenRxXoff { get; set; } = true; /// - /// Specidies the use to which the DTR output is put (default: none) + /// Specifies the use to which the DTR output is put (default: none) /// [UsedImplicitly] public HsOutput UseDtr { get; set; } = HsOutput.None; From 819f527663870e3ad2186fee1b7eb7d0c47cd5dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20H=C3=B6glinger-Stelzer?= Date: Fri, 30 Sep 2022 16:37:04 +0200 Subject: [PATCH 11/13] Fixed tester project --- .../Nefarius.Peripherals.SerialPort.csproj | 5 +- .../PInvokeSerialPortTest.cs | 50 ++++++++++--------- 2 files changed, 29 insertions(+), 26 deletions(-) diff --git a/Nefarius.Peripherals.SerialPort/Nefarius.Peripherals.SerialPort.csproj b/Nefarius.Peripherals.SerialPort/Nefarius.Peripherals.SerialPort.csproj index a071ba9..370ebf2 100644 --- a/Nefarius.Peripherals.SerialPort/Nefarius.Peripherals.SerialPort.csproj +++ b/Nefarius.Peripherals.SerialPort/Nefarius.Peripherals.SerialPort.csproj @@ -17,9 +17,10 @@ latest - - + + + diff --git a/PInvokeSerialPort.Test/PInvokeSerialPortTest.cs b/PInvokeSerialPort.Test/PInvokeSerialPortTest.cs index c0c607f..4a6a7da 100755 --- a/PInvokeSerialPort.Test/PInvokeSerialPortTest.cs +++ b/PInvokeSerialPort.Test/PInvokeSerialPortTest.cs @@ -1,21 +1,21 @@ using System; -using System.IO.Ports; using System.Text; -using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Threading; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Nefarius.Peripherals.SerialPort; namespace PInvokeSerialPort.Test { /// - /// Test class. - /// Attention: Run it just in test debug. + /// Test class. + /// Attention: Run it just in test debug. /// [TestClass] public class PInvokeSerialPortTest { - dynamic _sender; - dynamic _reciever; - StringBuilder _stringBuilder; + private dynamic _reciever; + private dynamic _sender; + private StringBuilder _stringBuilder; public void OpenWriteDoWaitClose(Action action) { @@ -25,7 +25,7 @@ namespace PInvokeSerialPort.Test _reciever.Open(); action(); - + _sender.Write(testSting); Thread.Sleep(100); Assert.AreEqual(testSting, _stringBuilder.ToString()); @@ -40,22 +40,22 @@ namespace PInvokeSerialPort.Test _sender = new SerialPort("com1"); _reciever = new SerialPort("com2"); OpenWriteDoWaitClose(() => - { - ((SerialPort)_reciever).DataReceived += x => _stringBuilder.Append((char)x); - }); + { + ((SerialPort)_reciever).DataReceived += x => _stringBuilder.Append((char)x); + }); } [TestMethod] public void OverallTest2() { _sender = new System.IO.Ports.SerialPort("com1"); - + _reciever = new SerialPort("com2"); - + OpenWriteDoWaitClose(() => - { - ((SerialPort)(object)_reciever).DataReceived += x => _stringBuilder.Append((char)x); - }); + { + ((SerialPort)(object)_reciever).DataReceived += x => _stringBuilder.Append((char)x); + }); } [TestMethod] @@ -65,9 +65,10 @@ namespace PInvokeSerialPort.Test _reciever = new System.IO.Ports.SerialPort("com2"); OpenWriteDoWaitClose(() => - { - ((System.IO.Ports.SerialPort)_reciever).DataReceived += (x, y) => _stringBuilder.Append(_reciever.ReadExisting()); - }); + { + ((System.IO.Ports.SerialPort)_reciever).DataReceived += + (x, y) => _stringBuilder.Append(_reciever.ReadExisting()); + }); } [TestMethod] @@ -75,11 +76,12 @@ namespace PInvokeSerialPort.Test { _sender = new System.IO.Ports.SerialPort("com1"); _reciever = new System.IO.Ports.SerialPort("com2"); - + OpenWriteDoWaitClose(() => - { - ((System.IO.Ports.SerialPort)_reciever).DataReceived += (x, y) => _stringBuilder.Append(_reciever.ReadExisting()); - }); + { + ((System.IO.Ports.SerialPort)_reciever).DataReceived += + (x, y) => _stringBuilder.Append(_reciever.ReadExisting()); + }); } } -} +} \ No newline at end of file From b0d34550b7ec9342241b2d2ea1687fd4988aec17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20H=C3=B6glinger-Stelzer?= Date: Mon, 3 Oct 2022 02:27:27 +0200 Subject: [PATCH 12/13] Attempting fixes --- .gitignore | 419 ++++++++++++++++-- Nefarius.Peripherals.SerialPort.sln | 8 +- ...ius.Peripherals.SerialPort.sln.DotSettings | 4 + Nefarius.Peripherals.SerialPort/SerialPort.cs | 2 - .../PInvokeSerialPort.Sample.csproj | 6 - PInvokeSerialPort.Sample/Program.cs | 22 +- 6 files changed, 414 insertions(+), 47 deletions(-) create mode 100644 Nefarius.Peripherals.SerialPort.sln.DotSettings diff --git a/.gitignore b/.gitignore index d29d198..bc10bc8 100755 --- a/.gitignore +++ b/.gitignore @@ -1,35 +1,400 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore -#ignore thumbnails created by windows -Thumbs.db -#Ignore files build by Visual Studio -*.obj -*.exe -*.pdb +# User-specific files +*.rsuser +*.suo *.user -*.aps -*.pch -*.vspscc +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio *_i.c *_p.c -*.ncb -*.suo -*.tlb -*.tlh -*.bak -*.cache +*_h.h *.ilk -*.log -[Bb]in -[Dd]ebug*/ -*.lib +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp *.sbr -obj/ -[Rr]elease*/ +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in _ReSharper*/ -[Tt]est[Rr]esult* -*.testsettings +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages *.nupkg -/.vs -/packages -/.tmp -*.DotSettings +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml + +/misc diff --git a/Nefarius.Peripherals.SerialPort.sln b/Nefarius.Peripherals.SerialPort.sln index 5d37659..3908637 100644 --- a/Nefarius.Peripherals.SerialPort.sln +++ b/Nefarius.Peripherals.SerialPort.sln @@ -15,7 +15,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PInvokeSerialPort.Test", "PInvokeSerialPort.Test\PInvokeSerialPort.Test.csproj", "{928609B4-70AB-4D93-A43E-4BE75C279066}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PInvokeSerialPort.Sample", "PInvokeSerialPort.Sample\PInvokeSerialPort.Sample.csproj", "{76FAB402-7515-4A9B-8605-4FEC0736C78A}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PInvokeSerialPort.Sample", "PInvokeSerialPort.Sample\PInvokeSerialPort.Sample.csproj", "{76FAB402-7515-4A9B-8605-4FEC0736C78A}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -48,12 +48,14 @@ Global {928609B4-70AB-4D93-A43E-4BE75C279066}.Release|Any CPU.Build.0 = Release|Any CPU {928609B4-70AB-4D93-A43E-4BE75C279066}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU {928609B4-70AB-4D93-A43E-4BE75C279066}.Release|x86.ActiveCfg = Release|Any CPU - {76FAB402-7515-4A9B-8605-4FEC0736C78A}.Debug|Any CPU.ActiveCfg = Debug|x86 + {76FAB402-7515-4A9B-8605-4FEC0736C78A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {76FAB402-7515-4A9B-8605-4FEC0736C78A}.Debug|Any CPU.Build.0 = Debug|Any CPU {76FAB402-7515-4A9B-8605-4FEC0736C78A}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 {76FAB402-7515-4A9B-8605-4FEC0736C78A}.Debug|Mixed Platforms.Build.0 = Debug|x86 {76FAB402-7515-4A9B-8605-4FEC0736C78A}.Debug|x86.ActiveCfg = Debug|x86 {76FAB402-7515-4A9B-8605-4FEC0736C78A}.Debug|x86.Build.0 = Debug|x86 - {76FAB402-7515-4A9B-8605-4FEC0736C78A}.Release|Any CPU.ActiveCfg = Release|x86 + {76FAB402-7515-4A9B-8605-4FEC0736C78A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {76FAB402-7515-4A9B-8605-4FEC0736C78A}.Release|Any CPU.Build.0 = Release|Any CPU {76FAB402-7515-4A9B-8605-4FEC0736C78A}.Release|Mixed Platforms.ActiveCfg = Release|x86 {76FAB402-7515-4A9B-8605-4FEC0736C78A}.Release|x86.ActiveCfg = Release|x86 {76FAB402-7515-4A9B-8605-4FEC0736C78A}.Release|x86.Build.0 = Release|x86 diff --git a/Nefarius.Peripherals.SerialPort.sln.DotSettings b/Nefarius.Peripherals.SerialPort.sln.DotSettings new file mode 100644 index 0000000..e3e5fbe --- /dev/null +++ b/Nefarius.Peripherals.SerialPort.sln.DotSettings @@ -0,0 +1,4 @@ + + True + True + True \ No newline at end of file diff --git a/Nefarius.Peripherals.SerialPort/SerialPort.cs b/Nefarius.Peripherals.SerialPort/SerialPort.cs index 08beacf..35fdf99 100644 --- a/Nefarius.Peripherals.SerialPort/SerialPort.cs +++ b/Nefarius.Peripherals.SerialPort/SerialPort.cs @@ -491,8 +491,6 @@ public sealed class SerialPort : IDisposable _auto = false; - Close(); - return false; } diff --git a/PInvokeSerialPort.Sample/PInvokeSerialPort.Sample.csproj b/PInvokeSerialPort.Sample/PInvokeSerialPort.Sample.csproj index 9ccc942..c8e3b9a 100755 --- a/PInvokeSerialPort.Sample/PInvokeSerialPort.Sample.csproj +++ b/PInvokeSerialPort.Sample/PInvokeSerialPort.Sample.csproj @@ -9,12 +9,6 @@ Copyright © 2012 bin\$(Configuration)\ - - full - - - pdbonly - diff --git a/PInvokeSerialPort.Sample/Program.cs b/PInvokeSerialPort.Sample/Program.cs index 7d6a008..415f305 100755 --- a/PInvokeSerialPort.Sample/Program.cs +++ b/PInvokeSerialPort.Sample/Program.cs @@ -1,16 +1,20 @@ using System; using Nefarius.Peripherals.SerialPort; -namespace PInvokeSerialPort.Sample +namespace PInvokeSerialPort.Sample; + +internal class Program { - internal class Program + private static void Main(string[] args) { - private static void Main(string[] args) - { - var serialPort = new SerialPort("com1", 14400); - serialPort.DataReceived += x => Console.Write((char)x); - serialPort.Open(); - while (true) serialPort.Write(Console.ReadKey().KeyChar); - } + var serialPort = new SerialPort("COM7") { UseRts = HsOutput.Online }; + + serialPort.DataReceived += x => Console.Write((char)x); + + serialPort.Open(); + + serialPort.Write("START\r\n"); + + Console.ReadKey(); } } \ No newline at end of file From 19479f2ccae1a8c3a4f11cf40e70dd512096b115 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20H=C3=B6glinger-Stelzer?= Date: Wed, 12 Jun 2024 14:11:19 +0200 Subject: [PATCH 13/13] Update README.md --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index a3603e2..9558139 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,4 @@ - - -# Nefarius.Peripherals.SerialPort +# Nefarius.Peripherals.SerialPort Like `System.IO.Ports.SerialPort` but actually works 😏