Add a bunch of APNs

Fork of C027_Support by Xinlei Cao

MDM.cpp

Committer:
mazgch
Date:
2014-06-05
Revision:
84:a05edb010176
Parent:
82:055dcfcf9dcc
Child:
85:dd8f4f0d0ca9

File content as of revision 84:a05edb010176:

#include "mbed.h"
#include "MDM.h"
#include "Relax.h"
#ifdef TARGET_UBLOX_C027
 #include "C027_api.h"
#endif

/* ----------------------------------------------------------------
   APN stands for Access Point Name, a setting on your modem or phone
   that identifies an external network your phone can access for data 
   (e.g., 3G or 4G Internet service on your phone). 
   
   The APN settings can be forced when calling the join function.
   Below is a list of known APNs that us used if no apn config 
   is forced. This list could be extended by other settings.
   
   For further reading:
   wiki apn: http://en.wikipedia.org/wiki/Access_Point_Name
   wiki mcc/mnc: http://en.wikipedia.org/wiki/Mobile_country_code
   google: https://www.google.de/search?q=APN+list   
---------------------------------------------------------------- */
//! helper 
#define _APN(a,u,p) a "\0" u "\0" p "\0"
//! default APN settings used by many networks
static const char* apndef =     _APN("internet",,); 
//! this is a list of special APNs for different network operators 
static const struct { const char* mccmnc; const char* cfg; } apnlut[] = {
// Germany 
    { /*T-Mobile*/ "26201",     _APN("internet.t-mobile","t-mobile","tm") },
// Switzerland
    { /*Swisscom*/ "22801",     _APN("gprs.swisscom.ch",,) },
// USA
    { /*T-Mobile*/ "310026|310260|310490",
                                _APN("epc.tmobile.com",,) 
                                _APN("fast.tmobile.com",,) /*LTE*/ },
    { /*AT&T*/     "310030|310150|310170|310260|310410|310560|310680",
                                _APN("phone",,)
                                _APN("wap.cingular","WAP@CINGULARGPRS.COM","CINGULAR1")
                                _APN("isp.cingular","ISP@CINGULARGPRS.COM","CINGULAR1") },
    // ...     
};
// ----------------------------------------------------------------
                
#define PROFILE         "0"   //!< this is the psd profile used
#define MAX_SIZE        128   //!< max expected messages
//! test if it is a socket
#define ISSOCKET(s)     (((s) >= 0) && ((s) < (sizeof(_sockets)/sizeof(*_sockets))))
//! check for timeout
#define TIMEOUT(t, ms)  ((ms != TIMEOUT_BLOCKING) && (ms < t.read_ms())) 
//! registration ok check helper
#define REG_OK(r)       ((r == REG_HOME) || (r == REG_ROAMING)) 
//! registration done check helper (no need to poll further)
#define REG_DONE(r)     ((r == REG_HOME) || (r == REG_ROAMING) || (r == REG_DENIED)) 

#ifdef MDM_DEBUG
void dumpAtCmd(const char* buf, int len)
{
    ::printf(" %3d \"", len);
    while (len --) {
        char ch = *buf++;
        if ((ch > 0x1F) && (ch != 0x7F)) { // is printable
            if      (ch == '%')  ::printf("%%");
            else if (ch == '"')  ::printf("\\\"");
            else if (ch == '\\') ::printf("\\\\");
            else putchar(ch);
        } else {
            if      (ch == '\a') ::printf("\\a"); // BEL (0x07)
            else if (ch == '\b') ::printf("\\b"); // Backspace (0x08)
            else if (ch == '\t') ::printf("\\t"); // Horizontal Tab (0x09)
            else if (ch == '\n') ::printf("\\n"); // Linefeed (0x0A)
            else if (ch == '\v') ::printf("\\v"); // Vertical Tab (0x0B)
            else if (ch == '\f') ::printf("\\f"); // Formfeed (0x0C)
            else if (ch == '\r') ::printf("\\r"); // Carriage Return (0x0D)
            else                 ::printf("\\x%02x", (unsigned char)ch);
        }
    }
    ::printf("\"\r\n");
}
 
 #define ERROR(fmt) (_debugLevel < 0) ? : ::printf(RED(fmt))
 #define INFO(fmt)  (_debugLevel < 1) ? : ::printf(GRE(fmt))
 #define TRACE(...) (_debugLevel < 2) ? : ::printf(__VA_ARGS__)
 
 #if 1 // colored terminal output using ANSI escape sequences
  #define COL(c,t) "\033[" c t "\033[" "39m"
 #else
  #define COL(c,t) t
 #endif
 #define BLA(t) COL("30m",t)
 #define RED(t) COL("31m",t)
 #define GRE(t) COL("32m",t)
 #define YEL(t) COL("33m",t)
 #define BLU(t) COL("34m",t)
 #define MAG(t) COL("35m",t)
 #define CYA(t) COL("36m",t)
 #define WHY(t) COL("37m",t)
 
#else
 
 #define ERROR(...) (void)0 // no tracing
 #define INFO(...)  (void)0 // no tracing
 #define TRACE(...) (void)0 // no tracing

#endif

MDMParser* MDMParser::inst;

MDMParser::MDMParser(void)
{
    inst = this;
    memset(&_dev, 0, sizeof(_dev));
    memset(&_net, 0, sizeof(_net));
    _net.lac = 0xFFFF;
    _net.ci = 0xFFFFFFFF;
    _ip        = NOIP;
    _init      = false;
    memset(_sockets, 0, sizeof(_sockets));
#ifdef MDM_DEBUG
    _debugLevel = 1;
    _debugTime.start();
#endif
}

int MDMParser::send(const char* buf, int len)
{
#ifdef MDM_DEBUG
    if (_debugLevel >= 3) {
        ::printf("%10.3f AT send    ", _debugTime.read_ms()*0.001);
        dumpAtCmd(buf,len);
    }
#endif
    return _send(buf, len);
}

int MDMParser::sendFormated(const char* format, ...) {
    char buf[MAX_SIZE];
    va_list args;
    va_start(args, format);
    int len = vsnprintf(buf,sizeof(buf), format, args);
    va_end(args);
    return send(buf, len);
}

