Library for the Adafruit FONA. This is a port of the original Arduino library available at: https://github.com/adafruit/Adafruit_FONA_Library . - Modified by Marc PLOUHINEC 27/06/2015 for use in mbed - Modified by lionel VOIRIN 05/08/2018 for use Adafruit FONA 3

Dependents:   Adafruit_FONA_3G_Library_Test

Adafruit_FONA.cpp

Committer:
Nels885
Date:
2018-08-06
Revision:
2:3fc229f5ec3f
Parent:
1:c0ae7ecfa511
Child:
3:addc5ef76145

File content as of revision 2:3fc229f5ec3f:

/***************************************************
  This is a library for our Adafruit FONA Cellular Module

  Designed specifically to work with the Adafruit FONA
  ----> http://www.adafruit.com/products/1946
  ----> http://www.adafruit.com/products/1963

  These displays use TTL Serial to communicate, 2 pins are required to
  interface
  Adafruit invests time and resources providing this open source code,
  please support Adafruit and open-source hardware by purchasing
  products from Adafruit!

  Written by Limor Fried/Ladyada for Adafruit Industries.
  BSD license, all text above must be included in any redistribution
 ****************************************************/

/*
 *  Modified by Marc PLOUHINEC 27/06/2015 for use in mbed
 *  Modified by lionel VOIRIN 05/08/2018 for use Adafruit FONA 3G
 */
#include <algorithm>
#include "Adafruit_FONA.h"

#define HIGH 1
#define LOW 0

Adafruit_FONA::Adafruit_FONA(PinName tx, PinName rx, PinName rst) :
    _rstpin(rst, false), mySerial(tx, rx)
{
    apn = "FONAnet";
    apnusername = 0;
    apnpassword = 0;
    httpsredirect = false;
    useragent = "FONA";
    ok_reply = "OK";
    _incomingCall = false;
    eventListener = 0;
    rxBufferInIndex = 0;
    rxBufferOutIndex = 0;
    currentReceivedLineSize = 0;
}

uint8_t Adafruit_FONA::type(void)
{
    return _type;
}

bool Adafruit_FONA::begin(int baudrate)
{
    mySerial.baud(baudrate);
    mySerial.attach(this, &Adafruit_FONA::onSerialDataReceived, Serial::RxIrq);

    _rstpin = HIGH;
    wait_ms(10);
    _rstpin = LOW;
    wait_ms(100);
    _rstpin = HIGH;

#ifdef ADAFRUIT_FONA_DEBUG
    printf("Attempting to open comm with ATs\r\n");
#endif

    // give 3 seconds to reboot
    int16_t timeout = 7000;

    while (timeout > 0) {
        while (readable()) getc();
        if (sendCheckReply("AT", ok_reply))
            break;
        while (readable()) getc();
        if (sendCheckReply("AT", "AT"))
            break;
        wait_ms(500);
        timeout -= 500;
    }

    if (timeout <= 0) {
#ifdef ADAFRUIT_FONA_DEBUG
        printf("Timeout: No response to AT... last ditch attempt.\r\n");
#endif

        sendCheckReply("AT", ok_reply);
        wait_ms(100);
        sendCheckReply("AT", ok_reply);
        wait_ms(100);
        sendCheckReply("AT", ok_reply);
        wait_ms(100);
    }

    // turn off Echo!
    sendCheckReply("ATE0", ok_reply);
    wait_ms(100);

    if (! sendCheckReply("ATE0", ok_reply)) {
        return false;
    }

    // turn on hangupitude
    sendCheckReply("AT+CVHU=0", ok_reply);

    wait_ms(100);
    flushInput();

#ifdef ADAFRUIT_FONA_DEBUG
    printf("\t---> %s\r\n", "ATI");
#endif

    mySerial.printf("%s\r\n", "ATI");
    readline(500, true);

#ifdef ADAFRUIT_FONA_DEBUG
    printf("\t<--- %s\r\n", replybuffer);
#endif

    if (strstr(replybuffer, "SIM808 R14") != 0) {
        _type = FONA808_V2;
    } else if (strstr(replybuffer, "SIM808 R13") != 0) {
        _type = FONA808_V1;
    } else if (strstr(replybuffer, "SIM800 R13") != 0) {
        _type = FONA800L;
    } else if (strstr(replybuffer, "SIMCOM_SIM5320A") != 0) {
        _type = FONA3G_A;
    } else if (strstr(replybuffer, "SIMCOM_SIM5320E") != 0) {
        _type = FONA3G_E;
    }

    if (_type == FONA800L) {
        // determine if L or H

        printf("\t---> %s\r\n", "AT+GMM");

        mySerial.printf("%s\r\n", "AT+GMM");
        readline(500, true);

        printf("\t<--- %s\r\n", replybuffer);

        if (strstr(replybuffer, "SIM800H") != 0)
            _type = FONA800H;
    }

#if defined(FONA_PREF_SMS_STORAGE)
    sendCheckReply("AT+CPMS=" FONA_PREF_SMS_STORAGE "," FONA_PREF_SMS_STORAGE "," FONA_PREF_SMS_STORAGE, ok_reply);
#endif

    return true;
}

void Adafruit_FONA::setEventListener(EventListener *eventListener)
{
    this->eventListener = eventListener;
}

/********* Stream ********************************************/

int Adafruit_FONA::_putc(int value)
{
    return mySerial.putc(value);
}

int Adafruit_FONA::_getc()
{
    __disable_irq(); // Start Critical Section - don't interrupt while changing global buffer variables

    // Wait for data if the buffer is empty
    if (isRxBufferEmpty()) {
        __enable_irq(); // End Critical Section - need to allow rx interrupt to get new characters for buffer

        while(isRxBufferEmpty());

        __disable_irq(); // Start Critical Section - don't interrupt while changing global buffer variables
    }

    int data = rxBuffer[rxBufferOutIndex];
    incrementRxBufferOutIndex();

    __enable_irq(); // End Critical Section

    return data;
}

int Adafruit_FONA::readable()
{
    return !isRxBufferEmpty();
}

void Adafruit_FONA::onSerialDataReceived()
{
    while (mySerial.readable() && !isRxBufferFull()) {
        int data = mySerial.getc();
        rxBuffer[rxBufferInIndex] = data;

        //
        // Analyze the received data in order to detect events like RING or NO CARRIER
        //

        // Copy the data in the current line
        if (currentReceivedLineSize < RX_BUFFER_SIZE && data != '\r' && data != '\n') {
            currentReceivedLine[currentReceivedLineSize] = (char) data;
            currentReceivedLineSize++;
        }

        // Check if the line is complete
        if (data == '\n') {
            currentReceivedLine[currentReceivedLineSize] = 0;

            if (eventListener != NULL) {
                // Check if we have a special event
                if (strcmp(currentReceivedLine, "RING") == 0) {
                    eventListener->onRing();
                } else if (strcmp(currentReceivedLine, "NO CARRIER") == 0) {
                    eventListener->onNoCarrier();
                }
            }

            currentReceivedLineSize = 0;
        }

        incrementRxBufferInIndex();
    }
}

