1

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
}
xinthose
  • 3,213
  • 3
  • 40
  • 59
  • We need to see the entire code flow that calls `async_read_some`, starting with where it's dispatched by the `io_service` and ending with where it returns, allowing the thread to do other work. Likely, the thread that calls `run` is doing something else and so can't process the completion. (You can check by stopping your program with a debugger and seeing what that thread is doing.) – David Schwartz Mar 03 '16 at 22:45
  • I call open, then read from `int main`; there is nothing else. Doesn't the boost thread run the `io_service`? – xinthose Mar 03 '16 at 22:47
  • 3
    Your `io_service.run()` is likely returning immediately, as there's no work. Common practice is to create an `io_service::work` and attach it to the `io_service`, to prevent `run()` from returning. – Collin Dauphinee Mar 03 '16 at 22:52
  • @xinthose Likely for some reason it's not. Use a debugger to find out what it's doing. It could have terminated. It could be stuck waiting for something in some other piece of code. – David Schwartz Mar 03 '16 at 22:57
  • adding `boost::asio::io_service::work work (io_service);` before `boost::thread t(boost::bind(&boost::asio::io_service::run, &io_service));` does not help either; `read_some` blocks until I swipe the card and there's data, then it gets it, I need to replicate that; or just put `read_some` in a thread – xinthose Mar 04 '16 at 14:44

2 Answers2

2

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
        )
    );

}
ravenspoint
  • 19,093
  • 6
  • 57
  • 103
  • 1
    thanks, but `data_received` is never called; I think I need to start the io_service run thread after `async_read_some` so that it has work? – xinthose Mar 06 '16 at 05:23
  • 1
    Yes . Note that the data_received will be executed in the thread where run() is executing. So, for clarity, I would execute async_read_some from that thread also. – ravenspoint Mar 06 '16 at 15:59
1

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;
}
xinthose
  • 3,213
  • 3
  • 40
  • 59
  • 1
    "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." No, I can't blame you. The documentation is appalling. Hence why I'm here.... But your comment highlighted why my code didn't work, so thank you! – CJBrew Jul 26 '17 at 12:11