support library for C027 helper functions for Buffer Pipes, Buffered Serial Port (rtos capable) and GPS parsing. It includes modem APIs for USSD, SMS and Sockets.

Dependents:   HTTPClient_Cellular_HelloWorld Cellular_HelloMQTT MbedSmartRestMain Car_Bon_car_module ... more

This library is intended to be used with u-blox products such as the C027 or a shield with u-blox cellular and GPS modules like the cellular and positioning shield from Embedded Artist.

For 2G/GSM and 3G/UMTS you need to:

  • have a SIM card and know its PIN number
  • need to know you network operators APN setting These setting should be passed to the connect or init and join functions. You can also extend the APN database in MDMAPN.h.

For CDMA products you need to make sure that you have provisioned and activated the modem with either Sprint or Verizon.

MDM.cpp

Committer:
mazgch
Date:
2014-04-08
Revision:
27:e35f2118368f
Parent:
26:07be5faf8925
Child:
28:4d9509e3b1cf

File content as of revision 27:e35f2118368f:

#include "mbed.h"
#include <ctype.h>
#include "MDM.h"

#define TRACE           (0)?:printf
//#define DEBUG         // enable this for AT command debugging
#define PROFILE         "0"   // this is the psd profile used
#define MAX_SIZE        256  // max expected messages
// some helper 
#define ISSOCKET(s)     (((s) >= 0) && ((s) < (sizeof(_sockets)/sizeof(*_sockets))))
#define IPSTR           "%d.%d.%d.%d"
#define IPNUM(addr)     (addr>>24)&0xff, (addr>>16)&0xff, (addr>>8)&0xff, addr&0xff
#define IPADR(a,b,c,d)  ((a<<24) | (b<<16) | (c<<8) | d)

#ifdef DEBUG
void dump(const char* buf, int len)
{
    while (len --) {
        char ch = *buf++;
        if      (ch == '\r') printf("\\r");
        else if (ch == '\n') printf("\\n");
        else if (ch >= 0x20) printf("%c", ch);
        else                 printf("\\x%02x", ch);
    }
}
#endif        

MDMParser::MDMParser(void)
{
    
    // device info
    _model   = MODEL_UNKNOWN;
    _sim     = SIM_UNKNOWN;
    *_ccid  = '\0';
    *_imsi  = '\0';
    *_imei  = '\0';
    // network info
    _net     = NET_UNKNOWN;
    _act     = ACT_UNKNOWN;
    _rssi    = 0;
    *_num    = '\0';
    *_opr    = '\0';
    // data network info
    _ip      = 0;
    for (int socket = 0; socket < sizeof(_sockets)/sizeof(*_sockets); socket++) {
        _sockets[socket].state = SOCK_FREE;
        _sockets[socket].pending = 0;
    }
}