/********* Serial port ********************************************/
bool Adafruit_FONA::setBaudrate(uint16_t baud)
{
    return sendCheckReply("AT+IPREX=", baud, ok_reply);
}

/********* Real Time Clock ********************************************/

bool Adafruit_FONA::enableRTC(uint8_t i)
{
    if (! sendCheckReply("AT+CLTS=", i, ok_reply))
        return false;
    return sendCheckReply("AT&W", ok_reply);
}

/********* BATTERY & ADC ********************************************/

/* returns value in mV (uint16_t) */
bool Adafruit_FONA::getBattVoltage(uint16_t *v)
{
    return sendParseReply("AT+CBC", "+CBC: ", v, ',', 2);
}

/* returns value in mV (uint16_t) */
bool Adafruit_FONA_3G::getBattVoltage(uint16_t *v)
{
    float f;
    bool b = sendParseReply("AT+CBC", "+CBC: ", &f, ',', 2);
    *v = f*1000;
    return b;
}

/* returns the percentage charge of battery as reported by sim800 */
bool Adafruit_FONA::getBattPercent(uint16_t *p)
{
    return sendParseReply("AT+CBC", "+CBC: ", p, ',', 1);
}

bool Adafruit_FONA::getADCVoltage(uint16_t *v)
{
    return sendParseReply("AT+CADC?", "+CADC: 1,", v);
}

/********* SIM ***********************************************************/

uint8_t Adafruit_FONA::unlockSIM(char *pin)
{
    // AT+CPIN=0000
    char sendbuff[14] = "AT+CPIN=";
    sendbuff[8] = pin[0];
    sendbuff[9] = pin[1];
    sendbuff[10] = pin[2];
    sendbuff[11] = pin[3];
    sendbuff[12] = NULL;

    return sendCheckReply(sendbuff, ok_reply);
}

uint8_t Adafruit_FONA::getSIMCCID(char *ccid)
{
    getReply("AT+CCID");
    // up to 28 chars for reply, 20 char total ccid
    if (replybuffer[0] == '+') {
        // fona 3g?
        strncpy(ccid, replybuffer+8, 20);
    } else {
        // fona 800 or 800
        strncpy(ccid, replybuffer, 20);
    }
    ccid[20] = 0;

    readline(); // eat 'OK'

    return strlen(ccid);
}

/********* IMEI **********************************************************/

uint8_t Adafruit_FONA::getIMEI(char *imei)
{
    getReply("AT+GSN");

    // up to 15 chars
    strncpy(imei, replybuffer, 15);
    imei[15] = 0;

    readline(); // eat 'OK'

    return strlen(imei);
}

/********* NETWORK *******************************************************/

uint8_t Adafruit_FONA::getNetworkStatus(void)
{
    uint16_t status;

    if (! sendParseReply("AT+CREG?", "+CREG: ", &status, ',', 1)) return 0;

    return status;
}


uint8_t Adafruit_FONA::getRSSI(void)
{
    uint16_t reply;

    if (! sendParseReply("AT+CSQ", "+CSQ: ", &reply) ) return 0;

    return reply;
}

/********* AUDIO *******************************************************/

bool Adafruit_FONA::setAudio(uint8_t a)
{
    // 0 is headset, 1 is external audio
    if (a > 1) return false;

    return sendCheckReply("AT+CHFA=", a, ok_reply);
}

uint8_t Adafruit_FONA::getVolume(void)
{
    uint16_t reply;

    if (! sendParseReply("AT+CLVL?", "+CLVL: ", &reply) ) return 0;

    return reply;
}

bool Adafruit_FONA::setVolume(uint8_t i)
{
    return sendCheckReply("AT+CLVL=", i, ok_reply);
}


bool Adafruit_FONA::playDTMF(char dtmf)
{
    char str[4];
    str[0] = '\"';
    str[1] = dtmf;
    str[2] = '\"';
    str[3] = 0;
    return sendCheckReply("AT+CLDTMF=3,", str, ok_reply);
}

bool Adafruit_FONA::playToolkitTone(uint8_t t, uint16_t len)
{
    return sendCheckReply("AT+STTONE=1,", t, len, ok_reply);
}

bool Adafruit_FONA::setMicVolume(uint8_t a, uint8_t level)
{
    // 0 is headset, 1 is external audio
    if (a > 1) return false;

    return sendCheckReply("AT+CMIC=", a, level, ok_reply);
}

/********* FM RADIO *******************************************************/


bool Adafruit_FONA::FMradio(bool onoff, uint8_t a)
{
    if (! onoff) {
        return sendCheckReply("AT+FMCLOSE", ok_reply);
    }

    // 0 is headset, 1 is external audio
    if (a > 1) return false;

    return sendCheckReply("AT+FMOPEN=", a, ok_reply);
}

bool Adafruit_FONA::tuneFMradio(uint16_t station)
{
    // Fail if FM station is outside allowed range.
    if ((station < 870) || (station > 1090))
        return false;

    return sendCheckReply("AT+FMFREQ=", station, ok_reply);
}

bool Adafruit_FONA::setFMVolume(uint8_t i)
{
    // Fail if volume is outside allowed range (0-6).
    if (i > 6) {
        return false;
    }
    // Send FM volume command and verify response.
    return sendCheckReply("AT+FMVOLUME=", i, ok_reply);
}

int8_t Adafruit_FONA::getFMVolume()
{
    uint16_t level;

    if (! sendParseReply("AT+FMVOLUME?", "+FMVOLUME: ", &level) ) return 0;

    return level;
}

int8_t Adafruit_FONA::getFMSignalLevel(uint16_t station)
{
    // Fail if FM station is outside allowed range.
    if ((station < 875) || (station > 1080)) {
        return -1;
    }

    // Send FM signal level query command.
    // Note, need to explicitly send timeout so right overload is chosen.
    getReply("AT+FMSIGNAL=", station, FONA_DEFAULT_TIMEOUT_MS);
    // Check response starts with expected value.
    char *p = strstr(replybuffer, "+FMSIGNAL: ");
    if (p == 0) return -1;
    p+=11;
    // Find second colon to get start of signal quality.
    p = strchr(p, ':');
    if (p == 0) return -1;
    p+=1;
    // Parse signal quality.
    int8_t level = atoi(p);
    readline();  // eat the "OK"
    return level;
}