int MDMParser::waitFinalResp(_CALLBACKPTR cb /* = NULL*/, 
                             void* param /* = NULL*/, 
                             int timeout_ms /*= 5000*/)
{
    char buf[MAX_SIZE + 64 /* add some more space for framing */]; 
    Timer timer;
    timer.start();
    do {
        int ret = getLine(buf, sizeof(buf));
#ifdef MDM_DEBUG
        if ((_debugLevel >= 3) && (ret != WAIT) && (ret != NOT_FOUND))
        {
            int len = LENGTH(ret);
            int type = TYPE(ret);
            const char* s = (type == TYPE_UNKNOWN)? YEL("UNK") : 
                            (type == TYPE_TEXT)   ? MAG("TXT") : 
                            (type == TYPE_OK   )  ? GRE("OK ") : 
                            (type == TYPE_ERROR)  ? RED("ERR") : 
                            (type == TYPE_PLUS)   ? CYA(" + ") : 
                            (type == TYPE_PROMPT) ? BLU(" > ") : 
                                                        "..."  ;
            ::printf("%10.3f AT read %s", _debugTime.read_ms()*0.001, s);
            dumpAtCmd(buf, len);
        }
#endif        
        if ((ret != WAIT) && (ret != NOT_FOUND))
        {
            int type = TYPE(ret);
            // handle unsolicited commands here
            if (type == TYPE_PLUS) {
                const char* cmd = buf+3;
                int a, b, c, d, r;
                char s[32];

                // SMS Command ---------------------------------
                // +CNMI: <mem>,<index>
                if (sscanf(cmd, "CMTI: \"%*[^\"]\",%d", &a) == 1) { 
                    TRACE("New SMS at index %d\r\n", a);
                // Socket Specific Command ---------------------------------
                // +UUSORD: <socket>,<length>
                } else if ((sscanf(cmd, "UUSORD: %d,%d", &a, &b) == 2) && 
                    ISSOCKET(a) /*&& (_sockets[a].state == SOCK_CONNECTED)*/) {
                    TRACE("Socket %d: %d bytes pending\r\n", a, b);
                    _sockets[a].pending = b;
                // +UUSORF: <socket>,<length>
                } else if ((sscanf(cmd, "UUSORF: %d,%d", &a, &b) == 2) && 
                    ISSOCKET(a) /*&& (_sockets[a].state == SOCK_CONNECTED)*/) {
                    TRACE("Socket %d: %d bytes pending\r\n", a, b);
                    _sockets[a].pending = b;
                // +UUSOCL: <socket>
                } else if ((sscanf(cmd, "UUSOCL: %d", &a) == 1) && 
                    ISSOCKET(a) && (_sockets[a].state == SOCK_CONNECTED)) {
                    TRACE("Socket %d: closed by remote host\r\n", a);
                    _sockets[a].state = SOCK_CREATED/*=CLOSED*/;
                }                
                if (_dev.dev == DEV_LISA_C200) {
                    // CDMA Specific -------------------------------------------
                    // +CREG: <n><SID>,<NID>,<stat>
                    if (sscanf(cmd, "CREG: %*d,%d,%d,%d",&a,&b,&c) == 3) {
                        // _net.sid = a;
                        // _net.nid = b;
                        if      (c == 0) _net.csd = REG_NONE;     // not registered, home network
                        else if (c == 1) _net.csd = REG_HOME;     // registered, home network
                        else if (c == 2) _net.csd = REG_NONE;     // not registered, but MT is currently searching a new operator to register to
                        else if (c == 3) _net.csd = REG_DENIED;   // registration denied
                        else if (c == 5) _net.csd = REG_ROAMING;  // registered, roaming
                        _net.psd = _net.csd; // fake PSD registration (CDMA is always registered)
                        _net.act = ACT_CDMA;
                        // +CSS: <mode>[,<format>,<oper>[,<AcT>]]
                    } else if (sscanf(cmd, "CSS %*c,%2s,%*d",s) == 1) {
                        //_net.reg = (strcmp("Z", s) == 0) ? REG_UNKNOWN : REG_HOME;
                    }
                } else {
                    // GSM/UMTS Specific -------------------------------------------
                    // +UUPSDD: <profile_id> 
                    if (sscanf(cmd, "UUPSDD: %d",&a) == 1) {
                        if (*PROFILE == a) _ip = NOIP;
                    } else {
                        // +CREG|CGREG: <n>,<stat>[,<lac>,<ci>[,AcT[,<rac>]]] // reply to AT+CREG|AT+CGREG
                        // +CREG|CGREG: <stat>[,<lac>,<ci>[,AcT[,<rac>]]]     // URC
                        b = 0xFFFF; c = 0xFFFFFFFF; d = -1;
                        r = sscanf(cmd, "%s %*d,%d,\"%X\",\"%X\",%d",s,&a,&b,&c,&d);
                        if (r <= 1)
                            r = sscanf(cmd, "%s %d,\"%X\",\"%X\",%d",s,&a,&b,&c,&d);
                        if (r >= 2) {
                            Reg *reg = !strcmp(s, "CREG:")  ? &_net.csd : 
                                       !strcmp(s, "CGREG:") ? &_net.psd : NULL;
                            if (reg) {
                                // network status
                                if      (a == 0) *reg = REG_NONE;     // 0: not registered, home network
                                else if (a == 1) *reg = REG_HOME;     // 1: registered, home network
                                else if (a == 2) *reg = REG_NONE;     // 2: not registered, but MT is currently searching a new operator to register to
                                else if (a == 3) *reg = REG_DENIED;   // 3: registration denied
                                else if (a == 4) *reg = REG_UNKNOWN;  // 4: unknown
                                else if (a == 5) *reg = REG_ROAMING;  // 5: registered, roaming
                                if ((r >= 3) && (b != 0xFFFF))      _net.lac = b; // location area code
                                if ((r >= 4) && (c != 0xFFFFFFFF))  _net.ci  = c; // cell ID
                                // access technology
                                if (r >= 5) {
                                    if      (d == 0) _net.act = ACT_GSM;      // 0: GSM
                                    else if (d == 1) _net.act = ACT_GSM;      // 1: GSM COMPACT
                                    else if (d == 2) _net.act = ACT_UTRAN;    // 2: UTRAN
                                    else if (d == 3) _net.act = ACT_EDGE;     // 3: GSM with EDGE availability
                                    else if (d == 4) _net.act = ACT_UTRAN;    // 4: UTRAN with HSDPA availability
                                    else if (d == 5) _net.act = ACT_UTRAN;    // 5: UTRAN with HSUPA availability
                                    else if (d == 6) _net.act = ACT_UTRAN;    // 6: UTRAN with HSDPA and HSUPA availability
                                }
                            }
                        }
                    }
                }
            }
            if (cb) {
                int len = LENGTH(ret);
                int ret = cb(type, buf, len, param);
                if (WAIT != ret)
                    return ret; 
            }
            if (type == TYPE_OK)        return RESP_OK;
            if (type == TYPE_ERROR)     return RESP_ERROR;
            if (type == TYPE_PROMPT)    return RESP_PROMPT;
        }
        // relax a bit
        RELAX_MS(10); 
    }
    while (!TIMEOUT(timer, timeout_ms));
    return WAIT;
}

int MDMParser::_cbString(int type, const char* buf, int len, char* str)
{
    if (str && (type == TYPE_UNKNOWN)) {
        if (sscanf(buf, "\r\n%s\r\n", str) == 1)
            /*nothing*/;
    }
    return WAIT;
}

int MDMParser::_cbInt(int type, const char* buf, int len, int* val)
{
    if (val && (type == TYPE_UNKNOWN)) {
        if (sscanf(buf, "\r\n%d\r\n", val) == 1)
            /*nothing*/;
    }
    return WAIT;
}

// ----------------------------------------------------------------

bool MDMParser::connect(
            const char* simpin, 
            const char* apn, const char* username, 
            const char* password, Auth auth,
            PinName pn)
{
    bool ok = init(simpin, NULL, pn);  
#ifdef MDM_DEBUG
    if (_debugLevel >= 1) dumpDevStatus(&_dev);
#endif
    if (!ok)
        return false;
    ok = registerNet();
#ifdef MDM_DEBUG
    if (_debugLevel >= 1) dumpNetStatus(&_net);
#endif
    if (!ok)
        return false;
    IP ip = join(apn,username,password,auth);
#ifdef MDM_DEBUG
    if (_debugLevel >= 1) dumpIp(ip);
#endif
    if (ip == NOIP)
        return false; 
    return true;
}

