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.
975 lines
23 KiB
975 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; |
|
bool inEmulator; |
|
|
|
if (0 != pthread_equal(s_tid_reader, pthread_self())) { |
|
/* cannot be called from reader thread */ |
|
return AT_ERROR_INVALID_THREAD; |
|
} |
|
inEmulator = isInEmulator(); |
|
if (inEmulator) { |
|
pthread_mutex_lock(&s_writeMutex); |
|
} |
|
pthread_mutex_lock(&s_commandmutex); |
|
|
|
err = at_send_command_full_nolock(command, type, |
|
responsePrefix, smspdu, |
|
timeoutMsec, pp_outResponse); |
|
|
|
pthread_mutex_unlock(&s_commandmutex); |
|
if (inEmulator) { |
|
pthread_mutex_unlock(&s_writeMutex); |
|
} |
|
|
|
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; |
|
bool inEmulator; |
|
|
|
if (0 != pthread_equal(s_tid_reader, pthread_self())) { |
|
/* cannot be called from reader thread */ |
|
return AT_ERROR_INVALID_THREAD; |
|
} |
|
inEmulator = isInEmulator(); |
|
if (inEmulator) { |
|
pthread_mutex_lock(&s_writeMutex); |
|
} |
|
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); |
|
if (inEmulator) { |
|
pthread_mutex_unlock(&s_writeMutex); |
|
} |
|
|
|
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; |
|
} |
|
|
|
|