/********* PWM/BUZZER **************************************************/

bool Adafruit_FONA::setPWM(uint16_t period, uint8_t duty)
{
    if (period > 2000) return false;
    if (duty > 100) return false;

    return sendCheckReply("AT+SPWM=0,", period, duty, ok_reply);
}

/********* CALL PHONES **************************************************/
bool Adafruit_FONA::callPhone(char *number)
{
    char sendbuff[35] = "ATD";
    strncpy(sendbuff+3, number, min((int)30, (int)strlen(number)));
    uint8_t x = strlen(sendbuff);
    sendbuff[x] = ';';
    sendbuff[x+1] = 0;

    return sendCheckReply(sendbuff, ok_reply);
}

bool Adafruit_FONA::hangUp(void)
{
    return sendCheckReply("ATH0", ok_reply);
}

bool Adafruit_FONA_3G::hangUp(void)
{
    getReply("ATH");

    return (strstr(replybuffer, "VOICE CALL: END") != 0);
}

bool Adafruit_FONA::pickUp(void)
{
    return sendCheckReply("ATA", ok_reply);
}

bool Adafruit_FONA_3G::pickUp(void)
{
    return sendCheckReply("ATA", "VOICE CALL: BEGIN");
}

void Adafruit_FONA::onIncomingCall()
{
#ifdef ADAFRUIT_FONA_DEBUG
    printf("> Incoming call...\r\n");
#endif
    _incomingCall = true;
}

bool Adafruit_FONA::incomingCallNumber(char* phonenum)
{
    //+CLIP: "<incoming phone number>",145,"",0,"",0
    if(!_incomingCall)
        return false;

    readline();
    while(!strcmp(replybuffer, "RING") == 0) {
        flushInput();
        readline();
    }

    readline(); //reads incoming phone number line

    parseReply("+CLIP: \"", phonenum, '"');

#ifdef ADAFRUIT_FONA_DEBUG
    printf("Phone Number: %s\r\n", replybuffer);
#endif

    _incomingCall = false;
    return true;
}

/********* SMS **********************************************************/

uint8_t Adafruit_FONA::getSMSInterrupt(void)
{
    uint16_t reply;

    if (! sendParseReply("AT+CFGRI?", "+CFGRI: ", &reply) ) return 0;

    return reply;
}

bool Adafruit_FONA::setSMSInterrupt(uint8_t i)
{
    return sendCheckReply("AT+CFGRI=", i, ok_reply);
}

int8_t Adafruit_FONA::getNumSMS(void)
{
    uint16_t numsms;

    if (! sendCheckReply("AT+CMGF=1", ok_reply)) return -1;
    // ask how many sms are stored

    if (! sendParseReply("AT+CPMS?", "+CPMS: \"SM_P\",", &numsms) ) return -1;

    return numsms;
}

// Reading SMS's is a bit involved so we don't use helpers that may cause delays or debug
// printouts!
bool Adafruit_FONA::readSMS(uint8_t i, char *smsbuff, uint16_t maxlen, uint16_t *readlen)
{
    // text mode
    if (! sendCheckReply("AT+CMGF=1", ok_reply)) return false;

    // show all text mode parameters
    if (! sendCheckReply("AT+CSDH=1", ok_reply)) return false;

    // parse out the SMS len
    uint16_t thesmslen = 0;

    //getReply(F("AT+CMGR="), i, 1000);  //  do not print debug!
    mySerial.printf("AT+CMGR=%d\r\n", i);
    readline(1000); // timeout

    // parse it out...
    if (! parseReply("+CMGR:", &thesmslen, ',', 11)) {
        *readlen = 0;
        return false;
    }

    readRaw(thesmslen);

    flushInput();

    uint16_t thelen = min(maxlen, (uint16_t)strlen(replybuffer));
    strncpy(smsbuff, replybuffer, thelen);
    smsbuff[thelen] = 0; // end the string

#ifdef ADAFRUIT_FONA_DEBUG
    printf("%s\r\n", replybuffer);
#endif
    *readlen = thelen;
    return true;
}

// Retrieve the sender of the specified SMS message and copy it as a string to
// the sender buffer.  Up to senderlen characters of the sender will be copied
// and a null terminator will be added if less than senderlen charactesr are
// copied to the result.  Returns true if a result was successfully retrieved,
// otherwise false.
bool Adafruit_FONA::getSMSSender(uint8_t i, char *sender, int senderlen)
{
    // Ensure text mode and all text mode parameters are sent.
    if (! sendCheckReply("AT+CMGF=1", ok_reply)) return false;
    if (! sendCheckReply("AT+CSDH=1", ok_reply)) return false;
    // Send command to retrieve SMS message and parse a line of response.
    mySerial.printf("AT+CMGR=%d\r\n", i);
    readline(1000);
    // Parse the second field in the response.
    bool result = parseReplyQuoted("+CMGR:", sender, senderlen, ',', 1);
    // Drop any remaining data from the response.
    flushInput();
    return result;
}

bool Adafruit_FONA::sendSMS(char *smsaddr, char *smsmsg)
{
    if (! sendCheckReply("AT+CMGF=1", ok_reply)) return -1;

    char sendcmd[30] = "AT+CMGS=\"";
    strncpy(sendcmd+9, smsaddr, 30-9-2);  // 9 bytes beginning, 2 bytes for close quote + null
    sendcmd[strlen(sendcmd)] = '\"';

    if (! sendCheckReply(sendcmd, "> ")) return false;
#ifdef ADAFRUIT_FONA_DEBUG
    printf("> %s\r\n", smsmsg);
#endif
    mySerial.printf("%s\r\n\r\n", smsmsg);
    mySerial.putc(0x1A);
#ifdef ADAFRUIT_FONA_DEBUG
    printf("^Z\r\n");
#endif
    readline(10000); // read the +CMGS reply, wait up to 10 seconds!!!
    //Serial.print("* "); Serial.println(replybuffer);
    if (strstr(replybuffer, "+CMGS") == 0) {
        return false;
    }
    readline(1000); // read OK
    //Serial.print("* "); Serial.println(replybuffer);

    if (strcmp(replybuffer, ok_reply) != 0) {
        return false;
    }

    return true;
}


bool Adafruit_FONA::deleteSMS(uint8_t i)
{
    if (! sendCheckReply("AT+CMGF=1", ok_reply)) return -1;
    // read an sms
    char sendbuff[12] = "AT+CMGD=000";
    sendbuff[8] = (i / 100) + '0';
    i %= 100;
    sendbuff[9] = (i / 10) + '0';
    i %= 10;
    sendbuff[10] = i + '0';

    return sendCheckReply(sendbuff, ok_reply, 2000);
}

