/* Copyright 2012 William Woodall and John Harrison */ #include "serial/impl/windows.h" using std::string; using std::invalid_argument; using serial::Serial; using serial::SerialExecption; using serial::PortNotOpenedException; using serial::IOException; Serial::SerialImpl::SerialImpl (const string &port, unsigned long baudrate, long timeout, bytesize_t bytesize, parity_t parity, stopbits_t stopbits, flowcontrol_t flowcontrol) : port_ (port), fd_ (-1), isOpen_ (false), xonxoff_ (true), rtscts_ (false), timeout_ (timeout), baudrate_ (baudrate), parity_ (parity), bytesize_ (bytesize), stopbits_ (stopbits), flowcontrol_ (flowcontrol) { if (port_.empty () == false) open (); } Serial::SerialImpl::~SerialImpl () { close(); } void Serial::SerialImpl::open () { if (port_.empty()) { throw invalid_argument ("bad port specified"); } if (isOpen_ == true) { throw SerialExecption ("port already open"); } fd_ = ::open (port_.c_str(), O_RDWR | O_NOCTTY | O_NONBLOCK); if (fd_ == -1) { switch (errno) { case EINTR: // Recurse because this is a recoverable error. open (); return; case ENFILE: case EMFILE: throw IOException ("to many file handles open"); break; default: throw IOException (errno); } } reconfigurePort(); isOpen_ = true; } void Serial::SerialImpl::reconfigurePort () { if (fd_ == -1) { // Can only operate on a valid file descriptor throw IOException ("invalid file descriptor"); } struct termios options; // The options for the file descriptor if (tcgetattr(fd_, &options) == -1) { throw IOException ("::tcgetattr"); } // set up raw mode / no echo / binary options.c_cflag |= (unsigned long) (CLOCAL|CREAD); options.c_lflag &= (unsigned long) ~(ICANON|ECHO|ECHOE|ECHOK|ECHONL| ISIG|IEXTEN); //|ECHOPRT options.c_oflag &= (unsigned long) ~(OPOST); options.c_iflag &= (unsigned long) ~(INLCR|IGNCR|ICRNL|IGNBRK); #ifdef IUCLC options.c_iflag &= (unsigned long) ~IUCLC; #endif #ifdef PARMRK options.c_iflag &= (unsigned long) ~PARMRK; #endif // setup baud rate bool custom_baud = false; speed_t baud; switch (baudrate_) { #ifdef B0 case 0: baud = B0; break; #endif #ifdef B50 case 50: baud = B50; break; #endif #ifdef B75 case 75: baud = B75; break; #endif #ifdef B110 case 110: baud = B110; break; #endif #ifdef B134 case 134: baud = B134; break; #endif #ifdef B150 case 150: baud = B150; break; #endif #ifdef B200 case 200: baud = B200; break; #endif #ifdef B300 case 300: baud = B300; break; #endif #ifdef B600 case 600: baud = B600; break; #endif #ifdef B1200 case 1200: baud = B1200; break; #endif #ifdef B1800 case 1800: baud = B1800; break; #endif #ifdef B2400 case 2400: baud = B2400; break; #endif #ifdef B4800 case 4800: baud = B4800; break; #endif #ifdef B7200 case 7200: baud = B7200; break; #endif #ifdef B9600 case 9600: baud = B9600; break; #endif #ifdef B14400 case 14400: baud = B14400; break; #endif #ifdef B19200 case 19200: baud = B19200; break; #endif #ifdef B28800 case 28800: baud = B28800; break; #endif #ifdef B57600 case 57600: baud = B57600; break; #endif #ifdef B76800 case 76800: baud = B76800; break; #endif #ifdef B38400 case 38400: baud = B38400; break; #endif #ifdef B115200 case 115200: baud = B115200; break; #endif #ifdef B128000 case 128000: baud = B128000; break; #endif #ifdef B153600 case 153600: baud = B153600; break; #endif #ifdef B230400 case 230400: baud = B230400; break; #endif #ifdef B256000 case 256000: baud = B256000; break; #endif #ifdef B460800 case 460800: baud = B460800; break; #endif #ifdef B921600 case 921600: baud = B921600; break; #endif default: custom_baud = true; // Mac OS X 10.x Support #if defined(__APPLE__) && defined(__MACH__) #define IOSSIOSPEED _IOW('T', 2, speed_t) int new_baud = static_cast<int> (baudrate_); if (ioctl (fd_, IOSSIOSPEED, &new_baud, 1) < 0) { throw IOException (errno); } // Linux Support #elif defined(__linux__) struct serial_struct ser; ioctl(fd_, TIOCGSERIAL, &ser); // set custom divisor ser.custom_divisor = ser.baud_base / baudrate_; // update flags ser.flags &= ~ASYNC_SPD_MASK; ser.flags |= ASYNC_SPD_CUST; if (ioctl(fd_, TIOCSSERIAL, ser) < 0) { throw IOException (errno); } #else throw invalid_argument ("OS does not currently support custom bauds"); #endif } if (custom_baud == false) { #ifdef _BSD_SOURCE ::cfsetspeed(&options, baud); #else ::cfsetispeed(&options, baud); ::cfsetospeed(&options, baud); #endif } // setup char len options.c_cflag &= (unsigned long) ~CSIZE; if (bytesize_ == EIGHTBITS) options.c_cflag |= CS8; else if (bytesize_ == SEVENBITS) options.c_cflag |= CS7; else if (bytesize_ == SIXBITS) options.c_cflag |= CS6; else if (bytesize_ == FIVEBITS) options.c_cflag |= CS5; else throw invalid_argument ("invalid char len"); // setup stopbits if (stopbits_ == STOPBITS_ONE) options.c_cflag &= (unsigned long) ~(CSTOPB); else if (stopbits_ == STOPBITS_ONE_POINT_FIVE) options.c_cflag |= (CSTOPB); // XXX same as TWO.. there is no POSIX support for 1.5 else if (stopbits_ == STOPBITS_TWO) options.c_cflag |= (CSTOPB); else throw invalid_argument ("invalid stop bit"); // setup parity options.c_iflag &= (unsigned long) ~(INPCK|ISTRIP); if (parity_ == PARITY_NONE) { options.c_cflag &= (unsigned long) ~(PARENB|PARODD); } else if (parity_ == PARITY_EVEN) { options.c_cflag &= (unsigned long) ~(PARODD); options.c_cflag |= (PARENB); } else if (parity_ == PARITY_ODD) { options.c_cflag |= (PARENB|PARODD); } else { throw invalid_argument ("invalid parity"); } // setup flow control // xonxoff #ifdef IXANY if (xonxoff_) options.c_iflag |= (IXON|IXOFF); //|IXANY) else options.c_iflag &= (unsigned long) ~(IXON|IXOFF|IXANY); #else if (xonxoff_) options.c_iflag |= (IXON|IXOFF); else options.c_iflag &= (unsigned long) ~(IXON|IXOFF); #endif // rtscts #ifdef CRTSCTS if (rtscts_) options.c_cflag |= (CRTSCTS); else options.c_cflag &= (unsigned long) ~(CRTSCTS); #elif defined CNEW_RTSCTS if (rtscts_) options.c_cflag |= (CNEW_RTSCTS); else options.c_cflag &= (unsigned long) ~(CNEW_RTSCTS); #else #error "OS Support seems wrong." #endif options.c_cc[VMIN] = 1; // Minimum of 1 character in the buffer options.c_cc[VTIME] = 0; // timeout on waiting for new data // activate settings ::tcsetattr (fd_, TCSANOW, &options); } void Serial::SerialImpl::close () { if (isOpen_ == true) { if (fd_ != -1) { ::close (fd_); // Ignoring the outcome fd_ = -1; } isOpen_ = false; } } bool Serial::SerialImpl::isOpen () const { return isOpen_; } size_t Serial::SerialImpl::available () { if (!isOpen_) { return 0; } int count = 0; int result = ioctl (fd_, TIOCINQ, &count); if (result == 0) { return static_cast<size_t> (count); } else { throw IOException (errno); } } size_t Serial::SerialImpl::read (char* buf, size_t size) { if (!isOpen_) { throw PortNotOpenedException ("Serial::read"); } fd_set readfds; ssize_t bytes_read = 0; int count = 0; while (true) { count++; if (timeout_ != -1) { FD_ZERO (&readfds); FD_SET (fd_, &readfds); struct timeval timeout; timeout.tv_sec = timeout_ / 1000; timeout.tv_usec = static_cast<int> (timeout_ % 1000) * 1000; int r = select (fd_ + 1, &readfds, NULL, NULL, &timeout); if (r == -1 && errno == EINTR) continue; if (r == -1) { throw IOException (errno); } } if (timeout_ == -1 || FD_ISSET (fd_, &readfds)) { bytes_read = ::read (fd_, buf, size); // read should always return some data as select reported it was // ready to read when we get to this point. if (bytes_read < 1) { // Disconnected devices, at least on Linux, show the // behavior that they are always ready to read immediately // but reading returns nothing. throw SerialExecption ("device reports readiness to read but " "returned no data (device disconnected?)"); } break; } else { break; } } return static_cast<size_t> (bytes_read); } size_t Serial::SerialImpl::write (const string &data) { if (isOpen_ == false) { throw PortNotOpenedException ("Serial::write"); } fd_set writefds; ssize_t bytes_written = 0; while (true) { if (timeout_ != -1) { FD_ZERO (&writefds); FD_SET (fd_, &writefds); struct timeval timeout; timeout.tv_sec = timeout_ / 1000; timeout.tv_usec = static_cast<int> (timeout_ % 1000) * 1000; int r = select (fd_ + 1, NULL, &writefds, NULL, &timeout); if (r == -1 && errno == EINTR) continue; if (r == -1) { throw IOException (errno); } } if (timeout_ == -1 || FD_ISSET (fd_, &writefds)) { bytes_written = ::write (fd_, data.c_str (), data.length ()); // read should always return some data as select reported it was // ready to read when we get to this point. if (bytes_written < 1) { // Disconnected devices, at least on Linux, show the // behavior that they are always ready to read immediately // but reading returns nothing. throw SerialExecption ("device reports readiness to read but " "returned no data (device disconnected?)"); } break; } else { break; } } return static_cast<size_t> (bytes_written); } void Serial::SerialImpl::setPort (const string &port) { port_ = port; } string Serial::SerialImpl::getPort () const { return port_; } void Serial::SerialImpl::setTimeout (long timeout) { timeout_ = timeout; } long Serial::SerialImpl::getTimeout () const { return timeout_; } void Serial::SerialImpl::setBaudrate (unsigned long baudrate) { baudrate_ = baudrate; if (isOpen_) reconfigurePort (); } unsigned long Serial::SerialImpl::getBaudrate () const { return baudrate_; } void Serial::SerialImpl::setBytesize (serial::bytesize_t bytesize) { bytesize_ = bytesize; if (isOpen_) reconfigurePort (); } serial::bytesize_t Serial::SerialImpl::getBytesize () const { return bytesize_; } void Serial::SerialImpl::setParity (serial::parity_t parity) { parity_ = parity; if (isOpen_) reconfigurePort (); } serial::parity_t Serial::SerialImpl::getParity () const { return parity_; } void Serial::SerialImpl::setStopbits (serial::stopbits_t stopbits) { stopbits_ = stopbits; if (isOpen_) reconfigurePort (); } serial::stopbits_t Serial::SerialImpl::getStopbits () const { return stopbits_; } void Serial::SerialImpl::setFlowcontrol (serial::flowcontrol_t flowcontrol) { flowcontrol_ = flowcontrol; if (isOpen_) reconfigurePort (); } serial::flowcontrol_t Serial::SerialImpl::getFlowcontrol () const { return flowcontrol_; } void Serial::SerialImpl::flush () { if (isOpen_ == false) { throw PortNotOpenedException ("Serial::flush"); } tcdrain (fd_); } void Serial::SerialImpl::flushInput () { if (isOpen_ == false) { throw PortNotOpenedException ("Serial::flushInput"); } tcflush (fd_, TCIFLUSH); } void Serial::SerialImpl::flushOutput () { if (isOpen_ == false) { throw PortNotOpenedException ("Serial::flushOutput"); } tcflush (fd_, TCOFLUSH); } void Serial::SerialImpl::sendBreak (int duration) { if (isOpen_ == false) { throw PortNotOpenedException ("Serial::sendBreak"); } tcsendbreak (fd_, static_cast<int> (duration/4)); } void Serial::SerialImpl::setBreak (bool level) { if (isOpen_ == false) { throw PortNotOpenedException ("Serial::setBreak"); } if (level) { ioctl (fd_, TIOCSBRK); } else { ioctl (fd_, TIOCCBRK); } } void Serial::SerialImpl::setRTS (bool level) { if (isOpen_ == false) { throw PortNotOpenedException ("Serial::setRTS"); } if (level) { ioctl (fd_, TIOCMBIS, TIOCM_RTS); } else { ioctl (fd_, TIOCMBIC, TIOCM_RTS); } } void Serial::SerialImpl::setDTR (bool level) { if (isOpen_ == false) { throw PortNotOpenedException ("Serial::setDTR"); } if (level) { ioctl (fd_, TIOCMBIS, TIOCM_DTR); } else { ioctl (fd_, TIOCMBIC, TIOCM_DTR); } } bool Serial::SerialImpl::getCTS () { if (isOpen_ == false) { throw PortNotOpenedException ("Serial::getCTS"); } int s = ioctl (fd_, TIOCMGET, 0); return (s & TIOCM_CTS) != 0; } bool Serial::SerialImpl::getDSR() { if (isOpen_ == false) { throw PortNotOpenedException ("Serial::getDSR"); } int s = ioctl(fd_, TIOCMGET, 0); return (s & TIOCM_DSR) != 0; } bool Serial::SerialImpl::getRI() { if (isOpen_ == false) { throw PortNotOpenedException ("Serial::getRI"); } int s = ioctl (fd_, TIOCMGET, 0); return (s & TIOCM_RI) != 0; } bool Serial::SerialImpl::getCD() { if (isOpen_ == false) { throw PortNotOpenedException ("Serial::getCD"); } int s = ioctl (fd_, TIOCMGET, 0); return (s & TIOCM_CD) != 0; }