Please review my serial port class written in C++. It is completely asynchronous, ie event driven. My idea for later is to inherit from this a sync_serial, where timeouts can be specified and it waits for responses. But that is not the purpose of this review. It is Windows only and I have tested on a Conexant voice modem. It uses the Microsoft overlapped IO model.
Header file, async_serial.hpp:
/*
asynchronous serial communications class
*/
#ifndef ASYNC_SERIAL_
#define ASYNC_SERIAL_
#include <thread>
#include <queue>
#include <unordered_map>
#include <stdint.h>
// forward declare pimpl
struct serialimpl;
// utility namespace
namespace utl {
enum line_status {
LS_CTS, // clear to send signal changed state
LS_DSR, // data set ready signal changed
LS_CD, // carrier detect signal changed state
LS_BREAK, // a break was detected in input
LS_ERR, // a line status error occurred
LS_RING // ring indicator detected
};
struct datum
{
datum(char* data, size_t length) : data_(data), length_(length) {}
char* data_;
size_t length_;
};
enum parity_type
{
PARITYTYPE_NONE, // most common
PARITYTYPE_ODD,
PARITYTYPE_EVEN
};
// bits per packet/frame
enum databits_type
{
DATA_5 = 5,
DATA_6 = 6,
DATA_7 = 7,
DATA_8 = 8 // most common
};
enum stopbits_type
{
STOPBIT_1, // most common
STOPBIT_1_5,
STOPBIT_2
};
enum flow_control_type
{
FLOWCONTROL_OFF, // no flow control - worth trying to get something working
FLOWCONTROL_HARDWARE_RTSCTS, // For best performance if supported by modem/cable
FLOWCONTROL_HARDWARE_DTRDSR, // not very common hardware flow control
FLOWCONTROL_XONXOFF // if cable has no flow control pins connected
};
struct port_settings
{
port_settings(unsigned baud, databits_type databits = DATA_8, parity_type parity = PARITYTYPE_NONE,
unsigned stopbits = STOPBIT_1, flow_control_type flowcontrol = FLOWCONTROL_HARDWARE_RTSCTS)
: baud_rate_(baud), databits_(databits), parity_type_(parity), stopbits_(stopbits), flowcontrol_(flowcontrol) {}
unsigned baud_rate_; // speed in bits per second
databits_type databits_; // usually set to 8 (8 bits per packet/frame)
parity_type parity_type_;
unsigned stopbits_;
flow_control_type flowcontrol_;
};
class async_serial
{
public:
// construct using port and baudrate
async_serial(int port, unsigned baudrate);
// construct using port and port_settings data
async_serial(int port, port_settings settings);
// close port related system resources
virtual ~async_serial();
// open serial port and setup handling for data
virtual bool open();
// check if serial port open
bool is_open() const;
// close serial port
virtual bool close();
// write data to port
bool write(const char* data, size_t length);
// override this function to handle received data
virtual void on_read(char* data, size_t length);
// override this function to identify when data successfully sent
virtual void on_write();
// override this function to indentify serial port state changes
virtual void on_status(const unsigned statechange, bool set = true);
// // override this function for diagnostic information
virtual void on_error(const std::string& error);
// get line status
inline unsigned get_status() const { return status_; }
// helper to convert status to a descriptive string
std::string get_status_string();
// helper function to get list of ports available and any available string description
static long enumerate_ports(std::unordered_map <uint32_t, std::string>& ports);
async_serial(const async_serial&) = delete;
async_serial& operator=(const async_serial&) = delete;
private:
// forward declare pimpl
serialimpl *pimpl_; // Handle object - pimpl
std::thread reader_thread; // thread to handle read events
void reader_thread_func(); // read thread handler function
bool closing_port_; // to indicate port is in process of being closed
int port_; // port indentifier
unsigned status_; // status indication
void update_pin_states(); // updates internal pin status flags
void update_error_states(unsigned long comm_event); // reports errors if any
port_settings settings_;
bool configure_comport(); // initial port setup called in open
std::thread writer_thread; // thread to handle write events
void writer_thread_func(); // write thread handler function
std::queue<datum> write_queue_; // data is queued for sending/(writing)
inline bool job_queue_empty() const { return write_queue_.empty(); }
};
} // utl
#endif // ASYNC_SERIAL_
Implementation file, async_serial.cpp:
/*
async_serial class implementation for Windows. Inspired by MTTTY sample application.
*/
#include <cstdio>
#include <cctype>
#include <chrono>
#include <memory>
#include <string>
#include <unordered_map>
#include <iostream>
#include <sstream>
#include <Windows.h>
// WMI header for Windows
#include <Wbemcli.h>
// WMI library for Windows
#pragma comment(lib, "wbemuuid.lib")
#include "async_serial.hpp"
static const DWORD READ_BUFFER_SIZE = 512;
// milliseconds timeout for waiting for serial port events - read or status
static const DWORD OVERLAPPED_CHECK_TIMEOUT = 500;
using namespace utl;
// port handle - keep system specifics out of header
// *** PROBLEM - TODO - problem if user wants to create multiple instances of class. PIMPL?
// hide implementation details from header
struct serialimpl {
HANDLE handle_ = INVALID_HANDLE_VALUE;
};
static std::string system_error() {
// Retrieve, format, and print out a message from the last error.
char buf[256] = {};
DWORD lasterror = GetLastError();
DWORD chars = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, lasterror, 0, buf, 255, NULL);
std::string error;
if (chars) {
error = buf;
}
else {
error = "FormatMessage returned zero characters. Last error code: ";
error += std::to_string(lasterror);
}
return error;
}
static std::string create_error_msg(const char* prepend) {
std::string s(prepend);
s += system_error();
return s;
}
long async_serial::enumerate_ports(std::unordered_map <uint32_t, std::string>& ports) {
// Initialize COM.
HRESULT hr = CoInitializeEx(0, COINIT_MULTITHREADED);
if (FAILED(hr))
return hr;
// Initialize security for application
hr = CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_DEFAULT,
RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, NULL);
if (FAILED(hr)) {
CoUninitialize();
return hr;
}
//Create the WBEM locator (to WMI)
std::shared_ptr<IWbemLocator> wben_locator;
hr = CoCreateInstance(CLSID_WbemLocator, nullptr, CLSCTX_INPROC_SERVER,
IID_IWbemLocator, reinterpret_cast<void**>(&wben_locator));
if (FAILED(hr)) {
CoUninitialize();
return hr;
}
IWbemServices* wbem_services = NULL;
hr = wben_locator->ConnectServer(L"ROOT\\CimV2", nullptr, nullptr, nullptr,
0, nullptr, nullptr, &wbem_services);
if (FAILED(hr)) {
wben_locator->Release();
CoUninitialize();
return hr;
}
// WMI query of serial ports on this computer
IEnumWbemClassObject* wbem_enumerate_object = NULL;
hr = wbem_services->CreateInstanceEnum(L"Win32_SerialPort",
WBEM_FLAG_RETURN_WBEM_COMPLETE, nullptr, &wbem_enumerate_object);
if (FAILED(hr)) {
wben_locator->Release();
CoUninitialize();
return hr;
}
// Now enumerate all the ports
hr = WBEM_S_NO_ERROR;
// when no Next object enumerated, WBEM_S_FALSE returned
while (hr == WBEM_S_NO_ERROR) {
ULONG numports = 0;
IWbemClassObject** wbem_object = (IWbemClassObject**)malloc(10 * sizeof(IWbemClassObject));
hr = wbem_enumerate_object->Next(WBEM_INFINITE, 10, reinterpret_cast<IWbemClassObject**>(wbem_object), &numports);
if (SUCCEEDED(hr)) {
// if > 10 objects returned have to re-allocate wbem_object
if (numports > 10) {
wbem_object = (IWbemClassObject**)realloc(wbem_object, numports * sizeof(IWbemClassObject));
}
for (ULONG i = 0; i < numports; ++i) {
VARIANT device_property1;
const HRESULT hrGet = wbem_object[i]->Get(L"DeviceID", 0, &device_property1, nullptr, nullptr);
if (SUCCEEDED(hrGet) && (device_property1.vt == VT_BSTR) && (wcslen(device_property1.bstrVal) > 3)) {
// if deviceID is prefaced with "COM" add ports list
if (wcsncmp(device_property1.bstrVal, L"COM", 3) == 0) {
// get port number
std::wistringstream wstrm(std::wstring(&(device_property1.bstrVal[3])));
unsigned int port;
wstrm >> port;
VariantClear(&device_property1);
std::pair<UINT, std::string> pair;
pair.first = port;
// get the friendly name of the port
VARIANT device_property2;
if (SUCCEEDED(wbem_object[i]->Get(L"Name", 0, &device_property2, nullptr, nullptr)) && (device_property2.vt == VT_BSTR)) {
std::wstring ws(device_property2.bstrVal);
std::string name(ws.begin(), ws.end());
pair.second = name;
VariantClear(&device_property2);
}
ports.insert(pair);
}
}
}
}
free(wbem_object);
}
// cleanup
wbem_enumerate_object->Release();
wbem_services->Release();
wben_locator->Release();
CoUninitialize();
return S_OK;
}
// comport settings based on configuration requested
bool async_serial::configure_comport()
{
DCB dcb = { 0 };
dcb.DCBlength = sizeof(dcb);
// get current DCB settings
if (!GetCommState(pimpl_->handle_, &dcb)) {
on_error(create_error_msg("GetCommState error "));
return false;
}
// update DCB rate, byte size, parity, and stop bits size
dcb.BaudRate = settings_.baud_rate_;
dcb.ByteSize = settings_.databits_;
dcb.Parity = settings_.parity_type_;
dcb.StopBits = settings_.stopbits_;
// event flag
dcb.EvtChar = '\0';
// set suitable flow control settings
switch (settings_.flowcontrol_) {
case FLOWCONTROL_HARDWARE_RTSCTS: // most common hardware flow control
dcb.fDtrControl = DTR_CONTROL_ENABLE;
dcb.fRtsControl = RTS_CONTROL_HANDSHAKE;
dcb.fOutxCtsFlow = 1;
dcb.fOutxDsrFlow = 0;
dcb.fOutX = 0;
dcb.fInX = 0;
break;
case FLOWCONTROL_HARDWARE_DTRDSR: // not very common hw flow control
dcb.fDtrControl = DTR_CONTROL_HANDSHAKE;
dcb.fRtsControl = RTS_CONTROL_ENABLE;
dcb.fOutxCtsFlow = 0;
dcb.fOutxDsrFlow = 1;
dcb.fOutX = 0;
dcb.fInX = 0;
break;
case FLOWCONTROL_XONXOFF: // software flow control
dcb.fDtrControl = DTR_CONTROL_ENABLE;
dcb.fRtsControl = RTS_CONTROL_ENABLE;
dcb.fOutxCtsFlow = 0;
dcb.fOutxDsrFlow = 0;
dcb.fOutX = 1;
dcb.fInX = 1;
break;
case FLOWCONTROL_OFF: // no flow control
dcb.fDtrControl = DTR_CONTROL_ENABLE;
dcb.fRtsControl = RTS_CONTROL_ENABLE;
dcb.fOutxCtsFlow = 0;
dcb.fOutxDsrFlow = 0;
dcb.fOutX = 0;
dcb.fInX = 0;
break;
}
// set the rest to suitable defaults
dcb.fDsrSensitivity = 0;
dcb.fTXContinueOnXoff = 0;
dcb.XonChar = 0x11; // Tx and Rx XON character
dcb.XoffChar = 0x13; // Tx and Rx XOFF character
dcb.XonLim = 0; // transmit XON threshold
dcb.XoffLim = 0; // transmit XOFF threshold
dcb.fAbortOnError = 0; // abort reads/writes on error
dcb.ErrorChar = 0; // error replacement character
dcb.EofChar = 0; // end of input character
// Windows does not support non-binary mode so this must be TRUE
dcb.fBinary = TRUE;
// DCB settings not in the user's control
dcb.fParity = TRUE;
// set new state
if (!SetCommState(pimpl_->handle_, &dcb)) {
on_error(create_error_msg("SetCommState error "));
return false;
}
COMMTIMEOUTS ctout = { 0x01, 0, 0, 0, 0 };
if (!SetCommTimeouts(pimpl_->handle_, &ctout)) {
on_error(create_error_msg("SetCommTimeouts error "));
}
return true;
}
async_serial::async_serial(int port, port_settings settings) :
port_(port),
settings_(settings),
closing_port_(false),
status_(0),
pimpl_(new serialimpl) {}
async_serial::async_serial(int port, unsigned baudrate)
: port_(port),
settings_(9600),
closing_port_(false),
status_(0),
pimpl_(new serialimpl) {}
void async_serial::writer_thread_func() {
OVERLAPPED overlapped_write = { 0 };
DWORD bytes_written;
// create this writes overlapped structure hEvent
overlapped_write.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (overlapped_write.hEvent == NULL)
{
on_error("Error creating writer overlapped event");
return;
}
// keep thread running all the time that we are not shutting down
while (!closing_port_) {
if (!is_open())
std::this_thread::sleep_for(std::chrono::seconds(5));
else if (job_queue_empty())
std::this_thread::sleep_for(std::chrono::seconds(1));
else {
// get next job
datum d = write_queue_.front();
write_queue_.pop();
// issue write
if (!WriteFile(pimpl_->handle_, d.data_, d.length_, &bytes_written, &overlapped_write)) {
if (GetLastError() == ERROR_IO_PENDING) {
// write is delayed
DWORD wait_result = WaitForSingleObject(overlapped_write.hEvent, OVERLAPPED_CHECK_TIMEOUT);
switch (wait_result)
{
// write event set
case WAIT_OBJECT_0:
SetLastError(ERROR_SUCCESS);
if (!GetOverlappedResult(pimpl_->handle_, &overlapped_write, &bytes_written, FALSE)) {
if (GetLastError() != ERROR_OPERATION_ABORTED) {
on_error(system_error());
}
}
if (bytes_written != d.length_) {
on_error(system_error());
}
else {
// success - we have written data ok
on_write();
}
break;
// wait timed out
case WAIT_TIMEOUT:
// timeout expected
break;
case WAIT_FAILED:
default:
on_error("WaitForSingleObject WAIT_FAILED in writer_thread_function");
break;
}
// deallocate data memory
delete[] d.data_;
}
else
// writefile failed - but not dut to ERROR_OPERATION_ABORTED
on_error(system_error());
}
else {
// writefile returned immediately
if (bytes_written != d.length_) {
on_error(system_error());
}
else {
// success - we have written data ok
on_write();
}
}
}
}
// don't leak event handle
CloseHandle(overlapped_write.hEvent);
}
std::string async_serial::get_status_string() {
std::string state;
if (status_ & (1UL << LS_CTS)) {
state += "CTS,";
}
if (status_ & (1UL << LS_DSR)) {
state += "DSR,";
}
if (status_ & (1UL << LS_CD)) {
state += "CD,";
}
if (status_ & (1UL << LS_BREAK)) {
state += "BREAK,";
}
if (status_ & (1UL << LS_ERR)) {
state += "ERROR,";
}
if (status_ & (1UL << LS_RING)) {
state += "RING,";
}
if (state.size() > 0 && state[state.size() - 1] == ',') {
state.erase(state.rfind(','));
}
return state;
}
void async_serial::update_pin_states() {
// GetCommModemStatus provides cts, dsr, ring and cd states
// if status checks are not allowed, then don't issue the
// modem status check nor the com stat check
DWORD modem_status;
if (!GetCommModemStatus(pimpl_->handle_, &modem_status))
{
on_error(create_error_msg("serial::update_pin_states GetCommModemStatus error "));
}
else {
const std::pair<unsigned, unsigned> mapping_list[] = {
{ MS_CTS_ON, LS_CTS }, { MS_DSR_ON, LS_DSR }, { MS_RING_ON, LS_RING }, { MS_RLSD_ON, LS_CD } };
for (const auto& item : mapping_list) {
bool is_set = (item.first & modem_status) == item.first;
if (is_set != ((status_ >> item.second) & 1U)) {
// update status_
is_set != 0 ? status_ |= (1 << item.second) : status_ &= ~(1 << item.second);
on_status(item.second, is_set != 0 ? true : false); // notify state change
}
}
}
}
void async_serial::reader_thread_func() {
DWORD bytes_read;
// flip between waiting on read and waiting on status events
BOOL wait_on_read = FALSE; // waiting on read
BOOL wait_on_status = FALSE; // waiting on line status events
OVERLAPPED overlapped_reader = { 0 }; // overlapped structure for read operations
OVERLAPPED overlapped_status = { 0 }; // overlapped structure for status operations
HANDLE event_array[2]; // list of events that WaitForxxx functions wait on
char read_buffer[READ_BUFFER_SIZE]; // temp storage of read data
DWORD comms_event; // result from WaitCommEvent
// Create the overlapped event. Must be closed before exiting
// to avoid a handle leak.
overlapped_reader.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (overlapped_reader.hEvent == NULL)
{
on_error("Error creating reader overlapped event");
return;
}
overlapped_status.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (overlapped_status.hEvent == NULL)
{
on_error("Error creating status overlapped event");
return;
}
// array of handles to wait for signals - we wait on read and status events
event_array[0] = overlapped_reader.hEvent;
event_array[1] = overlapped_status.hEvent;
// keep thread running until shutting down flag set
while (!closing_port_) {
if (!is_open())
std::this_thread::sleep_for(std::chrono::seconds(1));
else {
if (!wait_on_read) {
// issue read operation
if (!ReadFile(pimpl_->handle_, read_buffer, READ_BUFFER_SIZE, &bytes_read, &overlapped_reader)) {
if (GetLastError() != ERROR_IO_PENDING) { // read not delayed
on_error(system_error());
break;
}
else
wait_on_read = TRUE;
}
else {
on_read(read_buffer, bytes_read);
}
}
// if no status check is outstanding, then issue another one
if (!wait_on_status) {
if (!WaitCommEvent(pimpl_->handle_, &comms_event, &overlapped_status)) {
if (GetLastError() == ERROR_IO_PENDING) {
wait_on_status = TRUE; // have to wait for status event
} else {
on_error(create_error_msg("WaitCommEvent error "));
}
} else {
// WaitCommEvent returned immediately
update_error_states(comms_event);
}
}
// wait for pending operations to complete
if (wait_on_status || wait_on_read) {
DWORD wait_result = WaitForMultipleObjects(2, event_array, FALSE, OVERLAPPED_CHECK_TIMEOUT);
switch (wait_result)
{
// read completed
case WAIT_OBJECT_0:
if (!GetOverlappedResult(pimpl_->handle_, &overlapped_reader, &bytes_read, FALSE)) {
if (GetLastError() == ERROR_OPERATION_ABORTED) {
on_error("Read aborted");
wait_on_read = FALSE;
}
else {
on_error(create_error_msg("GetOverlappedResult (in reader_thread_func) error "));
}
}
else {
// read completed successfully
if (bytes_read) {
on_read(read_buffer, bytes_read);
}
}
wait_on_read = FALSE;
break;
// status completed
case WAIT_OBJECT_0 + 1:
{
DWORD overlapped_result;
if (!GetOverlappedResult(pimpl_->handle_, &overlapped_status, &overlapped_result, FALSE)) {
if (GetLastError() == ERROR_OPERATION_ABORTED) {
on_error("WaitCommEvent aborted");
} else {
on_error(create_error_msg("GetOverlappedResult for status wait returned "));
}
}
else {
// status check completed successfully
update_error_states(comms_event);
update_pin_states();
}
wait_on_status = FALSE;
}
break;
case WAIT_TIMEOUT:
update_pin_states(); // periodically update pin states if changed
break;
default:
break;
}
}
}
}
// close reader event handle - otherwise will get resource leak
CloseHandle(overlapped_reader.hEvent);
}
async_serial::~async_serial() {
// if port is not open then reader and writer threads not running
close();
delete pimpl_;
}
bool async_serial::open() {
// in case we are already open
close();
closing_port_ = false;
// The "\\.\" prefix form to access the Win32 device namespace must be used for com ports above COM9
char portname[100] = { 0 };
_snprintf_s(portname, sizeof(portname), sizeof(portname) - 1, "\\\\.\\COM%d", port_);
pimpl_->handle_ = CreateFileA(portname, GENERIC_READ | GENERIC_WRITE, 0, 0,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, 0);
if (pimpl_->handle_ == INVALID_HANDLE_VALUE)
return false;
// Save original comm timeouts and set new ones
COMMTIMEOUTS origtimeouts;
if (!GetCommTimeouts(pimpl_->handle_, &origtimeouts)) {
on_error(create_error_msg("GetCommTimeouts error "));
}
if (!configure_comport()) {
on_error("failed to configure serial port");
}
// set size of modem driver read and write buffers
if (!SetupComm(pimpl_->handle_, READ_BUFFER_SIZE, READ_BUFFER_SIZE)) {
on_error(create_error_msg("SetupComm error: "));
}
// Sends the DTR (data-terminal-ready) signal
if (!EscapeCommFunction(pimpl_->handle_, SETDTR)) {
on_error(create_error_msg("EscapeCommFunction(SETDTR) error: "));
}
// WaitCommEvent, in reader_thread_func, will monitor for these events
if (!SetCommMask(pimpl_->handle_, EV_BREAK | EV_CTS | EV_DSR | EV_ERR | EV_RING | EV_RLSD)) {
on_error(create_error_msg("SetCommMask error: "));
}
reader_thread = std::thread(&async_serial::reader_thread_func, this);
writer_thread = std::thread(&async_serial::writer_thread_func, this);
std::this_thread::yield();
return pimpl_->handle_ != INVALID_HANDLE_VALUE;
}
inline bool async_serial::is_open() const {
return pimpl_->handle_ != INVALID_HANDLE_VALUE;
}
bool async_serial::close() {
if (is_open()) {
closing_port_ = true;
// give the reader and writer threads time to finish
std::this_thread::sleep_for(std::chrono::seconds(2));
if (reader_thread.joinable())
reader_thread.join(); // prevents crash - due to terminate being called on running thread still 'alive'
if (writer_thread.joinable())
writer_thread.join();
BOOL b = CloseHandle(pimpl_->handle_);
pimpl_->handle_ = INVALID_HANDLE_VALUE;
return b == TRUE ? true : false;
}
else {
return false;
}
}
bool async_serial::write(const char* data, size_t length) {
// add to writer queue
char* cpy = new char[length]();
memcpy(cpy, data, length);
write_queue_.push(datum(cpy, length));
return true;
}
void async_serial::on_read(char* data, size_t length) {
// override this function to handle received data
}
void async_serial::on_write() {
// override this function to identify when data successfully sent
}
void async_serial::on_status(const unsigned statechange, bool set) {
// override this function to see line status changes
}
void async_serial::on_error(const std::string& error) {
// override this function for diagnostic information
}
void async_serial::update_error_states(unsigned long comm_event) {
// cache previous status for comparison
unsigned prev_status = status_;
if (comm_event & EV_BREAK) {
on_error("BREAK signal received");
status_ |= (1UL << LS_BREAK);
if (!((prev_status >> LS_BREAK) & 1U)) {
on_status(LS_BREAK, true);
}
}
else {
on_error("BREAK signal reset");
status_ &= ~(1UL << LS_BREAK);
if (((prev_status >> LS_BREAK) & 1U)) {
on_status(LS_BREAK, false);
}
}
// A line-status error occurred. Line-status errors are CE_FRAME, CE_OVERRUN, and CE_RXPARITY.
if (comm_event & EV_ERR) {
on_error("A line status error has occurred");
status_ |= (1UL << LS_ERR);
if (!((prev_status >> LS_ERR) & 1U)) {
on_status(LS_ERR, true);
}
COMSTAT ComStatNew;
DWORD dwErrors;
if (!ClearCommError(pimpl_->handle_, &dwErrors, &ComStatNew))
on_error("ErrorReporter(\"ClearCommError\")");
if (ComStatNew.fCtsHold)
on_error("waiting for the CTS(clear - to - send) signal to be sent.");
if (ComStatNew.fDsrHold)
on_error("waiting for the DSR (data-set-ready) signal to be sent.");
if (ComStatNew.fRlsdHold)
on_error("waiting for the RLSD (receive-line-signal-detect) aka CD (Carrier Detect) signal to be sent.");
if (ComStatNew.fXoffHold)
on_error("waiting because an XOFF character was received.");
if (ComStatNew.fXoffSent)
on_error("Transmission waiting because an XOFF character was sent.");
if (ComStatNew.fEof)
on_error("An end-of-file (EOF) character has been received.");
}
else {
on_error("A line status error cleared");
status_ &= ~(1UL << LS_ERR);
if (((prev_status >> LS_ERR) & 1U)) {
on_status(LS_ERR, false);
}
}
}
Test program, main.cpp:
#include <iostream>
#include <unordered_map>
#include <string>
#include <chrono>
#include <thread>
#include "async_serial.hpp"
static void print_byte_as_binary(unsigned n, unsigned numbits) {
for (int i = numbits; i >= 0; i--) {
std::cout << (n & (1 << i) ? '1' : '0');
}
}
class modem_tester : public utl::async_serial {
public:
modem_tester(int port, unsigned baudrate) : utl::async_serial(port, baudrate) {
}
modem_tester(int port, utl::port_settings settings) : utl::async_serial(port, settings) {
}
virtual bool open() {
return utl::async_serial::open();
}
// bytes read
virtual void on_read(char* data, size_t length) {
std::string s(data, length);
std::cout << "data: " << s;
}
// bytes written
virtual void on_write() {
std::cout << "on_write()\n";
}
// Line status change detected
virtual void on_status(const unsigned statechange, bool set = true) {
std::cout << "on_status bit: " << statechange << " " << (set ? "set" : "not set") << std::endl;
}
virtual void on_error(const std::string& error) {
std::cout << "on_error: " << error << std::endl;
}
};
int main() {
{
// get list of ports available on system
std::unordered_map <uint32_t, std::string> ports;
utl::async_serial::enumerate_ports(ports);
for (const auto& item : ports) {
std::cout << item.first << "->" << item.second << std::endl;
}
std::cout << "From list above select port number (number before arrow)\n";
int port;
std::cin >> port;
std::cin.ignore(1, '\n'); // ignore newline received after number entered
// verify a valid port selected
if (ports.find(port) == ports.end()) {
std::cout << "You have selected a port that doesn't exist on this system. Aborting...\n";
return 0;
}
utl::port_settings ps(1200);
ps.databits_ = utl::DATA_8;
ps.flowcontrol_ = utl::FLOWCONTROL_HARDWARE_RTSCTS;
//ps.flowcontrol = utl::FLOWCONTROL_XONXOFF; // select if hardware flow control not possible
ps.parity_type_ = utl::PARITYTYPE_NONE;
ps.stopbits_ = utl::STOPBIT_1;
modem_tester s(port, 1200);
bool ret = s.open();
std::cout << "open(" << port << ") returned " << std::boolalpha << ret << std::endl;
const char* commands[] = {
"ATQ0V1E0\r\n", // initialise the query, Q0=enable result codes, V1=verbal result codes, E0=disable command echo
"AT+GMM\r\n", // Request Model Indentification
"AT+FCLASS=?\r\n", // list of supported modes (data, fax, voice)
"AT#CLS=?\r\n", // not sure, I think similar to above command (CLS being shorthand for class?)
"AT+GCI?\r\n", // currently selected country code (US=B5)
"AT+GCI=?\r\n", // list of supported country codes
"ATI0\r\n", "ATI1\r\n", "ATI2\r\n", "ATI3\r\n", "ATI4\r\n", "ATI5\r\n", "ATI6\r\n", "ATI7\r\n", // Identification requests
"AT+GMI\r\n", // Request Manufacturer Identification
"AT+GMI9\r\n", // Request Conexant Identification
"AT+GCAP\r\n" }; // Request complete capabilities list
for (const auto& cmd : commands) {
std::cout << "Sending: " << cmd;
s.write(cmd, strlen(cmd));
std::this_thread::sleep_for(std::chrono::seconds(2));
}
// Give tester 10 seconds to ring modem - check for RING indicator - LS_RING
std::this_thread::sleep_for(std::chrono::seconds(10));
print_byte_as_binary(s.get_status(), 6);
std::cout << '\n' << s.get_status_string() << '\n';
ret = s.close();
std::cout << "close() returned " << std::boolalpha << ret << std::endl;
} // out of scope - so can check destructor
std::cout << "finished\n";
}
Example output using a Conexant voice modem:
1->Communications Port (COM1)
4->SAMSUNG Mobile USB Modem
12->Conexant USB CX93010 ACF Modem
From list above select port number (number before arrow)
12
open(12) returned true
on_status bit: 0 set
on_status bit: 1 set
on_status bit: 5 set
Sending: ATQ0V1E0
on_write()
data:
OK
Sending: AT+GMM
on_write()
data:
+GMM: V90
OK
Sending: AT+FCLASS=?
on_write()
data:
0,1,1.0,2,8
OK
Sending: AT#CLS=?
on_write()
data:
ERROR
Sending: AT+GCI?
on_write()
data:
+GCI: B4
OK
Sending: AT+GCI=?
on_write()
data:
+GCI: (00,07,09,0A,0F,16,1B,20,25,26,27,2D,2E,31,36,3C,3D,42,46,50,51,52,53,54,5
7,58,59,61,62,64,69,6C,73,77,7B,7E,82,84,89,8A,8B,8E,98,99,9C,9F,A0,A1,A5,A6,A9,
AD,AE,B3,B4,B5,B7,B8,C1,F9,FA,FB,FC,FD,FE)
OK
Sending: ATI0
on_write()
data:
56000
OK
Sending: ATI1
on_write()
data:
OK
Sending: ATI2
on_write()
data:
OK
Sending: ATI3
on_write()
data:
CX93001-EIS_V0.2013-V92
OK
Sending: ATI4
on_write()
data:
OK
Sending: ATI5
on_write()
data:
B4
OK
Sending: ATI6
on_write()
data:
OK
Sending: ATI7
on_write()
data:
OK
Sending: AT+GMI
on_write()
data:
+GMI: CONEXANT
OK
Sending: AT+GMI9
on_write()
data:
+GMI9: CONEXANT ACF
OK
Sending: AT+GCAP
on_write()
data:
+GCAP: +FCLASS,+MS,+ES,+DS
OK
0100011
CTS,DSR,RING
close() returned true
on_error: Read aborted
finished
is_open. all program logic very complex and not clean, due wrong asynchronous io implementation \$\endgroup\$WaitForSingleObject- this is already not asynchronous I/O by sense. by fact synchronous I/O - when your request sent to driver and then if pending returned - code is wait in kernel for complete. you simply move wait from kernel to user mode. if you use dedicated threads and loops - this is already not asynchronous I/O by sense, even if you formally use it. asynchronous I/O - this is when you begin it - and not wait after, and not do any loops. all program structure must be absolute another. \$\endgroup\$