Reputation: 11
I am trying to write a serial communication protocol between a C++ application and ESP32 microcontroller. The basic structure uses 1 byte headers specify the category of data followed by various lengths of byte array data (I want to encode integers and floats mainly). I have tested the ESP32 side of the protocol using a binary terminal application and it has the expected behavior, so the issue is with the C++ application side, which uses the Boost ASIO library (v 1.82.0). Any time I attempt to send binary data using the synchronous write method, I get the error "The handle is invalid", so I think the main issue is I am unable to get byte array data into a format acceptable to the library.
I have checked and double checked that I have the right port configuration (baud rate, etc.). I have tried various methods of passing the data to the write function including just passing a pointer to the byte array, wrapping in vector<char>, and converting to C++ string but all of these get the same error. Also, I assume reading the data will just involve reversing this conversion, but I am not sure. Below are my basic methods for configuring the port, sending and receiving data so far.
bool SerialInterface::begin(const char* port, long baudRate) {
try {
serialPort.open(port);
serialPort.set_option(asio::serial_port_base::baud_rate(baudRate));
serialPort.set_option(asio::serial_port_base::character_size(8));
serialPort.set_option(asio::serial_port_base::parity(asio::serial_port_base::parity::none));
serialPort.set_option(asio::serial_port_base::stop_bits(asio::serial_port_base::stop_bits::one));
serialPort.set_option(asio::serial_port_base::flow_control(asio::serial_port_base::flow_control::none));
}
catch (boost::system::system_error& e) {
cerr << "Error opening serial port: " << e.what() << endl;
return false; // Error opening the port
}
return true; // Success
}
void SerialInterface::sendByte(uint8_t data) {
try {
if (serialPort.is_open()) {
//Should convert byte array into acceptable string format for write buffer
uint8_t bytes[1] = { data };
string buffer(reinterpret_cast<char*>(bytes), 1);
asio::write(serialPort, asio::buffer(buffer, 1));
}
}
catch (system::system_error& e) {
//This exception gets thrown
std::cerr << "Exception: " << e.what() << std::endl;
}
}
uint8_t SerialInterface::readByte() {
if (!serialPort.is_open()) {
return 0;
}
uint8_t buffer[1];
system::error_code error;
try {
//May need to reverse the conversion done when sending data?
size_t bytes_read = asio::read(serialPort, boost::asio::buffer(buffer, 1), error);
if (error || bytes_read == 0) {
return 0;
}
}
catch (system::system_error& e) {
std::cerr << "Exception: " << e.what() << std::endl;
return 0;
}
return buffer[0];
}
Upvotes: 1
Views: 32
Reputation: 393664
"I get the error "The handle is invalid", so I think the main issue is I am unable to get byte array data into a format acceptable to the library"
I get the error "the color is red" so I think the main issue is that the color is green?
The handle refers to the file-descriptor/native handle used to identify the stream opened to the serial device. If it is bad, it implies that it isn't valid. Perhaps it wasn't opened, or it has been closed.
The only thing we can imagine is that you don't keep the SerialInterface
instance around long enough. When it is destructed, the serial_port
will be destructed along with it. But that's part of the code not shown.
Here's my fixed listing, which does work as expected:
#include <iostream>
#include <boost/asio.hpp>
#include <boost/asio/serial_port.hpp>
namespace asio = boost::asio;
struct SerialInterface {
SerialInterface(asio::any_io_executor ex) : port_(ex) {}
bool begin(const char* port, long baudRate);
void sendByte(uint8_t data);
uint8_t readByte();
private:
using system_error = boost::system::system_error;
using error_code = boost::system::error_code;
using SP = asio::serial_port;
SP port_;
};
bool SerialInterface::begin(char const* port, long baudRate) try {
port_.open(port);
port_.set_option(SP::baud_rate(baudRate));
port_.set_option(SP::character_size(8));
port_.set_option(SP::parity(SP::parity::none));
port_.set_option(SP::stop_bits(SP::stop_bits::one));
port_.set_option(SP::flow_control(SP::flow_control::none));
return true; // Success
} catch (system_error& e) {
std::cerr << "Error opening serial port: " << e.what() << std::endl;
return false; // Error opening the port
}
void SerialInterface::sendByte(uint8_t data) try {
if (port_.is_open()) {
// Should convert byte array into acceptable string format for write buffer
std::vector const bytes{data}; // just as another example
asio::write(port_, asio::buffer(bytes));
}
} catch (system_error const& e) {
std::cerr << "Exception: " << e.code().message() << std::endl;
}
uint8_t SerialInterface::readByte() {
std::array<uint8_t, 1> buffer{0};
try {
error_code ec;
size_t bytes_read = asio::read(port_, asio::buffer(buffer), ec);
if (ec || bytes_read == 0)
buffer = {0};
} catch (boost::system::system_error const& e) {
std::cerr << "Exception: " << e.code().message() << std::endl;
buffer = {0};
}
return buffer.front();
}
int main(int argc, char* argv[]) {
asio::io_context io;
SerialInterface serial(io.get_executor());
if (serial.begin(argc > 1 ? argv[1] : "/dev/pts/5", 9600)) {
serial.sendByte(0x42);
std::cout << "Received: " << std::showbase << std::hex << +serial.readByte() << std::endl;
}
}
Test commands:
socat -d -d pty,raw,echo=0 pyt,raw,echo=0
to emulate a serial device. It shows the endpoints as /dev/pts/5
and /dev/pts/6
./build/sotest /dev/pts/5
printf '\x43\x44\x45' | tee -a /dev/pts/6
provides some data sent back to the C++ codestdbuf -i 0 -o 0 -- cat /dev/pts/6 | xxd -c 100
to print a hex-dump byte-for-byte print of the data received from the C++ sideUpvotes: 1