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.
446 lines
9.7 KiB
446 lines
9.7 KiB
// *************************************************************************
|
|
// * 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
|