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