/********* TIME **********************************************************/

bool Adafruit_FONA::enableNetworkTimeSync(bool onoff)
{
    if (onoff) {
        if (! sendCheckReply("AT+CLTS=1", ok_reply))
            return false;
    } else {
        if (! sendCheckReply("AT+CLTS=0", ok_reply))
            return false;
    }

    flushInput(); // eat any 'Unsolicted Result Code'

    return true;
}

bool Adafruit_FONA::enableNTPTimeSync(bool onoff, const char* ntpserver)
{
    if (onoff) {
        if (! sendCheckReply("AT+CNTPCID=1", ok_reply))
            return false;

        mySerial.printf("AT+CNTP=\"");
        if (ntpserver != 0) {
            mySerial.printf(ntpserver);
        } else {
            mySerial.printf("pool.ntp.org");
        }
        mySerial.printf("\",0\r\n");
        readline(FONA_DEFAULT_TIMEOUT_MS);
        if (strcmp(replybuffer, ok_reply) != 0)
            return false;

        if (! sendCheckReply("AT+CNTP", ok_reply, 10000))
            return false;

        uint16_t status;
        readline(10000);
        if (! parseReply("+CNTP:", &status))
            return false;
    } else {
        if (! sendCheckReply("AT+CNTPCID=0", ok_reply))
            return false;
    }

    return true;
}

bool Adafruit_FONA::getTime(char* buff, uint16_t maxlen)
{
    getReply("AT+CCLK?", (uint16_t) 10000);
    if (strncmp(replybuffer, "+CCLK: ", 7) != 0)
        return false;

    char *p = replybuffer+7;
    uint16_t lentocopy = min((uint16_t)(maxlen-1), (uint16_t)strlen(p));
    strncpy(buff, p, lentocopy+1);
    buff[lentocopy] = 0;

    readline(); // eat OK

    return true;
}

/********* GPS **********************************************************/


bool Adafruit_FONA::enableGPS(bool onoff)
{
    uint16_t state;

    // first check if its already on or off
    if (! sendParseReply("AT+CGPSPWR?", "+CGPSPWR: ", &state) )
        return false;

    if (onoff && !state) {
        if (! sendCheckReply("AT+CGPSPWR=1", ok_reply))
            return false;
    } else if (!onoff && state) {
        if (! sendCheckReply("AT+CGPSPWR=0", ok_reply))
            return false;
    }
    return true;
}

bool Adafruit_FONA_3G::enableGPS(bool onoff)
{
    uint16_t state;

    // first check if its already on or off
    if (! Adafruit_FONA::sendParseReply("AT+CGPS?", "+CGPS: ", &state) )
        return false;

    if (onoff && !state) {
        if (! sendCheckReply("AT+CGPS=1", ok_reply))
            return false;
    } else if (!onoff && state) {
        if (! sendCheckReply("AT+CGPS=0", ok_reply))
            return false;
        // this takes a little time
        readline(2000); // eat '+CGPS: 0'
    }
    return true;
}

int8_t Adafruit_FONA::GPSstatus(void)
{
    getReply("AT+CGPSSTATUS?");

    char *p = strstr(replybuffer, "+CGPSSTATUS: Location ");
    if (p == 0) return -1;

    p+=22;

    readline(); // eat 'OK'


    if (p[0] == 'U') return 0;
    if (p[0] == 'N') return 1;
    if (p[0] == '2') return 2;
    if (p[0] == '3') return 3;

    // else
    return 0;
}

uint8_t Adafruit_FONA::getGPS(uint8_t arg, char *buffer, uint8_t maxbuff)
{
    int32_t x = arg;

    getReply("AT+CGPSINF=", x);

    char *p = strstr(replybuffer, "CGPSINF: ");
    if (p == 0) {
        buffer[0] = 0;
        return 0;
    }
    p+=9;
    uint8_t len = max((uint8_t)(maxbuff-1), (uint8_t)strlen(p));
    strncpy(buffer, p, len);
    buffer[len] = 0;

    readline(); // eat 'OK'
    return len;
}

bool Adafruit_FONA::getGPS(float *lat, float *lon, float *speed_kph, float *heading, float *altitude)
{
    char gpsbuffer[120];

    // we need at least a 2D fix
    if (GPSstatus() < 2)
        return false;

    // grab the mode 2^5 gps csv from the sim808
    uint8_t res_len = getGPS(32, gpsbuffer, 120);

    // make sure we have a response
    if (res_len == 0)
        return false;

    // skip mode
    char *tok = strtok(gpsbuffer, ",");
    if (! tok) return false;

    // skip date
    tok = strtok(NULL, ",");
    if (! tok) return false;

    // skip fix
    tok = strtok(NULL, ",");
    if (! tok) return false;

    // grab the latitude
    char *latp = strtok(NULL, ",");
    if (! latp) return false;

    // grab latitude direction
    char *latdir = strtok(NULL, ",");
    if (! latdir) return false;

    // grab longitude
    char *longp = strtok(NULL, ",");
    if (! longp) return false;

    // grab longitude direction
    char *longdir = strtok(NULL, ",");
    if (! longdir) return false;

    double latitude = atof(latp);
    double longitude = atof(longp);

    // convert latitude from minutes to decimal
    float degrees = floor(latitude / 100);
    double minutes = latitude - (100 * degrees);
    minutes /= 60;
    degrees += minutes;

    // turn direction into + or -
    if (latdir[0] == 'S') degrees *= -1;

    *lat = degrees;

    // convert longitude from minutes to decimal
    degrees = floor(longitude / 100);
    minutes = longitude - (100 * degrees);
    minutes /= 60;
    degrees += minutes;

    // turn direction into + or -
    if (longdir[0] == 'W') degrees *= -1;

    *lon = degrees;

    // only grab speed if needed
    if (speed_kph != NULL) {

        // grab the speed in knots
        char *speedp = strtok(NULL, ",");
        if (! speedp) return false;

        // convert to kph
        *speed_kph = atof(speedp) * 1.852;

    }

    // only grab heading if needed
    if (heading != NULL) {

        // grab the speed in knots
        char *coursep = strtok(NULL, ",");
        if (! coursep) return false;

        *heading = atof(coursep);

    }

    // no need to continue
    if (altitude == NULL)
        return true;

    // we need at least a 3D fix for altitude
    if (GPSstatus() < 3)
        return false;

    // grab the mode 0 gps csv from the sim808
    res_len = getGPS(0, gpsbuffer, 120);

    // make sure we have a response
    if (res_len == 0)
        return false;

    // skip mode
    tok = strtok(gpsbuffer, ",");
    if (! tok) return false;

    // skip lat
    tok = strtok(NULL, ",");
    if (! tok) return false;

    // skip long
    tok = strtok(NULL, ",");
    if (! tok) return false;

    // grab altitude
    char *altp = strtok(NULL, ",");
    if (! altp) return false;

    *altitude = atof(altp);

    return true;
}

