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
+}