/* 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(WIN32) #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(WIN32) 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(WIN32) //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(WIN32) 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(WIN32) // 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(WIN32) 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(WIN32) // 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(WIN32) 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(WIN32) 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(WIN32) // 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(WIN32) // 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; }