bool MDMParser::init(const char* simpin, DevStatus* status, PinName pn)
{
    int i = 10;
    memset(&_dev, 0, sizeof(_dev));
    if (pn != NC) {
        INFO("Modem::wakeup\r\n");
        DigitalOut pin(pn, 1);
        while (i--) {
            // SARA-U2/LISA-U2 50..80us
            pin = 0; wait_us(50);
            pin = 1; wait_ms(10); 
            
            // SARA-G35 >5ms, LISA-C2 > 150ms
            pin = 0; wait_ms(150);
            pin = 1; wait_ms(100);
            
            // purge any messages 
            while (WAIT != waitFinalResp(NULL,NULL,0))
                /* nothing */;
            // check interface and disable local echo
            sendFormated("AT\r\n");
            if(RESP_OK == waitFinalResp(NULL,NULL,500))
                break;
        }
        if (i < 0) {
            ERROR("No Reply from Modem");
            return false;
        }
    }
    _init = true;
    
    INFO("Modem::init\r\n");
    // echo off
    sendFormated("AT E0\r\n");
    if(RESP_OK != waitFinalResp())
        return false;
    // enable verbose error messages
    sendFormated("AT+CMEE=2\r\n");
    if(RESP_OK != waitFinalResp())
        return false;
    // set baud rate
    sendFormated("AT+IPR=115200\r\n");
    if (RESP_OK != waitFinalResp())
        return false;
    RELAX_MS(40);
    // identify the module 
    sendFormated("ATI\r\n");
    if (RESP_OK != waitFinalResp(_cbATI, &_dev.dev))
        return false;
    if (_dev.dev == DEV_UNKNOWN)
        return false;
    // device specific init
    if (_dev.dev == DEV_LISA_C200) {
        // get the manufacturer
        sendFormated("AT+GMI\r\n");
        if (RESP_OK != waitFinalResp(_cbString, _dev.manu))
            return false;
        // get the model identification
        sendFormated("AT+GMM\r\n");
        if (RESP_OK != waitFinalResp(_cbString, _dev.model))
            return false;
        // get the sw version
        sendFormated("AT+GMR\r\n");
        if (RESP_OK != waitFinalResp(_cbString, _dev.ver))
            return false;
        // Return the pseudo ESN or MEID
        sendFormated("AT+GSN\r\n");
        if (RESP_OK != waitFinalResp(_cbString, _dev.meid))
            return false;
#if 0
        // enable power saving
        if (_dev.lpm != LPM_DISABLED) {
             // enable power saving (requires flow control, cts at least)
            sendFormated("AT+UPSV=1,1280\r\n");
            if (RESP_OK != waitFinalResp())
                return false;  
            _dev.lpm = LPM_ACTIVE;
        }
#endif
    } else {
        if (_dev.dev == DEV_LISA_U200) {
            // enable the network identification feature 
            sendFormated("AT+UGPIOC=20,2\r\n");
            if (RESP_OK != waitFinalResp())
                return false;
        } else {
            // enable the network identification feature 
            sendFormated("AT+UGPIOC=16,2\r\n");
            if (RESP_OK != waitFinalResp())
                return false;
        }
        // check the sim card
        for (int i = 0; (i < 5) && (_dev.sim != SIM_READY); i++) {
            sendFormated("AT+CPIN?\r\n");
            int ret = waitFinalResp(_cbCPIN, &_dev.sim);
            // having an error here is ok (sim may still be initializing)
            if ((RESP_OK != ret) && (RESP_ERROR != ret))
                return false;
            // Enter PIN if needed
            if (_dev.sim == SIM_PIN) {
                if (!simpin) {
                    ERROR("SIM PIN not available\r\n");
                    return false;
                }
                sendFormated("AT+CPIN=%s\r\n", simpin);
                if (RESP_OK != waitFinalResp(_cbCPIN, &_dev.sim))
                    return false;
            } else if (_dev.sim != SIM_READY) {
                RELAX_MS(1000);
            }
        }
        if (_dev.sim != SIM_READY) {
            if (_dev.sim == SIM_MISSING)
                ERROR("SIM not inserted\r\n");
            return false;
        }
        // get the manufacturer
        sendFormated("AT+CGMI\r\n");
        if (RESP_OK != waitFinalResp(_cbString, _dev.manu))
            return false;
        // get the model identification
        sendFormated("AT+CGMM\r\n");
        if (RESP_OK != waitFinalResp(_cbString, _dev.model))
            return false;
        // get the 
        sendFormated("AT+CGMR\r\n");
        if (RESP_OK != waitFinalResp(_cbString, _dev.ver))
            return false;            
        // Returns the ICCID (Integrated Circuit Card ID) of the SIM-card. 
        // ICCID is a serial number identifying the SIM.
        sendFormated("AT+CCID\r\n");
        if (RESP_OK != waitFinalResp(_cbCCID, _dev.ccid))
            return false;
        // Returns the product serial number, IMEI (International Mobile Equipment Identity)
        sendFormated("AT+CGSN\r\n");
        if (RESP_OK != waitFinalResp(_cbString, _dev.imei))
            return false;
        // enable power saving
        if (_dev.lpm != LPM_DISABLED) {
             // enable power saving (requires flow control, cts at least)
            sendFormated("AT+UPSV=1\r\n");
            if (RESP_OK != waitFinalResp())
                return false;  
            _dev.lpm = LPM_ACTIVE;
        }
        // enable the psd registration unsolicited result code
        sendFormated("AT+CGREG=2\r\n");
        if (RESP_OK != waitFinalResp())
            return false;
        // set operator selection 
        sendFormated("AT+COPS=0,0\r\n");
        if (RESP_OK != waitFinalResp(NULL,NULL,180*1000))
            return false;
    } 
    // enable the network registration unsolicited result code
    sendFormated("AT+CREG=%d\r\n", (_dev.dev == DEV_LISA_C200) ? 1 : 2);
    if (RESP_OK != waitFinalResp())
        return false;
    // Setup SMS in text mode 
    sendFormated("AT+CMGF=1\r\n");
    if (RESP_OK != waitFinalResp())
        return false;
    // setup new message indication
    sendFormated("AT+CNMI=2,1\r\n");
    if (RESP_OK != waitFinalResp())
        return false;
    // Request IMSI (International Mobile Subscriber Identification)
    sendFormated("AT+CIMI\r\n");
    if (RESP_OK != waitFinalResp(_cbString, _dev.imsi))
        return false;
    if (status)
        memcpy(status, &_dev, sizeof(DevStatus));
    return true; 
}

bool MDMParser::powerOff(void)
{
    if (_init) {
        INFO("Modem::powerOff\r\n");
        sendFormated("AT+CPWROFF\r\n");
        if (RESP_OK != waitFinalResp(NULL,NULL,120*1000))
            return false;
        _init = false;
    }
    return true;
}

int MDMParser::_cbATI(int type, const char* buf, int len, Dev* dev)
{
    if ((type == TYPE_UNKNOWN) && dev) {
        if (strstr(buf, "SARA-G350")) {
            *dev = DEV_SARA_G350;
            /*TRACE("Identified Device: SARA-G350 2G\\n")*/;
        } else if (strstr(buf, "LISA-U200")) {
            *dev = DEV_LISA_U200;
            /*TRACE("Identified Device: LISA-U200 2G/3G\r\n")*/;
        } else if (strstr(buf, "LISA-C200")) {
            *dev= DEV_LISA_C200;
            /*TRACE("Identified Device: LISA-C200 CDMA\r\n")*/;
        }
    }
    return WAIT;
}

int MDMParser::_cbCPIN(int type, const char* buf, int len, Sim* sim)
{
    if (sim) {
        if (type == TYPE_PLUS){
            char s[16];
            if (sscanf(buf, "\r\n+CPIN: %[^\r]\r\n", s) >= 1)
                *sim = (0 == strcmp("READY", s)) ? SIM_READY : SIM_PIN;
        } else if (type == TYPE_ERROR) {
            if (strstr(buf, "+CME ERROR: SIM not inserted"))
                *sim = SIM_MISSING;
        }
    }
    return WAIT;
}

int MDMParser::_cbCCID(int type, const char* buf, int len, char* ccid)
{
    if ((type == TYPE_PLUS) && ccid){
        if (sscanf(buf, "\r\n+CCID: %[^\r]\r\n", ccid) == 1)
            /*TRACE("Got CCID: %s\r\n", ccid)*/;
    }
    return WAIT;
}

bool MDMParser::registerNet(NetStatus* status /*= NULL*/, int timeout_ms /*= 180000*/) 
{
    Timer timer;
    timer.start();
    INFO("Modem::register\r\n");
    while (!checkNetStatus(status) && !TIMEOUT(timer, timeout_ms))
        RELAX_MS(1000);
    if (_net.csd == REG_DENIED) ERROR("CSD Registration Denied\r\n");
    if (_net.psd == REG_DENIED) ERROR("PSD Registration Denied\r\n");
    return REG_OK(_net.csd) || REG_OK(_net.psd);
}

