-
-
Save yoggy/3323808 to your computer and use it in GitHub Desktop.
#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); | |
}; |
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.
Please use comments to show us, what your code is doing. thx.
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();
}
`
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?
perfect!
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().
Can I use this class in commercial project?