/* * Unofficial Qt Serial Port Library * * Copyright (c) 2010 Inbiza Systems Inc. All rights reserved. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU Lesser 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 Lesser General Public License * along with this program. If not, see * * * @file termioshelper.cpp */ #include #include #include #include #include "termioshelper.h" namespace TNX { /*! Constructs a TermiosHelper object with the given \a file descriptor. */ TermiosHelper::TermiosHelper(int fileDescriptor) : fileDescriptor_(fileDescriptor), originalAttrs_(NULL), currentAttrs_(NULL) { Q_ASSERT(fileDescriptor_ > 0); originalAttrs_ = new termios(); currentAttrs_ = new termios(); // save the current serial port attributes // see restoreTermios() saveTermios(); // clone the original attributes *currentAttrs_ = *originalAttrs_; // initialize port attributes for serial port communication initTermios(); } TermiosHelper::~TermiosHelper() { // It is good practice to reset a serial port back to the state in // which you found it. This is why we saved the original termios struct // The constant TCSANOW (defined in termios.h) indicates that // the change should take effect immediately. restoreTermios(); delete originalAttrs_; delete currentAttrs_; } /*! Sets the termios structure. */ bool TermiosHelper::applyChanges(ChangeApplyTypes /*apptype*/) { // there is only termios structure to be applied for posix compatible platforms if ( tcsetattr(fileDescriptor_, TCSANOW, currentAttrs_) == -1 ) { qCritical() << QString("TermiosHelper::applyChanges(file: %1, applyType: %2) failed " \ "when setting new attributes: %3(%4)") .arg(fileDescriptor_) .arg(TCSANOW) .arg(strerror(errno)) .arg(errno); return false; } return true; } /*! */ bool TermiosHelper::setCtrSignal(ControlSignals csig, bool value) { int status; if ( ioctl(fileDescriptor_, TIOCMGET, &status) == -1 ) { qCritical() << QString("TermiosHelper::setCtrSignal(file: %1, csig: %2) failed" \ "when fetching control signal values : %3(%4)") .arg(fileDescriptor_) .arg(csig) .arg(strerror(errno)) .arg(errno); return false; } if ( value ) status |= (int)csig; else status &= ~((int)csig); if ( ioctl(fileDescriptor_, TIOCMSET, &status) == -1 ) { qCritical() << QString("TermiosHelper::setCtrSignal(file: %1, csig: %2) failed" \ "when setting control signal values : %3(%4)") .arg(fileDescriptor_) .arg(csig) .arg(strerror(errno)) .arg(errno); return false; } return true; } /*! */ QSerialPort::CommSignalValues TermiosHelper::ctrSignal(ControlSignals csig) const { int status; if ( ioctl(fileDescriptor_, TIOCMGET, &status) == -1 ) { qCritical() << QString("TermiosHelper::ctrSignal(file: %1, csig: %2) failed" \ "when fetching control signal values : %3(%4)") .arg(fileDescriptor_) .arg(csig) .arg(strerror(errno)) .arg(errno); return QSerialPort::Signal_Unknown; } return (status & ((int)csig)) ? QSerialPort::Signal_On : QSerialPort::Signal_Off; } /*! */ void TermiosHelper::initTermios() { // Set raw input (non-canonical) mode, with reads blocking until either a single character // has been received or a one second timeout expires. // See tcsetattr(4) ("man 4 tcsetattr") and termios(4) ("man 4 termios") for details. cfmakeraw(currentAttrs_); currentAttrs_->c_cflag |= (CLOCAL | CREAD); // set communication timeouts currentAttrs_->c_cc[VMIN] = kDefaultNumOfBytes; // converting ms to one-tenths-of-second (sec * 0.1) currentAttrs_->c_cc[VTIME] = (kDefaultReadTimeout / 100); // ensure the new attributes take effect immediately applyChanges(); } /*! */ void TermiosHelper::saveTermios() { // get the current serial port attributes if ( tcgetattr(fileDescriptor_, originalAttrs_) == -1 ) { qWarning() << QString("TermiosHelper::saveTermios(file: %1) failed when" \ " getting original port attributes: %2(%3)") .arg(fileDescriptor_) .arg(strerror(errno)) .arg(errno); } } /*! */ void TermiosHelper::restoreTermios() { if ( !originalAttrs_ || tcsetattr(fileDescriptor_, TCSANOW, originalAttrs_) == -1 ) { qWarning() << QString("TermiosHelper::restoreTermios(file: %1) failed when resetting " \ "serial port attributes: %2(%3)") .arg(fileDescriptor_) .arg(strerror(errno)) .arg(errno); } // Block until all written output has been sent from the device. // Note that this call is simply passed on to the serial device driver. // See tcsendbreak(3) ("man 3 tcsendbreak") for details. if ( tcdrain(fileDescriptor_) == -1 ) { qWarning() << QString("TermiosHelper::restoreTermios(file: %1) failed while waiting for drain: %2(%3)") .arg(fileDescriptor_) .arg(strerror(errno)) .arg(errno); } } /*! */ void TermiosHelper::setBaudRate(QPortSettings::BaudRate baudRate) { speed_t baud = B9600; switch ( baudRate ) { case QPortSettings::BAUDR_50: baud = B50; break; case QPortSettings::BAUDR_75: baud = B75; break; case QPortSettings::BAUDR_110: baud = B110; break; case QPortSettings::BAUDR_134: baud = B134; break; case QPortSettings::BAUDR_150: baud = B150; break; case QPortSettings::BAUDR_200: baud = B200; break; case QPortSettings::BAUDR_300: baud = B300; break; case QPortSettings::BAUDR_600: baud = B600; break; case QPortSettings::BAUDR_1200: baud = B1200; break; case QPortSettings::BAUDR_1800: baud = B1800; break; case QPortSettings::BAUDR_2400: baud = B2400; break; case QPortSettings::BAUDR_4800: baud = B4800; break; case QPortSettings::BAUDR_9600: baud = B9600; break; case QPortSettings::BAUDR_19200: baud = B19200; break; case QPortSettings::BAUDR_38400: baud = B38400; break; case QPortSettings::BAUDR_57600: baud = B57600; break; //case QPortSettings::BAUDR_76800: // baud = B76800; // break; case QPortSettings::BAUDR_115200: baud = B115200; break; #if defined(Q_OS_LINUX) case QPortSettings::BAUDR_230400: baud = B230400; break; case QPortSettings::BAUDR_460800: baud = B460800; break; case QPortSettings::BAUDR_500000: baud = B500000; break; case QPortSettings::BAUDR_576000: baud = B576000; break; case QPortSettings::BAUDR_921600: baud = B921600; break; #endif default: qWarning() << "TermiosHelper::setBaudRate(" << baudRate << "): " \ "Unsupported baud rate"; } if ( cfsetspeed(currentAttrs_, baud) == -1 ) { qCritical() << QString("TermiosHelper::setBaudRate(file: %1) failed: %2(%3)") .arg(fileDescriptor_) .arg(strerror(errno)) .arg(errno); } } /*! */ QPortSettings::BaudRate TermiosHelper::baudRate() const { speed_t ibaud = cfgetispeed(currentAttrs_); speed_t obaud = cfgetospeed(currentAttrs_); (obaud == ibaud); Q_ASSERT(ibaud == obaud); switch ( ibaud ) { case B50: return QPortSettings::BAUDR_50; case B75: return QPortSettings::BAUDR_75; case B110: return QPortSettings::BAUDR_110; case B134: return QPortSettings::BAUDR_134; case B150: return QPortSettings::BAUDR_150; case B200: return QPortSettings::BAUDR_200; case B300: return QPortSettings::BAUDR_300; case B600: return QPortSettings::BAUDR_600; case B1200: return QPortSettings::BAUDR_1200; case B1800: return QPortSettings::BAUDR_1800; case B2400: return QPortSettings::BAUDR_2400; case B4800: return QPortSettings::BAUDR_4800; case B9600: return QPortSettings::BAUDR_9600; case B19200: return QPortSettings::BAUDR_19200; case B38400: return QPortSettings::BAUDR_38400; case B57600: return QPortSettings::BAUDR_57600; //case B76800: // return QPortSettings::BAUDR_76800; case B115200: return QPortSettings::BAUDR_115200; #if defined(Q_OS_LINUX) case B230400: return QPortSettings::BAUDR_230400; // the baud rates listed below are not supported by all UARTs // and Linux distros case B460800: return QPortSettings::BAUDR_460800; case B500000: return QPortSettings::BAUDR_500000; case B576000: return QPortSettings::BAUDR_576000; case B921600: return QPortSettings::BAUDR_921600; #endif default: qWarning() << "TermiosHelper::baudRate(): Unknown baud rate"; } return QPortSettings::BAUDR_UNKNOWN; } /*! */ QPortSettings::DataBits TermiosHelper::dataBits() const { struct termios options; // get the current serial port attributes if ( tcgetattr(fileDescriptor_, &options) == -1 ) { qWarning() << QString("TermiosHelper::dataBits(file: %1) failed when" \ " getting original port attributes: %2(%3)") .arg(fileDescriptor_) .arg(strerror(errno)) .arg(errno); return QPortSettings::DB_UNKNOWN; } if ( (options.c_cflag & CSIZE) == CS5 ) return QPortSettings::DB_5; else if ( (options.c_cflag & CSIZE) == CS6 ) return QPortSettings::DB_6; else if ( (options.c_cflag & CSIZE) == CS7 ) return QPortSettings::DB_7; else if ( (options.c_cflag & CSIZE) == CS8 ) return QPortSettings::DB_8; else return QPortSettings::DB_UNKNOWN; } /*! */ void TermiosHelper::setDataBits(QPortSettings::DataBits dataBits) { switch( dataBits ) { /*5 data bits*/ case QPortSettings::DB_5: currentAttrs_->c_cflag &= (~CSIZE); currentAttrs_->c_cflag |= CS5; break; /*6 data bits*/ case QPortSettings::DB_6: currentAttrs_->c_cflag &= (~CSIZE); currentAttrs_->c_cflag |= CS6; break; /*7 data bits*/ case QPortSettings::DB_7: currentAttrs_->c_cflag &= (~CSIZE); currentAttrs_->c_cflag |= CS7; break; /*8 data bits*/ case QPortSettings::DB_8: currentAttrs_->c_cflag &= (~CSIZE); currentAttrs_->c_cflag |= CS8; break; default: currentAttrs_->c_cflag &= (~CSIZE); currentAttrs_->c_cflag |= CS8; qWarning() << "TermiosHelper::setDataBits(" << dataBits << "): Unsupported data bits"; } } /*! */ QPortSettings::Parity TermiosHelper::parity() const { struct termios options; // get the current serial port attributes if ( tcgetattr(fileDescriptor_, &options) == -1 ) { qWarning() << QString("TermiosHelper::parity(file: %1) failed when getting original " \ "port attributes: %2(%3)") .arg(fileDescriptor_) .arg(strerror(errno)) .arg(errno); return QPortSettings::PAR_UNKNOWN; } if ( options.c_cflag & PARENB ) { if ( options.c_cflag & PARODD ) return QPortSettings::PAR_ODD; else return QPortSettings::PAR_EVEN; } else return QPortSettings::PAR_NONE; } /*! */ void TermiosHelper::setParity(QPortSettings::Parity parity) { switch ( parity ) { /*no parity*/ case QPortSettings::PAR_NONE: currentAttrs_->c_cflag &= (~PARENB); break; /*odd parity*/ case QPortSettings::PAR_ODD: currentAttrs_->c_cflag |= (PARENB|PARODD); break; /*even parity*/ case QPortSettings::PAR_EVEN: currentAttrs_->c_cflag &= (~PARODD); currentAttrs_->c_cflag |= PARENB; break; default: currentAttrs_->c_cflag &= (~PARENB); qWarning() << "TermiosHelper::setParity(" << parity << "): Unsupported parity"; } } /*! */ QPortSettings::StopBits TermiosHelper::stopBits() const { struct termios options; // get the current serial port attributes if ( tcgetattr(fileDescriptor_, &options) == -1 ) { qWarning() << QString("TermiosHelper::stopBits(file: %1) failed when getting original " \ "port attributes: %2(%3)") .arg(fileDescriptor_) .arg(strerror(errno)) .arg(errno); return QPortSettings::STOP_UNKNOWN; } if ( options.c_cflag & CSTOPB ) return QPortSettings::STOP_2; else return QPortSettings::STOP_1; } /*! */ void TermiosHelper::setStopBits(QPortSettings::StopBits stopBits) { switch( stopBits ) { /*one stop bit*/ case QPortSettings::STOP_1: currentAttrs_->c_cflag &= (~CSTOPB); break; /*two stop bits*/ case QPortSettings::STOP_2: currentAttrs_->c_cflag |= CSTOPB; break; default: currentAttrs_->c_cflag &= (~CSTOPB); qWarning() << "TermiosHelper::setStopBits(" << stopBits << "): Unsupported stop bits"; } } /*! @return FLOW_UNKNOWN in error case */ QPortSettings::FlowControl TermiosHelper::flowControl() const { struct termios options; // get the current serial port attributes if ( tcgetattr(fileDescriptor_, &options) == -1 ) { qWarning() << QString("TermiosHelper::flowControl(file: %1) failed when getting original " \ "port attributes: %2(%3)") .arg(fileDescriptor_) .arg(strerror(errno)) .arg(errno); return QPortSettings::FLOW_UNKNOWN; } if ( options.c_cflag & CRTSCTS ) { return QPortSettings::FLOW_HARDWARE; } else { if ( options.c_iflag & IXON ) return QPortSettings::FLOW_XONXOFF; else return QPortSettings::FLOW_OFF; } } /*! */ void TermiosHelper::setFlowControl(QPortSettings::FlowControl flow) { switch( flow ) { /*no flow control*/ case QPortSettings::FLOW_OFF: currentAttrs_->c_cflag &= (~CRTSCTS); currentAttrs_->c_iflag &= (~(IXON|IXOFF|IXANY)); break; /*software (XON/XOFF) flow control*/ case QPortSettings::FLOW_XONXOFF: currentAttrs_->c_cflag &= (~CRTSCTS); currentAttrs_->c_iflag |= (IXON|IXOFF|IXANY); break; case QPortSettings::FLOW_HARDWARE: currentAttrs_->c_cflag |= CRTSCTS; currentAttrs_->c_iflag &= (~(IXON|IXOFF|IXANY)); break; default: currentAttrs_->c_cflag &= (~CRTSCTS); currentAttrs_->c_iflag &= (~(IXON|IXOFF|IXANY)); qWarning() << "TermiosHelper::setFlowControl(" << flow << "): Unsupported flow control type"; } } // // information about VMIN and VTIME // @see www.unixwiz.net/techtips/termios-vmin-vtime.html // // VMIN is a character count ranging from 0 to 255 characters, and VTIME is time measured in 0.1 // second intervals, (0 to 25.5 seconds). The value of "zero" is special to both of these parameters, // and this suggests four combinations that we'll discuss below. In every case, the question is when // a read() system call is satisfied, and this is our prototype call: // int n = read(fd, buffer, nbytes); // // Keep in mind that the tty driver maintains an input queue of bytes already read from the // serial line and not passed to the user, so not every read() call waits for actual I/O - the read // may very well be satisfied directly from the input queue. // // VMIN = 0 and VTIME = 0 // This is a completely non-blocking read - the call is satisfied immediately directly from the // driver's input queue. If data are available, it's transferred to the caller's buffer up to // nbytes and returned. Otherwise zero is immediately returned to indicate "no data". We'll note // that this is "polling" of the serial port, and it's almost always a bad idea. If done repeatedly, // it can consume enormous amounts of processor time and is highly inefficient. Don't use this mode // unless you really, really know what you're doing. // // VMIN = 0 and VTIME > 0 // This is a pure timed read. If data are available in the input queue, it's transferred to the caller's // buffer up to a maximum of nbytes, and returned immediately to the caller. Otherwise the driver blocks // until data arrives, or when VTIME tenths expire from the start of the call. If the timer expires // without data, zero is returned. A single byte is sufficient to satisfy this read call, but if more // is available in the input queue, it's returned to the caller. Note that this is an overall timer, // not an intercharacter one. // // VMIN > 0 and VTIME > 0 // A read() is satisfied when either VMIN characters have been transferred to the caller's buffer, // or when VTIME tenths expire between characters. Since this timer is not started until the first // character arrives, this call can block indefinitely if the serial line is idle. This is the most // common mode of operation, and we consider VTIME to be an intercharacter timeout, not an overall one. // This call should never return zero bytes read. // // VMIN > 0 and VTIME = 0 // This is a counted read that is satisfied only when at least VMIN characters have been transferred // to the caller's buffer - there is no timing component involved. This read can be satisfied from the // driver's input queue (where the call could return immediately), or by waiting for new data to arrive: // in this respect the call could block indefinitely. We believe that it's undefined behavior if nbytes // is less then VMIN. // bool TermiosHelper::commTimeouts(CommTimeouts &commtimeouts) const { struct termios options; // get the current serial port attributes if ( tcgetattr(fileDescriptor_, &options) == -1 ) { qWarning() << QString("TermiosHelper::commTimeouts(file: %1) failed when getting original " \ "port attributes: %2(%3)") .arg(fileDescriptor_) .arg(strerror(errno)) .arg(errno); return false; } commtimeouts.PosixVMIN = options.c_cc[VMIN]; commtimeouts.PosixVTIME = options.c_cc[VTIME]; return true; } void TermiosHelper::setCommTimeouts(const CommTimeouts commtimeouts) { currentAttrs_->c_cc[VMIN] = (cc_t)commtimeouts.PosixVMIN; currentAttrs_->c_cc[VTIME] = (cc_t)commtimeouts.PosixVTIME; } } // namespace