bool MDMParser::checkNetStatus(NetStatus* status /*= NULL*/)
{
    memset(&_net, 0, sizeof(_net));
    _net.lac = 0xFFFF;
    _net.ci = 0xFFFFFFFF;
    // check registration
    sendFormated("AT+CREG?\r\n");
    if (RESP_OK != waitFinalResp())
        return false;
    if (_dev.dev != DEV_LISA_C200) {
        // check PSD registration
        sendFormated("AT+CGREG?\r\n");
        if (RESP_OK != waitFinalResp())
            return false;
    }
    if (REG_OK(_net.csd) || REG_OK(_net.psd))
    {
        // check modem specific status messages 
        if (_dev.dev == DEV_LISA_C200) {
            sendFormated("AT+CSS?\r\n");
            if (RESP_OK != waitFinalResp())
                return false;
            while (1) {
                // get the Telephone number
                sendFormated("AT$MDN?\r\n");
                if (RESP_OK != waitFinalResp(_cbString, _net.num))
                    return false;
                // check if we have a Mobile Directory Number
                if (*_net.num && (memcmp(_net.num, "000000", 6) != 0))
                    break;
                    
                INFO("Device not yet activated\r\n");
                INFO("Make sure you have a valid contract with the network operator for this device.\r\n");
                // Check if the the version contains a V for Verizon 
                // Verizon: E0.V.xx.00.xxR, 
                // Sprint E0.S.xx.00.xxR
                if (_dev.ver[3] == 'V') { 
                    int i;
                    INFO("Start device over-the-air activation (this can take a few minutes)\r\n");
                    sendFormated("AT+CDV=*22899\r\n");
                    i = 1;
                    if ((RESP_OK != waitFinalResp(_cbUACTIND, &i, 120*1000)) || (i == 1)) {
                        ERROR("Device over-the-air activation failed\r\n");
                        return false;
                    }
                    INFO("Device over-the-air activation successful\r\n");
                    
                    INFO("Start PRL over-the-air update (this can take a few minutes)\r\n");
                    sendFormated("AT+CDV=*22891\r\n");
                    i = 1;
                    if ((RESP_OK != waitFinalResp(_cbUACTIND, &i, 120*1000)) || (i == 1)) {
                        ERROR("PRL over-the-air update failed\r\n");
                        return false;
                    }
                    INFO("PRL over-the-air update successful\r\n");
                    
                } else { 
                    // Sprint or Aeris 
                    INFO("Wait for OMA-DM over-the-air activation (this can take a few minutes)\r\n");
                    RELAX_MS(120*1000);
                }
            }
            // get the the Network access identifier string
            char nai[64];
            sendFormated("AT$QCMIPNAI?\r\n");
            if (RESP_OK != waitFinalResp(_cbString, nai))
                return false;
        } else {
            sendFormated("AT+COPS?\r\n");
            if (RESP_OK != waitFinalResp(_cbCOPS, &_net))
                return false;
            // Returns the MSISDNs related to this subscriber
            sendFormated("AT+CNUM\r\n");
            if (RESP_OK != waitFinalResp(_cbCNUM, _net.num))
                return false;
        }  
        // Returns the signal strength indication
        sendFormated("AT+CSQ\r\n");
        if (RESP_OK != waitFinalResp(_cbCSQ, &_net))
            return false;
    }
    if (status) {
        memcpy(status, &_net, sizeof(NetStatus));
    }
    return REG_DONE(_net.csd) && REG_DONE(_net.psd);
}

int MDMParser::_cbCOPS(int type, const char* buf, int len, NetStatus* status)
{
    if ((type == TYPE_PLUS) && status){
        int act = 99;
        // +COPS: <mode>[,<format>,<oper>[,<AcT>]]
        if (sscanf(buf, "\r\n+COPS: %*d,%*d,\"%[^\"]\",%d",status->opr,&act) >= 1) {
            if      (act == 0) status->act = ACT_GSM;      // 0: GSM, 
            else if (act == 2) status->act = ACT_UTRAN;    // 2: UTRAN
        }
    }
    return WAIT;
}

int MDMParser::_cbCNUM(int type, const char* buf, int len, char* num)
{
    if ((type == TYPE_PLUS) && num){
        int a;
        if ((sscanf(buf, "\r\n+CNUM: \"My Number\",\"%31[^\"]\",%d", num, &a) == 2) && 
            ((a == 129) || (a == 145))) {
        }
    }
    return WAIT;
}
                    
int MDMParser::_cbCSQ(int type, const char* buf, int len, NetStatus* status)
{
    if ((type == TYPE_PLUS) && status){
        int a,b;
        char _ber[] = { 49, 43, 37, 25, 19, 13, 7, 0 }; // see 3GPP TS 45.008 [20] subclause 8.2.4
        // +CSQ: <rssi>,<qual>
        if (sscanf(buf, "\r\n+CSQ: %d,%d",&a,&b) == 2) {
            if (a != 99) status->rssi = -113 + 2*a;  // 0: -113 1: -111 ... 30: -53 dBm with 2 dBm steps
            if ((b != 99) && (b < sizeof(_ber))) status->ber = _ber[b];  // 
        }
    }
    return WAIT;
}

int MDMParser::_cbUACTIND(int type, const char* buf, int len, int* i)
{
    if ((type == TYPE_PLUS) && i){
        int a;
        if (sscanf(buf, "\r\n+UACTIND: %d", &a) == 1) {
            *i = a;
        }
    }
    return WAIT;
}

// ----------------------------------------------------------------
// internet connection 

