/* * Copyright (C) 2015 by Multi-Tech Systems * * This file is part of libmts-io. * * libmts-io 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 2 of the License, or * (at your option) any later version. * * libmts-io 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with libmts-io. If not, see . * */ /*! \file MTS_IO_SerialConnection.cpp \brief A brief description \date Jan 15, 2013 \author sgodinez A more elaborate description */ #include #include #include #include #ifdef WIN32 #else #include /* File control definitions */ #include /* Error number definitions */ #include /* POSIX terminal control definitions */ #include /* String function definitions */ #endif using namespace MTS::IO; SerialConnection::Builder::Builder(const std::string& sPortName) : m_sPortName(sPortName) , m_iBaudRate(9600) , m_eType(RS232) , m_eParity(OFF) , m_eDataBits(B8) , m_eStopBits(B1) , m_eFlowControl(NONE) , m_bBuilt(false) { } SerialConnection::Builder& SerialConnection::Builder::type(const ETYPE& eType) { m_eType = eType; return *this; } SerialConnection::Builder& SerialConnection::Builder::rs232() { m_eType = RS232; return *this; } SerialConnection::Builder& SerialConnection::Builder::rs422() { m_eType = RS422; return *this; } SerialConnection::Builder& SerialConnection::Builder::rs485() { m_eType = RS485; return *this; } SerialConnection::Builder& SerialConnection::Builder::parity(const EPARITY& eParity) { m_eParity = eParity; return *this; } SerialConnection::Builder& SerialConnection::Builder::parityOff() { m_eParity = OFF; return *this; } SerialConnection::Builder& SerialConnection::Builder::parityOdd() { m_eParity = ODD; return *this; } SerialConnection::Builder& SerialConnection::Builder::parityEven() { m_eParity = EVEN; return *this; } SerialConnection::Builder& SerialConnection::Builder::dataBits(const EDATABITS& eDataBits) { m_eDataBits = eDataBits; return *this; } SerialConnection::Builder& SerialConnection::Builder::fiveDataBits() { m_eDataBits = B5; return *this; } SerialConnection::Builder& SerialConnection::Builder::sixDataBits() { m_eDataBits = B6; return *this; } SerialConnection::Builder& SerialConnection::Builder::sevenDataBits() { m_eDataBits = B7; return *this; } SerialConnection::Builder& SerialConnection::Builder::eightDataBits() { m_eDataBits = B8; return *this; } SerialConnection::Builder& SerialConnection::Builder::stopBits(const ESTOPBITS& eStopBits) { m_eStopBits = eStopBits; return *this; } SerialConnection::Builder& SerialConnection::Builder::oneStopBit() { m_eStopBits = B1; return *this; } SerialConnection::Builder& SerialConnection::Builder::twoStopBits() { m_eStopBits = B2; return *this; } SerialConnection::Builder& SerialConnection::Builder::baudRate(uint32_t iBaud) { m_iBaudRate = iBaud; return *this; } SerialConnection::Builder& SerialConnection::Builder::useLockFile(bool bEnabled) { m_bLockFile = bEnabled; return *this; } SerialConnection::Builder& SerialConnection::Builder::build() { m_bBuilt = true; return *this; } const std::string& SerialConnection::Builder::getPortName() const { return m_sPortName; } const uint32_t& SerialConnection::Builder::getBaudRate() const { return m_iBaudRate; } SerialConnection::ETYPE SerialConnection::Builder::getType() const { return m_eType; } SerialConnection::EPARITY SerialConnection::Builder::getParity() const { return m_eParity; } SerialConnection::EDATABITS SerialConnection::Builder::getDataBits() const { return m_eDataBits; } SerialConnection::ESTOPBITS SerialConnection::Builder::getStopBits() const { return m_eStopBits; } bool SerialConnection::Builder::isUsingLockFile() const { return m_bLockFile; } std::string SerialConnection::Builder::getTypeAsString() const { std::string sType; switch(m_eType) { case RS232: sType = "RS232"; break; case RS422: sType = "RS422"; break; case RS485: sType = "RS485"; break; default: break; } return sType; } SerialConnection::Builder& SerialConnection::Builder::flowControl(const SerialConnection::FLOW_CONTROL& eFlowControl) { m_eFlowControl = eFlowControl; return *this; } SerialConnection::FLOW_CONTROL SerialConnection::Builder::getFlowControl() const { return m_eFlowControl; } bool SerialConnection::Builder::isBuilt() const { return m_bBuilt; } SerialConnection::SerialConnection(const Builder& oBuilder) : Connection(oBuilder.getTypeAsString()) , m_sPortName(oBuilder.getPortName()) , m_iBaudRate(oBuilder.getBaudRate()) , m_eType(oBuilder.getType()) , m_eParity(oBuilder.getParity()) , m_eDataBits(oBuilder.getDataBits()) , m_eStopBits(oBuilder.getStopBits()) , m_eFlowControl(oBuilder.getFlowControl()) { if(!oBuilder.isBuilt()) { //Throw Exception? } #ifdef WIN32 m_iHandle = INVALID_HANDLE_VALUE; #else m_iHandle = -1; #endif m_apHandleLock.reset(new Lock()); if(oBuilder.isUsingLockFile()) { std::vector vParts = MTS::Text::split(m_sPortName, "/"); m_apLockFile.reset(new LockFile("/var/lock/LCK.." + vParts[vParts.size()-1])); } } SerialConnection::~SerialConnection() { doClose(); m_apHandleLock.reset(); } const std::string& SerialConnection::getPortName() const { return m_sPortName; } uint32_t SerialConnection::getBaudRate() const { return m_iBaudRate; } int SerialConnection::getFileDescriptor() { m_apHandleLock->lock(); int h = m_iHandle; m_apHandleLock->unlock(); return h; } const char* SerialConnection::humanSpeed(speed_t speed) { const char *hspeed; switch (speed) { case B0: hspeed = "B0"; break; case B50: hspeed = "B50"; break; case B75: hspeed = "B75"; break; case B110: hspeed = "B110"; break; case B134: hspeed = "B134"; break; case B150: hspeed = "B150"; break; case B200: hspeed = "B200"; break; case B300: hspeed = "B300"; break; case B600: hspeed = "B600"; break; case B1200: hspeed = "B1200"; break; case B1800: hspeed = "B1800"; break; case B2400: hspeed = "B2400"; break; case B4800: hspeed = "B4800"; break; case B9600: hspeed = "B9600"; break; case B19200: hspeed = "B19200"; break; case B57600: hspeed = "B57600"; break; case B115200: hspeed = "B115200"; break; default: hspeed = "unknown"; } return hspeed; } void SerialConnection::printPortSetting(const termios *options){ printDebug("SERIAL| port settings:"); printDebug("SERIAL| in speed:%s out speed:%s", humanSpeed(cfgetispeed(options)), humanSpeed(cfgetospeed(options))); int data_length=0; if ((options->c_cflag&CSIZE) == CS5) { data_length = 5; } if ((options->c_cflag&CSIZE) == CS6) { data_length = 6; } if ((options->c_cflag&CSIZE) == CS7) { data_length = 7; } if ((options->c_cflag&CSIZE) == CS8) { data_length = 8; } printDebug("SERIAL| data:%d stop bits:%d", data_length,options->c_cflag&CSTOPB?2:1); if (!(options->c_cflag&PARENB)) { printDebug("SERIAL| parity: NONE"); } else if (options->c_cflag&PARODD) { printDebug("SERIAL| parity: ODD"); } else { printDebug("SERIAL| parity: EVEN"); } printDebug("SERIAL| CRTSCTS:%d", options->c_cflag&CRTSCTS?1:0); } bool SerialConnection::doOpen(const int32_t& timeoutMillis) { m_apHandleLock->lock(); if (m_iHandle != -1) { m_apHandleLock->unlock(); return true; } if(!m_apLockFile.isNull() && !m_apLockFile->lock(timeoutMillis)) { printError("SERIAL| Failed to obtain file lock"); m_apHandleLock->unlock(); return false; } #ifdef WIN32 #else m_iHandle = ::open(m_sPortName.c_str(), O_RDWR | O_NOCTTY ); if (m_iHandle == -1) { printWarning("SERIAL| Failed to open port [%s] [%d]%s", m_sPortName.c_str(), errno, errno == 13 ? " (Permission Denied)" : ""); if(!m_apLockFile.isNull()) { m_apLockFile->unlock(); } m_apHandleLock->unlock(); return false; } int result = tcgetattr(m_iHandle, &m_stPrevOptions); if (result == -1) { printWarning("SERIAL| Failed to get attributes on port [%s] [%d]%s", m_sPortName.c_str(), errno, errno == 13 ? " (Permission Denied)" : ""); } termios options = m_stPrevOptions; //Set to Raw Mode cfmakeraw(&options); //Set the Baud Rate speed_t speed = B9600; switch (m_iBaudRate) { case 300: speed = B300; break; case 600: speed = B600; break; case 1200: speed = B1200; break; case 2400: speed = B2400; break; case 4800: speed = B4800; break; case 9600: speed = B9600; break; case 19200: speed = B19200; break; case 38400: speed = B38400; break; case 57600: speed = B57600; break; case 115200: speed = B115200; break; case 230400: speed = B230400; break; case 460800: speed = B460800; break; case 921600: speed = B921600; break; default: printWarning("SERIAL| Baud Rate [%d] not supported. Defaulting to 9600.", m_iBaudRate); m_iBaudRate = 9600; speed = B9600; break; } result = cfsetispeed(&options, speed); if (result == -1) { printWarning("SERIAL| Failed to set input speed of [%d] on port [%s] [%d]", m_iBaudRate, m_sPortName.c_str(), errno); goto CLEANUP; } result = cfsetospeed(&options, speed); if (result == -1) { printWarning("SERIAL| Failed to set output speed of [%d] on port [%s] [%d]", m_iBaudRate, m_sPortName.c_str(), errno); goto CLEANUP; } //Set Parity, Stop Bits, and Data Length options.c_cflag &= ~(PARODD | PARENB); switch(m_eParity) { case ODD: options.c_cflag |= ( PARODD | PARENB ); break; case EVEN: options.c_cflag |= PARENB; break; case OFF: default: break; } options.c_cflag &= ~CSIZE; switch(m_eDataBits) { case B5: options.c_cflag |= CS5; break; case B6: options.c_cflag |= CS6; break; case B7: options.c_cflag |= CS7; break; case B8: default: options.c_cflag |= CS8; break; } switch(m_eStopBits) { case B2: options.c_cflag |= CSTOPB; break; case B1: default: options.c_cflag &= ~CSTOPB; break; } switch (m_eFlowControl) { case RTS_CTS: options.c_cflag |= CRTSCTS; break; case NONE: default: options.c_cflag &= ~CRTSCTS; break; } //Set Control Modes options.c_cc[VMIN] = 0; options.c_cc[VTIME] = 2; // tenths of seconds printPortSetting(&options); result = tcsetattr(m_iHandle, TCSANOW, &options); if (result == -1) { printWarning("SERIAL| Failed to set configurations on port [%s] [%d]%s", m_sPortName.c_str(), errno, errno == 13 ? " (Permission Denied)" : ""); goto CLEANUP; } result = tcflush(m_iHandle, TCIOFLUSH); if (result == -1) { printWarning("SERIAL| Failed to flush port [%s] [%d]", m_sPortName.c_str(), errno); goto CLEANUP; } #endif m_apHandleLock->unlock(); printInfo("SERIAL| %s connection opened on %s @ %d", getName().c_str(), m_sPortName.c_str(), m_iBaudRate); return true; CLEANUP: cleanup(); m_apHandleLock->unlock(); return false; } void SerialConnection::doClose() { m_apHandleLock->lock(); cleanup(); m_apHandleLock->unlock(); } int SerialConnection::doRead(char* pBuffer, const uint32_t& iSize, int32_t& timeoutMillis) { int32_t result = -1; #ifdef WIN32 #else int h = getFileDescriptor(); if (h == -1) { printError("SERIAL| bad handle on port %s", m_sPortName.c_str()); return result; } if(timeoutMillis < 0) { result = ::read(h, pBuffer, iSize); } else { fd_set readfs; struct timeval stTimeout; stTimeout.tv_usec = ((timeoutMillis%1000) * 1000); stTimeout.tv_sec = timeoutMillis/1000; FD_ZERO(&readfs); FD_SET(h, &readfs); int res = select(h + 1, &readfs, NULL, NULL, &stTimeout); if(res == -1) { result = -1; } else if (res == 0) { //Timeout Occured //printTrace("SERIAL| read timed out on port [%s]", m_sPortName.c_str()); result = 0; } else { if (FD_ISSET(h, &readfs)) { result = ::read(h, pBuffer, iSize); if (result == 0) { printTrace("SERIAL| serial port [%s] disconnected", m_sPortName.c_str()); result = -1; } } else { //socket closed? result = -1; } } timeoutMillis = (stTimeout.tv_usec / 1000) + (stTimeout.tv_sec * 1000); } #endif if (result < 0) { printWarning("SERIAL| failed to read from %s [%d]", m_sPortName.c_str(), errno); } return result; } int SerialConnection::doWrite(const char* pBuffer, const uint32_t& iSize, int32_t& timeoutMillis) { int32_t result = -1; #ifdef WIN32 #else int h = getFileDescriptor(); if (h == -1) { printError("SERIAL| bad handle on port %s", m_sPortName.c_str()); return result; } if(timeoutMillis < 0) { result = ::write(h, pBuffer, iSize); } else { //No difference between timeout and no timeout result = ::write(h, pBuffer, iSize); } #endif if (result < 0) { printWarning("SERIAL| failed to write to %s [%d]", m_sPortName.c_str(), errno); } return result; } void SerialConnection::cleanup() { #ifdef WIN32 CloseHandle(m_iHandle); m_iHandle = INVALID_HANDLE_VALUE; #else if(m_iHandle >= 0) { int result = tcsetattr(m_iHandle, TCSANOW, &m_stPrevOptions); if (result == -1) { printWarning("SERIAL| Failed to revert configurations on port [%s] [%d]", m_sPortName.c_str(), errno); } ::close(m_iHandle); m_iHandle = -1; } #endif if(!m_apLockFile.isNull()) { m_apLockFile->unlock(); } } void SerialConnection::drain() { int result = tcdrain(m_iHandle); if (result == -1) { printWarning("SERIAL| Failed to drain port [%s] [%d]", m_sPortName.c_str(), errno); } } void SerialConnection::flush() { int result = tcflush(m_iHandle, TCIOFLUSH); if (result == -1) { printWarning("SERIAL| Failed to flush port [%s] [%d]", m_sPortName.c_str(), errno); } }