Boost Asio Serial Port Read

Posted on  by 

Permalink
  1. The constructor of SerialPort constructs the boost::asio::serial_port, plus allocates the read buffer. The name of the port is provided in a system-dependent syntax. For example, under Windows,. COM1 would be appropriate, but under Linux, /dev/ttyS0 could be used.
  2. /** BOOST ASIO SERIAL EXAMPLE boost::asio::serial_port boost::asio::serial_port::async_read_some **/ /* compile with g++ -o serial serial.cpp -lboost_system -lboost.

Join GitHub today

Boost::asio::serial_port の read_some を使って シリアルポートから読んだ際に,一瞬にしてEOF Exceptionが 投げられました.デバイスファイルのパスもボーレートも権限も 正しいはずなのになんでだろう??と思って調べたのでメモ. what(): read_some: End of file 解決法1. Assign an existing native serial port to the serial port. Start an asynchronous read. Start an asynchronous write. Construct a basic_serial_port without opening it. Header: boost/asio/serial_port.hpp. Convenience header: boost/asio.hpp. Custom Query (3422 matches).

GitHub is home to over 40 million developers working together to host and review code, manage projects, and build software together.

Sign up
Find file Copy path
fedetftAdded C++11 support, replaced boost libs with the std equivalent when…291a799Aug 11, 2018
1 contributor
/*
* File: AsyncSerial.cpp
* Author: Terraneo Federico
* Distributed under the Boost Software License, Version 1.0.
* Created on September 7, 2009, 10:46 AM
*
* v1.03: C++11 support
*
* v1.02: Fixed a bug in BufferedAsyncSerial: Using the default constructor
* the callback was not set up and reading didn't work.
*
* v1.01: Fixed a bug that did not allow to reopen a closed serial port.
*
* v1.00: First release.
*
* IMPORTANT:
* On Mac OS X boost asio's serial ports have bugs, and the usual implementation
* of this class does not work. So a workaround class was written temporarily,
* until asio (hopefully) will fix Mac compatibility for serial ports.
*
* Please note that unlike said in the documentation on OS X until asio will
* be fixed serial port *writes* are *not* asynchronous, but at least
* asynchronous *read* works.
* In addition the serial port open ignores the following options: parity,
* character size, flow, stop bits, and defaults to 8N1 format.
* I know it is bad but at least it's better than nothing.
*
*/
#include'AsyncSerial.h'
#include<string>
#include<algorithm>
#include<thread>
#include<mutex>
#include<boost/bind.hpp>
#include<boost/shared_array.hpp>
usingnamespacestd;
usingnamespaceboost;
//
//Class AsyncSerial
//
#ifndef __APPLE__
classAsyncSerialImpl: privateboost::noncopyable
{
public:
AsyncSerialImpl(): io(), port(io), backgroundThread(), open(false),
error(false) {}
boost::asio::io_service io; ///< Io service object
boost::asio::serial_port port; ///< Serial port object
std::thread backgroundThread; ///< Thread that runs read/write operations
bool open; ///< True if port open
bool error; ///< Error flag
mutable std::mutex errorMutex; ///< Mutex for access to error
/// Data are queued here before they go in writeBuffer
std::vector<char> writeQueue;
boost::shared_array<char> writeBuffer; ///< Data being written
size_t writeBufferSize; ///< Size of writeBuffer
std::mutex writeQueueMutex; ///< Mutex for access to writeQueue
char readBuffer[AsyncSerial::readBufferSize]; ///< data being read
/// Read complete callback
std::function<void (constchar*, size_t)> callback;
};
AsyncSerial::AsyncSerial(): pimpl(new AsyncSerialImpl)
{
}
AsyncSerial::AsyncSerial(const std::string& devname, unsignedint 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)
: pimpl(new AsyncSerialImpl)
{
open(devname,baud_rate,opt_parity,opt_csize,opt_flow,opt_stop);
}
voidAsyncSerial::open(const std::string& devname, unsignedint 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();
setErrorStatus(true);//If an exception is thrown, error_ remains true
pimpl->port.open(devname);
pimpl->port.set_option(asio::serial_port_base::baud_rate(baud_rate));
pimpl->port.set_option(opt_parity);
pimpl->port.set_option(opt_csize);
pimpl->port.set_option(opt_flow);
pimpl->port.set_option(opt_stop);
//This gives some work to the io_service before it is started
pimpl->io.post(boost::bind(&AsyncSerial::doRead, this));
thread t(boost::bind(&asio::io_service::run, &pimpl->io));
pimpl->backgroundThread.swap(t);
setErrorStatus(false);//If we get here, no error
pimpl->open=true; //Port is now open
}
boolAsyncSerial::isOpen() const
{
return pimpl->open;
}
boolAsyncSerial::errorStatus() const
{
lock_guard<mutex> l(pimpl->errorMutex);
return pimpl->error;
}
voidAsyncSerial::close()
{
if(!isOpen()) return;
pimpl->open=false;
pimpl->io.post(boost::bind(&AsyncSerial::doClose, this));
pimpl->backgroundThread.join();
pimpl->io.reset();
if(errorStatus())
{
throw(boost::system::system_error(boost::system::error_code(),
'Error while closing the device'));
}
}
voidAsyncSerial::write(constchar *data, size_t size)
{
{
lock_guard<mutex> l(pimpl->writeQueueMutex);
pimpl->writeQueue.insert(pimpl->writeQueue.end(),data,data+size);
}
pimpl->io.post(boost::bind(&AsyncSerial::doWrite, this));
}
voidAsyncSerial::write(const std::vector<char>& data)
{
{
lock_guard<mutex> l(pimpl->writeQueueMutex);
pimpl->writeQueue.insert(pimpl->writeQueue.end(),data.begin(),
data.end());
}
pimpl->io.post(boost::bind(&AsyncSerial::doWrite, this));
}
voidAsyncSerial::writeString(const std::string& s)
{
{
lock_guard<mutex> l(pimpl->writeQueueMutex);
pimpl->writeQueue.insert(pimpl->writeQueue.end(),s.begin(),s.end());
}
pimpl->io.post(boost::bind(&AsyncSerial::doWrite, this));
}
AsyncSerial::~AsyncSerial()
{
if(isOpen())
{
try {
close();
} catch(...)
{
//Don't throw from a destructor
}
}
}
voidAsyncSerial::doRead()
{
pimpl->port.async_read_some(asio::buffer(pimpl->readBuffer,readBufferSize),
boost::bind(&AsyncSerial::readEnd,
this,
asio::placeholders::error,
asio::placeholders::bytes_transferred));
}
voidAsyncSerial::readEnd(const boost::system::error_code& error,
size_t bytes_transferred)
{
if(error)
{
#ifdef __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
doRead();
return;
}
#endif//__APPLE__
//error can be true even because the serial port was closed.
//In this case it is not a real error, so ignore
if(isOpen())
{
doClose();
setErrorStatus(true);
}
} else {
if(pimpl->callback) pimpl->callback(pimpl->readBuffer,
bytes_transferred);
doRead();
}
}
voidAsyncSerial::doWrite()
{
//If a write operation is already in progress, do nothing
if(pimpl->writeBuffer0)
{
lock_guard<mutex> l(pimpl->writeQueueMutex);
pimpl->writeBufferSize=pimpl->writeQueue.size();
pimpl->writeBuffer.reset(newchar[pimpl->writeQueue.size()]);
copy(pimpl->writeQueue.begin(),pimpl->writeQueue.end(),
pimpl->writeBuffer.get());
pimpl->writeQueue.clear();
async_write(pimpl->port,asio::buffer(pimpl->writeBuffer.get(),
pimpl->writeBufferSize),
boost::bind(&AsyncSerial::writeEnd, this, asio::placeholders::error));
}
}
voidAsyncSerial::writeEnd(const boost::system::error_code& error)
{
if(!error)
{
lock_guard<mutex> l(pimpl->writeQueueMutex);
if(pimpl->writeQueue.empty())
{
pimpl->writeBuffer.reset();
pimpl->writeBufferSize=0;
return;
}
pimpl->writeBufferSize=pimpl->writeQueue.size();
pimpl->writeBuffer.reset(newchar[pimpl->writeQueue.size()]);
copy(pimpl->writeQueue.begin(),pimpl->writeQueue.end(),
pimpl->writeBuffer.get());
pimpl->writeQueue.clear();
async_write(pimpl->port,asio::buffer(pimpl->writeBuffer.get(),
pimpl->writeBufferSize),
boost::bind(&AsyncSerial::writeEnd, this, asio::placeholders::error));
} else {
setErrorStatus(true);
doClose();
}
}
voidAsyncSerial::doClose()
{
boost::system::error_code ec;
pimpl->port.cancel(ec);
if(ec) setErrorStatus(true);
pimpl->port.close(ec);
if(ec) setErrorStatus(true);
}
voidAsyncSerial::setErrorStatus(bool e)
{
lock_guard<mutex> l(pimpl->errorMutex);
pimpl->error=e;
}
voidAsyncSerial::setReadCallback(const std::function<void (constchar*, size_t)>& callback)
{
pimpl->callback=callback;
}
voidAsyncSerial::clearReadCallback()
{
std::function<void (constchar*, size_t)> empty;
pimpl->callback.swap(empty);
}
#else//__APPLE__
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<termios.h>
#include<unistd.h>
classAsyncSerialImpl: privateboost::noncopyable
{
public:
AsyncSerialImpl(): backgroundThread(), open(false), error(false) {}
boost::thread backgroundThread; ///< Thread that runs read operations
bool open; ///< True if port open
bool error; ///< Error flag
mutable boost::mutex errorMutex; ///< Mutex for access to error
int fd; ///< File descriptor for serial port
char readBuffer[AsyncSerial::readBufferSize]; ///< data being read
/// Read complete callback
std::function<void (constchar*, size_t)> callback;
};
AsyncSerial::AsyncSerial(): pimpl(new AsyncSerialImpl)
{
}
AsyncSerial::AsyncSerial(const std::string& devname, unsignedint 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)
: pimpl(new AsyncSerialImpl)
{
open(devname,baud_rate,opt_parity,opt_csize,opt_flow,opt_stop);
}
voidAsyncSerial::open(const std::string& devname, unsignedint 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();
setErrorStatus(true);//If an exception is thrown, error remains true
structtermios new_attributes;
speed_t speed;
int status;
// Open port
pimpl->fd=::open(devname.c_str(), O_RDWR | O_NOCTTY | O_NONBLOCK);
if (pimpl->fd<0) throw(boost::system::system_error(
boost::system::error_code(),'Failed to open port'));
// Set Port parameters.
status=tcgetattr(pimpl->fd,&new_attributes);
if(status<0 || !isatty(pimpl->fd))
{
::close(pimpl->fd);
throw(boost::system::system_error(
boost::system::error_code(),'Device is not a tty'));
}
new_attributes.c_iflag = IGNBRK;
new_attributes.c_oflag = 0;
new_attributes.c_lflag = 0;
new_attributes.c_cflag = (CS8 | CREAD | CLOCAL);//8 data bit,Enable receiver,Ignore modem
/* In non canonical mode (Ctrl-C and other disabled, no echo,...) VMIN and VTIME work this way:
if the function read() has'nt read at least VMIN chars it waits until has read at least VMIN
chars (even if VTIME timeout expires); once it has read at least vmin chars, if subsequent
chars do not arrive before VTIME expires, it returns error; if a char arrives, it resets the
timeout, so the internal timer will again start from zero (for the nex char,if any)*/
new_attributes.c_cc[VMIN]=1;// Minimum number of characters to read before returning error
new_attributes.c_cc[VTIME]=1;// Set timeouts in tenths of second
// Set baud rate
switch(baud_rate)
{
case50:speed= B50; break;
case75:speed= B75; break;
case110:speed= B110; break;
case134:speed= B134; break;
case150:speed= B150; break;
case200:speed= B200; break;
case300:speed= B300; break;
case600:speed= B600; break;
case1200:speed= B1200; break;
case1800:speed= B1800; break;
case2400:speed= B2400; break;
case4800:speed= B4800; break;
case9600:speed= B9600; break;
case19200:speed= B19200; break;
case38400:speed= B38400; break;
case57600:speed= B57600; break;
case115200:speed= B115200; break;
case230400:speed= B230400; break;
default:
{
::close(pimpl->fd);
throw(boost::system::system_error(
boost::system::error_code(),'Unsupported baud rate'));
}
}
cfsetospeed(&new_attributes,speed);
cfsetispeed(&new_attributes,speed);
//Make changes effective
status=tcsetattr(pimpl->fd, TCSANOW, &new_attributes);
if(status<0)
{
::close(pimpl->fd);
throw(boost::system::system_error(
boost::system::error_code(),'Can't set port attributes'));
}
//These 3 lines clear the O_NONBLOCK flag
status=fcntl(pimpl->fd, F_GETFL, 0);
if(status!=-1) fcntl(pimpl->fd, F_SETFL, status & ~O_NONBLOCK);
setErrorStatus(false);//If we get here, no error
pimpl->open=true; //Port is now open
thread t(bind(&AsyncSerial::doRead, this));
pimpl->backgroundThread.swap(t);
}
boolAsyncSerial::isOpen() const
{
return pimpl->open;
}
boolAsyncSerial::errorStatus() const
{
lock_guard<mutex> l(pimpl->errorMutex);
return pimpl->error;
}
voidAsyncSerial::close()
{
if(!isOpen()) return;
pimpl->open=false;
::close(pimpl->fd); //The thread waiting on I/O should return
pimpl->backgroundThread.join();
if(errorStatus())
{
throw(boost::system::system_error(boost::system::error_code(),
'Error while closing the device'));
}
}
voidAsyncSerial::write(constchar *data, size_t size)
{
if(::write(pimpl->fd,data,size)!=size) setErrorStatus(true);
}
voidAsyncSerial::write(const std::vector<char>& data)
{
if(::write(pimpl->fd,&data[0],data.size())!=data.size())
setErrorStatus(true);
}
voidAsyncSerial::writeString(const std::string& s)
{
if(::write(pimpl->fd,&s[0],s.size())!=s.size()) setErrorStatus(true);
}
AsyncSerial::~AsyncSerial()
{
if(isOpen())
{
try {
close();
} catch(...)
{
//Don't throw from a destructor
}
}
}
voidAsyncSerial::doRead()
{
//Read loop in spawned thread
for(;;)
{
int received=::read(pimpl->fd,pimpl->readBuffer,readBufferSize);
if(received<0)
{
if(isOpen()false) return; //Thread interrupted because port closed
else {
setErrorStatus(true);
continue;
}
}
if(pimpl->callback) pimpl->callback(pimpl->readBuffer, received);
}
}
voidAsyncSerial::readEnd(const boost::system::error_code& error,
size_t bytes_transferred)
{
//Not used
}
voidAsyncSerial::doWrite()
{
//Not used
}
voidAsyncSerial::writeEnd(const boost::system::error_code& error)
{
//Not used
}
voidAsyncSerial::doClose()
{
//Not used
}
voidAsyncSerial::setErrorStatus(bool e)
{
lock_guard<mutex> l(pimpl->errorMutex);
pimpl->error=e;
}
voidAsyncSerial::setReadCallback(const std::function<void (constchar*, size_t)>& callback)
{
pimpl->callback=callback;
}
voidAsyncSerial::clearReadCallback()
{
std::function<void (constchar*, size_t)> empty;
pimpl->callback.swap(empty);
}
#endif //__APPLE__
//
//Class CallbackAsyncSerial
//
CallbackAsyncSerial::CallbackAsyncSerial(): AsyncSerial()
{
}
CallbackAsyncSerial::CallbackAsyncSerial(const std::string& devname,
unsignedint 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)
:AsyncSerial(devname,baud_rate,opt_parity,opt_csize,opt_flow,opt_stop)
{
}
voidCallbackAsyncSerial::setCallback(const std::function<void (constchar*, size_t)>& callback)
{
setReadCallback(callback);
}
voidCallbackAsyncSerial::clearCallback()
{
clearReadCallback();
}
CallbackAsyncSerial::~CallbackAsyncSerial()
{
clearReadCallback();
}
  • Copy lines
  • Copy permalink
Active7 months ago

I need to know how to read (sync or async doesn't matters) with a timeout. I want to check if a device is connected with a serial port or not.

For that I use asio::write and then I wait for the response of the device.

If a device is connected asio::read(serial, boost::asio::buffer(&r,1)) works fine but if there is no device the program stops, which is is why I need the timeout

I know that I need a deadline_timer but I have no idea how to use it in the async_read function.

An example of how it works would be really helpful.

I know that there are many similar threads and I read lot of them but I can't find a solution that helps me solving my problem!

dsolimano
7,6173 gold badges41 silver badges57 bronze badges
Chris K.Chris K.

4 Answers

The code posted by Igor R. did not compile for me. Here is my improved version of his code, which works perfectly. It uses lambdas to get rid of the set_result helper function.

Community
Robert HegnerRobert Hegner
4,7474 gold badges41 silver badges75 bronze badges

You don't use deadline_timer in async_read. But you can start two async processes:

  1. An async_read process on serial port. boost::asio::serial_port has a cancel method that cancels all async operations on it.
  2. A deadline timer with required timeout. In completion handler for deadline_timer you can cancel the serial port. This should close the async_read operation and call its completion handler with an error.

Code:

VikasVikas
6,3942 gold badges29 silver badges44 bronze badges

Boost Asio Serial Port Async_read Example

Once upon a time, the library author proposed the following way to read synchronously with timeout (this example involves tcp::socket, but you can use serial port instead):

Igor R.Igor R.
11.5k1 gold badge36 silver badges66 bronze badges

There's no one or easy answer per se, since even if you do an async read, the callback never gets called and you now have a loose thread somewhere around.

You're right in assuming that deadline_timer is one of the possible solutions, but it takes some fiddling and shared state. There's the blocking TCP example, but that's for async_connect and there's a cool thing about it returning when it has nothing to do. read won't do that, worst case scenario -- it'll crash & burn 'cause of the invalid resource. So the deadline timer thing is one of your options, but there's actually an easier one, that goes somewhat like this:

Basically, do the read in another thread and kill it off if it times out. You should read up on Boost.Threads.

If you interrupt it, make sure the resources are all closed.

PortTC1TC1
2,3123 gold badges16 silver badges30 bronze badges

Boost Asio Serial Port Read_until

Parallel port

Boost Asio Serial Port Reading

Not the answer you're looking for? Browse other questions tagged c++boosttimeoutserial-portboost-asio or ask your own question.

Coments are closed