简体   繁体   中英

Boost::Asio Serial Port async_read_some not storing data in buffer

I'm developing Serial Port program using Boost::Asio.
I call the SerialPort::read_async method every time I want to read data from serial port.
While I am testing I realized that the data received on serial port is not getting saved in the read_buffer however the read handler receives proper number of received bytes in boost::asio::placeholders::bytes_transferred field/parameter. The read handler also contains boost::system::errc::success in the boost::asio::placeholders::error field/parameter.

The read_buffer holds exactly the same value that was set before the async_read_some call was made.

this->read_buffer.fill(static_cast<std::byte>('\0')); //Clear Buffer
this->read_buffer.fill(static_cast<std::byte>('0')); //For Testing

Code

bool SerialPort::read_async(std::uint32_t read_timeout)
{
    try
    {
        this->read_buffer.fill(static_cast<std::byte>('\0')); //Clear Buffer
        //this->read_buffer.fill(static_cast<std::byte>('0')); //For Testing
        if (read_timeout not_eq SerialPort::ignore_timeout)
            this->read_timeout = read_timeout;//If read_timeout is not set to ignore_timeout, update the read_timeout else use old read_timeout
        this->port.async_read_some(boost::asio::buffer(this->read_buffer.data(), this->read_buffer.size()),
            boost::bind(&SerialPort::read_handler, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
        return true;
    }
    catch (const std::exception& ex)
    {
        PLOG_ERROR << ex.what();
        return false;
    }
}

Update

Remaining Code

bool SerialPort::open_port(const std::string& port_name, std::uint32_t baud_rate, std::uint8_t data_bits, std::uint8_t stop_bits,
    parity_t parity, flow_control_t flow_control, std::uint32_t read_timeout, std::uint32_t read_inter_byte_timeout,
    std::uint32_t write_timeout)
{
    try
    {
        this->port_name = port_name;
        if (not this->open_port())
            return false;
        if (not this->set_baud_rate(baud_rate).has_value())
            return false;
        if (not this->set_data_bits(data_bits).has_value())
            return false;
        if (not this->set_stop_bits(stop_bits).has_value())
            return false;
        if (not this->set_parity(parity).has_value())
            return false;
        if (not this->set_flow_control(flow_control).has_value())
            return false;
        this->read_timeout = read_timeout;
        if (read_inter_byte_timeout <= 0)
            this->read_inter_byte_timeout = 1;

#ifdef _WIN64
        BOOL            return_value;
        DCB             dcb = { 0 };
        COMMTIMEOUTS    timeouts = { 0 };
        if (this->line_mode)    //Set COM port to return data either at \n or \r
        {
            /*
            * If the function succeeds, the return value is nonzero.
            * If the function fails, the return value is zero. To get extended error information, call GetLastError.
            */
            return_value = GetCommState(this->native_port, &dcb);
            if (return_value)
            {
                if(this->new_line_character == '\r')
                    dcb.EofChar = '\r'; //Specify end of data character as carriage-return (\r)
                else // --> Default
                    dcb.EofChar = '\n'; //Specify end of data character as new-line (\n) 
            }
            else
            {
                PLOG_ERROR << "Error GetCommState : " << GetLastErrorAsString();
                return false;
            }
            /*
            * If the function succeeds, the return value is nonzero.
            * If the function fails, the return value is zero. To get extended error information, call GetLastError.
            */
            return_value = SetCommState(this->native_port, &dcb);
            if (not return_value)
            {
                PLOG_ERROR << "Error SetCommState : " << GetLastErrorAsString();
                return false;
            }
        }
        else    //Set COM port to return data on timeout
        {
            /*
            * If the function succeeds, the return value is nonzero.
            * If the function fails, the return value is zero. To get extended error information, call GetLastError.
            */
            return_value = GetCommTimeouts(this->native_port, &timeouts);
            if (return_value)
            {
                timeouts.ReadIntervalTimeout = this->read_inter_byte_timeout; // Timeout in miliseconds
                //timeouts.ReadTotalTimeoutConstant = 0;   //MAXDWORD; // in milliseconds - not needed
                //timeouts.ReadTotalTimeoutMultiplier = 0; // in milliseconds - not needed
                //timeouts.WriteTotalTimeoutConstant = 50; // in milliseconds - not needed
                //timeouts.WriteTotalTimeoutMultiplier = write_timeout; // in milliseconds - not needed
            }
            else
            {
                PLOG_ERROR << "Error GetCommTimeouts : " << GetLastErrorAsString();
                return false;
            }
            /*
            * If the function succeeds, the return value is nonzero.
            * If the function fails, the return value is zero. To get extended error information, call GetLastError.
            */
            return_value = SetCommTimeouts(this->native_port, &timeouts);
            if (not return_value)
            {
                PLOG_ERROR << "Error SetCommTimeouts : " << GetLastErrorAsString();
                return false;
            }
        }
#else //For Linux termios
#endif // _WIN64

        return true;
    }
    catch (const std::exception& ex)
    {
        PLOG_ERROR << ex.what();
        return false;
    }
}

void SerialPort::read_handler(const boost::system::error_code& error, std::size_t bytes_transferred)
{
    this->read_async(); // I realized I was calling read_async before reading data
    bool receive_complete{ false };
    try
    {
        if (error not_eq boost::system::errc::success)  //Error in serial port read
        {
            PLOG_ERROR << error.to_string();
            this->async_signal.emit(this->port_number, SerialPortEvents::read_error, error.to_string());
            return;
        }

        if (this->line_mode)
        {
            std::string temporary_recieve_data;
            std::transform(this->read_buffer.begin(), this->read_buffer.begin() + bytes_transferred, //Data is added to temporary buffer
                std::back_inserter(temporary_recieve_data), [](std::byte character) {
                    return static_cast<char>(character);
                }
            );
            boost::algorithm::trim(temporary_recieve_data); // Trim handles space character, tab, carriage return, newline, vertical tab and form feed
            //Data is further processed based on the Process logic
            receive_complete = true;
        }
        else    // Bulk-Data. Just append data to end of received_data string buffer.
                // Wait for timeout to trigger recevive_complete
        {
            //Test Function
            std::transform(this->read_buffer.begin(), this->read_buffer.begin() + bytes_transferred,
                std::back_inserter(this->received_data), [](std::byte character) {
                    return static_cast<char>(character);
                }
            );
            this->async_signal.emit(this->port_number, SerialPortEvents::read_data, this->received_data); //Data has been recieved send to server via MQTT
        }
            
    }
    catch (const std::exception& ex)
    {
        PLOG_ERROR << ex.what();
        this->async_signal.emit(this->port_number, SerialPortEvents::read_error, ex.what());
    }
}

Can you show the/a complete, self-contained minimal example. The code shown has no obvious issue (except some smells like the argument read_timeout soft-shadowing the member variable of the same name - and effectively being unused).

Here is a minimal self-contained example just from the code shown:

Live On Coliru

#include <boost/asio.hpp>
#include <boost/asio/serial_port.hpp>
#include <boost/bind/bind.hpp>
#include <iomanip>
#include <iostream>
namespace asio = boost::asio;

static inline std::ostream PLOG_ERROR(std::cerr.rdbuf());

struct SerialPort {
    static constexpr uint32_t ignore_timeout = -1;

    SerialPort(asio::any_io_executor ex, std::string dev) : port(ex, dev) {}

    bool read_async(uint32_t timeout_override) {
        try {
            read_buffer.fill({}); // Clear Buffer

            if (timeout_override not_eq SerialPort::ignore_timeout) {
                read_timeout = timeout_override;
            }
            using namespace asio::placeholders;

            port.async_read_some(
                asio::buffer(read_buffer),
                bind(&SerialPort::read_handler, this, error, bytes_transferred));

            return true;
        } catch (std::exception const& ex) {
            PLOG_ERROR << ex.what() << std::endl;
            return false;
        }
    }

  private:
    void read_handler(boost::system::error_code ec, size_t bytes_transferred) {
        std::cerr << "received " << bytes_transferred << " bytes (" << ec.message() << ")"
                  << std::endl;

        auto fmt = std::cerr.flags();
        for (auto b : read_buffer) {
            if (!bytes_transferred--)
                break;
            std::cerr << " " << std::hex << std::showbase << std::setfill('0')
                << std::setw(4) << static_cast<unsigned>(b);
        }
        std::cerr.flags(fmt);
        std::cerr << std::endl;

        if (!ec)
            read_async(ignore_timeout);
    }

    uint32_t read_timeout = 10;
    std::array<std::byte, 256> read_buffer{};
    asio::serial_port          port;
};

int main(int argc, char** argv) {
    asio::io_context ioc;

    SerialPort sp(make_strand(ioc), argc > 1 ? argv[1] : "/dev/ttyS0");
    sp.read_async(SerialPort::ignore_timeout);

    ioc.run();
    // ioc.run_for(std::chrono::seconds(1));
}

And testing using socat as described here: Virtual Serial Port for Linux

socat -d -d pty,raw,echo=0 pty,raw,echo=0

Local demo:

在此处输入图像描述

I figured out the problem.
In my SerialPort::read_handler method I was calling this->read_async() before reading/copying the data from buffer.
this->read_async() is resetting the buffer. The thing I don't understand is why this is haappening at random? Is this a scheduling issue (ie OS is causing context switching)?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM