using System; using System.IO; using System.Runtime.InteropServices; using MatterHackers.TermiosH; namespace MatterHackers.MatterControl.FrostedSerial { [StructLayout(LayoutKind.Sequential, Pack=1)] public unsafe struct tDeviceInfo { public uint c_iflag; public uint c_oflag; public uint c_cflag; public uint c_lflag; public fixed byte c_cc[20]; public uint c_ispeed; public uint c_ospeed; } class FrostedSerialPortStream : Stream, IFrostedSerialStream, IDisposable { int fd; int read_timeout; int write_timeout; bool disposed; [DllImport("FrostedSerialHelper", SetLastError = true)] static extern int open_serial(string portName); [DllImport("FrostedSerialHelper", SetLastError = true)] static extern int set_attributes (int fd, int baudRate, Parity parity, int dataBits, StopBits stopBits, Handshake handshake); public FrostedSerialPortStream(string portName, int baudRate, int dataBits, Parity parity, StopBits stopBits, bool dtrEnable, bool rtsEnable, Handshake handshake, int readTimeout, int writeTimeout, int readBufferSize, int writeBufferSize) { fd = open_serial(portName); if (fd == -1) { ThrowIOException (); } TryBaudRate(baudRate); int canSetAttributes = set_attributes (fd, baudRate, parity, dataBits, stopBits, handshake); if (canSetAttributes != 0) { throw new IOException(canSetAttributes.ToString()); // Probably Win32Exc for compatibility } read_timeout = readTimeout; write_timeout = writeTimeout; SetSignal(SerialSignal.Dtr, dtrEnable); if (handshake != Handshake.RequestToSend && handshake != Handshake.RequestToSendXOnXOff) SetSignal(SerialSignal.Rts, rtsEnable); } private int SetupBaudRate(int baudRate) { throw new NotImplementedException(); } [DllImport("FrostedSerialHelper", SetLastError = true)] static extern int tcgetattr(int fd, tDeviceInfo newtio); private int TCGetAttribute(int fd, tDeviceInfo newtio) { int result = tcgetattr (fd, newtio); return result; } [DllImport("FrostedSerialHelper", SetLastError = true)] static extern int tcsetattr(int fd, uint optional_actions, tDeviceInfo newtio); private int TCSetAttribute(int fd, uint optional_actions, tDeviceInfo newtio) { return tcsetattr (fd, optional_actions, newtio); } private int CFSetISpeed(tDeviceInfo newtio, int baudRate) { newtio.c_ispeed = (uint)baudRate; return (int)newtio.c_ispeed; } private int CFSetOSpeed(tDeviceInfo newtio, int baudRate) { newtio.c_ospeed = (uint)baudRate; return (int)newtio.c_ospeed; } private bool SetFrostedAttributes(int fd, int baudRate, Parity parity, int dataBits, StopBits stopBits, Handshake handshake) { tDeviceInfo newtio = new tDeviceInfo (); if (TCGetAttribute (fd, newtio) == -1) { return false; } newtio.c_cflag |= (uint)(e_c_oflag.CLOCAL | e_c_oflag.CREAD); // there is no defenition for e_c_lflag.ECHOL that I can find. It was in the list of or'ed flags below unchecked { newtio.c_lflag &= (uint)-(int)(e_c_lflag.ICANON | e_c_lflag.ECHO | e_c_lflag.ECHOE | e_c_lflag.ECHOK | e_c_lflag.ECHONL | e_c_lflag.ISIG | e_c_lflag.IEXTEN); } newtio.c_oflag &= (uint)(e_c_oflag.OPOST); newtio.c_iflag = (uint)e_c_iflag.IGNBRK; baudRate = SetupBaudRate (baudRate); unchecked { newtio.c_cflag &= (uint)-(uint)e_c_oflag.CSIZE; } switch (dataBits) { case 5: newtio.c_cflag |= (uint)e_c_oflag.CS5; break; case 6: newtio.c_cflag |= (uint)e_c_oflag.CS6; break; case 7: newtio.c_cflag |= (uint)e_c_oflag.CS6; break; case 8: default: newtio.c_cflag |= (uint)e_c_oflag.CS8; break; } switch (stopBits) { case StopBits.None: break; case StopBits.One: unchecked { newtio.c_cflag &= (uint)-(uint)e_c_oflag.CSTOPB; } break; case StopBits.Two: newtio.c_cflag |= (uint)e_c_oflag.CSTOPB; break; case StopBits.OnePointFive: break; } unchecked { newtio.c_iflag &= (uint)-(uint)(e_c_iflag.INPCK | e_c_iflag.ISTRIP); } switch (parity) { case Parity.None: /* None */ newtio.c_cflag &= ~(uint)(e_c_oflag.PARENB | e_c_oflag.PARODD); break; case Parity.Odd: /* Odd */ newtio.c_cflag |= (uint)(e_c_oflag.PARENB | e_c_oflag.PARODD); break; case Parity.Even: /* Even */ newtio.c_cflag &= ~(uint)(e_c_oflag.PARODD); newtio.c_cflag |= (uint)(e_c_oflag.PARENB); break; case Parity.Mark: /* Mark */ /* XXX unhandled */ break; case Parity.Space: /* Space */ /* XXX unhandled */ break; } newtio.c_iflag &= ~(uint)(e_c_iflag.IXOFF | e_c_iflag.IXON); #if CRTSCTS newtio.c_cflag &= ~CRTSCTS; #endif //* def CRTSCTS */ switch (handshake) { case Handshake.None: /* None */ /* do nothing */ break; case Handshake.RequestToSend: /* RequestToSend (RTS) */ #if CRTSCTS newtio.c_cflag |= CRTSCTS; #endif //* def CRTSCTS */ break; case Handshake.RequestToSendXOnXOff: /* RequestToSendXOnXOff (RTS + XON/XOFF) */ #if CRTSCTS newtio.c_cflag |= CRTSCTS; #endif //* def CRTSCTS */ /* fall through */ case Handshake.XOnXOff: /* XOnXOff */ newtio.c_iflag |= (uint)(e_c_iflag.IXOFF | e_c_iflag.IXON); break; } if (CFSetOSpeed (newtio, baudRate) < 0 || CFSetISpeed (newtio, baudRate) < 0 || TCSetAttribute (fd, (uint)e_tcsetaatr.TCSANOW, newtio) < 0) { return false; } else { return true; } //return set_attributes(fd, baudRate, parity, dataBits, sb, hs); } public override bool CanRead { get { return true; } } public override bool CanSeek { get { return false; } } public override bool CanWrite { get { return true; } } public override bool CanTimeout { get { return true; } } public override int ReadTimeout { get { return read_timeout; } set { if (value < 0 && value != FrostedSerialPort.InfiniteTimeout) throw new ArgumentOutOfRangeException("value"); read_timeout = value; } } public override int WriteTimeout { get { return write_timeout; } set { if (value < 0 && value != FrostedSerialPort.InfiniteTimeout) throw new ArgumentOutOfRangeException("value"); write_timeout = value; } } public override long Length { get { throw new NotSupportedException(); } } public override long Position { get { throw new NotSupportedException(); } set { throw new NotSupportedException(); } } public override void Flush() { // If used, this _could_ flush the serial port // buffer (not the SerialPort class buffer) } [DllImport("FrostedSerialHelper", SetLastError = true)] static extern int read_serial(int fd, byte[] buffer, int offset, int count); [DllImport("FrostedSerialHelper", SetLastError = true)] static extern bool poll_serial(int fd, out int error, int timeout); public override int Read([In, Out] byte[] buffer, int offset, int count) { CheckDisposed(); if (buffer == null) throw new ArgumentNullException("buffer"); if (offset < 0 || count < 0) throw new ArgumentOutOfRangeException("offset or count less than zero."); if (buffer.Length - offset < count) throw new ArgumentException("offset+count", "The size of the buffer is less than offset + count."); int error; bool poll_result = poll_serial(fd, out error, read_timeout); if (error == -1) ThrowIOException(); if (!poll_result) { // see bug 79735 http://bugzilla.ximian.com/show_bug.cgi?id=79735 // should the next line read: return -1; throw new TimeoutException(); } int result = read_serial(fd, buffer, offset, count); if (result == -1) ThrowIOException(); return result; } public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); } public override void SetLength(long value) { throw new NotSupportedException(); } [DllImport("FrostedSerialHelper", SetLastError = true)] static extern int write_serial(int fd, byte[] buffer, int offset, int count, int timeout); public override void Write(byte[] buffer, int offset, int count) { CheckDisposed(); if (buffer == null) throw new ArgumentNullException("buffer"); if (offset < 0 || count < 0) throw new ArgumentOutOfRangeException(); if (buffer.Length - offset < count) throw new ArgumentException("offset+count", "The size of the buffer is less than offset + count."); // FIXME: this reports every write error as timeout if (write_serial(fd, buffer, offset, count, write_timeout) < 0) throw new TimeoutException("The operation has timed-out"); } protected override void Dispose(bool disposing) { if (disposed) return; disposed = true; if (close_serial (fd) != 0) { //Don't do anything } } [DllImport("FrostedSerialHelper", SetLastError = true)] static extern int close_serial(int fd); public override void Close() { ((IDisposable)this).Dispose(); } void IDisposable.Dispose() { Dispose(true); GC.SuppressFinalize(this); } ~FrostedSerialPortStream() { Dispose(false); } void CheckDisposed() { if (disposed) throw new ObjectDisposedException(GetType().FullName); } public void SetAttributes(int baud_rate, Parity parity, int data_bits, StopBits sb, Handshake hs) { if (!SetFrostedAttributes(fd, baud_rate, parity, data_bits, sb, hs)) ThrowIOException(); } [DllImport("FrostedSerialHelper", SetLastError = true)] static extern int get_bytes_in_buffer(int fd, int input); public int BytesToRead { get { int result = get_bytes_in_buffer(fd, 1); if (result == -1) ThrowIOException(); return result; } } public int BytesToWrite { get { int result = get_bytes_in_buffer(fd, 0); if (result == -1) ThrowIOException(); return result; } } [DllImport("FrostedSerialHelper", SetLastError = true)] static extern int discard_buffer(int fd, bool inputBuffer); public void DiscardInBuffer() { if (discard_buffer(fd, true) != 0) ThrowIOException(); } public void DiscardOutBuffer() { if (discard_buffer(fd, false) != 0) ThrowIOException(); } [DllImport("FrostedSerialHelper", SetLastError = true)] static extern SerialSignal get_signals(int fd, out int error); public SerialSignal GetSignals() { int error; SerialSignal signals = get_signals(fd, out error); if (error == -1) ThrowIOException(); return signals; } [DllImport("FrostedSerialHelper", SetLastError = true)] static extern int set_signal(int fd, SerialSignal signal, bool value); public void SetSignal(SerialSignal signal, bool value) { if (signal < SerialSignal.Cd || signal > SerialSignal.Rts || signal == SerialSignal.Cd || signal == SerialSignal.Cts || signal == SerialSignal.Dsr) throw new Exception("Invalid internal value"); if (set_signal(fd, signal, value) == -1) ThrowIOException(); } [DllImport("FrostedSerialHelper", SetLastError = true)] static extern int breakprop(int fd); public void SetBreakState(bool value) { if (value) if (breakprop(fd) == -1) ThrowIOException(); } [DllImport("libc")] static extern IntPtr strerror(int errnum); static void ThrowIOException() { int errnum = Marshal.GetLastWin32Error(); string error_message = Marshal.PtrToStringAnsi(strerror(errnum)); throw new IOException(error_message); } [DllImport("FrostedSerialHelper")] static extern bool is_baud_rate_legal(int baud_rate); private void TryBaudRate(int baudRate) { if (!is_baud_rate_legal(baudRate)) { // this kind of exception to be compatible with MSDN API throw new ArgumentOutOfRangeException("baudRate", "Given baud rate is not supported on this platform."); } } } }