MDMParser::IP MDMParser::join(const char* apn /*= NULL*/, const char* username /*= NULL*/, 
                              const char* password /*= NULL*/, Auth auth /*= AUTH_DETECT*/)
{
    INFO("Modem::join\r\n");
    _ip = NOIP;
    if (_dev.dev == DEV_LISA_C200) {
        // make a dumy dns lookup (which will fail, so ignore the result) 
        sendFormated("AT+UDNSRN=0,\"u-blox.com\"\r\n");
        waitFinalResp(); 
        // This fake lookup will enable the IP connection and we 
        // should have an IP after this, so we check it
        
        //Get local IP address
        sendFormated("AT+CMIP?\r\n");
        if (RESP_OK != waitFinalResp(_cbCMIP, &_ip))
            return NOIP;
    } else { 
        // check gprs attach status 
        sendFormated("AT+CGATT=1\r\n");
        if (RESP_OK != waitFinalResp(NULL,NULL,3*60*1000)) 
            return NOIP;
        
        // Check the profile
        int a = 0;
        bool force = true;
        sendFormated("AT+UPSND=" PROFILE ",8\r\n");
        if (RESP_OK != waitFinalResp(_cbUPSND, &a))
            return NOIP;
        if (a == 1 && force) {
            // disconnect the profile already if it is connected 
            sendFormated("AT+UPSDA=" PROFILE ",4\r\n");
            if (RESP_OK != waitFinalResp(NULL,NULL,40*1000))
                return NOIP;
            a = 0;
        }
        if (a == 0) {
            bool ok = false;
            // try to lookup the apn settings from our local database by mccmnc
            const char* config = NULL;
            if (!apn && !username && !password) {
                char mccmnc[8] = "";
                config = apndef;
                sendFormated("AT+UDOPN=0\r\n");
                if ((RESP_OK == waitFinalResp(_cbUDOPN, mccmnc)) && *mccmnc) {
                    // many carriers use internet without username and password, os use this as default
                    // now try to lookup the setting for our table
                    for (int i = 0; i < sizeof(apnlut)/sizeof(*apnlut); i ++) {
                        if (strstr(apnlut[i].mccmnc, mccmnc)) {
                            config = apnlut[i].cfg;
                            break;
                        }
                    }
                }
            }
            
            // Set up the dynamic IP address assignment.
            sendFormated("AT+UPSD=" PROFILE ",7,\"0.0.0.0\"\r\n");
            if (RESP_OK != waitFinalResp())
                return NOIP;
 
            do {
                if (config) {
                    apn      = *config ? config : "";
                    config  += strlen(config)+1;
                    username = *config ? config : "";
                    config  += strlen(config)+1;
                    password = *config ? config : "";
                    config  += strlen(config)+1;
                    if (!*config) config  = NULL;
                    TRACE("Testing APN Settings(\"%s\",\"%s\",\"%s\")\r\n", apn, username, password);
                }
                // Set up the APN
                sendFormated("AT+UPSD=" PROFILE ",1,\"%s\"\r\n", apn?apn:"");
                if (RESP_OK != waitFinalResp())
                    return NOIP;
                sendFormated("AT+UPSD=" PROFILE ",2,\"%s\"\r\n", username?username:"");
                if (RESP_OK != waitFinalResp())
                    return NOIP;
                sendFormated("AT+UPSD=" PROFILE ",3,\"%s\"\r\n", password?password:"");
                if (RESP_OK != waitFinalResp())
                    return NOIP;
                // try different Authentication Protocols
                // 0 = none 
                // 1 = PAP (Password Authentication Protocol)
                // 2 = CHAP (Challenge Handshake Authentication Protocol)
                for (int i = AUTH_NONE; i <= AUTH_CHAP && !ok; i ++) {
                    if ((auth == AUTH_DETECT) || (auth == i)) {
                        // Set up the Authentication Protocol
                        sendFormated("AT+UPSD=" PROFILE ",6,%d\r\n", i);
                        if (RESP_OK != waitFinalResp())
                            return NOIP;
                        // Activate the profile and make connection
                        sendFormated("AT+UPSDA=" PROFILE ",3\r\n");
                        if (RESP_OK == waitFinalResp(NULL,NULL,150*1000))
                            ok = true;
                    }
                }
            } while (config); // maybe use next setting ? 
            if (!ok) {
                ERROR("Your modem APN/password/username may be wrong\r\n");
                return NOIP;
            }
        }
        //Get local IP address
        sendFormated("AT+UPSND=" PROFILE ",0\r\n");
        if (RESP_OK != waitFinalResp(_cbUPSND, &_ip))
            return NOIP;
    }
    return _ip;
}

int MDMParser::_cbUDOPN(int type, const char* buf, int len, char* mccmnc)
{
    if ((type == TYPE_PLUS) && mccmnc) {
        if (sscanf(buf, "\r\n+UDOPN: 0,\"%[^\"]\"", mccmnc) == 1)
            ;
    }
    return WAIT;
}

int MDMParser::_cbCMIP(int type, const char* buf, int len, IP* ip)
{
    if ((type == TYPE_PLUS) && ip) {
        int a,b,c,d;
        if (sscanf(buf, "\r\n+CMIP: " IPSTR, &a,&b,&c,&d) == 4)
            *ip = IPADR(a,b,c,d);
    }
    return WAIT;
}
        
int MDMParser::_cbUPSND(int type, const char* buf, int len, int* act)
{
    if ((type == TYPE_PLUS) && act) {
        if (sscanf(buf, "\r\n+UPSND: %*d,%*d,%d", act) == 1)
            /*nothing*/;
    }
    return WAIT;
}

int MDMParser::_cbUPSND(int type, const char* buf, int len, IP* ip)
{
    if ((type == TYPE_PLUS) && ip) {
        int a,b,c,d;
        // +UPSND=<profile_id>,<param_tag>[,<dynamic_param_val>]
        if (sscanf(buf, "\r\n+UPSND: " PROFILE ",0,\"" IPSTR "\"", &a,&b,&c,&d) == 4)
            *ip = IPADR(a,b,c,d);
    }
    return WAIT;
}

int MDMParser::_cbUDNSRN(int type, const char* buf, int len, IP* ip)
{
    if ((type == TYPE_PLUS) && ip) {
        int a,b,c,d;
        if (sscanf(buf, "\r\n+UDNSRN: \""IPSTR"\"", &a,&b,&c,&d) == 4)
            *ip = IPADR(a,b,c,d);
    }
    return WAIT;
}

bool MDMParser::disconnect(void)
{
    if (_ip == NOIP)
        return true;
    INFO("Modem::disconnect\r\n");
    if (_dev.dev == DEV_LISA_C200) {
        // There something to do here
    } else { 
        sendFormated("AT+UPSDA=" PROFILE ",4\r\n");
        if (RESP_OK != waitFinalResp())
            return false;
    }
    _ip = NOIP;
    return true;
}

MDMParser::IP MDMParser::gethostbyname(const char* host)
{
    IP ip = NOIP; 
    int a,b,c,d;
    if (sscanf(host, IPSTR, &a,&b,&c,&d) == 4)
        ip = IPADR(a,b,c,d);
    else {
        sendFormated("AT+UDNSRN=0,\"%s\"\r\n", host);
        if (RESP_OK != waitFinalResp(_cbUDNSRN, &ip))
            return false;
    }
    return ip;
}

// ----------------------------------------------------------------
// sockets

int MDMParser::_cbUSOCR(int type, const char* buf, int len, int* socket)
{
    if ((type == TYPE_PLUS) && socket) {
        const char* p = strstr(buf,"+USOCR: "); 
        if (p)
            *socket = atoi(p+8);
    }
    return WAIT;
}

int MDMParser::socketSocket(IpProtocol ipproto, int port)
{
    TRACE("socketSocket(%d)\r\n", ipproto);
    if(ipproto == IPPROTO_TCP) {
        sendFormated("AT+USOCR=6\r\n");
    } else if ((ipproto == IPPROTO_UDP) && (port == -1)){
        sendFormated("AT+USOCR=17\r\n");
    } else if (ipproto == IPPROTO_UDP){
        sendFormated("AT+USOCR=17,%d\r\n", port);
    } else { // other types not supported
        return SOCKET_ERROR;
    }
    int socket = -1;
    if (RESP_OK != waitFinalResp(_cbUSOCR, &socket))
        return SOCKET_ERROR;
    if (!ISSOCKET(socket) || (_sockets[socket].state != SOCK_FREE))
        return SOCKET_ERROR;
    // successfull
    _sockets[socket].state = SOCK_CREATED;
    _sockets[socket].pending = 0;
    _sockets[socket].timeout_ms = TIMEOUT_BLOCKING;
    return socket;
}

bool MDMParser::socketConnect(int socket, const char * host, int port)
{
    TRACE("socketConnect(%d,%s,%d)\r\n", socket, host,port);
    IP ip = gethostbyname(host);
    if (ip == NOIP)
        return false;
    // connect to socket
    if (!ISSOCKET(socket) || (_sockets[socket].state != SOCK_CREATED))
        return false;
    sendFormated("AT+USOCO=%d,\"" IPSTR "\",%d\r\n", socket, IPNUM(ip), port);
    if (RESP_OK != waitFinalResp())
        return false;
    _sockets[socket].state = SOCK_CONNECTED;
    return true;
}

bool MDMParser::socketIsConnected(int socket)
{
    TRACE("socketIsConnected(%d)\r\n", socket);
    if (!ISSOCKET(socket))
        return false;
    return _sockets[socket].state == SOCK_CONNECTED;
}

bool MDMParser::socketSetBlocking(int socket, int timeout_ms)
{
    TRACE("socketSetBlocking(%d,%d)\r\n", socket, timeout_ms);
    if (!ISSOCKET(socket))
        return false;
    _sockets[socket].timeout_ms = timeout_ms;
    return true;
}

