Skip to content

Instantly share code, notes, and snippets.

@yoggy
Created August 11, 2012 10:58
Show Gist options
  • Star 59 You must be signed in to star a gist
  • Fork 26 You must be signed in to fork a gist
  • Save yoggy/3323808 to your computer and use it in GitHub Desktop.
Save yoggy/3323808 to your computer and use it in GitHub Desktop.
SerialPort class sample using boost::asio::serial_port
#include "stdafx.h"
#include "SerialPort.h"
int main(int argc, char* argv[])
{
bool rv;
SerialPort::print_devices();
std::string name = SerialPort::get_port_name(0);
SerialPort serial;
rv = serial.start(name.c_str(), 115200);
if (rv == false) {
return -1;
}
// initialize
serial.end_of_line_char(0x0d);
serial.write_some("BI010\r\n");
serial.write_some("PE011\r\n");
serial.write_some("RA01000\r\n");
// wait
Sleep(5 * 1000);
serial.stop();
return 0;
}
#include "StdAfx.h"
#include <Setupapi.h>
#pragma comment(lib, "Setupapi.lib")
#include "SerialPort.h"
SerialPort::SerialPort(void) : end_of_line_char_('\n')
{
}
SerialPort::~SerialPort(void)
{
stop();
}
char SerialPort::end_of_line_char() const
{
return this->end_of_line_char_;
}
void SerialPort::end_of_line_char(const char &c)
{
this->end_of_line_char_ = c;
}
std::vector<std::string> SerialPort::get_port_names()
{
std::vector<std::string> names;
BOOL rv;
DWORD size;
GUID guid[1];
HDEVINFO hdevinfo;
DWORD idx = 0;
SP_DEVINFO_DATA devinfo_data;
devinfo_data.cbSize = sizeof(SP_DEVINFO_DATA);
int count = 0;
rv = SetupDiClassGuidsFromName("Ports", (LPGUID)&guid, 1, &size) ;
if (!rv) {
std::cout << "error : SetupDiClassGuidsFromName() failed..." << std::endl;
return names;
}
hdevinfo = SetupDiGetClassDevs(&guid[0], NULL, NULL, DIGCF_PRESENT | DIGCF_PROFILE);
if (hdevinfo == INVALID_HANDLE_VALUE) {
std::cout << "error : SetupDiGetClassDevs() failed..." << std::endl;
return names;
}
while(SetupDiEnumDeviceInfo(hdevinfo, idx++, &devinfo_data)) {
char friendly_name[MAX_PATH];
char port_name[MAX_PATH];
DWORD prop_type;
DWORD type = REG_SZ;
HKEY hKey = NULL;
rv = ::SetupDiGetDeviceRegistryProperty(hdevinfo, &devinfo_data, SPDRP_FRIENDLYNAME, &prop_type,
(LPBYTE)friendly_name, sizeof(friendly_name), &size);
if (!rv) {
std::cout << "error : SetupDiGetDeviceRegistryProperty() failed..." << std::endl;
continue;
}
hKey = ::SetupDiOpenDevRegKey(hdevinfo, &devinfo_data, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ);
if (!hKey) continue;
size = sizeof(port_name);
rv = ::RegQueryValueEx(hKey, "PortName", 0, &type, (LPBYTE)&port_name, &size);
::RegCloseKey(hKey);
names.push_back(port_name);
}
SetupDiDestroyDeviceInfoList(hdevinfo);
return names;
}
int SerialPort::get_port_number()
{
std::vector<std::string> names = get_port_names();
return names.size();
}
std::string SerialPort::get_port_name(const unsigned int &idx)
{
std::vector<std::string> names = get_port_names();
if (idx >= names.size()) return std::string();
return names[idx];
}
void SerialPort::print_devices()
{
std::cout << "SerialPort::print_devices()" << std::endl;
int n = SerialPort::get_port_number();
for (int i = 0; i < n; ++i) {
std::string name = SerialPort::get_port_name(i);
std::cout << "\t" << name.c_str() << std::endl;
}
}
bool SerialPort::start(const char *com_port_name, int baud_rate)
{
boost::system::error_code ec;
if (port_) {
std::cout << "error : port is already opened..." << std::endl;
return false;
}
port_ = serial_port_ptr(new boost::asio::serial_port(io_service_));
port_->open(com_port_name, ec);
if (ec) {
std::cout << "error : port_->open() failed...com_port_name="
<< com_port_name << ", e=" << ec.message().c_str() << std::endl;
return false;
}
// option settings...
port_->set_option(boost::asio::serial_port_base::baud_rate(baud_rate));
port_->set_option(boost::asio::serial_port_base::character_size(8));
port_->set_option(boost::asio::serial_port_base::stop_bits(boost::asio::serial_port_base::stop_bits::one));
port_->set_option(boost::asio::serial_port_base::parity(boost::asio::serial_port_base::parity::none));
port_->set_option(boost::asio::serial_port_base::flow_control(boost::asio::serial_port_base::flow_control::none));
boost::thread t(boost::bind(&boost::asio::io_service::run, &io_service_));
async_read_some_();
return true;
}
void SerialPort::stop()
{
boost::mutex::scoped_lock look(mutex_);
if (port_) {
port_->cancel();
port_->close();
port_.reset();
}
io_service_.stop();
io_service_.reset();
}
int SerialPort::write_some(const std::string &buf)
{
return write_some(buf.c_str(), buf.size());
}
int SerialPort::write_some(const char *buf, const int &size)
{
boost::system::error_code ec;
if (!port_) return -1;
if (size == 0) return 0;
return port_->write_some(boost::asio::buffer(buf, size), ec);
}
void SerialPort::async_read_some_()
{
if (port_.get() == NULL || !port_->is_open()) return;
port_->async_read_some(
boost::asio::buffer(read_buf_raw_, SERIAL_PORT_READ_BUF_SIZE),
boost::bind(
&SerialPort::on_receive_,
this, boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void SerialPort::on_receive_(const boost::system::error_code& ec, size_t bytes_transferred)
{
boost::mutex::scoped_lock look(mutex_);
if (port_.get() == NULL || !port_->is_open()) return;
if (ec) {
async_read_some_();
return;
}
for (unsigned int i = 0; i < bytes_transferred; ++i) {
char c = read_buf_raw_[i];
if (c == end_of_line_char_) {
this->on_receive_(read_buf_str_);
read_buf_str_.clear();
}
else {
read_buf_str_ += c;
}
}
async_read_some_();
}
void SerialPort::on_receive_(const std::string &data)
{
std::cout << "SerialPort::on_receive_() : " << data << std::endl;
}
#pragma once
#include <boost/asio.hpp>
#include <boost/asio/serial_port.hpp>
#include <boost/system/error_code.hpp>
#include <boost/system/system_error.hpp>
#include <boost/bind.hpp>
#include <boost/thread.hpp>
#include <string>
#include <vector>
typedef boost::shared_ptr<boost::asio::serial_port> serial_port_ptr;
#define SERIAL_PORT_READ_BUF_SIZE 256
class SerialPort
{
protected:
boost::asio::io_service io_service_;
serial_port_ptr port_;
boost::mutex mutex_;
char read_buf_raw_[SERIAL_PORT_READ_BUF_SIZE];
std::string read_buf_str_;
char end_of_line_char_;
private:
SerialPort(const SerialPort &p);
SerialPort &operator=(const SerialPort &p);
public:
SerialPort(void);
virtual ~SerialPort(void);
char end_of_line_char() const;
void end_of_line_char(const char &c);
virtual bool start(const char *com_port_name, int baud_rate=9600);
virtual void stop();
int write_some(const std::string &buf);
int write_some(const char *buf, const int &size);
static int get_port_number();
static std::string get_port_name(const unsigned int &idx);
static std::vector<std::string> get_port_names();
static void print_devices();
protected:
virtual void async_read_some_();
virtual void on_receive_(const boost::system::error_code& ec, size_t bytes_transferred);
virtual void on_receive_(const std::string &data);
};
@CiN1979
Copy link

CiN1979 commented May 30, 2014

Can I use this class in commercial project?

@AndreaCitrolo
Copy link

Hi, thank you for providing this nice wrapper, unfortunately I have noticed a small problem with this class that arises in the following situation:

{ //some scope
SerialPort my_port;
my_port.start("port_path&name", baud); // start reading with async_read_some()
..... //doing stuffs
my_port.stop();
} // if something is read from the port at this point the on_receive is called but mutex_ as been destroyed and consequently an //exception is raised

You probably don't see this problem in the example since you called the sleep function after the stop and consequently the last read from the port happens before the mutex_ has been destroyed and everything work fine. I think it is possible to solve this problem putting the lock in on_receive under a try statement as follows:

void SerialPort::on_receive_(const boost::system::error_code& ec, size_t bytes_transferred)
{
try
{
boost::mutex::scoped_lock look(mutex_);
}catch(...){/exception handling and return/ }

    /*Same as before*/

}

Let me know if I am ignoring something.

@wlanowski
Copy link

Please use comments to show us, what your code is doing. thx.

@spinorkit
Copy link

spinorkit commented Apr 18, 2018

Hi, thanks for this example. I ran into a serious bug in start() which stopped it ever receiving data because io_service_.run() was returning before it was given any work to do. The fix is to call async_read_some_(); before creating the thread and calling run():

`bool SerialPort::start(const char *com_port_name, int baud_rate)
{
boost::system::error_code ec;

if (port_) {
	std::cout << "error : port is already opened..." << std::endl;
	return false;
}

port_ = serial_port_ptr(new boost::asio::serial_port(io_service_));
port_->open(com_port_name, ec);
if (ec) {
	std::cout << "error : port_->open() failed...com_port_name="
		<< com_port_name << ", e=" << ec.message().c_str() << std::endl; 
	return false;
}

// option settings...
port_->set_option(boost::asio::serial_port_base::baud_rate(baud_rate));
port_->set_option(boost::asio::serial_port_base::character_size(8));
port_->set_option(boost::asio::serial_port_base::stop_bits(boost::asio::serial_port_base::stop_bits::one));
port_->set_option(boost::asio::serial_port_base::parity(boost::asio::serial_port_base::parity::none));
port_->set_option(boost::asio::serial_port_base::flow_control(boost::asio::serial_port_base::flow_control::none));

async_read_some_();
std::thread t( [this] () {this->io_service_.run(); });
thread_.swap(t);

return true;

}

void SerialPort::stop()
{
std::unique_lock lock(mutex_);

if (port_) {
	port_->cancel();
	port_->close();
	port_.reset();
}

if (thread_.joinable())
thread_.join();

io_service_.stop();
io_service_.reset();

}
`

@jmccabe
Copy link

jmccabe commented Sep 9, 2019

Quick question on your reply. In this bit:

async_read_some_();
std::thread t( [this] () {this->io_service_.run(); });
thread_.swap(t);

then

if (thread_.joinable())
thread_.join();

Am I right in thinking that thread_ is a member variable of the class?

@ahmetserdar
Copy link

perfect!

@llallo
Copy link

llallo commented Aug 25, 2023

Thank you for this excelent example.
Just one minor suggestion: io_service_.restart() needs to be called in function SerialPort::start if there has been a previous call to SerialPort::stop, where io_service_.reset() is invoked. like this:

bool SerialPort::start(const char *com_port_name, int baud_rate)
{
	boost::system::error_code ec;

	if (port_) {
		std::cout << "error : port is already opened..." << std::endl;
		return false;
	}
        io_service_.restart();
	port_ = serial_port_ptr(new boost::asio::serial_port(io_service_));
	port_->open(com_port_name, ec);
        ...

otherwise, it stops working on subsequent calls to stop() / start().

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment