0
votes

I have some code that needs to read from a serial device. It is a polled function that is called by a rate r.

The device spits data out in lines separated by \r\n and its fast, at around 100Hz. Whenever I poll, I want to read the entirety of the serial buffer. I am finding this hard to do with boost::asio because it doesn't seem to provide an available() function for me.

One of the methods I tried was to use read_until() but it does not solve my issue because there may be newer data in the buffer after the \r\n that read_until() stops at.

I tried consume() on the buffer after I read it, but that is still a hack job until I know that I have read the latest data from the device.

Anyone has advice on this issue?

1
This is something you'll have to take care of yourself, boost::asio doesn't think that \r\n is all that special. Use a buffer.Hans Passant
@HansPassant Of course, I did think about that and used a read_until() function to handle it. But what I'm thinking about is that if I use read_until(), it will read from the top of the buffer stack. I want the data that is the latest, and hence will be at the bottom of the stack. How do i reverse a buffer?Nopestradamus
Just don't replenish the buffer until it is empty. The serial port driver already has a receive buffer.Hans Passant

1 Answers

1
votes

As the serial port is read as a stream, rather than a random access handle, one must read the data in the order in which it was received. Given this behavior, here are a few options to getting the latest data:

  • Read from the stream until a read operation fails with a timeout. While Boost.Asio neither provides non-blocking synchronous reads nor reads with timeouts for serial I/O objects, one can implement this behavior using asynchronous reads and timers. Boost.Asio provides a collection of timeout examples. As the "\r\n" character sequence is being used for a data boundary, consider using async_read_until() to respect data boundaries without having to introduce boundary handling within the application code. If the ingress rate is greater than the consumption rate, then a timeout may not be deterministic.
  • Use the serial port's native_handle() with system specific calls to determine the amount of bytes available for reading. Then, have a reading strategy that uses the amount of bytes available to determine when to stop reading. Be aware that the amount of bytes available may not fall directly on a data boundary. Therefore, the application will need to handle fragmentation. For instance, one could async_read_until() with a timeout. However, once the previously known amount of bytes available have been consumed, the asynchronous call chain can be explicitly stopped. This provides a deterministic end for the asynchronous call chain, even if the ingress rate exceeds the consumption rate. Consult the system's documentation to determine how to query for the available bytes ready to be read, but it is often ioctl(..., FIONREAD, ...) on Linux, and ClearCommError() on Windows.
  • Decouple consuming data from the serial port and querying for the most recently received data. With a separation of responsibilities, a consumer thread would continuously read from the serial port via an async_read_until() call chain and persist the most recently read data. Depending on how one accesses the most recent data, synchronization mechanisms, such as mutexes, may be necessary to avoid race conditions. One alternative that removes the need to lock after reading form the serial port is to use a future/promise, and post an operation that fulfills the promise into the same strand that is running the async_read_until() loop.
  • If data is being written at a high enough frequency where waiting for a complete sample within the poll is acceptable, then one could flush the serial port's receive buffer, causing all data to be discarded. Once discarded, one could read from the serial port, waiting for a write to occur, resulting in the most recent data. As flushing the buffer may discard part of a sample, one may need to read up to the second data boundary to guarantee a complete sample has been read.