//******************************************************************************************************
// SerialClient.cs - Gbtc
//
// Copyright © 2012, Grid Protection Alliance. All Rights Reserved.
//
// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
// the NOTICE file distributed with this work for additional information regarding copyright ownership.
// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may
// not use this file except in compliance with the License. You may obtain a copy of the License at:
//
// http://www.opensource.org/licenses/MIT
//
// Unless agreed to in writing, the subject software distributed under the License is distributed on an
// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
// License for the specific language governing permissions and limitations.
//
// Code Modification History:
// ----------------------------------------------------------------------------------------------------
// 07/24/2006 - Pinal C. Patel
// Original version of source code generated.
// 09/06/2006 - J. Ritchie Carroll
// Added bypass optimizations for high-speed serial port data access.
// 09/29/2008 - J. Ritchie Carroll
// Converted to C#.
// 07/08/2009 - J. Ritchie Carroll
// Added WaitHandle return value from asynchronous connection.
// 07/15/2009 - Pinal C. Patel
// Modified Disconnect() to add error checking.
// 09/14/2009 - Stephen C. Wills
// Added new header and license agreement.
// 11/29/2010 - Pinal C. Patel
// Corrected the implementation of ConnectAsync() method.
// 09/21/2011 - J. Ritchie Carroll
// Added Mono implementation exception regions.
// 12/13/2012 - Starlynn Danyelle Gilliam
// Modified Header.
//
//******************************************************************************************************
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO.Ports;
using System.Threading;
namespace GSF.Communication
{
///
/// Represents a communication client based on .
///
///
/// This example shows how to use for communicating with :
///
/// using System;
/// using GSF;
/// using GSF.Communication;
///
/// class Program
/// {
/// static SerialClient s_client;
///
/// static void Main(string[] args)
/// {
/// // Initialize the client.
/// s_client = new SerialClient("Port=COM1; BaudRate=9600; Parity=None; StopBits=One; DataBits=8; DtrEnable=False; RtsEnable=False");
/// s_client.Initialize();
/// // Register event handlers.
/// s_client.ConnectionAttempt += s_client_ConnectionAttempt;
/// s_client.ConnectionEstablished += s_client_ConnectionEstablished;
/// s_client.ConnectionTerminated += s_client_ConnectionTerminated;
/// s_client.SendDataComplete += s_client_SendDataComplete;
/// s_client.ReceiveDataComplete += s_client_ReceiveDataComplete;
/// // Connect the client.
/// s_client.Connect();
///
/// // Write user input to the serial port.
/// string input;
/// while (string.Compare(input = Console.ReadLine(), "Exit", true) != 0)
/// {
/// s_client.Send(input);
/// }
///
/// // Disconnect the client on shutdown.
/// s_client.Dispose();
/// }
///
/// static void s_client_ConnectionAttempt(object sender, EventArgs e)
/// {
/// Console.WriteLine("Client is connecting to serial port.");
/// }
///
/// static void s_client_ConnectionEstablished(object sender, EventArgs e)
/// {
/// Console.WriteLine("Client connected to serial port.");
/// }
///
/// static void s_client_ConnectionTerminated(object sender, EventArgs e)
/// {
/// Console.WriteLine("Client disconnected from serial port.");
/// }
///
/// static void s_client_SendDataComplete(object sender, EventArgs e)
/// {
/// Console.WriteLine(string.Format("Sent data - {0}", s_client.TextEncoding.GetString(s_client.Client.SendBuffer)));
/// }
///
/// static void s_client_ReceiveDataComplete(object sender, EventArgs<byte[], int> e)
/// {
/// Console.WriteLine(string.Format("Received data - {0}", s_client.TextEncoding.GetString(e.Argument1, 0, e.Argument2)));
/// }
/// }
///
///
public class SerialClient : ClientBase
{
#region [ Members ]
// Constants
///
/// Specifies the default value for the property.
///
public const string DefaultConnectionString = "Port=COM1; BaudRate=9600; Parity=None; StopBits=One; DataBits=8; DtrEnable=False; RtsEnable=False";
// Fields
private readonly TransportProvider m_serialClient;
private Dictionary m_connectData;
private int m_receivedBytesThreshold;
private ManualResetEvent m_connectionHandle;
#if ThreadTracking
private ManagedThread m_connectionThread;
#else
private Thread m_connectionThread;
#endif
private bool m_disposed;
#endregion
#region [ Constructors ]
///
/// Initializes a new instance of the class.
///
public SerialClient()
: this(DefaultConnectionString)
{
}
///
/// Initializes a new instance of the class.
///
/// Connect string of the . See for format.
public SerialClient(string connectString)
: base(TransportProtocol.Serial, connectString)
{
m_serialClient = new TransportProvider();
m_receivedBytesThreshold = 1;
}
///
/// Initializes a new instance of the class.
///
/// object that contains the .
public SerialClient(IContainer container)
: this()
{
if (container != null)
container.Add(this);
}
#endregion
#region [ Properties ]
///
/// Gets the object for the .
///
[Browsable(false)]
public SerialPort Client
{
get
{
return m_serialClient.Provider;
}
}
///
/// Gets the server URI of the .
///
[Browsable(false)]
public override string ServerUri
{
get
{
return string.Format("{0}://{1}", TransportProtocol, m_connectData["port"]).ToLower();
}
}
///
/// Gets or sets the needed number of bytes in the internal input buffer before a event occurs.
///
///
/// This option is ignored under Mono deployments.
///
public int ReceivedBytesThreshold
{
get
{
return m_receivedBytesThreshold;
}
set
{
m_receivedBytesThreshold = value;
}
}
#endregion
#region [ Methods ]
///
/// Reads a number of bytes from the current received data buffer and writes those bytes into a byte array at the specified offset.
///
/// Destination buffer used to hold copied bytes.
/// 0-based starting index into destination to begin writing data.
/// The number of bytes to read from current received data buffer and write into .
/// The number of bytes read.
///
/// This function should only be called from within the event handler. Calling this method outside
/// this event will have unexpected results.
///
/// No received data buffer has been defined to read.
/// is null.
///
/// or is less than 0 -or-
/// and will exceed length.
///
public override int Read(byte[] buffer, int startIndex, int length)
{
buffer.ValidateParameters(startIndex, length);
if ((object)m_serialClient.ReceiveBuffer != null)
{
int sourceLength = m_serialClient.BytesReceived - ReadIndex;
int readBytes = length > sourceLength ? sourceLength : length;
Buffer.BlockCopy(m_serialClient.ReceiveBuffer, ReadIndex, buffer, startIndex, readBytes);
// Update read index for next call
ReadIndex += readBytes;
if (ReadIndex >= m_serialClient.BytesReceived)
ReadIndex = 0;
return readBytes;
}
throw new InvalidOperationException("No received data buffer has been defined to read.");
}
///
/// Disconnects the from the .
///
public override void Disconnect()
{
if (CurrentState != ClientState.Disconnected)
{
if (m_serialClient.Provider != null)
{
m_serialClient.Provider.DataReceived -= SerialPort_DataReceived;
m_serialClient.Provider.ErrorReceived -= SerialPort_ErrorReceived;
}
m_serialClient.Reset();
if (m_connectionThread != null)
m_connectionThread.Abort();
OnConnectionTerminated();
}
}
///
/// Connects the to the asynchronously.
///
/// Attempt is made to connect the when it is connected.
/// for the asynchronous operation.
public override WaitHandle ConnectAsync()
{
m_connectionHandle = (ManualResetEvent)base.ConnectAsync();
m_serialClient.SetReceiveBuffer(ReceiveBufferSize);
m_serialClient.Provider = new SerialPort();
#if !MONO
m_serialClient.Provider.ReceivedBytesThreshold = m_receivedBytesThreshold;
#endif
m_serialClient.Provider.DataReceived += SerialPort_DataReceived;
m_serialClient.Provider.ErrorReceived += SerialPort_ErrorReceived;
m_serialClient.Provider.PortName = m_connectData["port"];
m_serialClient.Provider.BaudRate = int.Parse(m_connectData["baudrate"]);
m_serialClient.Provider.DataBits = int.Parse(m_connectData["databits"]);
m_serialClient.Provider.Parity = (Parity)(Enum.Parse(typeof(Parity), m_connectData["parity"], true));
m_serialClient.Provider.StopBits = (StopBits)(Enum.Parse(typeof(StopBits), m_connectData["stopbits"], true));
if (m_connectData.ContainsKey("dtrenable"))
m_serialClient.Provider.DtrEnable = m_connectData["dtrenable"].ParseBoolean();
if (m_connectData.ContainsKey("rtsenable"))
m_serialClient.Provider.RtsEnable = m_connectData["rtsenable"].ParseBoolean();
#if ThreadTracking
m_connectionThread = new ManagedThread(OpenPort);
m_connectionThread.Name = "GSF.Communication.SerialClient.OpenPort()";
#else
m_connectionThread = new Thread(OpenPort);
#endif
m_connectionThread.Start();
return m_connectionHandle;
}
///
/// Releases the unmanaged resources used by the and optionally releases the managed resources.
///
/// true to release both managed and unmanaged resources; false to release only unmanaged resources.
protected override void Dispose(bool disposing)
{
if (!m_disposed)
{
try
{
// This will be done regardless of whether the object is finalized or disposed.
if (disposing)
{
// This will be done only when the object is disposed by calling Dispose().
if (m_connectionHandle != null)
m_connectionHandle.Dispose();
}
}
finally
{
m_disposed = true; // Prevent duplicate dispose.
base.Dispose(disposing); // Call base class Dispose().
}
}
}
///
/// Validates the specified .
///
/// Connection string to be validated.
/// Port property is missing.
/// BaudRate property is missing.
/// Parity property is missing.
/// StopBits property is missing.
/// DataBits property is missing.
protected override void ValidateConnectionString(string connectionString)
{
m_connectData = connectionString.ParseKeyValuePairs();
if (!m_connectData.ContainsKey("port"))
throw new ArgumentException(string.Format("Port property is missing (Example: {0})", DefaultConnectionString));
if (!m_connectData.ContainsKey("baudrate"))
throw new ArgumentException(string.Format("BaudRate property is missing (Example: {0})", DefaultConnectionString));
if (!m_connectData.ContainsKey("parity"))
throw new ArgumentException(string.Format("Parity property is missing (Example: {0})", DefaultConnectionString));
if (!m_connectData.ContainsKey("stopbits"))
throw new ArgumentException(string.Format("StopBits property is missing (Example: {0})", DefaultConnectionString));
if (!m_connectData.ContainsKey("databits"))
throw new ArgumentException(string.Format("DataBits property is missing (Example: {0})", DefaultConnectionString));
}
///
/// Sends (writes) data to the asynchronously.
///
/// The buffer that contains the binary data to be sent (written).
/// The zero-based position in the at which to begin sending (writing) data.
/// The number of bytes to be sent (written) from starting at the .
/// for the asynchronous operation.
protected override WaitHandle SendDataAsync(byte[] data, int offset, int length)
{
WaitHandle handle;
// Send data to the file asynchronously.
handle = m_serialClient.Provider.BaseStream.BeginWrite(data, offset, length, SendDataAsyncCallback, null).AsyncWaitHandle;
// Notify that the send operation has started.
m_serialClient.Statistics.UpdateBytesSent(length);
OnSendDataStart();
// Return the async handle that can be used to wait for the async operation to complete.
return handle;
}
///
/// Callback method for asynchronous send operation.
///
private void SendDataAsyncCallback(IAsyncResult asyncResult)
{
try
{
// Send operation is complete.
m_serialClient.Provider.BaseStream.EndWrite(asyncResult);
OnSendDataComplete();
}
catch (Exception ex)
{
// Send operation failed to complete - don't raised exceptions for a closed port
if (!m_disposed && m_serialClient.Provider != null && m_serialClient.Provider.IsOpen)
OnSendDataException(ex);
}
}
///
/// Connects to the .
///
private void OpenPort()
{
int connectionAttempts = 0;
while (MaxConnectionAttempts == -1 || connectionAttempts < MaxConnectionAttempts)
{
try
{
OnConnectionAttempt();
m_serialClient.Provider.Open();
m_connectionHandle.Set();
OnConnectionEstablished();
break;
}
catch (ThreadAbortException)
{
break;
}
catch (Exception ex)
{
Thread.Sleep(1000);
connectionAttempts++;
OnConnectionException(ex);
}
}
}
///
/// Receive (read) data from the (.NET serial port class raises this event when data is available).
///
private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
try
{
int bytesRead = 0;
while (bytesRead < m_serialClient.Provider.BytesToRead)
{
// Retrieve data from the port.
bytesRead += m_serialClient.Provider.Read(m_serialClient.ReceiveBuffer, bytesRead, m_serialClient.ReceiveBuffer.Length - bytesRead);
}
m_serialClient.BytesReceived = bytesRead;
m_serialClient.Statistics.UpdateBytesReceived(bytesRead);
// Notify of the retrieved data.
OnReceiveDataComplete(m_serialClient.ReceiveBuffer, bytesRead);
}
catch (Exception ex)
{
// Don't raised exceptions for a closed port
if (!m_disposed && m_serialClient.Provider != null && m_serialClient.Provider.IsOpen)
OnReceiveDataException(ex);
}
}
///
/// Receive (read) error data from the (.NET serial port class raises this event when error occurs).
///
private void SerialPort_ErrorReceived(object sender, SerialErrorReceivedEventArgs e)
{
switch (e.EventType)
{
case SerialError.Frame:
OnReceiveDataException(new ApplicationException("The hardware detected a framing error."));
break;
case SerialError.Overrun:
OnReceiveDataException(new ApplicationException("A character-buffer overrun has occurred. The next character is lost."));
break;
case SerialError.RXOver:
OnReceiveDataException(new ApplicationException("An input buffer overflow has occurred. There is either no room in the input buffer, or a character was received after the end-of-file (EOF) character."));
break;
case SerialError.RXParity:
OnReceiveDataException(new ApplicationException("The hardware detected a parity error."));
break;
case SerialError.TXFull:
OnReceiveDataException(new ApplicationException("The application tried to transmit a character, but the output buffer was full."));
break;
}
}
///
/// Raises the event.
///
/// Exception to send to event.
protected override void OnConnectionException(Exception ex)
{
base.Disconnect();
base.OnConnectionException(ex);
}
#endregion
}
}