diff options
Diffstat (limited to 'kopete/protocols/sms/services/kopete_unix_serial.cpp')
-rw-r--r-- | kopete/protocols/sms/services/kopete_unix_serial.cpp | 445 |
1 files changed, 445 insertions, 0 deletions
diff --git a/kopete/protocols/sms/services/kopete_unix_serial.cpp b/kopete/protocols/sms/services/kopete_unix_serial.cpp new file mode 100644 index 00000000..b694ab22 --- /dev/null +++ b/kopete/protocols/sms/services/kopete_unix_serial.cpp @@ -0,0 +1,445 @@ +// ************************************************************************* +// * Taken from the GSM TA/ME library +// * +// * File: gsm_unix_port.cc +// * +// * Purpose: UNIX serial port implementation with extras +// * +// * Original Author: Peter Hofmann (software@pxh.de) +// * Modified by: Justin Huff (jjhuff@mspin.net) +// * +// * Created: 10.5.1999 +// ************************************************************************* +#include "config.h" +#ifdef INCLUDE_SMSGSM + +#include <gsmlib/gsm_util.h> +#include <termios.h> +#include <fcntl.h> +#include <iostream> +#include <sstream> +#include <errno.h> +#include <stdio.h> +#include <unistd.h> +#include <sys/ioctl.h> +#include <signal.h> +#include <pthread.h> +#include <cassert> +#include <assert.h> + +#include <qsocketnotifier.h> + +#include "kopete_unix_serial.h" + +using namespace std; +using namespace gsmlib; + +static const int holdoff[] = {2000000, 1000000, 400000}; +static const int holdoffArraySize = sizeof(holdoff)/sizeof(int); + +// alarm handling for socket read/write +// the timerMtx is necessary since several threads cannot use the +// timer indepently of each other + +static pthread_mutex_t timerMtx = PTHREAD_MUTEX_INITIALIZER; +#define pthread_mutex_lock(x) +#define pthread_mutex_unlock(x) + +// for non-GNU systems, define alarm() +#ifndef HAVE_ALARM +unsigned int alarm(unsigned int seconds) +{ + struct itimerval old, newt; + newt.it_interval.tv_usec = 0; + newt.it_interval.tv_sec = 0; + newt.it_value.tv_usec = 0; + newt.it_value.tv_sec = (long int)seconds; + if (setitimer(ITIMER_REAL, &newt, &old) < 0) + return 0; + else + return old.it_value.tv_sec; +} +#endif + +// this routine is called in case of a timeout +static void catchAlarm(int) +{ + // do nothing +} + +// start timer +static void startTimer() +{ + pthread_mutex_lock(&timerMtx); + struct sigaction newAction; + newAction.sa_handler = catchAlarm; + newAction.sa_flags = 0; + sigaction(SIGALRM, &newAction, NULL); + alarm(1); +} + +// reset timer +static void stopTimer() +{ + alarm(0); + sigaction(SIGALRM, NULL, NULL); + pthread_mutex_unlock(&timerMtx); +} + +// KopeteUnixSerialPort members + +void KopeteUnixSerialPort::throwModemException(string message) throw(GsmException) +{ + ostringstream os; + os << message << " (errno: " << errno << "/" << strerror(errno) << ")"; + throw GsmException(os.str(), OSError, errno); +} + +void KopeteUnixSerialPort::putBack(unsigned char c) +{ + assert(_oldChar == -1); + _oldChar = c; +} + +int KopeteUnixSerialPort::readByte() throw(GsmException) +{ + if (_oldChar != -1) + { + int result = _oldChar; + _oldChar = -1; + return result; + } + + unsigned char c; + int timeElapsed = 0; + struct timeval oneSecond; + bool readDone = false; + + while (! readDone && timeElapsed < _timeoutVal) + { + if (interrupted()) + throwModemException("interrupted when reading from TA"); + + // setup fd_set data structure for select() + fd_set fdSet; + oneSecond.tv_sec = 1; + oneSecond.tv_usec = 0; + FD_ZERO(&fdSet); + FD_SET(_fd, &fdSet); + + switch (select(FD_SETSIZE, &fdSet, NULL, NULL, &oneSecond)) + { + case 1: + { + int res = read(_fd, &c, 1); + if (res != 1) + throwModemException("end of file when reading from TA"); + else + readDone = true; + break; + } + case 0: + ++timeElapsed; + break; + default: + if (errno != EINTR) + throwModemException("reading from TA"); + break; + } + } + if (! readDone) + throwModemException("timeout when reading from TA"); + +#ifndef NDEBUG + if (debugLevel() >= 2) + { + // some useful debugging code + if (c == LF) + cerr << "<LF>"; + else if (c == CR) + cerr << "<CR>"; + else cerr << "<'" << (char) c << "'>"; + cerr.flush(); + } +#endif + return c; +} + +KopeteUnixSerialPort::KopeteUnixSerialPort(string device, speed_t lineSpeed, + string initString, bool swHandshake) + throw(GsmException) : + _oldChar(-1), _timeoutVal(TIMEOUT_SECS) +{ + _readNotifier = NULL; + + struct termios t; + + // open device + _fd = open(device.c_str(), O_RDWR | O_NOCTTY | O_NONBLOCK); + if (_fd == -1) + throwModemException("opening device"); + + // switch off non-blocking mode + int fdFlags; + if ((fdFlags = fcntl(_fd, F_GETFL)) == -1) + { + close(_fd); + throwModemException("getting file status flags failed"); + } + fdFlags &= ~O_NONBLOCK; + if (fcntl(_fd, F_SETFL, fdFlags) == -1) + { + close(_fd); + throwModemException("switching of non-blocking mode failed"); + } + + // Set the close on exec flag + if ((fdFlags = fcntl(_fd, F_GETFD)) == -1) + { + close(_fd); + throwModemException("getting file status flags failed"); + } + fdFlags |= FD_CLOEXEC; + if (fcntl(_fd, F_SETFD, fdFlags) == -1) + { + close(_fd); + throwModemException("switching of non-blocking mode failed"); + } + + long int saveTimeoutVal = _timeoutVal; + _timeoutVal = 3; + int initTries = holdoffArraySize; + while (initTries-- > 0) + { + // flush all pending output + tcflush(_fd, TCOFLUSH); + + // toggle DTR to reset modem + int mctl = TIOCM_DTR; + if (ioctl(_fd, TIOCMBIC, &mctl) < 0 && errno != ENOTTY) + { + close(_fd); + throwModemException("clearing DTR failed"); + } + // the waiting time for DTR toggling is increased with each loop + usleep(holdoff[initTries]); + if (ioctl(_fd, TIOCMBIS, &mctl) < 0 && errno != ENOTTY) + { + close(_fd); + throwModemException("setting DTR failed"); + } + // get line modes + if (tcgetattr(_fd, &t) < 0) + { + close(_fd); + throwModemException("tcgetattr device"); + } + + // set line speed + cfsetispeed(&t, lineSpeed); + cfsetospeed(&t, lineSpeed); + + // set the device to a sane state + t.c_iflag |= IGNPAR | (swHandshake ? IXON | IXOFF : 0); + t.c_iflag &= ~(INPCK | ISTRIP | IMAXBEL | + (swHandshake ? 0 : IXON | IXOFF) + | IXANY | IGNCR | ICRNL | IMAXBEL | INLCR | IGNBRK); + t.c_oflag &= ~(OPOST); + // be careful, only touch "known" flags + t.c_cflag &= ~(CSIZE | CSTOPB | PARENB | PARODD | + (swHandshake ? CRTSCTS : 0 )); + t.c_cflag |= CS8 | CREAD | HUPCL | (swHandshake ? 0 : CRTSCTS) | CLOCAL; + t.c_lflag &= ~(ECHO | ECHOE | ECHOPRT | ECHOK | ECHOKE | ECHONL | + ECHOCTL | ISIG | IEXTEN | TOSTOP | FLUSHO | ICANON); + t.c_lflag |= NOFLSH; + t.c_cc[VMIN] = 1; + t.c_cc[VTIME] = 0; + + t.c_cc[VSUSP] = 0; + + // write back + if(tcsetattr (_fd, TCSANOW, &t) < 0) + { + close(_fd); + throwModemException("tcsetattr device"); + } + // the waiting time for writing to the ME/TA is increased with each loop + usleep(holdoff[initTries]); + + // flush all pending input + tcflush(_fd, TCIFLUSH); + + try + { + // reset modem + putLine("ATZ"); + bool foundOK = false; + int readTries = 5; + while (readTries-- > 0) + { + // for the first call getLine() waits only 3 seconds + // because of _timeoutVal = 3 + string s = getLine(); + if (s.find("OK") != string::npos || + s.find("CABLE: GSM") != string::npos) + { + foundOK = true; + readTries = 0; // found OK, exit loop + } + else if (s.find("ERROR") != string::npos) + readTries = 0; // error, exit loop + } + + // set getLine/putLine timeout back to old value + _timeoutVal = saveTimeoutVal; + + if (foundOK) + { + // init modem + readTries = 5; + putLine("AT" + initString); + while (readTries-- > 0) + { + string s = getLine(); + if (s.find("OK") != string::npos || + s.find("CABLE: GSM") != string::npos) + { + _readNotifier = new QSocketNotifier(_fd, QSocketNotifier::Read); + connect( _readNotifier, SIGNAL(activated(int)), this, SIGNAL(activated())); + return; // found OK, return + } + } + } + } + catch (GsmException &e) + { + _timeoutVal = saveTimeoutVal; + if (initTries == 0) + { + close(_fd); + throw e; + } + } + } + // no response after 3 tries + close(_fd); + throwModemException("reset modem failed"); +} + +string KopeteUnixSerialPort::getLine() throw(GsmException) +{ + string result; + int c; + while ((c = readByte()) >= 0) + { + while (c == CR) + { + c = readByte(); + } + if (c == LF) + break; + result += c; + } + +#ifndef NDEBUG + if (debugLevel() >= 1) + cerr << "<-- " << result << endl; +#endif + + return result; +} + +void KopeteUnixSerialPort::putLine(string line, + bool carriageReturn) throw(GsmException) +{ +#ifndef NDEBUG + if (debugLevel() >= 1) + cerr << "--> " << line << endl; +#endif + + if (carriageReturn) line += CR; + const char *l = line.c_str(); + + int timeElapsed = 0; + struct timeval oneSecond; + + ssize_t bytesWritten = 0; + while (bytesWritten < (ssize_t)line.length() && timeElapsed < _timeoutVal) + { + if (interrupted()) + throwModemException("interrupted when writing to TA"); + + // setup fd_set data structure for select() + fd_set fdSet; + oneSecond.tv_sec = 1; + oneSecond.tv_usec = 0; + FD_ZERO(&fdSet); + FD_SET(_fd, &fdSet); + + switch (select(FD_SETSIZE, NULL, &fdSet, NULL, &oneSecond)) + { + case 1: + { + ssize_t bw = write(_fd, l + bytesWritten, line.length() - bytesWritten); + if (bw < 0) + throwModemException("writing to TA"); + bytesWritten += bw; + break; + } + case 0: + ++timeElapsed; + break; + default: + if (errno != EINTR) + throwModemException("writing to TA"); + break; + } + } + + while (timeElapsed < _timeoutVal) + { + if (interrupted()) + throwModemException("interrupted when writing to TA"); + ::startTimer(); + int res = tcdrain(_fd); // wait for output to be read by TA + ::stopTimer(); + if (res == 0) + break; + else + { + assert(errno == EINTR); + ++timeElapsed; + } + } + if (timeElapsed >= _timeoutVal) + throwModemException("timeout when writing to TA"); + + // echo CR LF must be removed by higher layer functions in gsm_at because + // in order to properly handle unsolicited result codes from the ME/TA +} + +bool KopeteUnixSerialPort::wait(GsmTime timeout) throw(GsmException) +{ + fd_set fds; + FD_ZERO(&fds); + FD_SET(_fd, &fds); + return select(FD_SETSIZE, &fds, NULL, NULL, timeout) != 0; +} + +// set timeout for read or write in seconds. +void KopeteUnixSerialPort::setTimeOut(unsigned int timeout) +{ + _timeoutVal = timeout; +} + +KopeteUnixSerialPort::~KopeteUnixSerialPort() +{ + delete _readNotifier; + _readNotifier = NULL; + if (_fd != -1) + close(_fd); +} + +#include "kopete_unix_serial.moc" + +#endif |