Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Dependents: MbedSmartRestMain MbedSmartRestMain
Fork of C027_Support by
Revision 134:d84232916eac, committed 2017-09-15
- Comitter:
- xinlei
- Date:
- Fri Sep 15 10:12:03 2017 +0000
- Parent:
- 133:69700ba3fa72
- Parent:
- 122:98e685503727
- Commit message:
- new apns
Changed in this revision
--- a/MDM.cpp Tue Sep 12 14:17:31 2017 +0000
+++ b/MDM.cpp Fri Sep 15 10:12:03 2017 +0000
@@ -66,6 +66,7 @@
{
if (_debugLevel >= level)
{
+// ::printf("_debugPrint %d %d\r\n", _debugLevel, level);
va_list args;
va_start (args, format);
if (color) ::printf(color);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/MDM.cpp.orig Fri Sep 15 10:12:03 2017 +0000
@@ -0,0 +1,1701 @@
+#include "mbed.h"
+#include "MDM.h"
+#ifdef TARGET_UBLOX_C027
+ #include "C027_api.h"
+#endif
+#include "MDMAPN.h"
+
+#define PROFILE "0" //!< this is the psd profile used
+#define MAX_SIZE 128 //!< max expected messages
+// num sockets
+#define NUMSOCKETS (sizeof(_sockets)/sizeof(*_sockets))
+//! test if it is a socket is ok to use
+#define ISSOCKET(s) (((s) >= 0) && ((s) < NUMSOCKETS) && (_sockets[s].handle != SOCKET_ERROR))
+//! 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))
+//! helper to make sure that lock unlock pair is always balaced
+#define LOCK() { lock()
+//! helper to make sure that lock unlock pair is always balaced
+#define UNLOCK() } unlock()
+
+#ifdef MDM_DEBUG
+ #if 1 // colored terminal output using ANSI escape sequences
+ #define COL(c) "\033[" c
+ #else
+ #define COL(c)
+ #endif
+ #define DEF COL("39m")
+ #define BLA COL("30m")
+ #define RED COL("31m")
+ #define GRE COL("32m")
+ #define YEL COL("33m")
+ #define BLU COL("34m")
+ #define MAG COL("35m")
+ #define CYA COL("36m")
+ #define WHY COL("37m")
+
+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");
+}
+
+void MDMParser::_debugPrint(int level, const char* color, const char* format, ...)
+{
+ if (_debugLevel >= level)
+ {
+ va_list args;
+ va_start (args, format);
+ if (color) ::printf(color);
+ ::vprintf(format, args);
+ if (color) ::printf(DEF);
+ va_end (args);
+ }
+}
+
+ #define ERROR(...) _debugPrint(0, RED, __VA_ARGS__)
+ #define INFO(...) _debugPrint(1, GRE, __VA_ARGS__)
+ #define TRACE(...) _debugPrint(2, DEF, __VA_ARGS__)
+ #define TEST(...) _debugPrint(3, CYA, __VA_ARGS__)
+
+#else
+
+ #define ERROR(...) (void)0 // no tracing
+ #define TEST(...) (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));
+ for (int socket = 0; socket < NUMSOCKETS; socket ++)
+ _sockets[socket].handle = SOCKET_ERROR;
+#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" DEF :
+ (type == TYPE_TEXT) ? MAG "TXT" DEF :
+ (type == TYPE_OK ) ? GRE "OK " DEF :
+ (type == TYPE_ERROR) ? RED "ERR" DEF :
+ (type == TYPE_PLUS) ? CYA " + " DEF :
+ (type == TYPE_PROMPT) ? BLU " > " DEF :
+ "..." ;
+ ::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)) {
+ int socket = _findSocket(a);
+ TRACE("Socket %d: handle %d has %d bytes pending\r\n", socket, a, b);
+ if (socket != SOCKET_ERROR)
+ _sockets[socket].pending = b;
+ // +UUSORF: <socket>,<length>
+ } else if ((sscanf(cmd, "UUSORF: %d,%d", &a, &b) == 2)) {
+ int socket = _findSocket(a);
+ TRACE("Socket %d: handle %d has %d bytes pending\r\n", socket, a, b);
+ if (socket != SOCKET_ERROR)
+ _sockets[socket].pending = b;
+ // +UUSOCL: <socket>
+ } else if ((sscanf(cmd, "UUSOCL: %d", &a) == 1)) {
+ int socket = _findSocket(a);
+ TRACE("Socket %d: handle %d closed by remote host\r\n", socket, a);
+ if ((socket != SOCKET_ERROR) && _sockets[socket].connected)
+ _sockets[socket].connected = false;
+ }
+ 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
+ wait_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;
+ LOCK();
+ 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, LEON-G2 >5ms
+ pin = 0; ::wait_ms(150);
+ pin = 1; ::wait_ms(100);
+
+ // purge any messages
+ purge();
+
+ // check interface
+ sendFormated("AT\r\n");
+ int r = waitFinalResp(NULL,NULL,1000);
+ if(RESP_OK == r) break;
+ }
+ if (i < 0) {
+ ERROR("No Reply from Modem\r\n");
+ goto failure;
+ }
+ }
+ _init = true;
+
+ INFO("Modem::init\r\n");
+ // echo off
+ sendFormated("AT E0\r\n");
+ if(RESP_OK != waitFinalResp())
+ goto failure;
+ // enable verbose error messages
+ sendFormated("AT+CMEE=2\r\n");
+ if(RESP_OK != waitFinalResp())
+ goto failure;
+ // set baud rate
+ sendFormated("AT+IPR=115200\r\n");
+ if (RESP_OK != waitFinalResp())
+ goto failure;
+ // wait some time until baudrate is applied
+ wait_ms(200); // SARA-G > 40ms
+ // identify the module
+ sendFormated("ATI\r\n");
+ if (RESP_OK != waitFinalResp(_cbATI, &_dev.dev))
+ goto failure;
+ if (_dev.dev == DEV_UNKNOWN)
+ goto failure;
+ // device specific init
+ if (_dev.dev == DEV_LISA_C200) {
+ // get the manufacturer
+ sendFormated("AT+GMI\r\n");
+ if (RESP_OK != waitFinalResp(_cbString, _dev.manu))
+ goto failure;
+ // get the model identification
+ sendFormated("AT+GMM\r\n");
+ if (RESP_OK != waitFinalResp(_cbString, _dev.model))
+ goto failure;
+ // get the sw version
+ sendFormated("AT+GMR\r\n");
+ if (RESP_OK != waitFinalResp(_cbString, _dev.ver))
+ goto failure;
+ // get the pseudo ESN or MEID
+ sendFormated("AT+GSN\r\n");
+ if (RESP_OK != waitFinalResp(_cbString, _dev.meid))
+ goto failure;
+#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())
+ goto failure;
+ _dev.lpm = LPM_ACTIVE;
+ }
+#endif
+ } else {
+ if ((_dev.dev == DEV_LISA_U200) || (_dev.dev == DEV_LEON_G200)) {
+ // enable the network identification feature
+ sendFormated("AT+UGPIOC=20,2\r\n");
+ if (RESP_OK != waitFinalResp())
+ goto failure;
+ } else if ((_dev.dev == DEV_SARA_U260) || (_dev.dev == DEV_SARA_U270) ||
+ (_dev.dev == DEV_SARA_G350)) {
+ // enable the network identification feature
+ sendFormated("AT+UGPIOC=16,2\r\n");
+ if (RESP_OK != waitFinalResp())
+ goto failure;
+ }
+ // 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))
+ goto failure;
+ // Enter PIN if needed
+ if (_dev.sim == SIM_PIN) {
+ if (!simpin) {
+ ERROR("SIM PIN not available\r\n");
+ goto failure;
+ }
+ sendFormated("AT+CPIN=%s\r\n", simpin);
+ if (RESP_OK != waitFinalResp(_cbCPIN, &_dev.sim))
+ goto failure;
+ } else if (_dev.sim != SIM_READY) {
+ wait_ms(1000);
+ }
+ }
+ if (_dev.sim != SIM_READY) {
+ if (_dev.sim == SIM_MISSING)
+ ERROR("SIM not inserted\r\n");
+ goto failure;
+ }
+ // get the manufacturer
+ sendFormated("AT+CGMI\r\n");
+ if (RESP_OK != waitFinalResp(_cbString, _dev.manu))
+ goto failure;
+ // get the model identification
+ sendFormated("AT+CGMM\r\n");
+ if (RESP_OK != waitFinalResp(_cbString, _dev.model))
+ goto failure;
+ // get the
+ sendFormated("AT+CGMR\r\n");
+ if (RESP_OK != waitFinalResp(_cbString, _dev.ver))
+ goto failure;
+ // 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))
+ goto failure;
+ // Returns the product serial number, IMEI (International Mobile Equipment Identity)
+ sendFormated("AT+CGSN\r\n");
+ if (RESP_OK != waitFinalResp(_cbString, _dev.imei))
+ goto failure;
+ // 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())
+ goto failure;
+ _dev.lpm = LPM_ACTIVE;
+ }
+ // enable the psd registration unsolicited result code
+ sendFormated("AT+CGREG=2\r\n");
+ if (RESP_OK != waitFinalResp())
+ goto failure;
+ }
+ // enable the network registration unsolicited result code
+ sendFormated("AT+CREG=%d\r\n", (_dev.dev == DEV_LISA_C200) ? 1 : 2);
+ if (RESP_OK != waitFinalResp())
+ goto failure;
+ // Setup SMS in text mode
+ sendFormated("AT+CMGF=1\r\n");
+ if (RESP_OK != waitFinalResp())
+ goto failure;
+ // setup new message indication
+ sendFormated("AT+CNMI=2,1\r\n");
+ if (RESP_OK != waitFinalResp())
+ goto failure;
+ // Request IMSI (International Mobile Subscriber Identification)
+ sendFormated("AT+CIMI\r\n");
+ if (RESP_OK != waitFinalResp(_cbString, _dev.imsi))
+ goto failure;
+ if (status)
+ memcpy(status, &_dev, sizeof(DevStatus));
+ UNLOCK();
+ return true;
+failure:
+ unlock();
+ return false;
+}
+
+bool MDMParser::powerOff(void)
+{
+ bool ok = false;
+ if (_init) {
+ LOCK();
+ INFO("Modem::powerOff\r\n");
+ sendFormated("AT+CPWROFF\r\n");
+ if (RESP_OK == waitFinalResp(NULL,NULL,120*1000)) {
+ _init = false;
+ ok = true;
+ }
+ UNLOCK();
+ }
+ return ok;
+}
+
+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;
+ else if (strstr(buf, "LISA-U200")) *dev = DEV_LISA_U200;
+ else if (strstr(buf, "LISA-C200")) *dev = DEV_LISA_C200;
+ else if (strstr(buf, "SARA-U260")) *dev = DEV_SARA_U260;
+ else if (strstr(buf, "SARA-U270")) *dev = DEV_SARA_U270;
+ else if (strstr(buf, "LEON-G200")) *dev = DEV_LEON_G200;
+ }
+ 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))
+ wait_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*/)
+{
+ bool ok = false;
+ LOCK();
+ memset(&_net, 0, sizeof(_net));
+ _net.lac = 0xFFFF;
+ _net.ci = 0xFFFFFFFF;
+ // check registration
+ sendFormated("AT+CREG?\r\n");
+ waitFinalResp(); // don't fail as service could be not subscribed
+ if (_dev.dev != DEV_LISA_C200) {
+ // check PSD registration
+ sendFormated("AT+CGREG?\r\n");
+ waitFinalResp(); // don't fail as service could be not subscribed
+ }
+ 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())
+ goto failure;
+ while (1) {
+ // get the Telephone number
+ sendFormated("AT$MDN?\r\n");
+ if (RESP_OK != waitFinalResp(_cbString, _net.num))
+ goto failure;
+ // 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");
+ goto failure;
+ }
+ 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");
+ goto failure;
+ }
+ 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");
+ wait_ms(120*1000);
+ }
+ }
+ // get the the Network access identifier string
+ char nai[64];
+ sendFormated("AT$QCMIPNAI?\r\n");
+ if (RESP_OK != waitFinalResp(_cbString, nai))
+ goto failure;
+ } else {
+ sendFormated("AT+COPS?\r\n");
+ if (RESP_OK != waitFinalResp(_cbCOPS, &_net))
+ goto failure;
+ // get the MSISDNs related to this subscriber
+ sendFormated("AT+CNUM\r\n");
+ if (RESP_OK != waitFinalResp(_cbCNUM, _net.num))
+ goto failure;
+ }
+ // get the signal strength indication
+ sendFormated("AT+CSQ\r\n");
+ if (RESP_OK != waitFinalResp(_cbCSQ, &_net))
+ goto failure;
+ }
+ if (status) {
+ memcpy(status, &_net, sizeof(NetStatus));
+ }
+ ok = REG_DONE(_net.csd) && REG_DONE(_net.psd);
+ UNLOCK();
+ return ok;
+failure:
+ unlock();
+ return false;
+}
+
+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*/)
+{
+ LOCK();
+ 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))
+ goto failure;
+ } else {
+ // check gprs attach status
+ sendFormated("AT+CGATT=1\r\n");
+ if (RESP_OK != waitFinalResp(NULL,NULL,3*60*1000))
+ goto failure;
+
+ // Check the profile
+ int a = 0;
+ bool force = true;
+ sendFormated("AT+UPSND=" PROFILE ",8\r\n");
+ if (RESP_OK != waitFinalResp(_cbUPSND, &a))
+ goto failure;
+ 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))
+ goto failure;
+ 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)
+ config = apnconfig(_dev.imsi);
+
+ // Set up the dynamic IP address assignment.
+ sendFormated("AT+UPSD=" PROFILE ",7,\"0.0.0.0\"\r\n");
+ if (RESP_OK != waitFinalResp())
+ goto failure;
+
+ do {
+ if (config) {
+ apn = _APN_GET(config);
+ username = _APN_GET(config);
+ password = _APN_GET(config);
+ TRACE("Testing APN Settings(\"%s\",\"%s\",\"%s\")\r\n", apn, username, password);
+ }
+ // Set up the APN
+ if (apn && *apn) {
+ sendFormated("AT+UPSD=" PROFILE ",1,\"%s\"\r\n", apn);
+ if (RESP_OK != waitFinalResp())
+ goto failure;
+ }
+ if (username && *username) {
+ sendFormated("AT+UPSD=" PROFILE ",2,\"%s\"\r\n", username);
+ if (RESP_OK != waitFinalResp())
+ goto failure;
+ }
+ if (password && *password) {
+ sendFormated("AT+UPSD=" PROFILE ",3,\"%s\"\r\n", password);
+ if (RESP_OK != waitFinalResp())
+ goto failure;
+ }
+ // 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())
+ goto failure;
+ // Activate the profile and make connection
+ sendFormated("AT+UPSDA=" PROFILE ",3\r\n");
+ if (RESP_OK == waitFinalResp(NULL,NULL,150*1000))
+ ok = true;
+ }
+ }
+ } while (!ok && config && *config); // maybe use next setting ?
+ if (!ok) {
+ ERROR("Your modem APN/password/username may be wrong\r\n");
+ goto failure;
+ }
+ }
+ //Get local IP address
+ sendFormated("AT+UPSND=" PROFILE ",0\r\n");
+ if (RESP_OK != waitFinalResp(_cbUPSND, &_ip))
+ goto failure;
+ }
+ UNLOCK();
+ return _ip;
+failure:
+ unlock();
+ return NOIP;
+}
+
+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_UNKNOWN) && ip) {
+ int a,b,c,d;
+ if (sscanf(buf, "\r\n" 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)
+{
+ bool ok = false;
+ LOCK();
+ INFO("Modem::disconnect\r\n");
+ if (_ip != NOIP) {
+ if (_dev.dev == DEV_LISA_C200) {
+ // There something to do here
+ _ip = NOIP;
+ ok = true;
+ } else {
+ sendFormated("AT+UPSDA=" PROFILE ",4\r\n");
+ if (RESP_OK != waitFinalResp()) {
+ _ip = NOIP;
+ ok = true;
+ }
+ }
+ }
+ UNLOCK();
+ return ok;
+}
+
+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 {
+ LOCK();
+ sendFormated("AT+UDNSRN=0,\"%s\"\r\n", host);
+ if (RESP_OK != waitFinalResp(_cbUDNSRN, &ip))
+ ip = NOIP;
+ UNLOCK();
+ }
+ return ip;
+}
+
+// ----------------------------------------------------------------
+// sockets
+
+int MDMParser::_cbUSOCR(int type, const char* buf, int len, int* handle)
+{
+ if ((type == TYPE_PLUS) && handle) {
+ // +USOCR: socket
+ if (sscanf(buf, "\r\n+USOCR: %d", handle) == 1)
+ /*nothing*/;
+ }
+ return WAIT;
+}
+
+int MDMParser::socketSocket(IpProtocol ipproto, int port)
+{
+ int socket;
+ LOCK();
+ // find an free socket
+ socket = _findSocket();
+ TRACE("socketSocket(%d)\r\n", ipproto);
+ if (socket != SOCKET_ERROR) {
+ if (ipproto == IPPROTO_UDP) {
+ // sending port can only be set on 2G/3G modules
+ if ((port != -1) && (_dev.dev != DEV_LISA_C200)) {
+ sendFormated("AT+USOCR=17,%d\r\n", port);
+ } else {
+ sendFormated("AT+USOCR=17\r\n");
+ }
+ } else /*(ipproto == IPPROTO_TCP)*/ {
+ sendFormated("AT+USOCR=6\r\n");
+ }
+ int handle = SOCKET_ERROR;
+ if ((RESP_OK == waitFinalResp(_cbUSOCR, &handle)) &&
+ (handle != SOCKET_ERROR)) {
+ TRACE("Socket %d: handle %d was created\r\n", socket, handle);
+ _sockets[socket].handle = handle;
+ _sockets[socket].timeout_ms = TIMEOUT_BLOCKING;
+ _sockets[socket].connected = false;
+ _sockets[socket].pending = 0;
+ }
+ else
+ socket = SOCKET_ERROR;
+ }
+ UNLOCK();
+ return socket;
+}
+
+bool MDMParser::socketConnect(int socket, const char * host, int port)
+{
+ IP ip = gethostbyname(host);
+ if (ip == NOIP)
+ return false;
+ // connect to socket
+ bool ok = false;
+ LOCK();
+ if (ISSOCKET(socket) && (!_sockets[socket].connected)) {
+ TRACE("socketConnect(%d,%s,%d)\r\n", socket,host,port);
+ sendFormated("AT+USOCO=%d,\"" IPSTR "\",%d\r\n", _sockets[socket].handle, IPNUM(ip), port);
+ if (RESP_OK == waitFinalResp())
+ ok = _sockets[socket].connected = true;
+ }
+ UNLOCK();
+ return ok;
+}
+
+bool MDMParser::socketIsConnected(int socket)
+{
+ bool ok = false;
+ LOCK();
+ ok = ISSOCKET(socket) && _sockets[socket].connected;
+ TRACE("socketIsConnected(%d) %s\r\n", socket, ok?"yes":"no");
+ UNLOCK();
+ return ok;
+}
+
+bool MDMParser::socketSetBlocking(int socket, int timeout_ms)
+{
+ bool ok = false;
+ LOCK();
+ TRACE("socketSetBlocking(%d,%d)\r\n", socket,timeout_ms);
+ if (ISSOCKET(socket)) {
+ _sockets[socket].timeout_ms = timeout_ms;
+ ok = true;
+ }
+ UNLOCK();
+ return ok;
+}
+
+bool MDMParser::socketClose(int socket)
+{
+ bool ok = false;
+ LOCK();
+ if (ISSOCKET(socket) && _sockets[socket].connected) {
+ TRACE("socketClose(%d)\r\n", socket);
+ sendFormated("AT+USOCL=%d\r\n", _sockets[socket].handle);
+ if (RESP_OK == waitFinalResp()) {
+ _sockets[socket].connected = false;
+ ok = true;
+ }
+ }
+ UNLOCK();
+ return ok;
+}
+
+bool MDMParser::socketFree(int socket)
+{
+ // make sure it is closed
+ socketClose(socket);
+ bool ok = true;
+ LOCK();
+ if (ISSOCKET(socket)) {
+ TRACE("socketFree(%d)\r\n", socket);
+ _sockets[socket].handle = SOCKET_ERROR;
+ _sockets[socket].timeout_ms = TIMEOUT_BLOCKING;
+ _sockets[socket].connected = false;
+ _sockets[socket].pending = 0;
+ ok = true;
+ }
+ UNLOCK();
+ return ok;
+}
+
+#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;
+ bool ok = false;
+ LOCK();
+ if (ISSOCKET(socket)) {
+ sendFormated("AT+USOWR=%d,%d\r\n",_sockets[socket].handle,blk);
+ if (RESP_PROMPT == waitFinalResp()) {
+ wait_ms(50);
+ send(buf, blk);
+ if (RESP_OK == waitFinalResp())
+ ok = true;
+ }
+ }
+ UNLOCK();
+ if (!ok)
+ 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;
+ bool ok = false;
+ LOCK();
+ if (ISSOCKET(socket)) {
+ sendFormated("AT+USOST=%d,\"" IPSTR "\",%d,%d\r\n",_sockets[socket].handle,IPNUM(ip),port,blk);
+ if (RESP_PROMPT == waitFinalResp()) {
+ wait_ms(50);
+ send(buf, blk);
+ if (RESP_OK == waitFinalResp())
+ ok = true;
+ }
+ }
+ UNLOCK();
+ if (!ok)
+ return SOCKET_ERROR;
+ buf += blk;
+ cnt -= blk;
+ }
+ return (len - cnt);
+}
+
+int MDMParser::socketReadable(int socket)
+{
+ int pending = SOCKET_ERROR;
+ LOCK();
+ if (ISSOCKET(socket) && _sockets[socket].connected) {
+ TRACE("socketReadable(%d)\r\n", socket);
+ // allow to receive unsolicited commands
+ waitFinalResp(NULL, NULL, 0);
+ if (_sockets[socket].connected)
+ pending = _sockets[socket].pending;
+ }
+ UNLOCK();
+ return 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);
+#ifdef MDM_DEBUG
+ memset(buf, '\0', len);
+#endif
+ Timer timer;
+ timer.start();
+ while (len) {
+ int blk = MAX_SIZE; // still need space for headers and unsolicited commands
+ if (len < blk) blk = len;
+ bool ok = false;
+ LOCK();
+ if (ISSOCKET(socket)) {
+ if (_sockets[socket].connected) {
+ if (_sockets[socket].pending < blk) {
+ blk = _sockets[socket].pending;
+ }
+ if (blk > 0) {
+ sendFormated("AT+USORD=%d,%d\r\n",_sockets[socket].handle, blk);
+ if (RESP_OK == waitFinalResp(_cbUSORD, buf)) {
+ _sockets[socket].pending -= blk;
+ len -= blk;
+ cnt += blk;
+ buf += blk;
+ ok = true;
+ }
+ } else if (!TIMEOUT(timer, _sockets[socket].timeout_ms)) {
+ ok = (WAIT == waitFinalResp(NULL,NULL,0)); // wait for URCs
+ } else {
+ len = 0;
+ ok = true;
+ }
+ } else {
+ len = 0;
+ ok = true;
+ }
+ }
+ UNLOCK();
+ if (!ok) {
+ TRACE("socketRecv: ERROR\r\n");
+ return SOCKET_ERROR;
+ }
+ }
+ TRACE("socketRecv: %d \"%*s\"\r\n", cnt, cnt, buf-cnt);
+ 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;
+ int r = sscanf(buf, "\r\n+USORF: %d,\"" IPSTR "\",%d,%d,",
+ &sk,&a,&b,&c,&d,&p,&sz);
+ if ((r == 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);
+#ifdef MDM_DEBUG
+ memset(buf, '\0', len);
+#endif
+ Timer timer;
+ timer.start();
+ while (len) {
+ int blk = MAX_SIZE; // still need space for headers and unsolicited commands
+ if (len < blk) blk = len;
+ bool ok = false;
+ LOCK();
+ if (ISSOCKET(socket)) {
+ if (_sockets[socket].pending < blk)
+ blk = _sockets[socket].pending;
+ if (blk > 0) {
+ sendFormated("AT+USORF=%d,%d\r\n",_sockets[socket].handle, blk);
+ USORFparam param;
+ param.buf = buf;
+ if (RESP_OK == waitFinalResp(_cbUSORF, ¶m)) {
+ _sockets[socket].pending -= blk;
+ *ip = param.ip;
+ *port = param.port;
+ len -= blk;
+ cnt += blk;
+ buf += blk;
+ len = 0; // done
+ ok = true;
+ }
+ } else if (!TIMEOUT(timer, _sockets[socket].timeout_ms)) {
+ ok = (WAIT == waitFinalResp(NULL,NULL,0)); // wait for URCs
+ } else {
+ len = 0; // no more data and socket closed or timed-out
+ ok = true;
+ }
+ }
+ UNLOCK();
+ if (!ok) {
+ TRACE("socketRecv: ERROR\r\n");
+ return SOCKET_ERROR;
+ }
+ }
+ timer.stop();
+ timer.reset();
+ TRACE("socketRecv: %d \"%*s\"\r\n", cnt, cnt, buf-cnt);
+ return cnt;
+}
+
+int MDMParser::_findSocket(int handle) {
+ for (int socket = 0; socket < NUMSOCKETS; socket ++) {
+ if (_sockets[socket].handle == handle)
+ return socket;
+ }
+ return SOCKET_ERROR;
+}
+
+// ----------------------------------------------------------------
+
+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*/) {
+ int ret = -1;
+ LOCK();
+ sendFormated("AT+CMGL=\"%s\"\r\n", stat);
+ CMGLparam param;
+ param.ix = ix;
+ param.num = num;
+ if (RESP_OK == waitFinalResp(_cbCMGL, ¶m))
+ ret = num - param.num;
+ UNLOCK();
+ return ret;
+}
+
+bool MDMParser::smsSend(const char* num, const char* buf)
+{
+ bool ok = false;
+ LOCK();
+ sendFormated("AT+CMGS=\"%s\"\r\n",num);
+ if (RESP_PROMPT == waitFinalResp(NULL,NULL,150*1000)) {
+ send(buf, strlen(buf));
+ const char ctrlZ = 0x1A;
+ send(&ctrlZ, sizeof(ctrlZ));
+ ok = (RESP_OK == waitFinalResp());
+ }
+ UNLOCK();
+ return ok;
+}
+
+bool MDMParser::smsDelete(int ix)
+{
+ bool ok = false;
+ LOCK();
+ sendFormated("AT+CMGD=%d\r\n",ix);
+ ok = (RESP_OK == waitFinalResp());
+ UNLOCK();
+ return ok;
+}
+
+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)
+{
+ bool ok = false;
+ LOCK();
+ CMGRparam param;
+ param.num = num;
+ param.buf = buf;
+ sendFormated("AT+CMGR=%d\r\n",ix);
+ ok = (RESP_OK == waitFinalResp(_cbCMGR, ¶m));
+ UNLOCK();
+ return ok;
+}
+
+// ----------------------------------------------------------------
+
+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)
+{
+ bool ok = false;
+ LOCK();
+ *buf = '\0';
+ if (_dev.dev != DEV_LISA_C200) {
+ sendFormated("AT+CUSD=1,\"%s\"\r\n",cmd);
+ ok = (RESP_OK == waitFinalResp(_cbCUSD, buf));
+ }
+ UNLOCK();
+ return ok;
+}
+
+// ----------------------------------------------------------------
+
+int MDMParser::_cbUDELFILE(int type, const char* buf, int len, void*)
+{
+ if ((type == TYPE_ERROR) && strstr(buf, "+CME ERROR: FILE NOT FOUND"))
+ return RESP_OK; // file does not exist, so all ok...
+ return WAIT;
+}
+
+bool MDMParser::delFile(const char* filename)
+{
+ bool ok = false;
+ LOCK();
+ sendFormated("AT+UDELFILE=\"%s\"\r\n", filename);
+ ok = (RESP_OK == waitFinalResp(_cbUDELFILE));
+ UNLOCK();
+ return ok;
+}
+
+int MDMParser::writeFile(const char* filename, const char* buf, int len)
+{
+ bool ok = false;
+ LOCK();
+ sendFormated("AT+UDWNFILE=\"%s\",%d\r\n", filename, len);
+ if (RESP_PROMPT == waitFinalResp()) {
+ send(buf, len);
+ ok = (RESP_OK == waitFinalResp());
+ }
+ UNLOCK();
+ return ok ? len : -1;
+}
+
+int MDMParser::readFile(const char* filename, char* buf, int len)
+{
+ URDFILEparam param;
+ param.filename = filename;
+ param.buf = buf;
+ param.sz = len;
+ param.len = 0;
+ LOCK();
+ sendFormated("AT+URDFILE=\"%s\"\r\n", filename, len);
+ if (RESP_OK != waitFinalResp(_cbURDFILE, ¶m))
+ param.len = -1;
+ UNLOCK();
+ 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 >= -1) && (level >= -1) &&
+ (_debugLevel <= 3) && (level <= 3)) {
+ _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", "SARA-U260", "SARA-U270", "LEON-G200" };
+ if (status->dev < sizeof(txtDev)/sizeof(*txtDev) && (status->dev != 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 != 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 != REG_UNKNOWN))
+ dprint(param, " CSD Registration: %s\r\n", txtReg[status->csd]);
+ if (status->psd < sizeof(txtReg)/sizeof(*txtReg) && (status->psd != 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 != 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
--- a/MDMAPN.h Tue Sep 12 14:17:31 2017 +0000
+++ b/MDMAPN.h Fri Sep 15 10:12:03 2017 +0000
@@ -60,7 +60,6 @@
{ /* T-Mobile */ "262-02,06", _APN("m2m.business",,) },
{ /* T-Mobile */ "262-01", _APN("internet.m2mportal.de", "m2m", "sim")
_APN("internet.telekom",,) },
-
// 222 Italy - IT
{ /* TIM */ "222-01", _APN("ibox.tim.it",,) },
{ /* Vodafone */ "222-10", _APN("web.omnitel.it",,) },
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/MDMAPN.h.orig Fri Sep 15 10:12:03 2017 +0000
@@ -0,0 +1,171 @@
+#pragma once
+
+/* ----------------------------------------------------------------
+ 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 to generate the APN string
+#define _APN(apn,username,password) apn "\0" username "\0" password "\0"
+
+//! helper to extract a field from the config string
+#define _APN_GET(cfg) \
+ *cfg ? cfg : ""; \
+ cfg += strlen(cfg) + 1
+
+//! APN lookup struct
+typedef struct {
+ const char* mccmnc; //!< mobile country code (MCC) and mobile network code MNC
+ const char* cfg; //!< APN configuartion string, use _APN macro to generate
+} APN_t;
+
+//! default APN settings used by many networks
+static const char* apndef = _APN(,,)
+ _APN("internet",,);
+
+/*! this is a list of special APNs for different network operators
+ There is no need to enter the default apn internet in the table;
+ apndef will be used if no entry matches.
+
+ The APN without username/password have to be listed first.
+*/
+static const APN_t apnlut[] = {
+// MCC Country
+// { /* Operator */ "MCC-MNC[,MNC]" _APN(APN,USERNAME,PASSWORD) },
+// MCC must be 3 digits
+// MNC must be either 2 or 3 digits
+// MCC must be separated by '-' from MNC, multiple MNC can be separated by ','
+
+// 232 Austria - AUT
+ { /* T-Mobile */ "232-03", _APN("m2m.business",,) },
+
+// 460 China - CN
+ { /* CN Mobile */"460-00", _APN("cmnet",,)
+ _APN("cmwap",,) },
+ { /* Unicom */ "460-01", _APN("3gnet",,)
+ _APN("uninet","uninet","uninet") },
+
+// 262 Germany - DE
+// { /* T-Mobile */ "262-01", _APN("internet.t-mobile","t-mobile","tm") },
+ { /* T-Mobile */ "262-02,06", _APN("m2m.business",,) },
+ { /* T-Mobile */ "262-01", _APN("internet.m2mportal.de", "m2m", "sim")
+ _APN("internet.telekom",,) },
+
+// 222 Italy - IT
+ { /* TIM */ "222-01", _APN("ibox.tim.it",,) },
+ { /* Vodafone */ "222-10", _APN("web.omnitel.it",,) },
+ { /* Wind */ "222-88", _APN("internet.wind.biz",,) },
+
+// 440 Japan - JP
+ { /* Softbank */ "440-04,06,20,40,41,42,43,44,45,46,47,48,90,91,92,93,94,95"
+ ",96,97,98"
+ _APN("open.softbank.ne.jp","opensoftbank","ebMNuX1FIHg9d3DA")
+ _APN("smile.world","dna1trop","so2t3k3m2a") },
+ { /* NTTDoCoMo */"440-09,10,11,12,13,14,15,16,17,18,19,21,22,23,24,25,26,27,"
+ "28,29,30,31,32,33,34,35,36,37,38,39,58,59,60,61,62,63,"
+ "64,65,66,67,68,69,87,99",
+ _APN("bmobilewap",,) /*BMobile*/
+ _APN("mpr2.bizho.net","Mopera U",) /* DoCoMo */
+ _APN("3g-uno.ntt.com",,) /* DoCoMo */
+ _APN("3gd-ocn.ntt.com",,) /* DoCoMo */
+ _APN("nttcom.fr",,) /* DoCoMo */
+ _APN("bmobile.ne.jp","bmobile@wifi2","bmobile") /*BMobile*/ },
+
+// 204 Netherlands - NL
+ { /* Vodafone */ "204-04", _APN("public4.m2minternet.com",,)
+ _APN("test.teleena.com",,) },
+ { /* Teleena */ "204-07", _APN("test.teleena.com",,) },
+
+// 293 Slovenia - SI
+ { /* Si.mobil */ "293-40", _APN("internet.simobil.si",,) },
+ { /* Tusmobil */ "293-70", _APN("internet.tusmobil.si",,) },
+
+// 240 Sweden SE
+ { /* Telia */ "240-01", _APN("online.telia.se",,) },
+ { /* Telenor */ "240-06,08", _APN("services.telenor.se",,) },
+ { /* Tele2 */ "240-07", _APN("m2m.tele2.com",,)
+ _APN("mobileinternet.tele2.se",,) },
+
+// 228 Switzerland - CH
+ { /* Swisscom */ "228-01", _APN("swisscom-test.m2m.ch",,)
+ _APN("gprs.swisscom.ch",,) },
+ { /* Orange */ "228-03", _APN("internet",,) /* contract */
+ _APN("click",,) /* pre-pay */ },
+
+// 270 Luxembourg
+ { /* Post */ "270-01", _APN("postm2m.lu",,)},
+
+// 234 United Kingdom - GB
+ { /* O2 */ "234-02,10,11",
+ _APN("mobile.o2.co.uk","faster","web") /* contract */
+ _APN("mobile.o2.co.uk","bypass","web") /* pre-pay */
+ _APN("payandgo.o2.co.uk","payandgo","payandgo") },
+ { /* Vodafone */ "234-15", _APN("internet","web","web") /* contract */
+ _APN("pp.vodafone.co.uk","wap","wap") /* pre-pay */ },
+ { /* Three */ "234-20", _APN("three.co.uk",,) },
+ { /* EE */ "234-33,30", _APN("EEm2m",,)
+ _APN("m2mdata",,) },
+
+// 310 United States of America - US
+ { /* Kore Telematics */ "505-02", _APN("od1.korem2m.com",,) },
+ { /* T-Mobile */ "310-026,260,490",
+ _APN("epc.tmobile.com",,)
+ _APN("fast.tmobile.com",,) /* LTE */ },
+ { /* AT&T */ "310-030,150,170,260,410,560,680",
+ _APN("phone",,)
+ _APN("wap.cingular","WAP@CINGULARGPRS.COM","CINGULAR1")
+ _APN("isp.cingular","ISP@CINGULARGPRS.COM","CINGULAR1") },
+
+// 602 UAE
+ { /* Etisalat */ "602-602", _APN("internet.etisalat",,)},
+
+// 732 Colombia
+ { /* Comcel */ "732-101", _APN("internet.comcel.com.co",,)
+ _APN("internet.movistar.com.co",,) },
+
+// 405 India
+ { /* Reliance */ "405-21", _APN("unlimitiot",,)
+ _APN("IoTtest",,) },
+
+// 901 International - INT
+ { /* Transatel */ "901-37", _APN("netgprs.com","tsl","tsl") },
+};
+
+inline const char* apnconfig(const char* imsi)
+{
+ const char* config = NULL;
+ if (imsi && *imsi) {
+ // 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) && !config; i ++) {
+ const char* p = apnlut[i].mccmnc;
+ // check the MCC
+ if ((0 == memcmp(imsi, p, 3))) {
+ p += 3;
+ // check all the MNC, MNC length can be 2 or 3 digits
+ while (((p[0] == '-') || (p[0] == ',')) &&
+ (p[1] >= '0') && (p[1] <= '9') &&
+ (p[2] >= '0') && (p[2] <= '9') && !config) {
+ int l = ((p[3] >= '0') && (p[3] <= '9')) ? 3 : 2;
+ if (0 == memcmp(imsi+3,p+1,l))
+ config = apnlut[i].cfg;
+ p += 1 + l;
+ }
+ }
+ }
+ }
+ // use default if not found
+ if (!config)
+ config = apndef;
+ return config;
+}
\ No newline at end of file
