diff --git a/.gitignore b/.gitignore index bc10bc8..af5c6a9 100755 --- a/.gitignore +++ b/.gitignore @@ -1,400 +1,36 @@ -## 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 -# User-specific files -*.rsuser -*.suo +#ignore thumbnails created by windows +Thumbs.db +#Ignore files build by Visual Studio +*.obj +*.exe +*.pdb *.user -*.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 +*.aps +*.pch +*.vspscc *_i.c *_p.c -*_h.h -*.ilk -*.meta -*.obj -*.iobj -*.pch -*.pdb -*.ipdb -*.pgc -*.pgd -*.rsp -*.sbr +*.ncb +*.suo *.tlb -*.tli *.tlh -*.tmp -*.tmp_proj -*_wpftmp.csproj +*.bak +*.cache +*.ilk *.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 +[Bb]in +[Dd]ebug*/ +*.lib +*.sbr +obj/ +[Rr]elease*/ _ReSharper*/ -*.[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 +[Tt]est[Rr]esult* +*.testsettings *.nupkg -# 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 - +/.vs +/packages +/.tmp +*.DotSettings /misc diff --git a/Nefarius.Peripherals.SerialPort/NativeMethods.txt b/Nefarius.Peripherals.SerialPort/NativeMethods.txt index 15077ca..e754077 100644 --- a/Nefarius.Peripherals.SerialPort/NativeMethods.txt +++ b/Nefarius.Peripherals.SerialPort/NativeMethods.txt @@ -1,4 +1,4 @@ -CreateFile +CreateFile GetHandleInformation SetCommState SetCommTimeouts diff --git a/Nefarius.Peripherals.SerialPort/QueueStatus.cs b/Nefarius.Peripherals.SerialPort/QueueStatus.cs index 9d36b5f..63469fd 100644 --- a/Nefarius.Peripherals.SerialPort/QueueStatus.cs +++ b/Nefarius.Peripherals.SerialPort/QueueStatus.cs @@ -1,149 +1,151 @@ using System.Text; +using Nefarius.Peripherals.SerialPort.Win32PInvoke; -namespace Nefarius.Peripherals.SerialPort; - -/// -/// Represents the current condition of the port queues. -/// -public readonly struct QueueStatus +namespace Nefarius.Peripherals.SerialPort { - 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) + /// + /// Represents the current condition of the port queues. + /// + public struct QueueStatus { - _status = stat; - _inQueue = inQ; - _outQueue = outQ; - _inQueueSize = inQs; - _outQueueSize = outQs; - } + private readonly uint _status; + private readonly uint _inQueue; + private readonly uint _outQueue; + private readonly uint _inQueueSize; + private readonly uint _outQueueSize; - /// - /// 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) + internal QueueStatus(uint stat, uint inQ, uint outQ, uint inQs, uint 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."); + _status = stat; + _inQueue = inQ; + _outQueue = outQ; + _inQueueSize = inQs; + _outQueueSize = outQs; } - 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 "); - } + 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; - if (_outQueue > 0) + /// + /// 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() { - if (CtsHold || DsrHold || RlsdHold || XoffHold || XoffSent) + 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("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"); + m.Append("is empty."); + } + else if (_inQueue == 1) + { + m.Append("contains 1 byte."); } else { - m.Append("pumping data"); + m.Append("contains "); + m.Append(_inQueue.ToString()); + m.Append(" bytes."); } - } - m.Append(". The immediate buffer is "); - if (ImmediateWaiting) - m.Append("full."); - else - m.Append("empty."); - return m.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) + { + 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 35fdf99..cfddb9a 100644 --- a/Nefarius.Peripherals.SerialPort/SerialPort.cs +++ b/Nefarius.Peripherals.SerialPort/SerialPort.cs @@ -6,7 +6,8 @@ using Windows.Win32; using Windows.Win32.Devices.Communication; using Windows.Win32.Foundation; using Windows.Win32.Storage.FileSystem; -using JetBrains.Annotations; +using Microsoft.Win32.SafeHandles; +using Nefarius.Peripherals.SerialPort.Win32PInvoke; namespace Nefarius.Peripherals.SerialPort; @@ -14,15 +15,508 @@ namespace Nefarius.Peripherals.SerialPort; /// PInvokeSerialPort main class. /// Borrowed from http://msdn.microsoft.com/en-us/magazine/cc301786.aspx ;) /// -public sealed class SerialPort : IDisposable +public 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 + 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.ReadIntervalTimeout = 0; + commTimeouts.ReadTotalTimeoutConstant = 0; + commTimeouts.ReadTotalTimeoutMultiplier = 0; + commTimeouts.WriteTotalTimeoutConstant = (uint)SendTimeoutConstant; + commTimeouts.WriteTotalTimeoutMultiplier = (uint)SendTimeoutMultiplier; + portDcb.Init(Parity is Parity.Odd or Parity.Even, TxFlowCts, TxFlowDsr, + (int)UseDtr, RxGateDsr, !TxWhenRxXoff, TxFlowX, RxFlowX, (int)UseRts); + portDcb.BaudRate = (uint)BaudRate; + portDcb.ByteSize = (byte)DataBits; + portDcb.Parity = (DCB_PARITY)Parity; + portDcb.StopBits = (DCB_STOP_BITS)StopBits; + portDcb.XoffChar = (CHAR)(byte)XoffChar; + portDcb.XonChar = (CHAR)(byte)XonChar; + portDcb.XoffLim = (ushort)RxHighWater; + portDcb.XonLim = (ushort)RxLowWater; + + if (RxQueue != 0 || TxQueue != 0) + if (!PInvoke.SetupComm(_hPort, (uint)RxQueue, (uint)TxQueue)) + ThrowException("Bad queue settings"); + + if (!PInvoke.SetCommState(_hPort, portDcb)) + ThrowException("Bad com settings"); + + if (!PInvoke.SetCommTimeouts(_hPort, commTimeouts)) + ThrowException("Bad timeout settings"); + + _stateBrk = 0; + switch (UseDtr) + { + case HsOutput.None: + _stateDtr = 0; + break; + case HsOutput.Online: + _stateDtr = 1; + break; + } + + switch (UseRts) + { + case HsOutput.None: + _stateRts = 0; + break; + case HsOutput.Online: + _stateRts = 1; + break; + } + + _checkSends = CheckAllSends; + _ptrUwo.EventHandle = _checkSends ? _writeEvent.SafeWaitHandle.DangerousGetHandle() : IntPtr.Zero; + _writeCount = 0; + + _rxException = null; + _rxExceptionReported = false; + + // TODO: utilize Task Parallel Library here + _rxThread = new Thread(ReceiveThread) + { + Name = "CommBaseRx", + Priority = ThreadPriority.AboveNormal, + IsBackground = true + }; + + _rxThread.Start(); + Thread.Sleep(1); //Give rx thread time to start. By documentation, 0 should work, but it does not! + + _auto = false; + if (AfterOpen()) + { + _auto = AutoReopen; + return true; + } + + Close(); + return false; + } + + /// + /// Closes the com port. + /// + public void Close() + { + if (_online) + { + _auto = false; + BeforeClose(false); + InternalClose(); + _rxException = null; + } + } + + private void InternalClose() + { + Win32Com.CancelIo(_hPort.DangerousGetHandle()); + if (_rxThread != null) + { + _rxThread.Abort(); + _rxThread = null; + } + + _hPort.Dispose(); + _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 unsafe void Write(byte[] toSend) + { + uint sent; + CheckOnline(); + CheckResult(); + _writeCount = toSend.GetLength(0); + + fixed (byte* ptr = toSend) + fixed (NativeOverlapped* ptrOl = &_ptrUwo) + { + if (PInvoke.WriteFile(_hPort, ptr, (uint)_writeCount, &sent, ptrOl)) + { + _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 + public void Write(string toSend) + { + Write(new ASCIIEncoding().GetBytes(toSend)); + } + + /// + /// Queues a single byte for transmission. + /// + /// Byte to be sent + public void Write(byte toSend) + { + var b = new byte[1]; + b[0] = toSend; + Write(b); + } + + /// + /// Queues a single char for transmission. + /// + /// Byte to be sent + public void Write(char toSend) + { + Write(toSend.ToString()); + } + + /// + /// Queues string with a new line ("\r\n") for transmission. + /// + /// Array of bytes to be sent + public void WriteLine(string toSend) + { + Write(new ASCIIEncoding().GetBytes(toSend + Environment.NewLine)); + } + + private void CheckResult() + { + if (_writeCount <= 0) return; + if (PInvoke.GetOverlappedResult(_hPort, _ptrUwo, out var sent, _checkSends)) + { + _writeCount -= (int)sent; + if (_writeCount != 0) ThrowException("Send Timeout"); + } + else + { + if (Marshal.GetLastWin32Error() != (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 (!Win32Com.TransmitCommChar(_hPort.DangerousGetHandle(), tosend)) ThrowException("Transmission failure"); + } + + /// + /// Gets the status of the modem control input signals. + /// + /// Modem status object + protected ModemStatus GetModemStatus() + { + uint f; + + CheckOnline(); + if (!Win32Com.GetCommModemStatus(_hPort.DangerousGetHandle(), out f)) ThrowException("Unexpected failure"); + return new ModemStatus(f); + } + + + /// + /// Get the status of the queues + /// + /// Queue status object + protected unsafe QueueStatus GetQueueStatus() + { + 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); + } + + /// + /// 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 OVERLAPPED(); + var unmanagedOv = Marshal.AllocHGlobal(Marshal.SizeOf(ov)); + ov.Offset = 0; + ov.OffsetHigh = 0; + ov.hEvent = sg.SafeWaitHandle.DangerousGetHandle(); + Marshal.StructureToPtr(ov, unmanagedOv, true); + + uint eventMask = 0; + var uMask = Marshal.AllocHGlobal(Marshal.SizeOf(eventMask)); + + try + { + while (true) + { + if (!Win32Com.SetCommMask(_hPort.DangerousGetHandle(), + Win32Com.EV_RXCHAR | Win32Com.EV_TXEMPTY | Win32Com.EV_CTS | Win32Com.EV_DSR + | Win32Com.EV_BREAK | Win32Com.EV_RLSD | Win32Com.EV_RING | Win32Com.EV_ERR)) + throw new CommPortException("IO Error [001]"); + Marshal.WriteInt32(uMask, 0); + if (!Win32Com.WaitCommEvent(_hPort.DangerousGetHandle(), uMask, unmanagedOv)) + { + if (Marshal.GetLastWin32Error() == (int)WIN32_ERROR.ERROR_IO_PENDING) + sg.WaitOne(); + else + throw new CommPortException("IO Error [002]"); + } + + eventMask = (uint)Marshal.ReadInt32(uMask); + if ((eventMask & Win32Com.EV_ERR) != 0) + { + CLEAR_COMM_ERROR_FLAGS errs; + if (PInvoke.ClearCommError(_hPort, &errs, null)) + { + var s = new StringBuilder("UART Error: ", 40); + if (((uint)errs & Win32Com.CE_FRAME) != 0) s = s.Append("Framing,"); + if (((uint)errs & Win32Com.CE_IOE) != 0) s = s.Append("IO,"); + if (((uint)errs & Win32Com.CE_OVERRUN) != 0) s = s.Append("Overrun,"); + if (((uint)errs & Win32Com.CE_RXOVER) != 0) s = s.Append("Receive Overflow,"); + if (((uint)errs & Win32Com.CE_RXPARITY) != 0) s = s.Append("Parity,"); + if (((uint)errs & Win32Com.CE_TXFULL) != 0) s = s.Append("Transmit Overflow,"); + s.Length -= 1; + throw new CommPortException(s.ToString()); + } + + throw new CommPortException("IO Error [003]"); + } + + if ((eventMask & Win32Com.EV_RXCHAR) != 0) + { + uint gotbytes; + do + { + if (!Win32Com.ReadFile(_hPort.DangerousGetHandle(), buf, 1, out gotbytes, unmanagedOv)) + { + if (Marshal.GetLastWin32Error() == (int)WIN32_ERROR.ERROR_IO_PENDING) + { + Win32Com.CancelIo(_hPort.DangerousGetHandle()); + gotbytes = 0; + } + else + { + throw new CommPortException("IO Error [004]"); + } + } + + if (gotbytes == 1) OnRxChar(buf[0]); + } while (gotbytes > 0); + } + + if ((eventMask & Win32Com.EV_TXEMPTY) != 0) OnTxDone(); + if ((eventMask & Win32Com.EV_BREAK) != 0) OnBreak(); + + uint i = 0; + if ((eventMask & Win32Com.EV_CTS) != 0) i |= Win32Com.MS_CTS_ON; + if ((eventMask & Win32Com.EV_DSR) != 0) i |= Win32Com.MS_DSR_ON; + if ((eventMask & Win32Com.EV_RLSD) != 0) i |= Win32Com.MS_RLSD_ON; + if ((eventMask & Win32Com.EV_RING) != 0) i |= Win32Com.MS_RING_ON; + if (i != 0) + { + uint f; + if (!Win32Com.GetCommModemStatus(_hPort.DangerousGetHandle(), out f)) + throw new CommPortException("IO Error [005]"); + OnStatusChange(new ModemStatus(i), new ModemStatus(f)); + } + } + } + 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); + } + } + } + + private bool CheckOnline() + { + if (_rxException != null && !_rxExceptionReported) + { + _rxExceptionReported = true; + ThrowException("rx"); + } + + if (_online) + { + uint f; + if (Win32Com.GetHandleInformation(_hPort.DangerousGetHandle(), out f)) return true; + ThrowException("Offline"); + return false; + } + + if (_auto) + if (Open()) + return true; + ThrowException("Offline"); + return false; + } + + #region Private fields + private readonly ManualResetEvent _writeEvent = new(false); private bool _auto; private bool _checkSends = true; private Handshake _handShake; - private SafeHandle _hPort; + private SafeFileHandle _hPort; private bool _online; + private NativeOverlapped _ptrUwo; private Exception _rxException; private bool _rxExceptionReported; private Thread _rxThread; @@ -30,8 +524,10 @@ public sealed class SerialPort : IDisposable private int _stateDtr = 2; private int _stateRts = 2; private int _writeCount; - private NativeOverlapped _writeOverlapped; + #endregion + + #region Public properties /// /// Class constructor @@ -50,51 +546,16 @@ public sealed 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) /// - [UsedImplicitly] public bool AutoReopen { get; set; } /// /// Baud Rate (default: 115200) /// /// Unsupported rates will throw "Bad settings". - [UsedImplicitly] public int BaudRate { get; set; } = 115200; /// @@ -102,141 +563,119 @@ public sealed class SerialPort : IDisposable /// 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; /// - /// Specifies the use to which the DTR output is put (default: none) + /// 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] - public bool RtSAvailable => _stateRts < 2; + protected bool RtSavailable => _stateRts < 2; /// /// Set the state of the RTS modem control output /// - public bool Rts + protected bool Rts { set { @@ -244,14 +683,14 @@ public sealed class SerialPort : IDisposable CheckOnline(); if (value) { - if (PInvoke.EscapeCommFunction(_hPort, ESCAPE_COMM_FUNCTION.SETRTS)) + if (Win32Com.EscapeCommFunction(_hPort.DangerousGetHandle(), Win32Com.SETRTS)) _stateRts = 1; else ThrowException("Unexpected Failure"); } else { - if (PInvoke.EscapeCommFunction(_hPort, ESCAPE_COMM_FUNCTION.CLRRTS)) + if (Win32Com.EscapeCommFunction(_hPort.DangerousGetHandle(), Win32Com.CLRRTS)) _stateRts = 1; else ThrowException("Unexpected Failure"); @@ -263,12 +702,12 @@ public sealed class SerialPort : IDisposable /// /// True if the DTR pin is controllable via the DTR property /// - public bool DtrAvailable => _stateDtr < 2; + protected bool DtrAvailable => _stateDtr < 2; /// /// The state of the DTR modem control output /// - public bool Dtr + protected bool Dtr { set { @@ -276,14 +715,14 @@ public sealed class SerialPort : IDisposable CheckOnline(); if (value) { - if (PInvoke.EscapeCommFunction(_hPort, ESCAPE_COMM_FUNCTION.SETDTR)) + if (Win32Com.EscapeCommFunction(_hPort.DangerousGetHandle(), Win32Com.SETDTR)) _stateDtr = 1; else ThrowException("Unexpected Failure"); } else { - if (PInvoke.EscapeCommFunction(_hPort, ESCAPE_COMM_FUNCTION.CLRDTR)) + if (Win32Com.EscapeCommFunction(_hPort.DangerousGetHandle(), Win32Com.CLRDTR)) _stateDtr = 0; else ThrowException("Unexpected Failure"); @@ -295,7 +734,7 @@ public sealed class SerialPort : IDisposable /// /// Assert or remove a break condition from the transmission line /// - public bool IsBreakEnabled + protected bool Break { set { @@ -303,14 +742,14 @@ public sealed class SerialPort : IDisposable CheckOnline(); if (value) { - if (PInvoke.EscapeCommFunction(_hPort, ESCAPE_COMM_FUNCTION.SETBREAK)) + if (Win32Com.EscapeCommFunction(_hPort.DangerousGetHandle(), Win32Com.SETBREAK)) _stateBrk = 0; else ThrowException("Unexpected Failure"); } else { - if (PInvoke.EscapeCommFunction(_hPort, ESCAPE_COMM_FUNCTION.CLRBREAK)) + if (Win32Com.EscapeCommFunction(_hPort.DangerousGetHandle(), Win32Com.CLRBREAK)) _stateBrk = 0; else ThrowException("Unexpected Failure"); @@ -319,10 +758,10 @@ public sealed class SerialPort : IDisposable get => _stateBrk == 1; } + /// /// Port Name /// - [UsedImplicitly] public string PortName { get; set; } public Handshake Handshake @@ -379,427 +818,5 @@ 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(); - - _auto = false; - - 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; + #endregion } \ No newline at end of file diff --git a/Nefarius.Peripherals.SerialPort/Util.cs b/Nefarius.Peripherals.SerialPort/Util.cs index c0f22b0..72774af 100644 --- a/Nefarius.Peripherals.SerialPort/Util.cs +++ b/Nefarius.Peripherals.SerialPort/Util.cs @@ -4,7 +4,7 @@ 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, + public static void Init(this ref DCB dcb, bool parity, bool outCts, bool outDsr, int dtr, bool inDsr, bool txc, bool xOut, bool xIn, int rts) { diff --git a/Nefarius.Peripherals.SerialPort/Win32PInvoke/Win32Com.cs b/Nefarius.Peripherals.SerialPort/Win32PInvoke/Win32Com.cs new file mode 100644 index 0000000..a8ee677 --- /dev/null +++ b/Nefarius.Peripherals.SerialPort/Win32PInvoke/Win32Com.cs @@ -0,0 +1,91 @@ +using System; +using System.Runtime.InteropServices; + +namespace Nefarius.Peripherals.SerialPort.Win32PInvoke +{ + internal class Win32Com + { + + [DllImport("kernel32.dll")] + internal static extern Boolean GetHandleInformation(IntPtr hObject, out UInt32 lpdwFlags); + + + [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); + + //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; + + } +} \ No newline at end of file diff --git a/PInvokeSerialPort.Sample/PInvokeSerialPort.Sample.csproj b/PInvokeSerialPort.Sample/PInvokeSerialPort.Sample.csproj index c8e3b9a..5777734 100755 --- a/PInvokeSerialPort.Sample/PInvokeSerialPort.Sample.csproj +++ b/PInvokeSerialPort.Sample/PInvokeSerialPort.Sample.csproj @@ -8,8 +8,11 @@ PInvokeSerialPort.Sample Copyright © 2012 bin\$(Configuration)\ + latest + + \ No newline at end of file diff --git a/PInvokeSerialPort.Sample/Program.cs b/PInvokeSerialPort.Sample/Program.cs index 415f305..0d3358f 100755 --- a/PInvokeSerialPort.Sample/Program.cs +++ b/PInvokeSerialPort.Sample/Program.cs @@ -7,9 +7,12 @@ internal class Program { private static void Main(string[] args) { - var serialPort = new SerialPort("COM7") { UseRts = HsOutput.Online }; + var serialPort = new SerialPort("com7") { UseRts = HsOutput.Online }; - serialPort.DataReceived += x => Console.Write((char)x); + serialPort.DataReceived += x => + { + Console.Write($"{x:X2} "); + }; serialPort.Open(); diff --git a/PInvokeSerialPort.Sample/Properties/AssemblyInfo.cs b/PInvokeSerialPort.Sample/Properties/AssemblyInfo.cs new file mode 100755 index 0000000..5dcf965 --- /dev/null +++ b/PInvokeSerialPort.Sample/Properties/AssemblyInfo.cs @@ -0,0 +1,11 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// 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")] diff --git a/PInvokeSerialPort.Test/PInvokeSerialPortTest.cs b/PInvokeSerialPort.Test/PInvokeSerialPortTest.cs index 4a6a7da..cddff64 100755 --- a/PInvokeSerialPort.Test/PInvokeSerialPortTest.cs +++ b/PInvokeSerialPort.Test/PInvokeSerialPortTest.cs @@ -1,21 +1,21 @@ using System; using System.Text; -using System.Threading; using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Threading; 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 { - private dynamic _reciever; - private dynamic _sender; - private StringBuilder _stringBuilder; + dynamic _sender; + dynamic _reciever; + 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,10 +65,9 @@ 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] @@ -76,12 +75,11 @@ 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 +}