int MDMParser::send(const char* buf, int len)
{
#ifdef DEBUG
    printf("   send \"");
    dump(buf,len);
    printf("\"\n");
#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];
    Timer timer;
    timer.start();
    do {
        int ret = getLine(buf, sizeof(buf));
#ifdef DEBUG
        if ((ret != WAIT) && (ret != NOT_FOUND))
        {
            printf("   line %06X \"", ret);
            dump(buf, LENGTH(ret));
            printf("\"\n");
        }
#endif        
        if ((ret != WAIT) && (ret != NOT_FOUND))
        {
            int type = TYPE(ret);
            if (type == TYPE_OK)        return OK;
            if (type == TYPE_ERROR)     return ERROR;
            if (type == TYPE_PROMPT)    return PROMPT;
            // handle unsolicited commands here
            if (type == TYPE_PLUS) {
                const char* cmd = buf+3;
                int a, b, c, d;
                char s[32];

                // +CSQ: <rssi>,<qual>
                if (sscanf(cmd, "CSQ: %d,%d",&a,&b) == 2) {
                    if (a != 99) _rssi = -113 + 2*a;  // 0: -113 1: -111 ... 30: -53 dBm with 2 dBm steps
                    //if (b != 99) int qual = b;  // 
                // 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\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\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\n", a);
                    _sockets[a].state = SOCK_CREATED/*=CLOSED*/;
                }                
                if (_model == MODEL_LISA_C200) {
                    // CDMA Specific -------------------------------------------
                    // +CREG: <n><SID>,<NID>,<stat>
                    if (sscanf(cmd, "CREG: %*d,%*d,%*d,%d",&a) == 1) {
                        if      (a == 0) _net = NET_NONE;     // not registered, home network
                        else if (a == 1) _net = NET_HOME;     // registered, home network
                        else if (a == 2) _net = NET_NONE;     // not registered, but MT is currently searching a new operator to register to
                        else if (a == 3) _net = NET_DENIED;   // registration denied
                        else if (a == 5) _net = NET_ROAMING;  // registered, roaming
                        _act = ACT_CDMA;
                    // +CSS: <mode>[,<format>,<oper>[,<AcT>]]
                    } else if (sscanf(cmd, "CSS %*c,%2s,%*d",s) == 1) {
                        //_net = (strcmp("Z", s) == 0) ? NET_UNKNOWN : NET_HOME;
                    // +CMIP: xxx.xxx.xxx.xxx
                    } else if (sscanf(cmd, "CMIP: " IPSTR, &a,&b,&c,&d) == 4) {
                        _ip = IPADR(a,b,c,d);
                    }
                } else {
                    // GSM/UMTS Specific -------------------------------------------
                    // +CREG: <n>, <stat>[,<lac>,<ci>[,AcT]]
                    b = 255;
                    if (sscanf(cmd, "CREG: %*d,%d,%*d,%d",&a,&b) >= 1) {
                        // network status
                        if      (a == 0) _net = NET_NONE;     // 0: not registered, home network
                        else if (a == 1) _net = NET_HOME;     // 1: registered, home network
                        else if (a == 2) _net = NET_NONE;     // 2: not registered, but MT is currently searching a new operator to register to
                        else if (a == 3) _net = NET_DENIED;   // 3: registration denied
                        else if (a == 4) _net = NET_UNKNOWN;  // 4: unknown
                        else if (a == 5) _net = NET_ROAMING;  // 5: registered, roaming
                        // access technology
                        if      (b == 0) _act = ACT_GSM;      // 0: GSM
                        else if (b == 1) _act = ACT_GSM;      // 1: GSM COMPACT
                        else if (b == 2) _act = ACT_UTRAN;    // 2: UTRAN
                        else if (b == 3) _act = ACT_EDGE;     // 3: GSM with EDGE availability
                        else if (b == 4) _act = ACT_UTRAN;    // 4: UTRAN with HSDPA availability
                        else if (b == 5) _act = ACT_UTRAN;    // 5: UTRAN with HSUPA availability
                        else if (b == 6) _act = ACT_UTRAN;    // 6: UTRAN with HSDPA and HSUPA availability
                    // +COPS: <mode>[,<format>,<oper>[,<AcT>]]
                    } else if (sscanf(cmd, "COPS: %*d,%*d,\"%[^\"]\",%d",s,&b) >= 1) {
                        strcpy(_opr,s);
                        if      (a == 0) _act = ACT_GSM;      // 0: GSM, 
                        else if (a == 2) _act = ACT_UTRAN;    // 2: UTRAN
                    // +CPIN: <code>
                    } else if (sscanf(cmd, "CPIN: %7s",s) == 1) {
                        _sim = (strcmp("READY", s) == 0) ? SIM_READY : SIM_UNKNOWN;
                    // +CNUM: <code>
                    } else if (sscanf(cmd, "CNUM: \"My Number\",\"%31[^\"]\",%d", s, &a) == 2) {
                        if ((a == 129) || (a == 145)) strncpy(_num, s, sizeof(_num));
                    // +UPSND=<profile_id>,<param_tag>[,<dynamic_param_val>]
                    } else if (sscanf(cmd, "UPSND: " PROFILE ",0,\"" IPSTR "\"", &a,&b,&c,&d) == 4) {
                        _ip = IPADR(a,b,c,d);
                    // +UUPSDD: <profile_id> 
                    } else if (sscanf(cmd, "UUPSDD: %d",&a) == 1) {
                        if (*PROFILE == a) _ip = 0;
                    }
                }
            }
            if (cb) {
                int len = LENGTH(ret);
                int ret = cb(type, buf, len, param);
                if (WAIT != ret)
                    return ret; 
            }
        }
        // relax a bit
        wait_ms(10); 
    }
    while (timer.read_ms() < timeout_ms);
    timer.stop();
    timer.reset();
    return WAIT;
}

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

int MDMParser::_cbATI(int type, const char* buf, int len, Model* model)
{
    if ((type == TYPE_UNKNOWN) && model) {
        if (strstr(buf, "SARA-G350")) {
            *model = MODEL_SARA_G350;
            TRACE("Identified Model: SARA-G350 2G\n");
        } else if (strstr(buf, "LISA-U200")) {
            *model = MODEL_LISA_U200;
            TRACE("Identified Model: LISA-U200 2G/3G\n");
        } else if (strstr(buf, "LISA-C200")) {
            *model= MODEL_LISA_C200;
            TRACE("Identified Model: LISA-C200 CDMA\n");
        }
    }
    return WAIT;
}

