hti/PC_Tools/DataGateway/SRC/SerialComm.cpp
branchRCL_3
changeset 59 8ad140f3dd41
parent 0 a03f92240627
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hti/PC_Tools/DataGateway/SRC/SerialComm.cpp	Wed Oct 13 16:17:58 2010 +0300
@@ -0,0 +1,714 @@
+/*
+* Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). 
+* All rights reserved.
+* This component and the accompanying materials are made available
+* under the terms of "Eclipse Public License v1.0"
+* which accompanies this distribution, and is available
+* at the URL "http://www.eclipse.org/legal/epl-v10.html".
+*
+* Initial Contributors:
+* Nokia Corporation - initial contribution.
+*
+* Contributors:
+* 
+* Description:
+*   This file contains implementation of SerialComm communication channel plugin objects. These classes are SerialComm, SerialCommPlugin and
+*	SerialCommIoThread classes.
+*/
+
+// INCLUDES
+#include "SerialCommPlugin.h"
+#include "util.h"
+
+#define HIGH_COM_PORT "\\\\.\\"
+const static int g_SerialMaxResendNumber = 2;
+
+//**********************************************************************************
+// Class SerialCommPlugin
+//
+// This class implements a CommChannelPlugin used in serial communication with device
+//**********************************************************************************
+
+SerialCommPlugin::SerialCommPlugin(const CommChannelPluginObserver* observer)
+    : CommChannelPlugin(observer),
+      m_TxQueue(),
+      m_RxQueue(),
+      m_PropertySerialCommTimeout(g_SerialCommDefaultTimeout),
+	  m_PropertySerialMaxSendSize(0),
+	  m_ProperySerialSendPause(0),
+	  m_PropertySerialHwFlowControl(false)
+{
+    m_IoThread = NULL;
+}
+
+SerialCommPlugin::~SerialCommPlugin()
+{
+    Util::Debug("SerialCommPlugin::~SerialCommPlugin()");
+    if ( m_Open )
+    {
+        Close();
+    }
+    if ( m_IoThread != NULL )
+    {
+        delete m_IoThread;
+        m_IoThread = NULL;
+    }
+}
+
+/*
+ * This method initializes SerialCommPlugin and starts SerialCommPluginIoThread
+ */
+DWORD SerialCommPlugin::Init()
+{
+    Util::Debug("SerialCommPlugin::Init()");
+
+    std::string filename = SERIAL_INI_FILE_NAME;
+	//read properties from serialplugin.ini file
+	Util::ReadProperties(filename.c_str(), m_SerialCommPluginProperties);
+    
+	CheckProperties(m_SerialCommPluginProperties);
+
+    CommChannelPlugin::Init();
+
+    m_IoThread = new SerialCommIoThread(
+						&m_RxQueue,
+						&m_TxQueue,
+                        m_PropertySerialCommInitString,
+						m_PropertySerialCommPort,
+						m_PropertySerialCommTimeout,
+						m_PropertySerialHwFlowControl,
+						m_ProperySerialSendPause,
+						m_PropertySerialMaxSendSize);
+
+    m_IoThread->SetTimeout(m_PropertySerialCommTimeout);
+
+	m_IoThread->Start();
+
+	Event e = m_IoThread->GetOpenedEvent();
+    if ( e.Wait(m_PropertySerialCommTimeout) == WAIT_TIMEOUT )
+    {
+        Util::Debug("SerialCommPlugin::Init()   SerialComm opening timed out");
+        m_Open = false;
+        return ERR_DG_COMM_OPEN_TIMEOUT;
+    }
+    Util::Debug("SerialCommPlugin::Init() SerialComm opened");
+    m_Open = true;
+    Util::Debug("SerialCommPlugin::Init() OK");
+    return NO_ERRORS;
+}
+
+/*
+ * This method initializes class member variables from values in map
+ */
+void SerialCommPlugin::CheckProperties(map<string, string>& props)
+{
+    char tmp[256];
+
+    // Init values for connection
+    m_PropertySerialCommInitString = props[SERIAL_INI_INIT_PARAM];
+	Util::CheckCommandlineParam( PARAM_SWITCH_INIT_PARAM, m_PropertySerialCommInitString );
+    if ( m_PropertySerialCommInitString.empty() )
+    {
+        m_PropertySerialCommInitString = SERIAL_DEFAULT_INIT_STRING;
+    }
+    sprintf(tmp, "Connection Init : '%s'", m_PropertySerialCommInitString.c_str());
+    string s(tmp);
+    Util::Debug(s);
+
+    // comm port
+    m_PropertySerialCommPort = props[SERIAL_INI_COMPORT_PARAM];
+	Util::CheckCommandlineParam( PARAM_SWITCH_COMPORT_PARAM, m_PropertySerialCommPort );
+    if ( m_PropertySerialCommPort.empty() )
+    {
+        m_PropertySerialCommPort = SERIAL_DEFAULT_COMPORT;
+    }
+    sprintf(tmp, "Connection port : '%s'", m_PropertySerialCommPort.c_str());
+    s = tmp;
+    Util::Debug(s);
+
+    // Serial operations timeout
+    string val = props[SERIAL_INI_COMPORT_TIMEOUT];
+	Util::CheckCommandlineParam( PARAM_SWITCH_COMPORT_TIMEOUT, val );
+	m_PropertySerialCommTimeout = 0;
+    if ( !val.empty() )
+    {
+        m_PropertySerialCommTimeout = atol(val.c_str());
+    }
+
+    if ( m_PropertySerialCommTimeout <= 0 )
+    {
+        m_PropertySerialCommTimeout = g_SerialCommDefaultTimeout;
+    }
+	sprintf(tmp, "Timeout: '%d'", m_PropertySerialCommTimeout);
+    s = tmp;
+    Util::Debug(s);
+
+	// flow control
+	m_PropertySerialHwFlowControl = false;
+	val = props[SERIAL_INI_USE_HW_FLOW_CONTROL];
+	Util::CheckCommandlineParam( PARAM_SWITCH_USE_HW_FLOW_CONTROL, val );
+	if ( !val.empty() && val == "1" )
+	{
+		m_PropertySerialHwFlowControl = true;
+		Util::Debug("Use serial HW flow control");
+	}
+	else
+	{
+		Util::Debug("Do not use serial HW flow control");
+	}
+
+	// send pause
+	val = props[SERIAL_INI_SEND_SLEEP_PAUSE];
+	Util::CheckCommandlineParam( PARAM_SWITCH_SEND_SLEEP_PAUSE, val );
+    if ( !val.empty() )
+    {
+        m_ProperySerialSendPause = atol(val.c_str());
+    }
+    if ( m_ProperySerialSendPause <= 0 )
+    {
+        m_ProperySerialSendPause = g_SerialCommDefaultSendPause;
+    }
+	sprintf(tmp, "Send pause: '%d'", m_ProperySerialSendPause);
+    s = tmp;
+    Util::Debug(s);
+
+	// send size
+	val = props[SERIAL_INI_SEND_SIZE];
+	Util::CheckCommandlineParam( PARAM_SWITCH_SEND_SIZE, val );
+    if ( !val.empty() )
+    {
+        m_PropertySerialMaxSendSize = atol(val.c_str());
+    }
+    if ( m_PropertySerialMaxSendSize <= 0 )
+    {
+        m_PropertySerialMaxSendSize = 0;
+    }
+	sprintf(tmp, "Send max size: '%d'", m_PropertySerialMaxSendSize);
+    s = tmp;
+    Util::Debug(s);
+}
+
+/*
+ * This method checks if data is available on incoming queue
+ */
+bool SerialCommPlugin::IsDataAvailable()
+{
+    return ( !m_RxQueue.empty() );
+}
+
+/*
+ * This method is used to:
+ * -push given data to outgoing queue
+ * -wait for data to become available in incoming queue
+ * -pop the first Data object from queue and store it to the Data object given as parameter
+ */
+DWORD SerialCommPlugin::SendReceive(Data* data_in, Data** data_out, long timeout)
+{
+    DWORD res;
+    if ( ( res = Send(data_in, timeout)) == NO_ERRORS &&
+         ( res = ReceiveWait(data_out, timeout)) == NO_ERRORS )
+    {
+        return NO_ERRORS;
+    }
+    cout << "SerialCommPlugin::SendReceive: error" << endl;
+    return res;
+}
+
+/*
+ * This method pushes the given Data object(of type Data::EData) to outgoing queue
+ */
+DWORD SerialCommPlugin::Send(Data* data_in, long timeout)
+{
+    Data::DataType type = data_in->GetType();
+    if ( type == Data::EData )
+    {
+        DWORD length = data_in->GetLength();
+        m_TxQueue.push(data_in);
+        return NO_ERRORS;
+    }
+    else if ( type == Data::EControl )
+    {
+		Util::Debug("SerialCommPlugin::Send: Control Message Received");
+        return NO_ERRORS;
+    }
+    return ERR_DG_COMM_DATA_SEND;
+}
+
+/*
+ * This method is used to wait for data to become available in incoming queue 
+ * and then pop the first Data object from the queue and and store it to Data object given as parameter.
+ */
+DWORD SerialCommPlugin::ReceiveWait(Data** data_out, long timeout)
+{
+    long elapsed = 0;
+    while (elapsed < timeout && !IsDataAvailable())
+    {
+        elapsed += 25;
+        Sleep(25);
+    }
+    if ( elapsed >= timeout )
+    {
+        return ERR_DG_COMM_DATA_RECV_TIMEOUT;
+    }
+    return Receive(data_out, timeout);
+}
+
+/*
+ * This method is used to pop the first data object from incoming queue and store it to data object given as parameter
+ */
+DWORD SerialCommPlugin::Receive(Data** data_out, long timeout)
+{
+    if ( !m_RxQueue.empty() )
+    {
+        *data_out = m_RxQueue.front();
+        m_RxQueue.pop();
+        return NO_ERRORS;
+    }
+    return ERR_DG_COMM_DATA_RECV;
+}
+
+
+DWORD SerialCommPlugin::Open()
+{
+    return ( m_Open ? NO_ERRORS : ERR_DG_COMM_OPEN );
+}
+
+/*
+ * This method stops SerialCommIoThread and waits for it to be in signaled state
+ */
+DWORD SerialCommPlugin::Close()
+{
+    m_IoThread->Stop();
+    WaitForSingleObject( m_IoThread->ThreadHandle(), g_MaximumShutdownWaitTime );
+    return NO_ERRORS;
+}
+
+//**********************************************************************************
+// Class SerialComm
+//
+// This class implements the actual serial communication using Windows API
+//**********************************************************************************
+
+SerialComm::SerialComm()
+    : m_Open(false),
+	m_SendPause(0),
+	m_SendMaxSize(0)
+{
+
+}
+
+SerialComm::~SerialComm()
+{
+    Util::Debug("SerialComm::~SerialComm()");
+    if ( m_Open )
+    {
+        Close();
+    }
+}
+/*
+ * This method opens a communication device that uses serial communication and
+ * configures it according initialization parameters
+ */
+WORD SerialComm::Open( const string& commPort, const string& initParameters,
+					   long timeout,
+					   bool hwFlowControl, long sendPause, long sendMaxSize)
+{
+    Util::Debug("SerialComm::Open()");
+
+	m_SendPause = sendPause;
+
+	if ( sendMaxSize>0 )
+	{
+		m_SendMaxSize = sendMaxSize;
+	}
+
+	DWORD err;
+	//check commPort and add "\\.\" prefix if port is larger than 9
+	string localCommPort(commPort);
+	static const basic_string <char>::size_type npos = -1;
+	if ( localCommPort.find(HIGH_COM_PORT) == npos &&
+		 localCommPort.find("COM") == 0 &&
+		 localCommPort.length() > 4 //two digit port number
+	   )
+	{
+		localCommPort.insert(0, HIGH_COM_PORT);
+	}
+	Util::Debug(localCommPort.c_str());
+
+    // If COM-port opening fails, it's retried until DataGateway is killed.
+    m_Com = INVALID_HANDLE_VALUE;
+    while ( m_Com == INVALID_HANDLE_VALUE )
+    {
+	    // Open file (COM port)
+	    m_Com = CreateFile( localCommPort.c_str(),
+					    GENERIC_READ | GENERIC_WRITE,
+					    0, //excusive access
+					    NULL, //no security
+					    OPEN_EXISTING, //must use
+					    0, //not overlapped i/o
+					    NULL // must be NULL for comm
+					    );
+	    if ( m_Com == INVALID_HANDLE_VALUE )
+	    {
+		    err = GetLastError();
+            Util::Error("[SerialComm] Failed to open COM port", err);
+            Util::Info("[SerialComm] Retrying...");
+            Sleep(5000);
+        }
+    }
+
+    // Serial port cfg
+	Util::Debug(initParameters.c_str());
+	DCB dcb;
+
+	FillMemory(&dcb, sizeof(dcb), 0);
+	dcb.DCBlength = sizeof(dcb);
+	if ( !BuildCommDCB(initParameters.c_str(), &dcb) )
+	{
+		err = GetLastError();
+		Util::Error("[SerialComm] Failed to build port settings ", err);
+		return ERR_DG_COM_INIT;
+	}
+
+	dcb.fBinary = TRUE ; 
+
+	//set hw handshaking
+	dcb.fOutX  = FALSE;
+    dcb.fInX   = FALSE;
+    dcb.fNull   = FALSE;
+    dcb.fAbortOnError = TRUE;
+	
+	dcb.fOutxDsrFlow = FALSE;
+	dcb.fDtrControl = DTR_CONTROL_DISABLE;
+	dcb.fDsrSensitivity= FALSE;
+
+	if ( hwFlowControl )
+	{
+		dcb.fOutxCtsFlow = TRUE;
+		dcb.fRtsControl = RTS_CONTROL_HANDSHAKE;
+	}
+	else
+	{
+		dcb.fOutxCtsFlow = FALSE;
+		dcb.fRtsControl = RTS_CONTROL_ENABLE;
+	}
+    //Configure communication device according to the specifications in a device-control block (a DCB structure)
+	if ( !SetCommState(m_Com, &dcb) )
+	{
+		err = GetLastError();
+		Util::Error("[SerialComm] Failed to set port settings ", err);
+		return ERR_DG_COM_INIT;
+	}
+
+	COMMTIMEOUTS timeouts;
+
+	timeouts.ReadIntervalTimeout = MAXDWORD; 
+
+	timeouts.ReadTotalTimeoutMultiplier = timeout;
+	timeouts.ReadTotalTimeoutConstant = 1;
+	timeouts.WriteTotalTimeoutMultiplier = 1;
+	timeouts.WriteTotalTimeoutConstant = timeout;
+
+	//Set the time-out parameters for all read and write operations on communications device
+	if ( !SetCommTimeouts(m_Com, &timeouts) )
+	{
+		// Error setting time-outs.
+		err = GetLastError();
+		Util::Error("[SerialComm] Failed to set port timeouts ", err);
+		return ERR_DG_COM_INIT;
+	}
+
+	m_Open = true;
+    Util::Debug("SerialComm::Open() OK");
+    Util::Info("[SerialComm] COM port open");
+    return NO_ERRORS;
+}
+
+/*
+ * This method is used to send Data object using serial communication
+ */
+DWORD SerialComm::Send(const Data& data_in)
+{
+    Util::Debug("SerialComm::Send()");
+
+	if ( data_in.GetLength() > 0 )
+	{
+		DWORD sentBytes = 0;
+		void* data = data_in.GetData();
+		int numOfTries = 0;
+		//loop until whole Data object is sent
+		while ( sentBytes < data_in.GetLength() )
+		{
+			//Util::Debug("while");
+			DWORD sent;
+			BOOL r;
+			if ( m_SendMaxSize>0 )
+			{
+				//Write data to the communication device
+				r = WriteFile(m_Com,
+						((char*)data) + sentBytes,
+						//data_in.GetLength() - sentBytes,
+						min(data_in.GetLength() - sentBytes, m_SendMaxSize),
+						&sent,
+						NULL);
+			}
+			else
+			{
+				r = WriteFile(m_Com,
+						((char*)data) + sentBytes,
+						data_in.GetLength() - sentBytes,
+						&sent,
+						NULL);
+			}
+
+			if ( r && sent > 0 )
+			{
+				if (Util::GetVerboseLevel() == Util::debug)
+				{
+					printf("Send %d bytes\r\n", sent);
+					Util::Hex(((char*)data) + sentBytes, min(sent,16));
+				}
+				sentBytes+=sent;
+			}
+			else
+			{
+				if ( !r )
+				{
+					DWORD err = GetLastError();
+					Util::Error("[SerialComm] Send error", err);
+				}
+				else
+				{
+					Util::Error("[SerialComm] Send failed");
+				}
+				if ( numOfTries++==g_SerialMaxResendNumber )
+				{
+					return ERR_DG_COMM_DATA_RECV;
+				}
+			}
+			//used to "solve" overflow problem
+			if ( m_SendPause > 0 )
+			{
+				Sleep(m_SendPause);
+			}
+		}
+	}
+
+    Util::Debug("SerialComm::Send() OK");
+    return NO_ERRORS;
+}
+
+/*
+ * This method reads all available data from communication media 
+ * and returns it in the Data object given as parameter
+ */
+DWORD SerialComm::Receive(Data* data_out)
+{
+    Util::Debug("SerialComm::Receive()");
+
+	//get available bytes in incoming communication channel
+	DWORD rxBytes = GetRxBytesAvailable();
+	//printf("Rx bytes: %d \r\n", rxBytes);
+
+	if ( rxBytes > 0 )
+	{
+		char* data = new char[rxBytes];
+		DWORD readBytes = 0;
+		//read data until all available bytes have been red
+		do
+		{
+			DWORD reallyRead;
+			if ( ReadFile(m_Com,
+					 data + readBytes,
+					 rxBytes - readBytes,
+					 &reallyRead,
+					 NULL) )
+			{
+				if (Util::GetVerboseLevel() == Util::debug)
+				{
+					printf("Received %d bytes\r\n", reallyRead);
+					Util::Hex(data + readBytes, min(16,reallyRead));
+				}
+				readBytes += reallyRead;
+			}
+			else
+			{
+				delete[] data;
+				DWORD err = GetLastError();
+				Util::Error("[SerialComm] Error %d", err);
+				return ERR_DG_COMM_DATA_RECV;
+			}
+			if ( readBytes > rxBytes )
+			{
+				Util::Error("[SerialComm] !!! readBytes > rxBytes !!!");
+			}
+		}
+		while( readBytes < rxBytes );
+
+		data_out->SetData( data, rxBytes, Data::EData);
+		delete[] data;
+	}
+	else if ( rxBytes<0 ) //err
+	{
+        return ERR_DG_COMM_DATA_RECV;
+	}
+	return NO_ERRORS;
+}
+
+/*
+ * This method returns number of bytes available in incoming queue or
+ * -1 if communication error has occurred
+ */
+LONG SerialComm::GetRxBytesAvailable()
+{
+    COMSTAT res;
+	DWORD err;
+
+	if ( ClearCommError(m_Com, &err, &res ) )
+	{
+		return res.cbInQue;
+	}
+	else
+    {
+		DWORD err = GetLastError();
+        Util::Error("[SerialComm] Error getting Rx bytes %d", err);
+        return -1;
+    }
+}
+
+DWORD SerialComm::Close()
+{
+    Util::Debug("SerialComm::Close()");
+    m_Open = false;
+
+	if ( m_Com != INVALID_HANDLE_VALUE )
+	{
+		CloseHandle(m_Com);
+		m_Com = INVALID_HANDLE_VALUE;
+	}
+
+    Util::Debug("SerialComm::Close() OK");
+    return NO_ERRORS;
+}
+
+//**********************************************************************************
+// Class SerialCommIoThread
+//
+// This thread is used to send data from outgoing queue to serial port
+// and read bytes from serial port and push it to incoming queue 
+//**********************************************************************************
+
+SerialCommIoThread::SerialCommIoThread(SafeQueue<Data*>* in,
+                           SafeQueue<Data*>* out,
+                           const string& init,  const string& commPort, long timeout,
+						   bool hwFlowControl, long sendPause, long sendMaxSize)
+    : m_InitString(init),
+	  m_CommPort( commPort ),
+	  m_PropertySerialCommTimeout( timeout ),
+	  m_PropertySerialHwFlowControl( hwFlowControl ),
+	  m_ProperySerialSendPause(sendPause),
+	  m_PropertySerialMaxSendSize(sendMaxSize)
+{
+    m_InQueue = in;
+    m_OutQueue = out;
+    m_Running = true;
+    m_PropertySerialCommTimeout = g_SerialCommDefaultTimeout;
+    m_OpenedEvent.Reset();
+}
+
+/*
+ * Main execution loop which is used to send Data from outgoing queue to serial port
+ * and read bytes from serial port, encapsulate it to Data objects and push them to incoming queue 
+ */
+void SerialCommIoThread::Run()
+{
+    while ( m_Running )
+    {
+	    Util::Debug("SerialCommIoThread::Run()");
+        SerialComm m_SerialComm;
+
+        if ( m_SerialComm.Open(m_CommPort, m_InitString, m_PropertySerialCommTimeout,
+							    m_PropertySerialHwFlowControl, m_ProperySerialSendPause,
+							    m_PropertySerialMaxSendSize) != NO_ERRORS )
+        {
+            m_Running = false;
+            return;
+        }
+        m_OpenedEvent.Set();
+
+	    Util::Debug("SerialCommIoThread::Run() start loop");
+        while ( m_Running )
+        {
+            // Sending data
+            try
+            {
+				Data* out = m_OutQueue->front(30);
+                if ( m_SerialComm.Send(*out) != NO_ERRORS )
+                {
+                    Data* d = new Data("Error sending data", 19, Data::EControl);
+                    m_InQueue->push(d);
+                    d = NULL;
+                }
+                m_OutQueue->pop();
+                delete out;
+                out = NULL;
+            } catch (TimeoutException te) {}
+
+		    // Receiving data
+		    LONG rxBytes = 0;
+            while ( ( rxBytes = m_SerialComm.GetRxBytesAvailable() ) > 0 )
+            {
+			    //printf("rx %d\r\n", rxBytes);
+                Data* in = new Data;
+                if ( m_SerialComm.Receive(in) == NO_ERRORS )
+                {
+				    m_InQueue->push(in);
+				    in = NULL;
+                }
+                else
+                {
+                    delete in;
+                    in = NULL;
+                }
+            }
+            if ( rxBytes < 0 )
+            {
+                Util::Info("[SerialCommIoThread] COM-port lost. Trying to close & reopen");
+                break; // break out but keep the outermost while loop running
+            }
+        }
+        m_OpenedEvent.Reset();
+        m_SerialComm.Close();
+    }
+    Util::Debug("SerialCommIoThread::Run() exiting");
+}
+
+void SerialCommIoThread::Stop()
+{
+    Util::Debug("SerialCommIoThread::Stop()");
+    m_Running = false;
+}
+
+Event SerialCommIoThread::GetOpenedEvent()
+{
+    return m_OpenedEvent;
+}
+
+void SerialCommIoThread::SetTimeout(long timeout)
+{
+    if ( timeout > 0 )
+    {
+        m_PropertySerialCommTimeout = timeout;
+    }
+    else
+    {
+        m_PropertySerialCommTimeout = g_SerialCommDefaultTimeout;
+    }
+}
+
+long SerialCommIoThread::GetTimeout() const
+{
+    return m_PropertySerialCommTimeout;
+}
+
+// End of the file
\ No newline at end of file