using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.IO.Ports; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using Tango.Core.Threading; using Tango.Logging; namespace Tango.Transport.Adapters { /// /// Represents an which communicates over USB serial port. /// /// public class UsbTransportAdapter : TransportAdapterBase { private SerialPort _serialPort; //Serial port instance used to communicate over the serial port. private const int MAX_EXPECTED_SIZE = 50000; /// /// Gets or sets the baud rate. /// public UsbSerialBaudRates BaudRate { get; set; } /// /// Initializes a new instance of the class. /// public UsbTransportAdapter() : base() { BaudRate = UsbSerialBaudRates.BR_9600; Address = "COM1"; ComponentName = $"USB Adapter {_component_counter++}"; } /// /// Initializes a new instance of the class. /// /// The COM. public UsbTransportAdapter(String portName) : this() { Address = portName; } /// /// Initializes a new instance of the class. /// /// Name of the port. /// The baud rate. public UsbTransportAdapter(String portName, UsbSerialBaudRates baudRate) : this(portName) { BaudRate = baudRate; } /// /// Connects the transport component. /// /// public override Task Connect() { ThrowIfDisposed(); TaskCompletionSource source = new TaskCompletionSource(); if (State != TransportComponentState.Connected) { ThreadFactory.StartNew(() => { try { LogManager.Log("Connecting USB adapter on port " + Address + "..."); if (_serialPort != null) { _serialPort.DataReceived -= OnSerialPortDataReceived; } _serialPort = new SerialPort(); _serialPort.BaudRate = BaudRate.ToInt32(); _serialPort.PortName = Address; _serialPort.ReadBufferSize = MAX_BUFFER_SIZE; _serialPort.WriteBufferSize = MAX_BUFFER_SIZE; _serialPort.Open(); _serialPort.DiscardInBuffer(); _serialPort.DiscardOutBuffer(); _serialPort.DataReceived += OnSerialPortDataReceived; LogManager.Log($"USB adapter ({Address}) Connected..."); State = TransportComponentState.Connected; if (!source.Task.IsCompleted) { source.SetResult(true); } } catch (Exception ex) { if (!source.Task.IsCompleted) { source.SetException(LogManager.Log(ex, "Could not open serial port on " + Address + ".")); } } }); TimeoutTask.StartNew(() => { if (!source.Task.IsCompleted) { source.SetException(LogManager.Log(new IOException("The serial port seems to be in a froze state. Reinitialize the port and try again."))); } }, TimeSpan.FromSeconds(5)); } else { source.SetResult(true); } return source.Task; } /// /// Disconnects the transport component. /// /// public override Task Disconnect() { ThrowIfDisposed(); TaskCompletionSource source = new TaskCompletionSource(); if (State == TransportComponentState.Connected) { ThreadFactory.StartNew(() => { try { LogManager.Log("Disconnecting USB adapter on port " + Address + "..."); if (_serialPort != null) { _serialPort.DataReceived -= OnSerialPortDataReceived; } try { //_serialPort.DiscardOutBuffer(); //_serialPort.DiscardInBuffer(); _serialPort.Close(); _serialPort.Dispose(); _serialPort.DataReceived -= OnSerialPortDataReceived; LogManager.Log("USB adapter disconnected."); State = TransportComponentState.Disconnected; } catch (Exception ex) { LogManager.Log(ex, "Could not close serial port on " + Address + "."); } } catch (Exception ex) { LogManager.Log(ex, "Could not close serial port on " + Address + "."); } if (!source.Task.IsCompleted) { source.SetResult(true); } }); TimeoutTask.StartNew(() => { if (!source.Task.IsCompleted) { LogManager.Log(new IOException("The serial port seems to be in a froze state. Reinitialize the port and try again.")); State = TransportComponentState.Disconnected; source.SetResult(true); } }, TimeSpan.FromSeconds(5)); } else { source.SetResult(true); } return source.Task; } /// /// Writes the specified data to the stream. /// /// The data. /// Writes the data as soon as possible while ignoring any message queuing and batching. public override void Write(byte[] data, bool immidiate = false) { ThrowIfDisposed(); try { data = PostProcessBuffer(data); _serialPort.Write(data, 0, data.Length); } catch (Exception ex) { OnFailed(LogManager.Log(ex, $"Error writing to USB adapter ({Address}).")); } } /// /// Called when internal serial port has received data. /// /// The sender. /// The instance containing the event data. protected virtual void OnSerialPortDataReceived(object sender, SerialDataReceivedEventArgs e) { try { if (e.EventType == SerialData.Eof) { return; } if (_serialPort.BytesToRead > 4) { byte[] size = new byte[4]; _serialPort.Read(size, 0, size.Length); int expectedSize = BitConverter.ToInt32(size, 0); if (expectedSize > MAX_EXPECTED_SIZE || expectedSize < 1) { LogManager.Log($"Invalid expected size received on USB adapter ({expectedSize} bytes). Discarding buffers...", LogCategory.Warning); byte[] falseData = new byte[_serialPort.BytesToRead]; _serialPort.Read(falseData, 0, falseData.Length); try { _serialPort.DiscardInBuffer(); _serialPort.DiscardOutBuffer(); } catch { } return; } byte[] data = new byte[expectedSize]; int read = 0; while (read < expectedSize) { read += _serialPort.Read(data, read, Math.Min(_serialPort.BytesToRead, expectedSize - read)); if (State != TransportComponentState.Connected) { if (_serialPort != null) { _serialPort.DataReceived -= OnSerialPortDataReceived; } return; } } OnDataAvailable(data); } } catch (Exception ex) { LogManager.Log(ex, $"Error occurred while trying to read from the serial port ({Address})."); } } /// /// Finalizes an instance of the class. /// ~UsbTransportAdapter() { if (_serialPort != null) { try { _serialPort.Close(); _serialPort.Dispose(); } catch { } } } } }