Skip to main content
GameDev.net gamedev.net
🔒 Locked

Reading from serial port with boost asio?

Started by TriKri May 10, 2010 at 4:40 PM 8 replies 11.8k views
Original Post
TriKri
TriKri
Hi, we are programming a robot in a project at the university, and I thought you might be able to answer a question about boost asio's read function. We are going to communicate from a laptop with the robot, over bluetooth, via a firefly connected to the computers serial port. So long, I think I have managed the outgoing data correctly, using boost::asio::write. However, now I think I need some help with the incoming data (the boost asio documentation is, well ... limited). All messages starts with a header at one byte. The header specifies which message type has been sent. Each message has a unique length which I store in an array called message_lengths. I had imagined the code to loke something like this:

void user_interface::check_for_incoming_messages()
{
    boost::asio::streambuf response;
    boost::system::error_code error;
    std::string s1, s2;
    if (boost::asio::read(port, response, boost::asio::transfer_at_least(0), error)) {
        convert_streambuf_to_string(response, s1);
        int msg_code = s1[0];
        if (msg_code < 0 || msg_code >= NUM_MESSAGES) {
            // Handle error, invalid message header
        }
        if (boost::asio::read(port, response, boost::asio::transfer_at_least(message_lengths[msg_code]-s1.length()), error)) {
            response >> s2;
            // Handle the content of s1 and s2
        }
        else if (error != boost::asio::error::eof) {
            throw boost::system::system_error(error);
        }
    }
    else if (error != boost::asio::error::eof) {
        throw boost::system::system_error(error);
    }
}
Right now the code will just ignore a second message, if there will happen to be one. And I don't know what convert_streambuf_to_string will look like. Now I have just assumed that response will be reset each time read is called, but maybe that's wrong. Can someone who knows how to do this please help me? I would be really grateful.
Antheus
Antheus
Serial ports are incredibly dumb. They buffer a small number of bytes (16 or so) and that's it. If the buffer overflows, the data is lost, and application is left guessing which byte belongs where. Depending on flow control, errors might also not be detectable.

Unless some external or real-time synchronization is used to mark individual units, then serial ports are considered a stream. Same logic applies as with TCP stream. Keep reading from port, store any bytes read into a buffer. Scan this buffer periodically to see if it contains a full message. Only then process it.

read() will return number of bytes that were actually read, so always check it.

In this case, check if buffer has at least enough data for header byte and message length (len). If it does, check if buffer has at least header_size+message_length_size+len bytes in it. If yes, process that as a message and remove this from buffer. Otherwise, keep reading and appending new data to buffer.
TriKri
TriKri
Okay, but should I use boost::asio::streambuf to buffer the data? And then how can I extract the data from it so I can parse the message that has been sent?

You say that it can only store 16 bytes or so (I guess it uses some kind of cyclic buffer); the rest will be lost. I wonder if we possibly need to have a separate thread which only reads incoming messages, in case it can take so long time between two calls to the function that some data can be sent and lost in between? I'm using Qt as a widget toolkit and I don't know how long time it takes for it to process all it's events, although the application appears to run very smooth.
Antheus
Antheus
Quote:
Original post by TriKri
Okay, but should I use boost::asio::streambuf to buffer the data?

I've always used just a plain array or vector. Not sure what streambuf offers.

Quote:
And then how can I extract the data from it so I can parse the message that has been sent?

Um...
int MAX = 16384;char buffer[MAX];int len = 0;while (connected) {  boost::asio::buffer(&buffer[len], MAX-len);  int n_read = 0;  n_read = read(port, buffer, ...);   if (n_read > 0) {    len += n_read;    while (true) {  // while there is stuff in buffer      if (len == 0) break; // no data in buffer      if (msg_code < 0 || msg_code >= NUM_MESSAGES) {        connected = false;        break;      }      if (len < 2) break; // have header, but no message length      char message_type = buffer[0];      int message_len = buffer[1]; // assuming that is how it's stored      int end_offset = message_len + 2;      if (len >= end_offset) { // have at least one message        std::string s(&buffer[2], &buffer[end_offset]);        do_something_with_message(s);        // erase the message that was handled        for (int i = end_offset; i < len) buffer[i-len] = buffer;         len -= end_offset;      } else {        break; // no complete messages in buffer      }    }  }}


Quote:
You say that it can only store 16 bytes or so (I guess it uses some kind of cyclic buffer); the rest will be lost.

Serial ports are old. Oooooold old. What they use depends, but it's all fairly dated. Whether data is lost or not depends on flow control settings, but I've encountered fair share of devices which didn't support any, or where stream would become randomly corrupt or even fall out of sync by several bits.

Quote:
I wonder if we possibly need to have a separate thread which only reads incoming messages, in case it can take so long time between two calls to the function that some data can be sent and lost in between?
Depends. With flow control it should be possible for hardware to signal congestion and pause transmission.

A separate thread itself is not a reliable solution unless you happen to be on true real-time system.
TriKri
TriKri
How can I check if there is any incoming data at all to the serial port? If there is no incoming data, I don't want the function to lock...
TriKri
TriKri
Since I think I know the answer to my question (using async_read instead of read), I want to ask: How does an asynchronous function work? Will an interrupt be used which pauses the program flow and opens the handler function in the same thread, or will the asynchronous function open a new thread in which the handler function later will be called?
TriKri
TriKri
Quote:
Original post by TriKri
Since I think I know the answer to my question (using async_read instead of read), I want to ask: How does an asynchronous function work? Will an interrupt be used which pauses the program flow and opens the handler function in the same thread, or will the asynchronous function open a new thread in which the handler function later will be called?


Seems like the former to me when I'm reading this forum post.
Antheus
Antheus
Quote:
Original post by TriKri
Since I think I know the answer to my question (using async_read instead of read), I want to ask: How does an asynchronous function work?

Request/completion. Under asio one posts a request, when new data arrives, callback is invoked with bytes that were read.

Quote:
Will an interrupt be used which pauses the program flow and opens the handler function in the same thread, or will the asynchronous function open a new thread in which the handler function later will be called?

If serial ports behave differently than sockets then it should be documented in the code. If same processing model is used, then completion handlers will be invoked in the thread calling io_service.run().
TriKri
TriKri
Okay, so interruptions it is! :)
_moagstar_
_moagstar_
Quote:
Original post by Antheus
Not sure what streambuf offers.


You can attach it directly to an std::istream which may be helpful for deserialising the data.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.