bool MDMParser::init(const char* pin)
{
    for(int i = 0; i < 5; i++) {
        // check interface and disable local echo
        sendFormated("AT\r\n");
        if(OK == waitFinalResp())
            break;
    }
    // echo off
    sendFormated("AT E0\r\n");
    if(OK != waitFinalResp())
        return false;
    // enable verbose error messages
    sendFormated("AT+CMEE=2\r\n");
    if(OK != waitFinalResp())
        return false;
    // set baud rate
    sendFormated("AT+IPR=115200\r\n");
    if (OK != waitFinalResp())
        return false;
    wait_ms(40);
    // identify the module 
    sendFormated("ATI\r\n");
    if (OK != waitFinalResp(_cbATI, &_model))
        return false;
    if (_model == MODEL_UNKNOWN)
        return false;
    // model specific init
    if (_model == MODEL_LISA_C200) {
        // disable flow control
        sendFormated("AT+IFC=0,0\r\n");
        if (OK != waitFinalResp())
            return false;
        // Return the pseudo ESN or MEID
        sendFormated("AT+GSN\r\n");
        if (OK != waitFinalResp(_cbGSN, _imei))
            return false;
    } else {
        // disable flow control
        sendFormated("AT&K0\r\n");
        if (OK != waitFinalResp())
            return false;
        // enable power saving
        sendFormated("AT+UPSV=1\r\n");
        if (OK != waitFinalResp())
            return false;
        // enable the network identification feature 
        if (_model == MODEL_LISA_U200) {
            sendFormated("AT+UGPIOC=20,2\r\n");
            if (OK != waitFinalResp())
                return false;
        } else {
            sendFormated("AT+UGPIOC=16,2\r\n");
            if (OK != waitFinalResp())
                return false;
        }
        // Enter PIN if needed
        if (pin) {
            sendFormated("AT+CPIN=%s\r\n", pin);
            if (OK != waitFinalResp())
                return false;
        }
        // check the sim card
        for (int i = 0; i < 5; i++) {
            sendFormated("AT+CPIN?\r\n");
            int ret = waitFinalResp();
            if ((OK != ret) && (ERROR != ret))
                return false;
            if (_sim != SIM_UNKNOWN)
                break;
            wait_ms(1000);
        }
        if (_sim != SIM_READY)
            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 (OK != waitFinalResp(_cbCCID, _ccid))
            return false;
        // Returns the product serial number, IMEI (International Mobile Equipment Identity)
        sendFormated("AT+CGSN\r\n");
        if (OK != waitFinalResp(_cbCGSN, _imei))
            return false;
        // Setup SMS in text mode 
        sendFormated("AT+CMGF=1\r\n");
        if (OK != waitFinalResp())
            return false;
        // Configure New message indication
        //sendFormated("AT+CNMI=2,1,0,0,0\r\n");
        //if (OK != waitFinalResp())
        //    return false;
            
    } 
    // Request IMSI (International Mobile Subscriber Identification)
    sendFormated("AT+CIMI\r\n");
    if (OK != waitFinalResp(_cbCIMI, _imsi))
        return false;
    return true; 
}

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\n", ccid);
    }
    return WAIT;
}

int MDMParser::_cbGSN(int type, const char* buf, int len, char* imei)
{
    if ((type == TYPE_UNKNOWN) && imei){
        if (sscanf(buf, "\r\n%[^\r]\r\n%*[^\r]\r\n", imei) == 1)
            TRACE("Got IMEI: %s\n", imei);
    }
    return WAIT;
}

int MDMParser::_cbCGSN(int type, const char* buf, int len, char* imei)
{
    if ((type == TYPE_UNKNOWN) && imei){
        if (sscanf(buf, "\r\n%[^\r]\r\n", imei) == 1)
            TRACE("Got IMEI: %s\n", imei);
    }
    return WAIT;
}

int MDMParser::_cbCIMI(int type, const char* buf, int len, char* imsi)
{
    if ((type == TYPE_UNKNOWN) && imsi) {
        if (sscanf(buf, "\r\n%[^\r]\r\n", imsi) == 1)
            TRACE("Got IMSI: %s\n", imsi);
    }
    return WAIT;
}

