Reputation: 3848
My code works for read_some
, but not for async_read_some
. The data I'm reading is 5 chars long, whereas MAX_RESPONSE_SIZE 256
. I call async_read_some
once from my main after opening the port, but the callback is never called after I swipe my prox card a few times. I have tried adding io_service.run()
after async_read_some
but it did not help. Am I missing something? Thank you.
header
boost::system::error_code error;
boost::asio::io_service io_service;
typedef boost::shared_ptr<boost::asio::serial_port> serial_port_ptr;
serial_port_ptr serial_port;
char read_buffer[MAX_RESPONSE_SIZE];
open
serial_port.reset();
serial_port = serial_port_ptr(new boost::asio::serial_port(io_service));
serial_port->open(device_path, error);
serial_port->set_option(boost::asio::serial_port_base::baud_rate(baud_rate));
serial_port->set_option(boost::asio::serial_port_base::character_size(8));
serial_port->set_option(boost::asio::serial_port_base::stop_bits(boost::asio::serial_port_base::stop_bits::one));
serial_port->set_option(boost::asio::serial_port_base::parity(boost::asio::serial_port_base::parity::none));
serial_port->set_option(boost::asio::serial_port_base::flow_control(boost::asio::serial_port_base::flow_control::none));
boost::thread t(boost::bind(&boost::asio::io_service::run, &io_service));
read
serial_port->async_read_some(
boost::asio::buffer(read_buffer, MAX_RESPONSE_SIZE),
boost::bind(
&serial_comm::data_received,
this, boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred
)
);
callback
void serial_comm::data_received(const boost::system::error_code& error, size_t bytes_transferred)
{
// do stuff
}
Upvotes: 1
Views: 2964
Reputation: 3848
Basically my problem was not starting the io_service
thread after async_read_some
in the same function. Can you blame me? This stuff is not very clear cut. Here's my code in case anyone wants it (INFO and ERROR come from boost logging, see one of my other questions on it):
serial_comm.hpp
#ifndef __SERIAL_COMM_HPP
#define __SERIAL_COMM_HPP
#include <boost/asio.hpp>
#include <boost/asio/serial_port.hpp>
#include <boost/system/error_code.hpp>
#include <boost/system/system_error.hpp>
#include <boost/bind.hpp>
#include <boost/thread.hpp>
#include <string>
#include <atomic>
#include "logging.hpp" // Boost logging
#define MAX_RESPONSE_SIZE 256
class serial_comm
{
public:
void open_serial_port (std::string device_path, unsigned int baud_rate);
void close_serial_port (void);
void async_read_some (void);
std::string serial_read_data;
std::atomic <bool> serial_data_read_complete{false};
private:
// functions
void data_received (const boost::system::error_code& ec, size_t bytes_transferred);
// variables
boost::mutex mutex;
boost::system::error_code error;
boost::asio::io_service io_service;
typedef boost::shared_ptr<boost::asio::serial_port> serial_port_ptr;
serial_port_ptr serial_port;
char read_buffer[MAX_RESPONSE_SIZE];
};
#endif // __SERIAL_COMM_HPP
serial_comm.cpp
#include "../include/serial_comm.hpp"
void serial_comm::open_serial_port (std::string device_path, unsigned int baud_rate)
{
INFO << "started";
try
{
serial_port.reset();
serial_port = serial_port_ptr(new boost::asio::serial_port(io_service));
serial_port->open(device_path, error);
if (error)
{
ERROR << "error.message() >> " << error.message().c_str();
throw -3;
}
// set options
serial_port->set_option(boost::asio::serial_port_base::baud_rate(baud_rate));
serial_port->set_option(boost::asio::serial_port_base::character_size(8));
serial_port->set_option(boost::asio::serial_port_base::stop_bits(boost::asio::serial_port_base::stop_bits::one));
serial_port->set_option(boost::asio::serial_port_base::parity(boost::asio::serial_port_base::parity::none));
serial_port->set_option(boost::asio::serial_port_base::flow_control(boost::asio::serial_port_base::flow_control::none));
}
catch (int error)
{
ERROR << "error = " << error;
throw -1;
}
catch (const std::exception &e)
{
ERROR << "e.what() = " << e.what();
throw -2;
}
INFO << device_path << " opened correctly";
INFO << "ended";
return;
}
void serial_comm::close_serial_port()
{
boost::mutex::scoped_lock lock(mutex); // prevent multiple thread access
INFO << "started";
try
{
if (serial_port)
{
serial_port->cancel();
serial_port->close();
serial_port.reset();
}
else
{
WARNING << "serial port is not open";
}
io_service.stop();
io_service.reset();
}
catch (const std::exception &e)
{
ERROR << "e.what() = " << e.what();
throw -1;
}
INFO << "ended";
return;
}
void serial_comm::async_read_some (void)
{
boost::mutex::scoped_lock lock (mutex); // prevent multiple threads
INFO << "started";
std::string data;
try
{
if (serial_port.get() == NULL || !serial_port->is_open())
{
WARNING << "serial port is not open";
throw -2;
}
serial_port->async_read_some(
boost::asio::buffer(read_buffer, MAX_RESPONSE_SIZE),
boost::bind(
&serial_comm::data_received,
this, boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred
)
);
// start io_service run thread after giving it work
boost::thread t(boost::bind(&boost::asio::io_service::run, &io_service));
}
catch (const std::exception &e)
{
ERROR << "e.what() = " << e.what();
throw -1;
}
INFO << "ended";
return;
}
void serial_comm::data_received(const boost::system::error_code& error, size_t bytes_transferred)
{
boost::mutex::scoped_lock lock(mutex); // prevent multiple thread access
INFO << "started";
try
{
if (serial_port.get() == NULL || !serial_port->is_open())
{
WARNING << "serial port is not open";
throw -2;
}
if (error)
{
ERROR << "error.message() >> " << error.message().c_str();
throw -3;
}
for (unsigned int i = 0; i < bytes_transferred; ++i) {
serial_read_data += read_buffer[i];
}
INFO << "bytes_transferred = " << bytes_transferred << "; serial_read_data = " << serial_read_data;
serial_data_read_complete = true;
}
catch (const std::exception &e)
{
ERROR << "e.what() = " << e.what();
throw -1;
}
// prevent io_service from returning due to lack of work
serial_port->async_read_some(
boost::asio::buffer(read_buffer, MAX_RESPONSE_SIZE),
boost::bind(
&serial_comm::data_received,
this, boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred
)
);
INFO << "ended";
return;
}
main.cpp
#include "../include/serial_comm.hpp"
int main(void)
{
serial_comm _serial_comm;
try
{
_serial_comm.open_serial_port("/dev/ttyS0", 9600);
_serial_comm.async_read_some(); // this function will always check for data
loop:
while (!_serial_comm.serial_data_read_complete)
{
sleep(1);
}
INFO << "_serial_comm.serial_read_data = " << _serial_comm.serial_read_data;
_serial_comm.serial_read_data.clear();
_serial_comm.serial_data_read_complete = false;
goto loop;
}
catch (int error)
{
ERROR << "error >> " << error;
return;
}
FATAL << "main ended";
return;
}
Upvotes: 1
Reputation: 20596
You must ensure that there is always work to do, so that io_service::run() does not return and complete the thread where it is running.
As mentioned in the comments, you can create an io_service::work. However, I consider this artificial, a symptom of a design problem.
The better answer, probably, is that in the data_received handler, you should prepare for the next read if no fatal error occurred
void serial_comm::data_received(
const boost::system::error_code& error,
size_t bytes_transferred)
{
// do stuff
if( any_kind_of_fatal_error )
{
// return without setting up the next read
// this will end reading
return;
}
// the last read was successful
// so setup for the next
serial_port->async_read_some(
boost::asio::buffer(read_buffer, MAX_RESPONSE_SIZE),
boost::bind(
&serial_comm::data_received,
this, boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred
)
);
}
Upvotes: 2