bool Adafruit_FONA::enableGPSNMEA(uint8_t i)
{
    char sendbuff[15] = "AT+CGPSOUT=000";
    sendbuff[11] = (i / 100) + '0';
    i %= 100;
    sendbuff[12] = (i / 10) + '0';
    i %= 10;
    sendbuff[13] = i + '0';

    return sendCheckReply(sendbuff, ok_reply, 2000);
}


/********* GPRS **********************************************************/


bool Adafruit_FONA::enableGPRS(bool onoff)
{
    if (onoff) {
        // disconnect all sockets
        sendCheckReply("AT+CIPSHUT", "SHUT OK", 5000);

        if (! sendCheckReply("AT+CGATT=1", ok_reply, 10000))
            return false;

        // set bearer profile! connection type GPRS
        if (! sendCheckReply("AT+SAPBR=3,1,\"CONTYPE\",\"GPRS\"", ok_reply, 10000))
            return false;

        // set bearer profile access point name
        if (apn) {
            // Send command AT+SAPBR=3,1,"APN","<apn value>" where <apn value> is the configured APN value.
            if (! sendCheckReplyQuoted("AT+SAPBR=3,1,\"APN\",", apn, ok_reply, 10000))
                return false;

            // set username/password
            if (apnusername) {
                // Send command AT+SAPBR=3,1,"USER","<user>" where <user> is the configured APN username.
                if (! sendCheckReplyQuoted("AT+SAPBR=3,1,\"USER\",", apnusername, ok_reply, 10000))
                    return false;
            }
            if (apnpassword) {
                // Send command AT+SAPBR=3,1,"PWD","<password>" where <password> is the configured APN password.
                if (! sendCheckReplyQuoted("AT+SAPBR=3,1,\"PWD\",", apnpassword, ok_reply, 10000))
                    return false;
            }
        }

        // open GPRS context
        if (! sendCheckReply("AT+SAPBR=1,1", ok_reply, 10000))
            return false;
    } else {
        // disconnect all sockets
        if (! sendCheckReply("AT+CIPSHUT", "SHUT OK", 5000))
            return false;

        // close GPRS context
        if (! sendCheckReply("AT+SAPBR=0,1", ok_reply, 10000))
            return false;

        if (! sendCheckReply("AT+CGATT=0", ok_reply, 10000))
            return false;
    }
    return true;
}



uint8_t Adafruit_FONA::GPRSstate(void)
{
    uint16_t state;

    if (! sendParseReply("AT+CGATT?", "+CGATT: ", &state) )
        return -1;

    return state;
}

void Adafruit_FONA::setGPRSNetworkSettings(const char* apn, const char* ausername, const char* apassword)
{
    this->apn = (char*) apn;
    this->apnusername = (char*) ausername;
    this->apnpassword = (char*) apassword;
}

bool Adafruit_FONA::getGSMLoc(uint16_t *errorcode, char *buff, uint16_t maxlen)
{
    getReply("AT+CIPGSMLOC=1,1", (uint16_t)10000);

    if (! parseReply("+CIPGSMLOC: ", errorcode))
        return false;

    char *p = replybuffer+14;
    uint16_t lentocopy = min((uint16_t)(maxlen-1), (uint16_t)strlen(p));
    strncpy(buff, p, lentocopy+1);

    readline(); // eat OK

    return true;
}

bool Adafruit_FONA::getGSMLoc(float *lat, float *lon)
{
    uint16_t returncode;
    char gpsbuffer[120];

    // make sure we could get a response
    if (! getGSMLoc(&returncode, gpsbuffer, 120))
        return false;

    // make sure we have a valid return code
    if (returncode != 0)
        return false;

    // tokenize the gps buffer to locate the lat & long
    char *latp = strtok(gpsbuffer, ",");
    if (! latp) return false;

    char *longp = strtok(NULL, ",");
    if (! longp) return false;

    *lat = atof(latp);
    *lon = atof(longp);

    return true;
}

/********* TCP FUNCTIONS  ************************************/

uint8_t Adafruit_FONA_3G::TCPinitialize(void)
{

    // AT+CGDCONT=1,"IP","orange-mib"  AT+CGDCONT?
    if (! sendCheckReply("AT+CGDCONT=1,\"IP\",\"orange-mib\"", ok_reply, 5000)) return 1;
    wait_ms(1000);
    if (! sendCheckReply("AT+CGDCONT?", "+CGDCONT: 1,\"IP\",\"orange-mib\",\"0.0.0.0\",0,0", 5000)) return 1;
    wait_ms(1000);

    // AT+CREG=1  AT+CREG?
    if (! sendCheckReply("AT+CREG=1", ok_reply, 500)) return 2;
    wait_ms(1000);
    if (! sendCheckReply("AT+CREG?", "+CREG: 1,1", 500)) return 2;
    wait_ms(1000);

    /*
    // AT+CSOCKSETPN=1  AT+CSOCKSETPN?
    if (! sendCheckReply("AT+CSOCKSETPN=1", ok_reply, 5000)) return 3;
    wait_ms(1000);
    if (! sendCheckReply("AT+CSOCKSETPN?", "+CSOCKSETPN: 1", 5000)) return 3;
    wait_ms(1000);
    */

    /*
    // AT+CGAUTH=1,2,"orange","orange"   AT+CGAUTH?
    if (! sendCheckReply("AT+CGAUTH=1,2,\"orange\",\"orange\"", ok_reply, 5000)) return 4;
    wait_ms(1000);
    if (! sendCheckReply("AT+CGAUTH?", "+CGAUTH: 1,2,\"orange\"", 5000)) return 4;
    wait_ms(1000);
    */

    /*
    // AT+CSOCKAUTH=1,2,"orange","orange"  AT+CSOCKAUTH?
    if (! sendCheckReply("AT+CSOCKAUTH=1,2,\"orange\",\"orange\"", ok_reply, 5000)) return 5;
    wait_ms(1000);
    */

    // AT+CIPMODE=1  AT+CIPMODE?
    if (! sendCheckReply("AT+CIPMODE=1", ok_reply, 5000)) return 6;
    wait_ms(1000);
    if (! sendCheckReply("AT+CIPMODE?", "+CIPMODE: 1", 5000)) return 6;
    wait_ms(1000);

    // ATS0=007  ATS0?
    if (! sendCheckReply("ATS0=007", ok_reply, 5000)) return 7;
    wait_ms(1000);
    if (! sendCheckReply("ATS0?", "007", 5000)) return 7;
    wait_ms(1000);

    /*
    // AT+CGATT=1  AT+CGATT?
    if (! sendCheckReply("AT+CGATT=1", ok_reply, 5000)) return 8;
    wait_ms(1000);
    */

    // AT+CGACT=1,1  AT+CGACT?
    if (! sendCheckReply("AT+CGACT=1,1", ok_reply, 5000)) return 9;
    wait_ms(1000);
    if (! sendCheckReply("AT+CGACT?", "+CGACT: 1,1", 5000)) return 9;
    wait_ms(1000);

    // AT+NETOPEN?
    if (! sendCheckReply("AT+NETOPEN", ok_reply, 5000)) return 10;
    wait_ms(1000);

    return 0;
}