bool MDMParser::checkNetStatus(Status* info /*= NULL*/)
{
    // check registration
    sendFormated("AT+CREG?\r\n");
    if (OK != waitFinalResp())
        return false;
    if ((_net != NET_ROAMING) && (_net != NET_HOME))
        return false;
    // check modem specific status messages 
    if (_model == MODEL_LISA_C200) {
        sendFormated("AT+CSS?\r\n");
        if (OK != waitFinalResp())
            return false;
        if ((_net != NET_ROAMING) && (_net != NET_HOME))
            return false;
    } else {
        // check operator selection 
        sendFormated("AT+COPS?\r\n");
        if (OK != waitFinalResp())
            return false;
        // Returns the MSISDNs related to this subscriber
        sendFormated("AT+CNUM\r\n");
        if (OK != waitFinalResp())
            return false;
    }  
    // Returns the signal strength indication
    sendFormated("AT+CSQ\r\n");
    if (OK != waitFinalResp())
        return false;
    if (info) {
        info->num = _num;
        info->opr = _opr;
        info->rssi = _rssi;
        info->net = _net;
        info->act = _act;
    }
    return true;
}

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

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

MDMParser::IP MDMParser::strToIp(const char* str)
{
    IP ip = 0;
    char* p = (char*)str;
    for(int i = 0; i < 4; i++) {
        ip |= atoi(p);
        p = strchr(p, '.');
        if (p == NULL) {
            break;
        }
        ip <<= 8;
        p++;
    }
    return ip;
}

bool MDMParser::join(const char* apn, const char* user /*= NULL*/, const char* password /*= NULL*/)
{
    if (_model == MODEL_LISA_C200) {
#ifdef TODO // TODO implement 
        // enable the 
        sendFormated("AT$QCMIPEP=1\r\n");
        if (OK != waitFinalResp())
            return false;
        //Get local IP address
        sendFormated("AT+CMIP?\r\n");
        if (OK != waitFinalResp())
            return false;
#endif
    } else { 
        // check gprs attach status 
        sendFormated("AT+CGATT?\r\n");
        if (OK != waitFinalResp())
            return false;
         // Set up the APN
        if (apn) {
            sendFormated("AT+UPSD=" PROFILE ",1,\"%s\"\r\n", apn);
            if (OK != waitFinalResp())
                return false;
        }
        if (user) {    
            sendFormated("AT+UPSD=" PROFILE ",2,\"%s\"\r\n", user);
            if (OK != waitFinalResp())
                return false;
        }
        if (password) {
            sendFormated("AT+UPSD=" PROFILE ",3,\"%s\"\r\n", password);
            if (OK != waitFinalResp())
                return false;
        }
        // Set up the dynamic IP address assignment.
        sendFormated("AT+UPSD=" PROFILE ",7,\"0.0.0.0\"\r\n");
        if (OK != waitFinalResp())
            return false;
        // Activate the profile and make connection
        sendFormated("AT+UPSDA=" PROFILE ",3\r\n");
        if (OK != waitFinalResp())
            return false;
        //Get local IP address
        sendFormated("AT+UPSND=" PROFILE ",0\r\n");
        if (OK != waitFinalResp())
            return false;
    }
    if (!_ip)
        return false;
    TRACE("Got IP address: " IPSTR "\n",  IPNUM(_ip));
    return true;
}

bool MDMParser::disconnect(void)
{
    if (_ip == 0)
        return true;
    if (_model == MODEL_LISA_C200) {
#ifdef TODO // TODO implement 
        sendFormated("AT$QCMIPEP=0\r\n");
#endif
    } else { 
        sendFormated("AT+UPSDA=" PROFILE ",4\r\n");
    }
    if (OK != waitFinalResp())
        return false;
    _ip = 0;
    return true;
}

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

bool MDMParser::gethostbyname(const char* host, IP* ip)
{
    char ipstr[16];
    IP addr = strToIp(host);
    *ip = 0;
    snprintf(ipstr, sizeof(ipstr), IPSTR, IPNUM(addr));
    if (strcmp(ipstr, host) == 0) {
        *ip = addr;
        return true;
    }
    sendFormated("AT+UDNSRN=0,\"%s\"\r\n", host);
    if (OK != waitFinalResp(_cbUDNSRN, ip))
        return false;
    return *ip != 0;
}

