I'm trying to use Boost.Asio to read from and write to a serial port. Here is my code:
void async_read(boost::asio::serial_port& serial_port)
{
auto buffer = std::make_shared<std::vector<uint8_t>>(64);
serial_port.async_read_some(boost::asio::buffer(*buffer),
[buffer, &serial_port](const boost::system::error_code& error, size_t bytes_read)
{
if (error)
{
std::cout << "Error reading serial port: " << error.message() << std::endl;
return;
}
std::string message(buffer->begin(), buffer->end());
std::cout << "Read " << bytes_read << " bytes:\t" << message << std::endl;
async_read(serial_port);
});
}
void async_write(boost::asio::serial_port& serial_port)
{
auto timer = std::make_shared<boost::asio::deadline_timer>(serial_port.get_io_service(), boost::posix_time::seconds(1));
timer->async_wait(
[&serial_port, timer](const boost::system::error_code& error)
{
if (error)
{
std::cout << "Timer error: " << error.message() << std::endl;
return;
}
auto message = std::make_shared<std::string>("Hello\n");
boost::asio::async_write(serial_port, boost::asio::buffer(*message),
[message, &serial_port](const boost::system::error_code& error, size_t bytes_sent)
{
if (error)
{
std::cout << "Error writing to serial port: " << error.message() << std::endl;
return;
}
std::cout << "Wrote " << bytes_sent << " bytes" << std::endl;
async_write(serial_port);
});
});
}
int main()
{
boost::asio::io_service service;
auto serial_port = boost::asio::serial_port(service, "/dev/ttyUSB0");
serial_port.set_option(boost::asio::serial_port::baud_rate(9600));
async_read(serial_port);
async_write(serial_port);
service.run();
}
At the opposite end of the serial cable, I have a separate machine running cat /dev/ttyTHS0.
My issue is that whenever the program performs an async_write, those same bytes are immediately handled by the async_read completion handler - even though the remote machine isn't sending anything.
I'm not sure if the root cause is the code, or because I'm using cat at the other end. When cat is not running, I'm not having the issue.
Running the above code, (with cat /dev/ttyTHS0 at the other end) gives output like this:
Wrote 6 bytes
Read 7 bytes: Hello
Wrote 6 bytes
Read 3 bytes: Hel
Read 4 bytes: lo
Wrote 6 bytes
Read 7 bytes: Hello
Wrote 6 bytes
Read 1 bytes: H
Read 6 bytes: ello
Wrote 6 bytes
Read 7 bytes: Hello
Maybe I'm missing something obvious, but any help is appreciated! Thank you!
This is the expected behaviour for a serial port. These were typically used to connect a terminal like a vt100 to a computer. When you type on the keyboard, the character is sent to the computer, and the serial port echoes it back to the vt100 screen where it is displayed.
If you run stty -a -F /dev/ttyTHS0 on the remote you will see the setting echo is on (before you ran your program on the remote). You can switch it off with stty -echo -F /dev/ttyTHS0, when it will show as -echo.
Typically, when serial ports are used for comms between computers, the application will set the port to raw, noecho. This is to avoid certain translations being done by the serial port driver, so the data arrives unchanged at the application.
Related
I have a program that uses the modbus protocol to send chunks of data between a 64-bit Raspberry Pi 4 (running Raspberry Pi OS 64) and a receiving computer. My intended setup for the serial port is baud rate of 57600, 8 data bits, two stop bits, no flow control, and no parity. I have noticed that the data is only properly interpreted when the receiving computer is set to view one stop bit and no parity, regardless of the settings on the Raspberry Pi.
What is interesting is this program works as expected when run on Windows, only the Pi has caused problems at the moment. This was originally seen in ASIO 1.20 and can still be reproduced in 1.24 on the Pi.
I wrote a minimal example that reproduces the issue for me on the Pi:
#include <asio.hpp>
#include <asio/serial_port.hpp>
#include <iostream>
int main(void) {
asio::io_service ioService;
asio::serial_port serialPort(ioService, "/dev/serial0");
serialPort.set_option(asio::serial_port_base::baud_rate(57600));
serialPort.set_option(asio::serial_port_base::character_size(8));
serialPort.set_option(asio::serial_port_base::stop_bits(asio::serial_port_base::stop_bits::two));
serialPort.set_option(asio::serial_port_base::flow_control(asio::serial_port_base::flow_control::none));
serialPort.set_option(asio::serial_port_base::parity(asio::serial_port_base::parity::none));
std::string test("Test#");
asio::write(serialPort, asio::buffer(test.data(), test.size()));
std::array<char, 5> buf;
asio::read(serialPort, asio::buffer(buf.data(), buf.size()));
std::cout << "Received: " << std::string(std::begin(buf), std::end(buf)) << std::endl;
serialPort.close();
return 0;
}
I looked closer at the issue and used a Saleae Logic Analyzer to see what data is being sent between the machines. Below you can see the expected behavior for a successful run, this is when the test is run on Windows.
Here you can see the behavior that occurs on the Raspberry Pi when it runs the test code. The analyzer fails to interpret the data using the parameters set in the code.
Below you can see that when the analyzer is set with one stop bit rather than two, it interprets the hex without an issue.
Overall you can see that the issue takes place on the Pi's end because of the responses seen in the logic analyzer. The program running on the Pi can interpret messages sent to it using the given parameters without any issue, however when it tries to reply to those messages it seems that the ASIO port settings are not being applied.
Any insight that can be provided would be very helpful. Let me know if you need more information. Thanks for the help!
UPDATE: Ran #sehe's test code as they recommended and results are as follows:
baud_rate: Success
character_size: Success
stop_bits: Success
flow_control: Success
parity: Success
parity: 0 (Success)
flow_control: 0 (Success)
stop_bits: 0 (Success)
character_size: 8 (Success)
baud_rate: 57600 (Success)
ModbusTest: Main.cpp:37: int main(): Assertion `sb.value() == serial_port::stop_bits::two' failed.
It appears that the setting for stop bits did not successfully apply and rather failed silently. Any ideas on how to proceed with further debugging?
UPDATE 2: Also wanted to mention that I ran minicom with the same hardware setup and was able to communicate without issue using two stop bits.
Very solid debugging and analysis info.
I don't immediately see something wrong with the code. My intuition was to separate construction from open(), so the options could be set prior to opening, but it turns out that is just not working.
So maybe you can verify that the set_option calls had their desired effect. I can imagine hardware limitations that don't allow certain configuration?
This should definitely uncover any unexpected behavior:
Live On Coliru
//#undef NDEBUG
#include <boost/asio.hpp>
#include <boost/asio/serial_port.hpp>
namespace asio = boost::asio;
using asio::serial_port;
using boost::system::error_code;
#include <iostream>
int main() {
asio::io_service ioService;
asio::serial_port sp(ioService);
sp.open("/dev/serial0");
serial_port::baud_rate br{57600};
serial_port::character_size cs{8};
serial_port::stop_bits sb{serial_port::stop_bits::two};
serial_port::flow_control fc{serial_port::flow_control::none};
serial_port::parity pb{serial_port::parity::none};
error_code ec;
if (!ec) { sp.set_option(br, ec); std::cout << "baud_rate: " << ec.message() << std::endl; }
if (!ec) { sp.set_option(cs, ec); std::cout << "character_size: " << ec.message() << std::endl; }
if (!ec) { sp.set_option(sb, ec); std::cout << "stop_bits: " << ec.message() << std::endl; }
if (!ec) { sp.set_option(fc, ec); std::cout << "flow_control: " << ec.message() << std::endl; }
if (!ec) { sp.set_option(pb, ec); std::cout << "parity: " << ec.message() << std::endl; }
sp.get_option(pb, ec); std::cout << "parity: " << pb.value() << " (" << ec.message() << ")" << std::endl;
sp.get_option(fc, ec); std::cout << "flow_control: " << fc.value() << " (" << ec.message() << ")" << std::endl;
sp.get_option(sb, ec); std::cout << "stop_bits: " << sb.value() << " (" << ec.message() << ")" << std::endl;
sp.get_option(cs, ec); std::cout << "character_size: " << cs.value() << " (" << ec.message() << ")" << std::endl;
sp.get_option(br, ec); std::cout << "baud_rate: " << br.value() << " (" << ec.message() << ")" << std::endl;
assert(br.value() == 57600);
assert(cs.value() == 8);
assert(sb.value() == serial_port::stop_bits::two);
assert(fc.value() == serial_port::flow_control::none);
assert(pb.value() == serial_port::parity::none);
std::string test("Test#");
write(sp, asio::buffer(test));
std::array<char, 5> buf;
auto n = read(sp, asio::buffer(buf));
std::cout << "Received: " << std::string(buf.data(), n) << std::endl;
}
Which on my system (Ubuntu host, using /dev/ttyS0) prints e.g.
baud_rate: Success
character_size: Success
stop_bits: Success
flow_control: Success
parity: Success
parity: 0 (Success)
flow_control: 0 (Success)
stop_bits: 2 (Success)
character_size: 8 (Success)
baud_rate: 57600 (Success)
As expected
I was able to discover the cause and fix the problem!
I am using a Raspberry Pi 4 for this project and interfacing with GPIO pins 14/15 to use /dev/serial0. With the default configuration /dev/serial0 maps to /dev/ttyS0 which is a mini UART and is not capable of using multiple stop bits, etc.
Disabling Bluetooth sets the symlink to map to /dev/ttyAMA0 which is a full UART and is capable of parity and multiple stop bits.
In /boot/config.txt I added the following lines:
[all]
dtoverlay=disable-bt
If you are experiencing a similar problem with /dev/serial0, this may be worth a shot.
I have a server which is written with by boost.asio. This server gets the file from the client and write it to disk. I have just a problem with that. When server get the file, it write it to disk when it recieved the file completely. I wanted server write the buffer to disk in real-time fashion. For example, server write to disk every 100kb size of the file it get from the client. I have written the following code but I don't know how can I edited to get to this goal.
void Session::DoReadFileContent(size_t arg_bytes_transferred)
{
if (arg_bytes_transferred > 0)
{
m_outputFile.write(m_buffer.data(), static_cast<std::streamsize>(arg_bytes_transferred));
if (m_outputFile.tellp() >= static_cast<std::streamsize>(m_fileSize))
{
std::cout << "Received file: " << m_fileName << std::endl;
return;
}
}
auto self = shared_from_this();
m_socket.async_read_some(boost::asio::buffer(m_buffer.data(), m_buffer.size()),
[this, self](boost::system::error_code arg_error_code, size_t arg_bytes)
{
DoReadFileContent(arg_bytes);
});
}
First off, in that case it seems better to read explicit sizes of data instead of read_some which reads whatever is available.
In this pattern, it becomes easier to track "remaining bytes receivable" than m_fileSize.
Here's some minor re-shufflings that made your code into a self-contained example. It expects a server to send a line of text giving the payload size and output filename, followed by the contents of that file. An example server can be run with netcat e.g.:
(stat -c '%soutput.dat' main.cpp; cat main.cpp) | netcat -l -p 6969
Live On Coliru
#include <boost/asio.hpp>
#include <fstream>
#include <iostream>
using boost::system::error_code;
using boost::asio::ip::tcp;
struct Session : std::enable_shared_from_this<Session> {
Session(boost::asio::io_context& io, uint16_t port)
: m_socket(io)
{
m_socket.connect({{}, port});
}
void Start();
void DoReadFileContent(size_t transferred = 0);
private:
std::array<char, 1024> m_buffer;
std::streamsize m_remainingSize = 0;
std::string m_fileName = "noname.dat";
std::ofstream m_outputFile;
tcp::socket m_socket;
};
void Session::Start() {
// Reading a size (in text for simplicity) and subsequently receive as many bytes
//
// I'm keeping this sync for simplicity, because you probably already have
// this coded somehwere
boost::asio::streambuf buf;
error_code ec;
auto n = read_until(m_socket, buf, "\n", ec);
std::istream is(&buf);
if (is >> m_remainingSize && getline(is, m_fileName)) {
std::cerr << "Protocol trace: n:" << n << ", fileName:" << m_fileName << " payload_size:" << m_remainingSize << "\n";
m_outputFile.exceptions(std::ios::failbit | std::ios::badbit);
m_outputFile.open(m_fileName, std::ios::binary);
// write excess buffer contents as part of payload
if (buf.size()) {
std::cerr << "Writing " << buf.size() << " bytes\n";
m_remainingSize -= buf.size();
m_outputFile << &buf;
}
DoReadFileContent();
} else {
std::cerr << "Protocol error, payload_size expected\n";
}
}
void Session::DoReadFileContent(size_t transferred) {
if (transferred > 0) {
std::cerr << "Writing " << transferred << " bytes\n";
m_remainingSize -= transferred;
m_outputFile.write(m_buffer.data(), transferred);
}
if (m_remainingSize <= 0) {
std::cout << "Completed file: " << m_fileName << std::endl;
return;
}
auto self = shared_from_this();
auto expect = std::min(size_t(m_remainingSize), m_buffer.size());
std::cout << "Trying to receive next " << expect << " bytes" << std::endl;
async_read(m_socket,
boost::asio::buffer(m_buffer.data(), expect),
[this, self](error_code ec, size_t arg_bytes) {
std::cerr << "async_read: " << ec.message() << " - " << arg_bytes << " bytes\n";
if (!ec) {
DoReadFileContent(arg_bytes);
}
});
}
int main() {
boost::asio::io_context io;
std::make_shared<Session>(io, 6868) // download from port 6868
->Start();
io.run(); // complete
}
Testing with
(stat -c '%soutput.dat' main.cpp; cat main.cpp) | netcat -l -p 6868&
./a.out
md5sum main.cpp output.dat
Prints, e.g.:
Protocol trace: n:15, fileName:output.dat payload_size:2654
Trying to receive next 1024 bytes
async_read: Success - 1024 bytes
Writing 1024 bytes
Trying to receive next 1024 bytes
async_read: Success - 1024 bytes
Writing 1024 bytes
Trying to receive next 606 bytes
async_read: Success - 606 bytes
Writing 606 bytes
Completed file: output.dat
The last two lines
b4eec7203f6a1dcbfbf3d298c7ec0832 main.cpp
b4eec7203f6a1dcbfbf3d298c7ec0832 output.dat
indicate that the received file is identical to the original.
Notes:
packets are delivered in unspecified sizes, on my system e.g. the same file is received as:
Protocol trace: n:15, fileName:output.dat payload_size:2654
Writing 497 bytes
Trying to receive next 1024 bytes
async_read: Success - 1024 bytes
Writing 1024 bytes
Trying to receive next 1024 bytes
async_read: Success - 1024 bytes
Writing 1024 bytes
Trying to receive next 109 bytes
async_read: Success - 109 bytes
Writing 109 bytes
Completed file: output.dat
b4eec7203f6a1dcbfbf3d298c7ec0832 main.cpp
b4eec7203f6a1dcbfbf3d298c7ec0832 output.dat
Note that it starts out with 497 bytes already in the input buffer from the read_until.
The protocol is not secure:
the file names should be validated. Just imagine what happens if the file would be '/home/sehe/myimportant_file.txt' or worse, say /dev/sde1 and we have permissions to do raw block device access...
you might want to specify a amximum size for streambuf, so that if you get a fuzzer that doesn't ever send a '\n' you wouldn't just gobble up all RAM
the error handling on file IO is very rough. I used io exceptions, but you probably want to check for m_outputFile.good() instead at various places
i am trying to generate a class for reading from a specific serial device.
For the start process it is necessary to send a char '1', then i have to wait for a response (254 and 255).
Within a period of 10 milliseconds i must sent the next command to the device, but this time the command length is 5 char.
When the communication hasn´t been send in the correct time, the device will run into a timeout and is sending me 255,255,255,2,4.
So i need different sizes of reading and the most importing thing for me is a timeout for the communication, cause otherwise the system will stop working by missing some values.
Therefore i have tried to generate a class using boost::asio::async_read.
It is working in the correct way, i can define the timeout,also the size of bytes to be read. When the device isn´t sending the correct size, the routine is going to be left.
But only the first time, when i try it a second time, the device isn´t sending me something. I have tried to use .open again, but it isn´t solving the issue. Also deactivating the close-function isn´t solving the issue, then the routine is running into an error.
Can someone give me a small tip for my issue. Maybe i am to blind to see my problem.... Bernd
ConnectionWithTimeout::ConnectionWithTimeout(int timeout_)
: timer_(io_service_, boost::posix_time::milliseconds(timeout_))
, serial_port_(io_service_) {
}
void ConnectionWithTimeout::ReadNumberOfChars(int numberOfCharactersToRead_)
{
buffer_.resize(numberOfCharactersToRead_);
for (int i = 0; i < numberOfCharactersToRead_; ++i) {
std::cout << "Clear Buffer[" << i << "]" << std::endl;
buffer_[i] = 0;
}
timer_.async_wait(boost::bind(&::ConnectionWithTimeout::Stop, this));
//async read from serial port
boost::asio::async_read(serial_port_, boost::asio::buffer(buffer_),
boost::bind(&ConnectionWithTimeout::ReadHandle, this,
boost::asio::placeholders::error));
io_service_.run();
}
void ConnectionWithTimeout::Stop() {
std::cout << "Connection is being closed." << std::endl;
serial_port_.close();
std::cout << "Connection has been closed." << std::endl;
}
void ConnectionWithTimeout::ReadHandle(const boost::system::error_code& ec) {
if (ec) {
std::cout << "The amount of data is to low: " << ec << std::endl;
for (std::vector<char>::iterator it = buffer_.begin();
it != buffer_.end(); ++it)
{
std::cout << int(*it) << std::endl;
}
}
else {
std::cout << "The amount of data is correct: " << ec << std::endl;
for (std::vector<char>::iterator it = buffer_.begin(); it !=
buffer_.end(); ++it)
{
std::cout << int(*it) << std::endl;
}
}
}
Here are the simple echo server I'm working on, the server will accept the request from client and return what client sends to it. The program works fine with socat, but will freeze when using my own client.
The problem that my old code has is that I use read instead of read_some. read will block the pipe until it reads certain number of bytes or get a broken pipe exception, whereas read_some will read a chunk at a time. The updated version uses read_some to read input stream and check if the last character the program read is \0, if it is \0, that means it reaches the end of command, so it will echo back. This works because I only pass string literals and there is no binary data in the pipe.
The code of the server is
using namespace std;
const char* epStr = "/tmp/socketDemo";
int main() {
namespace local = boost::asio::local;
boost::asio::io_service io_service;
::unlink(epStr);
local::stream_protocol::endpoint ep(epStr);
local::stream_protocol::acceptor acceptor(io_service, ep);
while(1) {
local::stream_protocol::socket *socket = new local::stream_protocol::socket(io_service);
acceptor.accept(*socket);
char buf[2048] = {0};
boost::system::error_code error;
size_t len = 0;
while(1) {
len += socket->read_some(boost::asio::buffer(buf + len, 2048 - len));
cout << "read " << len << endl;
if (buf[len] == '\0') {
break;
}
}
cout << "read " << len << " bytes" << endl;
cout << buf << endl;
boost::asio::write(*socket, boost::asio::buffer(buf, len), boost::asio::transfer_all());
}
}
When testing the server with socat command, for example
echo "12345" | socat - UNIX-CONNECT:/tmp/socketDemo
it will return the desired result.
My client code is
const char* epStr = "/tmp/socketDemo";
int main(int argc, const char* argv[]) {
boost::asio::io_service io_service;
boost::asio::local::stream_protocol::endpoint ep(epStr);
boost::asio::local::stream_protocol::socket socket(io_service);
socket.connect(ep);
boost::asio::write(socket, boost::asio::buffer(argv[1], strlen(argv[1])), boost::asio::transfer_all());
char buf[1024] = {0};
size_t len = 0;
while(1) {
len += socket.read_some(boost::asio::buffer(buf + len, 2048 - len));
std::cout << "read " << len << std::endl;
if (buf[len] == '\0') {
break;
}
}
std::cout << "read " << len << " bytes\n";
std::cout << buf << std::endl;
socket.close();
When execute the client, at first both have no output, after I killed the client, the server will output that it reads n bytes and get a broken pipe exception.
Can this be caused by the read function in the server? If so is there a way to let it know how much data it should read without sending the size of data chunk at the beginning of each message? I am also wondering why socat can work with this server without any problem? Thanks!
I am also wondering why socat can work with this server without any
problem?
Probably because socat closes the socket and your client doesn't.
If so is there a way to let it know how much data it should read
without sending the size of data chunk at the beginning of each
message?
For instance, reading one byte at a time until you read an end-of-message character, assuming that you're defining / using a protocol that includes EOM.
I'm developing a client-server app, both sides of which use boost::asio.
I'm trying to send a large package of data over TCP (356 kb)
On server side, I write like:
boost::asio::async_write(Msocket,
boost::asio::buffer(sendBuffer,dataLen),
boost::bind(&ServerSession::onDataWrite,
this, boost::asio::placeholders::error,
boost::asio::placeholders::bytes_trasferred));
The onDataWrite is simple:
void ServerSession::onDataWrite(const boost::system::error_code& error, const std::size_t bytesSent) {
if (error) {
std::cout << "Error " << error << " while sending data" << std::endl;
}
}
On client side:
int readSize = ...; // defined from msg header, in this case equals 300 kbytes.
boost::asio::async_read(*Msocket,
boost::asio::buffer(recvBuffer, 50*1024*1024),
boost::asio::transfer_exactly(readSize),
boost::bind(&ClientSession::onDataRead,
this, boost::asio::placeholders::error,
boost::asio::placeholders::bytes_trasferred,
readSize));
And onDataRead is:
void ClientSession::onDataRead(const boost::system::error_code& error, const std::size_t bytesRecvd, const int readSize) {
if (error || bytesRecvd != readSize) {
std::cout << "Error " << error << " while getting data, expect " << readSize <<", got " << bytesRecvd << std::endl;
}
}
During write, server side prints
Error system:10014 while sending data
And client prints
Error system:0 while getting data, expect 393216, got 131064
While 131064 = 128kb - 8 bytes of header.
It looks like this 128-kb issue is caused by send/receive buffer overflow. But I though Boost will take care about those buffers itself, transparently for me.
What do I misunderstand?