bool Adafruit_FONA::TCPconnect(char *server, uint16_t port)
{
    flushInput();

    // close all old connections
    if (! sendCheckReply("AT+CIPSHUT", "SHUT OK", 5000) ) return false;

    // single connection at a time
    if (! sendCheckReply("AT+CIPMUX=0", ok_reply) ) return false;

    // manually read data
    if (! sendCheckReply("AT+CIPRXGET=1", ok_reply) ) return false;

#ifdef ADAFRUIT_FONA_DEBUG
    printf("AT+CIPSTART=\"TCP\",\"%s\",\"%d\"\r\n", server, port);
#endif

    mySerial.printf("AT+CIPSTART=\"TCP\",\"%s\",\"%d\"\r\n", server, port);

    if (! expectReply(ok_reply)) return false;

    if (! expectReply("CONNECT OK")) return false;

    return true;
}

bool Adafruit_FONA_3G::TCPconnect(char *server, uint16_t port)
{
    flushInput();

    // AT+CSOCKSETPN=1  AT+CSOCKSETPN?
    if (! sendCheckReply("AT+CSOCKSETPN=1", ok_reply, 5000)) return false;

    if (! sendCheckReply("AT+CIPMODE=1", ok_reply) ) return false;

    if (! sendCheckReply("AT+NETOPEN", ok_reply) ) return false;
    
#ifdef ADAFRUIT_FONA_DEBUG
    printf("AT+CIPOPEN=0,\"TCP\",\"%s\",%d\r\n", server, port);
#endif
    mySerial.printf("AT+CIPOPEN=0,\"TCP\",\"%s\",%d\r\n", server, port);
    // AT+CIPOPEN=0,"TCP","217.182.85.123",2323

    if (! expectReply(ok_reply)) return false;

    if (! expectReply("CONNECT OK")) return false;


    return true;
}

bool Adafruit_FONA::TCPclose(void)
{
    return sendCheckReply("AT+CIPCLOSE", ok_reply);
}

bool Adafruit_FONA_3G::TCPclose(void)
{
    return sendCheckReply("AT+CIPCLOSE=0", ok_reply);
}

bool Adafruit_FONA::TCPconnected(void)
{
    if (! sendCheckReply("AT+CIPSTATUS", ok_reply, 100) ) return false;
    readline(100);
#ifdef ADAFRUIT_FONA_DEBUG
    printf("\t<--- %s\r\n", replybuffer);
#endif
    return (strcmp(replybuffer, "STATE: CONNECT OK") == 0);
}

bool Adafruit_FONA::TCPsend(char *packet, uint8_t len)
{
#ifdef ADAFRUIT_FONA_DEBUG
    printf("AT+CIPSEND=%d\r\n", len);

    for (uint16_t i=0; i<len; i++) {
        printf(" 0x%#02x", packet[i]);
    }
    printf("\r\n");
#endif


    mySerial.printf("AT+CIPSEND=%d\r\n", len);
    readline();
#ifdef ADAFRUIT_FONA_DEBUG
    printf("\t<--- %s\r\n", replybuffer);
#endif
    if (replybuffer[0] != '>') return false;

    for (uint16_t i=0; i<len; i++) {
        mySerial.putc(packet[i]);
    }
    readline(3000); // wait up to 3 seconds to send the data
#ifdef ADAFRUIT_FONA_DEBUG
    printf("\t<--- %s\r\n", replybuffer);
#endif

    return (strcmp(replybuffer, "SEND OK") == 0);
}

uint16_t Adafruit_FONA::TCPavailable(void)
{
    uint16_t avail;

    if (! sendParseReply("AT+CIPRXGET=4", "+CIPRXGET: 4,", &avail, ',', 0) ) return false;

#ifdef ADAFRUIT_FONA_DEBUG
    printf("%d bytes available\r\n", avail);
#endif

    return avail;
}


uint16_t Adafruit_FONA::TCPread(uint8_t *buff, uint8_t len)
{
    uint16_t avail;

    mySerial.printf("AT+CIPRXGET=2,%d\r\n", len);
    readline();
    if (! parseReply("+CIPRXGET: 2,", &avail, ',', 0)) return false;

    readRaw(avail);

#ifdef ADAFRUIT_FONA_DEBUG
    printf("%d bytes read\r\n", avail);
    for (uint8_t i=0; i<avail; i++) {
        printf(" 0x%#02x", replybuffer[i]);
    }
    printf("\r\n");
#endif

    memcpy(buff, replybuffer, avail);

    return avail;
}

/********* HTTP LOW LEVEL FUNCTIONS  ************************************/

bool Adafruit_FONA::HTTP_init()
{
    return sendCheckReply("AT+HTTPINIT", ok_reply);
}

bool Adafruit_FONA::HTTP_term()
{
    return sendCheckReply("AT+HTTPTERM", ok_reply);
}

void Adafruit_FONA::HTTP_para_start(const char* parameter, bool quoted)
{
    flushInput();

#ifdef ADAFRUIT_FONA_DEBUG
    printf("\t---> AT+HTTPPARA=\"%s\"\r\n", parameter);
#endif

    mySerial.printf("AT+HTTPPARA=\"%s", parameter);
    if (quoted)
        mySerial.printf("\",\"");
    else
        mySerial.printf("\",");
}

bool Adafruit_FONA::HTTP_para_end(bool quoted)
{
    if (quoted)
        mySerial.printf("\"\r\n");
    else
        mySerial.printf("\r\n");

    return expectReply(ok_reply);
}

bool Adafruit_FONA::HTTP_para(const char* parameter, const char* value)
{
    HTTP_para_start(parameter, true);
    mySerial.printf(value);
    return HTTP_para_end(true);
}

bool Adafruit_FONA::HTTP_para(const char* parameter, int32_t value)
{
    HTTP_para_start(parameter, false);
    mySerial.printf("%d", value);
    return HTTP_para_end(false);
}

