diff -r 7fdc9a71d314 -r 8ad140f3dd41 hti/PC_Tools/HTIGateway/HtiGateway/src/SerialComm.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hti/PC_Tools/HTIGateway/HtiGateway/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->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& 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_s(tmp, 256, "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_s(tmp, 256, "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_s(tmp, 256, "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_s(tmp, 256, "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_s(tmp, 256, "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 ::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 HtiGateway 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; + dcb.fDtrControl = DTR_CONTROL_DISABLE; + + if ( hwFlowControl ) + { + dcb.fOutxCtsFlow = TRUE; + dcb.fRtsControl = RTS_CONTROL_HANDSHAKE; + } + else + { + dcb.fOutxCtsFlow = FALSE; + dcb.fRtsControl = RTS_CONTROL_ENABLE; + } + + 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; + + 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) + { + 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 + LONG rxBytes = GetRxBytesAvailable(); + + 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) + { + char temp[64]; + sprintf_s(temp, 64, "Received %d bytes", reallyRead); + Util::Debug( temp ); + 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* in, + SafeQueue* 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_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); + Data* d = CREATE_CONTROL_MESSAGE("[HtiGateway]: Error sending data"); + 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 ) + { + 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