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 { }
}
}
}
}