You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdenetwork/kopete/protocols/sms/services/kopete_unix_serial.cpp

446 lines
9.7 KiB

// *************************************************************************
// * Taken from the GSM TA/ME library
// *
// * File: kopete_unix_serial.cpp
// *
// * 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 <tqsocketnotifier.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 TQSocketNotifier(_fd, TQSocketNotifier::Read);
connect( _readNotifier, TQT_SIGNAL(activated(int)), this, TQT_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