bool Adafruit_FONA::HTTP_data(uint32_t size, uint32_t maxTime)
{
    flushInput();

#ifdef ADAFRUIT_FONA_DEBUG
    printf("\t---> AT+HTTPDATA=%d,%d\r\n", size, maxTime);
#endif

    mySerial.printf("AT+HTTPDATA=%d,%d\r\n", size, maxTime);

    return expectReply("DOWNLOAD");
}

bool Adafruit_FONA::HTTP_action(uint8_t method, uint16_t *status, uint16_t *datalen, int32_t timeout)
{
    // Send request.
    if (! sendCheckReply("AT+HTTPACTION=", method, ok_reply))
        return false;

    // Parse response status and size.
    readline(timeout);
    if (! parseReply("+HTTPACTION:", status, ',', 1))
        return false;
    if (! parseReply("+HTTPACTION:", datalen, ',', 2))
        return false;

    return true;
}

bool Adafruit_FONA::HTTP_readall(uint16_t *datalen)
{
    getReply("AT+HTTPREAD");
    if (! parseReply("+HTTPREAD:", datalen, ',', 0))
        return false;

    return true;
}

bool Adafruit_FONA::HTTP_ssl(bool onoff)
{
    return sendCheckReply("AT+HTTPSSL=", onoff ? 1 : 0, ok_reply);
}

/********* HTTP HIGH LEVEL FUNCTIONS ***************************/

bool Adafruit_FONA::HTTP_GET_start(char *url, uint16_t *status, uint16_t *datalen)
{
    if (! HTTP_setup(url))
        return false;

    // HTTP GET
    if (! HTTP_action(FONA_HTTP_GET, status, datalen))
        return false;

#ifdef ADAFRUIT_FONA_DEBUG
    printf("Status: %d\r\n", *status);
    printf("Len: %d\r\n", *datalen);
#endif

    // HTTP response data
    if (! HTTP_readall(datalen))
        return false;

    return true;
}

void Adafruit_FONA::HTTP_GET_end(void)
{
    HTTP_term();
}

bool Adafruit_FONA::HTTP_POST_start(char *url, const char* contenttype, const uint8_t *postdata, uint16_t postdatalen, uint16_t *status, uint16_t *datalen)
{
    if (! HTTP_setup(url))
        return false;

    if (! HTTP_para("CONTENT", contenttype)) {
        return false;
    }

    // HTTP POST data
    if (! HTTP_data(postdatalen, 10000))
        return false;
    for (uint16_t i = 0; i < postdatalen; i++) {
        mySerial.putc(postdata[i]);
    }
    if (! expectReply(ok_reply))
        return false;

    // HTTP POST
    if (! HTTP_action(FONA_HTTP_POST, status, datalen))
        return false;

#ifdef ADAFRUIT_FONA_DEBUG
    printf("Status: %d\r\n", *status);
    printf("Len: %d\r\n", *datalen);
#endif

    // HTTP response data
    if (! HTTP_readall(datalen))
        return false;

    return true;
}

void Adafruit_FONA::HTTP_POST_end(void)
{
    HTTP_term();
}

void Adafruit_FONA::setUserAgent(const char* useragent)
{
    this->useragent = (char*) useragent;
}

void Adafruit_FONA::setHTTPSRedirect(bool onoff)
{
    httpsredirect = onoff;
}

/********* HTTP HELPERS ****************************************/

bool Adafruit_FONA::HTTP_setup(char *url)
{
    // Handle any pending
    HTTP_term();

    // Initialize and set parameters
    if (! HTTP_init())
        return false;
    if (! HTTP_para("CID", 1))
        return false;
    if (! HTTP_para("UA", useragent))
        return false;
    if (! HTTP_para("URL", url))
        return false;

    // HTTPS redirect
    if (httpsredirect) {
        if (! HTTP_para("REDIR",1))
            return false;

        if (! HTTP_ssl(true))
            return false;
    }

    return true;
}


/********* HELPERS *********************************************/

bool Adafruit_FONA::expectReply(const char* reply, uint16_t timeout)
{
    readline(timeout);
#ifdef ADAFRUIT_FONA_DEBUG
    printf("\t<--- %s\r\n", replybuffer);
#endif
    return (strcmp(replybuffer, reply) == 0);
}

/********* LOW LEVEL *******************************************/

void Adafruit_FONA::flushInput()
{
    // Read all available serial input to flush pending data.
    uint16_t timeoutloop = 0;
    while (timeoutloop++ < 400) {
        while(readable()) {
            getc();
            timeoutloop = 0;  // If char was received reset the timer
        }
        wait_ms(1);
    }
}

uint16_t Adafruit_FONA::readRaw(uint16_t b)
{
    uint16_t idx = 0;

    while (b && (idx < sizeof(replybuffer)-1)) {
        if (readable()) {
            replybuffer[idx] = getc();
            idx++;
            b--;
        }
    }
    replybuffer[idx] = 0;

    return idx;
}

uint8_t Adafruit_FONA::readline(uint16_t timeout, bool multiline)
{
    uint16_t replyidx = 0;

    while (timeout--) {
        if (replyidx >= 254) {
            break;
        }

        while(readable()) {
            char c =  getc();
            if (c == '\r') continue;
            if (c == 0xA) {
                if (replyidx == 0)   // the first 0x0A is ignored
                    continue;

                if (!multiline) {
                    timeout = 0;         // the second 0x0A is the end of the line
                    break;
                }
            }
            replybuffer[replyidx] = c;
            replyidx++;
        }

        if (timeout == 0) {
            break;
        }
        wait_ms(1);
    }
    replybuffer[replyidx] = 0;  // null term
    return replyidx;
}

uint8_t Adafruit_FONA::getReply(const char* send, uint16_t timeout)
{
    flushInput();

#ifdef ADAFRUIT_FONA_DEBUG
    printf("\t---> %s\r\n", send);
#endif

    mySerial.printf("%s\r\n",send);

    uint8_t l = readline(timeout);
#ifdef ADAFRUIT_FONA_DEBUG
    printf("\t<--- %s\r\n", replybuffer);
#endif
    return l;
}

// Send prefix, suffix, and newline. Return response (and also set replybuffer with response).
uint8_t Adafruit_FONA::getReply(const char* prefix, char* suffix, uint16_t timeout)
{
    flushInput();

#ifdef ADAFRUIT_FONA_DEBUG
    printf("\t---> %s%s\r\n", prefix, suffix);
#endif

    mySerial.printf("%s%s\r\n", prefix, suffix);

    uint8_t l = readline(timeout);
#ifdef ADAFRUIT_FONA_DEBUG
    printf("\t<--- %s\r\n", replybuffer);
#endif
    return l;
}

