I am working on serial port routines using Boost::Asio.
I am configuring the port using the wrappers provided by Boost::Asio.
In my application either the end of data is denoted by receive timeout or \r\n
line termination sequence.
As Boost::Asio doesn't provide wrapper to access and configure DCB
- Windows, termios
- Linux structure in order to configure timeout and/or line-ending; I am accessing native port handle/file descriptor that is returned via the native_handle
wrapper and configuring the structures manually.
However it seems I am unable to configure the port properly.
Even if I configure the end of data to be denoted by \n
the data is partially returned in chunks.
Similarly the data is also returned before timeout has occurred.
Update
The system works in 2 modes
- Line Mode -> The data is read line by line and the processed. Line ending characters
\r
,\n
or\r\n
. - Bulk Mode -> Multi-line data is read. Once no data is received for pre-dertmined interval, the data is said to be completely received. e.g. If I don't receive new data for 50 milli-seconds I consider the transfer to be complete.
Code
bool SerialPort::open_port(void)
{
try
{
this->port.open(this->port_name);
this->native_port = this->port.native_handle();
return true;
}
catch (const std::exception& ex)
{
PLOG_FATAL << ex.what();
}
return false;
}
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());
}
}
Supporting Function
std::optional<std::uint32_t> SerialPort::set_baud_rate(std::uint32_t baud_rate)
{
boost::system::error_code error;
std::uint32_t _baud_rate = 1200;
switch (baud_rate)
{
case 1200:
case 2400:
case 4800:
case 9600:
case 115200:
_baud_rate = baud_rate;
break;
default:
_baud_rate = 1200;
break;
}
this->port.set_option(boost::asio::serial_port_base::baud_rate(_baud_rate), error);
if (error)
{
PLOG_FATAL << error.message();
return std::nullopt;
}
return baud_rate;
}
std::optional<std::uint8_t> SerialPort::set_data_bits(std::uint8_t data_bits)
{
boost::system::error_code error;
std::uint32_t _data_bits = 8;
switch (data_bits)
{
case 7:
case 8:
_data_bits = data_bits;
break;
default:
_data_bits = 8;
break;
}
this->port.set_option(boost::asio::serial_port_base::character_size(_data_bits), error);
if (error)
{
PLOG_FATAL << error.message();
return std::nullopt;
}
return data_bits;
}
std::optional<std::uint8_t> SerialPort::set_stop_bits(std::uint8_t stop_bits)
{
boost::system::error_code error;
switch (stop_bits)
{
case 1:
this->port.set_option(boost::asio::serial_port_base::stop_bits(boost::asio::serial_port_base::stop_bits::one), error);
break;
case 2:
this->port.set_option(boost::asio::serial_port_base::stop_bits(boost::asio::serial_port_base::stop_bits::two), error);
break;
default:
this->port.set_option(boost::asio::serial_port_base::stop_bits(boost::asio::serial_port_base::stop_bits::one), error);
break;
}
if (error)
{
PLOG_FATAL << error.message();
return std::nullopt;
}
return stop_bits;
}
std::optional<parity_t> SerialPort::set_parity(parity_t parity)
{
boost::system::error_code error;
switch (parity)
{
case Parity::none:
this->port.set_option(boost::asio::serial_port_base::parity(boost::asio::serial_port_base::parity::none), error);
break;
case Parity::even:
this->port.set_option(boost::asio::serial_port_base::parity(boost::asio::serial_port_base::parity::even), error);
break;
case Parity::odd:
this->port.set_option(boost::asio::serial_port_base::parity(boost::asio::serial_port_base::parity::odd), error);
break;
default:
this->port.set_option(boost::asio::serial_port_base::parity(boost::asio::serial_port_base::parity::none), error);
break;
}
if (error)
{
PLOG_FATAL << error.message();
return std::nullopt;
}
return parity;
}
std::optional<flow_control_t> SerialPort::set_flow_control(flow_control_t flow_control)
{
boost::system::error_code error;
switch (flow_control)
{
case FlowControl::none:
this->port.set_option(boost::asio::serial_port_base::flow_control(boost::asio::serial_port_base::flow_control::none), error);
break;
case FlowControl::hardware:
this->port.set_option(boost::asio::serial_port_base::flow_control(boost::asio::serial_port_base::flow_control::hardware), error);
break;
case FlowControl::software:
this->port.set_option(boost::asio::serial_port_base::flow_control(boost::asio::serial_port_base::flow_control::software), error);
break;
default:
this->port.set_option(boost::asio::serial_port_base::flow_control(boost::asio::serial_port_base::flow_control::none), error);
break;
}
if (error)
{
PLOG_FATAL << error.message();
return std::nullopt;
}
return flow_control;
}