/* -*- Mode: Csharp; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ // // // This class has several problems: // // * No buffering, the specification requires that there is buffering, this // matters because a few methods expose strings and chars and the reading // is encoding sensitive. This means that when we do a read of a byte // sequence that can not be turned into a full string by the current encoding // we should keep a buffer with this data, and read from it on the next // iteration. // // * Calls to read_serial from the unmanaged C do not check for errors, // like EINTR, that should be retried // // * Calls to the encoder that do not consume all bytes because of partial // reads // using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Text; using System.Runtime.InteropServices; using Microsoft.Win32; namespace System.IO.Ports { [MonitoringDescription ("")] public class SerialPort : Component { public const int InfiniteTimeout = -1; const int DefaultReadBufferSize = 4096; const int DefaultWriteBufferSize = 2048; const int DefaultBaudRate = 9600; const int DefaultDataBits = 8; const Parity DefaultParity = Parity.None; const StopBits DefaultStopBits = StopBits.One; bool is_open; int baud_rate; Parity parity; StopBits stop_bits; Handshake handshake; int data_bits; bool break_state = false; bool dtr_enable = false; bool rts_enable = false; ISerialStream stream; Encoding encoding = Encoding.ASCII; string new_line = Environment.NewLine; string port_name; int read_timeout = InfiniteTimeout; int write_timeout = InfiniteTimeout; int readBufferSize = DefaultReadBufferSize; int writeBufferSize = DefaultWriteBufferSize; object error_received = new object (); object data_received = new object (); object pin_changed = new object (); public SerialPort () : this (GetDefaultPortName (), DefaultBaudRate, DefaultParity, DefaultDataBits, DefaultStopBits) { } public SerialPort (IContainer container) : this () { // TODO: What to do here? } public SerialPort (string portName) : this (portName, DefaultBaudRate, DefaultParity, DefaultDataBits, DefaultStopBits) { } public SerialPort (string portName, int baudRate) : this (portName, baudRate, DefaultParity, DefaultDataBits, DefaultStopBits) { } public SerialPort (string portName, int baudRate, Parity parity) : this (portName, baudRate, parity, DefaultDataBits, DefaultStopBits) { } public SerialPort (string portName, int baudRate, Parity parity, int dataBits) : this (portName, baudRate, parity, dataBits, DefaultStopBits) { } public SerialPort (string portName, int baudRate, Parity parity, int dataBits, StopBits stopBits) { port_name = portName; baud_rate = baudRate; data_bits = dataBits; stop_bits = stopBits; this.parity = parity; } static string GetDefaultPortName () { string[] ports = GetPortNames(); if (ports.Length > 0) { return ports[0]; } else { int p = (int)Environment.OSVersion.Platform; if (p == 4 || p == 128 || p == 6) return "ttyS0"; // Default for Unix else return "COM1"; // Default for Windows } } [Browsable (false)] [DesignerSerializationVisibilityAttribute (DesignerSerializationVisibility.Hidden)] public Stream BaseStream { get { CheckOpen (); return (Stream) stream; } } [DefaultValueAttribute (DefaultBaudRate)] [Browsable (true)] [MonitoringDescription ("")] public int BaudRate { get { return baud_rate; } set { if (value <= 0) throw new ArgumentOutOfRangeException ("value"); if (is_open) stream.SetAttributes (value, parity, data_bits, stop_bits, handshake); baud_rate = value; } } [Browsable (false)] [DesignerSerializationVisibilityAttribute (DesignerSerializationVisibility.Hidden)] public bool BreakState { get { return break_state; } set { CheckOpen (); if (value == break_state) return; // Do nothing. stream.SetBreakState (value); break_state = value; } } [Browsable (false)] [DesignerSerializationVisibilityAttribute (DesignerSerializationVisibility.Hidden)] public int BytesToRead { get { CheckOpen (); return stream.BytesToRead; } } [Browsable (false)] [DesignerSerializationVisibilityAttribute (DesignerSerializationVisibility.Hidden)] public int BytesToWrite { get { CheckOpen (); return stream.BytesToWrite; } } [Browsable (false)] [DesignerSerializationVisibilityAttribute (DesignerSerializationVisibility.Hidden)] public bool CDHolding { get { CheckOpen (); return (stream.GetSignals () & SerialSignal.Cd) != 0; } } [Browsable (false)] [DesignerSerializationVisibilityAttribute (DesignerSerializationVisibility.Hidden)] public bool CtsHolding { get { CheckOpen (); return (stream.GetSignals () & SerialSignal.Cts) != 0; } } [DefaultValueAttribute(DefaultDataBits)] [Browsable (true)] [MonitoringDescription ("")] public int DataBits { get { return data_bits; } set { if (value < 5 || value > 8) throw new ArgumentOutOfRangeException ("value"); if (is_open) stream.SetAttributes (baud_rate, parity, value, stop_bits, handshake); data_bits = value; } } [MonoTODO("Not implemented")] [Browsable (true)] [MonitoringDescription ("")] [DefaultValue (false)] public bool DiscardNull { get { throw new NotImplementedException (); } set { // LAMESPEC: Msdn states that an InvalidOperationException exception // is fired if the port is not open, which is *not* happening. throw new NotImplementedException (); } } [Browsable (false)] [DesignerSerializationVisibilityAttribute (DesignerSerializationVisibility.Hidden)] public bool DsrHolding { get { CheckOpen (); return (stream.GetSignals () & SerialSignal.Dsr) != 0; } } [DefaultValueAttribute(false)] [Browsable (true)] [MonitoringDescription ("")] public bool DtrEnable { get { return dtr_enable; } set { if (value == dtr_enable) return; if (is_open) stream.SetSignal (SerialSignal.Dtr, value); dtr_enable = value; } } [Browsable (false)] [DesignerSerializationVisibilityAttribute (DesignerSerializationVisibility.Hidden)] [MonitoringDescription ("")] public Encoding Encoding { get { return encoding; } set { if (value == null) throw new ArgumentNullException ("value"); encoding = value; } } [DefaultValueAttribute(Handshake.None)] [Browsable (true)] [MonitoringDescription ("")] public Handshake Handshake { get { return handshake; } set { if (value < Handshake.None || value > Handshake.RequestToSendXOnXOff) throw new ArgumentOutOfRangeException ("value"); if (is_open) stream.SetAttributes (baud_rate, parity, data_bits, stop_bits, value); handshake = value; } } [Browsable (false)] public bool IsOpen { get { return is_open; } } [DefaultValueAttribute("\n")] [Browsable (false)] [MonitoringDescription ("")] public string NewLine { get { return new_line; } set { if (value == null) throw new ArgumentNullException ("value"); if (value.Length == 0) throw new ArgumentException ("NewLine cannot be null or empty.", "value"); new_line = value; } } [DefaultValueAttribute(DefaultParity)] [Browsable (true)] [MonitoringDescription ("")] public Parity Parity { get { return parity; } set { if (value < Parity.None || value > Parity.Space) throw new ArgumentOutOfRangeException ("value"); if (is_open) stream.SetAttributes (baud_rate, value, data_bits, stop_bits, handshake); parity = value; } } [MonoTODO("Not implemented")] [Browsable (true)] [MonitoringDescription ("")] [DefaultValue (63)] public byte ParityReplace { get { throw new NotImplementedException (); } set { throw new NotImplementedException (); } } [Browsable (true)] [MonitoringDescription ("")] [DefaultValue ("COM1")] // silly Windows-ism. We should ignore it. public string PortName { get { return port_name; } set { if (is_open) throw new InvalidOperationException ("Port name cannot be set while port is open."); if (value == null) throw new ArgumentNullException ("value"); if (value.Length == 0 || value.StartsWith ("\\\\")) throw new ArgumentException ("value"); port_name = value; } } [DefaultValueAttribute(DefaultReadBufferSize)] [Browsable (true)] [MonitoringDescription ("")] public int ReadBufferSize { get { return readBufferSize; } set { if (is_open) throw new InvalidOperationException (); if (value <= 0) throw new ArgumentOutOfRangeException ("value"); if (value <= DefaultReadBufferSize) return; readBufferSize = value; } } [DefaultValueAttribute(InfiniteTimeout)] [Browsable (true)] [MonitoringDescription ("")] public int ReadTimeout { get { return read_timeout; } set { if (value < 0 && value != InfiniteTimeout) throw new ArgumentOutOfRangeException ("value"); if (is_open) stream.ReadTimeout = value; read_timeout = value; } } [MonoTODO("Not implemented")] [DefaultValueAttribute(1)] [Browsable (true)] [MonitoringDescription ("")] public int ReceivedBytesThreshold { get { throw new NotImplementedException (); } set { if (value <= 0) throw new ArgumentOutOfRangeException ("value"); throw new NotImplementedException (); } } [DefaultValueAttribute(false)] [Browsable (true)] [MonitoringDescription ("")] public bool RtsEnable { get { return rts_enable; } set { if (value == rts_enable) return; if (is_open) stream.SetSignal (SerialSignal.Rts, value); rts_enable = value; } } [DefaultValueAttribute(DefaultStopBits)] [Browsable (true)] [MonitoringDescription ("")] public StopBits StopBits { get { return stop_bits; } set { if (value < StopBits.One || value > StopBits.OnePointFive) throw new ArgumentOutOfRangeException ("value"); if (is_open) stream.SetAttributes (baud_rate, parity, data_bits, value, handshake); stop_bits = value; } } [DefaultValueAttribute(DefaultWriteBufferSize)] [Browsable (true)] [MonitoringDescription ("")] public int WriteBufferSize { get { return writeBufferSize; } set { if (is_open) throw new InvalidOperationException (); if (value <= 0) throw new ArgumentOutOfRangeException ("value"); if (value <= DefaultWriteBufferSize) return; writeBufferSize = value; } } [DefaultValueAttribute(InfiniteTimeout)] [Browsable (true)] [MonitoringDescription ("")] public int WriteTimeout { get { return write_timeout; } set { if (value < 0 && value != InfiniteTimeout) throw new ArgumentOutOfRangeException ("value"); if (is_open) stream.WriteTimeout = value; write_timeout = value; } } // methods public void Close () { Dispose (true); } protected override void Dispose (bool disposing) { if (!is_open) return; is_open = false; // Do not close the base stream when the finalizer is run; the managed code can still hold a reference to it. if (disposing) stream.Close (); stream = null; } public void DiscardInBuffer () { CheckOpen (); stream.DiscardInBuffer (); } public void DiscardOutBuffer () { CheckOpen (); stream.DiscardOutBuffer (); } public static string [] GetPortNames () { int p = (int) Environment.OSVersion.Platform; List serial_ports = new List(); // Are we on Unix? if (p == 4 || p == 128 || p == 6) { string[] ttys = Directory.GetFiles("/dev/", "tty*"); bool linux_style = false; // // Probe for Linux-styled devices: /dev/ttyS* or /dev/ttyUSB* // foreach (string dev in ttys) { if (dev.StartsWith("/dev/ttyS") || dev.StartsWith("/dev/ttyUSB") || dev.StartsWith("/dev/ttyACM")) { linux_style = true; break; } } foreach (string dev in ttys) { if (linux_style){ if (dev.StartsWith("/dev/ttyS") || dev.StartsWith("/dev/ttyUSB") || dev.StartsWith("/dev/ttyACM")) serial_ports.Add (dev); } else { if (dev != "/dev/tty" && dev.StartsWith ("/dev/tty") && !dev.StartsWith ("/dev/ttyC")) serial_ports.Add (dev); } } } else { using (RegistryKey subkey = Registry.LocalMachine.OpenSubKey("HARDWARE\\DEVICEMAP\\SERIALCOMM")) { if (subkey != null) { string[] names = subkey.GetValueNames(); foreach (string value in names) { string port = subkey.GetValue(value, "").ToString(); if (port != "") serial_ports.Add(port); } } } } return serial_ports.ToArray(); } static bool IsWindows { get { PlatformID id = Environment.OSVersion.Platform; return id == PlatformID.Win32Windows || id == PlatformID.Win32NT; // WinCE not supported } } public void Open () { if (is_open) throw new InvalidOperationException ("Port is already open"); if (IsWindows) // Use windows kernel32 backend stream = new WinSerialStream (port_name, baud_rate, data_bits, parity, stop_bits, dtr_enable, rts_enable, handshake, read_timeout, write_timeout, readBufferSize, writeBufferSize); else // Use standard unix backend stream = new SerialPortStream (port_name, baud_rate, data_bits, parity, stop_bits, dtr_enable, rts_enable, handshake, read_timeout, write_timeout, readBufferSize, writeBufferSize); is_open = true; } public int Read (byte[] buffer, int offset, int count) { CheckOpen (); 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."); return stream.Read (buffer, offset, count); } public int Read (char[] buffer, int offset, int count) { CheckOpen (); 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 c, i; for (i = 0; i < count && (c = ReadChar ()) != -1; i++) buffer[offset + i] = (char) c; return i; } internal int read_byte () { byte [] buff = new byte [1]; if (stream.Read (buff, 0, 1) > 0) return buff [0]; return -1; } public int ReadByte () { CheckOpen (); return read_byte (); } public int ReadChar () { CheckOpen (); byte [] buffer = new byte [16]; int i = 0; do { int b = read_byte (); if (b == -1) return -1; buffer [i++] = (byte) b; char [] c = encoding.GetChars (buffer, 0, 1); if (c.Length > 0) return (int) c [0]; } while (i < buffer.Length); return -1; } public string ReadExisting () { CheckOpen (); int count = BytesToRead; byte [] bytes = new byte [count]; int n = stream.Read (bytes, 0, count); return new String (encoding.GetChars (bytes, 0, n)); } public string ReadLine () { return ReadTo (new_line); } public string ReadTo (string value) { CheckOpen (); if (value == null) throw new ArgumentNullException ("value"); if (value.Length == 0) throw new ArgumentException ("value"); // Turn into byte array, so we can compare byte [] byte_value = encoding.GetBytes (value); int current = 0; List seen = new List (); while (true){ int n = read_byte (); if (n == -1) break; seen.Add ((byte)n); if (n == byte_value [current]){ current++; if (current == byte_value.Length) return encoding.GetString (seen.ToArray (), 0, seen.Count - byte_value.Length); } else { current = (byte_value [0] == n) ? 1 : 0; } } return encoding.GetString (seen.ToArray ()); } public void Write (string text) { CheckOpen (); if (text == null) throw new ArgumentNullException ("text"); byte [] buffer = encoding.GetBytes (text); Write (buffer, 0, buffer.Length); } public void Write (byte [] buffer, int offset, int count) { CheckOpen (); 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."); stream.Write (buffer, offset, count); } public void Write (char [] buffer, int offset, int count) { CheckOpen (); 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."); byte [] bytes = encoding.GetBytes (buffer, offset, count); stream.Write (bytes, 0, bytes.Length); } public void WriteLine (string text) { Write (text + new_line); } void CheckOpen () { if (!is_open) throw new InvalidOperationException ("Specified port is not open."); } internal void OnErrorReceived (SerialErrorReceivedEventArgs args) { SerialErrorReceivedEventHandler handler = (SerialErrorReceivedEventHandler) Events [error_received]; if (handler != null) handler (this, args); } internal void OnDataReceived (SerialDataReceivedEventArgs args) { SerialDataReceivedEventHandler handler = (SerialDataReceivedEventHandler) Events [data_received]; if (handler != null) handler (this, args); } internal void OnDataReceived (SerialPinChangedEventArgs args) { SerialPinChangedEventHandler handler = (SerialPinChangedEventHandler) Events [pin_changed]; if (handler != null) handler (this, args); } // events [MonitoringDescription ("")] public event SerialErrorReceivedEventHandler ErrorReceived { add { Events.AddHandler (error_received, value); } remove { Events.RemoveHandler (error_received, value); } } [MonitoringDescription ("")] public event SerialPinChangedEventHandler PinChanged { add { Events.AddHandler (pin_changed, value); } remove { Events.RemoveHandler (pin_changed, value); } } [MonitoringDescription ("")] public event SerialDataReceivedEventHandler DataReceived { add { Events.AddHandler (data_received, value); } remove { Events.RemoveHandler (data_received, value); } } } public delegate void SerialDataReceivedEventHandler (object sender, SerialDataReceivedEventArgs e); public delegate void SerialPinChangedEventHandler (object sender, SerialPinChangedEventArgs e); public delegate void SerialErrorReceivedEventHandler (object sender, SerialErrorReceivedEventArgs e); }