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.
960 lines
23 KiB
960 lines
23 KiB
/* //device/system/reference-ril/atchannel.c
|
|
**
|
|
** Copyright 2006, The Android Open Source Project
|
|
**
|
|
** Licensed under the Apache License, Version 2.0 (the "License");
|
|
** you may not use this file except in compliance with the License.
|
|
** You may obtain a copy of the License at
|
|
**
|
|
** http://www.apache.org/licenses/LICENSE-2.0
|
|
**
|
|
** Unless required by applicable law or agreed to in writing, software
|
|
** distributed under the License is distributed on an "AS IS" BASIS,
|
|
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
** See the License for the specific language governing permissions and
|
|
** limitations under the License.
|
|
*/
|
|
|
|
#include "atchannel.h"
|
|
#include "at_tok.h"
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <pthread.h>
|
|
#include <ctype.h>
|
|
#include <stdlib.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <sys/time.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
#define LOG_NDEBUG 0
|
|
#define LOG_TAG "AT"
|
|
#include <utils/Log.h>
|
|
|
|
#include "misc.h"
|
|
|
|
|
|
#define NUM_ELEMS(x) (sizeof(x)/sizeof((x)[0]))
|
|
|
|
#define MAX_AT_RESPONSE (8 * 1024)
|
|
#define HANDSHAKE_RETRY_COUNT 8
|
|
#define HANDSHAKE_TIMEOUT_MSEC 250
|
|
|
|
static pthread_t s_tid_reader;
|
|
static int s_fd = -1; /* fd of the AT channel */
|
|
static ATUnsolHandler s_unsolHandler;
|
|
|
|
/* for input buffering */
|
|
|
|
static char s_ATBuffer[MAX_AT_RESPONSE+1];
|
|
static char *s_ATBufferCur = s_ATBuffer;
|
|
|
|
#if AT_DEBUG
|
|
void AT_DUMP(const char* prefix, const char* buff, int len)
|
|
{
|
|
if (len < 0)
|
|
len = strlen(buff);
|
|
RLOGD("%.*s", len, buff);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* There is one reader thread |s_tid_reader| and potentially multiple writer
|
|
* threads. |s_commandmutex| and |s_commandcond| are used to maintain the
|
|
* condition that the writer thread will not read from |sp_response| until the
|
|
* reader thread has signaled itself is finished, etc. |s_writeMutex| is used to
|
|
* prevent multiple writer threads from calling at_send_command_full_nolock
|
|
* function at the same time.
|
|
*/
|
|
|
|
static pthread_mutex_t s_commandmutex = PTHREAD_MUTEX_INITIALIZER;
|
|
static pthread_cond_t s_commandcond = PTHREAD_COND_INITIALIZER;
|
|
static pthread_mutex_t s_writeMutex = PTHREAD_MUTEX_INITIALIZER;
|
|
|
|
static ATCommandType s_type;
|
|
static const char *s_responsePrefix = NULL;
|
|
static const char *s_smsPDU = NULL;
|
|
static ATResponse *sp_response = NULL;
|
|
|
|
static void (*s_onTimeout)(void) = NULL;
|
|
static void (*s_onReaderClosed)(void) = NULL;
|
|
static int s_readerClosed;
|
|
|
|
static void onReaderClosed();
|
|
static int writeCtrlZ (const char *s);
|
|
static int writeline (const char *s);
|
|
|
|
#define NS_PER_S 1000000000
|
|
static void setTimespecRelative(struct timespec *p_ts, long long msec)
|
|
{
|
|
struct timeval tv;
|
|
|
|
gettimeofday(&tv, (struct timezone *) NULL);
|
|
|
|
p_ts->tv_sec = tv.tv_sec + (msec / 1000);
|
|
p_ts->tv_nsec = (tv.tv_usec + (msec % 1000) * 1000L ) * 1000L;
|
|
/* assuming tv.tv_usec < 10^6 */
|
|
if (p_ts->tv_nsec >= NS_PER_S) {
|
|
p_ts->tv_sec++;
|
|
p_ts->tv_nsec -= NS_PER_S;
|
|
}
|
|
}
|
|
|
|
static void sleepMsec(long long msec)
|
|
{
|
|
struct timespec ts;
|
|
int err;
|
|
|
|
ts.tv_sec = (msec / 1000);
|
|
ts.tv_nsec = (msec % 1000) * 1000 * 1000;
|
|
|
|
do {
|
|
err = nanosleep (&ts, &ts);
|
|
} while (err < 0 && errno == EINTR);
|
|
}
|
|
|
|
|
|
|
|
/** add an intermediate response to sp_response*/
|
|
static void addIntermediate(const char *line)
|
|
{
|
|
ATLine *p_new;
|
|
|
|
p_new = (ATLine *) malloc(sizeof(ATLine));
|
|
|
|
p_new->line = strdup(line);
|
|
|
|
/* note: this adds to the head of the list, so the list
|
|
will be in reverse order of lines received. the order is flipped
|
|
again before passing on to the command issuer */
|
|
p_new->p_next = sp_response->p_intermediates;
|
|
sp_response->p_intermediates = p_new;
|
|
}
|
|
|
|
|
|
/**
|
|
* returns 1 if line is a final response indicating error
|
|
* See 27.007 annex B
|
|
* WARNING: NO CARRIER and others are sometimes unsolicited
|
|
*/
|
|
static const char * s_finalResponsesError[] = {
|
|
"ERROR",
|
|
"+CMS ERROR:",
|
|
"+CME ERROR:",
|
|
"NO CARRIER", /* sometimes! */
|
|
"NO ANSWER",
|
|
"NO DIALTONE",
|
|
};
|
|
static int isFinalResponseError(const char *line)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0 ; i < NUM_ELEMS(s_finalResponsesError) ; i++) {
|
|
if (strStartsWith(line, s_finalResponsesError[i])) {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* returns 1 if line is a final response indicating success
|
|
* See 27.007 annex B
|
|
* WARNING: NO CARRIER and others are sometimes unsolicited
|
|
*/
|
|
static const char * s_finalResponsesSuccess[] = {
|
|
"OK",
|
|
"CONNECT" /* some stacks start up data on another channel */
|
|
};
|
|
static int isFinalResponseSuccess(const char *line)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0 ; i < NUM_ELEMS(s_finalResponsesSuccess) ; i++) {
|
|
if (strStartsWith(line, s_finalResponsesSuccess[i])) {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* returns 1 if line is a final response, either error or success
|
|
* See 27.007 annex B
|
|
* WARNING: NO CARRIER and others are sometimes unsolicited
|
|
*/
|
|
static int isFinalResponse(const char *line)
|
|
{
|
|
return isFinalResponseSuccess(line) || isFinalResponseError(line);
|
|
}
|
|
|
|
|
|
/**
|
|
* returns 1 if line is the first line in (what will be) a two-line
|
|
* SMS unsolicited response
|
|
*/
|
|
static const char * s_smsUnsoliciteds[] = {
|
|
"+CMT:",
|
|
"+CDS:",
|
|
"+CBM:"
|
|
};
|
|
static int isSMSUnsolicited(const char *line)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0 ; i < NUM_ELEMS(s_smsUnsoliciteds) ; i++) {
|
|
if (strStartsWith(line, s_smsUnsoliciteds[i])) {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/** assumes s_commandmutex is held */
|
|
static void handleFinalResponse(const char *line)
|
|
{
|
|
sp_response->finalResponse = strdup(line);
|
|
|
|
pthread_cond_signal(&s_commandcond);
|
|
}
|
|
|
|
static void handleUnsolicited(const char *line)
|
|
{
|
|
if (s_unsolHandler != NULL) {
|
|
s_unsolHandler(line, NULL);
|
|
}
|
|
}
|
|
|
|
static void processLine(const char *line)
|
|
{
|
|
pthread_mutex_lock(&s_commandmutex);
|
|
|
|
if (sp_response == NULL) {
|
|
/* no command pending */
|
|
handleUnsolicited(line);
|
|
} else if (isFinalResponseSuccess(line)) {
|
|
sp_response->success = 1;
|
|
handleFinalResponse(line);
|
|
} else if (isFinalResponseError(line)) {
|
|
sp_response->success = 0;
|
|
handleFinalResponse(line);
|
|
} else if (s_smsPDU != NULL && 0 == strcmp(line, "> ")) {
|
|
// See eg. TS 27.005 4.3
|
|
// Commands like AT+CMGS have a "> " prompt
|
|
writeCtrlZ(s_smsPDU);
|
|
s_smsPDU = NULL;
|
|
} else switch (s_type) {
|
|
case NO_RESULT:
|
|
handleUnsolicited(line);
|
|
break;
|
|
case NUMERIC:
|
|
if (sp_response->p_intermediates == NULL
|
|
&& isdigit(line[0])
|
|
) {
|
|
addIntermediate(line);
|
|
} else {
|
|
/* either we already have an intermediate response or
|
|
the line doesn't begin with a digit */
|
|
handleUnsolicited(line);
|
|
}
|
|
break;
|
|
case SINGLELINE:
|
|
if (sp_response->p_intermediates == NULL
|
|
&& strStartsWith (line, s_responsePrefix)
|
|
) {
|
|
addIntermediate(line);
|
|
} else {
|
|
/* we already have an intermediate response */
|
|
handleUnsolicited(line);
|
|
}
|
|
break;
|
|
case MULTILINE:
|
|
if (strStartsWith (line, s_responsePrefix)) {
|
|
addIntermediate(line);
|
|
} else {
|
|
handleUnsolicited(line);
|
|
}
|
|
break;
|
|
|
|
default: /* this should never be reached */
|
|
RLOGE("Unsupported AT command type %d\n", s_type);
|
|
handleUnsolicited(line);
|
|
break;
|
|
}
|
|
|
|
pthread_mutex_unlock(&s_commandmutex);
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns a pointer to the end of the next line
|
|
* special-cases the "> " SMS prompt
|
|
*
|
|
* returns NULL if there is no complete line
|
|
*/
|
|
static char * findNextEOL(char *cur)
|
|
{
|
|
if (cur[0] == '>' && cur[1] == ' ' && cur[2] == '\0') {
|
|
/* SMS prompt character...not \r terminated */
|
|
return cur+2;
|
|
}
|
|
|
|
// Find next newline
|
|
while (*cur != '\0' && *cur != '\r' && *cur != '\n') cur++;
|
|
|
|
return *cur == '\0' ? NULL : cur;
|
|
}
|
|
|
|
|
|
/**
|
|
* Reads a line from the AT channel, returns NULL on timeout.
|
|
* Assumes it has exclusive read access to the FD
|
|
*
|
|
* This line is valid only until the next call to readline
|
|
*
|
|
* This function exists because as of writing, android libc does not
|
|
* have buffered stdio.
|
|
*/
|
|
|
|
static const char *readline()
|
|
{
|
|
ssize_t count;
|
|
|
|
char *p_read = NULL;
|
|
char *p_eol = NULL;
|
|
char *ret;
|
|
|
|
/* this is a little odd. I use *s_ATBufferCur == 0 to
|
|
* mean "buffer consumed completely". If it points to a character, than
|
|
* the buffer continues until a \0
|
|
*/
|
|
if (*s_ATBufferCur == '\0') {
|
|
/* empty buffer */
|
|
s_ATBufferCur = s_ATBuffer;
|
|
*s_ATBufferCur = '\0';
|
|
p_read = s_ATBuffer;
|
|
} else { /* *s_ATBufferCur != '\0' */
|
|
/* there's data in the buffer from the last read */
|
|
|
|
// skip over leading newlines
|
|
while (*s_ATBufferCur == '\r' || *s_ATBufferCur == '\n')
|
|
s_ATBufferCur++;
|
|
|
|
p_eol = findNextEOL(s_ATBufferCur);
|
|
|
|
if (p_eol == NULL) {
|
|
/* a partial line. move it up and prepare to read more */
|
|
size_t len;
|
|
|
|
len = strlen(s_ATBufferCur);
|
|
|
|
memmove(s_ATBuffer, s_ATBufferCur, len + 1);
|
|
p_read = s_ATBuffer + len;
|
|
s_ATBufferCur = s_ATBuffer;
|
|
}
|
|
/* Otherwise, (p_eol !- NULL) there is a complete line */
|
|
/* that will be returned the while () loop below */
|
|
}
|
|
|
|
while (p_eol == NULL) {
|
|
if (0 == MAX_AT_RESPONSE - (p_read - s_ATBuffer)) {
|
|
RLOGE("ERROR: Input line exceeded buffer\n");
|
|
/* ditch buffer and start over again */
|
|
s_ATBufferCur = s_ATBuffer;
|
|
*s_ATBufferCur = '\0';
|
|
p_read = s_ATBuffer;
|
|
}
|
|
|
|
do {
|
|
count = read(s_fd, p_read,
|
|
MAX_AT_RESPONSE - (p_read - s_ATBuffer));
|
|
} while (count < 0 && errno == EINTR);
|
|
|
|
if (count > 0) {
|
|
AT_DUMP( "<< ", p_read, count );
|
|
|
|
p_read[count] = '\0';
|
|
|
|
// skip over leading newlines
|
|
while (*s_ATBufferCur == '\r' || *s_ATBufferCur == '\n')
|
|
s_ATBufferCur++;
|
|
|
|
p_eol = findNextEOL(s_ATBufferCur);
|
|
p_read += count;
|
|
} else if (count <= 0) {
|
|
/* read error encountered or EOF reached */
|
|
if(count == 0) {
|
|
RLOGD("atchannel: EOF reached");
|
|
} else {
|
|
RLOGD("atchannel: read error %s", strerror(errno));
|
|
}
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/* a full line in the buffer. Place a \0 over the \r and return */
|
|
|
|
ret = s_ATBufferCur;
|
|
*p_eol = '\0';
|
|
s_ATBufferCur = p_eol + 1; /* this will always be <= p_read, */
|
|
/* and there will be a \0 at *p_read */
|
|
|
|
RLOGD("AT< %s\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static void onReaderClosed()
|
|
{
|
|
if (s_onReaderClosed != NULL && s_readerClosed == 0) {
|
|
|
|
pthread_mutex_lock(&s_commandmutex);
|
|
|
|
s_readerClosed = 1;
|
|
|
|
pthread_cond_signal(&s_commandcond);
|
|
|
|
pthread_mutex_unlock(&s_commandmutex);
|
|
|
|
s_onReaderClosed();
|
|
}
|
|
}
|
|
|
|
|
|
static void *readerLoop(void *arg __unused)
|
|
{
|
|
for (;;) {
|
|
const char * line;
|
|
|
|
line = readline();
|
|
|
|
if (line == NULL) {
|
|
break;
|
|
}
|
|
|
|
if(isSMSUnsolicited(line)) {
|
|
char *line1;
|
|
const char *line2;
|
|
|
|
// The scope of string returned by 'readline()' is valid only
|
|
// till next call to 'readline()' hence making a copy of line
|
|
// before calling readline again.
|
|
line1 = strdup(line);
|
|
line2 = readline();
|
|
|
|
if (line2 == NULL) {
|
|
free(line1);
|
|
break;
|
|
}
|
|
|
|
if (s_unsolHandler != NULL) {
|
|
s_unsolHandler (line1, line2);
|
|
}
|
|
free(line1);
|
|
} else {
|
|
processLine(line);
|
|
}
|
|
}
|
|
|
|
onReaderClosed();
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Sends string s to the radio with a \r appended.
|
|
* Returns AT_ERROR_* on error, 0 on success
|
|
*
|
|
* This function exists because as of writing, android libc does not
|
|
* have buffered stdio.
|
|
*/
|
|
static int writeline (const char *s)
|
|
{
|
|
size_t cur = 0;
|
|
size_t len = strlen(s);
|
|
ssize_t written;
|
|
|
|
if (s_fd < 0 || s_readerClosed > 0) {
|
|
return AT_ERROR_CHANNEL_CLOSED;
|
|
}
|
|
|
|
RLOGD("AT> %s\n", s);
|
|
|
|
AT_DUMP( ">> ", s, strlen(s) );
|
|
|
|
/* the main string */
|
|
while (cur < len) {
|
|
do {
|
|
written = write (s_fd, s + cur, len - cur);
|
|
} while (written < 0 && errno == EINTR);
|
|
|
|
if (written < 0) {
|
|
return AT_ERROR_GENERIC;
|
|
}
|
|
|
|
cur += written;
|
|
}
|
|
|
|
/* the \r */
|
|
|
|
do {
|
|
written = write (s_fd, "\r" , 1);
|
|
} while ((written < 0 && errno == EINTR) || (written == 0));
|
|
|
|
if (written < 0) {
|
|
return AT_ERROR_GENERIC;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
static int writeCtrlZ (const char *s)
|
|
{
|
|
size_t cur = 0;
|
|
size_t len = strlen(s);
|
|
ssize_t written;
|
|
|
|
if (s_fd < 0 || s_readerClosed > 0) {
|
|
return AT_ERROR_CHANNEL_CLOSED;
|
|
}
|
|
|
|
RLOGD("AT> %s^Z\n", s);
|
|
|
|
AT_DUMP( ">* ", s, strlen(s) );
|
|
|
|
/* the main string */
|
|
while (cur < len) {
|
|
do {
|
|
written = write (s_fd, s + cur, len - cur);
|
|
} while (written < 0 && errno == EINTR);
|
|
|
|
if (written < 0) {
|
|
return AT_ERROR_GENERIC;
|
|
}
|
|
|
|
cur += written;
|
|
}
|
|
|
|
/* the ^Z */
|
|
|
|
do {
|
|
written = write (s_fd, "\032" , 1);
|
|
} while ((written < 0 && errno == EINTR) || (written == 0));
|
|
|
|
if (written < 0) {
|
|
return AT_ERROR_GENERIC;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void clearPendingCommand()
|
|
{
|
|
if (sp_response != NULL) {
|
|
at_response_free(sp_response);
|
|
}
|
|
|
|
sp_response = NULL;
|
|
s_responsePrefix = NULL;
|
|
s_smsPDU = NULL;
|
|
}
|
|
|
|
|
|
/**
|
|
* Starts AT handler on stream "fd'
|
|
* returns 0 on success, -1 on error
|
|
*/
|
|
int at_open(int fd, ATUnsolHandler h)
|
|
{
|
|
int ret;
|
|
pthread_t tid;
|
|
pthread_attr_t attr;
|
|
|
|
s_fd = fd;
|
|
s_unsolHandler = h;
|
|
s_readerClosed = 0;
|
|
|
|
s_responsePrefix = NULL;
|
|
s_smsPDU = NULL;
|
|
sp_response = NULL;
|
|
|
|
pthread_attr_init (&attr);
|
|
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
|
|
|
|
ret = pthread_create(&s_tid_reader, &attr, readerLoop, &attr);
|
|
|
|
if (ret < 0) {
|
|
perror ("pthread_create");
|
|
return -1;
|
|
}
|
|
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* FIXME is it ok to call this from the reader and the command thread? */
|
|
void at_close()
|
|
{
|
|
if (s_fd >= 0) {
|
|
close(s_fd);
|
|
}
|
|
s_fd = -1;
|
|
|
|
pthread_mutex_lock(&s_commandmutex);
|
|
|
|
s_readerClosed = 1;
|
|
|
|
pthread_cond_signal(&s_commandcond);
|
|
|
|
pthread_mutex_unlock(&s_commandmutex);
|
|
|
|
/* the reader thread should eventually die */
|
|
}
|
|
|
|
static ATResponse * at_response_new()
|
|
{
|
|
return (ATResponse *) calloc(1, sizeof(ATResponse));
|
|
}
|
|
|
|
void at_response_free(ATResponse *p_response)
|
|
{
|
|
ATLine *p_line;
|
|
|
|
if (p_response == NULL) return;
|
|
|
|
p_line = p_response->p_intermediates;
|
|
|
|
while (p_line != NULL) {
|
|
ATLine *p_toFree;
|
|
|
|
p_toFree = p_line;
|
|
p_line = p_line->p_next;
|
|
|
|
free(p_toFree->line);
|
|
free(p_toFree);
|
|
}
|
|
|
|
free (p_response->finalResponse);
|
|
free (p_response);
|
|
}
|
|
|
|
/**
|
|
* The line reader places the intermediate responses in reverse order
|
|
* here we flip them back
|
|
*/
|
|
static void reverseIntermediates(ATResponse *p_response)
|
|
{
|
|
ATLine *pcur,*pnext;
|
|
|
|
pcur = p_response->p_intermediates;
|
|
p_response->p_intermediates = NULL;
|
|
|
|
while (pcur != NULL) {
|
|
pnext = pcur->p_next;
|
|
pcur->p_next = p_response->p_intermediates;
|
|
p_response->p_intermediates = pcur;
|
|
pcur = pnext;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Internal send_command implementation
|
|
* Doesn't lock or call the timeout callback
|
|
*
|
|
* timeoutMsec == 0 means infinite timeout
|
|
*/
|
|
|
|
static int at_send_command_full_nolock (const char *command, ATCommandType type,
|
|
const char *responsePrefix, const char *smspdu,
|
|
long long timeoutMsec, ATResponse **pp_outResponse)
|
|
{
|
|
int err = 0;
|
|
struct timespec ts;
|
|
|
|
if(sp_response != NULL) {
|
|
err = AT_ERROR_COMMAND_PENDING;
|
|
goto error;
|
|
}
|
|
|
|
err = writeline (command);
|
|
|
|
if (err < 0) {
|
|
goto error;
|
|
}
|
|
|
|
s_type = type;
|
|
s_responsePrefix = responsePrefix;
|
|
s_smsPDU = smspdu;
|
|
sp_response = at_response_new();
|
|
|
|
if (timeoutMsec != 0) {
|
|
setTimespecRelative(&ts, timeoutMsec);
|
|
}
|
|
|
|
while (sp_response->finalResponse == NULL && s_readerClosed == 0) {
|
|
if (timeoutMsec != 0) {
|
|
err = pthread_cond_timedwait(&s_commandcond, &s_commandmutex, &ts);
|
|
} else {
|
|
err = pthread_cond_wait(&s_commandcond, &s_commandmutex);
|
|
}
|
|
|
|
if (err == ETIMEDOUT) {
|
|
err = AT_ERROR_TIMEOUT;
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
if (pp_outResponse == NULL) {
|
|
at_response_free(sp_response);
|
|
} else {
|
|
/* line reader stores intermediate responses in reverse order */
|
|
reverseIntermediates(sp_response);
|
|
*pp_outResponse = sp_response;
|
|
}
|
|
|
|
sp_response = NULL;
|
|
|
|
if(s_readerClosed > 0) {
|
|
err = AT_ERROR_CHANNEL_CLOSED;
|
|
goto error;
|
|
}
|
|
|
|
err = 0;
|
|
error:
|
|
clearPendingCommand();
|
|
|
|
return err;
|
|
}
|
|
|
|
/**
|
|
* Internal send_command implementation
|
|
*
|
|
* timeoutMsec == 0 means infinite timeout
|
|
*/
|
|
static int at_send_command_full (const char *command, ATCommandType type,
|
|
const char *responsePrefix, const char *smspdu,
|
|
long long timeoutMsec, ATResponse **pp_outResponse)
|
|
{
|
|
int err;
|
|
|
|
if (0 != pthread_equal(s_tid_reader, pthread_self())) {
|
|
/* cannot be called from reader thread */
|
|
return AT_ERROR_INVALID_THREAD;
|
|
}
|
|
pthread_mutex_lock(&s_commandmutex);
|
|
|
|
err = at_send_command_full_nolock(command, type,
|
|
responsePrefix, smspdu,
|
|
timeoutMsec, pp_outResponse);
|
|
|
|
pthread_mutex_unlock(&s_commandmutex);
|
|
|
|
if (err == AT_ERROR_TIMEOUT && s_onTimeout != NULL) {
|
|
s_onTimeout();
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
/**
|
|
* Issue a single normal AT command with no intermediate response expected
|
|
*
|
|
* "command" should not include \r
|
|
* pp_outResponse can be NULL
|
|
*
|
|
* if non-NULL, the resulting ATResponse * must be eventually freed with
|
|
* at_response_free
|
|
*/
|
|
int at_send_command (const char *command, ATResponse **pp_outResponse)
|
|
{
|
|
int err;
|
|
|
|
err = at_send_command_full (command, NO_RESULT, NULL,
|
|
NULL, 0, pp_outResponse);
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
int at_send_command_singleline (const char *command,
|
|
const char *responsePrefix,
|
|
ATResponse **pp_outResponse)
|
|
{
|
|
int err;
|
|
|
|
err = at_send_command_full (command, SINGLELINE, responsePrefix,
|
|
NULL, 0, pp_outResponse);
|
|
|
|
if (err == 0 && pp_outResponse != NULL
|
|
&& (*pp_outResponse)->success > 0
|
|
&& (*pp_outResponse)->p_intermediates == NULL
|
|
) {
|
|
/* successful command must have an intermediate response */
|
|
at_response_free(*pp_outResponse);
|
|
*pp_outResponse = NULL;
|
|
return AT_ERROR_INVALID_RESPONSE;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
int at_send_command_numeric (const char *command,
|
|
ATResponse **pp_outResponse)
|
|
{
|
|
int err;
|
|
|
|
err = at_send_command_full (command, NUMERIC, NULL,
|
|
NULL, 0, pp_outResponse);
|
|
|
|
if (err == 0 && pp_outResponse != NULL
|
|
&& (*pp_outResponse)->success > 0
|
|
&& (*pp_outResponse)->p_intermediates == NULL
|
|
) {
|
|
/* successful command must have an intermediate response */
|
|
at_response_free(*pp_outResponse);
|
|
*pp_outResponse = NULL;
|
|
return AT_ERROR_INVALID_RESPONSE;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
int at_send_command_sms (const char *command,
|
|
const char *pdu,
|
|
const char *responsePrefix,
|
|
ATResponse **pp_outResponse)
|
|
{
|
|
int err;
|
|
|
|
err = at_send_command_full (command, SINGLELINE, responsePrefix,
|
|
pdu, 0, pp_outResponse);
|
|
|
|
if (err == 0 && pp_outResponse != NULL
|
|
&& (*pp_outResponse)->success > 0
|
|
&& (*pp_outResponse)->p_intermediates == NULL
|
|
) {
|
|
/* successful command must have an intermediate response */
|
|
at_response_free(*pp_outResponse);
|
|
*pp_outResponse = NULL;
|
|
return AT_ERROR_INVALID_RESPONSE;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
int at_send_command_multiline (const char *command,
|
|
const char *responsePrefix,
|
|
ATResponse **pp_outResponse)
|
|
{
|
|
int err;
|
|
|
|
err = at_send_command_full (command, MULTILINE, responsePrefix,
|
|
NULL, 0, pp_outResponse);
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
/** This callback is invoked on the command thread */
|
|
void at_set_on_timeout(void (*onTimeout)(void))
|
|
{
|
|
s_onTimeout = onTimeout;
|
|
}
|
|
|
|
/**
|
|
* This callback is invoked on the reader thread (like ATUnsolHandler)
|
|
* when the input stream closes before you call at_close
|
|
* (not when you call at_close())
|
|
* You should still call at_close()
|
|
*/
|
|
|
|
void at_set_on_reader_closed(void (*onClose)(void))
|
|
{
|
|
s_onReaderClosed = onClose;
|
|
}
|
|
|
|
|
|
/**
|
|
* Periodically issue an AT command and wait for a response.
|
|
* Used to ensure channel has start up and is active
|
|
*/
|
|
|
|
int at_handshake()
|
|
{
|
|
int i;
|
|
int err = 0;
|
|
|
|
if (0 != pthread_equal(s_tid_reader, pthread_self())) {
|
|
/* cannot be called from reader thread */
|
|
return AT_ERROR_INVALID_THREAD;
|
|
}
|
|
pthread_mutex_lock(&s_commandmutex);
|
|
|
|
for (i = 0 ; i < HANDSHAKE_RETRY_COUNT ; i++) {
|
|
/* some stacks start with verbose off */
|
|
err = at_send_command_full_nolock ("ATE0Q0V1", NO_RESULT,
|
|
NULL, NULL, HANDSHAKE_TIMEOUT_MSEC, NULL);
|
|
|
|
if (err == 0) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (err == 0) {
|
|
/* pause for a bit to let the input buffer drain any unmatched OK's
|
|
(they will appear as extraneous unsolicited responses) */
|
|
|
|
sleepMsec(HANDSHAKE_TIMEOUT_MSEC);
|
|
}
|
|
|
|
pthread_mutex_unlock(&s_commandmutex);
|
|
|
|
return err;
|
|
}
|
|
|
|
/**
|
|
* Returns error code from response
|
|
* Assumes AT+CMEE=1 (numeric) mode
|
|
*/
|
|
AT_CME_Error at_get_cme_error(const ATResponse *p_response)
|
|
{
|
|
int ret;
|
|
int err;
|
|
char *p_cur;
|
|
|
|
if (p_response->success > 0) {
|
|
return CME_SUCCESS;
|
|
}
|
|
|
|
if (p_response->finalResponse == NULL
|
|
|| !strStartsWith(p_response->finalResponse, "+CME ERROR:")
|
|
) {
|
|
return CME_ERROR_NON_CME;
|
|
}
|
|
|
|
p_cur = p_response->finalResponse;
|
|
err = at_tok_start(&p_cur);
|
|
|
|
if (err < 0) {
|
|
return CME_ERROR_NON_CME;
|
|
}
|
|
|
|
err = at_tok_nextint(&p_cur, &ret);
|
|
|
|
if (err < 0) {
|
|
return CME_ERROR_NON_CME;
|
|
}
|
|
|
|
return (AT_CME_Error) ret;
|
|
}
|
|
|