bool  MDMParser::socketClose(int socket)
{
    TRACE("socketClose(%d)\r\n", socket);
    if (!ISSOCKET(socket) || (_sockets[socket].state != SOCK_CONNECTED))
        return false;
    sendFormated("AT+USOCL=%d\r\n", socket);
    if (RESP_OK != waitFinalResp())
        return false;
    _sockets[socket].state = SOCK_CREATED;
    return true;
}

bool  MDMParser::socketFree(int socket)
{
    TRACE("socketFree(%d)\r\n", socket);
    socketClose(socket);
    if (!ISSOCKET(socket) || (_sockets[socket].state != SOCK_CREATED))
        return false;
    _sockets[socket].state = SOCK_FREE;
    return true;
}

#define USO_MAX_WRITE 1024 //!< maximum number of bytes to write to socket

int MDMParser::socketSend(int socket, const char * buf, int len)
{
    TRACE("socketSend(%d,,%d)\r\n", socket,len);
    int cnt = len;
    while (cnt > 0) {
        int blk = USO_MAX_WRITE;
        if (cnt < blk) 
            blk = cnt;
        sendFormated("AT+USOWR=%d,%d\r\n",socket,blk);
        if (RESP_PROMPT != waitFinalResp())
            return SOCKET_ERROR;
        RELAX_MS(50);
        send(buf, blk);
        if (RESP_OK != waitFinalResp()) 
            return SOCKET_ERROR;
        buf += blk;
        cnt -= blk;
    }
    return (len - cnt);
}

int MDMParser::socketSendTo(int socket, IP ip, int port, const char * buf, int len)
{
    TRACE("socketSendTo(%d," IPSTR ",%d,,%d)\r\n", socket, IPNUM(ip),port,len);
    int cnt = len;
    while (cnt > 0) {
        int blk = USO_MAX_WRITE;
        if (cnt < blk) 
            blk = cnt;
       sendFormated("AT+USOST=%d,\"" IPSTR "\",%d,%d\r\n",socket,IPNUM(ip),port,blk);
        if (RESP_PROMPT != waitFinalResp())
            return SOCKET_ERROR;
        RELAX_MS(50);
        send(buf, blk);
        if (RESP_OK != waitFinalResp())
            return SOCKET_ERROR;
        buf += blk;
        cnt -= blk;
    }
    return (len - cnt);
}

int MDMParser::socketReadable(int socket)
{
    TRACE("socketReadable(%d)\r\n", socket);
    if (!ISSOCKET(socket) || (_sockets[socket].state != SOCK_CONNECTED))
        return SOCKET_ERROR;
    // allow to receive unsolicited commands 
    waitFinalResp(NULL, NULL, 0);
    if (_sockets[socket].state != SOCK_CONNECTED)
        return SOCKET_ERROR;
    return _sockets[socket].pending;
}

int MDMParser::_cbUSORD(int type, const char* buf, int len, char* out)
{
    if ((type == TYPE_PLUS) && out) {
        int sz, sk;
        if ((sscanf(buf, "\r\n+USORD: %d,%d,", &sk, &sz) == 2) && 
            (buf[len-sz-2] == '\"') && (buf[len-1] == '\"')) {
            memcpy(out, &buf[len-1-sz], sz);
        }
    }
    return WAIT;
}

int MDMParser::socketRecv(int socket, char* buf, int len)
{
    int cnt = 0;
    TRACE("socketRecv(%d,,%d)\r\n", socket, len);
    if (!ISSOCKET(socket))
        return SOCKET_ERROR;
    memset(buf, '\0', len);
    Timer timer;
    timer.start();
    while (len) {
        int blk = MAX_SIZE; // still need space for headers and unsolicited  commands 
        if (_sockets[socket].pending < blk)
            blk = _sockets[socket].pending;
        if (len < blk) blk = len;
        if (blk) {
            sendFormated("AT+USORD=%d,%d\r\n",socket, blk);
            if (RESP_OK != waitFinalResp(_cbUSORD, buf)) {
                return SOCKET_ERROR;
            }
            len -= blk;
            cnt += blk;
            buf += blk;
            _sockets[socket].pending -= blk;
        } else if ((_sockets[socket].state == SOCK_CONNECTED) && 
                   !TIMEOUT(timer, _sockets[socket].timeout_ms)) {
            // allow to receive unsolicited commands 
            waitFinalResp(NULL, NULL, 10);
        } else {
            len = 0; // no more data and socket closed or timed-out
        }
    }
    return cnt;
}

int MDMParser::_cbUSORF(int type, const char* buf, int len, USORFparam* param)
{
    if ((type == TYPE_PLUS) && param) {
        int sz, sk, p, a,b,c,d;
        if ((sscanf(buf, "\r\n+USORF: %d,\""IPSTR"\",%d,%d,", 
            &sk,&a,&b,&c,&d,&p,&sz) == 7) && 
            (buf[len-sz-2] == '\"') && (buf[len-1] == '\"')) {
            memcpy(param->buf, &buf[len-1-sz], sz);
            param->ip = IPADR(a,b,c,d);
            param->port = p;
        }
    }
    return WAIT;
}

int MDMParser::socketRecvFrom(int socket, IP* ip, int* port, char* buf, int len)
{
    int cnt = 0;
    TRACE("socketRecvFrom(%d,,%d)\r\n", socket, len);
    if (!ISSOCKET(socket))
        return SOCKET_ERROR;
    memset(buf, '\0', len);
    Timer timer;
    timer.start();
    while (len) {
        int blk = MAX_SIZE; // still need space for headers and unsolicited commands 
        if (_sockets[socket].pending < blk)
            blk = _sockets[socket].pending;
        if (len < blk) blk = len;
        if (blk) {
            sendFormated("AT+USORF=%d,%d\r\n",socket, blk);
            USORFparam param;
            param.buf = buf;
            if (RESP_OK != waitFinalResp(_cbUSORF, &param)) {
                return SOCKET_ERROR;
            }
            *ip = param.ip;
            *port = param.port;
            len -= blk;
            cnt += blk;
            buf += blk;
            _sockets[socket].pending -= blk;
        } else if (!TIMEOUT(timer, _sockets[socket].timeout_ms)) {
            // allow to receive unsolicited commands 
            waitFinalResp(NULL, NULL, 10);
        } else
            len = 0; // no more data and socket closed or timed-out
    }
    timer.stop();
    timer.reset();
    return cnt;
}

// ----------------------------------------------------------------

int MDMParser::_cbCMGL(int type, const char* buf, int len, CMGLparam* param)
{ 
    if ((type == TYPE_PLUS) && param && param->num) {
        // +CMGL: <ix>,...
        int ix;
        if (sscanf(buf, "\r\n+CMGL: %d,", &ix) == 1)
        {
            *param->ix++ = ix;
            param->num--;
        }
    }
    return WAIT;
}

int MDMParser::smsList(const char* stat /*= "ALL"*/, int* ix /*=NULL*/, int num /*= 0*/) {
    sendFormated("AT+CMGL=\"%s\"\r\n", stat);
    CMGLparam param;
    param.ix = ix;
    param.num = num;
    if (RESP_OK != waitFinalResp(_cbCMGL, &param))
        return -1;
    return num - param.num;
}

bool MDMParser::smsSend(const char* num, const char* buf)
{
    sendFormated("AT+CMGS=\"%s\"\r\n",num);
    if (RESP_PROMPT != waitFinalResp(NULL,NULL,150*1000)) {
        return false;
    }
    send(buf, strlen(buf));
    const char ctrlZ = 0x1A;
    send(&ctrlZ, sizeof(ctrlZ));
    if (RESP_OK != waitFinalResp()) {
        return false;
    }
    return true;
}

bool MDMParser::smsDelete(int ix)
{
    sendFormated("AT+CMGD=%d\r\n",ix);
    if (RESP_OK != waitFinalResp()) {
        return false;
    }
    return true;
}

