using System; using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Collections.Concurrent; //JH 1.1: Version 1.1 changes labelled thus. //JH 1.2: Version 1.2 changes labelled thus. //JH 1.3: Version 1.3 changes labelled thus. namespace JHCommBase { /// /// Lowest level Com driver handling all Win32 API calls and processing send and receive in terms of /// individual bytes. Used as a base class for higher level drivers. /// public class CommBase : IDisposable { private IntPtr hPort; private IntPtr ptrUWO = IntPtr.Zero; private Thread rxThread = null; private bool online = false; private bool auto = false; private bool checkSends = true; private Exception rxException = null; private bool rxExceptionReported = false; private int writeCount = 0; private ManualResetEvent writeEvent = new ManualResetEvent(false); //JH 1.2: Added below to improve robustness of thread start-up. private ManualResetEvent startEvent = new ManualResetEvent(false); private int stateRTS = 2; private int stateDTR = 2; private int stateBRK = 2; //JH 1.3: Added to support the new congestion detection scheme (following two lines): private bool[] empty = new bool[1]; private bool dataQueued = false; private ConcurrentQueue Que = null; public delegate void OnSend(); public static OnSend onTx; public delegate void OnRecv(byte ch); public static OnRecv onRx; public delegate void OnRecvPack(Byte[] arr); public static OnRecvPack onRxPack; public CommBase(String port, int bitrate, Parity parity = Parity.none, StopBits stopbits = StopBits.one, int databits = 8) { CommSet.port = port; CommSet.baudRate = bitrate; CommSet.parity = parity; CommSet.dataBits = databits; CommSet.stopBits = stopbits; CommSet.autoReopen = true; } /// /// Parity settings /// public enum Parity { /// /// Characters do not have a parity bit. /// none = 0, /// /// If there are an odd number of 1s in the data bits, the parity bit is 1. /// odd = 1, /// /// If there are an even number of 1s in the data bits, the parity bit is 1. /// even = 2, /// /// The parity bit is always 1. /// mark = 3, /// /// The parity bit is always 0. /// space = 4 }; /// /// Stop bit settings /// public enum StopBits { /// /// Line is asserted for 1 bit duration at end of each character /// one = 0, /// /// Line is asserted for 1.5 bit duration at end of each character /// onePointFive = 1, /// /// Line is asserted for 2 bit duration at end of each character /// two = 2 }; /// /// Uses for RTS or DTR pins /// public enum HSOutput { /// /// Pin is asserted when this station is able to receive data. /// handshake = 2, /// /// Pin is asserted when this station is transmitting data (RTS on NT, 2000 or XP only). /// gate = 3, /// /// Pin is asserted when this station is online (port is open). /// online = 1, /// /// Pin is never asserted. /// none = 0 }; /// /// Standard handshake methods /// public enum Handshake { /// /// No handshaking /// none, /// /// Software handshaking using Xon / Xoff /// XonXoff, /// /// Hardware handshaking using CTS / RTS /// CtsRts, /// /// Hardware handshaking using DSR / DTR /// DsrDtr } /// /// Set the public fields to supply settings to CommBase. /// private static class CommSet { /// /// Port Name (default: "COM1:") /// public static string port = "COM1:"; /// /// Baud Rate (default: 2400) unsupported rates will throw "Bad settings" /// public static int baudRate = 2400; /// /// The parity checking scheme (default: none) /// public static Parity parity = Parity.none; /// /// Number of databits 1..8 (default: 8) unsupported values will throw "Bad settings" /// public static int dataBits = 8; /// /// Number of stop bits (default: one) /// public static StopBits stopBits = StopBits.one; /// /// If true, transmission is halted unless CTS is asserted by the remote station (default: false) /// public static bool txFlowCTS = false; /// /// If true, transmission is halted unless DSR is asserted by the remote station (default: false) /// public static bool txFlowDSR = false; /// /// If true, transmission is halted when Xoff is received and restarted when Xon is received (default: false) /// public static bool txFlowX = false; /// /// If false, transmission is suspended when this station has sent Xoff to the remote station (default: true) /// Set false if the remote station treats any character as an Xon. /// public static bool txWhenRxXoff = true; /// /// If true, received characters are ignored unless DSR is asserted by the remote station (default: false) /// public static bool rxGateDSR = false; /// /// If true, Xon and Xoff characters are sent to control the data flow from the remote station (default: false) /// public static bool rxFlowX = false; /// /// Specifies the use to which the RTS output is put (default: none) /// public static HSOutput useRTS = HSOutput.none; /// /// Specidies the use to which the DTR output is put (default: none) /// public static HSOutput useDTR = HSOutput.none; /// /// The character used to signal Xon for X flow control (default: DC1) /// public static ASCII XonChar = ASCII.DC1; /// /// The character used to signal Xoff for X flow control (default: DC3) /// public static ASCII XoffChar = ASCII.DC3; //JH 1.2: Next two defaults changed to 0 to use new defaulting mechanism dependant on queue size. /// /// The number of free bytes in the reception queue at which flow is disabled /// (Default: 0 = Set to 1/10th of actual rxQueue size) /// public static int rxHighWater = 0; /// /// The number of bytes in the reception queue at which flow is re-enabled /// (Default: 0 = Set to 1/10th of actual rxQueue size) /// public static int rxLowWater = 0; /// /// Multiplier. Max time for Send in ms = (Multiplier * Characters) + Constant /// (default: 0 = No timeout) /// public static uint sendTimeoutMultiplier = 0; /// /// Constant. Max time for Send in ms = (Multiplier * Characters) + Constant (default: 0) /// public static uint sendTimeoutConstant = 0; /// /// Requested size for receive queue (default: 0 = use operating system default) /// public static int rxQueue = 0; /// /// Requested size for transmit queue (default: 0 = use operating system default) /// public static int txQueue = 0; /// /// If true, the port will automatically re-open on next send if it was previously closed due /// to an error (default: false) /// public static bool autoReopen = false; /// /// If true, subsequent Send commands wait for completion of earlier ones enabling the results /// to be checked. If false, errors, including timeouts, may not be detected, but performance /// may be better. /// public static bool checkAllSends = true; /// /// Pre-configures settings for most modern devices: 8 databits, 1 stop bit, no parity and /// one of the common handshake protocols. Change individual settings later if necessary. /// /// The port to use (i.e. "COM1:") /// The baud rate /// The handshake protocol public static void SetStandard(string Port, int Baud, Handshake Hs) { dataBits = 8; stopBits = StopBits.one; parity = Parity.none; port = Port; baudRate = Baud; switch (Hs) { case Handshake.none: txFlowCTS = false; txFlowDSR = false; txFlowX = false; rxFlowX = false; useRTS = HSOutput.online; useDTR = HSOutput.online; txWhenRxXoff = true; rxGateDSR = false; break; case Handshake.XonXoff: txFlowCTS = false; txFlowDSR = false; txFlowX = true; rxFlowX = true; useRTS = HSOutput.online; useDTR = HSOutput.online; txWhenRxXoff = true; rxGateDSR = false; XonChar = ASCII.DC1; XoffChar = ASCII.DC3; break; case Handshake.CtsRts: txFlowCTS = true; txFlowDSR = false; txFlowX = false; rxFlowX = false; useRTS = HSOutput.handshake; useDTR = HSOutput.online; txWhenRxXoff = true; rxGateDSR = false; break; case Handshake.DsrDtr: txFlowCTS = false; txFlowDSR = true; txFlowX = false; rxFlowX = false; useRTS = HSOutput.online; useDTR = HSOutput.handshake; txWhenRxXoff = true; rxGateDSR = false; break; } } } //JH 1.3: Corrected STH -> STX (Thanks - Johan Thelin!) /// /// Byte type with enumeration constants for ASCII control codes. /// public enum ASCII : byte { NULL = 0x00,SOH = 0x01,STX = 0x02,ETX = 0x03,EOT = 0x04,ENQ = 0x05,ACK = 0x06,BELL = 0x07, BS = 0x08,HT = 0x09,LF = 0x0A,VT = 0x0B,FF = 0x0C,CR = 0x0D,SO = 0x0E,SI = 0x0F,DC1 = 0x11, DC2 = 0x12,DC3 = 0x13,DC4 = 0x14,NAK = 0x15,SYN = 0x16,ETB = 0x17,CAN = 0x18,EM = 0x19, SUB = 0x1A,ESC = 0x1B,FS = 0x1C,GS = 0x1D,RS = 0x1E,US = 0x1F,SP = 0x20,DEL = 0x7F } //JH 1.3: Added AltName function, PortStatus enum and IsPortAvailable function. /// /// Returns the alternative name of a com port i.e. \\.\COM1 for COM1: /// Some systems require this form for double or more digit com port numbers. /// /// Name in form COM1 or COM1: /// Name in form \\.\COM1 private string AltName(string s) { string r = s.Trim(); if (s.EndsWith(":")) s = s.Substring(0, s.Length - 1); if (s.StartsWith(@"\")) return s; return @"\\.\" + s; } /// /// Availability status of a port /// public enum PortStatus { /// /// Port exists but is unavailable (may be open to another program) /// unavailable = 0, /// /// Available for use /// available = 1, /// /// Port does not exist /// absent = -1 } /// /// Tests the availability of a named comm port. /// /// Name of port /// Availability of port public PortStatus IsPortAvailable(string s) { IntPtr h; h = Win32Com.CreateFile(s, Win32Com.GENERIC_READ | Win32Com.GENERIC_WRITE, 0, IntPtr.Zero, Win32Com.OPEN_EXISTING, Win32Com.FILE_FLAG_OVERLAPPED, IntPtr.Zero); if (h == (IntPtr)Win32Com.INVALID_HANDLE_VALUE) { if (Marshal.GetLastWin32Error() == Win32Com.ERROR_ACCESS_DENIED) { return PortStatus.unavailable; } else { //JH 1.3: Automatically try AltName if supplied name fails: h = Win32Com.CreateFile(AltName(s), Win32Com.GENERIC_READ | Win32Com.GENERIC_WRITE, 0, IntPtr.Zero, Win32Com.OPEN_EXISTING, Win32Com.FILE_FLAG_OVERLAPPED, IntPtr.Zero); if (h == (IntPtr)Win32Com.INVALID_HANDLE_VALUE) { if (Marshal.GetLastWin32Error() == Win32Com.ERROR_ACCESS_DENIED) { return PortStatus.unavailable; } else { return PortStatus.absent; } } } } Win32Com.CloseHandle(h); return PortStatus.available; } /// /// Opens the com port and configures it with the required settings /// /// false if the port could not be opened public bool Open() { Win32Com.DCB PortDCB = new Win32Com.DCB(); Win32Com.COMMTIMEOUTS CommTimeouts = new Win32Com.COMMTIMEOUTS(); Win32Com.OVERLAPPED wo = new Win32Com.OVERLAPPED(); Win32Com.COMMPROP cp; Que = new ConcurrentQueue(); if (online) return false; hPort = Win32Com.CreateFile(CommSet.port, Win32Com.GENERIC_READ | Win32Com.GENERIC_WRITE, 0, IntPtr.Zero, Win32Com.OPEN_EXISTING, Win32Com.FILE_FLAG_OVERLAPPED, IntPtr.Zero); if (hPort == (IntPtr)Win32Com.INVALID_HANDLE_VALUE) { if (Marshal.GetLastWin32Error() == Win32Com.ERROR_ACCESS_DENIED) { return false; } else { //JH 1.3: Try alternative name form if main one fails: hPort = Win32Com.CreateFile(AltName(CommSet.port), Win32Com.GENERIC_READ | Win32Com.GENERIC_WRITE, 0, IntPtr.Zero, Win32Com.OPEN_EXISTING, Win32Com.FILE_FLAG_OVERLAPPED, IntPtr.Zero); if (hPort == (IntPtr)Win32Com.INVALID_HANDLE_VALUE) { if (Marshal.GetLastWin32Error() == Win32Com.ERROR_ACCESS_DENIED) { return false; } else { throw new CommPortException("Port Open Failure"); } } } } online = true; //JH1.1: Changed from 0 to "magic number" to give instant return on ReadFile: CommTimeouts.ReadIntervalTimeout = Win32Com.MAXDWORD; CommTimeouts.ReadTotalTimeoutConstant = 0; CommTimeouts.ReadTotalTimeoutMultiplier = 0; //JH1.2: 0 does not seem to mean infinite on non-NT platforms, so default it to 10 //seconds per byte which should be enough for anyone. if (CommSet.sendTimeoutMultiplier == 0) { if (System.Environment.OSVersion.Platform == System.PlatformID.Win32NT) { CommTimeouts.WriteTotalTimeoutMultiplier = 0; } else { CommTimeouts.WriteTotalTimeoutMultiplier = 10000; } } else { CommTimeouts.WriteTotalTimeoutMultiplier = CommSet.sendTimeoutMultiplier; } CommTimeouts.WriteTotalTimeoutConstant = CommSet.sendTimeoutConstant; PortDCB.init(((CommSet.parity == Parity.odd) || (CommSet.parity == Parity.even)), CommSet.txFlowCTS, CommSet.txFlowDSR, (int)CommSet.useDTR, CommSet.rxGateDSR, !CommSet.txWhenRxXoff, CommSet.txFlowX, CommSet.rxFlowX, (int)CommSet.useRTS); PortDCB.BaudRate = CommSet.baudRate; PortDCB.ByteSize = (byte)CommSet.dataBits; PortDCB.Parity = (byte)CommSet.parity; PortDCB.StopBits = (byte)CommSet.stopBits; PortDCB.XoffChar = (byte)CommSet.XoffChar; PortDCB.XonChar = (byte)CommSet.XonChar; if ((CommSet.rxQueue != 0) || (CommSet.txQueue != 0)) if (!Win32Com.SetupComm(hPort, (uint)CommSet.rxQueue, (uint)CommSet.txQueue)) ThrowException("Bad queue settings"); //JH 1.2: Defaulting mechanism for handshake thresholds - prevents problems of setting specific //defaults which may violate the size of the actually granted queue. If the user specifically sets //these values, it's their problem! if ((CommSet.rxLowWater == 0) || (CommSet.rxHighWater == 0)) { if (!Win32Com.GetCommProperties(hPort, out cp)) cp.dwCurrentRxQueue = 0; if (cp.dwCurrentRxQueue > 0) { //If we can determine the queue size, default to 1/10th, 8/10ths, 1/10th. //Note that HighWater is measured from top of queue. PortDCB.XoffLim = PortDCB.XonLim = (short)((int)cp.dwCurrentRxQueue / 10); } else { //If we do not know the queue size, set very low defaults for safety. PortDCB.XoffLim = PortDCB.XonLim = 8; } } else { PortDCB.XoffLim = (short)CommSet.rxHighWater; PortDCB.XonLim = (short)CommSet.rxLowWater; } if (!Win32Com.SetCommState(hPort, ref PortDCB)) ThrowException("Bad com settings"); if (!Win32Com.SetCommTimeouts(hPort, ref CommTimeouts)) ThrowException("Bad timeout settings"); stateBRK = 0; if (CommSet.useDTR == HSOutput.none) stateDTR = 0; if (CommSet.useDTR == HSOutput.online) stateDTR = 1; if (CommSet.useRTS == HSOutput.none) stateRTS = 0; if (CommSet.useRTS == HSOutput.online) stateRTS = 1; checkSends = CommSet.checkAllSends; wo.Offset = 0; wo.OffsetHigh = 0; if (checkSends) wo.hEvent = writeEvent.Handle; else wo.hEvent = IntPtr.Zero; ptrUWO = Marshal.AllocHGlobal(Marshal.SizeOf(wo)); Marshal.StructureToPtr(wo, ptrUWO, true); writeCount = 0; //JH1.3: empty[0] = true; dataQueued = false; rxException = null; rxExceptionReported = false; rxThread = new Thread(new ThreadStart(this.ReceiveThread)); rxThread.Name = "CommBaseRx"; rxThread.Priority = ThreadPriority.AboveNormal; rxThread.Start(); //JH1.2: More robust thread start-up wait. startEvent.WaitOne(500, false); auto = false; if (AfterOpen()) { auto = CommSet.autoReopen; return true; } else { Close(); return false; } } /// /// Closes the com port. /// public void Close() { if (online) { auto = false; BeforeClose(false); InternalClose(); rxException = null; } } private void InternalClose() { Win32Com.CancelIo(hPort); if (rxThread != null) { rxThread.Abort(); //JH 1.3: Improve robustness of Close in case were followed by Open: rxThread.Join(100); rxThread = null; } Win32Com.CloseHandle(hPort); if (ptrUWO != IntPtr.Zero) Marshal.FreeHGlobal(ptrUWO); stateRTS = 2; stateDTR = 2; stateBRK = 2; online = false; } /// /// For IDisposable /// public void Dispose() {Close();} /// /// Destructor (just in case) /// ~CommBase() {Close();} /// /// True if online. /// public bool Online {get {if (!online) return false; else return CheckOnline();}} /// /// 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); } else { if (online) { BeforeClose(true); InternalClose(); } if (rxException == null) { throw new CommPortException(reason); } else { throw new CommPortException(rxException); } } } /// /// Queues bytes for transmission. /// /// Array of bytes to be sent public void Send(byte[] tosend) { uint sent = 0; CheckOnline(); CheckResult(); writeCount = tosend.GetLength(0); if (Win32Com.WriteFile(hPort, tosend, (uint)writeCount, out sent, ptrUWO)) { writeCount -= (int)sent; OnTxDone(); } else { if (Marshal.GetLastWin32Error() != Win32Com.ERROR_IO_PENDING) ThrowException("Send failed"); //JH1.3: dataQueued = true; } } /// /// Queues a single byte for transmission. /// /// Byte to be sent public void Send(byte tosend) { byte[] b = new byte[1]; b[0] = tosend; Send(b); } private void CheckResult() { uint sent = 0; //JH 1.3: Fixed a number of problems working with checkSends == false. Byte counting was unreliable because //occasionally GetOverlappedResult would return true with a completion having missed one or more previous //completions. The test for ERROR_IO_INCOMPLETE was incorrectly for ERROR_IO_PENDING instead. if (writeCount > 0) { if (Win32Com.GetOverlappedResult(hPort, ptrUWO, out sent, checkSends)) { if (checkSends) { writeCount -= (int)sent; if (writeCount != 0) ThrowException("Send Timeout"); writeCount = 0; } } else { if (Marshal.GetLastWin32Error() != Win32Com.ERROR_IO_INCOMPLETE) ThrowException("Write Error"); } } } /// /// Sends a protocol byte immediately ahead of any queued bytes. /// /// Byte to send public void SendImmediate(byte tosend) { CheckOnline(); if (!Win32Com.TransmitCommChar(hPort, tosend)) ThrowException("Transmission failure"); else OnTxDone(); } /// /// Delay processing. /// /// Milliseconds to delay by protected void Sleep(int milliseconds) { Thread.Sleep(milliseconds); } /// /// Represents the status of the modem control input signals. /// public struct ModemStatus { private uint status; internal ModemStatus(uint val) {status = val;} /// /// Condition of the Clear To Send signal. /// public bool cts {get{return ((status & Win32Com.MS_CTS_ON) != 0);}} /// /// Condition of the Data Set Ready signal. /// public bool dsr {get{return ((status & Win32Com.MS_DSR_ON) != 0);}} /// /// Condition of the Receive Line Status Detection signal. /// public bool rlsd {get{return ((status & Win32Com.MS_RLSD_ON) != 0);}} /// /// Condition of the Ring Detection signal. /// public bool ring {get{return ((status & Win32Com.MS_RING_ON) != 0);}} } /// /// Gets the status of the modem control input signals. /// /// Modem status object protected ModemStatus GetModemStatus() { uint f; CheckOnline(); if (!Win32Com.GetCommModemStatus(hPort, out f)) ThrowException("Unexpected failure"); return new ModemStatus(f); } /// /// Represents the current condition of the port queues. /// public struct QueueStatus { private uint status; private uint inQueue; private uint outQueue; private uint inQueueSize; private uint outQueueSize; internal QueueStatus(uint stat, uint inQ, uint outQ, uint inQs, uint outQs) {status = stat; inQueue = inQ; outQueue = outQ; inQueueSize = inQs; outQueueSize = outQs;} /// /// Output is blocked by CTS handshaking. /// public bool ctsHold {get{return ((status & Win32Com.COMSTAT.fCtsHold) != 0);}} /// /// Output is blocked by DRS handshaking. /// public bool dsrHold {get{return ((status & Win32Com.COMSTAT.fDsrHold) != 0);}} /// /// Output is blocked by RLSD handshaking. /// public bool rlsdHold {get{return ((status & Win32Com.COMSTAT.fRlsdHold) != 0);}} /// /// Output is blocked because software handshaking is enabled and XOFF was received. /// public bool xoffHold {get{return ((status & Win32Com.COMSTAT.fXoffHold) != 0);}} /// /// Output was blocked because XOFF was sent and this station is not yet ready to receive. /// public bool xoffSent {get{return ((status & Win32Com.COMSTAT.fXoffSent) != 0);}} /// /// There is a character waiting for transmission in the immediate buffer. /// public bool immediateWaiting {get{return ((status & Win32Com.COMSTAT.fTxim) != 0);}} /// /// Number of bytes waiting in the input queue. /// public long InQueue {get{return (long)inQueue;}} /// /// Number of bytes waiting for transmission. /// public long OutQueue {get{return (long)outQueue;}} /// /// Total size of input queue (0 means information unavailable) /// public long InQueueSize {get{return (long)inQueueSize;}} /// /// Total size of output queue (0 means information unavailable) /// public long OutQueueSize {get{return (long)outQueueSize;}} public override string ToString() { StringBuilder m = new StringBuilder("The reception queue is ", 60); if (inQueueSize == 0) { m.Append("of unknown size and "); } else { m.Append(inQueueSize.ToString() + " bytes long and "); } if (inQueue == 0) { m.Append("is empty."); } else if (inQueue == 1) { m.Append("contains 1 byte."); } else { m.Append("contains "); m.Append(inQueue.ToString()); m.Append(" bytes."); } m.Append(" The transmission queue is "); if (outQueueSize == 0) { m.Append("of unknown size and "); } else { m.Append(outQueueSize.ToString() + " 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(); } } /// /// Get the status of the queues /// /// Queue status object protected QueueStatus GetQueueStatus() { Win32Com.COMSTAT cs; Win32Com.COMMPROP cp; uint er; CheckOnline(); if (!Win32Com.ClearCommError(hPort, out er, out cs)) ThrowException("Unexpected failure"); if (!Win32Com.GetCommProperties(hPort, out cp)) ThrowException("Unexpected failure"); return new QueueStatus(cs.Flags, cs.cbInQue, cs.cbOutQue, cp.dwCurrentRxQueue, cp.dwCurrentTxQueue); } // JH 1.3. Added for this version. /// /// Test if the line is congested (data being queued for send faster than it is being dequeued) /// This detects if baud rate is too slow or if handshaking is not allowing enough transmission /// time. It should be called at reasonably long fixed intervals. If data has been sent during /// the interval, congestion is reported if the queue was never empty during the interval. /// /// True if congested protected bool IsCongested() { bool e; if (!dataQueued) return false; lock(empty) {e = empty[0]; empty[0] = false;} dataQueued = false; return !e; } /// /// True if the RTS pin is controllable via the RTS property /// protected bool RTSavailable { get { return (stateRTS < 2);}} /// /// Set the state of the RTS modem control output /// protected bool RTS { set { if (stateRTS > 1) return; CheckOnline(); if (value) { if (Win32Com.EscapeCommFunction(hPort, Win32Com.SETRTS)) stateRTS = 1; else ThrowException("Unexpected Failure"); } else { if (Win32Com.EscapeCommFunction(hPort, Win32Com.CLRRTS)) //JH 1.3: Was 1, should be 0: stateRTS = 0; else ThrowException("Unexpected Failure"); } } get { return (stateRTS == 1); } } /// /// True if the DTR pin is controllable via the DTR property /// protected bool DTRavailable { get { return (stateDTR < 2);}} /// /// The state of the DTR modem control output /// protected bool DTR { set { if (stateDTR > 1) return; CheckOnline(); if (value) { if (Win32Com.EscapeCommFunction(hPort, Win32Com.SETDTR)) stateDTR = 1; else ThrowException("Unexpected Failure"); } else { if (Win32Com.EscapeCommFunction(hPort, Win32Com.CLRDTR)) stateDTR = 0; else ThrowException("Unexpected Failure"); } } get { return (stateDTR == 1); } } /// /// Assert or remove a break condition from the transmission line /// protected bool Break { set { if (stateBRK > 1) return; CheckOnline(); if (value) { if (Win32Com.EscapeCommFunction(hPort, Win32Com.SETBREAK)) stateBRK = 0; else ThrowException("Unexpected Failure"); } else { if (Win32Com.EscapeCommFunction(hPort, Win32Com.CLRBREAK)) stateBRK = 0; else ThrowException("Unexpected Failure"); } } get { return (stateBRK == 1); } } /// /// Override this to provide settings. (NB this is called during Open method) /// /// CommSet, or derived object with required settings initialised /// /// Override this to provide processing after the port is openned (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) {} /// /// Override this to process received bytes. /// /// The byte that was received public virtual void OnRxChar(byte ch) { if (onRx != null) onRx(ch); } /// /// Override this to take action when transmission is complete (i.e. all bytes have actually /// been sent, not just queued). /// private void OnTxDone() { if (onTx != null) onTx(); } /// /// Override this to take action when a break condition is detected on the input line. /// private void OnBreak() { if (Que.Count != 0) { // Console.Write($"OnBreak break que={Que.Count} "); Byte[] bb = new Byte[Que.Count]; int i = 0; while (Que.Count > 0) { Byte b = 0; Que.TryDequeue(out b); bb[i++] = b; } //bb.ToList().ForEach(b => Console.Write($"0x{b:X2} ")); // Console.WriteLine(); if (onRxPack != null) onRxPack(bb); } } //JH 1.3: Deleted OnRing() which was never called: use OnStatusChange instead (Thanks Jim Foster) /// /// Override this to take action when one or more modem status inputs change state /// /// The status inputs that have changed state /// The state of the status inputs protected virtual void OnStatusChange(ModemStatus mask, ModemStatus state) {} /// /// Override this to take action when the reception thread closes due to an exception being thrown. /// /// The exception which was thrown protected virtual void OnRxException(Exception e) {} private void ReceiveThread() { byte[] buf = new Byte[1]; uint gotbytes; bool starting; starting = true; AutoResetEvent sg = new AutoResetEvent(false); Win32Com.OVERLAPPED ov = new Win32Com.OVERLAPPED(); IntPtr unmanagedOv; IntPtr uMask; uint eventMask = 0; unmanagedOv = Marshal.AllocHGlobal(Marshal.SizeOf(ov)); uMask = Marshal.AllocHGlobal(Marshal.SizeOf(eventMask)); ov.Offset = 0; ov.OffsetHigh = 0; ov.hEvent = sg.Handle; Marshal.StructureToPtr(ov, unmanagedOv, true); try { while(true) { if (!Win32Com.SetCommMask(hPort, Win32Com.EV_RXCHAR | Win32Com.EV_TXEMPTY | Win32Com.EV_CTS | Win32Com.EV_DSR | Win32Com.EV_BREAK | Win32Com.EV_RLSD | Win32Com.EV_RING | Win32Com.EV_ERR)) { throw new CommPortException("IO Error [001]"); } Marshal.WriteInt32(uMask, 0); //JH 1.2: Tells the main thread that this thread is ready for action. if (starting) { startEvent.Set(); starting = false; } if (!Win32Com.WaitCommEvent(hPort, uMask, unmanagedOv)) { if (Marshal.GetLastWin32Error() == Win32Com.ERROR_IO_PENDING) { sg.WaitOne(); } else { throw new CommPortException("IO Error [002]"); } } eventMask = (uint)Marshal.ReadInt32(uMask); if ((eventMask & Win32Com.EV_ERR) != 0) { UInt32 errs; if (Win32Com.ClearCommError(hPort, out errs, IntPtr.Zero)) { //JH 1.2: BREAK condition has an error flag and and an event flag. Not sure if both //are always raised, so if CE_BREAK is only error flag ignore it and set the EV_BREAK //flag for normal handling. Also made more robust by handling case were no recognised //error was present in the flags. (Thanks to Fred Pittroff for finding this problem!) int ec = 0; StringBuilder s = new StringBuilder("UART Error: ", 40); if ((errs & Win32Com.CE_FRAME) != 0) { s = s.Append("Framing,"); ec++; } if ((errs & Win32Com.CE_IOE) != 0) { s = s.Append("IO,"); ec++; } if ((errs & Win32Com.CE_OVERRUN) != 0) { s = s.Append("Overrun,"); ec++; } if ((errs & Win32Com.CE_RXOVER) != 0) { s = s.Append("Receive Cverflow,"); ec++; } if ((errs & Win32Com.CE_RXPARITY) != 0) { s = s.Append("Parity,"); ec++; } if ((errs & Win32Com.CE_TXFULL) != 0) { s = s.Append("Transmit Overflow,"); ec++; } if (ec > 0) { s.Length = s.Length - 1; throw new CommPortException(s.ToString()); } else { if (errs == Win32Com.CE_BREAK) { eventMask |= Win32Com.EV_BREAK; } else { throw new CommPortException("IO Error [003]"); } } } else { throw new CommPortException("IO Error [003]"); } } if ((eventMask & Win32Com.EV_RXCHAR) != 0) { do { gotbytes = 0; if (!Win32Com.ReadFile(hPort, buf, 1, out gotbytes, unmanagedOv)) { //JH 1.1: Removed ERROR_IO_PENDING handling as comm timeouts have now //been set so ReadFile returns immediately. This avoids use of CancelIo //which was causing loss of data. Thanks to Daniel Moth for suggesting this //might be a problem, and to many others for reporting that it was! int x = Marshal.GetLastWin32Error(); throw new CommPortException("IO Error [004]"); } if (gotbytes == 1) { OnRxChar(buf[0]); Que.Enqueue(buf[0]); } } while (gotbytes > 0); OnBreak(); } if ((eventMask & Win32Com.EV_TXEMPTY) != 0) { //JH1.3: lock(empty) empty[0] = true; OnTxDone(); } if ((eventMask & Win32Com.EV_BREAK) != 0) OnBreak(); uint i = 0; if ((eventMask & Win32Com.EV_CTS) != 0) i |= Win32Com.MS_CTS_ON; if ((eventMask & Win32Com.EV_DSR) != 0) i |= Win32Com.MS_DSR_ON; if ((eventMask & Win32Com.EV_RLSD) != 0) i |= Win32Com.MS_RLSD_ON; if ((eventMask & Win32Com.EV_RING) != 0) i |= Win32Com.MS_RING_ON; if (i != 0) { uint f; if (!Win32Com.GetCommModemStatus(hPort, out f)) throw new CommPortException("IO Error [005]"); OnStatusChange(new ModemStatus(i), new ModemStatus(f)); } } } catch (Exception e) { //JH 1.3: Added for shutdown robustness (Thanks to Fred Pittroff, Mark Behner and Kevin Williamson!), . Win32Com.CancelIo(hPort); 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) { //JH 1.1: Avoid use of GetHandleInformation for W98 compatability. if (hPort != (System.IntPtr)Win32Com.INVALID_HANDLE_VALUE) return true; ThrowException("Offline"); return false; } else { if (auto) { if (Open()) return true; } ThrowException("Offline"); return false; } } } /// /// Exception used for all errors. /// public class CommPortException : ApplicationException { /// /// Constructor for raising direct exceptions /// /// Description of error public CommPortException(string desc) : base(desc) {} /// /// Constructor for re-raising exceptions from receive thread /// /// Inner exception raised on receive thread public CommPortException(Exception e) : base("Receive Thread Exception", e) {} } internal class Win32Com { /// /// Opening Testing and Closing the Port Handle. /// [DllImport("kernel32.dll", SetLastError=true)] internal static extern IntPtr CreateFile(String lpFileName, UInt32 dwDesiredAccess, UInt32 dwShareMode, IntPtr lpSecurityAttributes, UInt32 dwCreationDisposition, UInt32 dwFlagsAndAttributes, IntPtr hTemplateFile); //Constants for errors: internal const UInt32 ERROR_FILE_NOT_FOUND = 2; internal const UInt32 ERROR_INVALID_NAME = 123; internal const UInt32 ERROR_ACCESS_DENIED = 5; internal const UInt32 ERROR_IO_PENDING = 997; internal const UInt32 ERROR_IO_INCOMPLETE = 996; //Constants for return value: internal const Int32 INVALID_HANDLE_VALUE = -1; //Constants for dwFlagsAndAttributes: internal const UInt32 FILE_FLAG_OVERLAPPED = 0x40000000; //Constants for dwCreationDisposition: internal const UInt32 OPEN_EXISTING = 3; //Constants for dwDesiredAccess: internal const UInt32 GENERIC_READ = 0x80000000; internal const UInt32 GENERIC_WRITE = 0x40000000; [DllImport("kernel32.dll")] internal static extern Boolean CloseHandle(IntPtr hObject); /// /// Manipulating the communications settings. /// [DllImport("kernel32.dll")] internal static extern Boolean GetCommState(IntPtr hFile, ref DCB lpDCB); [DllImport("kernel32.dll")] internal static extern Boolean GetCommTimeouts(IntPtr hFile, out COMMTIMEOUTS lpCommTimeouts); [DllImport("kernel32.dll")] internal static extern Boolean BuildCommDCBAndTimeouts(String lpDef, ref DCB lpDCB, ref COMMTIMEOUTS lpCommTimeouts); [DllImport("kernel32.dll")] internal static extern Boolean SetCommState(IntPtr hFile, [In] ref DCB lpDCB); [DllImport("kernel32.dll")] internal static extern Boolean SetCommTimeouts(IntPtr hFile, [In] ref COMMTIMEOUTS lpCommTimeouts); [DllImport("kernel32.dll")] internal static extern Boolean SetupComm(IntPtr hFile, UInt32 dwInQueue, UInt32 dwOutQueue); [StructLayout( LayoutKind.Sequential )] internal struct COMMTIMEOUTS { //JH 1.1: Changed Int32 to UInt32 to allow setting to MAXDWORD internal UInt32 ReadIntervalTimeout; internal UInt32 ReadTotalTimeoutMultiplier; internal UInt32 ReadTotalTimeoutConstant; internal UInt32 WriteTotalTimeoutMultiplier; internal UInt32 WriteTotalTimeoutConstant; } //JH 1.1: Added to enable use of "return immediately" timeout. internal const UInt32 MAXDWORD = 0xffffffff; [StructLayout( LayoutKind.Sequential )] internal struct DCB { internal Int32 DCBlength; internal Int32 BaudRate; internal Int32 PackedValues; internal Int16 wReserved; internal Int16 XonLim; internal Int16 XoffLim; internal Byte ByteSize; internal Byte Parity; internal Byte StopBits; internal Byte XonChar; internal Byte XoffChar; internal Byte ErrorChar; internal Byte EofChar; internal Byte EvtChar; internal Int16 wReserved1; internal void init(bool parity, bool outCTS, bool outDSR, int dtr, bool inDSR, bool txc, bool xOut, bool xIn, int rts) { //JH 1.3: Was 0x8001 ans so not setting fAbortOnError - Thanks Larry Delby! DCBlength = 28; PackedValues = 0x4001; if (parity) PackedValues |= 0x0002; if (outCTS) PackedValues |= 0x0004; if (outDSR) PackedValues |= 0x0008; PackedValues |= ((dtr & 0x0003) << 4); if (inDSR) PackedValues |= 0x0040; if (txc) PackedValues |= 0x0080; if (xOut) PackedValues |= 0x0100; if (xIn) PackedValues |= 0x0200; PackedValues |= ((rts & 0x0003) << 12); } } /// /// Reading and writing. /// [DllImport("kernel32.dll", SetLastError=true)] internal static extern Boolean WriteFile(IntPtr fFile, Byte[] lpBuffer, UInt32 nNumberOfBytesToWrite, out UInt32 lpNumberOfBytesWritten, IntPtr lpOverlapped); [StructLayout( LayoutKind.Sequential )] internal struct OVERLAPPED { internal UIntPtr Internal; internal UIntPtr InternalHigh; internal UInt32 Offset; internal UInt32 OffsetHigh; internal IntPtr hEvent; } [DllImport("kernel32.dll")] internal static extern Boolean SetCommMask(IntPtr hFile, UInt32 dwEvtMask); // Constants for dwEvtMask: internal const UInt32 EV_RXCHAR = 0x0001; internal const UInt32 EV_RXFLAG = 0x0002; internal const UInt32 EV_TXEMPTY = 0x0004; internal const UInt32 EV_CTS = 0x0008; internal const UInt32 EV_DSR = 0x0010; internal const UInt32 EV_RLSD = 0x0020; internal const UInt32 EV_BREAK = 0x0040; internal const UInt32 EV_ERR = 0x0080; internal const UInt32 EV_RING = 0x0100; internal const UInt32 EV_PERR = 0x0200; internal const UInt32 EV_RX80FULL = 0x0400; internal const UInt32 EV_EVENT1 = 0x0800; internal const UInt32 EV_EVENT2 = 0x1000; [DllImport("kernel32.dll", SetLastError=true)] internal static extern Boolean WaitCommEvent(IntPtr hFile, IntPtr lpEvtMask, IntPtr lpOverlapped); [DllImport("kernel32.dll")] internal static extern Boolean CancelIo(IntPtr hFile); [DllImport("kernel32.dll", SetLastError=true)] internal static extern Boolean ReadFile(IntPtr hFile, [Out] Byte[] lpBuffer, UInt32 nNumberOfBytesToRead, out UInt32 nNumberOfBytesRead, IntPtr lpOverlapped); [DllImport("kernel32.dll")] internal static extern Boolean TransmitCommChar(IntPtr hFile, Byte cChar); /// /// Control port functions. /// [DllImport("kernel32.dll")] internal static extern Boolean EscapeCommFunction(IntPtr hFile, UInt32 dwFunc); // Constants for dwFunc: internal const UInt32 SETXOFF = 1; internal const UInt32 SETXON = 2; internal const UInt32 SETRTS = 3; internal const UInt32 CLRRTS = 4; internal const UInt32 SETDTR = 5; internal const UInt32 CLRDTR = 6; internal const UInt32 RESETDEV = 7; internal const UInt32 SETBREAK = 8; internal const UInt32 CLRBREAK = 9; [DllImport("kernel32.dll")] internal static extern Boolean GetCommModemStatus(IntPtr hFile, out UInt32 lpModemStat); // Constants for lpModemStat: internal const UInt32 MS_CTS_ON = 0x0010; internal const UInt32 MS_DSR_ON = 0x0020; internal const UInt32 MS_RING_ON = 0x0040; internal const UInt32 MS_RLSD_ON = 0x0080; /// /// Status Functions. /// [DllImport("kernel32.dll", SetLastError=true)] internal static extern Boolean GetOverlappedResult(IntPtr hFile, IntPtr lpOverlapped, out UInt32 nNumberOfBytesTransferred, Boolean bWait); [DllImport("kernel32.dll")] internal static extern Boolean ClearCommError(IntPtr hFile, out UInt32 lpErrors, IntPtr lpStat); [DllImport("kernel32.dll")] internal static extern Boolean ClearCommError(IntPtr hFile, out UInt32 lpErrors, out COMSTAT cs); //Constants for lpErrors: internal const UInt32 CE_RXOVER = 0x0001; internal const UInt32 CE_OVERRUN = 0x0002; internal const UInt32 CE_RXPARITY = 0x0004; internal const UInt32 CE_FRAME = 0x0008; internal const UInt32 CE_BREAK = 0x0010; internal const UInt32 CE_TXFULL = 0x0100; internal const UInt32 CE_PTO = 0x0200; internal const UInt32 CE_IOE = 0x0400; internal const UInt32 CE_DNS = 0x0800; internal const UInt32 CE_OOP = 0x1000; internal const UInt32 CE_MODE = 0x8000; [StructLayout( LayoutKind.Sequential )] internal struct COMSTAT { internal const uint fCtsHold = 0x1; internal const uint fDsrHold = 0x2; internal const uint fRlsdHold = 0x4; internal const uint fXoffHold = 0x8; internal const uint fXoffSent = 0x10; internal const uint fEof = 0x20; internal const uint fTxim = 0x40; internal UInt32 Flags; internal UInt32 cbInQue; internal UInt32 cbOutQue; } [DllImport("kernel32.dll")] internal static extern Boolean GetCommProperties(IntPtr hFile, out COMMPROP cp); [StructLayout( LayoutKind.Sequential )] internal struct COMMPROP { internal UInt16 wPacketLength; internal UInt16 wPacketVersion; internal UInt32 dwServiceMask; internal UInt32 dwReserved1; internal UInt32 dwMaxTxQueue; internal UInt32 dwMaxRxQueue; internal UInt32 dwMaxBaud; internal UInt32 dwProvSubType; internal UInt32 dwProvCapabilities; internal UInt32 dwSettableParams; internal UInt32 dwSettableBaud; internal UInt16 wSettableData; internal UInt16 wSettableStopParity; internal UInt32 dwCurrentTxQueue; internal UInt32 dwCurrentRxQueue; internal UInt32 dwProvSpec1; internal UInt32 dwProvSpec2; internal Byte wcProvChar; } } }