/* Serial port object for use with wxWidgets
* Copyright 2010, Paul Stoffregen (paul@pjrc.com)
* Modified by: Leonardo Laguna Ruiz
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include "serial.h"
#if defined(LINUX)
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#elif defined(MACOSX)
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#elif defined(WINDOWS)
#include
#define win32_err(s) FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, \
GetLastError(), 0, (s), sizeof(s), NULL)
#define QUERYDOSDEVICE_BUFFER_SIZE 262144
#if _MSC_VER
#define snprintf _snprintf_s
#endif
#else
#error "This platform is unsupported, sorry"
#endif
Serial::Serial()
{
port_is_open = 0;
baud_rate = 38400; // default baud rate
}
Serial::~Serial()
{
Close();
}
// Open a port, by name. Return 0 on success, non-zero for error
int Serial::Open(const string& name)
{
Close();
#if defined(LINUX)
struct serial_struct kernel_serial_settings;
int bits;
port_fd = open(name.c_str(), O_RDWR | O_NOCTTY | O_NONBLOCK);
if (port_fd < 0) {
if (errno == EACCES) {
error_msg = "Unable to access " + name + ", insufficient permission";
// TODO: we could look at the permission bits and owner
// to make a better message here
} else if (errno == EISDIR) {
error_msg = "Unable to open " + name +
", Object is a directory, not a serial port";
} else if (errno == ENODEV || errno == ENXIO) {
error_msg = "Unable to open " + name +
", Serial port hardware not installed";
} else if (errno == ENOENT) {
error_msg = "Unable to open " + name +
", Device name does not exist";
} else {
error_msg = "Unable to open " + name +
", " + strerror(errno);
}
return -1;
}
if (ioctl(port_fd, TIOCMGET, &bits) < 0) {
close(port_fd);
error_msg = "Unable to query serial port signals";
return -1;
}
bits &= ~(TIOCM_DTR | TIOCM_RTS);
if (ioctl(port_fd, TIOCMSET, &bits) < 0) {
close(port_fd);
error_msg = "Unable to control serial port signals";
return -1;
}
if (tcgetattr(port_fd, &settings_orig) != 0) {
close(port_fd);
error_msg = "Unable to query serial port settings (perhaps not a serial port)";
return -1;
}
memset(&settings, 0, sizeof(settings));
settings.c_iflag = IGNBRK | IGNPAR;
settings.c_cflag = CS8 | CREAD | HUPCL | CLOCAL;
Set_baud(baud_rate);
if (ioctl(port_fd, TIOCGSERIAL, &kernel_serial_settings) == 0) {
kernel_serial_settings.flags |= ASYNC_LOW_LATENCY;
ioctl(port_fd, TIOCSSERIAL, &kernel_serial_settings);
}
tcflush(port_fd, TCIFLUSH);
#elif defined(MACOSX)
int bits;
port_fd = open(name.c_str(), O_RDWR | O_NOCTTY | O_NONBLOCK);
if (port_fd < 0) {
error_msg = "Unable to open " + name + ", " + strerror(errno);
return -1;
}
if (ioctl(port_fd, TIOCEXCL) == -1) {
close(port_fd);
error_msg = "Unable to get exclussive access to port " + name;
return -1;
}
if (ioctl(port_fd, TIOCMGET, &bits) < 0) {
close(port_fd);
error_msg = "Unable to query serial port signals on " + name;
return -1;
}
bits &= ~(TIOCM_DTR | TIOCM_RTS);
if (ioctl(port_fd, TIOCMSET, &bits) < 0) {
close(port_fd);
error_msg = "Unable to control serial port signals on " + name;
return -1;
}
if (tcgetattr(port_fd, &settings_orig) < 0) {
close(port_fd);
error_msg = "Unable to access baud rate on port " + name;
return -1;
}
memset(&settings, 0, sizeof(settings));
settings.c_cflag = CS8 | CLOCAL | CREAD | HUPCL;
settings.c_iflag = IGNBRK | IGNPAR;
Set_baud(baud_rate);
tcflush(port_fd, TCIFLUSH);
#elif defined(WINDOWS)
COMMCONFIG cfg;
COMMTIMEOUTS timeouts;
int got_default_cfg=0, port_num;
char buf[1024], name_createfile[64], name_commconfig[64], *p;
DWORD len;
snprintf(buf, sizeof(buf), "%s", name.c_str());
p = strstr(buf, "COM");
if (p && sscanf(p + 3, "%d", &port_num) == 1) {
//printf("port_num = %d\n", port_num);
snprintf(name_createfile, sizeof(name_createfile), "\\\\.\\COM%d", port_num);
snprintf(name_commconfig, sizeof(name_commconfig), "COM%d", port_num);
} else {
snprintf(name_createfile, sizeof(name_createfile), "%s", name.c_str());
snprintf(name_commconfig, sizeof(name_commconfig), "%s", name.c_str());
}
len = sizeof(COMMCONFIG);
if (GetDefaultCommConfig(name_commconfig, &cfg, &len)) {
// this prevents unintentionally raising DTR when opening
// might only work on COM1 to COM9
got_default_cfg = 1;
memcpy(&port_cfg_orig, &cfg, sizeof(COMMCONFIG));
cfg.dcb.fDtrControl = DTR_CONTROL_DISABLE;
cfg.dcb.fRtsControl = RTS_CONTROL_DISABLE;
SetDefaultCommConfig(name_commconfig, &cfg, sizeof(COMMCONFIG));
} else {
printf("error with GetDefaultCommConfig\n");
}
port_handle = CreateFile(name_createfile, GENERIC_READ | GENERIC_WRITE,
0, 0, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
if (port_handle == INVALID_HANDLE_VALUE) {
win32_err(buf);
error_msg = "Unable to open " + name + ", " + buf;
return -1;
}
len = sizeof(COMMCONFIG);
if (!GetCommConfig(port_handle, &port_cfg, &len)) {
CloseHandle(port_handle);
win32_err(buf);
error_msg = "Unable to read communication config on " + name + ", " + buf;
return -1;
}
if (!got_default_cfg) {
memcpy(&port_cfg_orig, &port_cfg, sizeof(COMMCONFIG));
}
// http://msdn2.microsoft.com/en-us/library/aa363188(VS.85).aspx
port_cfg.dcb.BaudRate = baud_rate;
port_cfg.dcb.fBinary = TRUE;
port_cfg.dcb.fParity = FALSE;
port_cfg.dcb.fOutxCtsFlow = FALSE;
port_cfg.dcb.fOutxDsrFlow = FALSE;
port_cfg.dcb.fDtrControl = DTR_CONTROL_ENABLE;
port_cfg.dcb.fDsrSensitivity = FALSE;
port_cfg.dcb.fTXContinueOnXoff = TRUE; // ???
port_cfg.dcb.fOutX = FALSE;
port_cfg.dcb.fInX = FALSE;
port_cfg.dcb.fErrorChar = FALSE;
port_cfg.dcb.fNull = FALSE;
port_cfg.dcb.fRtsControl = RTS_CONTROL_DISABLE;
port_cfg.dcb.fAbortOnError = FALSE;
port_cfg.dcb.ByteSize = 8;
port_cfg.dcb.Parity = NOPARITY;
port_cfg.dcb.StopBits = ONESTOPBIT;
if (!SetCommConfig(port_handle, &port_cfg, sizeof(COMMCONFIG))) {
CloseHandle(port_handle);
win32_err(buf);
error_msg = "Unable to write communication config to " + name + ", " + buf;
return -1;
}
if (!EscapeCommFunction(port_handle, CLRDTR | CLRRTS)) {
CloseHandle(port_handle);
win32_err(buf);
error_msg = "Unable to control serial port signals on " + name + ", " + buf;
return -1;
}
// http://msdn2.microsoft.com/en-us/library/aa363190(VS.85).aspx
// setting to all zeros means timeouts are not used
//timeouts.ReadIntervalTimeout = 0;
timeouts.ReadIntervalTimeout = MAXDWORD;
timeouts.ReadTotalTimeoutMultiplier = 0;
timeouts.ReadTotalTimeoutConstant = 0;
timeouts.WriteTotalTimeoutMultiplier = 0;
timeouts.WriteTotalTimeoutConstant = 0;
if (!SetCommTimeouts(port_handle, &timeouts)) {
CloseHandle(port_handle);
win32_err(buf);
error_msg = "Unable to write timeout settings to " + name + ", " + buf;
return -1;
}
#endif
port_name = name;
port_is_open = 1;
return 0;
}
string Serial::get_name(void)
{
if (!port_is_open) return "";
return port_name;
}
// Return the last error message in a (hopefully) user friendly format
string Serial::error_message(void)
{
return error_msg;
}
int Serial::Is_open(void)
{
return port_is_open;
}
// Close the port
void Serial::Close(void)
{
if (!port_is_open) return;
Output_flush();
Input_discard();
port_is_open = 0;
port_name = "";
#if defined(LINUX) || defined(MACOSX)
tcsetattr(port_fd, TCSANOW, &settings_orig);
close(port_fd);
#elif defined(WINDOWS)
//SetCommConfig(port_handle, &port_cfg_orig, sizeof(COMMCONFIG));
CloseHandle(port_handle);
#endif
}
// set the baud rate
int Serial::Set_baud(int baud)
{
if (baud <= 0) return -1;
//printf("set_baud: %d\n", baud);
baud_rate = baud;
if (!port_is_open) return -1;
#if defined(LINUX)
speed_t spd;
switch (baud) {
case 230400: spd = B230400; break;
case 115200: spd = B115200; break;
case 57600: spd = B57600; break;
case 38400: spd = B38400; break;
case 19200: spd = B19200; break;
case 9600: spd = B9600; break;
case 4800: spd = B4800; break;
case 2400: spd = B2400; break;
case 1800: spd = B1800; break;
case 1200: spd = B1200; break;
case 600: spd = B600; break;
case 300: spd = B300; break;
case 200: spd = B200; break;
case 150: spd = B150; break;
case 134: spd = B134; break;
case 110: spd = B110; break;
case 75: spd = B75; break;
case 50: spd = B50; break;
#ifdef B460800
case 460800: spd = B460800; break;
#endif
#ifdef B500000
case 500000: spd = B500000; break;
#endif
#ifdef B576000
case 576000: spd = B576000; break;
#endif
#ifdef B921600
case 921600: spd = B921600; break;
#endif
#ifdef B1000000
case 1000000: spd = B1000000; break;
#endif
#ifdef B1152000
case 1152000: spd = B1152000; break;
#endif
#ifdef B1500000
case 1500000: spd = B1500000; break;
#endif
#ifdef B2000000
case 2000000: spd = B2000000; break;
#endif
#ifdef B2500000
case 2500000: spd = B2500000; break;
#endif
#ifdef B3000000
case 3000000: spd = B3000000; break;
#endif
#ifdef B3500000
case 3500000: spd = B3500000; break;
#endif
#ifdef B4000000
case 4000000: spd = B4000000; break;
#endif
#ifdef B7200
case 7200: spd = B7200; break;
#endif
#ifdef B14400
case 14400: spd = B14400; break;
#endif
#ifdef B28800
case 28800: spd = B28800; break;
#endif
#ifdef B76800
case 76800: spd = B76800; break;
#endif
default: {
return -1;
}
}
cfsetospeed(&settings, spd);
cfsetispeed(&settings, spd);
if (tcsetattr(port_fd, TCSANOW, &settings) < 0) return -1;
#elif defined(MACOSX)
cfsetospeed(&settings, (speed_t)baud);
cfsetispeed(&settings, (speed_t)baud);
if (tcsetattr(port_fd, TCSANOW, &settings) < 0) return -1;
#elif defined(WINDOWS)
DWORD len = sizeof(COMMCONFIG);
port_cfg.dcb.BaudRate = baud;
SetCommConfig(port_handle, &port_cfg, len);
#endif
return 0;
}
int Serial::Set_baud(const string& baud_str)
{
unsigned long b;
b = atoi(baud_str.c_str());
if (!b) return -1;
return Set_baud((int)b);
}
// Read from the serial port. Returns only the bytes that are
// already received, up to count. This always returns without delay,
// returning 0 if nothing has been received
int Serial::Read(void *ptr, int count)
{
if (!port_is_open) return -1;
if (count <= 0) return 0;
#if defined(LINUX)
int n, bits;
n = read(port_fd, ptr, count);
if (n < 0 && (errno == EAGAIN || errno == EINTR)) return 0;
if (n == 0 && ioctl(port_fd, TIOCMGET, &bits) < 0) return -99;
return n;
#elif defined(MACOSX)
int n;
n = read(port_fd, ptr, count);
if (n < 0 && (errno == EAGAIN || errno == EINTR)) return 0;
return n;
#elif defined(WINDOWS)
// first, we'll find out how many bytes have been received
// and are currently waiting for us in the receive buffer.
// http://msdn.microsoft.com/en-us/library/ms885167.aspx
// http://msdn.microsoft.com/en-us/library/ms885173.aspx
// http://source.winehq.org/WineAPI/ClearCommError.html
COMSTAT st;
DWORD errmask=0, num_read, num_request;
OVERLAPPED ov;
int r;
if (!ClearCommError(port_handle, &errmask, &st)) return -1;
//printf("Read, %d requested, %lu buffered\n", count, st.cbInQue);
if (st.cbInQue <= 0) return 0;
// now do a ReadFile, now that we know how much we can read
// a blocking (non-overlapped) read would be simple, but win32
// is all-or-nothing on async I/O and we must have it enabled
// because it's the only way to get a timeout for WaitCommEvent
num_request = ((DWORD)count < st.cbInQue) ? (DWORD)count : st.cbInQue;
ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (ov.hEvent == NULL) return -1;
ov.Internal = ov.InternalHigh = 0;
ov.Offset = ov.OffsetHigh = 0;
if (ReadFile(port_handle, ptr, num_request, &num_read, &ov)) {
// this should usually be the result, since we asked for
// data we knew was already buffered
//printf("Read, immediate complete, num_read=%lu\n", num_read);
r = num_read;
} else {
if (GetLastError() == ERROR_IO_PENDING) {
if (GetOverlappedResult(port_handle, &ov, &num_read, TRUE)) {
//printf("Read, delayed, num_read=%lu\n", num_read);
r = num_read;
} else {
//printf("Read, delayed error\n");
r = -1;
}
} else {
//printf("Read, error\n");
r = -1;
}
}
CloseHandle(ov.hEvent);
// TODO: how much overhead does creating new event objects on
// every I/O request really cost? Would it be better to create
// just 3 when we open the port, and reset/reuse them every time?
// Would mutexes or critical sections be needed to protect them?
return r;
#endif
}
// Write to the serial port. This blocks until the data is
// sent (or in a buffer to be sent). All bytes are written,
// unless there is some sort of error.
int Serial::Write(const void *ptr, int len)
{
//printf("Write %d\n", len);
if (!port_is_open) return -1;
#if defined(LINUX) || defined(MACOSX)
int n, written=0;
fd_set wfds;
struct timeval tv;
while (written < len) {
n = write(port_fd, (const char *)ptr + written, len - written);
if (n < 0 && (errno == EAGAIN || errno == EINTR)) n = 0;
//printf("Write, n = %d\n", n);
if (n < 0) return -1;
if (n > 0) {
written += n;
} else {
tv.tv_sec = 10;
tv.tv_usec = 0;
FD_ZERO(&wfds);
FD_SET(port_fd, &wfds);
n = select(port_fd+1, NULL, &wfds, NULL, &tv);
if (n < 0 && errno == EINTR) n = 1;
if (n <= 0) return -1;
}
}
return written;
#elif defined(WINDOWS)
DWORD num_written;
OVERLAPPED ov;
int r;
ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (ov.hEvent == NULL) return -1;
ov.Internal = ov.InternalHigh = 0;
ov.Offset = ov.OffsetHigh = 0;
if (WriteFile(port_handle, ptr, len, &num_written, &ov)) {
//printf("Write, immediate complete, num_written=%lu\n", num_written);
r = num_written;
} else {
if (GetLastError() == ERROR_IO_PENDING) {
if (GetOverlappedResult(port_handle, &ov, &num_written, TRUE)) {
//printf("Write, delayed, num_written=%lu\n", num_written);
r = num_written;
} else {
//printf("Write, delayed error\n");
r = -1;
}
} else {
//printf("Write, error\n");
r = -1;
}
};
CloseHandle(ov.hEvent);
return r;
#endif
}
// Wait up to msec for data to become available for reading.
// return 0 if timeout, or non-zero if one or more bytes are
// received and can be read. -1 if an error occurs
int Serial::Input_wait(int msec)
{
if (!port_is_open) return -1;
#if defined(LINUX) || defined(MACOSX)
fd_set rfds;
struct timeval tv;
tv.tv_sec = msec / 1000;
tv.tv_usec = (msec % 1000) * 1000;
FD_ZERO(&rfds);
FD_SET(port_fd, &rfds);
return select(port_fd+1, &rfds, NULL, NULL, &tv);
#elif defined(WINDOWS)
// http://msdn2.microsoft.com/en-us/library/aa363479(VS.85).aspx
// http://msdn2.microsoft.com/en-us/library/aa363424(VS.85).aspx
// http://source.winehq.org/WineAPI/WaitCommEvent.html
COMSTAT st;
DWORD errmask=0, eventmask=EV_RXCHAR, ret;
OVERLAPPED ov;
int r;
// first, request comm event when characters arrive
if (!SetCommMask(port_handle, EV_RXCHAR)) return -1;
// look if there are characters in the buffer already
if (!ClearCommError(port_handle, &errmask, &st)) return -1;
//printf("Input_wait, %lu buffered, timeout = %d ms\n", st.cbInQue, msec);
if (st.cbInQue > 0) return 1;
ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (ov.hEvent == NULL) return -1;
ov.Internal = ov.InternalHigh = 0;
ov.Offset = ov.OffsetHigh = 0;
if (WaitCommEvent(port_handle, &eventmask, &ov)) {
//printf("Input_wait, WaitCommEvent, immediate success\n");
r = 1;
} else {
if (GetLastError() == ERROR_IO_PENDING) {
ret = WaitForSingleObject(ov.hEvent, msec);
if (ret == WAIT_OBJECT_0) {
//printf("Input_wait, WaitCommEvent, delayed success\n");
r = 1;
} else if (ret == WAIT_TIMEOUT) {
//printf("Input_wait, WaitCommEvent, timeout\n");
GetCommMask(port_handle, &eventmask);
r = 0;
} else { // WAIT_FAILED or WAIT_ABANDONED
//printf("Input_wait, WaitCommEvent, delayed error\n");
r = -1;
}
} else {
//printf("Input_wait, WaitCommEvent, immediate error\n");
r = -1;
}
}
SetCommMask(port_handle, 0);
CloseHandle(ov.hEvent);
return r;
#endif
}
// Discard all received data that hasn't been read
void Serial::Input_discard(void)
{
if (!port_is_open) return;
#if defined(LINUX) || defined(MACOSX)
// does this really work properly (and is it thread safe) on Linux??
tcflush(port_fd, TCIFLUSH);
#elif defined(WINDOWS)
PurgeComm(port_handle, PURGE_RXCLEAR);
#endif
}
// Wait for all transmitted data with Write to actually leave the serial port
void Serial::Output_flush(void)
{
if (!port_is_open) return;
#if defined(LINUX) || defined(MACOSX)
tcdrain(port_fd);
#elif defined(WINDOWS)
FlushFileBuffers(port_handle);
#endif
}
// set DTR and RTS, 0 = low, 1=high, -1=unchanged
int Serial::Set_control(int dtr, int rts)
{
if (!port_is_open) return -1;
#if defined(LINUX) || defined(MACOSX)
// on Mac OS X, "man 4 tty"
// on Linux, where is this actually documented?
int bits;
if (ioctl(port_fd, TIOCMGET, &bits) < 0) return -1;
if (dtr == 1) {
bits |= TIOCM_DTR;
} else if (dtr == 0) {
bits &= ~TIOCM_DTR;
}
if (rts == 1) {
bits |= TIOCM_RTS;
} else if (rts == 0) {
bits &= ~TIOCM_RTS;
}
if (ioctl(port_fd, TIOCMSET, &bits) < 0) return -1;;
#elif defined(WINDOWS)
// http://msdn.microsoft.com/en-us/library/aa363254(VS.85).aspx
if (dtr == 1) {
if (!EscapeCommFunction(port_handle, SETDTR)) return -1;
} else if (dtr == 0) {
if (!EscapeCommFunction(port_handle, CLRDTR)) return -1;
}
if (rts == 1) {
if (!EscapeCommFunction(port_handle, SETRTS)) return -1;
} else if (rts == 0) {
if (!EscapeCommFunction(port_handle, CLRRTS)) return -1;
}
#endif
return 0;
}
#if defined(LINUX)
// All linux serial port device names. Hopefully all of them anyway. This
// is a long list, but each entry takes only a few bytes and a quick strcmp()
static const char *devnames[] = {
"S", // "normal" Serial Ports - MANY drivers using this
"USB", // USB to serial converters
"ACM", // USB serial modem, CDC class, Abstract Control Model
"MI", // MOXA Smartio/Industio family multiport serial... nice card, I have one :-)
"MX", // MOXA Intellio family multiport serial
"C", // Cyclades async multiport, no longer available, but I have an old ISA one! :-)
"D", // Digiboard (still in 2.6 but no longer supported), new Moschip MCS9901
"P", // Hayes ESP serial cards (obsolete)
"M", // PAM Software's multimodem & Multitech ISI-Cards
"E", // Stallion intelligent multiport (no longer made)
"L", // RISCom/8 multiport serial
"W", // specialix IO8+ multiport serial
"X", // Specialix SX series cards, also SI & XIO series
"SR", // Specialix RIO serial card 257+
"n", // Digi International Neo (yes lowercase 'n', drivers/serial/jsm/jsm_driver.c)
"FB", // serial port on the 21285 StrongArm-110 core logic chip
"AM", // ARM AMBA-type serial ports (no DTR/RTS)
"AMA", // ARM AMBA-type serial ports (no DTR/RTS)
"AT", // Atmel AT91 / AT32 Serial ports
"BF", // Blackfin 5xx serial ports (Analog Devices embedded DSP chips)
"CL", // CLPS711x serial ports (ARM processor)
"A", // ICOM Serial
"SMX", // Motorola IMX serial ports
"SOIC", // ioc3 serial
"IOC", // ioc4 serial
"PSC", // Freescale MPC52xx PSCs configured as UARTs
"MM", // MPSC (UART mode) on Marvell GT64240, GT64260, MV64340...
"B", // Mux console found in some PA-RISC servers
"NX", // NetX serial port
"PZ", // PowerMac Z85c30 based ESCC cell found in the "macio" ASIC
"SAC", // Samsung S3C24XX onboard UARTs
"SA", // SA11x0 serial ports
"AM", // KS8695 serial ports & Sharp LH7A40X embedded serial ports
"TX", // TX3927/TX4927/TX4925/TX4938 internal SIO controller
"SC", // Hitachi SuperH on-chip serial module
"SG", // C-Brick Serial Port (and console) SGI Altix machines
"HV", // SUN4V hypervisor console
"UL", // Xilinx uartlite serial controller
"VR", // NEC VR4100 series Serial Interface Unit
"CPM", // CPM (SCC/SMC) serial ports; core driver
"Y", // Amiga A2232 board
"SL", // Microgate SyncLink ISA and PCI high speed multiprotocol serial
"SLG", // Microgate SyncLink GT (might be sync HDLC only?)
"SLM", // Microgate SyncLink Multiport high speed multiprotocol serial
"CH", // Chase Research AT/PCI-Fast serial card
"F", // Computone IntelliPort serial card
"H", // Chase serial card
"I", // virtual modems
"R", // Comtrol RocketPort
"SI", // SmartIO serial card
"T", // Technology Concepts serial card
"V" // Comtrol VS-1000 serial controller
};
#define NUM_DEVNAMES (sizeof(devnames) / sizeof(const char *))
#endif
#if defined(MACOSX)
static void macos_ports(io_iterator_t * PortIterator, vector& list)
{
io_object_t modemService;
CFTypeRef nameCFstring;
char s[MAXPATHLEN];
while ((modemService = IOIteratorNext(*PortIterator))) {
nameCFstring = IORegistryEntryCreateCFProperty(modemService,
CFSTR(kIOCalloutDeviceKey), kCFAllocatorDefault, 0);
if (nameCFstring) {
if (CFStringGetCString((const __CFString *)nameCFstring,
s, sizeof(s), kCFStringEncodingASCII)) {
list.push_back(s);
}
CFRelease(nameCFstring);
}
IOObjectRelease(modemService);
}
}
#endif
// Return a list of all serial ports
vector Serial::port_list()
{
vector list;
#if defined(LINUX)
// This is ugly guessing, but Linux doesn't seem to provide anything else.
// If there really is an API to discover serial devices on Linux, please
// email paul@pjrc.com with the info. Please?
// The really BAD aspect is all ports get DTR raised briefly, because linux
// has no way to open the port without raising DTR, and there isn't any way
// to tell if the device file really represents hardware without opening it.
// maybe sysfs or udev provides a useful API??
DIR *dir;
struct dirent *f;
struct stat st;
unsigned int i, len[NUM_DEVNAMES];
char s[512];
int fd, bits;
termios mytios;
dir = opendir("/dev/");
if (dir == NULL) return list;
for (i=0; id_name, "tty", 3)) continue;
// ignore anything that's not a known serial device name
for (i=0; id_name + 3, devnames[i], len[i])) break;
}
if (i >= NUM_DEVNAMES) continue;
snprintf(s, sizeof(s), "/dev/%s", f->d_name);
// check if it's a character type device (almost certainly is)
if (stat(s, &st) != 0 || !(st.st_mode & S_IFCHR)) continue;
// now see if we can open the file - if the device file is
// populating /dev but doesn't actually represent a loaded
// driver, this is where we will detect it.
fd = open(s, O_RDONLY | O_NOCTTY | O_NONBLOCK);
if (fd < 0) {
// if permission denied, give benefit of the doubt
// (otherwise the port will be invisible to the user
// and we won't have a to alert them to the permssion
// problem)
if (errno == EACCES) list.push_back(s);
// any other error, assume it's not a real device
continue;
}
// does it respond to termios requests? (probably will since
// the name began with tty). Some devices where a single
// driver exports multiple names will open but this is where
// we can really tell if they work with real hardare.
if (tcgetattr(fd, &mytios) != 0) {
close(fd);
continue;
}
// does it respond to reading the control signals? If it's
// some sort of non-serial terminal (eg, pseudo terminals)
// this is where we will detect it's not really a serial port
if (ioctl(fd, TIOCMGET, &bits) < 0) {
close(fd);
continue;
}
// it passed all the tests, it's a serial port, or some sort
// of "terminal" that looks exactly like a real serial port!
close(fd);
// unfortunately, Linux always raises DTR when open is called.
// not nice! Every serial port is going to get DTR raised
// and then lowered. I wish there were a way to prevent this,
// but it seems impossible.
list.push_back(s);
}
closedir(dir);
#elif defined(MACOSX)
// adapted from SerialPortSample.c, by Apple
// http://developer.apple.com/samplecode/SerialPortSample/listing2.html
// and also testserial.c, by Keyspan
// http://www.keyspan.com/downloads-files/developer/macosx/KesypanTestSerial.c
// www.rxtx.org, src/SerialImp.c seems to be based on Keyspan's testserial.c
// neither keyspan nor rxtx properly release memory allocated.
// more documentation at:
// http://developer.apple.com/documentation/DeviceDrivers/Conceptual/WorkingWSerial/WWSerial_SerialDevs/chapter_2_section_6.html
mach_port_t masterPort;
CFMutableDictionaryRef classesToMatch;
io_iterator_t serialPortIterator;
if (IOMasterPort(NULL, &masterPort) != KERN_SUCCESS) return list;
// a usb-serial adaptor is usually considered a "modem",
// especially when it implements the CDC class spec
classesToMatch = IOServiceMatching(kIOSerialBSDServiceValue);
if (!classesToMatch) return list;
CFDictionarySetValue(classesToMatch, CFSTR(kIOSerialBSDTypeKey),
CFSTR(kIOSerialBSDModemType));
if (IOServiceGetMatchingServices(masterPort, classesToMatch,
&serialPortIterator) != KERN_SUCCESS) return list;
macos_ports(&serialPortIterator, list);
IOObjectRelease(serialPortIterator);
// but it might be considered a "rs232 port", so repeat this
// search for rs232 ports
classesToMatch = IOServiceMatching(kIOSerialBSDServiceValue);
if (!classesToMatch) return list;
CFDictionarySetValue(classesToMatch, CFSTR(kIOSerialBSDTypeKey),
CFSTR(kIOSerialBSDRS232Type));
if (IOServiceGetMatchingServices(masterPort, classesToMatch,
&serialPortIterator) != KERN_SUCCESS) return list;
macos_ports(&serialPortIterator, list);
IOObjectRelease(serialPortIterator);
#elif defined(WINDOWS)
// http://msdn.microsoft.com/en-us/library/aa365461(VS.85).aspx
// page with 7 ways - not all of them work!
// http://www.naughter.com/enumser.html
// may be possible to just query the windows registary
// http://it.gps678.com/2/ca9c8631868fdd65.html
// search in HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\SERIALCOMM
// Vista has some special new way, vista-only
// http://msdn2.microsoft.com/en-us/library/aa814070(VS.85).aspx
char *buffer, *p;
//DWORD size = QUERYDOSDEVICE_BUFFER_SIZE;
DWORD ret;
buffer = (char *)malloc(QUERYDOSDEVICE_BUFFER_SIZE);
if (buffer == NULL) return list;
memset(buffer, 0, QUERYDOSDEVICE_BUFFER_SIZE);
ret = QueryDosDeviceA(NULL, buffer, QUERYDOSDEVICE_BUFFER_SIZE);
if (ret) {
//printf("Detect Serial using QueryDosDeviceA: ");
for (p = buffer; *p; p += strlen(p) + 1) {
if (strncmp(p, "COM", 3)) continue;
list.push_back(string(p) );
//printf(": %s\n", p);
}
} else {
char buf[1024];
win32_err(buf);
printf("QueryDosDeviceA failed, error \"%s\"\n", buf);
printf("Detect Serial using brute force GetDefaultCommConfig probing: ");
for (int i=1; i<=32; i++) {
printf("try %s", buf);
COMMCONFIG cfg;
DWORD len;
snprintf(buf, sizeof(buf), "COM%d", i);
if (GetDefaultCommConfig(buf, &cfg, &len)) {
std::ostringstream name;
name << "COM" << i << ":";
list.push_back(name.str());
printf(": %s", buf);
}
}
}
free(buffer);
#endif
std::sort (list.begin(), list.end());
return list;
}