318 lines
9.3 KiB
C++
Executable File
318 lines
9.3 KiB
C++
Executable File
/*
|
|
* 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 <string>
|
|
#include <algorithm>
|
|
#include <iostream>
|
|
#include <boost/bind.hpp>
|
|
#include <sys/ioctl.h>
|
|
#include <boost/log/trivial.hpp>
|
|
|
|
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<char>& 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<char> result(1,'\0');
|
|
|
|
timeout = posix_time::seconds(delay);
|
|
try {
|
|
result = read(1);
|
|
std::cerr << std::hex << (uint8_t
|
|
)result[0] << endl;
|
|
return static_cast<u_int8_t>(result[0]);
|
|
} catch (boost::system::system_error& e) {
|
|
cerr << "EOF" << endl;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
std::vector<char> TimeoutSerial::read(size_t size)
|
|
{
|
|
vector<char> 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
|
|
} |