int MDMParser::_cbCMGR(int type, const char* buf, int len, CMGRparam* param)
{
    if (param) {
        if (type == TYPE_PLUS) {
            if (sscanf(buf, "\r\n+CMGR: \"%*[^\"]\",\"%[^\"]", param->num) == 1) {
            }
        } else if ((type == TYPE_UNKNOWN) && (buf[len-2] == '\r') && (buf[len-1] == '\n')) {
            memcpy(param->buf, buf, len-2);
            param->buf[len-2] = '\0';
        }
    }
    return WAIT;
}

bool MDMParser::smsRead(int ix, char* num, char* buf, int len)
{
    CMGRparam param;
    param.num = num;
    param.buf = buf;
    sendFormated("AT+CMGR=%d\r\n",ix);
    if (RESP_OK != waitFinalResp(_cbCMGR, &param)) {
        return false;
    }
    return true;
}
   
// ----------------------------------------------------------------
  
int MDMParser::_cbCUSD(int type, const char* buf, int len, char* resp)
{
    if ((type == TYPE_PLUS) && resp) {
        // +USD: \"%*[^\"]\",\"%[^\"]\",,\"%*[^\"]\",%d,%d,%d,%d,\"*[^\"]\",%d,%d"..);
        if (sscanf(buf, "\r\n+CUSD: %*d,\"%[^\"]\",%*d", resp) == 1) {
            /*nothing*/            
        }
    }
    return WAIT;
}  

bool MDMParser::ussdCommand(const char* cmd, char* buf)
{
    if (_dev.dev == DEV_LISA_C200) 
        return false;
    *buf = '\0';
    sendFormated("AT+CUSD=1,\"%s\"\r\n",cmd);
    if (RESP_OK != waitFinalResp(_cbCUSD, buf)) {
        return false;
    }
    return true;
}

// ----------------------------------------------------------------
   
bool MDMParser::delFile(const char* filename)
{
    sendFormated("AT+UDELFILE=\"%s\"\r\n", filename);
    if (RESP_OK != waitFinalResp())
        return false;
    return true;
}

int MDMParser::writeFile(const char* filename, const char* buf, int len)
{
    sendFormated("AT+UDWNFILE=\"%s\",%d\r\n", filename, len);
    if (RESP_PROMPT != waitFinalResp())
        return 0;
    send(buf, len);
    if (RESP_OK != waitFinalResp())
        return 0;
    return len;
}

int MDMParser::readFile(const char* filename, char* buf, int len)
{
    sendFormated("AT+URDFILE=\"%s\"\r\n", filename, len);
    URDFILEparam param;
    param.filename = filename;
    param.buf = buf; 
    param.sz = len; 
    param.len = 0;
    if (RESP_OK != waitFinalResp(_cbURDFILE, &param))
        return -1;
    return param.len;
}

int MDMParser::_cbURDFILE(int type, const char* buf, int len, URDFILEparam* param)
{
    if ((type == TYPE_PLUS) && param && param->filename && param->buf) {
        char filename[48];
        int sz;
        if ((sscanf(buf, "\r\n+URDFILE: \"%[^\"]\",%d,", filename, &sz) == 2) && 
            (0 == strcmp(param->filename, filename)) &&
            (buf[len-sz-2] == '\"') && (buf[len-1] == '\"')) {
            param->len = (sz < param->sz) ? sz : param->sz;
            memcpy(param->buf, &buf[len-1-sz], param->len);
        }
    }
    return WAIT;
}
  
// ----------------------------------------------------------------
bool MDMParser::setDebug(int level) 
{
#ifdef MDM_DEBUG
    if ((_debugLevel >= 0) && (level >= 0)) {
        _debugLevel = level;
        return true;
    }
#endif
    return false;
}

void MDMParser::dumpDevStatus(MDMParser::DevStatus* status, 
            _DPRINT dprint, void* param) 
{
    dprint(param, "Modem::devStatus\r\n");
    const char* txtDev[] = { "Unknown", "SARA-G350", "LISA-U200", "LISA-C200" };
    if (status->dev < sizeof(txtDev)/sizeof(*txtDev) && (status->dev != MDMParser::DEV_UNKNOWN))
        dprint(param, "  Device:       %s\r\n", txtDev[status->dev]);
    const char* txtLpm[] = { "Disabled", "Enabled", "Active" };
    if (status->lpm < sizeof(txtLpm)/sizeof(*txtLpm))
        dprint(param, "  Power Save:   %s\r\n", txtLpm[status->lpm]);
    const char* txtSim[] = { "Unknown", "Missing", "Pin", "Ready" };
    if (status->sim < sizeof(txtSim)/sizeof(*txtSim) && (status->sim != MDMParser::SIM_UNKNOWN))
        dprint(param, "  SIM:          %s\r\n", txtSim[status->sim]);
    if (*status->ccid)  
        dprint(param, "  CCID:         %s\r\n", status->ccid);
    if (*status->imei) 
        dprint(param, "  IMEI:         %s\r\n", status->imei);
    if (*status->imsi)  
        dprint(param, "  IMSI:         %s\r\n", status->imsi);
    if (*status->meid) 
        dprint(param, "  MEID:         %s\r\n", status->meid); // LISA-C
    if (*status->manu) 
        dprint(param, "  Manufacturer: %s\r\n", status->manu);
    if (*status->model)  
        dprint(param, "  Model:        %s\r\n", status->model);
    if (*status->ver)  
        dprint(param, "  Version:      %s\r\n", status->ver);
}

void MDMParser::dumpNetStatus(MDMParser::NetStatus *status,
            _DPRINT dprint, void* param)
{
    dprint(param, "Modem::netStatus\r\n");
    const char* txtReg[] = { "Unknown", "Denied", "None", "Home", "Roaming" };
    if (status->csd < sizeof(txtReg)/sizeof(*txtReg) && (status->csd != MDMParser::REG_UNKNOWN))
        dprint(param, "  CSD Registration:   %s\r\n", txtReg[status->csd]);
    if (status->psd < sizeof(txtReg)/sizeof(*txtReg) && (status->psd != MDMParser::REG_UNKNOWN))
        dprint(param, "  PSD Registration:   %s\r\n", txtReg[status->psd]);
    const char* txtAct[] = { "Unknown", "GSM", "Edge", "3G", "CDMA" };
    if (status->act < sizeof(txtAct)/sizeof(*txtAct) && (status->act != MDMParser::ACT_UNKNOWN))
        dprint(param, "  Access Technology:  %s\r\n", txtAct[status->act]);
    if (status->rssi) 
        dprint(param, "  Signal Strength:    %d dBm\r\n", status->rssi);
    if (status->ber) 
        dprint(param, "  Bit Error Rate:     %d\r\n", status->ber);
    if (*status->opr)  
        dprint(param, "  Operator:           %s\r\n", status->opr);
    if (status->lac != 0xFFFF)  
        dprint(param, "  Location Area Code: %04X\r\n", status->lac);
    if (status->ci != 0xFFFFFFFF)  
        dprint(param, "  Cell ID:            %08X\r\n", status->ci);
    if (*status->num)  
        dprint(param, "  Phone Number:       %s\r\n", status->num);
}

void MDMParser::dumpIp(MDMParser::IP ip,
            _DPRINT dprint, void* param) 
{
    if (ip != NOIP)
        dprint(param, "Modem:IP " IPSTR "\r\n", IPNUM(ip));
}
    
// ----------------------------------------------------------------
int MDMParser::_parseMatch(Pipe<char>* pipe, int len, const char* sta, const char* end)
{
    int o = 0;
    if (sta) {
        while (*sta) {
            if (++o > len)                  return WAIT;
            char ch = pipe->next();
            if (*sta++ != ch)               return NOT_FOUND;
        }
    }
    if (!end)                               return o; // no termination
    // at least any char
    if (++o > len)                      return WAIT;
    pipe->next();
    // check the end     
    int x = 0;
    while (end[x]) {
        if (++o > len)                      return WAIT;
        char ch = pipe->next();
        x = (end[x] == ch) ? x + 1 : 
            (end[0] == ch) ? 1 : 
                            0;
    }
    return o;
}