// ----------------------------------------------------------------
// 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)
{
    const char* cmd;
    if(ipproto == IPPROTO_TCP) {
        cmd = "AT+USOCR=6\r\n";
    } else if(ipproto == IPPROTO_UDP) {
        cmd = "AT+USOCR=17\r\n";
    } else { // other types not supported
        return SOCKET_ERROR;
    }
    sendFormated(cmd);
    int socket = -1;
    if (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;
    return socket;
}

bool MDMParser::socketConnect(int socket, const char * host, int port)
{
    IP ip;
    if (!gethostbyname(host, &ip))
        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 (OK != waitFinalResp())
        return false;
    _sockets[socket].state = SOCK_CONNECTED;
    return true;
}

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

bool  MDMParser::socketFree(int socket)
{
    socketClose(socket);
    if (!ISSOCKET(socket) || (_sockets[socket].state != SOCK_CREATED))
        return false;
    _sockets[socket].state = SOCK_FREE;
    return true;
}

int MDMParser::socketSend(int socket, const char * buf, int len)
{
    if(len > 0) {
        sendFormated("AT+USOWR=%d,%d\r\n",socket,len);
        if (PROMPT != waitFinalResp())
            return SOCKET_ERROR;
        wait_ms(50);
        send(buf, len);
        if (OK != waitFinalResp()) 
            return SOCKET_ERROR;
    }
    return len;
}

int MDMParser::socketSendTo(int socket, IP ip, int port, const char * buf, int len)
{
    if(len > 0) {
        sendFormated("AT+USOWR=%d,\"" IPSTR "\",%d,%d\r\n",socket,IPNUM(ip),port,len);
        if (PROMPT != waitFinalResp())
            return SOCKET_ERROR;
        wait_ms(50);
        send(buf, len);
        if (OK != waitFinalResp()) 
            return SOCKET_ERROR;
    }
    return len;
}

int MDMParser::socketReadable(int 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;
    if (!ISSOCKET(socket))
        return SOCKET_ERROR;
    memset(buf, '\0', len);
    while (len) {
        int blk = MAX_SIZE - 64; // still need space for headers and unsolicited  commands 
        if (_sockets[socket].state != SOCK_CONNECTED)
            return cnt ? cnt : SOCKET_ERROR;
        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 (OK != waitFinalResp(_cbUSORD, buf)) {
                return cnt ? cnt : SOCKET_ERROR;
            }
            len -= blk;
            cnt += blk;
            buf += blk;
            _sockets[socket].pending -= blk;
        } else {
            // allow to receive unsolicited commands 
            waitFinalResp(NULL, NULL, 10);
        }
    }
    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, char* buf, int len, IP* ip)
{
    int cnt = 0;
    if (!ISSOCKET(socket))
        return SOCKET_ERROR;
    memset(buf, '\0', len);
    while (len) {
        int blk = MAX_SIZE - 64; // still need space for headers and unsolicited commands 
        if (_sockets[socket].state != SOCK_CONNECTED)
            return cnt ? cnt : SOCKET_ERROR;
        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 (OK != waitFinalResp(_cbUSORF, &param)) {
                return cnt ? cnt : SOCKET_ERROR;
            }
            *ip = param.ip;
            //*port = param.port;
            len -= blk;
            cnt += blk;
            buf += blk;
            _sockets[socket].pending -= blk;
        } else {
            // allow to receive unsolicited commands 
            waitFinalResp(NULL, NULL, 10);
        }
    }
    return cnt;
}

// ----------------------------------------------------------------
#if 0
void _cbCMGL()
{ 
    //
}

int MDMParser::smsCount(void)
{
    int num = 0;
    sendFormated("AT+CMGL=ALL\r\n");
    if (OK != waitFinalResp(_cbCMGL,&num)) {
        return false;
    }
    return true;
}
#endif

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

bool MDMParser::smsDelete(int ix)
{
    sendFormated("AT+CMGD=%d\r\n",ix);
    if (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 (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;
}  
int MDMParser::ussdCommand(const char* cmd, char* buf, int len)
{
    *buf = '\0';
    sendFormated("AT+CUSD=1,\"%s\"\r\n",cmd);
    if (OK != waitFinalResp(_cbCUSD, buf)) {
        return -1;
    }
    return strlen(buf);
}
       
// ----------------------------------------------------------------
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
    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();
                    }   
                }
            }
            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       },
        };
        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
        };
        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 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 pipe->get(buf, unkn);
            if (ln > 0)
                return lut[i].type | pipe->get(buf, ln);
        }
        // UNKNOWN
        unkn ++;
        len--;
    }
    return WAIT;
}

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

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*/) : 
#if DEVICE_SERIAL_FC
            SerialPipe(tx, rx, rts, cts, rxSize, txSize)
#else
            SerialPipe(tx, rx, rxSize, txSize)
#endif
{
    baud(baudrate);
}

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
// TODO properly implement with USB 
MDMUsb::MDMUsb(void)                             { }
int MDMUsb::_send(const void* buf, int len)      { return len; }
int MDMUsb::getLine(char* buffer, int length)    { return NOT_FOUND; }
#endif