#region Using using System; using System.IO; using System.Threading; using System.Runtime.InteropServices; using System.ComponentModel; #endregion Using namespace LoMaN.IO { public class SerialStream : Stream { #region Attributes private IOCompletionCallback m_IOCompletionCallback; private IntPtr m_hFile = IntPtr.Zero; private string m_sPort; private bool m_bRead; private bool m_bWrite; #endregion Attributes #region Properties public string Port { get { return m_sPort; } set { if (m_sPort != value) { Close(); Open(value); } } } public override bool CanRead { get { return m_bRead; } } public override bool CanWrite { get { return m_bWrite; } } public override bool CanSeek { get { return false; } } public bool Closed { get { return m_hFile.ToInt32() 0; } } public bool Dsr { get { uint status; if (!GetCommModemStatus(m_hFile, out status)) { throw new Win32Exception(); } return (status & MS_DSR_ON) > 0; } } public bool Ring { get { uint status; if (!GetCommModemStatus(m_hFile, out status)) { throw new Win32Exception(); } return (status & MS_RING_ON) > 0; } } public bool Rlsd { get { uint status; if (!GetCommModemStatus(m_hFile, out status)) { throw new Win32Exception(); } return (status & MS_RLSD_ON) > 0; } } #endregion Properties #region Constructors public SerialStream() : this(FileAccess.ReadWrite) { } public SerialStream(FileAccess access) { m_bRead = ((int)access & (int)FileAccess.Read) != 0; m_bWrite = ((int)access & (int)FileAccess.Write) != 0; unsafe { m_IOCompletionCallback = new IOCompletionCallback(AsyncFSCallback); } } public SerialStream(string port) : this(FileAccess.ReadWrite) { Open(port); } public SerialStream(string port, FileAccess access) : this(access) { Open(port); } #endregion Constructors #region Methods public void Open(string port) { if (m_hFile != IntPtr.Zero) { throw new IOException("Stream already opened."); } m_sPort = port; m_hFile = CreateFile(port, (uint)((m_bRead?GENERIC_READ:0)|(m_bWrite?GENERIC_WRITE:0)), 0, 0, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0); if (m_hFile.ToInt32() == INVALID_HANDLE_VALUE) { m_hFile = IntPtr.Zero; throw new FileNotFoundException("Unable to open " + port); } ThreadPool.BindHandle(m_hFile); SetTimeouts(0, 0, 0, 0, 0); } public override void Close() { CloseHandle(m_hFile); m_hFile = IntPtr.Zero; m_sPort = null; } public IAsyncResult BeginRead(byte[] buffer) { return BeginRead(buffer, 0, buffer.Length, null, null); } public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) { GCHandle gchBuffer = GCHandle.Alloc(buffer, GCHandleType.Pinned); SerialAsyncResult sar = new SerialAsyncResult(this, state, callback, true, gchBuffer); Overlapped ov = new Overlapped(0, 0, sar.AsyncWaitHandle.Handle.ToInt32(), sar); unsafe { NativeOverlapped* nov = ov.Pack(m_IOCompletionCallback); byte* data = (byte*)((int)gchBuffer.AddrOfPinnedObject() + offset); uint read = 0; if (ReadFile(m_hFile, data, (uint)count, out read, nov)) { sar.m_bCompletedSynchronously = true; return sar; } else if (GetLastError() == ERROR_IO_PENDING) { return sar; } else throw new Exception("Unable to initialize read. Errorcode: " + GetLastError().ToString()); } } public IAsyncResult BeginWrite(byte[] buffer) { return BeginWrite(buffer, 0, buffer.Length, null, null); } public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) { GCHandle gchBuffer = GCHandle.Alloc(buffer, GCHandleType.Pinned); SerialAsyncResult sar = new SerialAsyncResult(this, state, callback, false, gchBuffer); Overlapped ov = new Overlapped(0, 0, sar.AsyncWaitHandle.Handle.ToInt32(), sar); unsafe { NativeOverlapped* nov = ov.Pack(m_IOCompletionCallback); byte* data = (byte*)((int)gchBuffer.AddrOfPinnedObject() + offset); uint written = 0; if (WriteFile(m_hFile, data, (uint)count, out written, nov)) { sar.m_bCompletedSynchronously = true; return sar; } else if (GetLastError() == ERROR_IO_PENDING) { return sar; } else throw new Exception("Unable to initialize write. Errorcode: " + GetLastError().ToString()); } } private int EndOperation(IAsyncResult asyncResult, bool isRead) { SerialAsyncResult sar = (SerialAsyncResult)asyncResult; if (sar.m_bIsRead != isRead) throw new IOException("Invalid parameter: IAsyncResult is not from a " + (isRead ? "read" : "write")); if (sar.EndOperationCalled) { throw new IOException("End" + (isRead ? "Read" : "Write") + " called twice for the same operation."); } else { sar.m_bEndOperationCalled = true; } while (!sar.m_bCompleted) { sar.AsyncWaitHandle.WaitOne(); } sar.Dispose(); if (sar.m_nErrorCode != ERROR_SUCCESS && sar.m_nErrorCode != ERROR_OPERATION_ABORTED) { throw new IOException("Operation finished with errorcode: " + sar.m_nErrorCode); } return sar.m_nReadWritten; } public override int EndRead(IAsyncResult asyncResult) { return EndOperation(asyncResult, true); } public override void EndWrite(IAsyncResult asyncResult) { EndOperation(asyncResult, false); } public int EndWriteEx(IAsyncResult asyncResult) { return EndOperation(asyncResult, false); } public override int Read(byte[] buffer, int offset, int count) { return EndRead(BeginRead(buffer, offset, count, null, null)); } public override void Write(byte[] buffer, int offset, int count) { EndWrite(BeginWrite(buffer, offset, count, null, null)); } public int WriteEx(byte[] buffer, int offset, int count) { return EndWriteEx(BeginWrite(buffer, offset, count, null, null)); } public int Read(byte[] buffer) { return EndRead(BeginRead(buffer, 0, buffer.Length, null, null)); } public int Write(byte[] buffer) { return EndOperation(BeginWrite(buffer, 0, buffer.Length, null, null), false); } public override void Flush() { FlushFileBuffers(m_hFile); } public bool PurgeRead() { return PurgeComm(m_hFile, PURGE_RXCLEAR); } public bool PurgeWrite() { return PurgeComm(m_hFile, PURGE_TXCLEAR); } public bool Purge() { return PurgeRead() && PurgeWrite(); } public bool CancelRead() { return PurgeComm(m_hFile, PURGE_RXABORT); } public bool CancelWrite() { return PurgeComm(m_hFile, PURGE_TXABORT); } public bool CancelAll() { return CancelRead() && CancelWrite(); } public override void SetLength(long nLength) { throw new NotSupportedException("SetLength isn't supported on serial ports."); } public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException("Seek isn't supported on serial ports."); } public void SetTimeouts(int ReadIntervalTimeout, int ReadTotalTimeoutMultiplier, int ReadTotalTimeoutConstant, int WriteTotalTimeoutMultiplier, int WriteTotalTimeoutConstant) { SerialTimeouts Timeouts = new SerialTimeouts(ReadIntervalTimeout, ReadTotalTimeoutMultiplier, ReadTotalTimeoutConstant, WriteTotalTimeoutMultiplier, WriteTotalTimeoutConstant); unsafe { SetCommTimeouts(m_hFile, ref Timeouts); } } public bool SetPortSettings(uint baudrate) { return SetPortSettings(baudrate, FlowControl.Hardware); } public bool SetPortSettings(uint baudrate, FlowControl flowControl) { return SetPortSettings(baudrate, flowControl, Parity.None); } public bool SetPortSettings(uint baudrate, FlowControl flowControl, Parity parity) { return SetPortSettings(baudrate, flowControl, parity, 8, StopBits.One); } public bool SetPortSettings(uint baudrate, FlowControl flowControl, Parity parity, byte databits, StopBits stopbits) { unsafe { DCB dcb = new DCB(); dcb.DCBlength = sizeof(DCB); dcb.BaudRate = baudrate; dcb.ByteSize = databits; dcb.StopBits = (byte)stopbits; dcb.Parity = (byte)parity; dcb.fParity = (parity > 0)? 1U : 0U; dcb.fBinary = dcb.fDtrControl = dcb.fTXContinueOnXoff = 1; dcb.fOutxCtsFlow = dcb.fAbortOnError = (flowControl == FlowControl.Hardware)? 1U : 0U; dcb.fOutX = dcb.fInX = (flowControl == FlowControl.XOnXOff)? 1U : 0U; dcb.fRtsControl = (flowControl == FlowControl.Hardware)? 2U : 1U; dcb.XonLim = 2048; dcb.XoffLim = 512; dcb.XonChar = 0x11; // Ctrl-Q dcb.XoffChar = 0x13; // Ctrl-S return SetCommState(m_hFile, ref dcb); } } public bool SetPortSettings(DCB dcb) { return SetCommState(m_hFile, ref dcb); } public bool GetPortSettings(out DCB dcb) { unsafe { DCB dcb2 = new DCB(); dcb2.DCBlength = sizeof(DCB); bool ret = GetCommState(m_hFile, ref dcb2); dcb = dcb2; return ret; } } public bool SetXOn() { return EscapeCommFunction(m_hFile, SETXON); } public bool SetXOff() { return EscapeCommFunction(m_hFile, SETXOFF); } private unsafe void AsyncFSCallback(uint errorCode, uint numBytes, NativeOverlapped* pOverlapped) { SerialAsyncResult sar = (SerialAsyncResult)Overlapped.Unpack(pOverlapped).AsyncResult; sar.m_nErrorCode = errorCode; sar.m_nReadWritten = (int)numBytes; sar.m_bCompleted = true; if (sar.Callback != null) sar.Callback.Invoke(sar); Overlapped.Free(pOverlapped); } #endregion Methods #region Constants private const uint PURGE_TXABORT = 0x0001; // Kill the pending/current writes to the comm port. private const uint PURGE_RXABORT = 0x0002; // Kill the pending/current reads to the comm port. private const uint PURGE_TXCLEAR = 0x0004; // Kill the transmit queue if there. private const uint PURGE_RXCLEAR = 0x0008; // Kill the typeahead buffer if there. private const uint SETXOFF = 1; // Simulate XOFF received private const uint SETXON = 2; // Simulate XON received private const uint SETRTS = 3; // Set RTS high private const uint CLRRTS = 4; // Set RTS low private const uint SETDTR = 5; // Set DTR high private const uint CLRDTR = 6; // Set DTR low private const uint SETBREAK = 8; // Set the device break line. private const uint CLRBREAK = 9; // Clear the device break line. private const uint MS_CTS_ON = 0x0010; private const uint MS_DSR_ON = 0x0020; private const uint MS_RING_ON = 0x0040; private const uint MS_RLSD_ON = 0x0080; private const uint FILE_FLAG_OVERLAPPED = 0x40000000; private const uint OPEN_EXISTING = 3; private const int INVALID_HANDLE_VALUE = -1; private const uint GENERIC_READ = 0x80000000; private const uint GENERIC_WRITE = 0x40000000; private const uint ERROR_SUCCESS = 0; private const uint ERROR_OPERATION_ABORTED = 995; private const uint ERROR_IO_PENDING = 997; #endregion Constants #region Enums public enum Parity {None, Odd, Even, Mark, Space}; public enum StopBits {One, OneAndHalf, Two}; public enum FlowControl {None, XOnXOff, Hardware}; #endregion Enums #region Classes [StructLayout(LayoutKind.Sequential)] public struct DCB { #region Attributes public int DCBlength; public uint BaudRate; public uint Flags; public ushort wReserved; public ushort XonLim; public ushort XoffLim; public byte ByteSize; public byte Parity; public byte StopBits; public sbyte XonChar; public sbyte XoffChar; public sbyte ErrorChar; public sbyte EofChar; public sbyte EvtChar; public ushort wReserved1; #endregion Attributes #region Properties public uint fBinary { get { return Flags&0x0001; } set { Flags = Flags & ~1U | value; } } public uint fParity { get { return (Flags>>1)&1; } set { Flags = Flags & ~(1U >2)&1; } set { Flags = Flags & ~(1U >3)&1; } set { Flags = Flags & ~(1U >4)&3; } set { Flags = Flags & ~(3U >6)&1; } set { Flags = Flags & ~(1U >7)&1; } set { Flags = Flags & ~(1U >8)&1; } set { Flags = Flags & ~(1U >9)&1; } set { Flags = Flags & ~(1U >10)&1; } set { Flags = Flags & ~(1U >11)&1; } set { Flags = Flags & ~(1U >12)&3; } set { Flags = Flags & ~(3U >14)&1; } set { Flags = Flags & ~(1U << 14) | (value << 14); } } #endregion Properties #region Methods public override string ToString() { return "DCBlength: " + DCBlength + "\r\n" + "BaudRate: " + BaudRate + "\r\n" + "fBinary: " + fBinary + "\r\n" + "fParity: " + fParity + "\r\n" + "fOutxCtsFlow: " + fOutxCtsFlow + "\r\n" + "fOutxDsrFlow: " + fOutxDsrFlow + "\r\n" + "fDtrControl: " + fDtrControl + "\r\n" + "fDsrSensitivity: " + fDsrSensitivity + "\r\n" + "fTXContinueOnXoff: " + fTXContinueOnXoff + "\r\n" + "fOutX: " + fOutX + "\r\n" + "fInX: " + fInX + "\r\n" + "fErrorChar: " + fErrorChar + "\r\n" + "fNull: " + fNull + "\r\n" + "fRtsControl: " + fRtsControl + "\r\n" + "fAbortOnError: " + fAbortOnError + "\r\n" + "XonLim: " + XonLim + "\r\n" + "XoffLim: " + XoffLim + "\r\n" + "ByteSize: " + ByteSize + "\r\n" + "Parity: " + Parity + "\r\n" + "StopBits: " + StopBits + "\r\n" + "XonChar: " + XonChar + "\r\n" + "XoffChar: " + XoffChar + "\r\n" + "EofChar: " + EofChar + "\r\n" + "EvtChar: " + EvtChar + "\r\n"; } #endregion Methods } private class SerialAsyncResult : IAsyncResult, IDisposable { #region Attributes internal bool m_bEndOperationCalled = false; internal bool m_bIsRead; internal int m_nReadWritten = 0; internal bool m_bCompleted = false; internal bool m_bCompletedSynchronously = false; internal uint m_nErrorCode = ERROR_SUCCESS; private object m_AsyncObject; private object m_StateObject; private ManualResetEvent m_WaitHandle = new ManualResetEvent(false); private AsyncCallback m_Callback; private GCHandle m_gchBuffer; #endregion Attributes #region Properties internal bool EndOperationCalled { get { return m_bEndOperationCalled; } } public bool IsCompleted { get { return m_bCompleted; } } public bool CompletedSynchronously { get { return m_bCompletedSynchronously; } } public object AsyncObject { get { return m_AsyncObject; } } public object AsyncState { get { return m_StateObject; } } public WaitHandle AsyncWaitHandle { get { return m_WaitHandle; } } internal ManualResetEvent WaitHandle { get { return m_WaitHandle; } } public AsyncCallback Callback { get { return m_Callback; } } #endregion Properties #region Constructors public SerialAsyncResult(object asyncObject, object stateObject, AsyncCallback callback, bool bIsRead, GCHandle gchBuffer) { m_AsyncObject = asyncObject; m_StateObject = stateObject; m_Callback = callback; m_bIsRead = bIsRead; m_gchBuffer = gchBuffer; } #endregion Constructors #region Methods public void Dispose() { m_WaitHandle.Close(); m_gchBuffer.Free(); } #endregion Methods } #endregion Classes #region Imports [DllImport("kernel32.dll", EntryPoint="CreateFileW", SetLastError=true, CharSet=CharSet.Unicode, ExactSpelling=true)] static extern IntPtr CreateFile(string filename, uint access, uint sharemode, uint security_attributes, uint creation, uint flags, uint template); [DllImport("kernel32.dll", SetLastError=true)] static extern bool CloseHandle(IntPtr handle); [DllImport("kernel32.dll", SetLastError=true)] static extern unsafe bool ReadFile(IntPtr hFile, byte* lpBuffer, uint nNumberOfBytesToRead, out uint lpNumberOfBytesRead, NativeOverlapped* lpOverlapped); [DllImport("kernel32.dll", SetLastError=true)] static extern unsafe bool WriteFile(IntPtr hFile, byte* lpBuffer, uint nNumberOfBytesToWrite, out uint lpNumberOfBytesWritten, NativeOverlapped* lpOverlapped); [DllImport("kernel32.dll", SetLastError=true)] static extern bool SetCommTimeouts(IntPtr hFile, ref SerialTimeouts lpCommTimeouts); [DllImport("kernel32.dll", SetLastError=true)] static extern bool SetCommState(IntPtr hFile, ref DCB lpDCB); [DllImport("kernel32.dll", SetLastError=true)] static extern bool GetCommState(IntPtr hFile, ref DCB lpDCB); [DllImport("kernel32.dll", SetLastError=true)] static extern bool BuildCommDCB(string def, ref DCB lpDCB); [DllImport("kernel32.dll", SetLastError=true)] static extern int GetLastError(); [DllImport("kernel32.dll", SetLastError=true)] static extern bool FlushFileBuffers(IntPtr hFile); [DllImport("kernel32.dll", SetLastError=true)] static extern bool PurgeComm(IntPtr hFile, uint dwFlags); [DllImport("kernel32.dll", SetLastError=true)] static extern bool EscapeCommFunction(IntPtr hFile, uint dwFunc); [DllImport("kernel32.dll", SetLastError=true)] static extern bool GetCommModemStatus(IntPtr hFile, out uint modemStat); #endregion Imports } [StructLayout(LayoutKind.Sequential)] public struct SerialTimeouts { #region Attributes public int ReadIntervalTimeout; public int ReadTotalTimeoutMultiplier; public int ReadTotalTimeoutConstant; public int WriteTotalTimeoutMultiplier; public int WriteTotalTimeoutConstant; #endregion Attributes #region Constructors public SerialTimeouts(int r1, int r2, int r3, int w1, int w2) { ReadIntervalTimeout = r1; ReadTotalTimeoutMultiplier = r2; ReadTotalTimeoutConstant = r3; WriteTotalTimeoutMultiplier = w1; WriteTotalTimeoutConstant = w2; } #endregion Constructors #region Methods public override string ToString() { return "ReadIntervalTimeout: " + ReadIntervalTimeout + "\r\n" + "ReadTotalTimeoutMultiplier: " + ReadTotalTimeoutMultiplier + "\r\n" + "ReadTotalTimeoutConstant: " + ReadTotalTimeoutConstant + "\r\n" + "WriteTotalTimeoutMultiplier: " + WriteTotalTimeoutMultiplier + "\r\n" + "WriteTotalTimeoutConstant: " + WriteTotalTimeoutConstant + "\r\n"; } #endregion Methods } } using System; using System.IO; using System.Threading; using LoMaN.IO; namespace SerialStreamReader { class App { // The main serial stream static SerialStream ss; [STAThread] static void Main(string[] args) { // Create a serial port ss = new SerialStream(); try { ss.Open("COM3"); } catch (Exception e) { Console.WriteLine("Error: " + e.Message); return; } // Set port settings ss.SetPortSettings(9600); // Set timeout so read ends after 20ms of silence after a response ss.SetTimeouts(20, 0, 0, 0, 0); // Create the StreamWriter used to send commands StreamWriter sw = new StreamWriter(ss, System.Text.Encoding.ASCII); // Create the Thread used to read responses Thread responseReaderThread = new Thread(new ThreadStart(ReadResponseThread)); responseReaderThread.Start(); // Read all returned lines for (;;) { // Read command from console string command = Console.ReadLine(); // Check for exit command if (command.Trim().ToLower() == "exit") { responseReaderThread.Abort(); break; } // Write command to modem sw.WriteLine(command); sw.Flush(); } } // Main loop for reading responses static void ReadResponseThread() { StreamReader sr = new StreamReader(ss, System.Text.Encoding.ASCII); try { for (;;) { // Read response from modem string response = sr.ReadLine(); Console.WriteLine("Response: " + response); } } catch (ThreadAbortException) { } } } }