/* * File: TimeoutSerial.cpp * Author: Terraneo Federico * Distributed under the Boost Software License, Version 1.0. * Created on September 12, 2009, 3:47 PM * * v1.06: C++11 support * * v1.05: Fixed a bug regarding reading after a timeout (again). * * v1.04: Fixed bug with timeout set to zero * * v1.03: Fix for Mac OS X, now fully working on Mac. * * v1.02: Code cleanup, speed improvements, bug fixes. * * v1.01: Fixed a bug that caused errors while reading after a timeout. * * v1.00: First release. */ #include "TimeoutSerial.h" #include #include #include #include #include #include using namespace std; using namespace boost; TimeoutSerial::TimeoutSerial(): io(), port(io), timer(io), timeout(boost::posix_time::seconds(0)) {} TimeoutSerial::TimeoutSerial(const std::string& devname, unsigned int baud_rate, asio::serial_port_base::parity opt_parity, asio::serial_port_base::character_size opt_csize, asio::serial_port_base::flow_control opt_flow, asio::serial_port_base::stop_bits opt_stop) : io(), port(io), timer(io), timeout(boost::posix_time::seconds(0)) { open(devname,baud_rate,opt_parity,opt_csize,opt_flow,opt_stop); } void TimeoutSerial::open(const std::string& devname, unsigned int baud_rate, asio::serial_port_base::parity opt_parity, asio::serial_port_base::character_size opt_csize, asio::serial_port_base::flow_control opt_flow, asio::serial_port_base::stop_bits opt_stop) { if(isOpen()) close(); port.open(devname); port.set_option(asio::serial_port_base::baud_rate(baud_rate)); port.set_option(opt_parity); port.set_option(opt_csize); port.set_option(opt_flow); port.set_option(opt_stop); } bool TimeoutSerial::isOpen() const { return port.is_open(); } void TimeoutSerial::close() { if(isOpen()==false) return; port.close(); } void TimeoutSerial::setTimeout(const boost::posix_time::time_duration& t) { timeout=t; } size_t TimeoutSerial::write(const char *data, size_t size) { return asio::write(port,asio::buffer(data,size)); } void TimeoutSerial::write(const std::vector& data) { asio::write(port,asio::buffer(&data[0],data.size())); } void TimeoutSerial::writeString(const std::string& s) { asio::write(port,asio::buffer(s.c_str(),s.size())); } void TimeoutSerial::dump(char *data, size_t size) { BOOST_LOG_TRIVIAL(debug) << "Size = " << size; for (int i=0; i < size; i++) { BOOST_LOG_TRIVIAL(debug) << i << "(" << int(data[i]) << ")"; } } size_t TimeoutSerial::read(char *data, size_t size) { size_t toRead = 0; if(readData.size()>0)//If there is some data from a previous read { BOOST_LOG_TRIVIAL(debug) << "UNCONSUMED DATA " << readData.size(); istream is(&readData); toRead=min(readData.size(),size);//How many bytes to read? is.read(data,toRead); data+=toRead; size-=toRead; if(size==0) return 0;//If read data was enough, just return } setupParameters=ReadSetupParameters(data,size); performReadSetup(setupParameters); //For this code to work, there should always be a timeout, so the //request for no timeout is translated into a very long timeout if(timeout!=boost::posix_time::seconds(0)) timer.expires_from_now(timeout); else timer.expires_from_now(boost::posix_time::hours(100000)); timer.async_wait(boost::bind(&TimeoutSerial::timeoutExpired,this, asio::placeholders::error)); result=resultInProgress; bytesTransferred=toRead; for(;;) { io.run_one(); switch(result) { case resultSuccess: timer.cancel(); dump(data,size); return this->bytesTransferred; case resultTimeoutExpired: port.cancel(); throw(timeout_exception("Timeout expired")); case resultError: timer.cancel(); port.cancel(); throw(boost::system::system_error(boost::system::error_code(), "Error while reading")); //if resultInProgress remain in the loop } } } int TimeoutSerial::readChar(int delay) { vector result(1,'\0'); timeout = posix_time::seconds(delay); try { result = read(1); std::cerr << std::hex << (uint8_t )result[0] << endl; return static_cast(result[0]); } catch (boost::system::system_error& e) { cerr << "EOF" << endl; return -1; } } std::vector TimeoutSerial::read(size_t size) { vector result(size,'\0');//Allocate a vector with the desired size read(&result[0],size);//Fill it with values return result; } std::string TimeoutSerial::readString(size_t size) { string result(size,'\0');//Allocate a string with the desired size read(&result[0],size);//Fill it with values return result; } std::string TimeoutSerial::readStringUntil(const std::string& delim) { // Note: if readData contains some previously read data, the call to // async_read_until (which is done in performReadSetup) correctly handles // it. If the data is enough it will also immediately call readCompleted() setupParameters=ReadSetupParameters(delim); performReadSetup(setupParameters); //For this code to work, there should always be a timeout, so the //request for no timeout is translated into a very long timeout if(timeout!=boost::posix_time::seconds(0)) timer.expires_from_now(timeout); else timer.expires_from_now(boost::posix_time::hours(100000)); timer.async_wait(boost::bind(&TimeoutSerial::timeoutExpired,this, asio::placeholders::error)); result=resultInProgress; bytesTransferred=0; for(;;) { io.run_one(); switch(result) { case resultSuccess: { timer.cancel(); bytesTransferred-=delim.size();//Don't count delim istream is(&readData); string result(bytesTransferred,'\0');//Alloc string is.read(&result[0],bytesTransferred);//Fill values is.ignore(delim.size());//Remove delimiter from stream return result; } case resultTimeoutExpired: port.cancel(); throw(timeout_exception("Timeout expired")); case resultError: timer.cancel(); port.cancel(); throw(boost::system::system_error(boost::system::error_code(), "Error while reading")); //if resultInProgress remain in the loop } } } TimeoutSerial::~TimeoutSerial() {} void TimeoutSerial::performReadSetup(const ReadSetupParameters& param) { if(param.fixedSize) { asio::async_read(port,asio::buffer(param.data,param.size),boost::bind( &TimeoutSerial::readCompleted,this,asio::placeholders::error, asio::placeholders::bytes_transferred)); } else { asio::async_read_until(port,readData,param.delim,boost::bind( &TimeoutSerial::readCompleted,this,asio::placeholders::error, asio::placeholders::bytes_transferred)); } } void TimeoutSerial::timeoutExpired(const boost::system::error_code& error) { if(!error && result==resultInProgress) result=resultTimeoutExpired; } void TimeoutSerial::readCompleted(const boost::system::error_code& error, const size_t bytesTransferred) { BOOST_LOG_TRIVIAL(debug) << "Error = " << error.value() << " Message = " << error.message(); if(!error) { result=resultSuccess; this->bytesTransferred+=bytesTransferred; return; } //In case a asynchronous operation is cancelled due to a timeout, //each OS seems to have its way to react. #ifdef _WIN32 if(error.value()==995) return; //Windows spits out error 995 #elif defined(__APPLE__) if(error.value()==45) { //Bug on OS X, it might be necessary to repeat the setup //http://osdir.com/ml/lib.boost.asio.user/2008-08/msg00004.html performReadSetup(setupParameters); return; } #else //Linux if(error.value()==125) return; //Linux outputs error 125 if (error.value()==158) { //performReadSetup(setupParameters); return; } #endif result=resultError; } void TimeoutSerial::setRAW() { int fd = port.native_handle(); struct termios options; fcntl(fd, F_SETFL, 0); /* get the current options */ tcgetattr(fd, &options); options.c_cflag |= (CLOCAL | CREAD); options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); options.c_oflag &= ~OPOST; tcsetattr(fd, TCSANOW, &options); } void TimeoutSerial::setRTS(bool enabled) { int fd = port.native_handle(); int data = TIOCM_RTS; if (!enabled) ioctl(fd, TIOCMBIC, &data); else ioctl(fd, TIOCMBIS, &data); } void TimeoutSerial::setDTR(bool enabled) { int fd = port.native_handle(); int data = TIOCM_DTR; if (!enabled) ioctl(fd, TIOCMBIC, &data); // Clears the DTR pin else ioctl(fd, TIOCMBIS, &data); // Sets the DTR pin }