- Boost Asio Serial Port Async_read Example
- Boost Asio Serial Port Read_until
- Boost Asio Serial Port Reading
- 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.
- /** 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 up1 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
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!
dsolimano4 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.
You don't use deadline_timer
in async_read
. But you can start two async processes:
- An
async_read
process on serial port. boost::asio::serial_port has a cancel method that cancels all async operations on it. - A deadline timer with required timeout. In completion handler for
deadline_timer
you cancancel
the serial port. This should close theasync_read
operation and call its completion handler with an error.
Code:
VikasVikasBoost 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):
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.
TC1TC1