// Send prefix, suffix, and newline. Return response (and also set replybuffer with response).
uint8_t Adafruit_FONA::getReply(const char* prefix, int32_t suffix, uint16_t timeout)
{
    flushInput();

#ifdef ADAFRUIT_FONA_DEBUG
    printf("\t---> %s%d\r\n", prefix, suffix);
#endif

    mySerial.printf("%s%d\r\n", prefix, suffix);

    uint8_t l = readline(timeout);
#ifdef ADAFRUIT_FONA_DEBUG
    printf("\t<--- %s\r\n", replybuffer);
#endif
    return l;
}

// Send prefix, suffix, suffix2, and newline. Return response (and also set replybuffer with response).
uint8_t Adafruit_FONA::getReply(const char* prefix, int32_t suffix1, int32_t suffix2, uint16_t timeout)
{
    flushInput();

#ifdef ADAFRUIT_FONA_DEBUG
    printf("\t---> %s%d,%d\r\n", prefix, suffix1, suffix2);
#endif

    mySerial.printf("%s%d,%d\r\n", prefix, suffix1, suffix2);

    uint8_t l = readline(timeout);
#ifdef ADAFRUIT_FONA_DEBUG
    printf("\t<--- %s\r\n", replybuffer);
#endif

    return l;
}

// Send prefix, ", suffix, ", and newline. Return response (and also set replybuffer with response).
uint8_t Adafruit_FONA::getReplyQuoted(const char* prefix, const char* suffix, uint16_t timeout)
{
    flushInput();

#ifdef ADAFRUIT_FONA_DEBUG
    printf("\t---> %s\"%s\"\r\n", prefix, suffix);
#endif

    mySerial.printf("%s\"%s\"\r\n", prefix, suffix);

    uint8_t l = readline(timeout);
#ifdef ADAFRUIT_FONA_DEBUG
    printf("\t<--- %s\r\n", replybuffer);
#endif
    return l;
}

bool Adafruit_FONA::sendCheckReply(const char *send, const char *reply, uint16_t timeout)
{
    if (! getReply(send, timeout))
        return false;

    return (strcmp(replybuffer, reply) == 0);
}

// Send prefix, suffix, and newline.  Verify FONA response matches reply parameter.
bool Adafruit_FONA::sendCheckReply(const char* prefix, char *suffix, const char* reply, uint16_t timeout)
{
    if (! getReply(prefix, suffix, timeout))
        return false;

    return (strcmp(replybuffer, reply) == 0);
}

// Send prefix, suffix, and newline.  Verify FONA response matches reply parameter.
bool Adafruit_FONA::sendCheckReply(const char* prefix, int32_t suffix, const char* reply, uint16_t timeout)
{
    if (! getReply(prefix, suffix, timeout))
        return false;
    return (strcmp(replybuffer, reply) == 0);
}

// Send prefix, suffix, suffix2, and newline.  Verify FONA response matches reply parameter.
bool Adafruit_FONA::sendCheckReply(const char* prefix, int32_t suffix1, int32_t suffix2, const char* reply, uint16_t timeout)
{
    getReply(prefix, suffix1, suffix2, timeout);
    return (strcmp(replybuffer, reply) == 0);
}

// Send prefix, ", suffix, ", and newline.  Verify FONA response matches reply parameter.
bool Adafruit_FONA::sendCheckReplyQuoted(const char* prefix, const char* suffix, const char* reply, uint16_t timeout)
{
    getReplyQuoted(prefix, suffix, timeout);
    return (strcmp(replybuffer, reply) == 0);
}

bool Adafruit_FONA::parseReply(const char* toreply, uint16_t *v, char divider, uint8_t index)
{
    char *p = strstr(replybuffer, toreply);  // get the pointer to the voltage
    if (p == 0) return false;
    p += strlen(toreply);

    for (uint8_t i=0; i<index; i++) {
        // increment dividers
        p = strchr(p, divider);
        if (!p) return false;
        p++;
    }

    *v = atoi(p);

    return true;
}

bool Adafruit_FONA::parseReply(const char* toreply, char *v, char divider, uint8_t index)
{
    uint8_t i=0;
    char *p = strstr(replybuffer, toreply);
    if (p == 0) return false;
    p+=strlen(toreply);

    for (i=0; i<index; i++) {
        // increment dividers
        p = strchr(p, divider);
        if (!p) return false;
        p++;
    }

    for(i=0; i<strlen(p); i++) {
        if(p[i] == divider)
            break;
        v[i] = p[i];
    }

    v[i] = '\0';

    return true;
}

// Parse a quoted string in the response fields and copy its value (without quotes)
// to the specified character array (v).  Only up to maxlen characters are copied
// into the result buffer, so make sure to pass a large enough buffer to handle the
// response.
bool Adafruit_FONA::parseReplyQuoted(const char* toreply, char* v, int maxlen, char divider, uint8_t index)
{
    uint8_t i=0, j;
    // Verify response starts with toreply.
    char *p = strstr(replybuffer, toreply);
    if (p == 0) return false;
    p+=strlen(toreply);

    // Find location of desired response field.
    for (i=0; i<index; i++) {
        // increment dividers
        p = strchr(p, divider);
        if (!p) return false;
        p++;
    }

    // Copy characters from response field into result string.
    for(i=0, j=0; j<maxlen && i<strlen(p); ++i) {
        // Stop if a divier is found.
        if(p[i] == divider)
            break;
        // Skip any quotation marks.
        else if(p[i] == '"')
            continue;
        v[j++] = p[i];
    }

    // Add a null terminator if result string buffer was not filled.
    if (j < maxlen)
        v[j] = '\0';

    return true;
}

bool Adafruit_FONA::sendParseReply(const char* tosend, const char* toreply, uint16_t *v, char divider, uint8_t index)
{
    getReply(tosend);

    if (! parseReply(toreply, v, divider, index)) return false;

    readline(); // eat 'OK'

    return true;
}

// needed for CBC and others

bool Adafruit_FONA_3G::sendParseReply(const char* tosend, const char* toreply, float *f, char divider, uint8_t index)
{
    getReply(tosend);

    if (! parseReply(toreply, f, divider, index)) return false;

    readline(); // eat 'OK'

    return true;
}

bool Adafruit_FONA_3G::parseReply(const char* toreply, float *f, char divider, uint8_t index)
{
    char *p = strstr(replybuffer, toreply);  // get the pointer to the voltage
    if (p == 0) return false;
    p+=strlen(toreply);
    //DEBUG_PRINTLN(p);
    for (uint8_t i=0; i<index; i++) {
        // increment dividers
        p = strchr(p, divider);
        if (!p) return false;
        p++;
        //DEBUG_PRINTLN(p);

    }
    *f = atof(p);

    return true;
}