#include "Brooks0254Port.hh" unsigned Brooks0254Port::bpDebFlag = 0; //! The three parameters below are tuned to work well with Brooks0254 serial //! interface. Reducing them can cause problems and result in unstable //! settings in the Brooks module. Increasing them would mean slower //! exchange of information between the driver and Brooks0254 module // unsigned Brooks0254Port::bpReadbackTimeoutCount = 1<<17; //! Max character buffer length unsigned Brooks0254Port::bpReadbackTimeoutCount = 1<<14; //! Max character buffer length unsigned Brooks0254Port::bpReadDelay = 400; //! Wait 0.5 msec before readding //! the next character unsigned Brooks0254Port::bpWriteDelay = 10000; //! Wait 10 msec after sending //! before reading the response //! Time to transfer a character in usec. It is set to 9600 baud rate double Brooks0254Port::bpCharTime = 2 * 1.0e+06 / 9600.; unsigned Brooks0254Port::bpResponseLine = 0; //! Response is on this line long Brooks0254Port::bpMaxReadAttempt = 1; //! Quit after this many attempts to read int const Brooks0254Port::bpSuccess = 0; int const Brooks0254Port::bpFail = 3; int const Brooks0254Port::bpError = -1; pthread_mutex_t Brooks0254Port::bpGlobMutex; pthread_mutexattr_t Brooks0254Port::bpGlobMtxAttr; int bpDummyInt = Brooks0254Port::InitGlobalMutex(); //! Initilize the static map of all serial ports map Brooks0254Port::bpPortMap; //! Constructor for the port object. It makes sure that we do not //! have a port for that device and calls Init method to initilize the //! port for use with Brooks2054 module Brooks0254Port::Brooks0254Port( int portID, string devName ) : bpID( portID ), bpDev( devName ) { if( bpDebFlag > 1 ) cout << "Starting Brooks0254Port constructor function" << endl; //! Create Semaphore if ( sem_init( &bpSemPos, 0, 1) < 0 ) { stringstream errMsg; errMsg << "Brooks0254Port::Init(): Could not create binary semaphore " << endl; throw FailedOpenPort( errMsg.str() ); } MtxLock globLock( bpGlobMutex ); map::iterator portItr = bpPortMap.find( bpDev ); bool portExists = ( portItr != bpPortMap.end() ); if( portExists ) { stringstream errMsg; errMsg << "Brooks0254Port(): Device " << devName << " was already open as port # " << portItr->second << endl; throw FailedOpenPort( errMsg.str() ); } //! Open and Initialize the serial port Init(); //! Create an entry in the port-map hash table bpPortMap[devName] = portID; if( bpDebFlag > 1 ) cout << "Finished Brooks0254Port constructor function" << endl; return; } //! Open serial port, initialize it using terminal control structure //! and POSIX control functions void Brooks0254Port::Init() { SemLock sLock( bpSemPos ); //! Open Serial Port in read/write mode, no control terminal //! and no delay for DCD signal bpFD = open( bpDev.c_str(), O_RDWR | O_NOCTTY | O_NDELAY ); if ( bpFD == bpError ) { //! On error condition for opening the serial port device file //! throw and exceptio stringstream errMsg; errMsg << "Brooks0254Port::Init(): Could not open " << bpDev << endl; throw FailedOpenPort( errMsg.str() ); } if( bpDebFlag > 1 ) cout << "Brooks0254Port::Init(): serial port " << bpID << " open... FD: " << bpFD << endl; //! set serial port in raw mode and wait-for-character mode fcntl( bpFD, F_SETFL, FNDELAY ); //! Get the termianl attributes into bpOpt tcgetattr( bpFD, &bpOpt ); //! Set Baud Rate to 9600 cfsetispeed( &bpOpt, B9600 ); cfsetospeed( &bpOpt, B9600 ); //! enable receiver and set local mode bpOpt.c_cflag |= (CLOCAL | CREAD); //! enable raw mode output and input bpOpt.c_oflag &= ~OPOST; bpOpt.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); //! set min char read and max timeout (5 sec) bpOpt.c_cc[VMIN] = 0; bpOpt.c_cc[VTIME] = 50; //! set to 8N1 bpOpt.c_cflag &= ~PARENB; bpOpt.c_cflag &= ~CSTOPB; bpOpt.c_cflag &= ~CSIZE; bpOpt.c_cflag |= CS8; //! disable flow control bpOpt.c_cflag &= ~CRTSCTS; bpOpt.c_iflag &= ~(IXON | IXOFF | IXANY); // bpOpt.c_cflag &= ~CRTSCTS; // bpOpt.c_iflag |= (IXON | IXOFF | IXANY); //! Flush I/O Port // tcsetattr( bpFD, TCSANOW, &bpOpt ); tcsetattr( bpFD, TCSAFLUSH, &bpOpt ); return; } //! Destructor Brooks0254Port::~Brooks0254Port() { cout << "Starting ~Brooks0254Port() destructor" << endl; ClosePort(); if( true ) { MtxLock globLock( bpGlobMutex ); sem_destroy( &bpSemPos ); bpPortMap.erase( bpDev ); } cout << "Exiting ~Brooks0254Port() destructor" << endl; return ; } //! This method resets the serial port. It closes the port, reopens //! and initilizes by calling Init method. void Brooks0254Port::Reset() { cout << "Resetting port #" << bpID << endl; this->ClosePort(); this->Init(); return; } //! Method to close the connection to serial device void Brooks0254Port::ClosePort() { SemLock sLock( bpSemPos ); tcflush( bpFD, TCIOFLUSH) ; usleep( bpWriteDelay ); int errFlag = close( bpFD ); if( errFlag == bpError ) { stringstream errMsg; errMsg << "~Brooks0254Port(): Could not close port #" << bpID << " " << bpDev << endl; throw FailedClosePort( errMsg.str() ); } return; } //! A method to send message to the port. If a response from the //! device is expected the 2nd argument should be set to 1. This //! will make sure that a response is received with a correct checksum string Brooks0254Port::Send( const string message, bool checkResponse, unsigned nReadLine ) { SemLock sLock( bpSemPos ); //! Create the synchronization string to terminate commands on the //! Brook device and put it into the initial state stringstream ssClear; ssClear << static_cast(27) << string( "AZ\r" ) << endl; Write( ssClear.str() ); //! Add CR at the end, it should not change the outcome string strOut = message + string("\r"); long iAttempt = 0; //! Number of attempts to read string readBack; //! Readback string bool errFlag = false; //! Error flag from readblack do { Write( strOut ); //! Write the Message //! If checking response is required then sleep for a while then //! read the response if( checkResponse ) { readBack.clear(); //! Read the response if( bpDebFlag > 1 ) cout << "Will read the response" << endl; readBack = this->Read( nReadLine ); //! Check if the returned string is empty. Empty string //! indicates a problem with the readback, including wrong checksum if( readBack.empty() ) { errFlag = true; } else { errFlag = false; } if( bpDebFlag > 1 ) { cout << endl; cout << "Brooks0254Port::Send(): ReadBack Message Size: <" << readBack.size() << ">" << endl; cout << "Brooks0254Port::Send(): ReadBack Message: <" << readBack << ">" << endl; cout << endl; } iAttempt++; } //! Check that the maximum number of read attempts is not exceeded //! If exceeded throw an exeption about it if( ( iAttempt >= bpMaxReadAttempt ) && errFlag ) { stringstream errMsg; errMsg << "Brooks0254Port::Send(): Tried to read " << iAttempt << " times and failed" << endl; throw FailedReadPort( errMsg.str() ); } } while ( errFlag ); return( readBack ); } //! Pseudo-synchrounous send method to serial device //! It sleeps certain amount of time before returning //! so that the calling function does not need to delay int Brooks0254Port::Write( string strOut ) { //! Check if there is alrady anything in the buffer, and if there is then //! flush the IO buffer before writing anything int bytes2read; ioctl( bpFD, FIONREAD, &bytes2read ); if( bytes2read > 0 ) { if( bpDebFlag > 1 ) { cout << "Brooks0254Port::Write(): There are " << bytes2read << " bytes in the serial buffer. Will flush it." << endl; char tmpChar; ssize_t bytesRead = read( bpFD, &tmpChar, sizeof( tmpChar ) ); cout << "It was " << static_cast(tmpChar) << " = " << string( 1, tmpChar ) << endl; } tcflush( bpFD, TCIOFLUSH ) ; //! Flush the I/O buffer if( bpDebFlag > 1 ) { ioctl( bpFD, FIONREAD, &bytes2read ); cout << "Brooks0254Port::Write(): After flushing there is " << bytes2read << " left." << endl; } } //! Write the string into the buffer int bytesSent = write( bpFD, strOut.c_str(), strOut.size() ); if ( bpDebFlag > 10 ) { cout << "nBrooks0254Port::Write(): Sent " << bytesSent << " bytes." << endl; } //! If the message lengths do not match print error message and exit if ( bytesSent != static_cast( strOut.size() ) ) { stringstream errMsg; errMsg << "Brooks0254Port::Write(): bytesSent != msgSize: " << endl << "Message Size: <" << strOut.size() << "> != Actual Sent <" << bytesSent << ">"; throw FailedWritePort( errMsg.str() ); } if( bpDebFlag > 1 ) { cout << endl; cout << "Brooks0254Port::Write(): Sent Message Size: <" << strOut.size() << "> Actual Sent <" << bytesSent << ">" << endl; cout << "Brooks0254Port::Write(): Sent Message: <" << strOut.c_str() << ">" << endl; cout << endl; } usleep( bpWriteDelay ); return bytesSent; } //! A method to receive a message from the device //! Received message is checked to verify that the checksum //! send by the device itself matches the checksum calculated //! from the received message body. If there are to many characters in //! the string excceding the maximum number of cahracters or the //! checksums to not match this method return empty string. There are //! no exceptions thrown by this method. string Brooks0254Port::Read( unsigned nReadLine ) { if( bpDebFlag > 1 ) cout << "Starting to read from port #" << bpID << endl; Brooks0254Msg retStr; unsigned iCount = 0; // Read Responce vector vecLine; string strLine = ""; do { // unsigned timeDelay = 2 * static_cast( bpCharTime ); usleep( bpReadDelay ); unsigned char tmpChar = 0; if ( bpDebFlag > 10 ) cout << "Locking port " << bpID << endl; ssize_t bytesRead = read( bpFD, &tmpChar, sizeof( tmpChar ) ); if ( bpDebFlag > 10 ) { cout << "nBrooks0254Port::Read(): Read " << bytesRead << " bytes." << endl; } if( tmpChar == '\012' ) { /* Received new line character */ vecLine.push_back( strLine ); strLine.clear(); } else if( tmpChar != '\0' ) { // Not null character strLine.append( 1, tmpChar ); } iCount++; if ( bpDebFlag > 10 ) cout << "Read count is " << iCount << endl; } while( ( vecLine.size() < nReadLine ) && ( iCount < bpReadbackTimeoutCount ) ); if( bpDebFlag > 1 ) cout << "Finished reading from port #" << bpID << endl; /* We are looking for a specific line, because I know that where the answer is */ if( vecLine.size() > 0 && vecLine[bpResponseLine][0] != '\0' && vecLine[bpResponseLine][0] != '\012' ) { retStr = vecLine[bpResponseLine] ; } unsigned checkSum = retStr.CheckSum(); if ( bpDebFlag > 1 ) { printf("\nBrooks0254Port::Read() Input:%s: Count: %d Lines: %d \n", retStr.c_str(), iCount, vecLine.size() ); printf("\nBrooks0254Port::Read() Input:%s: CheckSum 0x%x \n", retStr.c_str(), checkSum ); } //! If the checksums do not match or the timeout count is reached //! empty string will be returned indicating a failure to read if( ( iCount == bpReadbackTimeoutCount || checkSum != 0 ) ) { retStr.clear(); } //! Flush I/O buffer // tcflush( bpFD, TCIOFLUSH ) ; return retStr; } //! Initiliaze global mutex int Brooks0254Port::InitGlobalMutex() { pthread_mutexattr_init( &bpGlobMtxAttr ); pthread_mutexattr_setpshared( &bpGlobMtxAttr, PTHREAD_PROCESS_PRIVATE ); pthread_mutex_init( &bpGlobMutex, &bpGlobMtxAttr ); return 0; } //! Calculate negated Mod256 Checksum of the body message and adds the //! checksum provided as the last two characters in the message. unsigned Brooks0254Msg::CheckSum() { //! Make sure first two characters are "AZ" string msgPrelim = this->substr( 0, 2 ); if( msgPrelim != "AZ" ) { return 0x1; } //! Ignore first two characters in the message as they are pre-limiters //! Also ignore the last two character as they are the reported //! checksum value. string msgBody = this->substr( 2, this->size() - 4 ); string msgCheckSum = "0x" + this->substr( this->size() - 2, 2 ); //! Use stringstream to manipulate the string of reportred checksum stringstream ssCheckSum; ssCheckSum << hex << msgCheckSum ; unsigned checkSum; //! Create a checksum variable, ssCheckSum >> checkSum; //! put the reported negated checksum checkSum += 0x100 ; //! re-negate the reported checksum for 8 bits //! Calculate the checksum for the actual message body for( unsigned iChar = 0; iChar < msgBody.size() ; iChar++ ) { checkSum += msgBody[iChar]; } //! Report only the last byte for mod256 unsigned mask = 0x3; return ( mask & checkSum ); }