int MDMParser::_parseFormated(Pipe<char>* pipe, int len, const char* fmt)
{
    int o = 0;
    int num = 0;
    if (fmt) {
        while (*fmt) {
            if (++o > len)                  return WAIT;
            char ch = pipe->next();
            if (*fmt == '%') {
                fmt++;
                if (*fmt == 'd') { // numeric
                    fmt ++;
                    num = 0;
                    while (ch >= '0' && ch <= '9') {
                        num = num * 10 + (ch - '0'); 
                        if (++o > len)      return WAIT;
                        ch = pipe->next();
                    }
                }   
                else if (*fmt == 'c') { // char buffer (takes last numeric as length)
                    fmt ++;
                    while (num --) {
                        if (++o > len)      return WAIT;
                        ch = pipe->next();
                    }   
                }
                else if (*fmt == 's') {
                    fmt ++;
                    if (ch != '\"')         return NOT_FOUND;
                    do {
                        if (++o > len)      return WAIT;
                        ch = pipe->next();
                    } while (ch != '\"');
                    if (++o > len)          return WAIT;
                    ch = pipe->next();
                }
            }
            if (*fmt++ != ch)               return NOT_FOUND;
        }
    }
    return o; 
}

int MDMParser::_getLine(Pipe<char>* pipe, char* buf, int len)
{
    int unkn = 0;
    int sz = pipe->size();
    int fr = pipe->free();
    if (len > sz)
        len = sz;
    while (len > 0)
    {
        static struct { 
              const char* fmt;                              int type; 
        } lutF[] = {
            { "\r\n+USORD: %d,%d,\"%c\"",                   TYPE_PLUS       },
            { "\r\n+USORF: %d,\""IPSTR"\",%d,%d,\"%c\"",    TYPE_PLUS       },
            { "\r\n+URDFILE: %s,%d,\"%c\"",                 TYPE_PLUS       },
        };
        static struct { 
              const char* sta;          const char* end;    int type; 
        } lut[] = {
            { "\r\nOK\r\n",             NULL,               TYPE_OK         },
            { "\r\nERROR\r\n",          NULL,               TYPE_ERROR      },
            { "\r\n+CME ERROR:",        "\r\n",             TYPE_ERROR      }, 
            { "\r\n+CMS ERROR:",        "\r\n",             TYPE_ERROR      },
            { "\r\nRING\r\n",           NULL,               TYPE_RING       },
            { "\r\nCONNECT\r\n",        NULL,               TYPE_CONNECT    },
            { "\r\nNO CARRIER\r\n",     NULL,               TYPE_NOCARRIER  },
            { "\r\nNO DIALTONE\r\n",    NULL,               TYPE_NODIALTONE },
            { "\r\nBUSY\r\n",           NULL,               TYPE_BUSY       },
            { "\r\nNO ANSWER\r\n",      NULL,               TYPE_NOANSWER   },
            { "\r\n+",                  "\r\n",             TYPE_PLUS       },
            { "\r\n@",                  NULL,               TYPE_PROMPT     }, // Sockets
            { "\r\n>",                  NULL,               TYPE_PROMPT     }, // SMS
            { "\n>",                    NULL,               TYPE_PROMPT     }, // File
        };
        for (int i = 0; i < sizeof(lutF)/sizeof(*lutF); i ++) {
            pipe->set(unkn);
            int ln = _parseFormated(pipe, len, lutF[i].fmt);
            if (ln == WAIT && fr)                       
                return WAIT;
            if ((ln != NOT_FOUND) && (unkn > 0))  
                return TYPE_UNKNOWN | pipe->get(buf, unkn);
            if (ln > 0)
                return lutF[i].type  | pipe->get(buf, ln);
        }
        for (int i = 0; i < sizeof(lut)/sizeof(*lut); i ++) {
            pipe->set(unkn);
            int ln = _parseMatch(pipe, len, lut[i].sta, lut[i].end);
            if (ln == WAIT && fr)                       
                return WAIT;
            if ((ln != NOT_FOUND) && (unkn > 0))  
                return TYPE_UNKNOWN | pipe->get(buf, unkn);
            if (ln > 0)
                return lut[i].type | pipe->get(buf, ln);
        }
        // UNKNOWN
        unkn ++;
        len--;
    }
    return WAIT;
}

// ----------------------------------------------------------------
// Serial Implementation 
// ----------------------------------------------------------------

/*! Helper Dev Null Device 
    Small helper class used to shut off stderr/stdout. Sometimes stdin/stdout
    is shared with the serial port of the modem. Having printfs inbetween the 
    AT commands you cause a failure of the modem.
*/
class DevNull : public Stream {
public: 
    DevNull() : Stream(_name+1) { }             //!< Constructor
    void claim(const char* mode, FILE* file) 
        { freopen(_name, mode, file); }         //!< claim a stream
protected:
    virtual int _getc()         { return EOF; } //!< Nothing
    virtual int _putc(int c)    { return c; }   //!< Discard
    static const char* _name;                   //!< File name
};
const char* DevNull::_name = "/null";  //!< the null device name
static      DevNull null;              //!< the null device

MDMSerial::MDMSerial(PinName tx /*= MDMTXD*/, PinName rx /*= MDMRXD*/, 
            int baudrate /*= MDMBAUD*/,
#if DEVICE_SERIAL_FC
            PinName rts /*= MDMRTS*/, PinName cts /*= MDMCTS*/, 
#endif
            int rxSize /*= 256*/, int txSize /*= 128*/) : 
            SerialPipe(tx, rx, rxSize, txSize) 
{
    if (rx == USBRX) 
        null.claim("r", stdin);
    if (tx == USBTX) {
        null.claim("w", stdout);
        null.claim("w", stderr);
#ifdef MDM_DEBUG
        _debugLevel = -1;
#endif
    }
#ifdef TARGET_UBLOX_C027
    _onboard = (tx == MDMTXD) && (rx == MDMRXD);
    if (_onboard)
       c027_mdm_powerOn(false);
#endif
    baud(baudrate);
#if DEVICE_SERIAL_FC
    if ((rts != NC) || (cts != NC))
    {
        Flow flow = (cts == NC) ? RTS :
                    (rts == NC) ? CTS : RTSCTS ;
        set_flow_control(flow, rts, cts);
        if (cts != NC) _dev.lpm = LPM_ENABLED;
    }
#endif
}

MDMSerial::~MDMSerial(void)
{
    powerOff();
#ifdef TARGET_UBLOX_C027
    if (_onboard)
        c027_mdm_powerOff();
#endif
}

int MDMSerial::_send(const void* buf, int len)   
{ 
    return put((const char*)buf, len, true/*=blocking*/);
}

int MDMSerial::getLine(char* buffer, int length)
{
    return _getLine(&_pipeRx, buffer, length);
}

// ----------------------------------------------------------------
// USB Implementation 
// ----------------------------------------------------------------

#ifdef HAVE_MDMUSB
MDMUsb::MDMUsb(void)                             
{ 
#ifdef MDM_DEBUG
    _debugLevel = 1;
#endif    
#ifdef TARGET_UBLOX_C027
    _onboard = true;
    c027_mdm_powerOn(true);
#endif
}

MDMUsb::~MDMUsb(void)
{
    powerOff();
#ifdef TARGET_UBLOX_C027
    if (_onboard)
        c027_mdm_powerOff();
#endif
}

int MDMUsb::_send(const void* buf, int len)      { return 0; }

int MDMUsb::getLine(char* buffer, int length)    { return NOT_FOUND; }

#endif